Zachary Turner | dd50f74 | 2015-04-10 22:58:56 +0000 | [diff] [blame] | 1 | """ Copies the build output of a custom python interpreter to a directory |
| 2 | structure that mirrors that of an official Python distribution. |
| 3 | |
| 4 | -------------------------------------------------------------------------- |
| 5 | File: install_custom_python.py |
| 6 | |
| 7 | Overview: Most users build LLDB by linking against the standard |
| 8 | Python distribution installed on their system. Occasionally |
| 9 | a user may want to build their own version of Python, and on |
| 10 | platforms such as Windows this is a hard requirement. This |
| 11 | script will take the build output of a custom interpreter and |
| 12 | install it into a canonical structure that mirrors that of an |
| 13 | official Python distribution, thus allowing PYTHONHOME to be |
| 14 | set appropriately. |
| 15 | |
| 16 | Gotchas: None. |
| 17 | |
| 18 | Copyright: None. |
| 19 | -------------------------------------------------------------------------- |
| 20 | |
| 21 | """ |
| 22 | |
| 23 | import argparse |
| 24 | import itertools |
| 25 | import os |
| 26 | import shutil |
| 27 | import sys |
| 28 | |
| 29 | def copy_one_file(dest_dir, source_dir, filename): |
| 30 | source_path = os.path.join(source_dir, filename) |
| 31 | dest_path = os.path.join(dest_dir, filename) |
| 32 | print 'Copying file %s ==> %s...' % (source_path, dest_path) |
| 33 | shutil.copyfile(source_path, dest_path) |
| 34 | |
| 35 | def copy_named_files(dest_dir, source_dir, files, extensions, copy_debug_suffix_also): |
| 36 | for (file, ext) in itertools.product(files, extensions): |
| 37 | copy_one_file(dest_dir, source_dir, file + '.' + ext) |
| 38 | if copy_debug_suffix_also: |
| 39 | copy_one_file(dest_dir, source_dir, file + '_d.' + ext) |
| 40 | |
| 41 | def copy_subdirectory(dest_dir, source_dir, subdir): |
| 42 | dest_dir = os.path.join(dest_dir, subdir) |
| 43 | source_dir = os.path.join(source_dir, subdir) |
| 44 | print 'Copying directory %s ==> %s...' % (source_dir, dest_dir) |
| 45 | shutil.copytree(source_dir, dest_dir) |
| 46 | |
| 47 | def copy_distro(dest_dir, dest_subdir, source_dir, source_prefix): |
| 48 | dest_dir = os.path.join(dest_dir, dest_subdir) |
| 49 | |
| 50 | print 'Copying distribution %s ==> %s' % (source_dir, dest_dir) |
| 51 | |
| 52 | os.mkdir(dest_dir) |
| 53 | PCbuild_dir = os.path.join(source_dir, 'PCbuild') |
| 54 | if source_prefix: |
| 55 | PCbuild_dir = os.path.join(PCbuild_dir, source_prefix) |
| 56 | # First copy the files that go into the root of the new distribution. This |
| 57 | # includes the Python executables, python27(_d).dll, and relevant PDB files. |
| 58 | print 'Copying Python executables...' |
| 59 | copy_named_files(dest_dir, PCbuild_dir, ['w9xpopen'], ['exe', 'pdb'], False) |
| 60 | copy_named_files(dest_dir, PCbuild_dir, ['python_d', 'pythonw_d'], ['exe'], False) |
| 61 | copy_named_files(dest_dir, PCbuild_dir, ['python', 'pythonw'], ['exe', 'pdb'], False) |
| 62 | copy_named_files(dest_dir, PCbuild_dir, ['python27'], ['dll', 'pdb'], True) |
| 63 | |
| 64 | # Next copy everything in the Include directory. |
| 65 | print 'Copying Python include directory' |
| 66 | copy_subdirectory(dest_dir, source_dir, 'Include') |
| 67 | |
| 68 | # Copy Lib folder (builtin Python modules) |
| 69 | print 'Copying Python Lib directory' |
| 70 | copy_subdirectory(dest_dir, source_dir, 'Lib') |
| 71 | |
| 72 | # Copy tools folder. These are probably not necessary, but we copy them anyway to |
| 73 | # match an official distribution as closely as possible. Note that we don't just copy |
| 74 | # the subdirectory recursively. The source distribution ships with many more tools |
| 75 | # than what you get by installing python regularly. We only copy the tools that appear |
| 76 | # in an installed distribution. |
| 77 | tools_dest_dir = os.path.join(dest_dir, 'Tools') |
| 78 | tools_source_dir = os.path.join(source_dir, 'Tools') |
| 79 | os.mkdir(tools_dest_dir) |
| 80 | copy_subdirectory(tools_dest_dir, tools_source_dir, 'i18n') |
| 81 | copy_subdirectory(tools_dest_dir, tools_source_dir, 'pynche') |
| 82 | copy_subdirectory(tools_dest_dir, tools_source_dir, 'scripts') |
| 83 | copy_subdirectory(tools_dest_dir, tools_source_dir, 'versioncheck') |
| 84 | copy_subdirectory(tools_dest_dir, tools_source_dir, 'webchecker') |
| 85 | |
| 86 | pyd_names = ['_ctypes', '_ctypes_test', '_elementtree', '_multiprocessing', '_socket', |
| 87 | '_testcapi', 'pyexpat', 'select', 'unicodedata', 'winsound'] |
| 88 | |
| 89 | # Copy builtin extension modules (pyd files) |
| 90 | dlls_dir = os.path.join(dest_dir, 'DLLs') |
| 91 | os.mkdir(dlls_dir) |
| 92 | print 'Copying DLLs directory' |
| 93 | copy_named_files(dlls_dir, PCbuild_dir, pyd_names, ['pyd', 'pdb'], True) |
| 94 | |
| 95 | # Copy libs folder (implibs for the pyd files) |
| 96 | libs_dir = os.path.join(dest_dir, 'libs') |
| 97 | os.mkdir(libs_dir) |
| 98 | print 'Copying libs directory' |
| 99 | copy_named_files(libs_dir, PCbuild_dir, pyd_names, ['lib'], False) |
| 100 | copy_named_files(libs_dir, PCbuild_dir, ['python27'], ['lib'], True) |
| 101 | |
| 102 | |
| 103 | parser = argparse.ArgumentParser(description='Install a custom Python distribution') |
| 104 | parser.add_argument('--source', required=True, help='The root of the source tree where Python is built.') |
| 105 | parser.add_argument('--dest', required=True, help='The location to install the Python distributions.') |
| 106 | parser.add_argument('--overwrite', default=False, action='store_true', help='If the destination directory already exists, destroys its contents first.') |
| 107 | parser.add_argument('--silent', default=False, action='store_true', help='If --overwite was specified, suppress confirmation before deleting a directory tree.') |
| 108 | |
| 109 | args = parser.parse_args() |
| 110 | |
| 111 | args.source = os.path.normpath(args.source) |
| 112 | args.dest = os.path.normpath(args.dest) |
| 113 | |
| 114 | if not os.path.exists(args.source): |
| 115 | print 'The source directory %s does not exist. Exiting...' |
| 116 | sys.exit(1) |
| 117 | |
| 118 | if os.path.exists(args.dest): |
| 119 | if not args.overwrite: |
| 120 | print 'The destination directory \'%s\' already exists and --overwrite was not specified. Exiting...' % args.dest |
| 121 | sys.exit(1) |
| 122 | while not args.silent: |
| 123 | print 'Ok to recursively delete \'%s\' and all contents (Y/N)? Choosing Y will permanently delete the contents.' % args.dest |
| 124 | result = str.upper(sys.stdin.read(1)) |
| 125 | if result == 'N': |
| 126 | print 'Unable to copy files to the destination. The destination already exists.' |
| 127 | sys.exit(1) |
| 128 | elif result == 'Y': |
| 129 | break |
| 130 | shutil.rmtree(args.dest) |
| 131 | |
| 132 | os.mkdir(args.dest) |
| 133 | copy_distro(args.dest, 'x86', args.source, None) |
| 134 | copy_distro(args.dest, 'x64', args.source, 'amd64') |