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