blob: 368b4ec8f0403f47359344cbf53ebc41ad55e2b6 [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',
28 'xxlimited.pyd',
29 }
Steve Dowerf70fdd22015-04-14 18:34:04 -040030
31def is_not_debug_or_python(p):
32 return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name)
33
34def include_in_lib(p):
35 name = p.name.lower()
36 if p.is_dir():
37 if name in {'__pycache__', 'ensurepip', 'idlelib', 'pydoc_data', 'tkinter', 'turtledemo'}:
38 return False
39 if name.startswith('plat-'):
40 return False
41 if name == 'test' and p.parts[-2].lower() == 'lib':
42 return False
43 return True
44
Steve Dower777af302015-04-19 19:50:35 -070045 suffix = p.suffix.lower()
Steve Dower777af302015-04-19 19:50:35 -070046 return suffix not in {'.pyc', '.pyo'}
Steve Dowerf70fdd22015-04-14 18:34:04 -040047
48def include_in_tools(p):
49 if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}:
50 return True
51
52 return p.suffix.lower() in {'.py', '.pyw', '.txt'}
53
54FULL_LAYOUT = [
55 ('/', 'PCBuild/$arch', 'python*.exe', is_not_debug),
56 ('/', 'PCBuild/$arch', 'python*.dll', is_not_debug),
57 ('DLLs/', 'PCBuild/$arch', '*.pyd', is_not_debug),
58 ('DLLs/', 'PCBuild/$arch', '*.dll', is_not_debug),
59 ('include/', 'include', '*.h', None),
60 ('include/', 'PC', 'pyconfig.h', None),
61 ('Lib/', 'Lib', '**/*', include_in_lib),
62 ('Tools/', 'Tools', '**/*', include_in_tools),
63]
64
65if os.getenv('DOC_FILENAME'):
66 FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None))
67
68EMBED_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
75def copy_to_layout(target, source, rel_sources):
76 count = 0
77
78 if target.suffix.lower() == '.zip':
79 if target.exists():
80 target.unlink()
81
82 with ZipFile(str(target), 'w', ZIP_DEFLATED) as f:
83 for s, rel in rel_sources:
84 f.write(str(s), str(rel))
85 count += 1
86
87 else:
88 for s, rel in rel_sources:
89 try:
90 (target / rel).parent.mkdir(parents=True)
91 except FileExistsError:
92 pass
93 shutil.copy(str(s), str(target / rel))
94 count += 1
95
96 return count
97
98def rglob(root, pattern, condition):
99 dirs = [root]
100 recurse = pattern[:3] in {'**/', '**\\'}
101 while dirs:
102 d = dirs.pop(0)
103 for f in d.glob(pattern[3:] if recurse else pattern):
104 if recurse and f.is_dir() and (not condition or condition(f)):
105 dirs.append(f)
106 elif f.is_file() and (not condition or condition(f)):
107 yield f, f.relative_to(root)
108
109def main():
110 parser = argparse.ArgumentParser()
111 parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path)
112 parser.add_argument('-o', '--out', metavar='file', help='The name of the output self-extracting archive', type=Path, required=True)
113 parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None)
114 parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False)
115 parser.add_argument('-a', '--arch', help='Specify the architecture to use (win32/amd64)', type=str, default="win32")
116 parser.add_argument('--rar', help='Full path to WinRAR compressor (rar.exe)', type=Path, default=Path("rar.exe"))
117 ns = parser.parse_args()
118
119 source = ns.source or (Path(__file__).parent.parent.parent)
120 out = ns.out
121 arch = ns.arch
122 rar = getattr(ns, 'rar')
123 assert isinstance(source, Path)
124 assert isinstance(out, Path)
125 assert isinstance(arch, str)
126 assert isinstance(rar, Path)
127
128 if ns.temp:
129 temp = ns.temp
130 delete_temp = False
131 else:
132 temp = Path(tempfile.mkdtemp())
133 delete_temp = True
134
135 try:
136 out.parent.mkdir(parents=True)
137 except FileExistsError:
138 pass
139 try:
140 temp.mkdir(parents=True)
141 except FileExistsError:
142 pass
143
144 layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT
145
146 try:
147 for t, s, p, c in layout:
148 s = source / s.replace("$arch", arch)
149 copied = copy_to_layout(
150 temp / t.rstrip('/'),
151 source,
152 rglob(s, p, c)
153 )
154 print('Copied {} files'.format(copied))
155
156 if rar and rar.is_file():
157 subprocess.check_call([
158 str(rar),
159 "a",
Steve Dower6b4c63d2015-05-02 15:32:14 -0700160 "-ed", "-ep1", "-s", "-r",
Steve Dowerf70fdd22015-04-14 18:34:04 -0400161 str(out),
162 str(temp / '*')
163 ])
164 finally:
165 if delete_temp:
166 shutil.rmtree(temp, True)
167
168
169if __name__ == "__main__":
170 sys.exit(int(main() or 0))