blob: c256008acaa6dc514c3a9d70956c4a5baf55f13c [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
18def is_not_debug(p):
Steve Dower6b4c63d2015-05-02 15:32:14 -070019 if DEBUG_RE.search(p.name):
20 return False
21
22 if TKTCL_RE.search(p.name):
23 return False
24
25 return p.name.lower() not in {
26 '_ctypes_test.pyd',
27 '_testbuffer.pyd',
28 '_testcapi.pyd',
29 '_testimportmultiple.pyd',
Steve Dower38050192015-05-23 18:08:55 -070030 '_testmultiphase.pyd',
Steve Dower6b4c63d2015-05-02 15:32:14 -070031 'xxlimited.pyd',
32 }
Steve Dowerf70fdd22015-04-14 18:34:04 -040033
34def is_not_debug_or_python(p):
35 return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name)
36
37def include_in_lib(p):
38 name = p.name.lower()
39 if p.is_dir():
40 if name in {'__pycache__', 'ensurepip', 'idlelib', 'pydoc_data', 'tkinter', 'turtledemo'}:
41 return False
42 if name.startswith('plat-'):
43 return False
44 if name == 'test' and p.parts[-2].lower() == 'lib':
45 return False
46 return True
47
Steve Dower777af302015-04-19 19:50:35 -070048 suffix = p.suffix.lower()
Steve Dower777af302015-04-19 19:50:35 -070049 return suffix not in {'.pyc', '.pyo'}
Steve Dowerf70fdd22015-04-14 18:34:04 -040050
51def include_in_tools(p):
52 if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}:
53 return True
54
55 return p.suffix.lower() in {'.py', '.pyw', '.txt'}
56
57FULL_LAYOUT = [
58 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
59 ('/', 'PCBuild/$arch', 'python*.dll', is_not_debug),
60 ('DLLs/', 'PCBuild/$arch', '*.pyd', is_not_debug),
61 ('DLLs/', 'PCBuild/$arch', '*.dll', is_not_debug),
62 ('include/', 'include', '*.h', None),
63 ('include/', 'PC', 'pyconfig.h', None),
64 ('Lib/', 'Lib', '**/*', include_in_lib),
65 ('Tools/', 'Tools', '**/*', include_in_tools),
66]
67
Steve Dowerf70fdd22015-04-14 18:34:04 -040068EMBED_LAYOUT = [
69 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
70 ('/', 'PCBuild/$arch', '*.pyd', is_not_debug),
71 ('/', 'PCBuild/$arch', '*.dll', is_not_debug),
72 ('python35.zip', 'Lib', '**/*', include_in_lib),
73]
74
Steve Dowerfcbe1df2015-09-08 21:39:01 -070075if os.getenv('DOC_FILENAME'):
76 FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None))
77if os.getenv('VCREDIST_PATH'):
78 FULL_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None))
79 EMBED_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None))
80
Steve Dower8c1cee92015-05-02 21:38:26 -070081def copy_to_layout(target, rel_sources):
Steve Dowerf70fdd22015-04-14 18:34:04 -040082 count = 0
83
84 if target.suffix.lower() == '.zip':
85 if target.exists():
86 target.unlink()
87
88 with ZipFile(str(target), 'w', ZIP_DEFLATED) as f:
Steve Dower315b7482015-08-05 11:34:50 -070089 with tempfile.TemporaryDirectory() as tmpdir:
90 for s, rel in rel_sources:
91 if rel.suffix.lower() == '.py':
92 pyc = Path(tmpdir) / rel.with_suffix('.pyc').name
93 try:
94 py_compile.compile(str(s), str(pyc), str(rel), doraise=True, optimize=2)
95 except py_compile.PyCompileError:
96 f.write(str(s), str(rel))
97 else:
98 f.write(str(pyc), str(rel.with_suffix('.pyc')))
Steve Dower08b18172015-08-04 16:02:40 -070099 else:
Steve Dower315b7482015-08-05 11:34:50 -0700100 f.write(str(s), str(rel))
101 count += 1
Steve Dowerf70fdd22015-04-14 18:34:04 -0400102
103 else:
104 for s, rel in rel_sources:
Steve Dowerae69de62015-09-09 19:32:45 -0700105 dest = target / rel
Steve Dowerf70fdd22015-04-14 18:34:04 -0400106 try:
Steve Dowerae69de62015-09-09 19:32:45 -0700107 dest.parent.mkdir(parents=True)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400108 except FileExistsError:
109 pass
Steve Dowerae69de62015-09-09 19:32:45 -0700110 if dest.is_file():
111 dest.chmod(stat.S_IWRITE)
112 shutil.copy(str(s), str(dest))
113 if dest.is_file():
114 dest.chmod(stat.S_IWRITE)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400115 count += 1
116
117 return count
118
119def rglob(root, pattern, condition):
120 dirs = [root]
121 recurse = pattern[:3] in {'**/', '**\\'}
122 while dirs:
123 d = dirs.pop(0)
124 for f in d.glob(pattern[3:] if recurse else pattern):
125 if recurse and f.is_dir() and (not condition or condition(f)):
126 dirs.append(f)
127 elif f.is_file() and (not condition or condition(f)):
128 yield f, f.relative_to(root)
129
130def main():
131 parser = argparse.ArgumentParser()
132 parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path)
133 parser.add_argument('-o', '--out', metavar='file', help='The name of the output self-extracting archive', type=Path, required=True)
134 parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None)
135 parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False)
136 parser.add_argument('-a', '--arch', help='Specify the architecture to use (win32/amd64)', type=str, default="win32")
Steve Dowerf70fdd22015-04-14 18:34:04 -0400137 ns = parser.parse_args()
138
139 source = ns.source or (Path(__file__).parent.parent.parent)
140 out = ns.out
141 arch = ns.arch
Steve Dowerf70fdd22015-04-14 18:34:04 -0400142 assert isinstance(source, Path)
143 assert isinstance(out, Path)
144 assert isinstance(arch, str)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400145
146 if ns.temp:
147 temp = ns.temp
148 delete_temp = False
149 else:
150 temp = Path(tempfile.mkdtemp())
151 delete_temp = True
152
153 try:
154 out.parent.mkdir(parents=True)
155 except FileExistsError:
156 pass
157 try:
158 temp.mkdir(parents=True)
159 except FileExistsError:
160 pass
161
162 layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT
163
164 try:
165 for t, s, p, c in layout:
166 s = source / s.replace("$arch", arch)
Steve Dower8c1cee92015-05-02 21:38:26 -0700167 copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400168 print('Copied {} files'.format(copied))
169
Steve Dower4a7fe7e2015-05-22 15:10:10 -0700170 with open(str(temp / 'pyvenv.cfg'), 'w') as f:
171 print('applocal = true', file=f)
172
Steve Dower8c1cee92015-05-02 21:38:26 -0700173 total = copy_to_layout(out, rglob(temp, '*', None))
174 print('Wrote {} files to {}'.format(total, out))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400175 finally:
176 if delete_temp:
177 shutil.rmtree(temp, True)
178
179
180if __name__ == "__main__":
181 sys.exit(int(main() or 0))