From dcc4137ee881e794b670bfcc9ee66241236ce5f8 Mon Sep 17 00:00:00 2001 From: Caleb Connolly <caleb@postmarketos.org> Date: Sun, 16 Mar 2025 15:10:30 +0000 Subject: [PATCH] FIXUP: build: abstract CrossCompile type logic * Make CrossCompile a proper enum type rather than a string literal, * Introduce methods to get the correct host/build chroots depending on the cross compile type and target architecture. * Remove autodetect.chroot() since it doesn't do what we expect, adjust all users to use cross.build_chroot() instead. * Refactor package building to correctly use cross.host_chroot() and cross.build_chroot(). Signed-off-by: Caleb Connolly <caleb@postmarketos.org> Part-of: https://gitlab.postmarketos.org/postmarketOS/pmbootstrap/-/merge_requests/2568 --- pmb/build/_package.py | 46 +++++++++++++++------------------ pmb/build/autodetect.py | 27 ++++++------------- pmb/build/backend.py | 46 ++++++++++++++++++--------------- pmb/build/envkernel.py | 5 +--- pmb/build/init.py | 8 +++--- pmb/build/kconfig.py | 2 +- pmb/types.py | 57 +++++++++++++++++++++++++++++++++++------ 7 files changed, 107 insertions(+), 84 deletions(-) diff --git a/pmb/build/_package.py b/pmb/build/_package.py index 0ad7fb0da..401a815fd 100644 --- a/pmb/build/_package.py +++ b/pmb/build/_package.py @@ -10,7 +10,7 @@ from pmb.core.arch import Arch from pmb.core.context import Context from pmb.core.pkgrepo import pkgrepo_relative_path from pmb.helpers import logging -from pmb.types import Apkbuild, CrossCompileType +from pmb.types import Apkbuild, CrossCompile from pathlib import Path import pmb.build @@ -217,8 +217,7 @@ class BuildQueueItem(TypedDict): output_path: Path channel: str depends: list[str] - cross: CrossCompileType - chroot: Chroot + cross: CrossCompile def has_cyclical_dependency( @@ -493,7 +492,7 @@ def packages( aports: Path, apkbuild: dict[str, Any], depends: list[str], - cross: CrossCompileType = "autodetect", + cross: CrossCompile | None = None, ) -> list[str]: # Skip if already queued name = apkbuild["pkgname"] @@ -540,7 +539,6 @@ def packages( ), "channel": channel, "depends": depends, - "chroot": chroot, "cross": cross, } ) @@ -613,16 +611,19 @@ def packages( " build the package with --src again." ) - cross = "autodetect" - prev_cross = "autodetect" + cross = None + prev_cross = None hostchroot = None # buildroot for the architecture we're building for total_pkgs = len(build_queue) count = 0 for pkg in build_queue: count += 1 - hostchroot = chroot = pkg["chroot"] + prev_cross = cross + cross = pkg["cross"] pkg_arch = pkg["arch"] + hostchroot = cross.host_chroot(pkg_arch) + buildchroot = cross.build_chroot(pkg_arch) apkbuild = pkg["apkbuild"] channel = pkg["channel"] @@ -648,25 +649,19 @@ def packages( ) ) - # (re)-initialize the cross compiler stuff when cross method changes - prev_cross = cross - cross = pmb.build.autodetect.crosscompile(pkg["apkbuild"], pkg_arch) - if cross == "cross-native2" or cross == "cross-native": - chroot = Chroot.native() - # One time chroot initialization - if hostchroot != chroot: + if hostchroot != buildchroot: pmb.build.init(hostchroot) - if pmb.build.init(chroot): - pmb.build.other.configure_abuild(chroot) - pmb.build.other.configure_ccache(chroot) + if pmb.build.init(buildchroot): + pmb.build.other.configure_abuild(buildchroot) + pmb.build.other.configure_ccache(buildchroot) if "rust" in all_dependencies or "cargo" in all_dependencies: - pmb.chroot.apk.install(["sccache"], chroot) + pmb.chroot.apk.install(["sccache"], buildchroot) - if cross != prev_cross and cross not in ["unnecessary", "qemu-only"]: + if cross != prev_cross and cross.enabled(): pmb.build.init_compiler(context, pkg_depends, cross, pkg_arch) - if cross == "crossdirect": - pmb.chroot.mount_native_into_foreign(chroot) + if cross == CrossCompile.CROSSDIRECT: + pmb.chroot.mount_native_into_foreign(buildchroot) depends_build: list[str] = [] depends_host: list[str] = [] @@ -702,11 +697,11 @@ def packages( logging.info("*** Install build dependencies") if src: depends_build.append("rsync") - pmb.chroot.apk.install(depends_build, chroot, build=False) + pmb.chroot.apk.install(depends_build, buildchroot, build=False) # Build and finish up msg = f"@YELLOW@=>@END@ @BLUE@{channel}/{pkg['name']}@END@: Building package" - if cross != "unnecessary": + if cross != CrossCompile.UNNECESSARY: msg += f" (cross compiling: {cross})" logging.info(msg) @@ -720,13 +715,12 @@ def packages( cross, strict, force, - hostchroot, src, bootstrap_stage, ) except RuntimeError: raise BuildFailedError(f"Couldn't build {output}!") - finish(pkg["apkbuild"], channel, pkg_arch, output, chroot, strict) + finish(pkg["apkbuild"], channel, pkg_arch, output, buildchroot, strict) # Clear package cache for the next run _package_cache = {} diff --git a/pmb/build/autodetect.py b/pmb/build/autodetect.py index b42df7b52..b0bde3925 100644 --- a/pmb/build/autodetect.py +++ b/pmb/build/autodetect.py @@ -7,10 +7,9 @@ from pmb.helpers import logging import pmb.config import pmb.chroot.apk import pmb.helpers.pmaports -from pmb.core import Chroot from pmb.core.context import get_context from pmb.meta import Cache -from pmb.types import Apkbuild, CrossCompileType +from pmb.types import Apkbuild, CrossCompile def arch_from_deviceinfo(pkgname: str, aport: Path) -> Arch | None: @@ -82,26 +81,16 @@ def arch(package: str | Apkbuild) -> Arch: return Arch.native() -def chroot(apkbuild: Apkbuild, arch: Arch) -> Chroot: - if arch == Arch.native(): - return Chroot.native() - - if "pmb:cross-native" in apkbuild["options"]: - return Chroot.native() - - return Chroot.buildroot(arch) - - -def crosscompile(apkbuild: Apkbuild, arch: Arch) -> CrossCompileType: +def crosscompile(apkbuild: Apkbuild, arch: Arch) -> CrossCompile: """Decide the type of compilation necessary to build a given APKBUILD.""" if not get_context().cross: - return "qemu-only" + return CrossCompile.QEMU_ONLY if not arch.cpu_emulation_required(): - return "unnecessary" + return CrossCompile.UNNECESSARY if "pmb:cross-native" in apkbuild["options"]: - return "cross-native" + return CrossCompile.CROSS_NATIVE if arch.is_native() or "pmb:cross-native2" in apkbuild["options"]: - return "cross-native2" + return CrossCompile.CROSS_NATIVE2 if "!pmb:crossdirect" in apkbuild["options"]: - return "qemu-only" - return "crossdirect" + return CrossCompile.QEMU_ONLY + return CrossCompile.CROSSDIRECT diff --git a/pmb/build/backend.py b/pmb/build/backend.py index 752c2c97c..ad426039c 100644 --- a/pmb/build/backend.py +++ b/pmb/build/backend.py @@ -11,7 +11,7 @@ from pmb.core import Context from pmb.core.arch import Arch from pmb.core.chroot import Chroot from pmb.helpers import logging -from pmb.types import Apkbuild, CrossCompileType, Env +from pmb.types import Apkbuild, CrossCompile, Env class BootstrapStage(enum.IntEnum): @@ -190,10 +190,9 @@ def run_abuild( pkgver: str, channel: str, arch: Arch, - cross: CrossCompileType, + cross: CrossCompile, strict: bool = False, force: bool = False, - hostchroot: Chroot = Chroot.native(), src: str | None = None, bootstrap_stage: int = BootstrapStage.NONE, ) -> None: @@ -210,18 +209,23 @@ def run_abuild( the environment variables dict generated in this function. """ # Sanity check - if cross == "cross-native" and "!tracedeps" not in apkbuild["options"]: + if cross == CrossCompile.CROSS_NATIVE and "!tracedeps" not in apkbuild["options"]: logging.warning( "WARNING: Option !tracedeps is not set, but cross compiling with" " cross-native (version 1). This will probably fail!" ) + hostchroot = cross.host_chroot(arch) + buildchroot = cross.build_chroot(arch) + # For cross-native2 compilation, bindmount the "host" rootfs to /mnt/sysroot # it will be used as the "sysroot" - if cross == "cross-native2": - pmb.mount.bind(hostchroot.path, Chroot.native() / "/mnt/sysroot", umount=True) - - chroot = Chroot.native() if cross == "cross-native2" else hostchroot + if cross == CrossCompile.CROSS_NATIVE2: + if buildchroot != Chroot.native(): + raise ValueError( + "Trying to use cross-native2 build buildchroot != native! This is a bug" + ) + pmb.mount.bind(hostchroot.path, buildchroot / "/mnt/sysroot", umount=True) pkgdir = context.config.work / "packages" / channel if not pkgdir.exists(): @@ -241,16 +245,16 @@ def run_abuild( ["rm", "-f", "/home/pmos/packages/pmos"], ["ln", "-sf", f"/mnt/pmbootstrap/packages/{channel}", "/home/pmos/packages/pmos"], ], - chroot, + buildchroot, ) # Environment variables env: Env = {"SUDO_APK": "abuild-apk --no-progress"} - if cross == "cross-native": + if cross == CrossCompile.CROSS_NATIVE: hostspec = arch.alpine_triple() env["CROSS_COMPILE"] = hostspec + "-" env["CC"] = hostspec + "-gcc" - if cross == "cross-native2": + if cross == CrossCompile.CROSS_NATIVE2: env["CHOST"] = str(arch) env["CBUILDROOT"] = "/mnt/sysroot" env["CFLAGS"] = "-Wl,-rpath-link=/mnt/sysroot/usr/lib" @@ -261,7 +265,7 @@ def run_abuild( except ValueError: logging.debug(f"Not setting $GOARCH for {arch}") - elif cross == "crossdirect": + elif cross == CrossCompile.CROSSDIRECT: env["PATH"] = ":".join([f"/native/usr/lib/crossdirect/{arch}", pmb.config.chroot_path]) else: env["CARCH"] = str(arch) @@ -269,7 +273,7 @@ def run_abuild( env["CCACHE_DISABLE"] = "1" # Use sccache without crossdirect (crossdirect uses it via rustc.sh) - if context.ccache and cross != "crossdirect": + if context.ccache and cross != CrossCompile.CROSSDIRECT: env["RUSTC_WRAPPER"] = "/usr/bin/sccache" # Cache binary objects from go in this path (like ccache) @@ -300,16 +304,16 @@ def run_abuild( cmd += ["-K"] # Copy the aport to the chroot and build it - pmb.build.copy_to_buildpath(apkbuild["pkgname"], chroot, no_override=strict) + pmb.build.copy_to_buildpath(apkbuild["pkgname"], buildchroot, no_override=strict) if src and strict: - logging.debug(f"({chroot}) Ensuring previous build artifacts are removed") - pmb.chroot.root(["rm", "-rf", "/tmp/pmbootstrap-local-source-copy"], chroot) - override_source(apkbuild, pkgver, src, chroot) - link_to_git_dir(chroot) + logging.debug(f"({buildchroot}) Ensuring previous build artifacts are removed") + pmb.chroot.root(["rm", "-rf", "/tmp/pmbootstrap-local-source-copy"], buildchroot) + override_source(apkbuild, pkgver, src, buildchroot) + link_to_git_dir(buildchroot) try: - pmb.chroot.user(cmd, chroot, Path("/home/pmos/build"), env=env) + pmb.chroot.user(cmd, buildchroot, Path("/home/pmos/build"), env=env) finally: - handle_csum_failure(apkbuild, chroot) + handle_csum_failure(apkbuild, buildchroot) - pmb.helpers.run.root(["umount", Chroot.native() / "/mnt/sysroot"], output="null", check=False) + pmb.helpers.run.root(["umount", buildchroot / "/mnt/sysroot"], output="null", check=False) diff --git a/pmb/build/envkernel.py b/pmb/build/envkernel.py index 095995c40..a16ab2a9c 100644 --- a/pmb/build/envkernel.py +++ b/pmb/build/envkernel.py @@ -247,10 +247,7 @@ def package_kernel(args: PmbArgs) -> None: if not kbuild_out: kbuild_out = ".output" - if "pmb:cross-native" in apkbuild["options"]: - chroot = Chroot.native() - else: - chroot = pmb.build.autodetect.chroot(apkbuild, arch) + chroot = Chroot.native() # Install package dependencies depends = pmb.build.get_depends(context, apkbuild) diff --git a/pmb/build/init.py b/pmb/build/init.py index 3089aa1dd..58e15535b 100644 --- a/pmb/build/init.py +++ b/pmb/build/init.py @@ -13,7 +13,7 @@ import pmb.chroot.apk import pmb.helpers.run from pmb.core import Chroot from pmb.core.context import get_context -from pmb.types import CrossCompileType +from pmb.types import CrossCompile def init_abuild_minimal(chroot: Chroot = Chroot.native(), build_pkgs: list[str] = []) -> None: @@ -112,9 +112,7 @@ def init(chroot: Chroot = Chroot.native()) -> bool: return True -def init_compiler( - context: Context, depends: list[str], cross: CrossCompileType, arch: Arch -) -> None: +def init_compiler(context: Context, depends: list[str], cross: CrossCompile, arch: Arch) -> None: arch_str = str(arch) cross_pkgs = ["ccache-cross-symlinks", "abuild"] if "gcc4" in depends: @@ -125,7 +123,7 @@ def init_compiler( cross_pkgs += ["gcc-" + arch_str, "g++-" + arch_str] if "clang" in depends or "clang-dev" in depends: cross_pkgs += ["clang"] - if cross == "crossdirect": + if cross == CrossCompile.CROSSDIRECT: cross_pkgs += ["crossdirect"] if "rust" in depends or "cargo" in depends: if context.ccache: diff --git a/pmb/build/kconfig.py b/pmb/build/kconfig.py index 0dd5ce47b..d5da0bd53 100644 --- a/pmb/build/kconfig.py +++ b/pmb/build/kconfig.py @@ -177,8 +177,8 @@ def _init(pkgname: str, arch: Arch | None) -> tuple[str, Arch, Any, Chroot, Env] if arch is None: arch = get_arch(apkbuild) - chroot = pmb.build.autodetect.chroot(apkbuild, arch) cross = pmb.build.autodetect.crosscompile(apkbuild, arch) + chroot = cross.build_chroot(arch) hostspec = arch.alpine_triple() # Set up build tools and makedepends diff --git a/pmb/types.py b/pmb/types.py index bfbe59d4c..2edaa3b29 100644 --- a/pmb/types.py +++ b/pmb/types.py @@ -1,21 +1,62 @@ # Copyright 2024 Caleb Connolly # SPDX-License-Identifier: GPL-3.0-or-later +import enum import subprocess from argparse import Namespace from pathlib import Path from typing import Any, Literal, TypedDict from pmb.core.arch import Arch +from pmb.core.chroot import Chroot + + +class CrossCompile(enum.Enum): + # Cross compilation isn't needed for this package + UNNECESSARY = "unnecessary" + # Cross compilation disabled, only use QEMU + QEMU_ONLY = "qemu-only" + # Cross compilation will use crossdirect + CROSSDIRECT = "crossdirect" + # Cross compilation will use cross-native + CROSS_NATIVE = "cross-native" + # Cross compilation will use cross-native2 + CROSS_NATIVE2 = "cross-native2" + + def __str__(self) -> str: + return self.value + + def enabled(self) -> bool: + """Are we cross-compiling for this value of cross?""" + return self not in [CrossCompile.UNNECESSARY, CrossCompile.QEMU_ONLY] + + def host_chroot(self, arch: Arch) -> Chroot: + """Chroot for the package target architecture (the "host" machine). + Cross native (v1) is the exception, since we exclusively use the native + chroot for that.""" + if arch == Arch.native(): + return Chroot.native() + + match self: + case CrossCompile.CROSS_NATIVE: + return Chroot.native() + case _: + return Chroot.buildroot(arch) + + def build_chroot(self, arch: Arch) -> Chroot: + """Chroot for the package build architecture (the "build" machine).""" + if arch == Arch.native(): + return Chroot.native() + + match self: + case CrossCompile.CROSSDIRECT | CrossCompile.QEMU_ONLY: + return Chroot.buildroot(arch) + # FIXME: are there cases where we're building for a different arch + # but don't need to cross compile? + case CrossCompile.UNNECESSARY | CrossCompile.CROSS_NATIVE | CrossCompile.CROSS_NATIVE2: + return Chroot.native() + -CrossCompileType = Literal[ - "autodetect", - "unnecessary", - "qemu-only", - "crossdirect", - "cross-native", - "cross-native2", -] RunOutputTypeDefault = Literal["log", "stdout", "interactive", "tui", "null"] RunOutputTypePopen = Literal["background", "pipe"] RunOutputType = RunOutputTypeDefault | RunOutputTypePopen -- GitLab