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