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