blob: bace19a2be3c2d961fd459981b4d6e4615f263f0 [file] [log] [blame]
Steve Dowerf70fdd22015-04-14 18:34:04 -04001import argparse
Steve Dower08b18172015-08-04 16:02:40 -07002import py_compile
Steve Dowerf70fdd22015-04-14 18:34:04 -04003import re
4import sys
5import shutil
6import os
7import tempfile
8
9from pathlib import Path
10from zipfile import ZipFile, ZIP_DEFLATED
11import subprocess
12
13TKTCL_RE = re.compile(r'^(_?tk|tcl).+\.(pyd|dll)', re.IGNORECASE)
14DEBUG_RE = re.compile(r'_d\.(pyd|dll|exe)$', re.IGNORECASE)
15PYTHON_DLL_RE = re.compile(r'python\d\d?\.dll$', re.IGNORECASE)
16
17def is_not_debug(p):
Steve Dower6b4c63d2015-05-02 15:32:14 -070018 if DEBUG_RE.search(p.name):
19 return False
20
21 if TKTCL_RE.search(p.name):
22 return False
23
24 return p.name.lower() not in {
25 '_ctypes_test.pyd',
26 '_testbuffer.pyd',
27 '_testcapi.pyd',
28 '_testimportmultiple.pyd',
Steve Dower38050192015-05-23 18:08:55 -070029 '_testmultiphase.pyd',
Steve Dower6b4c63d2015-05-02 15:32:14 -070030 'xxlimited.pyd',
31 }
Steve Dowerf70fdd22015-04-14 18:34:04 -040032
33def is_not_debug_or_python(p):
34 return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name)
35
36def include_in_lib(p):
37 name = p.name.lower()
38 if p.is_dir():
39 if name in {'__pycache__', 'ensurepip', 'idlelib', 'pydoc_data', 'tkinter', 'turtledemo'}:
40 return False
41 if name.startswith('plat-'):
42 return False
43 if name == 'test' and p.parts[-2].lower() == 'lib':
44 return False
45 return True
46
Steve Dower777af302015-04-19 19:50:35 -070047 suffix = p.suffix.lower()
Steve Dower777af302015-04-19 19:50:35 -070048 return suffix not in {'.pyc', '.pyo'}
Steve Dowerf70fdd22015-04-14 18:34:04 -040049
50def include_in_tools(p):
51 if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}:
52 return True
53
54 return p.suffix.lower() in {'.py', '.pyw', '.txt'}
55
56FULL_LAYOUT = [
57 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
58 ('/', 'PCBuild/$arch', 'python*.dll', is_not_debug),
59 ('DLLs/', 'PCBuild/$arch', '*.pyd', is_not_debug),
60 ('DLLs/', 'PCBuild/$arch', '*.dll', is_not_debug),
61 ('include/', 'include', '*.h', None),
62 ('include/', 'PC', 'pyconfig.h', None),
63 ('Lib/', 'Lib', '**/*', include_in_lib),
64 ('Tools/', 'Tools', '**/*', include_in_tools),
65]
66
67if os.getenv('DOC_FILENAME'):
68 FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None))
69
70EMBED_LAYOUT = [
71 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
72 ('/', 'PCBuild/$arch', '*.pyd', is_not_debug),
73 ('/', 'PCBuild/$arch', '*.dll', is_not_debug),
74 ('python35.zip', 'Lib', '**/*', include_in_lib),
75]
76
Steve Dower8c1cee92015-05-02 21:38:26 -070077def copy_to_layout(target, rel_sources):
Steve Dowerf70fdd22015-04-14 18:34:04 -040078 count = 0
79
80 if target.suffix.lower() == '.zip':
81 if target.exists():
82 target.unlink()
83
84 with ZipFile(str(target), 'w', ZIP_DEFLATED) as f:
85 for s, rel in rel_sources:
Steve Dower08b18172015-08-04 16:02:40 -070086 if rel.suffix.lower() == '.py':
87 pyc = Path(tempfile.gettempdir()) / rel.with_suffix('.pyc').name
88 try:
89 py_compile.compile(str(s), str(pyc), str(rel), doraise=True, optimize=2)
90 except py_compile.PyCompileError:
91 f.write(str(s), str(rel))
92 else:
93 f.write(str(pyc), str(rel.with_suffix('.pyc')))
94 else:
95 f.write(str(s), str(rel))
Steve Dowerf70fdd22015-04-14 18:34:04 -040096 count += 1
97
98 else:
99 for s, rel in rel_sources:
100 try:
101 (target / rel).parent.mkdir(parents=True)
102 except FileExistsError:
103 pass
104 shutil.copy(str(s), str(target / rel))
105 count += 1
106
107 return count
108
109def rglob(root, pattern, condition):
110 dirs = [root]
111 recurse = pattern[:3] in {'**/', '**\\'}
112 while dirs:
113 d = dirs.pop(0)
114 for f in d.glob(pattern[3:] if recurse else pattern):
115 if recurse and f.is_dir() and (not condition or condition(f)):
116 dirs.append(f)
117 elif f.is_file() and (not condition or condition(f)):
118 yield f, f.relative_to(root)
119
120def main():
121 parser = argparse.ArgumentParser()
122 parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path)
123 parser.add_argument('-o', '--out', metavar='file', help='The name of the output self-extracting archive', type=Path, required=True)
124 parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None)
125 parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False)
126 parser.add_argument('-a', '--arch', help='Specify the architecture to use (win32/amd64)', type=str, default="win32")
Steve Dowerf70fdd22015-04-14 18:34:04 -0400127 ns = parser.parse_args()
128
129 source = ns.source or (Path(__file__).parent.parent.parent)
130 out = ns.out
131 arch = ns.arch
Steve Dowerf70fdd22015-04-14 18:34:04 -0400132 assert isinstance(source, Path)
133 assert isinstance(out, Path)
134 assert isinstance(arch, str)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400135
136 if ns.temp:
137 temp = ns.temp
138 delete_temp = False
139 else:
140 temp = Path(tempfile.mkdtemp())
141 delete_temp = True
142
143 try:
144 out.parent.mkdir(parents=True)
145 except FileExistsError:
146 pass
147 try:
148 temp.mkdir(parents=True)
149 except FileExistsError:
150 pass
151
152 layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT
153
154 try:
155 for t, s, p, c in layout:
156 s = source / s.replace("$arch", arch)
Steve Dower8c1cee92015-05-02 21:38:26 -0700157 copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400158 print('Copied {} files'.format(copied))
159
Steve Dower4a7fe7e2015-05-22 15:10:10 -0700160 with open(str(temp / 'pyvenv.cfg'), 'w') as f:
161 print('applocal = true', file=f)
162
Steve Dower8c1cee92015-05-02 21:38:26 -0700163 total = copy_to_layout(out, rglob(temp, '*', None))
164 print('Wrote {} files to {}'.format(total, out))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400165 finally:
166 if delete_temp:
167 shutil.rmtree(temp, True)
168
169
170if __name__ == "__main__":
171 sys.exit(int(main() or 0))