blob: ba711c484a9b58e3c847f05d4dd9a9566e1cb4eb [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:
Steve Dower315b7482015-08-05 11:34:50 -070085 with tempfile.TemporaryDirectory() as tmpdir:
86 for s, rel in rel_sources:
87 if rel.suffix.lower() == '.py':
88 pyc = Path(tmpdir) / rel.with_suffix('.pyc').name
89 try:
90 py_compile.compile(str(s), str(pyc), str(rel), doraise=True, optimize=2)
91 except py_compile.PyCompileError:
92 f.write(str(s), str(rel))
93 else:
94 f.write(str(pyc), str(rel.with_suffix('.pyc')))
Steve Dower08b18172015-08-04 16:02:40 -070095 else:
Steve Dower315b7482015-08-05 11:34:50 -070096 f.write(str(s), str(rel))
97 count += 1
Steve Dowerf70fdd22015-04-14 18:34:04 -040098
99 else:
100 for s, rel in rel_sources:
101 try:
102 (target / rel).parent.mkdir(parents=True)
103 except FileExistsError:
104 pass
105 shutil.copy(str(s), str(target / rel))
106 count += 1
107
108 return count
109
110def rglob(root, pattern, condition):
111 dirs = [root]
112 recurse = pattern[:3] in {'**/', '**\\'}
113 while dirs:
114 d = dirs.pop(0)
115 for f in d.glob(pattern[3:] if recurse else pattern):
116 if recurse and f.is_dir() and (not condition or condition(f)):
117 dirs.append(f)
118 elif f.is_file() and (not condition or condition(f)):
119 yield f, f.relative_to(root)
120
121def main():
122 parser = argparse.ArgumentParser()
123 parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path)
124 parser.add_argument('-o', '--out', metavar='file', help='The name of the output self-extracting archive', type=Path, required=True)
125 parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None)
126 parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False)
127 parser.add_argument('-a', '--arch', help='Specify the architecture to use (win32/amd64)', type=str, default="win32")
Steve Dowerf70fdd22015-04-14 18:34:04 -0400128 ns = parser.parse_args()
129
130 source = ns.source or (Path(__file__).parent.parent.parent)
131 out = ns.out
132 arch = ns.arch
Steve Dowerf70fdd22015-04-14 18:34:04 -0400133 assert isinstance(source, Path)
134 assert isinstance(out, Path)
135 assert isinstance(arch, str)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400136
137 if ns.temp:
138 temp = ns.temp
139 delete_temp = False
140 else:
141 temp = Path(tempfile.mkdtemp())
142 delete_temp = True
143
144 try:
145 out.parent.mkdir(parents=True)
146 except FileExistsError:
147 pass
148 try:
149 temp.mkdir(parents=True)
150 except FileExistsError:
151 pass
152
153 layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT
154
155 try:
156 for t, s, p, c in layout:
157 s = source / s.replace("$arch", arch)
Steve Dower8c1cee92015-05-02 21:38:26 -0700158 copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400159 print('Copied {} files'.format(copied))
160
Steve Dower4a7fe7e2015-05-22 15:10:10 -0700161 with open(str(temp / 'pyvenv.cfg'), 'w') as f:
162 print('applocal = true', file=f)
163
Steve Dower8c1cee92015-05-02 21:38:26 -0700164 total = copy_to_layout(out, rglob(temp, '*', None))
165 print('Wrote {} files to {}'.format(total, out))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400166 finally:
167 if delete_temp:
168 shutil.rmtree(temp, True)
169
170
171if __name__ == "__main__":
172 sys.exit(int(main() or 0))