blob: 96fdad2197b366335574726fadc3d4e41aad8269 [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
Steve Dowerae69de62015-09-09 19:32:45 -07006import stat
Steve Dowerf70fdd22015-04-14 18:34:04 -04007import os
8import tempfile
9
10from pathlib import Path
11from zipfile import ZipFile, ZIP_DEFLATED
12import subprocess
13
14TKTCL_RE = re.compile(r'^(_?tk|tcl).+\.(pyd|dll)', re.IGNORECASE)
15DEBUG_RE = re.compile(r'_d\.(pyd|dll|exe)$', re.IGNORECASE)
16PYTHON_DLL_RE = re.compile(r'python\d\d?\.dll$', re.IGNORECASE)
17
Steve Dower2495faf2015-09-22 15:03:54 -070018EXCLUDE_FROM_LIBRARY = {
19 '__pycache__',
20 'ensurepip',
21 'idlelib',
22 'pydoc_data',
23 'site-packages',
24 'tkinter',
25 'turtledemo',
26}
27
28EXCLUDE_FILE_FROM_LIBRARY = {
29 'bdist_wininst.py',
30}
31
Steve Dowerf70fdd22015-04-14 18:34:04 -040032def is_not_debug(p):
Steve Dower6b4c63d2015-05-02 15:32:14 -070033 if DEBUG_RE.search(p.name):
34 return False
35
36 if TKTCL_RE.search(p.name):
37 return False
38
39 return p.name.lower() not in {
40 '_ctypes_test.pyd',
41 '_testbuffer.pyd',
42 '_testcapi.pyd',
43 '_testimportmultiple.pyd',
Steve Dower38050192015-05-23 18:08:55 -070044 '_testmultiphase.pyd',
Steve Dower6b4c63d2015-05-02 15:32:14 -070045 'xxlimited.pyd',
46 }
Steve Dowerf70fdd22015-04-14 18:34:04 -040047
48def is_not_debug_or_python(p):
49 return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name)
50
51def include_in_lib(p):
52 name = p.name.lower()
53 if p.is_dir():
Steve Dower2495faf2015-09-22 15:03:54 -070054 if name in EXCLUDE_FROM_LIBRARY:
Steve Dowerf70fdd22015-04-14 18:34:04 -040055 return False
56 if name.startswith('plat-'):
57 return False
58 if name == 'test' and p.parts[-2].lower() == 'lib':
59 return False
Steve Dower2495faf2015-09-22 15:03:54 -070060 if name in {'test', 'tests'} and p.parts[-3].lower() == 'lib':
61 return False
Steve Dowerf70fdd22015-04-14 18:34:04 -040062 return True
63
Steve Dower2495faf2015-09-22 15:03:54 -070064 if name in EXCLUDE_FILE_FROM_LIBRARY:
65 return False
66
Steve Dower777af302015-04-19 19:50:35 -070067 suffix = p.suffix.lower()
Steve Dower2495faf2015-09-22 15:03:54 -070068 return suffix not in {'.pyc', '.pyo', '.exe'}
Steve Dowerf70fdd22015-04-14 18:34:04 -040069
70def include_in_tools(p):
71 if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}:
72 return True
73
74 return p.suffix.lower() in {'.py', '.pyw', '.txt'}
75
76FULL_LAYOUT = [
77 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
78 ('/', 'PCBuild/$arch', 'python*.dll', is_not_debug),
79 ('DLLs/', 'PCBuild/$arch', '*.pyd', is_not_debug),
80 ('DLLs/', 'PCBuild/$arch', '*.dll', is_not_debug),
81 ('include/', 'include', '*.h', None),
82 ('include/', 'PC', 'pyconfig.h', None),
83 ('Lib/', 'Lib', '**/*', include_in_lib),
84 ('Tools/', 'Tools', '**/*', include_in_tools),
85]
86
Steve Dowerf70fdd22015-04-14 18:34:04 -040087EMBED_LAYOUT = [
88 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
89 ('/', 'PCBuild/$arch', '*.pyd', is_not_debug),
90 ('/', 'PCBuild/$arch', '*.dll', is_not_debug),
91 ('python35.zip', 'Lib', '**/*', include_in_lib),
92]
93
Steve Dowerfcbe1df2015-09-08 21:39:01 -070094if os.getenv('DOC_FILENAME'):
95 FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None))
96if os.getenv('VCREDIST_PATH'):
97 FULL_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None))
98 EMBED_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None))
99
Steve Dower8c1cee92015-05-02 21:38:26 -0700100def copy_to_layout(target, rel_sources):
Steve Dowerf70fdd22015-04-14 18:34:04 -0400101 count = 0
102
103 if target.suffix.lower() == '.zip':
104 if target.exists():
105 target.unlink()
106
107 with ZipFile(str(target), 'w', ZIP_DEFLATED) as f:
Steve Dower315b7482015-08-05 11:34:50 -0700108 with tempfile.TemporaryDirectory() as tmpdir:
109 for s, rel in rel_sources:
110 if rel.suffix.lower() == '.py':
111 pyc = Path(tmpdir) / rel.with_suffix('.pyc').name
112 try:
113 py_compile.compile(str(s), str(pyc), str(rel), doraise=True, optimize=2)
114 except py_compile.PyCompileError:
115 f.write(str(s), str(rel))
116 else:
117 f.write(str(pyc), str(rel.with_suffix('.pyc')))
Steve Dower08b18172015-08-04 16:02:40 -0700118 else:
Steve Dower315b7482015-08-05 11:34:50 -0700119 f.write(str(s), str(rel))
120 count += 1
Steve Dowerf70fdd22015-04-14 18:34:04 -0400121
122 else:
123 for s, rel in rel_sources:
Steve Dowerae69de62015-09-09 19:32:45 -0700124 dest = target / rel
Steve Dowerf70fdd22015-04-14 18:34:04 -0400125 try:
Steve Dowerae69de62015-09-09 19:32:45 -0700126 dest.parent.mkdir(parents=True)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400127 except FileExistsError:
128 pass
Steve Dowerae69de62015-09-09 19:32:45 -0700129 if dest.is_file():
130 dest.chmod(stat.S_IWRITE)
131 shutil.copy(str(s), str(dest))
132 if dest.is_file():
133 dest.chmod(stat.S_IWRITE)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400134 count += 1
135
136 return count
137
138def rglob(root, pattern, condition):
139 dirs = [root]
140 recurse = pattern[:3] in {'**/', '**\\'}
141 while dirs:
142 d = dirs.pop(0)
143 for f in d.glob(pattern[3:] if recurse else pattern):
144 if recurse and f.is_dir() and (not condition or condition(f)):
145 dirs.append(f)
146 elif f.is_file() and (not condition or condition(f)):
147 yield f, f.relative_to(root)
148
149def main():
150 parser = argparse.ArgumentParser()
151 parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path)
152 parser.add_argument('-o', '--out', metavar='file', help='The name of the output self-extracting archive', type=Path, required=True)
153 parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None)
154 parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False)
155 parser.add_argument('-a', '--arch', help='Specify the architecture to use (win32/amd64)', type=str, default="win32")
Steve Dowerf70fdd22015-04-14 18:34:04 -0400156 ns = parser.parse_args()
157
158 source = ns.source or (Path(__file__).parent.parent.parent)
159 out = ns.out
160 arch = ns.arch
Steve Dowerf70fdd22015-04-14 18:34:04 -0400161 assert isinstance(source, Path)
162 assert isinstance(out, Path)
163 assert isinstance(arch, str)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400164
165 if ns.temp:
166 temp = ns.temp
167 delete_temp = False
168 else:
169 temp = Path(tempfile.mkdtemp())
170 delete_temp = True
171
172 try:
173 out.parent.mkdir(parents=True)
174 except FileExistsError:
175 pass
176 try:
177 temp.mkdir(parents=True)
178 except FileExistsError:
179 pass
180
181 layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT
182
183 try:
184 for t, s, p, c in layout:
185 s = source / s.replace("$arch", arch)
Steve Dower8c1cee92015-05-02 21:38:26 -0700186 copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400187 print('Copied {} files'.format(copied))
188
Steve Dower4a7fe7e2015-05-22 15:10:10 -0700189 with open(str(temp / 'pyvenv.cfg'), 'w') as f:
190 print('applocal = true', file=f)
191
Steve Dower8c1cee92015-05-02 21:38:26 -0700192 total = copy_to_layout(out, rglob(temp, '*', None))
193 print('Wrote {} files to {}'.format(total, out))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400194 finally:
195 if delete_temp:
196 shutil.rmtree(temp, True)
197
198
199if __name__ == "__main__":
200 sys.exit(int(main() or 0))