blob: 521ba93ba545b0bca2bd5487e84c066908d45df3 [file] [log] [blame]
Steve Dowerf70fdd22015-04-14 18:34:04 -04001import argparse
2import re
3import sys
4import shutil
5import os
6import tempfile
7
8from pathlib import Path
9from zipfile import ZipFile, ZIP_DEFLATED
10import subprocess
11
12TKTCL_RE = re.compile(r'^(_?tk|tcl).+\.(pyd|dll)', re.IGNORECASE)
13DEBUG_RE = re.compile(r'_d\.(pyd|dll|exe)$', re.IGNORECASE)
14PYTHON_DLL_RE = re.compile(r'python\d\d?\.dll$', re.IGNORECASE)
15
16def is_not_debug(p):
Steve Dower6b4c63d2015-05-02 15:32:14 -070017 if DEBUG_RE.search(p.name):
18 return False
19
20 if TKTCL_RE.search(p.name):
21 return False
22
23 return p.name.lower() not in {
24 '_ctypes_test.pyd',
25 '_testbuffer.pyd',
26 '_testcapi.pyd',
27 '_testimportmultiple.pyd',
Steve Dower38050192015-05-23 18:08:55 -070028 '_testmultiphase.pyd',
Steve Dower6b4c63d2015-05-02 15:32:14 -070029 'xxlimited.pyd',
30 }
Steve Dowerf70fdd22015-04-14 18:34:04 -040031
32def is_not_debug_or_python(p):
33 return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name)
34
35def include_in_lib(p):
36 name = p.name.lower()
37 if p.is_dir():
38 if name in {'__pycache__', 'ensurepip', 'idlelib', 'pydoc_data', 'tkinter', 'turtledemo'}:
39 return False
40 if name.startswith('plat-'):
41 return False
42 if name == 'test' and p.parts[-2].lower() == 'lib':
43 return False
44 return True
45
Steve Dower777af302015-04-19 19:50:35 -070046 suffix = p.suffix.lower()
Steve Dower777af302015-04-19 19:50:35 -070047 return suffix not in {'.pyc', '.pyo'}
Steve Dowerf70fdd22015-04-14 18:34:04 -040048
49def include_in_tools(p):
50 if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}:
51 return True
52
53 return p.suffix.lower() in {'.py', '.pyw', '.txt'}
54
55FULL_LAYOUT = [
56 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
57 ('/', 'PCBuild/$arch', 'python*.dll', is_not_debug),
58 ('DLLs/', 'PCBuild/$arch', '*.pyd', is_not_debug),
59 ('DLLs/', 'PCBuild/$arch', '*.dll', is_not_debug),
60 ('include/', 'include', '*.h', None),
61 ('include/', 'PC', 'pyconfig.h', None),
62 ('Lib/', 'Lib', '**/*', include_in_lib),
63 ('Tools/', 'Tools', '**/*', include_in_tools),
64]
65
66if os.getenv('DOC_FILENAME'):
67 FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None))
68
69EMBED_LAYOUT = [
70 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
71 ('/', 'PCBuild/$arch', '*.pyd', is_not_debug),
72 ('/', 'PCBuild/$arch', '*.dll', is_not_debug),
73 ('python35.zip', 'Lib', '**/*', include_in_lib),
74]
75
Steve Dower8c1cee92015-05-02 21:38:26 -070076def copy_to_layout(target, rel_sources):
Steve Dowerf70fdd22015-04-14 18:34:04 -040077 count = 0
78
79 if target.suffix.lower() == '.zip':
80 if target.exists():
81 target.unlink()
82
83 with ZipFile(str(target), 'w', ZIP_DEFLATED) as f:
84 for s, rel in rel_sources:
85 f.write(str(s), str(rel))
86 count += 1
87
88 else:
89 for s, rel in rel_sources:
90 try:
91 (target / rel).parent.mkdir(parents=True)
92 except FileExistsError:
93 pass
94 shutil.copy(str(s), str(target / rel))
95 count += 1
96
97 return count
98
99def rglob(root, pattern, condition):
100 dirs = [root]
101 recurse = pattern[:3] in {'**/', '**\\'}
102 while dirs:
103 d = dirs.pop(0)
104 for f in d.glob(pattern[3:] if recurse else pattern):
105 if recurse and f.is_dir() and (not condition or condition(f)):
106 dirs.append(f)
107 elif f.is_file() and (not condition or condition(f)):
108 yield f, f.relative_to(root)
109
110def main():
111 parser = argparse.ArgumentParser()
112 parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path)
113 parser.add_argument('-o', '--out', metavar='file', help='The name of the output self-extracting archive', type=Path, required=True)
114 parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None)
115 parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False)
116 parser.add_argument('-a', '--arch', help='Specify the architecture to use (win32/amd64)', type=str, default="win32")
Steve Dowerf70fdd22015-04-14 18:34:04 -0400117 ns = parser.parse_args()
118
119 source = ns.source or (Path(__file__).parent.parent.parent)
120 out = ns.out
121 arch = ns.arch
Steve Dowerf70fdd22015-04-14 18:34:04 -0400122 assert isinstance(source, Path)
123 assert isinstance(out, Path)
124 assert isinstance(arch, str)
Steve Dowerf70fdd22015-04-14 18:34:04 -0400125
126 if ns.temp:
127 temp = ns.temp
128 delete_temp = False
129 else:
130 temp = Path(tempfile.mkdtemp())
131 delete_temp = True
132
133 try:
134 out.parent.mkdir(parents=True)
135 except FileExistsError:
136 pass
137 try:
138 temp.mkdir(parents=True)
139 except FileExistsError:
140 pass
141
142 layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT
143
144 try:
145 for t, s, p, c in layout:
146 s = source / s.replace("$arch", arch)
Steve Dower8c1cee92015-05-02 21:38:26 -0700147 copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400148 print('Copied {} files'.format(copied))
149
Steve Dower4a7fe7e2015-05-22 15:10:10 -0700150 with open(str(temp / 'pyvenv.cfg'), 'w') as f:
151 print('applocal = true', file=f)
152
Steve Dower8c1cee92015-05-02 21:38:26 -0700153 total = copy_to_layout(out, rglob(temp, '*', None))
154 print('Wrote {} files to {}'.format(total, out))
Steve Dowerf70fdd22015-04-14 18:34:04 -0400155 finally:
156 if delete_temp:
157 shutil.rmtree(temp, True)
158
159
160if __name__ == "__main__":
161 sys.exit(int(main() or 0))