Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 1 | import argparse |
| 2 | import re |
| 3 | import sys |
| 4 | import shutil |
| 5 | import os |
| 6 | import tempfile |
| 7 | |
| 8 | from pathlib import Path |
| 9 | from zipfile import ZipFile, ZIP_DEFLATED |
| 10 | import subprocess |
| 11 | |
| 12 | TKTCL_RE = re.compile(r'^(_?tk|tcl).+\.(pyd|dll)', re.IGNORECASE) |
| 13 | DEBUG_RE = re.compile(r'_d\.(pyd|dll|exe)$', re.IGNORECASE) |
| 14 | PYTHON_DLL_RE = re.compile(r'python\d\d?\.dll$', re.IGNORECASE) |
| 15 | |
| 16 | def is_not_debug(p): |
Steve Dower | 6b4c63d | 2015-05-02 15:32:14 -0700 | [diff] [blame] | 17 | 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 Dower | 3805019 | 2015-05-23 18:08:55 -0700 | [diff] [blame] | 28 | '_testmultiphase.pyd', |
Steve Dower | 6b4c63d | 2015-05-02 15:32:14 -0700 | [diff] [blame] | 29 | 'xxlimited.pyd', |
| 30 | } |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 31 | |
| 32 | def is_not_debug_or_python(p): |
| 33 | return is_not_debug(p) and not PYTHON_DLL_RE.search(p.name) |
| 34 | |
| 35 | def 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 Dower | 777af30 | 2015-04-19 19:50:35 -0700 | [diff] [blame] | 46 | suffix = p.suffix.lower() |
Steve Dower | 777af30 | 2015-04-19 19:50:35 -0700 | [diff] [blame] | 47 | return suffix not in {'.pyc', '.pyo'} |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 48 | |
| 49 | def 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 | |
| 55 | FULL_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 | |
| 66 | if os.getenv('DOC_FILENAME'): |
| 67 | FULL_LAYOUT.append(('Doc/', 'Doc/build/htmlhelp', os.getenv('DOC_FILENAME'), None)) |
| 68 | |
| 69 | EMBED_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 Dower | 8c1cee9 | 2015-05-02 21:38:26 -0700 | [diff] [blame] | 76 | def copy_to_layout(target, rel_sources): |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 77 | 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 | |
| 99 | def 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 | |
| 110 | def 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 Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 117 | ns = parser.parse_args() |
| 118 | |
| 119 | source = ns.source or (Path(__file__).parent.parent.parent) |
| 120 | out = ns.out |
| 121 | arch = ns.arch |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 122 | assert isinstance(source, Path) |
| 123 | assert isinstance(out, Path) |
| 124 | assert isinstance(arch, str) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 125 | |
| 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 Dower | 8c1cee9 | 2015-05-02 21:38:26 -0700 | [diff] [blame] | 147 | copied = copy_to_layout(temp / t.rstrip('/'), rglob(s, p, c)) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 148 | print('Copied {} files'.format(copied)) |
| 149 | |
Steve Dower | 4a7fe7e | 2015-05-22 15:10:10 -0700 | [diff] [blame] | 150 | with open(str(temp / 'pyvenv.cfg'), 'w') as f: |
| 151 | print('applocal = true', file=f) |
| 152 | |
Steve Dower | 8c1cee9 | 2015-05-02 21:38:26 -0700 | [diff] [blame] | 153 | total = copy_to_layout(out, rglob(temp, '*', None)) |
| 154 | print('Wrote {} files to {}'.format(total, out)) |
Steve Dower | f70fdd2 | 2015-04-14 18:34:04 -0400 | [diff] [blame] | 155 | finally: |
| 156 | if delete_temp: |
| 157 | shutil.rmtree(temp, True) |
| 158 | |
| 159 | |
| 160 | if __name__ == "__main__": |
| 161 | sys.exit(int(main() or 0)) |