Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 1 | import argparse |
Steve Dower | 08b1817 | 2015-08-04 16:02:40 -0700 | [diff] [blame] | 2 | import py_compile |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 3 | import re |
| 4 | import sys |
| 5 | import shutil |
Steve Dower | ae69de6 | 2015-09-09 19:32:45 -0700 | [diff] [blame] | 6 | import stat |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 7 | import os |
| 8 | import tempfile |
| 9 | |
Steve Dower | f0888cd | 2016-11-23 10:23:47 -0800 | [diff] [blame] | 10 | from itertools import chain |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 11 | from pathlib import Path |
| 12 | from zipfile import ZipFile, ZIP_DEFLATED |
Victor Stinner | d6debb2 | 2017-03-27 16:05:26 +0200 | [diff] [blame] | 13 | |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 14 | |
| 15 | TKTCL_RE = re.compile(r'^(_?tk|tcl).+\.(pyd|dll)', re.IGNORECASE) |
Steve Dower | 33128c8 | 2016-06-27 09:34:18 -0700 | [diff] [blame] | 16 | DEBUG_RE = re.compile(r'_d\.(pyd|dll|exe|pdb|lib)$', re.IGNORECASE) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 17 | PYTHON_DLL_RE = re.compile(r'python\d\d?\.dll$', re.IGNORECASE) |
| 18 | |
Steve Dower | 33128c8 | 2016-06-27 09:34:18 -0700 | [diff] [blame] | 19 | DEBUG_FILES = { |
| 20 | '_ctypes_test', |
| 21 | '_testbuffer', |
| 22 | '_testcapi', |
Steve Dower | 4782ab3 | 2016-10-29 09:23:39 -0700 | [diff] [blame] | 23 | '_testconsole', |
Steve Dower | 33128c8 | 2016-06-27 09:34:18 -0700 | [diff] [blame] | 24 | '_testimportmultiple', |
| 25 | '_testmultiphase', |
| 26 | 'xxlimited', |
| 27 | 'python3_dstub', |
| 28 | } |
| 29 | |
Steve Dower | 2495faf | 2015-09-22 15:03:54 -0700 | [diff] [blame] | 30 | EXCLUDE_FROM_LIBRARY = { |
| 31 | '__pycache__', |
Steve Dower | 2495faf | 2015-09-22 15:03:54 -0700 | [diff] [blame] | 32 | 'idlelib', |
| 33 | 'pydoc_data', |
| 34 | 'site-packages', |
| 35 | 'tkinter', |
| 36 | 'turtledemo', |
Steve Dower | 5288477 | 2017-02-06 14:11:34 -0800 | [diff] [blame] | 37 | } |
| 38 | |
| 39 | EXCLUDE_FROM_EMBEDDABLE_LIBRARY = { |
| 40 | 'ensurepip', |
Steve Dower | 10cabcb | 2016-01-16 13:44:43 -0800 | [diff] [blame] | 41 | 'venv', |
Steve Dower | 2495faf | 2015-09-22 15:03:54 -0700 | [diff] [blame] | 42 | } |
| 43 | |
| 44 | EXCLUDE_FILE_FROM_LIBRARY = { |
| 45 | 'bdist_wininst.py', |
| 46 | } |
| 47 | |
Steve Dower | 33128c8 | 2016-06-27 09:34:18 -0700 | [diff] [blame] | 48 | EXCLUDE_FILE_FROM_LIBS = { |
Steve Dower | 2a94301 | 2016-11-23 11:42:35 -0800 | [diff] [blame] | 49 | 'liblzma', |
Steve Dower | 33128c8 | 2016-06-27 09:34:18 -0700 | [diff] [blame] | 50 | 'python3stub', |
| 51 | } |
| 52 | |
Steve Dower | 4db86bc | 2016-09-09 09:17:35 -0700 | [diff] [blame] | 53 | EXCLUDED_FILES = { |
| 54 | 'pyshellext', |
| 55 | } |
| 56 | |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 57 | def is_not_debug(p): |
Steve Dower | 6b4c63d | 2015-05-02 15:32:14 -0700 | [diff] [blame] | 58 | if DEBUG_RE.search(p.name): |
| 59 | return False |
| 60 | |
| 61 | if TKTCL_RE.search(p.name): |
| 62 | return False |
| 63 | |
Steve Dower | 4db86bc | 2016-09-09 09:17:35 -0700 | [diff] [blame] | 64 | return p.stem.lower() not in DEBUG_FILES and p.stem.lower() not in EXCLUDED_FILES |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 65 | |
| 66 | def is_not_debug_or_python(p): |
| 67 | return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name) |
| 68 | |
| 69 | def include_in_lib(p): |
| 70 | name = p.name.lower() |
| 71 | if p.is_dir(): |
Steve Dower | 2495faf | 2015-09-22 15:03:54 -0700 | [diff] [blame] | 72 | if name in EXCLUDE_FROM_LIBRARY: |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 73 | return False |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 74 | if name == 'test' and p.parts[-2].lower() == 'lib': |
| 75 | return False |
Steve Dower | 2495faf | 2015-09-22 15:03:54 -0700 | [diff] [blame] | 76 | if name in {'test', 'tests'} and p.parts[-3].lower() == 'lib': |
| 77 | return False |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 78 | return True |
| 79 | |
Steve Dower | 2495faf | 2015-09-22 15:03:54 -0700 | [diff] [blame] | 80 | if name in EXCLUDE_FILE_FROM_LIBRARY: |
| 81 | return False |
| 82 | |
Steve Dower | 777af30 | 2015-04-19 19:50:35 -0700 | [diff] [blame] | 83 | suffix = p.suffix.lower() |
Steve Dower | 2495faf | 2015-09-22 15:03:54 -0700 | [diff] [blame] | 84 | return suffix not in {'.pyc', '.pyo', '.exe'} |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 85 | |
Steve Dower | 5288477 | 2017-02-06 14:11:34 -0800 | [diff] [blame] | 86 | def include_in_embeddable_lib(p): |
| 87 | if p.is_dir() and p.name.lower() in EXCLUDE_FROM_EMBEDDABLE_LIBRARY: |
| 88 | return False |
| 89 | |
| 90 | return include_in_lib(p) |
| 91 | |
Steve Dower | 33128c8 | 2016-06-27 09:34:18 -0700 | [diff] [blame] | 92 | def include_in_libs(p): |
| 93 | if not is_not_debug(p): |
| 94 | return False |
| 95 | |
| 96 | return p.stem.lower() not in EXCLUDE_FILE_FROM_LIBS |
| 97 | |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 98 | def include_in_tools(p): |
| 99 | if p.is_dir() and p.name.lower() in {'scripts', 'i18n', 'pynche', 'demo', 'parser'}: |
| 100 | return True |
| 101 | |
| 102 | return p.suffix.lower() in {'.py', '.pyw', '.txt'} |
| 103 | |
Steve Dower | ed51b26 | 2016-09-17 12:54:06 -0700 | [diff] [blame] | 104 | BASE_NAME = 'python{0.major}{0.minor}'.format(sys.version_info) |
| 105 | |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 106 | FULL_LAYOUT = [ |
Steve Dower | f085191 | 2017-07-26 09:09:01 -0700 | [diff] [blame] | 107 | ('/', '$build', 'python.exe', is_not_debug), |
| 108 | ('/', '$build', 'pythonw.exe', is_not_debug), |
| 109 | ('/', '$build', 'python{}.dll'.format(sys.version_info.major), is_not_debug), |
| 110 | ('/', '$build', '{}.dll'.format(BASE_NAME), is_not_debug), |
| 111 | ('DLLs/', '$build', '*.pyd', is_not_debug), |
| 112 | ('DLLs/', '$build', '*.dll', is_not_debug_or_python), |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 113 | ('include/', 'include', '*.h', None), |
| 114 | ('include/', 'PC', 'pyconfig.h', None), |
| 115 | ('Lib/', 'Lib', '**/*', include_in_lib), |
Steve Dower | f085191 | 2017-07-26 09:09:01 -0700 | [diff] [blame] | 116 | ('libs/', '$build', '*.lib', include_in_libs), |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 117 | ('Tools/', 'Tools', '**/*', include_in_tools), |
| 118 | ] |
| 119 | |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 120 | EMBED_LAYOUT = [ |
Steve Dower | f085191 | 2017-07-26 09:09:01 -0700 | [diff] [blame] | 121 | ('/', '$build', 'python*.exe', is_not_debug), |
| 122 | ('/', '$build', '*.pyd', is_not_debug), |
| 123 | ('/', '$build', '*.dll', is_not_debug), |
Steve Dower | f007b49 | 2017-02-06 14:12:19 -0800 | [diff] [blame] | 124 | ('{}.zip'.format(BASE_NAME), 'Lib', '**/*', include_in_embeddable_lib), |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 125 | ] |
| 126 | |
Steve Dower | fcbe1df | 2015-09-08 21:39:01 -0700 | [diff] [blame] | 127 | if os.getenv('DOC_FILENAME'): |
| 128 | FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None)) |
| 129 | if os.getenv('VCREDIST_PATH'): |
| 130 | FULL_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None)) |
| 131 | EMBED_LAYOUT.append(('/', os.getenv('VCREDIST_PATH'), 'vcruntime*.dll', None)) |
| 132 | |
Steve Dower | 8c1cee9 | 2015-05-02 21:38:26 -0700 | [diff] [blame] | 133 | def copy_to_layout(target, rel_sources): |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 134 | count = 0 |
| 135 | |
| 136 | if target.suffix.lower() == '.zip': |
| 137 | if target.exists(): |
| 138 | target.unlink() |
| 139 | |
| 140 | with ZipFile(str(target), 'w', ZIP_DEFLATED) as f: |
Steve Dower | 315b748 | 2015-08-05 11:34:50 -0700 | [diff] [blame] | 141 | with tempfile.TemporaryDirectory() as tmpdir: |
| 142 | for s, rel in rel_sources: |
| 143 | if rel.suffix.lower() == '.py': |
| 144 | pyc = Path(tmpdir) / rel.with_suffix('.pyc').name |
| 145 | try: |
| 146 | py_compile.compile(str(s), str(pyc), str(rel), doraise=True, optimize=2) |
| 147 | except py_compile.PyCompileError: |
| 148 | f.write(str(s), str(rel)) |
| 149 | else: |
| 150 | f.write(str(pyc), str(rel.with_suffix('.pyc'))) |
Steve Dower | 08b1817 | 2015-08-04 16:02:40 -0700 | [diff] [blame] | 151 | else: |
Steve Dower | 315b748 | 2015-08-05 11:34:50 -0700 | [diff] [blame] | 152 | f.write(str(s), str(rel)) |
| 153 | count += 1 |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 154 | |
| 155 | else: |
| 156 | for s, rel in rel_sources: |
Steve Dower | ae69de6 | 2015-09-09 19:32:45 -0700 | [diff] [blame] | 157 | dest = target / rel |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 158 | try: |
Steve Dower | ae69de6 | 2015-09-09 19:32:45 -0700 | [diff] [blame] | 159 | dest.parent.mkdir(parents=True) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 160 | except FileExistsError: |
| 161 | pass |
Steve Dower | ae69de6 | 2015-09-09 19:32:45 -0700 | [diff] [blame] | 162 | if dest.is_file(): |
| 163 | dest.chmod(stat.S_IWRITE) |
| 164 | shutil.copy(str(s), str(dest)) |
| 165 | if dest.is_file(): |
| 166 | dest.chmod(stat.S_IWRITE) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 167 | count += 1 |
| 168 | |
| 169 | return count |
| 170 | |
| 171 | def rglob(root, pattern, condition): |
| 172 | dirs = [root] |
| 173 | recurse = pattern[:3] in {'**/', '**\\'} |
| 174 | while dirs: |
| 175 | d = dirs.pop(0) |
| 176 | for f in d.glob(pattern[3:] if recurse else pattern): |
| 177 | if recurse and f.is_dir() and (not condition or condition(f)): |
| 178 | dirs.append(f) |
| 179 | elif f.is_file() and (not condition or condition(f)): |
| 180 | yield f, f.relative_to(root) |
| 181 | |
| 182 | def main(): |
| 183 | parser = argparse.ArgumentParser() |
| 184 | parser.add_argument('-s', '--source', metavar='dir', help='The directory containing the repository root', type=Path) |
Steve Dower | 6fd76bc | 2016-07-16 16:13:19 -0700 | [diff] [blame] | 185 | parser.add_argument('-o', '--out', metavar='file', help='The name of the output archive', type=Path, default=None) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 186 | parser.add_argument('-t', '--temp', metavar='dir', help='A directory to temporarily extract files into', type=Path, default=None) |
| 187 | parser.add_argument('-e', '--embed', help='Create an embedding layout', action='store_true', default=False) |
Steve Dower | f085191 | 2017-07-26 09:09:01 -0700 | [diff] [blame] | 188 | parser.add_argument('-b', '--build', help='Specify the build directory', type=Path, default=None) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 189 | ns = parser.parse_args() |
| 190 | |
Steve Dower | 33f7310 | 2016-06-24 10:32:15 -0700 | [diff] [blame] | 191 | source = ns.source or (Path(__file__).resolve().parent.parent.parent) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 192 | out = ns.out |
Steve Dower | f085191 | 2017-07-26 09:09:01 -0700 | [diff] [blame] | 193 | build = ns.build or Path(sys.exec_prefix) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 194 | assert isinstance(source, Path) |
Steve Dower | 33f7310 | 2016-06-24 10:32:15 -0700 | [diff] [blame] | 195 | assert not out or isinstance(out, Path) |
Steve Dower | f085191 | 2017-07-26 09:09:01 -0700 | [diff] [blame] | 196 | assert isinstance(build, Path) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 197 | |
| 198 | if ns.temp: |
| 199 | temp = ns.temp |
| 200 | delete_temp = False |
| 201 | else: |
| 202 | temp = Path(tempfile.mkdtemp()) |
| 203 | delete_temp = True |
| 204 | |
Steve Dower | 33f7310 | 2016-06-24 10:32:15 -0700 | [diff] [blame] | 205 | if out: |
| 206 | try: |
| 207 | out.parent.mkdir(parents=True) |
| 208 | except FileExistsError: |
| 209 | pass |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 210 | try: |
| 211 | temp.mkdir(parents=True) |
| 212 | except FileExistsError: |
| 213 | pass |
| 214 | |
| 215 | layout = EMBED_LAYOUT if ns.embed else FULL_LAYOUT |
| 216 | |
| 217 | try: |
| 218 | for t, s, p, c in layout: |
Steve Dower | f085191 | 2017-07-26 09:09:01 -0700 | [diff] [blame] | 219 | if s == '$build': |
| 220 | fs = build |
| 221 | else: |
| 222 | fs = source / s |
Steve Dower | f0888cd | 2016-11-23 10:23:47 -0800 | [diff] [blame] | 223 | files = rglob(fs, p, c) |
| 224 | extra_files = [] |
| 225 | if s == 'Lib' and p == '**/*': |
| 226 | extra_files.append(( |
Steve Dower | e711cc0 | 2016-12-11 14:35:07 -0800 | [diff] [blame] | 227 | source / 'tools' / 'msi' / 'distutils.command.bdist_wininst.py', |
| 228 | Path('distutils') / 'command' / 'bdist_wininst.py' |
Steve Dower | f0888cd | 2016-11-23 10:23:47 -0800 | [diff] [blame] | 229 | )) |
| 230 | copied = copy_to_layout(temp / t.rstrip('/'), chain(files, extra_files)) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 231 | print('Copied {} files'.format(copied)) |
| 232 | |
Steve Dower | 41fca9d | 2016-09-12 13:29:58 -0700 | [diff] [blame] | 233 | if ns.embed: |
Steve Dower | ed51b26 | 2016-09-17 12:54:06 -0700 | [diff] [blame] | 234 | with open(str(temp / (BASE_NAME + '._pth')), 'w') as f: |
| 235 | print(BASE_NAME + '.zip', file=f) |
Steve Dower | 41fca9d | 2016-09-12 13:29:58 -0700 | [diff] [blame] | 236 | print('.', file=f) |
Steve Dower | ed51b26 | 2016-09-17 12:54:06 -0700 | [diff] [blame] | 237 | print('', file=f) |
| 238 | print('# Uncomment to run site.main() automatically', file=f) |
| 239 | print('#import site', file=f) |
Steve Dower | 4a7fe7e | 2015-05-22 15:10:10 -0700 | [diff] [blame] | 240 | |
Steve Dower | 33f7310 | 2016-06-24 10:32:15 -0700 | [diff] [blame] | 241 | if out: |
| 242 | total = copy_to_layout(out, rglob(temp, '**/*', None)) |
| 243 | print('Wrote {} files to {}'.format(total, out)) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 244 | finally: |
| 245 | if delete_temp: |
| 246 | shutil.rmtree(temp, True) |
| 247 | |
| 248 | |
| 249 | if __name__ == "__main__": |
| 250 | sys.exit(int(main() or 0)) |