llvm/lldb/scripts/install_custom_python.py

""" Copies the build output of a custom python interpreter to a directory
    structure that mirrors that of an official Python distribution.

    --------------------------------------------------------------------------
    File:           install_custom_python.py

    Overview:       Most users build LLDB by linking against the standard
                    Python distribution installed on their system.  Occasionally
                    a user may want to build their own version of Python, and on
                    platforms such as Windows this is a hard requirement.  This
                    script will take the build output of a custom interpreter and
                    install it into a canonical structure that mirrors that of an
                    official Python distribution, thus allowing PYTHONHOME to be
                    set appropriately.

    Gotchas:        None.

    Copyright:      None.
    --------------------------------------------------------------------------

"""

import argparse
import itertools
import os
import shutil
import sys


def copy_one_file(dest_dir, source_dir, filename):
    source_path = os.path.join(source_dir, filename)
    dest_path = os.path.join(dest_dir, filename)
    print("Copying file %s ==> %s..." % (source_path, dest_path))
    shutil.copyfile(source_path, dest_path)


def copy_named_files(dest_dir, source_dir, files, extensions, copy_debug_suffix_also):
    for file, ext in itertools.product(files, extensions):
        copy_one_file(dest_dir, source_dir, file + "." + ext)
        if copy_debug_suffix_also:
            copy_one_file(dest_dir, source_dir, file + "_d." + ext)


def copy_subdirectory(dest_dir, source_dir, subdir):
    dest_dir = os.path.join(dest_dir, subdir)
    source_dir = os.path.join(source_dir, subdir)
    print("Copying directory %s ==> %s..." % (source_dir, dest_dir))
    shutil.copytree(source_dir, dest_dir)


def copy_distro(dest_dir, dest_subdir, source_dir, source_prefix):
    dest_dir = os.path.join(dest_dir, dest_subdir)

    print("Copying distribution %s ==> %s" % (source_dir, dest_dir))

    os.mkdir(dest_dir)
    PCbuild_dir = os.path.join(source_dir, "PCbuild")
    if source_prefix:
        PCbuild_dir = os.path.join(PCbuild_dir, source_prefix)
    # First copy the files that go into the root of the new distribution. This
    # includes the Python executables, python27(_d).dll, and relevant PDB
    # files.
    print("Copying Python executables...")
    copy_named_files(dest_dir, PCbuild_dir, ["w9xpopen"], ["exe", "pdb"], False)
    copy_named_files(dest_dir, PCbuild_dir, ["python_d", "pythonw_d"], ["exe"], False)
    copy_named_files(
        dest_dir, PCbuild_dir, ["python", "pythonw"], ["exe", "pdb"], False
    )
    copy_named_files(dest_dir, PCbuild_dir, ["python27"], ["dll", "pdb"], True)

    # Next copy everything in the Include directory.
    print("Copying Python include directory")
    copy_subdirectory(dest_dir, source_dir, "Include")

    # Copy Lib folder (builtin Python modules)
    print("Copying Python Lib directory")
    copy_subdirectory(dest_dir, source_dir, "Lib")

    # Copy tools folder.  These are probably not necessary, but we copy them anyway to
    # match an official distribution as closely as possible.  Note that we don't just copy
    # the subdirectory recursively.  The source distribution ships with many more tools
    # than what you get by installing python regularly.  We only copy the tools that appear
    # in an installed distribution.
    tools_dest_dir = os.path.join(dest_dir, "Tools")
    tools_source_dir = os.path.join(source_dir, "Tools")
    os.mkdir(tools_dest_dir)
    copy_subdirectory(tools_dest_dir, tools_source_dir, "i18n")
    copy_subdirectory(tools_dest_dir, tools_source_dir, "pynche")
    copy_subdirectory(tools_dest_dir, tools_source_dir, "scripts")
    copy_subdirectory(tools_dest_dir, tools_source_dir, "versioncheck")
    copy_subdirectory(tools_dest_dir, tools_source_dir, "webchecker")

    pyd_names = [
        "_ctypes",
        "_ctypes_test",
        "_elementtree",
        "_multiprocessing",
        "_socket",
        "_testcapi",
        "pyexpat",
        "select",
        "unicodedata",
        "winsound",
    ]

    # Copy builtin extension modules (pyd files)
    dlls_dir = os.path.join(dest_dir, "DLLs")
    os.mkdir(dlls_dir)
    print("Copying DLLs directory")
    copy_named_files(dlls_dir, PCbuild_dir, pyd_names, ["pyd", "pdb"], True)

    # Copy libs folder (implibs for the pyd files)
    libs_dir = os.path.join(dest_dir, "libs")
    os.mkdir(libs_dir)
    print("Copying libs directory")
    copy_named_files(libs_dir, PCbuild_dir, pyd_names, ["lib"], False)
    copy_named_files(libs_dir, PCbuild_dir, ["python27"], ["lib"], True)


parser = argparse.ArgumentParser(description="Install a custom Python distribution")
parser.add_argument(
    "--source", required=True, help="The root of the source tree where Python is built."
)
parser.add_argument(
    "--dest", required=True, help="The location to install the Python distributions."
)
parser.add_argument(
    "--overwrite",
    default=False,
    action="store_true",
    help="If the destination directory already exists, destroys its contents first.",
)
parser.add_argument(
    "--silent",
    default=False,
    action="store_true",
    help="If --overwite was specified, suppress confirmation before deleting a directory tree.",
)

args = parser.parse_args()

args.source = os.path.normpath(args.source)
args.dest = os.path.normpath(args.dest)

if not os.path.exists(args.source):
    print("The source directory %s does not exist.  Exiting...")
    sys.exit(1)

if os.path.exists(args.dest):
    if not args.overwrite:
        print(
            "The destination directory '%s' already exists and --overwrite was not specified.  Exiting..."
            % args.dest
        )
        sys.exit(1)
    while not args.silent:
        print(
            "Ok to recursively delete '%s' and all contents (Y/N)?  Choosing Y will permanently delete the contents."
            % args.dest
        )
        result = str.upper(sys.stdin.read(1))
        if result == "N":
            print(
                "Unable to copy files to the destination.  The destination already exists."
            )
            sys.exit(1)
        elif result == "Y":
            break
    shutil.rmtree(args.dest)

os.mkdir(args.dest)
copy_distro(args.dest, "x86", args.source, None)
copy_distro(args.dest, "x64", args.source, "amd64")