# Copyright 2023 Oliver Smith # SPDX-License-Identifier: GPL-3.0-or-later import enum import os from pathlib import Path from pmb.core.arch import Arch from pmb.core.context import get_context from pmb.helpers import logging from typing import Any import pmb.build import pmb.build.autodetect import pmb.build.checksum import pmb.chroot import pmb.chroot.apk import pmb.chroot.other import pmb.helpers.pmaports import pmb.helpers.run import pmb.parse from pmb.core import Chroot from pmb.types import Apkbuild, Env class KConfigUI(enum.Enum): MENUCONFIG = "menuconfig" XCONFIG = "xconfig" NCONFIG = "nconfig" def is_graphical(self) -> bool: match self: case KConfigUI.MENUCONFIG | KConfigUI.NCONFIG: return False case KConfigUI.XCONFIG: return True def depends(self) -> list[str]: match self: case KConfigUI.MENUCONFIG: return ["ncurses-dev"] case KConfigUI.NCONFIG: return ["ncurses-dev"] case KConfigUI.XCONFIG: return ["qt5-qtbase-dev", "font-noto"] def __str__(self) -> str: return self.value def get_arch(apkbuild: Apkbuild) -> Arch: """Take the architecture from the APKBUILD or complain if it's ambiguous. This function only gets called if --arch is not set. :param apkbuild: looks like: {"pkgname": "linux-...", "arch": ["x86_64", "armhf", "aarch64"]} or: {"pkgname": "linux-...", "arch": ["armhf"]} """ pkgname = apkbuild["pkgname"] # Disabled package (arch="") if not apkbuild["arch"]: raise RuntimeError( f"'{pkgname}' is disabled (arch=\"\"). Please use" " '--arch' to specify the desired architecture." ) # Multiple architectures if len(apkbuild["arch"]) > 1: raise RuntimeError( f"'{pkgname}' supports multiple architectures" f" ({', '.join(apkbuild['arch'])}). Please use" " '--arch' to specify the desired architecture." ) return Arch.from_str(apkbuild["arch"][0]) def get_outputdir(pkgname: str, apkbuild: Apkbuild) -> Path: """Get the folder for the kernel compilation output. For most APKBUILDs, this is $builddir. But some older ones still use $srcdir/build (see the discussion in #1551). """ # Old style ($srcdir/build) ret = Path("/home/pmos/build/src/build") chroot = Chroot.native() if os.path.exists(chroot / ret / ".config"): logging.warning("*****") logging.warning( "NOTE: The code in this linux APKBUILD is pretty old." " Consider making a backup and migrating to a modern" " version with: pmbootstrap aportgen " + pkgname ) logging.warning("*****") return ret # New style ($builddir) cmd = "srcdir=/home/pmos/build/src source APKBUILD; echo $builddir" ret = Path( pmb.chroot.user( ["sh", "-c", cmd], chroot, Path("/home/pmos/build"), output_return=True ).rstrip() ) if (chroot / ret / ".config").exists(): return ret # Some Mediatek kernels use a 'kernel' subdirectory if (chroot / ret / "kernel/.config").exists(): return ret / "kernel" # Out-of-tree builds ($_outdir) if (chroot / ret / apkbuild["_outdir"] / ".config").exists(): return ret / apkbuild["_outdir"] # Not found raise RuntimeError( "Could not find the kernel config. Consider making a" " backup of your APKBUILD and recreating it from the" " template with: pmbootstrap aportgen " + pkgname ) def extract_and_patch_sources(pkgname: str, arch: Arch) -> None: pmb.build.copy_to_buildpath(pkgname) logging.info("(native) extract kernel source") pmb.chroot.user(["abuild", "unpack"], working_dir=Path("/home/pmos/build")) logging.info("(native) apply patches") pmb.chroot.user( ["abuild", "prepare"], working_dir=Path("/home/pmos/build"), output="interactive", env={"CARCH": str(arch)}, ) def _make( chroot: pmb.core.Chroot, make_command: str, env: Env, pkgname: str, arch: Arch, apkbuild: Apkbuild, ) -> None: aport = pmb.helpers.pmaports.find(pkgname) outputdir = get_outputdir(pkgname, apkbuild) logging.info("(native) make " + make_command) pmb.chroot.user(["make", str(make_command)], chroot, outputdir, output="tui", env=env) # Find the updated config source = Chroot.native() / outputdir / ".config" if not source.exists(): raise RuntimeError(f"No kernel config generated: {source}") # Update the aport (config and checksum) logging.info("Copy kernel config back to pmaports dir") config = f"config-{apkbuild['_flavor']}.{arch}" target = aport / config pmb.helpers.run.user(["cp", source, target]) pmb.build.checksum.update(pkgname) def _init(pkgname: str, arch: Arch | None) -> tuple[str, Arch, Any, Chroot, Env]: """ :returns: pkgname, arch, apkbuild, chroot, env """ # Pkgname: allow omitting "linux-" prefix if not pkgname.startswith("linux-"): pkgname = "linux-" + pkgname aport = pmb.helpers.pmaports.find(pkgname) apkbuild = pmb.parse.apkbuild(aport / "APKBUILD") if arch is None: arch = get_arch(apkbuild) cross = pmb.build.autodetect.crosscompile(apkbuild, arch) chroot = cross.build_chroot(arch) hostspec = arch.alpine_triple() # Set up build tools and makedepends pmb.build.init(chroot) if cross: pmb.build.init_compiler(get_context(), [], cross, arch) depends = apkbuild["makedepends"] + ["gcc", "make"] pmb.chroot.apk.install(depends, chroot) extract_and_patch_sources(pkgname, arch) env: Env = { "ARCH": arch.kernel(), } if cross: env["CROSS_COMPILE"] = f"{hostspec}-" env["CC"] = f"{hostspec}-gcc" return pkgname, arch, apkbuild, chroot, env def migrate_config(pkgname: str, arch: Arch | None) -> None: pkgname, arch, apkbuild, chroot, env = _init(pkgname, arch) _make(chroot, "oldconfig", env, pkgname, arch, apkbuild) def edit_config(pkgname: str, arch: Arch | None, config_ui: KConfigUI) -> None: pkgname, arch, apkbuild, chroot, env = _init(pkgname, arch) pmb.chroot.apk.install(config_ui.depends(), chroot) # Copy host's .xauthority into native if config_ui.is_graphical(): pmb.chroot.other.copy_xauthority(chroot) env["DISPLAY"] = os.environ.get("DISPLAY") or ":0" env["XAUTHORITY"] = "/home/pmos/.Xauthority" # Check for background color variable color = os.environ.get("MENUCONFIG_COLOR") if color: env["MENUCONFIG_COLOR"] = color mode = os.environ.get("MENUCONFIG_MODE") if mode: env["MENUCONFIG_MODE"] = mode _make(chroot, str(config_ui), env, pkgname, arch, apkbuild)