blob: d49b5b48b3baa13af62063f2a213bc92dc7adfa7 [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',
Steve Dower10cabcb2016-01-16 13:44:43 -080026 'venv',
Steve Dower2495faf2015-09-22 15:03:54 -070027}
28
29EXCLUDE_FILE_FROM_LIBRARY = {
30 'bdist_wininst.py',
31}
32
Steve Dowerf70fdd22015-04-14 18:34:04 -040033def is_not_debug(p):
Steve Dower6b4c63d2015-05-02 15:32:14 -070034 if DEBUG_RE.search(p.name):
35 return False
36
37 if TKTCL_RE.search(p.name):
38 return False
39
40 return p.name.lower() not in {
41 '_ctypes_test.pyd',
42 '_testbuffer.pyd',
43 '_testcapi.pyd',
44 '_testimportmultiple.pyd',
Steve Dower38050192015-05-23 18:08:55 -070045 '_testmultiphase.pyd',
Steve Dower6b4c63d2015-05-02 15:32:14 -070046 'xxlimited.pyd',
47 }
Steve Dowerf70fdd22015-04-14 18:34:04 -040048
49def is_not_debug_or_python(p):
50 return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name)
51
52def include_in_lib(p):
53 name = p.name.lower()
54 if p.is_dir():
Steve Dower2495faf2015-09-22 15:03:54 -070055 if name in EXCLUDE_FROM_LIBRARY:
Steve Dowerf70fdd22015-04-14 18:34:04 -040056 return False
57 if name.startswith('plat-'):
58 return False
59 if name == 'test' and p.parts[-2].lower() == 'lib':
60 return False
Steve Dower2495faf2015-09-22 15:03:54 -070061 if name in {'test', 'tests'} and p.parts[-3].lower() == 'lib':
62 return False
Steve Dowerf70fdd22015-04-14 18:34:04 -040063 return True
64
Steve Dower2495faf2015-09-22 15:03:54 -070065 if name in EXCLUDE_FILE_FROM_LIBRARY:
66 return False
67
Steve Dower777af302015-04-19 19:50:35 -070068 suffix = p.suffix.lower()
Steve Dower2495faf2015-09-22 15:03:54 -070069 return suffix not in {'.pyc', '.pyo', '.exe'}
Steve Dowerf70fdd22015-04-14 18:34:04 -040070
71def include_in_tools(p):
72 if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}:
73 return True
74
75 return p.suffix.lower() in {'.py', '.pyw', '.txt'}
76
77FULL_LAYOUT = [
78 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
79 ('/', 'PCBuild/$arch', 'python*.dll', is_not_debug),
80 ('DLLs/', 'PCBuild/$arch', '*.pyd', is_not_debug),
81 ('DLLs/', 'PCBuild/$arch', '*.dll', is_not_debug),
82 ('include/', 'include', '*.h', None),
83 ('include/', 'PC', 'pyconfig.h', None),
84 ('Lib/', 'Lib', '**/*', include_in_lib),
85 ('Tools/', 'Tools', '**/*', include_in_tools),
86]
87
Steve Dowerf70fdd22015-04-14 18:34:04 -040088EMBED_LAYOUT = [
89 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
90 ('/', 'PCBuild/$arch', '*.pyd', is_not_debug),
91 ('/', 'PCBuild/$arch', '*.dll', is_not_debug),
92 ('python35.zip', 'Lib', '**/*', include_in_lib),
93]
94
Steve Dowerfcbe1df2015-09-08 21:39:01 -070095if os.getenv('DOC_FILENAME'):
96 FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None))
97if os.getenv('VCREDIST_PATH'):
98 FULL_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None))
99 EMBED_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None))
100
Steve Dower8c1cee92015-05-02 21:38:26 -0700101def copy_to_layout(target, rel_sources):
Steve Dowerf70fdd22015-04-14 18:34:04 -0400102 count = 0
103
104 if target.suffix.lower() == '.zip':
105 if target.exists():
106 target.unlink()
107
108 with ZipFile(str(target), 'w', ZIP_DEFLATED) as f:
Steve Dower315b7482015-08-05 11:34:50 -0700109 with tempfile.TemporaryDirectory() as tmpdir:
110 for s, rel in rel_sources:
111 if rel.suffix.lower() == '.py':
112 pyc = Path(tmpdir) / rel.with_suffix('.pyc').name
113 try:
114 py_compile.compile(str(s), str(pyc), str(rel), doraise=True, optimize=2)
115 except py_compile.PyCompileError:
116 f.write(str(s), str(rel))
117 else:
118 f.write(str(pyc), str(rel.with_suffix('.pyc')))
Steve Dower08b18172015-08-04 16:02:40 -0700119 else:
Steve Dower315b7482015-08-05 11:34:50 -0700120 f.write(str(s), str(rel))
121 count += 1
Steve Dowerf70fdd22015-04-14 18:34:04 -0400122
123 else:
124 for s, rel in rel_sources:
Steve Dowerae69de62015-09-09 19:32:45 -0700125 dest = target / rel
Steve Dowerf70fdd22015-04-14 18:34:04 -0400126 try:
Steve Dowerae69de62015-09-09 19:32:45 -0700127 dest.parent.mkdir(parents=True)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400128 except FileExistsError:
129 pass
Steve Dowerae69de62015-09-09 19:32:45 -0700130 if dest.is_file():
131 dest.chmod(stat.S_IWRITE)
132 shutil.copy(str(s), str(dest))
133 if dest.is_file():
134 dest.chmod(stat.S_IWRITE)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400135 count += 1
136
137 return count
138
139def rglob(root, pattern, condition):
140 dirs = [root]
141 recurse = pattern[:3] in {'**/', '**\\'}
142 while dirs:
143 d = dirs.pop(0)
144 for f in d.glob(pattern[3:] if recurse else pattern):
145 if recurse and f.is_dir() and (not condition or condition(f)):
146 dirs.append(f)
147 elif f.is_file() and (not condition or condition(f)):
148 yield f, f.relative_to(root)
149
150def main():
151 parser = argparse.ArgumentParser()
152 parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path)
153 parser.add_argument('-o', '--out', metavar='file', help='The name of the output self-extracting archive', type=Path, required=True)
154 parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None)
155 parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False)
156 parser.add_argument('-a', '--arch', help='Specify the architecture to use (win32/amd64)', type=str, default="win32")
Steve Dowerf70fdd22015-04-14 18:34:04 -0400157 ns = parser.parse_args()
158
159 source = ns.source or (Path(__file__).parent.parent.parent)
160 out = ns.out
161 arch = ns.arch
Steve Dowerf70fdd22015-04-14 18:34:04 -0400162 assert isinstance(source, Path)
163 assert isinstance(out, Path)
164 assert isinstance(arch, str)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400165
166 if ns.temp:
167 temp = ns.temp
168 delete_temp = False
169 else:
170 temp = Path(tempfile.mkdtemp())
171 delete_temp = True
172
173 try:
174 out.parent.mkdir(parents=True)
175 except FileExistsError:
176 pass
177 try:
178 temp.mkdir(parents=True)
179 except FileExistsError:
180 pass
181
182 layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT
183
184 try:
185 for t, s, p, c in layout:
186 s = source / s.replace("$arch", arch)
Steve Dower8c1cee92015-05-02 21:38:26 -0700187 copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400188 print('Copied {} files'.format(copied))
189
Steve Dower4a7fe7e2015-05-22 15:10:10 -0700190 with open(str(temp / 'pyvenv.cfg'), 'w') as f:
191 print('applocal = true', file=f)
192
Steve Dower8c1cee92015-05-02 21:38:26 -0700193 total = copy_to_layout(out, rglob(temp, '*', None))
194 print('Wrote {} files to {}'.format(total, out))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400195 finally:
196 if delete_temp:
197 shutil.rmtree(temp, True)
198
199
200if __name__ == "__main__":
201 sys.exit(int(main() or 0))