install_custom_python.py 6.38 KB
""" 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')