def main():
    if sys.platform == "linux":
        host_platform = "linux64"
        default_target_triple = "x86_64-unknown-linux-gnu"
    elif sys.platform == "darwin":
        host_platform = "macos"
        machine = platform.machine()

        if machine == "arm64":
            default_target_triple = "aarch64-apple-darwin"
        elif machine == "x86_64":
            default_target_triple = "x86_64-apple-darwin"
        else:
            raise Exception("unhandled macOS machine value: %s" % machine)
    else:
        print("unsupport build platform: %s" % sys.platform)
        return 1

    parser = argparse.ArgumentParser()

    parser.add_argument(
        "--target-triple",
        default=default_target_triple,
        choices=supported_targets(TARGETS_CONFIG),
        help="Target host triple to build for",
    )

    parser.add_argument(
        "--optimizations",
        choices={"debug", "noopt", "pgo", "lto", "pgo+lto"},
        default="noopt",
        help="Optimizations to apply when compiling Python",
    )
    parser.add_argument(
        "--python",
        choices={"cpython-3.8", "cpython-3.9", "cpython-3.10"},
        default="cpython-3.9",
        help="Python distribution to build",
    )
    parser.add_argument(
        "--break-on-failure",
        action="store_true",
        help="Enter a Python debugger if an error occurs",
    )
    parser.add_argument(
        "--no-docker",
        action="store_true",
        default=True if sys.platform == "darwin" else False,
        help="Disable building in Docker",
    )
    parser.add_argument(
        "--serial",
        action="store_true",
        help="Build packages serially, without parallelism",
    )
    parser.add_argument(
        "--make-target",
        choices={"default", "toolchain"},
        default="default",
        help="The make target to evaluate",
    )

    args = parser.parse_args()

    target_triple = args.target_triple

    settings = get_target_settings(TARGETS_CONFIG, target_triple)

    supported_pythons = {
        "cpython-%s" % p
        for p in settings["pythons_supported"]
    }

    if args.python not in supported_pythons:
        print("%s only supports following Pythons: %s" %
              (target_triple, ", ".join(supported_pythons)))
        return 1

    musl = "musl" in target_triple

    env = dict(os.environ)

    env["PYBUILD_HOST_PLATFORM"] = host_platform
    env["PYBUILD_TARGET_TRIPLE"] = target_triple
    env["PYBUILD_OPTIMIZATIONS"] = args.optimizations
    if musl:
        env["PYBUILD_MUSL"] = "1"
    if args.break_on_failure:
        env["PYBUILD_BREAK_ON_FAILURE"] = "1"
    if args.no_docker:
        env["PYBUILD_NO_DOCKER"] = "1"

    entry = DOWNLOADS[args.python]
    env["PYBUILD_PYTHON_VERSION"] = entry["version"]
    env["PYBUILD_PYTHON_MAJOR_VERSION"] = ".".join(
        entry["version"].split(".")[0:2])

    if "PYBUILD_RELEASE_TAG" in os.environ:
        release_tag = os.environ["PYBUILD_RELEASE_TAG"]
    else:
        release_tag = release_tag_from_git()

    archive_components = [
        "cpython-%s" % entry["version"],
        target_triple,
        args.optimizations,
    ]

    build_basename = "-".join(archive_components) + ".tar"
    dist_basename = "-".join(archive_components + [release_tag])

    # We run make with static parallelism no greater than the machine's CPU count
    # because we can get some speedup from parallel operations. But we also don't
    # share a make job server with each build. So if we didn't limit the
    # parallelism we could easily oversaturate the CPU. Higher levels of
    # parallelism don't result in meaningful build speedups because tk/tix has
    # a long, serial dependency chain that can't be built in parallel.
    parallelism = min(1 if args.serial else 4, multiprocessing.cpu_count())

    subprocess.run(["make", "-j%d" % parallelism, args.make_target],
                   env=env,
                   check=True)

    DIST.mkdir(exist_ok=True)

    if args.make_target == "default":
        compress_python_archive(BUILD / build_basename, DIST, dist_basename)
def main():
    BUILD.mkdir(exist_ok=True)
    DOWNLOADS_PATH.mkdir(exist_ok=True)
    (BUILD / "logs").mkdir(exist_ok=True)

    if os.environ.get("PYBUILD_NO_DOCKER"):
        client = None
    else:
        try:
            client = docker.from_env()
            client.ping()
        except Exception as e:
            print("unable to connect to Docker: %s" % e)
            return 1

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--host-platform", required=True, help="Platform we are building from"
    )
    parser.add_argument(
        "--target-triple",
        required=True,
        help="Host triple that we are building Python for",
    )
    parser.add_argument(
        "--optimizations",
        choices={"debug", "noopt", "pgo", "lto", "pgo+lto"},
        required=True,
        help="Optimization profile to use",
    )
    parser.add_argument(
        "--toolchain",
        action="store_true",
        help="Indicates we are building a toolchain artifact",
    )
    parser.add_argument(
        "--dest-archive", required=True, help="Path to archive that we are producing"
    )
    parser.add_argument("--docker-image", help="Docker image to use for building")
    parser.add_argument("action")

    args = parser.parse_args()

    action = args.action

    target_triple = args.target_triple
    host_platform = args.host_platform
    optimizations = args.optimizations
    dest_archive = pathlib.Path(args.dest_archive)
    docker_image = args.docker_image

    settings = get_target_settings(TARGETS_CONFIG, target_triple)

    if args.action == "makefiles":
        log_name = "makefiles"
    elif args.action.startswith("image-"):
        log_name = "image-%s" % action
    elif args.toolchain:
        log_name = "%s-%s" % (action, host_platform)
    else:
        entry = DOWNLOADS[action]
        log_name = "%s-%s-%s-%s" % (
            action,
            entry["version"],
            target_triple,
            optimizations,
        )

    log_path = BUILD / "logs" / ("build.%s.log" % log_name)

    with log_path.open("wb") as log_fh:
        set_logger(action, log_fh)
        if action == "makefiles":
            write_triples_makefiles(get_targets(TARGETS_CONFIG), BUILD)
            write_package_versions(BUILD / "versions")

        elif action.startswith("image-"):
            build_docker_image(client, ROOT, BUILD, action[6:])

        elif action == "binutils":
            build_binutils(client, get_image(client, ROOT, BUILD, "gcc"), host_platform)

        elif action == "clang":
            build_clang(
                client,
                get_image(client, ROOT, BUILD, "clang"),
                host_platform=host_platform,
            )

        elif action == "gcc":
            build_gcc(client, get_image(client, ROOT, BUILD, "gcc"), host_platform)

        elif action == "musl":
            build_musl(client, get_image(client, ROOT, BUILD, "gcc"), host_platform)

        elif action == "libedit":
            build_libedit(
                settings,
                client,
                get_image(client, ROOT, BUILD, docker_image),
                host_platform=host_platform,
                target_triple=target_triple,
                optimizations=optimizations,
                dest_archive=dest_archive,
            )

        elif action == "readline":
            build_readline(
                settings,
                client,
                get_image(client, ROOT, BUILD, docker_image),
                host_platform=host_platform,
                target_triple=target_triple,
                optimizations=optimizations,
                dest_archive=dest_archive,
            )

        elif action in (
            "bdb",
            "bzip2",
            "gdbm",
            "gettext",
            "inputproto",
            "kbproto",
            "libffi",
            "libpthread-stubs",
            "libressl",
            "ncurses",
            "openssl",
            "sqlite",
            "tcl",
            "uuid",
            "x11-util-macros",
            "xextproto",
            "xorgproto",
            "xproto",
            "xtrans",
            "xz",
            "zlib",
        ):
            simple_build(
                settings,
                client,
                get_image(client, ROOT, BUILD, docker_image),
                action,
                host_platform=host_platform,
                target_triple=target_triple,
                optimizations=optimizations,
                dest_archive=dest_archive,
            )

        elif action == "libX11":
            simple_build(
                settings,
                client,
                get_image(client, ROOT, BUILD, docker_image),
                action,
                host_platform=host_platform,
                target_triple=target_triple,
                optimizations=optimizations,
                dest_archive=dest_archive,
                extra_archives={
                    "inputproto",
                    "kbproto",
                    "libpthread-stubs",
                    "libXau",
                    "libxcb",
                    "x11-util-macros",
                    "xextproto",
                    "xorgproto",
                    "xproto",
                    "xtrans",
                },
            )

        elif action == "libXau":
            simple_build(
                settings,
                client,
                get_image(client, ROOT, BUILD, docker_image),
                action,
                host_platform=host_platform,
                target_triple=target_triple,
                optimizations=optimizations,
                dest_archive=dest_archive,
                extra_archives={"x11-util-macros", "xproto"},
            )

        elif action == "xcb-proto":
            simple_build(
                settings,
                client,
                get_image(client, ROOT, BUILD, docker_image),
                action,
                host_platform=host_platform,
                target_triple=target_triple,
                optimizations=optimizations,
                dest_archive=dest_archive,
            )

        elif action == "libxcb":
            simple_build(
                settings,
                client,
                get_image(client, ROOT, BUILD, docker_image),
                action,
                host_platform=host_platform,
                target_triple=target_triple,
                optimizations=optimizations,
                dest_archive=dest_archive,
                extra_archives={"libpthread-stubs", "libXau", "xcb-proto", "xproto"},
            )

        elif action == "tix":
            build_tix(
                settings,
                client,
                get_image(client, ROOT, BUILD, docker_image),
                host_platform=host_platform,
                target_triple=target_triple,
                optimizations=optimizations,
                dest_archive=dest_archive,
            )

        elif action == "tk":
            extra_archives = {"tcl"}
            if host_platform != "macos":
                extra_archives |= {
                    "libX11",
                    "libXau",
                    "libxcb",
                    "xcb-proto",
                    "xorgproto",
                }

            simple_build(
                settings,
                client,
                get_image(client, ROOT, BUILD, docker_image),
                action,
                host_platform=host_platform,
                target_triple=target_triple,
                optimizations=optimizations,
                dest_archive=dest_archive,
                extra_archives=extra_archives,
            )

        elif action in ("cpython-3.8", "cpython-3.9", "cpython-3.10"):
            build_cpython(
                settings,
                client,
                get_image(client, ROOT, BUILD, docker_image),
                host_platform=host_platform,
                target_triple=target_triple,
                optimizations=optimizations,
                dest_archive=dest_archive,
                libressl="PYBUILD_LIBRESSL" in os.environ,
                version=action.split("-")[1],
            )

        else:
            print("unknown build action: %s" % action)
            return 1
def main():
    if sys.platform == "linux":
        host_platform = "linux64"
        default_target_triple = "x86_64-unknown-linux-gnu"
    elif sys.platform == "darwin":
        host_platform = "macos"
        machine = platform.machine()

        if machine == "arm64":
            default_target_triple = "aarch64-apple-darwin"
        elif machine == "x86_64":
            default_target_triple = "x86_64-apple-darwin"
        else:
            raise Exception("unhandled macOS machine value: %s" % machine)
    else:
        print("unsupport build platform: %s" % sys.platform)
        return 1

    parser = argparse.ArgumentParser()

    parser.add_argument(
        "--target-triple",
        default=default_target_triple,
        choices=supported_targets(TARGETS_CONFIG),
        help="Target host triple to build for",
    )

    parser.add_argument(
        "--optimizations",
        choices={"debug", "noopt", "pgo", "lto", "pgo+lto"},
        default="noopt",
        help="Optimizations to apply when compiling Python",
    )

    parser.add_argument("--libressl",
                        action="store_true",
                        help="Build LibreSSL instead of OpenSSL")
    parser.add_argument(
        "--python",
        choices={"cpython-3.8", "cpython-3.9", "cpython-3.10"},
        default="cpython-3.9",
        help="Python distribution to build",
    )
    parser.add_argument(
        "--break-on-failure",
        action="store_true",
        help="Enter a Python debugger if an error occurs",
    )
    parser.add_argument(
        "--no-docker",
        action="store_true",
        default=True if sys.platform == "darwin" else False,
        help="Disable building in Docker",
    )
    parser.add_argument(
        "--skip-toolchain",
        action="store_true",
        help=
        "Skip building the toolchain (requires a tar file in expected location)",
    )
    parser.add_argument(
        "--make-target",
        choices={"default", "toolchain"},
        default="default",
        help="The make target to evaluate",
    )

    args = parser.parse_args()

    target_triple = args.target_triple

    settings = get_target_settings(TARGETS_CONFIG, target_triple)

    if args.python not in settings["pythons_supported"]:
        print("%s only supports following Pythons: %s" %
              (target_triple, ", ".join(settings["pythons_supported"])))
        return 1

    musl = "musl" in target_triple

    env = dict(os.environ)

    env["PYBUILD_HOST_PLATFORM"] = host_platform
    env["PYBUILD_TARGET_TRIPLE"] = target_triple
    env["PYBUILD_OPTIMIZATIONS"] = args.optimizations
    if args.libressl or musl:
        env["PYBUILD_LIBRESSL"] = "1"
    if musl:
        env["PYBUILD_MUSL"] = "1"
    if args.break_on_failure:
        env["PYBUILD_BREAK_ON_FAILURE"] = "1"
    if args.no_docker:
        env["PYBUILD_NO_DOCKER"] = "1"
    if args.skip_toolchain:
        env["PYBUILD_SKIP_TOOLCHAIN"] = "1"

    entry = DOWNLOADS[args.python]
    env["PYBUILD_PYTHON_VERSION"] = entry["version"]
    env["PYBUILD_PYTHON_MAJOR_VERSION"] = ".".join(
        entry["version"].split(".")[0:2])

    if "PYBUILD_RELEASE_TAG" in os.environ:
        release_tag = os.environ["PYBUILD_RELEASE_TAG"]
    else:
        release_tag = release_tag_from_git()

    archive_components = [
        "cpython-%s" % entry["version"],
        target_triple,
        args.optimizations,
    ]

    build_basename = "-".join(archive_components) + ".tar"
    dist_basename = "-".join(archive_components + [release_tag])

    subprocess.run(["make", args.make_target], env=env, check=True)

    DIST.mkdir(exist_ok=True)

    if args.make_target == "default":
        compress_python_archive(BUILD / build_basename, DIST, dist_basename)
def add_target_env(env, build_platform, target_triple, build_env):
    add_env_common(env)

    settings = get_target_settings(TARGETS_CONFIG, target_triple)

    env["HOST_CC"] = settings["host_cc"]
    env["CC"] = settings["target_cc"]

    env["PYBUILD_PLATFORM"] = build_platform
    env["TOOLS_PATH"] = build_env.tools_path

    extra_target_cflags = list(settings.get("target_cflags", []))
    extra_target_ldflags = list(settings.get("target_ldflags", []))
    extra_host_cflags = []
    extra_host_ldflags = []

    if build_platform == "linux64":
        env["BUILD_TRIPLE"] = "x86_64-unknown-linux-gnu"

        # TODO should the musl target be normalized?
        if target_triple == "x86_64-unknown-linux-musl":
            env["TARGET_TRIPLE"] = "x86_64-unknown-linux-gnu"
        else:
            env["TARGET_TRIPLE"] = target_triple

    if build_platform == "macos":
        machine = platform.machine()

        if machine == "arm64":
            env["BUILD_TRIPLE"] = "aarch64-apple-darwin"
        elif machine == "x86_64":
            env["BUILD_TRIPLE"] = "x86_64-apple-darwin"
        else:
            raise Exception("unhandled macOS machine value: %s" % machine)

        # Sniff out the Apple SDK minimum deployment target from cflags and
        # export in its own variable. This is used by CPython's configure, as
        # it doesn't sniff the cflag.
        for flag in extra_target_cflags:
            m = re.search("-version-min=(.*)$", flag)
            if m:
                env["APPLE_MIN_DEPLOYMENT_TARGET"] = m.group(1)
                break
        else:
            raise Exception("could not find minimum Apple SDK version in cflags")

        sdk_platform = settings["apple_sdk_platform"]
        env["APPLE_SDK_PLATFORM"] = sdk_platform

        env["TARGET_TRIPLE"] = target_triple

        # We don't have build isolation on macOS. We nerf PATH to prevent
        # non-system (e.g. Homebrew) executables from being used.
        env["PATH"] = "/usr/bin:/bin"

        if "APPLE_SDK_PATH" in os.environ:
            sdk_path = os.environ["APPLE_SDK_PATH"]
        else:
            # macOS SDK has historically been in /usr courtesy of an
            # installer provided by Xcode. But with Catalina, the files
            # are now typically in
            # /Applications/Xcode.app/Contents/Developer/Platforms/.
            # The proper way to resolve this path is with xcrun, which
            # will give us the headers that Xcode is configured to use.
            res = subprocess.run(
                ["xcrun", "--sdk", sdk_platform, "--show-sdk-path"],
                check=True,
                capture_output=True,
                encoding="utf-8",
            )

            sdk_path = res.stdout.strip()

        if not os.path.exists(sdk_path):
            raise Exception("macOS SDK path %s does not exist" % sdk_path)

        # Grab the version from the SDK so we can put it in PYTHON.json.
        sdk_settings_path = pathlib.Path(sdk_path) / "SDKSettings.json"
        with sdk_settings_path.open("rb") as fh:
            sdk_settings = json.load(fh)
            env["APPLE_SDK_VERSION"] = sdk_settings["Version"]
            env["APPLE_SDK_CANONICAL_NAME"] = sdk_settings["CanonicalName"]

        extra_target_cflags.extend(["-isysroot", sdk_path])
        extra_target_ldflags.extend(["-isysroot", sdk_path])

        # The host SDK may be for a different platform from the target SDK.
        # Resolve that separately.
        if "APPLE_HOST_SDK_PATH" in os.environ:
            host_sdk_path = os.environ["APPLE_HOST_SDK_PATH"]
        else:
            host_sdk_path = subprocess.run(
                ["xcrun", "--show-sdk-path"],
                check=True,
                capture_output=True,
                encoding="utf-8",
            ).stdout.strip()

        if not os.path.exists(host_sdk_path):
            raise Exception("macOS host SDK path %s does not exist" % host_sdk_path)

        extra_host_cflags.extend(["-isysroot", host_sdk_path])
        extra_host_ldflags.extend(["-isysroot", host_sdk_path])

    env["EXTRA_HOST_CFLAGS"] = " ".join(extra_host_cflags)
    env["EXTRA_HOST_LDFLAGS"] = " ".join(extra_host_ldflags)
    env["EXTRA_TARGET_CFLAGS"] = " ".join(extra_target_cflags)
    env["EXTRA_TARGET_LDFLAGS"] = " ".join(extra_target_ldflags)