[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/winpython/winpython/master/build_winpython_meta.py [Back]  [Original]

# build_winpython_meta.py
# Local builds are made with Python 3.11 or later.
import os
import sys
import tomllib  # stdlib since 3.11; we choose toml over yaml, more python standard
import subprocess
from pathlib import Path

# Flavor paths are names, not full paths: these say what they hang from.
UNDER_BASEDIR = ("requirements", "source_dirs", "wheelhousereq")
UNDER_ROOT = ("toolsdirs",)

def load_builds(config_file):
    with open(config_file, "rb") as f:
        config = tomllib.load(f)
    python_versions = config.get("pythons", {})
    # A file may still spell out one [[builds]] block per build; otherwise the
    # builds are the (python, flavor) pairs [pythons] asks for.
    builds = config["builds"] if "builds" in config else expand_builds(config)
    return builds, python_versions

def expand_builds(config):
    """One build per flavor listed by each [pythons."3XX"], paths derived.

    A flavor names its files relative to the build directory, so a new Python
    minor is one [pythons] block. Where a Python needs its own file - a
    requirements list under another name, say - [pythons."3XX".overrides.flavor]
    replaces that flavor's entries for that Python alone.
    """
    defaults = config.get("defaults", {})
    flavors = config.get("flavors", {})
    builds = []
    for target, vinfo in config.get("pythons", {}).items():
        root = Path(vinfo.get("root_dir_for_builds", defaults.get("root_dir_for_builds", "")))
        basedir = root / f"bd{target}"
        overrides = vinfo.get("overrides", {})
        for flavor in vinfo.get("builds", []):
            if flavor not in flavors:
                raise KeyError(f"python {target} builds {flavor!r}, which has no [flavors.{flavor}]")
            build = {**defaults, "name": flavor, "python_target": target, "flavor": flavor}
            for key, value in {**flavors[flavor], **overrides.get(flavor, {})}.items():
                if key in UNDER_BASEDIR:
                    value = str(basedir / value)
                elif key in UNDER_ROOT:
                    value = str(root / value)
                build[key] = value
            builds.append(build)
    return builds

def select_builds(builds, wanted):
    """Builds asked for on the command line: "315", "315:slim", ":slim"."""
    if not wanted:
        return builds
    kept = []
    for spec in wanted:
        target, _, flavor = spec.partition(":")
        matching = [b for b in builds
                    if (not target or b["python_target"] == target)
                    and (not flavor or b["flavor"] == flavor)]
        if not matching:
            raise SystemExit(f"no build matches {spec!r}")
        kept += [b for b in matching if b not in kept]
    return kept

def must_exist(path, build, key):
    """pip_install() skips a missing requirements file without failing, which
    would drop those packages silently. Say so instead."""
    if path and not Path(path).exists():
        raise FileNotFoundError(f"build {build['name']!r} needs {key} = {path!r}, which does not exist")
    return path

def declared_file(build, key, default):
    """Path named by the build for `key`, else `default`."""
    declared = build.get(key)
    return str(default) if declared is None else must_exist(str(declared), build, key)

def run_build(build, python_versions, dry_run=False):
    print(f"\n=== Building WinPython: {build['python_target']} {build['name']} ===")
    print(build)

    root_dir_for_builds = build["root_dir_for_builds"]
    my_python_target = build["python_target"]
    my_flavor = build["flavor"]
    my_arch = str(build["arch"])
    my_create_installer = build.get("create_installer", "True")
    my_requirements = must_exist(build.get("requirements", ""), build, "requirements")
    my_source_dirs = build.get("source_dirs", "")
    my_find_links = build.get("find_links", "")
    my_toolsdirs = build.get("toolsdirs", "")
    wheelhousereq = must_exist(build.get("wheelhousereq", ""), build, "wheelhousereq")
    # "pip" (default, what ships) | "none" | "parallel" | "parallel-N"
    my_bytecode = build.get("bytecode", "pip")

    # Get Python release info from TOML [pythons]
    py_target = my_python_target
    vinfo = python_versions.get(py_target, {})
    my_python_target_release = vinfo.get("python_target_release", "")
    my_release = vinfo.get("release", "")
    my_release_level = vinfo.get("my_release_level", "b0")
    # A build may name its own file for a specific problem; otherwise the one
    # shipped beside this script is used.
    here = Path(__file__).parent
    mandatory_requirements = declared_file(build, "mandatory_requirements", here / "mandatory_requirements.txt")
    my_constraints = declared_file(build, "constraints", here / "constraints.txt")

    # Build directory logic
    my_basedir = f"{root_dir_for_builds}\\bd{my_python_target}"
    my_WINPYDIRBASE = f"{my_basedir}\\bu{my_flavor}\\WPy{my_arch}-{my_python_target_release}{my_release}{my_release_level}"

    # Build env paths (customize as needed) already defined per the launcher of that script...
 
    my_python_exe = Path(sys.executable)
    my_buildenvi = str(my_python_exe.parent)

    my_archive_dir = os.path.join(os.getcwd(), "WinPython_build_logs")
    os.makedirs(my_archive_dir, exist_ok=True)

    # Build command
    build_cmd = [
        str(my_python_exe),
        "-m", "winpython.build_winpython",
        "--buildenv", my_buildenvi,
        "--python-target", my_python_target,
        "--release", my_release,
        "--release-level", my_release_level,
        "--winpydirbase", my_WINPYDIRBASE,
        "--flavor", my_flavor,
        "--source_dirs", my_source_dirs,
        "--tools_dirs", my_toolsdirs,
        "--log-dir", my_archive_dir,
        "--mandatory-req", mandatory_requirements,
        "--requirements", my_requirements,
        "--constraints", my_constraints,
        "--find-links", my_find_links,
        "--wheelhousereq", wheelhousereq,
        "--bytecode", my_bytecode,
        "--create-installer", my_create_installer,
    ]

    print("Dry run, build command:" if dry_run else "Running build command:")
    print(" ".join(build_cmd))
    if not dry_run:
        subprocess.run(build_cmd, check=False)

def main():
    args = [a for a in sys.argv[1:] if a != "--dry-run"]
    dry_run = len(args) != len(sys.argv) - 1
    config_file = args[0] if args else "winpython_builds.toml"
    builds, python_versions = load_builds(config_file)
    for build in select_builds(builds, args[1:]):
        run_build(build, python_versions, dry_run)

if __name__ == "__main__":
    main()

Web Proxy Viewer  |  New URL  |  Original Page