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