blob: 09ee49166d98926a65547b487bc875c351c4c370 [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
Steve Dowerf70fdd22015-04-14 18:34:04 -040067EMBED_LAYOUT = [
68 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
69 ('/', 'PCBuild/$arch', '*.pyd', is_not_debug),
70 ('/', 'PCBuild/$arch', '*.dll', is_not_debug),
71 ('python35.zip', 'Lib', '**/*', include_in_lib),
72]
73
Steve Dowerfcbe1df2015-09-08 21:39:01 -070074if os.getenv('DOC_FILENAME'):
75 FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None))
76if os.getenv('VCREDIST_PATH'):
77 FULL_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None))
78 EMBED_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None))
79
Steve Dower8c1cee92015-05-02 21:38:26 -070080def copy_to_layout(target, rel_sources):
Steve Dowerf70fdd22015-04-14 18:34:04 -040081 count = 0
82
83 if target.suffix.lower() == '.zip':
84 if target.exists():
85 target.unlink()
86
87 with ZipFile(str(target), 'w', ZIP_DEFLATED) as f:
Steve Dower315b7482015-08-05 11:34:50 -070088 with tempfile.TemporaryDirectory() as tmpdir:
89 for s, rel in rel_sources:
90 if rel.suffix.lower() == '.py':
91 pyc = Path(tmpdir) / rel.with_suffix('.pyc').name
92 try:
93 py_compile.compile(str(s), str(pyc), str(rel), doraise=True, optimize=2)
94 except py_compile.PyCompileError:
95 f.write(str(s), str(rel))
96 else:
97 f.write(str(pyc), str(rel.with_suffix('.pyc')))
Steve Dower08b18172015-08-04 16:02:40 -070098 else:
Steve Dower315b7482015-08-05 11:34:50 -070099 f.write(str(s), str(rel))
100 count += 1
Steve Dowerf70fdd22015-04-14 18:34:04 -0400101
102 else:
103 for s, rel in rel_sources:
104 try:
105 (target / rel).parent.mkdir(parents=True)
106 except FileExistsError:
107 pass
108 shutil.copy(str(s), str(target / rel))
109 count += 1
110
111 return count
112
113def rglob(root, pattern, condition):
114 dirs = [root]
115 recurse = pattern[:3] in {'**/', '**\\'}
116 while dirs:
117 d = dirs.pop(0)
118 for f in d.glob(pattern[3:] if recurse else pattern):
119 if recurse and f.is_dir() and (not condition or condition(f)):
120 dirs.append(f)
121 elif f.is_file() and (not condition or condition(f)):
122 yield f, f.relative_to(root)
123
124def main():
125 parser = argparse.ArgumentParser()
126 parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path)
127 parser.add_argument('-o', '--out', metavar='file', help='The name of the output self-extracting archive', type=Path, required=True)
128 parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None)
129 parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False)
130 parser.add_argument('-a', '--arch', help='Specify the architecture to use (win32/amd64)', type=str, default="win32")
Steve Dowerf70fdd22015-04-14 18:34:04 -0400131 ns = parser.parse_args()
132
133 source = ns.source or (Path(__file__).parent.parent.parent)
134 out = ns.out
135 arch = ns.arch
Steve Dowerf70fdd22015-04-14 18:34:04 -0400136 assert isinstance(source, Path)
137 assert isinstance(out, Path)
138 assert isinstance(arch, str)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400139
140 if ns.temp:
141 temp = ns.temp
142 delete_temp = False
143 else:
144 temp = Path(tempfile.mkdtemp())
145 delete_temp = True
146
147 try:
148 out.parent.mkdir(parents=True)
149 except FileExistsError:
150 pass
151 try:
152 temp.mkdir(parents=True)
153 except FileExistsError:
154 pass
155
156 layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT
157
158 try:
159 for t, s, p, c in layout:
160 s = source / s.replace("$arch", arch)
Steve Dower8c1cee92015-05-02 21:38:26 -0700161 copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400162 print('Copied {} files'.format(copied))
163
Steve Dower4a7fe7e2015-05-22 15:10:10 -0700164 with open(str(temp / 'pyvenv.cfg'), 'w') as f:
165 print('applocal = true', file=f)
166
Steve Dower8c1cee92015-05-02 21:38:26 -0700167 total = copy_to_layout(out, rglob(temp, '*', None))
168 print('Wrote {} files to {}'.format(total, out))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400169 finally:
170 if delete_temp:
171 shutil.rmtree(temp, True)
172
173
174if __name__ == "__main__":
175 sys.exit(int(main() or 0))