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 23import argparse 24import itertools 25import os 26import shutil 27import sys 28 29 30def copy_one_file(dest_dir, source_dir, filename): 31 source_path = os.path.join(source_dir, filename) 32 dest_path = os.path.join(dest_dir, filename) 33 print('Copying file %s ==> %s...' % (source_path, dest_path)) 34 shutil.copyfile(source_path, dest_path) 35 36 37def copy_named_files( 38 dest_dir, 39 source_dir, 40 files, 41 extensions, 42 copy_debug_suffix_also): 43 for (file, ext) in itertools.product(files, extensions): 44 copy_one_file(dest_dir, source_dir, file + '.' + ext) 45 if copy_debug_suffix_also: 46 copy_one_file(dest_dir, source_dir, file + '_d.' + ext) 47 48 49def copy_subdirectory(dest_dir, source_dir, subdir): 50 dest_dir = os.path.join(dest_dir, subdir) 51 source_dir = os.path.join(source_dir, subdir) 52 print('Copying directory %s ==> %s...' % (source_dir, dest_dir)) 53 shutil.copytree(source_dir, dest_dir) 54 55 56def copy_distro(dest_dir, dest_subdir, source_dir, source_prefix): 57 dest_dir = os.path.join(dest_dir, dest_subdir) 58 59 print('Copying distribution %s ==> %s' % (source_dir, dest_dir)) 60 61 os.mkdir(dest_dir) 62 PCbuild_dir = os.path.join(source_dir, 'PCbuild') 63 if source_prefix: 64 PCbuild_dir = os.path.join(PCbuild_dir, source_prefix) 65 # First copy the files that go into the root of the new distribution. This 66 # includes the Python executables, python27(_d).dll, and relevant PDB 67 # files. 68 print('Copying Python executables...') 69 copy_named_files( 70 dest_dir, PCbuild_dir, ['w9xpopen'], [ 71 'exe', 'pdb'], False) 72 copy_named_files( 73 dest_dir, PCbuild_dir, [ 74 'python_d', 'pythonw_d'], ['exe'], False) 75 copy_named_files( 76 dest_dir, PCbuild_dir, [ 77 'python', 'pythonw'], [ 78 'exe', 'pdb'], False) 79 copy_named_files(dest_dir, PCbuild_dir, ['python27'], ['dll', 'pdb'], True) 80 81 # Next copy everything in the Include directory. 82 print('Copying Python include directory') 83 copy_subdirectory(dest_dir, source_dir, 'Include') 84 85 # Copy Lib folder (builtin Python modules) 86 print('Copying Python Lib directory') 87 copy_subdirectory(dest_dir, source_dir, 'Lib') 88 89 # Copy tools folder. These are probably not necessary, but we copy them anyway to 90 # match an official distribution as closely as possible. Note that we don't just copy 91 # the subdirectory recursively. The source distribution ships with many more tools 92 # than what you get by installing python regularly. We only copy the tools that appear 93 # in an installed distribution. 94 tools_dest_dir = os.path.join(dest_dir, 'Tools') 95 tools_source_dir = os.path.join(source_dir, 'Tools') 96 os.mkdir(tools_dest_dir) 97 copy_subdirectory(tools_dest_dir, tools_source_dir, 'i18n') 98 copy_subdirectory(tools_dest_dir, tools_source_dir, 'pynche') 99 copy_subdirectory(tools_dest_dir, tools_source_dir, 'scripts') 100 copy_subdirectory(tools_dest_dir, tools_source_dir, 'versioncheck') 101 copy_subdirectory(tools_dest_dir, tools_source_dir, 'webchecker') 102 103 pyd_names = [ 104 '_ctypes', 105 '_ctypes_test', 106 '_elementtree', 107 '_multiprocessing', 108 '_socket', 109 '_testcapi', 110 'pyexpat', 111 'select', 112 'unicodedata', 113 'winsound'] 114 115 # Copy builtin extension modules (pyd files) 116 dlls_dir = os.path.join(dest_dir, 'DLLs') 117 os.mkdir(dlls_dir) 118 print('Copying DLLs directory') 119 copy_named_files(dlls_dir, PCbuild_dir, pyd_names, ['pyd', 'pdb'], True) 120 121 # Copy libs folder (implibs for the pyd files) 122 libs_dir = os.path.join(dest_dir, 'libs') 123 os.mkdir(libs_dir) 124 print('Copying libs directory') 125 copy_named_files(libs_dir, PCbuild_dir, pyd_names, ['lib'], False) 126 copy_named_files(libs_dir, PCbuild_dir, ['python27'], ['lib'], True) 127 128 129parser = argparse.ArgumentParser( 130 description='Install a custom Python distribution') 131parser.add_argument( 132 '--source', 133 required=True, 134 help='The root of the source tree where Python is built.') 135parser.add_argument( 136 '--dest', 137 required=True, 138 help='The location to install the Python distributions.') 139parser.add_argument( 140 '--overwrite', 141 default=False, 142 action='store_true', 143 help='If the destination directory already exists, destroys its contents first.') 144parser.add_argument( 145 '--silent', 146 default=False, 147 action='store_true', 148 help='If --overwite was specified, suppress confirmation before deleting a directory tree.') 149 150args = parser.parse_args() 151 152args.source = os.path.normpath(args.source) 153args.dest = os.path.normpath(args.dest) 154 155if not os.path.exists(args.source): 156 print('The source directory %s does not exist. Exiting...') 157 sys.exit(1) 158 159if os.path.exists(args.dest): 160 if not args.overwrite: 161 print('The destination directory \'%s\' already exists and --overwrite was not specified. Exiting...' % args.dest) 162 sys.exit(1) 163 while not args.silent: 164 print('Ok to recursively delete \'%s\' and all contents (Y/N)? Choosing Y will permanently delete the contents.' % args.dest) 165 result = str.upper(sys.stdin.read(1)) 166 if result == 'N': 167 print('Unable to copy files to the destination. The destination already exists.') 168 sys.exit(1) 169 elif result == 'Y': 170 break 171 shutil.rmtree(args.dest) 172 173os.mkdir(args.dest) 174copy_distro(args.dest, 'x86', args.source, None) 175copy_distro(args.dest, 'x64', args.source, 'amd64') 176