core: add an Arch type (MR 2252)

Move pmb/parse/arch.py over to core and refactor it as an Arch type,
similar to how Chroot was done. Fix all the uses (that I can find) of
arch in the codebase that need adjusting.

The new Arch type is an Enum, making it clear what architectures can be
represented and making it much easier to reason about. Since we support
~5 (kinda) different representations of an Architecture (Alpine, Kernel,
target triple, platform, and QEMU), we now formalise that the Alpine
format is what we represent internally, with methods to convert to any
of the others as-needed.

Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
This commit is contained in:
Caleb Connolly 2024-06-08 21:27:27 +02:00 committed by Oliver Smith
parent 505165dc13
commit 866e5bcfab
No known key found for this signature in database
GPG key ID: 5AE7F5513E0885CB
42 changed files with 389 additions and 303 deletions

View file

@ -33,7 +33,7 @@ if version < (3, 9):
def print_log_hint() -> None: def print_log_hint() -> None:
context = get_context(allow_failure=True) context = get_context(allow_failure=True)
log = context.log if context else types.Config().work / "log.txt" log = context.log if context else Config().work / "log.txt"
# Hints about the log file (print to stdout only) # Hints about the log file (print to stdout only)
log_hint = "Run 'pmbootstrap log' for details." log_hint = "Run 'pmbootstrap log' for details."
if not os.path.exists(log): if not os.path.exists(log):

View file

@ -12,7 +12,7 @@ import pmb.parse
def ask_for_architecture(): def ask_for_architecture():
architectures = pmb.config.build_device_architectures architectures = Arch.supported()
# Don't show armhf, new ports shouldn't use this architecture # Don't show armhf, new ports shouldn't use this architecture
if "armhf" in architectures: if "armhf" in architectures:
architectures.remove("armhf") architectures.remove("armhf")

View file

@ -5,6 +5,7 @@ import pmb.aportgen.core
import pmb.build import pmb.build
import pmb.chroot.apk import pmb.chroot.apk
import pmb.chroot.apk_static import pmb.chroot.apk_static
from pmb.core.arch import Arch
from pmb.types import PmbArgs from pmb.types import PmbArgs
import pmb.helpers.run import pmb.helpers.run
import pmb.parse.apkindex import pmb.parse.apkindex
@ -48,7 +49,7 @@ def generate(pkgname):
pkgdesc="GRUB $_arch EFI files for every architecture" pkgdesc="GRUB $_arch EFI files for every architecture"
url="https://www.gnu.org/software/grub/" url="https://www.gnu.org/software/grub/"
license="GPL-3.0-or-later" license="GPL-3.0-or-later"
arch="{pmb.config.arch_native}" arch="{Arch.native()}"
source="grub-efi-$pkgver-r$pkgrel-$_arch-{mirrordir}.apk::$_mirror/{mirrordir}/main/$_arch/grub-efi-$pkgver-r$pkgrel.apk" source="grub-efi-$pkgver-r$pkgrel-$_arch-{mirrordir}.apk::$_mirror/{mirrordir}/main/$_arch/grub-efi-$pkgver-r$pkgrel.apk"
package() {{ package() {{

View file

@ -5,12 +5,11 @@ from pmb.parse.deviceinfo import Deviceinfo
import pmb.helpers.run import pmb.helpers.run
import pmb.aportgen.core import pmb.aportgen.core
import pmb.parse.apkindex import pmb.parse.apkindex
import pmb.parse.arch
def generate_apkbuild(pkgname, deviceinfo: Deviceinfo, patches): def generate_apkbuild(pkgname, deviceinfo: Deviceinfo, patches):
device = "-".join(pkgname.split("-")[1:]) device = "-".join(pkgname.split("-")[1:])
carch = pmb.parse.arch.alpine_to_kernel(deviceinfo.arch) carch = deviceinfo.arch.kernel()
makedepends = ["bash", "bc", "bison", "devicepkg-dev", "findutils", "flex", makedepends = ["bash", "bc", "bison", "devicepkg-dev", "findutils", "flex",
"openssl-dev", "perl"] "openssl-dev", "perl"]

View file

@ -6,4 +6,4 @@ from pmb.build.kconfig import menuconfig
from pmb.build.newapkbuild import newapkbuild from pmb.build.newapkbuild import newapkbuild
from pmb.build.other import copy_to_buildpath, is_necessary, \ from pmb.build.other import copy_to_buildpath, is_necessary, \
index_repo index_repo
from pmb.build._package import BootstrapStage, mount_pmaports, package from pmb.build._package import BootstrapStage, mount_pmaports, package, output_path

View file

@ -2,7 +2,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import datetime import datetime
import enum import enum
from typing import Dict, List from typing import Dict, List, Optional
from pmb.core.arch import Arch
from pmb.core.context import Context from pmb.core.context import Context
from pmb.core.pkgrepo import pkgrepo_paths, pkgrepo_relative_path from pmb.core.pkgrepo import pkgrepo_paths, pkgrepo_relative_path
from pmb.helpers import logging from pmb.helpers import logging
@ -17,7 +18,6 @@ import pmb.helpers.pmaports
import pmb.helpers.repo import pmb.helpers.repo
import pmb.helpers.mount import pmb.helpers.mount
import pmb.parse import pmb.parse
import pmb.parse.arch
import pmb.parse.apkindex import pmb.parse.apkindex
from pmb.helpers.exceptions import BuildFailedError from pmb.helpers.exceptions import BuildFailedError
@ -221,7 +221,7 @@ def init_buildenv(context: Context, apkbuild, arch, strict=False, force=False, c
depends_arch = arch depends_arch = arch
if cross == "native": if cross == "native":
depends_arch = pmb.config.arch_native depends_arch = Arch.native()
# Build dependencies # Build dependencies
depends, built = build_depends(context, apkbuild, depends_arch, strict) depends, built = build_depends(context, apkbuild, depends_arch, strict)
@ -386,8 +386,9 @@ def link_to_git_dir(chroot: Chroot):
"/home/pmos/build/.git"], chroot) "/home/pmos/build/.git"], chroot)
def output_path(arch: str, pkgname: str, pkgver: str, pkgrel: str) -> Path: def output_path(arch: Arch, pkgname: str, pkgver: str, pkgrel: str) -> Path:
return Path(arch) / f"{pkgname}-{pkgver}-r{pkgrel}.apk" # Yeahp, you can just treat an Arch like a path!
return arch / f"{pkgname}-{pkgver}-r{pkgrel}.apk"
def run_abuild(context: Context, apkbuild, channel, arch, strict=False, force=False, cross=None, def run_abuild(context: Context, apkbuild, channel, arch, strict=False, force=False, cross=None,
@ -433,11 +434,11 @@ def run_abuild(context: Context, apkbuild, channel, arch, strict=False, force=Fa
env = {"CARCH": arch, env = {"CARCH": arch,
"SUDO_APK": "abuild-apk --no-progress"} "SUDO_APK": "abuild-apk --no-progress"}
if cross == "native": if cross == "native":
hostspec = pmb.parse.arch.alpine_to_hostspec(arch) hostspec = arch.alpine_triple()
env["CROSS_COMPILE"] = hostspec + "-" env["CROSS_COMPILE"] = hostspec + "-"
env["CC"] = hostspec + "-gcc" env["CC"] = hostspec + "-gcc"
if cross == "crossdirect": if cross == "crossdirect":
env["PATH"] = ":".join(["/native/usr/lib/crossdirect/" + arch, env["PATH"] = ":".join([f"/native/usr/lib/crossdirect/{arch}",
pmb.config.chroot_path]) pmb.config.chroot_path])
if not context.ccache: if not context.ccache:
env["CCACHE_DISABLE"] = "1" env["CCACHE_DISABLE"] = "1"
@ -503,7 +504,7 @@ def finish(apkbuild, channel, arch, output: str, chroot: Chroot, strict=False):
pmb.chroot.init_keys() pmb.chroot.init_keys()
def package(context: Context, pkgname, arch=None, force=False, strict=False, def package(context: Context, pkgname, arch: Optional[Arch]=None, force=False, strict=False,
skip_init_buildenv=False, src=None, skip_init_buildenv=False, src=None,
bootstrap_stage=BootstrapStage.NONE): bootstrap_stage=BootstrapStage.NONE):
""" """
@ -531,7 +532,7 @@ def package(context: Context, pkgname, arch=None, force=False, strict=False,
logging.verbose(f"{pkgname}: running pmb.build._package.package") logging.verbose(f"{pkgname}: running pmb.build._package.package")
# Once per session is enough # Once per session is enough
arch = arch or pmb.config.arch_native arch = arch or Arch.native()
# the order of checks here is intentional, # the order of checks here is intentional,
# skip_already_built() has side effects! # skip_already_built() has side effects!
if skip_already_built(pkgname, arch) and not force: if skip_already_built(pkgname, arch) and not force:

View file

@ -1,14 +1,13 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pathlib import Path from pathlib import Path
from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
from typing import Dict, Optional from typing import Dict, Optional
import pmb.config import pmb.config
import pmb.chroot.apk import pmb.chroot.apk
from pmb.types import PmbArgs
import pmb.helpers.pmaports import pmb.helpers.pmaports
import pmb.parse.arch
from pmb.core import Chroot, ChrootType, get_context from pmb.core import Chroot, ChrootType, get_context
@ -60,9 +59,9 @@ def arch(pkgname: str):
if get_context().config.build_default_device_arch: if get_context().config.build_default_device_arch:
preferred_arch = deviceinfo.arch preferred_arch = deviceinfo.arch
preferred_arch_2nd = pmb.config.arch_native preferred_arch_2nd = Arch.native()
else: else:
preferred_arch = pmb.config.arch_native preferred_arch = Arch.native()
preferred_arch_2nd = deviceinfo.arch preferred_arch_2nd = deviceinfo.arch
if "noarch" in arches or "all" in arches or preferred_arch in arches: if "noarch" in arches or "all" in arches or preferred_arch in arches:
@ -77,8 +76,8 @@ def arch(pkgname: str):
return None return None
def chroot(apkbuild: Dict[str, str], arch: str) -> Chroot: def chroot(apkbuild: Dict[str, str], arch: Arch) -> Chroot:
if arch == pmb.config.arch_native: if arch == Arch.native():
return Chroot.native() return Chroot.native()
if "pmb:cross-native" in apkbuild["options"]: if "pmb:cross-native" in apkbuild["options"]:
@ -87,13 +86,13 @@ def chroot(apkbuild: Dict[str, str], arch: str) -> Chroot:
return Chroot.buildroot(arch) return Chroot.buildroot(arch)
def crosscompile(apkbuild, arch, suffix: Chroot): def crosscompile(apkbuild, arch: Arch, suffix: Chroot):
""" """
:returns: None, "native", "crossdirect" :returns: None, "native", "crossdirect"
""" """
if not get_context().cross: if not get_context().cross:
return None return None
if not pmb.parse.arch.cpu_emulation_required(arch): if not arch.cpu_emulation_required():
return None return None
if suffix.type == ChrootType.NATIVE: if suffix.type == ChrootType.NATIVE:
return "native" return "native"

View file

@ -1,6 +1,7 @@
# Copyright 2023 Robert Yang # Copyright 2023 Robert Yang
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from typing import List from typing import List
from pmb.core.arch import Arch
from pmb.core.context import Context from pmb.core.context import Context
from pmb.helpers import logging from pmb.helpers import logging
import os import os
@ -114,7 +115,7 @@ def modify_apkbuild(pkgname: str, aport: Path):
pmb.aportgen.core.rewrite(pkgname, apkbuild_path, fields=fields) pmb.aportgen.core.rewrite(pkgname, apkbuild_path, fields=fields)
def run_abuild(context: Context, pkgname: str, arch: str, apkbuild_path: Path, kbuild_out): def run_abuild(context: Context, pkgname: str, arch: Arch, apkbuild_path: Path, kbuild_out):
""" """
Prepare build environment and run abuild. Prepare build environment and run abuild.
@ -159,9 +160,9 @@ def run_abuild(context: Context, pkgname: str, arch: str, apkbuild_path: Path, k
pmb.helpers.run.root(cmd) pmb.helpers.run.root(cmd)
# Create the apk package # Create the apk package
env = {"CARCH": arch, env = {"CARCH": str(arch),
"CHOST": arch, "CHOST": str(arch),
"CBUILD": pmb.config.arch_native, "CBUILD": Arch.native(),
"SUDO_APK": "abuild-apk --no-progress"} "SUDO_APK": "abuild-apk --no-progress"}
cmd = ["abuild", "rootpkg"] cmd = ["abuild", "rootpkg"]
pmb.chroot.user(cmd, working_dir=build_path, env=env) pmb.chroot.user(cmd, working_dir=build_path, env=env)
@ -201,15 +202,15 @@ def package_kernel(args: PmbArgs):
# Install package dependencies # Install package dependencies
depends, _ = pmb.build._package.build_depends( depends, _ = pmb.build._package.build_depends(
context, apkbuild, pmb.config.arch_native, strict=False) context, apkbuild, Arch.native(), strict=False)
pmb.build.init(chroot) pmb.build.init(chroot)
if pmb.parse.arch.cpu_emulation_required(arch): if arch.cpu_emulation_required():
depends.append("binutils-" + arch) depends.append(f"binutils-{arch}")
pmb.chroot.apk.install(depends, chroot) pmb.chroot.apk.install(depends, chroot)
output = (arch + "/" + apkbuild["pkgname"] + "-" + apkbuild["pkgver"] + output = pmb.build.output_path(arch, apkbuild["pkgname"], apkbuild["pkgver"],
"-r" + apkbuild["pkgrel"] + ".apk") apkbuild["pkgrel"])
message = f"({chroot}) build " + output message = f"({chroot}) build {output}"
logging.info(message) logging.info(message)
try: try:

View file

@ -1,5 +1,6 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pmb.core.arch import Arch
from pmb.core.context import Context from pmb.core.context import Context
from pmb.helpers import logging from pmb.helpers import logging
import os import os
@ -9,9 +10,7 @@ import pmb.build
import pmb.config import pmb.config
import pmb.chroot import pmb.chroot
import pmb.chroot.apk import pmb.chroot.apk
from pmb.types import PmbArgs
import pmb.helpers.run import pmb.helpers.run
import pmb.parse.arch
from pmb.core import Chroot, get_context from pmb.core import Chroot, get_context
@ -66,7 +65,7 @@ def init(chroot: Chroot=Chroot.native()):
apk_arch = chroot.arch apk_arch = chroot.arch
# Add apk wrapper that runs native apk and lies about arch # Add apk wrapper that runs native apk and lies about arch
if pmb.parse.arch.cpu_emulation_required(apk_arch) and \ if apk_arch.cpu_emulation_required() and \
not (chroot / "usr/local/bin/abuild-apk").exists(): not (chroot / "usr/local/bin/abuild-apk").exists():
with (chroot / "tmp/apk_wrapper.sh").open("w") as handle: with (chroot / "tmp/apk_wrapper.sh").open("w") as handle:
content = f""" content = f"""
@ -112,14 +111,15 @@ def init(chroot: Chroot=Chroot.native()):
pathlib.Path(marker).touch() pathlib.Path(marker).touch()
def init_compiler(context: Context, depends, cross, arch): def init_compiler(context: Context, depends, cross, arch: Arch):
arch_str = str(arch)
cross_pkgs = ["ccache-cross-symlinks", "abuild"] cross_pkgs = ["ccache-cross-symlinks", "abuild"]
if "gcc4" in depends: if "gcc4" in depends:
cross_pkgs += ["gcc4-" + arch] cross_pkgs += ["gcc4-" + arch_str]
elif "gcc6" in depends: elif "gcc6" in depends:
cross_pkgs += ["gcc6-" + arch] cross_pkgs += ["gcc6-" + arch_str]
else: else:
cross_pkgs += ["gcc-" + arch, "g++-" + arch] cross_pkgs += ["gcc-" + arch_str, "g++-" + arch_str]
if "clang" in depends or "clang-dev" in depends: if "clang" in depends or "clang-dev" in depends:
cross_pkgs += ["clang"] cross_pkgs += ["clang"]
if cross == "crossdirect": if cross == "crossdirect":

View file

@ -105,7 +105,7 @@ def menuconfig(args: PmbArgs, pkgname: str, use_oldconfig):
arch = args.arch or get_arch(apkbuild) arch = args.arch or get_arch(apkbuild)
chroot = pmb.build.autodetect.chroot(apkbuild, arch) chroot = pmb.build.autodetect.chroot(apkbuild, arch)
cross = pmb.build.autodetect.crosscompile(apkbuild, arch, chroot) cross = pmb.build.autodetect.crosscompile(apkbuild, arch, chroot)
hostspec = pmb.parse.arch.alpine_to_hostspec(arch) hostspec = arch.alpine_triple()
# Set up build tools and makedepends # Set up build tools and makedepends
pmb.build.init(chroot) pmb.build.init(chroot)
@ -143,7 +143,7 @@ def menuconfig(args: PmbArgs, pkgname: str, use_oldconfig):
# Run make menuconfig # Run make menuconfig
outputdir = get_outputdir(args, pkgname, apkbuild) outputdir = get_outputdir(args, pkgname, apkbuild)
logging.info("(native) make " + kopt) logging.info("(native) make " + kopt)
env = {"ARCH": pmb.parse.arch.alpine_to_kernel(arch), env = {"ARCH": arch.kernel(),
"DISPLAY": os.environ.get("DISPLAY"), "DISPLAY": os.environ.get("DISPLAY"),
"XAUTHORITY": "/home/pmos/.Xauthority"} "XAUTHORITY": "/home/pmos/.Xauthority"}
if cross: if cross:

View file

@ -8,13 +8,11 @@ import datetime
from typing import List from typing import List
import pmb.chroot import pmb.chroot
from pmb.types import PmbArgs
import pmb.build import pmb.build
import pmb.helpers.file import pmb.helpers.file
import pmb.helpers.git import pmb.helpers.git
import pmb.helpers.pmaports import pmb.helpers.pmaports
import pmb.helpers.run import pmb.helpers.run
import pmb.parse.arch
import pmb.parse.apkindex import pmb.parse.apkindex
import pmb.parse.version import pmb.parse.version
from pmb.core import Chroot, get_context from pmb.core import Chroot, get_context

View file

@ -3,7 +3,7 @@
import os import os
from pathlib import Path from pathlib import Path
import pmb.chroot.apk_static import pmb.chroot.apk_static
from pmb.core.chroot import ChrootType from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
import shlex import shlex
from typing import List from typing import List
@ -11,14 +11,12 @@ from typing import List
import pmb.build import pmb.build
import pmb.chroot import pmb.chroot
import pmb.config import pmb.config
from pmb.types import PmbArgs
import pmb.helpers.apk import pmb.helpers.apk
import pmb.helpers.other import pmb.helpers.other
import pmb.helpers.pmaports import pmb.helpers.pmaports
import pmb.helpers.repo import pmb.helpers.repo
import pmb.helpers.run import pmb.helpers.run
import pmb.parse.apkindex import pmb.parse.apkindex
import pmb.parse.arch
import pmb.parse.depends import pmb.parse.depends
import pmb.parse.version import pmb.parse.version
from pmb.core import Chroot, get_context from pmb.core import Chroot, get_context
@ -120,7 +118,7 @@ def packages_split_to_add_del(packages):
return (to_add, to_del) return (to_add, to_del)
def packages_get_locally_built_apks(packages, arch: str) -> List[Path]: def packages_get_locally_built_apks(packages, arch: Arch) -> List[Path]:
""" """
Iterate over packages and if existing, get paths to locally built packages. Iterate over packages and if existing, get paths to locally built packages.
This is used to force apk to upgrade packages to newer local versions, even This is used to force apk to upgrade packages to newer local versions, even
@ -183,6 +181,9 @@ def install_run_apk(to_add, to_add_local, to_del, chroot: Chroot):
# we expect apk.static to be installed in the native chroot (which # we expect apk.static to be installed in the native chroot (which
# will be the systemd version if building for systemd) and run # will be the systemd version if building for systemd) and run
# it from there. # it from there.
# pmb.chroot.init(Chroot.native())
# if chroot != Chroot.native():
# pmb.chroot.init(chroot)
apk_static = Chroot.native() / "sbin/apk.static" apk_static = Chroot.native() / "sbin/apk.static"
arch = chroot.arch arch = chroot.arch
apk_cache = get_context().config.work / f"cache_apk_{arch}" apk_cache = get_context().config.work / f"cache_apk_{arch}"

View file

@ -1,6 +1,7 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import os import os
from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
import shutil import shutil
import tarfile import tarfile
@ -142,7 +143,7 @@ def download(file):
""" """
channel_cfg = pmb.config.pmaports.read_config_channel() channel_cfg = pmb.config.pmaports.read_config_channel()
mirrordir = channel_cfg["mirrordir_alpine"] mirrordir = channel_cfg["mirrordir_alpine"]
base_url = f"{get_context().config.mirror_alpine}{mirrordir}/main/{pmb.config.arch_native}" base_url = f"{get_context().config.mirror_alpine}{mirrordir}/main/{Arch.native()}"
return pmb.helpers.http.download(f"{base_url}/{file}", file) return pmb.helpers.http.download(f"{base_url}/{file}", file)

View file

@ -1,26 +1,25 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import os import os
from pmb.core.arch import Arch
from pmb.core.chroot import Chroot from pmb.core.chroot import Chroot
from pmb.helpers import logging from pmb.helpers import logging
from pmb.types import PmbArgs
import pmb.helpers.run import pmb.helpers.run
import pmb.helpers.other import pmb.helpers.other
import pmb.parse import pmb.parse
import pmb.parse.arch
import pmb.chroot.apk import pmb.chroot.apk
def is_registered(arch_qemu): def is_registered(arch_qemu: Arch):
return os.path.exists("/proc/sys/fs/binfmt_misc/qemu-" + arch_qemu) return os.path.exists(f"/proc/sys/fs/binfmt_misc/qemu-{arch_qemu}")
def register(arch): def register(arch: Arch):
""" """
Get arch, magic, mask. Get arch, magic, mask.
""" """
arch_qemu = pmb.parse.arch.alpine_to_qemu(arch) arch_qemu = arch.qemu()
chroot = Chroot.native() chroot = Chroot.native()
# always make sure the qemu-<arch> binary is installed, since registering # always make sure the qemu-<arch> binary is installed, since registering
@ -58,8 +57,8 @@ def register(arch):
pmb.helpers.run.root(["sh", "-c", 'echo "' + code + '" > ' + register]) pmb.helpers.run.root(["sh", "-c", 'echo "' + code + '" > ' + register])
def unregister(arch): def unregister(arch: Arch):
arch_qemu = pmb.parse.arch.alpine_to_qemu(arch) arch_qemu = arch.qemu()
binfmt_file = "/proc/sys/fs/binfmt_misc/qemu-" + arch_qemu binfmt_file = "/proc/sys/fs/binfmt_misc/qemu-" + arch_qemu
if not os.path.exists(binfmt_file): if not os.path.exists(binfmt_file):
return return

View file

@ -14,7 +14,6 @@ import pmb.config.workdir
import pmb.helpers.repo import pmb.helpers.repo
import pmb.helpers.run import pmb.helpers.run
import pmb.helpers.other import pmb.helpers.other
import pmb.parse.arch
from pmb.core import Chroot, ChrootType, get_context from pmb.core import Chroot, ChrootType, get_context
cache_chroot_is_outdated: List[str] = [] cache_chroot_is_outdated: List[str] = []
@ -58,10 +57,10 @@ def mark_in_chroot(chroot: Chroot=Chroot.native()):
def setup_qemu_emulation(chroot: Chroot): def setup_qemu_emulation(chroot: Chroot):
arch = chroot.arch arch = chroot.arch
if not pmb.parse.arch.cpu_emulation_required(arch): if not arch.cpu_emulation_required():
return return
arch_qemu = pmb.parse.arch.alpine_to_qemu(arch) arch_qemu = arch.qemu()
# mount --bind the qemu-user binary # mount --bind the qemu-user binary
pmb.chroot.binfmt.register(arch) pmb.chroot.binfmt.register(arch)

View file

@ -88,7 +88,7 @@ def mount(chroot: Chroot):
mountpoints: Dict[Path, Path] = {} mountpoints: Dict[Path, Path] = {}
for src_template, target_template in pmb.config.chroot_mount_bind.items(): for src_template, target_template in pmb.config.chroot_mount_bind.items():
src_template = src_template.replace("$WORK", os.fspath(get_context().config.work)) src_template = src_template.replace("$WORK", os.fspath(get_context().config.work))
src_template = src_template.replace("$ARCH", arch) src_template = src_template.replace("$ARCH", str(arch))
src_template = src_template.replace("$CHANNEL", channel) src_template = src_template.replace("$CHANNEL", channel)
mountpoints[Path(src_template)] = Path(target_template) mountpoints[Path(src_template)] = Path(target_template)

View file

@ -1,14 +1,13 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
import socket import socket
from contextlib import closing from contextlib import closing
import pmb.chroot import pmb.chroot
from pmb.types import PmbArgs
import pmb.helpers.mount import pmb.helpers.mount
import pmb.install.losetup import pmb.install.losetup
import pmb.parse.arch
from pmb.core import Chroot, ChrootType, get_context from pmb.core import Chroot, ChrootType, get_context
@ -100,7 +99,7 @@ def shutdown(only_install_related=False):
pmb.helpers.mount.umount_all(path) pmb.helpers.mount.umount_all(path)
# Clean up the rest # Clean up the rest
for arch in pmb.config.build_device_architectures: for arch in Arch.supported():
if pmb.parse.arch.cpu_emulation_required(arch): if arch.cpu_emulation_required():
pmb.chroot.binfmt.unregister(arch) pmb.chroot.binfmt.unregister(arch)
logging.debug("Shutdown complete") logging.debug("Shutdown complete")

View file

@ -1,6 +1,7 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import glob import glob
from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
import os import os
@ -137,7 +138,7 @@ def zap_pkgs_local_mismatch(confirm=True, dry=False):
def zap_pkgs_online_mismatch(confirm=True, dry=False): def zap_pkgs_online_mismatch(confirm=True, dry=False):
# Check whether we need to do anything # Check whether we need to do anything
paths = glob.glob(f"{get_context().config.work}/cache_apk_*") paths = list(get_context().config.work.glob("cache_apk_*"))
if not len(paths): if not len(paths):
return return
if (confirm and not pmb.helpers.cli.confirm("Remove outdated" if (confirm and not pmb.helpers.cli.confirm("Remove outdated"
@ -146,8 +147,8 @@ def zap_pkgs_online_mismatch(confirm=True, dry=False):
# Iterate over existing apk caches # Iterate over existing apk caches
for path in paths: for path in paths:
arch = os.path.basename(path).split("_", 2)[2] arch = Arch.from_str(path.name.split("_", 2)[2])
if arch == pmb.config.arch_native: if arch == Arch.native():
suffix = Chroot.native() suffix = Chroot.native()
else: else:
try: try:

View file

@ -1,6 +1,7 @@
# Copyright 2024 Oliver Smith # Copyright 2024 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from typing import Optional from typing import Optional
from pmb.core.arch import Arch
from pmb.core.chroot import Chroot, ChrootType from pmb.core.chroot import Chroot, ChrootType
from pmb.core.context import Context from pmb.core.context import Context
from pmb.helpers import logging from pmb.helpers import logging
@ -15,7 +16,7 @@ from pmb.core import get_context
from pmb import commands from pmb import commands
class RepoBootstrap(commands.Command): class RepoBootstrap(commands.Command):
arch: str arch: Arch
repo: str repo: str
context: Context context: Context
@ -38,7 +39,7 @@ class RepoBootstrap(commands.Command):
" current branch") " current branch")
def __init__(self, arch: Optional[str], repository: str): def __init__(self, arch: Optional[Arch], repository: str):
context = get_context() context = get_context()
if arch: if arch:
self.arch = arch self.arch = arch
@ -46,7 +47,7 @@ class RepoBootstrap(commands.Command):
if context.config.build_default_device_arch: if context.config.build_default_device_arch:
self.arch = pmb.parse.deviceinfo().arch self.arch = pmb.parse.deviceinfo().arch
else: else:
self.arch = pmb.config.arch_native self.arch = Arch.native()
self.repo = repository self.repo = repository
self.context = context self.context = context
@ -74,7 +75,7 @@ class RepoBootstrap(commands.Command):
self.progress_total += len(steps) * 2 self.progress_total += len(steps) * 2
# Foreign arch: need to initialize one additional chroot each step # Foreign arch: need to initialize one additional chroot each step
if pmb.parse.arch.cpu_emulation_required(self.arch): if self.arch.cpu_emulation_required():
self.progress_total += len(steps) self.progress_total += len(steps)
@ -87,7 +88,7 @@ class RepoBootstrap(commands.Command):
def run_steps(self, steps): def run_steps(self, steps):
chroot: Chroot chroot: Chroot
if pmb.parse.arch.cpu_emulation_required(self.arch): if self.arch.cpu_emulation_required():
chroot = Chroot(ChrootType.BUILDROOT, self.arch) chroot = Chroot(ChrootType.BUILDROOT, self.arch)
else: else:
chroot = Chroot.native() chroot = Chroot.native()
@ -129,7 +130,7 @@ class RepoBootstrap(commands.Command):
msg = f"Found previously built packages for {channel}/{self.arch}, run" \ msg = f"Found previously built packages for {channel}/{self.arch}, run" \
" 'pmbootstrap zap -p' first" " 'pmbootstrap zap -p' first"
if pmb.parse.arch.cpu_emulation_required(self.arch): if self.arch.cpu_emulation_required():
msg += " or remove the path manually (to keep cross compilers if" \ msg += " or remove the path manually (to keep cross compilers if" \
" you just built them)" " you just built them)"

View file

@ -1,12 +1,10 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import multiprocessing
import os import os
from pathlib import Path from pathlib import Path
from pmb.types import AportGenEntry, PathString from pmb.types import AportGenEntry, PathString
import pmb.parse.arch
import sys import sys
from typing import Dict, List, Sequence, TypedDict from typing import Dict, List, Sequence
# #
# Exported functions # Exported functions
@ -23,7 +21,6 @@ from pmb.config.other import is_systemd_selected
# #
pmb_src: Path = Path(Path(__file__) / "../../..").resolve() pmb_src: Path = Path(Path(__file__) / "../../..").resolve()
apk_keys_path: Path = (pmb_src / "pmb/data/keys") apk_keys_path: Path = (pmb_src / "pmb/data/keys")
arch_native = pmb.parse.arch.alpine_native()
# apk-tools minimum version # apk-tools minimum version
# https://pkgs.alpinelinux.org/packages?name=apk-tools&branch=edge # https://pkgs.alpinelinux.org/packages?name=apk-tools&branch=edge
@ -239,15 +236,6 @@ apkindex_retention_time = 4
# When chroot is considered outdated (in seconds) # When chroot is considered outdated (in seconds)
chroot_outdated = 3600 * 24 * 2 chroot_outdated = 3600 * 24 * 2
#
# BUILD
#
# Officially supported host/target architectures for postmarketOS. Only
# specify architectures supported by Alpine here. For cross-compiling,
# we need to generate the "musl-$ARCH" and "gcc-$ARCH" packages (use
# "pmbootstrap aportgen musl-armhf" etc.).
build_device_architectures = ["armhf", "armv7", "aarch64", "x86_64", "x86", "riscv64"]
# Packages that will be installed in a chroot before it builds packages # Packages that will be installed in a chroot before it builds packages
# for the first time # for the first time
build_packages = ["abuild", "build-base", "ccache", "git"] build_packages = ["abuild", "build-base", "ccache", "git"]

View file

@ -29,7 +29,7 @@ def chroot_save_init(suffix: Chroot):
cfg[key] = {} cfg[key] = {}
# Update sections # Update sections
channel = pmb.config.pmaports.read_config()["channel"] channel = pmb.config.pmaports.read_config(support_systemd=False)["channel"]
cfg["chroot-channels"][str(suffix)] = channel cfg["chroot-channels"][str(suffix)] = channel
cfg["chroot-init-dates"][str(suffix)] = str(int(time.time())) cfg["chroot-init-dates"][str(suffix)] = str(int(time.time()))
@ -82,7 +82,8 @@ def chroot_check_channel(chroot: Chroot):
if key not in cfg or str(chroot) not in cfg[key]: if key not in cfg or str(chroot) not in cfg[key]:
raise RuntimeError(f"{msg_unknown} {msg_again}") raise RuntimeError(f"{msg_unknown} {msg_again}")
channel = pmb.config.pmaports.read_config()["channel"] # Exclude systemd repo
channel = pmb.config.pmaports.read_config(support_systemd=False)["channel"]
channel_cfg = cfg[key][str(chroot)] channel_cfg = cfg[key][str(chroot)]
if channel != channel_cfg: if channel != channel_cfg:
raise RuntimeError(f"Chroot '{chroot}' was created for the" raise RuntimeError(f"Chroot '{chroot}' was created for the"

191
pmb/core/arch.py Normal file
View file

@ -0,0 +1,191 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import enum
from pathlib import Path, PosixPath, PurePosixPath
import platform
from typing import Set
# Initialised at the bottom
_cached_native_arch: "Arch"
class Arch(enum.Enum):
"""Supported architectures according to the Alpine
APKBUILD format."""
x86 = "x86"
x86_64 = "x86_64"
armhf = "armhf"
armv7 = "armv7"
aarch64 = "aarch64"
riscv64 = "riscv64"
s390x = "s390x"
ppc64le = "ppc64le"
# Arches Alpine can build for
armel = "armel"
loongarch32 = "loongarch32"
loongarchx32 = "loongarchx32"
loongarch64 = "loongarch64"
mips = "mips"
mips64 = "mips64"
mipsel = "mipsel"
mips64el = "mips64el"
ppc = "ppc"
ppc64 = "ppc64"
riscv32 = "riscv32"
def __str__(self) -> str:
return self.value
@staticmethod
def from_str(arch: str) -> "Arch":
try:
return Arch(arch)
except ValueError:
raise ValueError(f"Invalid architecture: {arch}")
@staticmethod
def from_machine_type(machine_type: str) -> "Arch":
mapping = {
"i686": Arch.x86,
"x86_64": Arch.x86_64,
"aarch64": Arch.aarch64,
"arm64": Arch.aarch64,
"armv6l": Arch.armhf,
"armv7l": Arch.armv7,
"armv8l": Arch.armv7,
}
return mapping[machine_type]
@staticmethod
def native() -> "Arch":
global _cached_native_arch
return _cached_native_arch
def is_native(self):
return self == Arch.native()
@staticmethod
def supported() -> Set["Arch"]:
"""Officially supported host/target architectures for postmarketOS. Only
specify architectures supported by Alpine here. For cross-compiling,
we need to generate the "musl-$ARCH" and "gcc-$ARCH" packages (use
"pmbootstrap aportgen musl-armhf" etc.)."""
# FIXME: cache?
return set([
Arch.armhf,
Arch.armv7,
Arch.aarch64,
Arch.x86_64,
Arch.x86,
Arch.riscv64,
Arch.native(),
])
def kernel(self):
mapping = {
Arch.x86: "x86",
Arch.x86_64: "x86_64",
Arch.armhf: "arm",
Arch.aarch64: "arm64",
Arch.riscv64: "riscv",
Arch.ppc64le: "powerpc",
Arch.s390x: "s390",
}
return mapping.get(self, self.value)
def qemu(self):
mapping = {
Arch.x86: "i386",
Arch.armhf: "arm",
Arch.armv7: "arm",
Arch.ppc64le: "ppc64",
}
return mapping.get(self, self.value)
def alpine_triple(self):
mapping = {
Arch.aarch64: "aarch64-alpine-linux-musl",
Arch.armel: "armv5-alpine-linux-musleabi",
Arch.armhf: "armv6-alpine-linux-musleabihf",
Arch.armv7: "armv7-alpine-linux-musleabihf",
Arch.loongarch32: "loongarch32-alpine-linux-musl",
Arch.loongarchx32: "loongarchx32-alpine-linux-musl",
Arch.loongarch64: "loongarch64-alpine-linux-musl",
Arch.mips: "mips-alpine-linux-musl",
Arch.mips64: "mips64-alpine-linux-musl",
Arch.mipsel: "mipsel-alpine-linux-musl",
Arch.mips64el: "mips64el-alpine-linux-musl",
Arch.ppc: "powerpc-alpine-linux-musl",
Arch.ppc64: "powerpc64-alpine-linux-musl",
Arch.ppc64le: "powerpc64le-alpine-linux-musl",
Arch.riscv32: "riscv32-alpine-linux-musl",
Arch.riscv64: "riscv64-alpine-linux-musl",
Arch.s390x: "s390x-alpine-linux-musl",
Arch.x86: "i586-alpine-linux-musl",
Arch.x86_64: "x86_64-alpine-linux-musl",
}
if self in mapping:
return mapping[self]
raise ValueError(f"Can not map Alpine architecture '{self}'"
" to the right hostspec value")
def cpu_emulation_required(self):
# Obvious case: host arch is target arch
if self == Arch.native():
return False
# Other cases: host arch on the left, target archs on the right
not_required = {
Arch.x86_64: [Arch.x86],
Arch.armv7: [Arch.armel, Arch.armhf],
Arch.aarch64: [Arch.armv7],
}
if Arch.native() in not_required:
if self in not_required[Arch.native()]:
return False
# No match: then it's required
return True
# Magic to let us use an arch as a Path element
def __truediv__(self, other: object) -> Path:
if isinstance(other, PosixPath) or isinstance(other, PurePosixPath):
# Convert the other path to a relative path
# FIXME: we should avoid creating absolute paths that we actually want
# to make relative to the chroot...
# if other.is_absolute():
# logging.warning("FIXME: absolute path made relative to Arch??")
other = other.relative_to("/") if other.is_absolute() else other
return Path(str(self)).joinpath(other)
if isinstance(other, str):
# Let's us do Arch / "whatever.apk" and magically produce a path
# maybe this is a pattern we should avoid, but it seems somewhat
# sensible
return Path(str(self)).joinpath(other.strip("/"))
return NotImplemented
def __rtruediv__(self, other: object) -> Path:
if isinstance(other, PosixPath) or isinstance(other, PurePosixPath):
# Important to produce a new Path object here, otherwise we
# end up with one object getting shared around and modified
# and lots of weird stuff happens.
return Path(other) / str(self)
# We don't support str / Arch since that is a weird pattern
return NotImplemented
_cached_native_arch = Arch.from_machine_type(platform.machine())

View file

@ -3,9 +3,10 @@
from __future__ import annotations from __future__ import annotations
import enum import enum
from typing import Generator, Optional from typing import Generator, Optional, Union
from pathlib import Path, PosixPath, PurePosixPath from pathlib import Path, PosixPath, PurePosixPath
import pmb.config import pmb.config
from pmb.core.arch import Arch
from .context import get_context from .context import get_context
class ChrootType(enum.Enum): class ChrootType(enum.Enum):
@ -21,9 +22,9 @@ class Chroot:
__type: ChrootType __type: ChrootType
__name: str __name: str
def __init__(self, suffix_type: ChrootType, name: Optional[str] = ""): def __init__(self, suffix_type: ChrootType, name: Optional[Union[str, Arch]] = ""):
self.__type = suffix_type self.__type = suffix_type
self.__name = name or "" self.__name = str(name or "")
self.__validate() self.__validate()
@ -74,16 +75,15 @@ class Chroot:
@property @property
# FIXME: make an Arch type def arch(self) -> Arch:
def arch(self) -> str:
if self.type == ChrootType.NATIVE: if self.type == ChrootType.NATIVE:
return pmb.config.arch_native return Arch.native()
if self.type == ChrootType.BUILDROOT: if self.type == ChrootType.BUILDROOT:
return self.name() return Arch.from_str(self.name())
# FIXME: this is quite delicate as it will only be valid # FIXME: this is quite delicate as it will only be valid
# for certain pmbootstrap commands... It was like this # for certain pmbootstrap commands... It was like this
# before but it should be fixed. # before but it should be fixed.
arch = pmb.core.get_context().device_arch arch = pmb.parse.deviceinfo().arch
if arch is not None: if arch is not None:
return arch return arch
@ -118,8 +118,12 @@ class Chroot:
def __rtruediv__(self, other: object) -> Path: def __rtruediv__(self, other: object) -> Path:
if isinstance(other, PosixPath) or isinstance(other, PurePosixPath): if isinstance(other, PosixPath) or isinstance(other, PurePosixPath):
# Important to produce a new Path object here, otherwise we
# end up with one object getting shared around and modified
# and lots of weird stuff happens.
return Path(other) / self.path return Path(other) / self.path
if isinstance(other, str): if isinstance(other, str):
# This implicitly creates a new Path object
return other / self.path return other / self.path
return NotImplemented return NotImplemented
@ -139,7 +143,7 @@ class Chroot:
@staticmethod @staticmethod
def buildroot(arch: str) -> Chroot: def buildroot(arch: Arch) -> Chroot:
return Chroot(ChrootType.BUILDROOT, arch) return Chroot(ChrootType.BUILDROOT, arch)

View file

@ -5,7 +5,8 @@
from typing import List, Optional from typing import List, Optional
from pathlib import Path from pathlib import Path
from pmb.types import Config from pmb.core.arch import Arch
from .config import Config
class Context(): class Context():
@ -15,8 +16,6 @@ class Context():
sudo_timer: bool = False sudo_timer: bool = False
force: bool = False force: bool = False
log: Path log: Path
# The architecture of the selected device
device_arch: Optional[str] = None
# assume yes to prompts # assume yes to prompts
assume_yes: bool = False assume_yes: bool = False

View file

@ -6,6 +6,7 @@ from typing import List, Sequence
import pmb.chroot import pmb.chroot
import pmb.config.pmaports import pmb.config.pmaports
from pmb.core.arch import Arch
from pmb.types import PathString, PmbArgs from pmb.types import PathString, PmbArgs
import pmb.helpers.cli import pmb.helpers.cli
import pmb.helpers.run import pmb.helpers.run
@ -72,7 +73,12 @@ def apk_with_progress(command: Sequence[PathString]):
:raises RuntimeError: when the apk command fails :raises RuntimeError: when the apk command fails
""" """
fifo, fifo_outside = _prepare_fifo() fifo, fifo_outside = _prepare_fifo()
_command: List[str] = [os.fspath(c) for c in command] _command: List[str] = []
for c in command:
if isinstance(c, Arch):
_command.append(str(c))
else:
_command.append(os.fspath(c))
command_with_progress = _create_command_with_progress(_command, fifo) command_with_progress = _create_command_with_progress(_command, fifo)
log_msg = " ".join(_command) log_msg = " ".join(_command)
with pmb.helpers.run.root(['cat', fifo], with pmb.helpers.run.root(['cat', fifo],

View file

@ -99,8 +99,6 @@ def init(args: PmbArgs) -> PmbArgs:
"pull", "shutdown", "zap"]: "pull", "shutdown", "zap"]:
pmb.config.pmaports.read_config() pmb.config.pmaports.read_config()
pmb.helpers.git.parse_channels_cfg(pkgrepo_default_path()) pmb.helpers.git.parse_channels_cfg(pkgrepo_default_path())
deviceinfo = pmb.parse.deviceinfo()
context.device_arch = deviceinfo.arch
# Remove attributes from args so they don't get used by mistake # Remove attributes from args so they don't get used by mistake
delattr(args, "timeout") delattr(args, "timeout")

View file

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import json import json
from typing import List, Sequence, Tuple from typing import List, Sequence, Tuple
from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
import os import os
from pathlib import Path from pathlib import Path
@ -67,7 +68,7 @@ def _parse_suffix(args: PmbArgs) -> Chroot:
if args.buildroot == "device": if args.buildroot == "device":
return Chroot.buildroot(pmb.parse.deviceinfo().arch) return Chroot.buildroot(pmb.parse.deviceinfo().arch)
else: else:
return Chroot.buildroot(args.buildroot) return Chroot.buildroot(Arch.from_str(args.buildroot))
elif args.suffix: elif args.suffix:
(_t, s) = args.suffix.split("_") (_t, s) = args.suffix.split("_")
t: ChrootType = ChrootType(_t) t: ChrootType = ChrootType(_t)
@ -550,7 +551,7 @@ def shutdown(args: PmbArgs):
def stats(args: PmbArgs): def stats(args: PmbArgs):
# Chroot suffix # Chroot suffix
chroot = Chroot.native() chroot = Chroot.native()
if args.arch != pmb.config.arch_native: if args.arch != Arch.native():
chroot = Chroot.buildroot(args.arch) chroot = Chroot.buildroot(args.arch)
# Install ccache and display stats # Install ccache and display stats

View file

@ -1,6 +1,7 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pmb.core import get_context from pmb.core import get_context
from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
import os import os
from pathlib import Path from pathlib import Path
@ -191,7 +192,7 @@ def migrate_work_folder(args: PmbArgs):
# Move packages to edge subdir # Move packages to edge subdir
edge_path = context.config.work / "packages/edge" edge_path = context.config.work / "packages/edge"
pmb.helpers.run.root(["mkdir", "-p", edge_path]) pmb.helpers.run.root(["mkdir", "-p", edge_path])
for arch in pmb.config.build_device_architectures: for arch in Arch.supported():
old_path = context.config.work / "packages" / arch old_path = context.config.work / "packages" / arch
new_path = edge_path / arch new_path = edge_path / arch
if old_path.exists(): if old_path.exists():

View file

@ -10,6 +10,7 @@ See also:
""" """
import copy import copy
from typing import Any, Dict from typing import Any, Dict
from pmb.core.arch import Arch
from pmb.core.context import get_context from pmb.core.context import get_context
from pmb.helpers import logging from pmb.helpers import logging
import pmb.build._package import pmb.build._package
@ -83,7 +84,7 @@ def get(pkgname, arch, replace_subpkgnames=False, must_exist=True):
# Find in APKINDEX (other arches) # Find in APKINDEX (other arches)
if not ret: if not ret:
pmb.helpers.repo.update() pmb.helpers.repo.update()
for arch_i in pmb.config.build_device_architectures: for arch_i in Arch.supported():
if arch_i != arch: if arch_i != arch:
ret = pmb.parse.apkindex.package(pkgname, arch_i, False) ret = pmb.parse.apkindex.package(pkgname, arch_i, False)
if ret: if ret:

View file

@ -1,5 +1,6 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
from pmb.types import PmbArgs from pmb.types import PmbArgs
@ -104,7 +105,7 @@ def auto_apkindex_package(args: PmbArgs, arch, aport, apk, dry=False):
def auto(args: PmbArgs, dry=False): def auto(args: PmbArgs, dry=False):
""":returns: list of aport names, where the pkgrel needed to be changed""" """:returns: list of aport names, where the pkgrel needed to be changed"""
ret = [] ret = []
for arch in pmb.config.build_device_architectures: for arch in Arch.supported():
paths = pmb.helpers.repo.apkindex_files(args, arch, alpine=False) paths = pmb.helpers.repo.apkindex_files(args, arch, alpine=False)
for path in paths: for path in paths:
logging.info(f"scan {path}") logging.info(f"scan {path}")

View file

@ -8,6 +8,7 @@ See also:
""" """
import glob import glob
from pmb.core import get_context from pmb.core import get_context
from pmb.core.arch import Arch
from pmb.core.pkgrepo import pkgrepo_iter_package_dirs from pmb.core.pkgrepo import pkgrepo_iter_package_dirs
from pmb.helpers import logging from pmb.helpers import logging
from pathlib import Path from pathlib import Path
@ -164,6 +165,7 @@ def find(package, must_exist=True, subpackages=True, skip_extra_repos=False):
# Try to find an APKBUILD with the exact pkgname we are looking for # Try to find an APKBUILD with the exact pkgname we are looking for
path = _find_apkbuilds(skip_extra_repos).get(package) path = _find_apkbuilds(skip_extra_repos).get(package)
if path: if path:
logging.verbose(f"{package}: found apkbuild: {path}")
ret = path.parent ret = path.parent
elif subpackages: elif subpackages:
# No luck, take a guess what APKBUILD could have the package we are # No luck, take a guess what APKBUILD could have the package we are
@ -282,7 +284,7 @@ def get_repo(pkgname, must_exist=True) -> Optional[str]:
return aport.parent.name return aport.parent.name
def check_arches(arches, arch): def check_arches(arches, arch: Arch):
"""Check if building for a certain arch is allowed. """Check if building for a certain arch is allowed.
:param arches: list of all supported arches, as it can be found in the :param arches: list of all supported arches, as it can be found in the
@ -293,9 +295,9 @@ def check_arches(arches, arch):
:returns: True when building is allowed, False otherwise :returns: True when building is allowed, False otherwise
""" """
if "!" + arch in arches: if f"!{arch}" in arches:
return False return False
for value in [arch, "all", "noarch"]: for value in [str(arch), "all", "noarch"]:
if value in arches: if value in arches:
return True return True
return False return False

View file

@ -10,10 +10,11 @@ See also:
import os import os
import hashlib import hashlib
from pmb.core import get_context from pmb.core import get_context
from pmb.core.arch import Arch
from pmb.core.pkgrepo import pkgrepo_paths from pmb.core.pkgrepo import pkgrepo_paths
from pmb.helpers import logging from pmb.helpers import logging
from pathlib import Path from pathlib import Path
from typing import List from typing import List, Optional
import pmb.config.pmaports import pmb.config.pmaports
from pmb.types import PmbArgs from pmb.types import PmbArgs
@ -69,7 +70,6 @@ def urls(user_repository=True, postmarketos_mirror=True, alpine=True):
# Local user repository (for packages compiled with pmbootstrap) # Local user repository (for packages compiled with pmbootstrap)
if user_repository: if user_repository:
channel = pmb.config.pmaports.read_config()["channel"]
# FIXME: We shouldn't hardcod this here # FIXME: We shouldn't hardcod this here
for channel in pmb.config.pmaports.all_channels(): for channel in pmb.config.pmaports.all_channels():
ret.append(f"/mnt/pmbootstrap/packages/{channel}") ret.append(f"/mnt/pmbootstrap/packages/{channel}")
@ -98,7 +98,7 @@ def urls(user_repository=True, postmarketos_mirror=True, alpine=True):
return ret return ret
def apkindex_files(arch=None, user_repository=True, pmos=True, def apkindex_files(arch: Optional[Arch]=None, user_repository=True, pmos=True,
alpine=True) -> List[Path]: alpine=True) -> List[Path]:
"""Get a list of outside paths to all resolved APKINDEX.tar.gz files for a specific arch. """Get a list of outside paths to all resolved APKINDEX.tar.gz files for a specific arch.
@ -109,7 +109,7 @@ def apkindex_files(arch=None, user_repository=True, pmos=True,
:returns: list of absolute APKINDEX.tar.gz file paths :returns: list of absolute APKINDEX.tar.gz file paths
""" """
if not arch: if not arch:
arch = pmb.config.arch_native arch = Arch.native()
ret = [] ret = []
# Local user repository (for packages compiled with pmbootstrap) # Local user repository (for packages compiled with pmbootstrap)
@ -144,7 +144,7 @@ def update(arch=None, force=False, existing_only=False):
return False return False
# Architectures and retention time # Architectures and retention time
architectures = [arch] if arch else pmb.config.build_device_architectures architectures = [arch] if arch else Arch.supported()
retention_hours = pmb.config.apkindex_retention_time retention_hours = pmb.config.apkindex_retention_time
retention_seconds = retention_hours * 3600 retention_seconds = retention_hours * 3600
@ -152,13 +152,13 @@ def update(arch=None, force=False, existing_only=False):
# outdated: {URL: apkindex_path, ... } # outdated: {URL: apkindex_path, ... }
# outdated_arches: ["armhf", "x86_64", ... ] # outdated_arches: ["armhf", "x86_64", ... ]
outdated = {} outdated = {}
outdated_arches = [] outdated_arches: List[Arch] = []
for url in urls(False): for url in urls(False):
for arch in architectures: for arch in architectures:
# APKINDEX file name from the URL # APKINDEX file name from the URL
url_full = url + "/" + arch + "/APKINDEX.tar.gz" url_full = f"{url}/{arch}/APKINDEX.tar.gz"
cache_apk_outside = get_context().config.work / f"cache_apk_{arch}" cache_apk_outside = get_context().config.work / f"cache_apk_{arch}"
apkindex = cache_apk_outside / f"APKINDEX.{apkindex_hash(url)}.tar.gz" apkindex = cache_apk_outside / f"{apkindex_hash(url)}.tar.gz"
# Find update reason, possibly skip non-existing or known 404 files # Find update reason, possibly skip non-existing or known 404 files
reason = None reason = None
@ -186,7 +186,7 @@ def update(arch=None, force=False, existing_only=False):
# Bail out or show log message # Bail out or show log message
if not len(outdated): if not len(outdated):
return False return False
logging.info("Update package index for " + ", ".join(outdated_arches) + logging.info("Update package index for " + ", ".join([str(a) for a in outdated_arches]) +
" (" + str(len(outdated)) + " file(s))") " (" + str(len(outdated)) + " file(s))")
# Download and move to right location # Download and move to right location
@ -206,7 +206,7 @@ def update(arch=None, force=False, existing_only=False):
return True return True
def alpine_apkindex_path(repo="main", arch=None): def alpine_apkindex_path(repo="main", arch: Optional[Arch]=None):
"""Get the path to a specific Alpine APKINDEX file on disk and download it if necessary. """Get the path to a specific Alpine APKINDEX file on disk and download it if necessary.
:param repo: Alpine repository name (e.g. "main") :param repo: Alpine repository name (e.g. "main")
@ -215,10 +215,10 @@ def alpine_apkindex_path(repo="main", arch=None):
""" """
# Repo sanity check # Repo sanity check
if repo not in ["main", "community", "testing", "non-free"]: if repo not in ["main", "community", "testing", "non-free"]:
raise RuntimeError("Invalid Alpine repository: " + repo) raise RuntimeError(f"Invalid Alpine repository: {repo}")
# Download the file # Download the file
arch = arch or pmb.config.arch_native arch = arch or Arch.native()
update(arch) update(arch)
# Find it on disk # Find it on disk

View file

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import fcntl import fcntl
from pmb.core import get_context from pmb.core import get_context
from pmb.core.arch import Arch
from pmb.types import PathString, Env from pmb.types import PathString, Env
from pmb.helpers import logging from pmb.helpers import logging
import os import os
@ -35,6 +36,8 @@ def flat_cmd(cmds: Sequence[Sequence[PathString]], working_dir: Optional[Path]=N
# Merge env and cmd into escaped list # Merge env and cmd into escaped list
escaped = [] escaped = []
for key, value in env.items(): for key, value in env.items():
if isinstance(value, Arch):
value = str(value)
escaped.append(key + "=" + shlex.quote(os.fspath(value))) escaped.append(key + "=" + shlex.quote(os.fspath(value)))
for cmd in cmds: for cmd in cmds:
for i in range(len(cmd)): for i in range(len(cmd)):

View file

@ -1,5 +1,6 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
import os import os
import re import re
@ -120,7 +121,7 @@ def copy_files_from_chroot(args: PmbArgs, chroot: Chroot):
mountpoint_outside = Chroot.native() / mountpoint mountpoint_outside = Chroot.native() / mountpoint
# Remove empty qemu-user binary stub (where the binary was bind-mounted) # Remove empty qemu-user binary stub (where the binary was bind-mounted)
arch_qemu = pmb.parse.arch.alpine_to_qemu(pmb.parse.deviceinfo().arch) arch_qemu = pmb.parse.deviceinfo().arch.qemu()
qemu_binary = mountpoint_outside / ("/usr/bin/qemu-" + arch_qemu + "-static") qemu_binary = mountpoint_outside / ("/usr/bin/qemu-" + arch_qemu + "-static")
if os.path.exists(qemu_binary): if os.path.exists(qemu_binary):
pmb.helpers.run.root(["rm", qemu_binary]) pmb.helpers.run.root(["rm", qemu_binary])
@ -367,7 +368,7 @@ def setup_keymap(config: Config):
def setup_timezone(chroot: Chroot, timezone: str): def setup_timezone(chroot: Chroot, timezone: str):
# We don't care about the arch since it's built for all! # We don't care about the arch since it's built for all!
alpine_conf = pmb.helpers.package.get("alpine-conf", pmb.config.arch_native) alpine_conf = pmb.helpers.package.get("alpine-conf", Arch.native())
version = alpine_conf["version"].split("-r")[0] version = alpine_conf["version"].split("-r")[0]
setup_tz_cmd = ["setup-timezone"] setup_tz_cmd = ["setup-timezone"]
@ -476,7 +477,7 @@ def disable_firewall(chroot: Chroot):
raise RuntimeError(f"Failed to disable firewall: {nftables_files}") raise RuntimeError(f"Failed to disable firewall: {nftables_files}")
def print_firewall_info(disabled: bool, arch: str): def print_firewall_info(disabled: bool, arch: Arch):
pmaports_cfg = pmb.config.pmaports.read_config() pmaports_cfg = pmb.config.pmaports.read_config()
pmaports_ok = pmaports_cfg.get("supported_firewall", None) == "nftables" pmaports_ok = pmaports_cfg.get("supported_firewall", None) == "nftables"
@ -981,7 +982,7 @@ def print_flash_info(device: str, deviceinfo: Deviceinfo, split: bool, have_disk
" and flash outside of pmbootstrap.") " and flash outside of pmbootstrap.")
def install_recovery_zip(args: PmbArgs, device: str, arch: str, steps): def install_recovery_zip(args: PmbArgs, device: str, arch: Arch, steps):
logging.info(f"*** ({steps}/{steps}) CREATING RECOVERY-FLASHABLE ZIP ***") logging.info(f"*** ({steps}/{steps}) CREATING RECOVERY-FLASHABLE ZIP ***")
chroot = Chroot(ChrootType.BUILDROOT, arch) chroot = Chroot(ChrootType.BUILDROOT, arch)
mount_device_rootfs(Chroot.rootfs(device)) mount_device_rootfs(Chroot.rootfs(device))

View file

@ -8,4 +8,3 @@ from pmb.parse.deviceinfo import deviceinfo
from pmb.parse.kconfig import check from pmb.parse.kconfig import check
from pmb.parse.bootimg import bootimg from pmb.parse.bootimg import bootimg
from pmb.parse.cpuinfo import arm_big_little_first_group_ncpus from pmb.parse.cpuinfo import arm_big_little_first_group_ncpus
import pmb.parse.arch

View file

@ -1,119 +0,0 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import fnmatch
import platform
import pmb.config
import pmb.parse.arch
def alpine_native():
machine = platform.machine()
return machine_type_to_alpine(machine)
def alpine_to_qemu(arch):
"""
Convert the architecture to the string used in the QEMU packaging.
This corresponds to the package name of e.g. qemu-system-aarch64.
"""
mapping = {
"x86": "i386",
"x86_64": "x86_64",
"armhf": "arm",
"armv7": "arm",
"aarch64": "aarch64",
"riscv64": "riscv64",
}
for pattern, arch_qemu in mapping.items():
if fnmatch.fnmatch(arch, pattern):
return arch_qemu
raise ValueError("Can not map Alpine architecture '" + arch + "'"
" to the right Debian architecture.")
def alpine_to_kernel(arch):
"""
Convert the architecture to the string used inside the kernel sources.
You can read the mapping from the linux-vanilla APKBUILD for example.
"""
mapping = {
"aarch64*": "arm64",
"arm*": "arm",
"ppc*": "powerpc",
"s390*": "s390",
"riscv64*": "riscv",
}
for pattern, arch_kernel in mapping.items():
if fnmatch.fnmatch(arch, pattern):
return arch_kernel
return arch
def alpine_to_hostspec(arch):
"""
See: abuild source code/functions.sh.in: arch_to_hostspec()
"""
mapping = {
"aarch64": "aarch64-alpine-linux-musl",
"armel": "armv5-alpine-linux-musleabi",
"armhf": "armv6-alpine-linux-musleabihf",
"armv7": "armv7-alpine-linux-musleabihf",
"loongarch32": "loongarch32-alpine-linux-musl",
"loongarchx32": "loongarchx32-alpine-linux-musl",
"loongarch64": "loongarch64-alpine-linux-musl",
"mips": "mips-alpine-linux-musl",
"mips64": "mips64-alpine-linux-musl",
"mipsel": "mipsel-alpine-linux-musl",
"mips64el": "mips64el-alpine-linux-musl",
"ppc": "powerpc-alpine-linux-musl",
"ppc64": "powerpc64-alpine-linux-musl",
"ppc64le": "powerpc64le-alpine-linux-musl",
"riscv32": "riscv32-alpine-linux-musl",
"riscv64": "riscv64-alpine-linux-musl",
"s390x": "s390x-alpine-linux-musl",
"x86": "i586-alpine-linux-musl",
"x86_64": "x86_64-alpine-linux-musl",
}
if arch in mapping:
return mapping[arch]
raise ValueError("Can not map Alpine architecture '" + arch + "'"
" to the right hostspec value")
def cpu_emulation_required(arch):
# Obvious case: host arch is target arch
if pmb.config.arch_native == arch:
return False
# Other cases: host arch on the left, target archs on the right
not_required = {
"x86_64": ["x86"],
"armv7": ["armel", "armhf"],
"aarch64": ["armv7"],
}
if pmb.config.arch_native in not_required:
if arch in not_required[pmb.config.arch_native]:
return False
# No match: then it's required
return True
def machine_type_to_alpine(machine_type: str) -> str:
"""Translate a machine type to an Alpine architecture. A machine type can come from
for example `$ uname -m` or platform.machine() from Python's standard library."""
mapping = {
"i686": "x86",
"x86_64": "x86_64",
"aarch64": "aarch64",
"arm64": "aarch64",
"armv6l": "armhf",
"armv7l": "armv7",
"armv8l": "armv7",
}
if machine_type in mapping:
return mapping[machine_type]
raise ValueError(f"Can not map machine type '{machine_type}'"
" to the right Alpine Linux architecture")

View file

@ -1,7 +1,6 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import argparse import argparse
import copy
import os import os
from pathlib import Path from pathlib import Path
import sys import sys
@ -16,7 +15,6 @@ except ImportError:
pass pass
import pmb.config import pmb.config
import pmb.parse.arch
import pmb.helpers.args import pmb.helpers.args
import pmb.helpers.pmaports import pmb.helpers.pmaports
@ -234,7 +232,8 @@ def arguments_sideload(subparser):
ret.add_argument("--user", help="use a different username than the" ret.add_argument("--user", help="use a different username than the"
" one set in init") " one set in init")
ret.add_argument("--arch", help="skip automatic architecture deduction and use the" ret.add_argument("--arch", help="skip automatic architecture deduction and use the"
" given value") " given value",
type=lambda x: Arch.from_str(x))
ret.add_argument("--install-key", help="install the apk key from this" ret.add_argument("--install-key", help="install the apk key from this"
" machine if needed", " machine if needed",
action="store_true", dest="install_key") action="store_true", dest="install_key")
@ -483,8 +482,7 @@ def arguments_newapkbuild(subparser):
def arguments_kconfig(subparser): def arguments_kconfig(subparser):
# Allowed architectures # Allowed architectures
arch_native = pmb.config.arch_native arch_choices = Arch.supported()
arch_choices = set(pmb.config.build_device_architectures + [arch_native])
# Kconfig subparser # Kconfig subparser
ret = subparser.add_parser("kconfig", help="change or edit kernel configs") ret = subparser.add_parser("kconfig", help="change or edit kernel configs")
@ -496,7 +494,8 @@ def arguments_kconfig(subparser):
check.add_argument("-f", "--force", action="store_true", help="check all" check.add_argument("-f", "--force", action="store_true", help="check all"
" kernels, even the ones that would be ignored by" " kernels, even the ones that would be ignored by"
" default") " default")
check.add_argument("--arch", choices=arch_choices, dest="arch") check.add_argument("--arch", choices=arch_choices, dest="arch",
type=lambda x: Arch.from_str(x))
check.add_argument("--file", help="check a file directly instead of a" check.add_argument("--file", help="check a file directly instead of a"
" config in a package") " config in a package")
check.add_argument("--no-details", action="store_false", check.add_argument("--no-details", action="store_false",
@ -511,7 +510,8 @@ def arguments_kconfig(subparser):
# "pmbootstrap kconfig edit" # "pmbootstrap kconfig edit"
edit = sub.add_parser("edit", help="edit kernel aport config") edit = sub.add_parser("edit", help="edit kernel aport config")
edit.add_argument("--arch", choices=arch_choices, dest="arch") edit.add_argument("--arch", choices=arch_choices, dest="arch",
type=lambda x: Arch.from_str(x))
edit.add_argument("-x", dest="xconfig", action="store_true", edit.add_argument("-x", dest="xconfig", action="store_true",
help="use xconfig rather than menuconfig for kernel" help="use xconfig rather than menuconfig for kernel"
" configuration") " configuration")
@ -526,18 +526,19 @@ def arguments_kconfig(subparser):
"newer. Internally runs 'make oldconfig', " "newer. Internally runs 'make oldconfig', "
"which asks question for every new kernel " "which asks question for every new kernel "
"config option.") "config option.")
migrate.add_argument("--arch", choices=arch_choices, dest="arch") migrate.add_argument("--arch", choices=arch_choices, dest="arch",
type=lambda x: Arch.from_str(x))
add_kernel_arg(migrate) add_kernel_arg(migrate)
def arguments_repo_bootstrap(subparser): def arguments_repo_bootstrap(subparser):
arch_native = pmb.config.arch_native arch_choices = Arch.supported()
arch_choices = set(pmb.config.build_device_architectures + [arch_native])
ret = subparser.add_parser("repo_bootstrap") ret = subparser.add_parser("repo_bootstrap")
ret.add_argument("repository", ret.add_argument("repository",
help="which repository to bootstrap (e.g. systemd)") help="which repository to bootstrap (e.g. systemd)")
ret.add_argument("--arch", choices=arch_choices, dest="arch") ret.add_argument("--arch", choices=arch_choices, dest="arch",
type=lambda x: Arch.from_str(x))
return ret return ret
@ -547,8 +548,9 @@ def arguments_repo_missing(subparser):
" specific package and its dependencies") " specific package and its dependencies")
if "argcomplete" in sys.modules: if "argcomplete" in sys.modules:
package.completer = package_completer package.completer = package_completer
ret.add_argument("--arch", choices=pmb.config.build_device_architectures, ret.add_argument("--arch", choices=Arch.supported(),
default=pmb.config.arch_native) default=Arch.native(),
type=lambda x: Arch.from_str(x))
ret.add_argument("--built", action="store_true", ret.add_argument("--built", action="store_true",
help="include packages which exist in the binary repos") help="include packages which exist in the binary repos")
ret.add_argument("--overview", action="store_true", ret.add_argument("--overview", action="store_true",
@ -635,8 +637,8 @@ def add_kernel_arg(subparser, name="package", nargs="?", *args, **kwargs):
def get_parser(): def get_parser():
parser = argparse.ArgumentParser(prog="pmbootstrap") parser = argparse.ArgumentParser(prog="pmbootstrap")
arch_native = pmb.config.arch_native arch_native = Arch.native()
arch_choices = set(pmb.config.build_device_architectures + [arch_native]) arch_choices = Arch.supported()
default_config = Config() default_config = Config()
mirrors_pmos_default = ",".join(default_config.mirrors_postmarketos) mirrors_pmos_default = ",".join(default_config.mirrors_postmarketos)
@ -774,13 +776,15 @@ def get_parser():
# Action: stats # Action: stats
stats = sub.add_parser("stats", help="show ccache stats") stats = sub.add_parser("stats", help="show ccache stats")
stats.add_argument("--arch", default=arch_native, choices=arch_choices) stats.add_argument("--arch", default=arch_native, choices=arch_choices,
type=lambda x: Arch.from_str(x))
# Action: update # Action: update
update = sub.add_parser("update", help="update all existing APKINDEX" update = sub.add_parser("update", help="update all existing APKINDEX"
" files") " files")
update.add_argument("--arch", default=None, choices=arch_choices, update.add_argument("--arch", default=None, choices=arch_choices,
help="only update a specific architecture") help="only update a specific architecture",
type=lambda x: Arch.from_str(x))
update.add_argument("--non-existing", action="store_true", help="do not" update.add_argument("--non-existing", action="store_true", help="do not"
" only update the existing APKINDEX files, but all of" " only update the existing APKINDEX files, but all of"
" them", dest="non_existing") " them", dest="non_existing")
@ -817,7 +821,7 @@ def get_parser():
suffix.add_argument("-r", "--rootfs", action="store_true", suffix.add_argument("-r", "--rootfs", action="store_true",
help="Chroot for the device root file system") help="Chroot for the device root file system")
suffix.add_argument("-b", "--buildroot", nargs="?", const="device", suffix.add_argument("-b", "--buildroot", nargs="?", const="device",
choices={"device"} | arch_choices, choices={"device"} | {str(a) for a in arch_choices},
help="Chroot for building packages, defaults to" help="Chroot for building packages, defaults to"
" device architecture") " device architecture")
suffix.add_argument("-s", "--suffix", default=None, suffix.add_argument("-s", "--suffix", default=None,
@ -853,9 +857,10 @@ def get_parser():
build = sub.add_parser("build", help="create a package for a" build = sub.add_parser("build", help="create a package for a"
" specific architecture") " specific architecture")
build.add_argument("--arch", choices=arch_choices, default=None, build.add_argument("--arch", choices=arch_choices, default=None,
help="CPU architecture to build for (default: " + help="CPU architecture to build for (default: "
arch_native + " or first available architecture in" f"{arch_native} or first available architecture in"
" APKBUILD)") " APKBUILD)",
type=lambda x: Arch.from_str(x))
build.add_argument("--force", action="store_true", help="even build if not" build.add_argument("--force", action="store_true", help="even build if not"
" necessary") " necessary")
build.add_argument("--strict", action="store_true", help="(slower) zap and" build.add_argument("--strict", action="store_true", help="(slower) zap and"

View file

@ -4,10 +4,8 @@ from typing import Dict, List, Sequence, Set
from pmb.helpers import logging from pmb.helpers import logging
import pmb.chroot import pmb.chroot
import pmb.chroot.apk import pmb.chroot.apk
from pmb.types import PmbArgs
import pmb.helpers.pmaports import pmb.helpers.pmaports
import pmb.parse.apkindex import pmb.parse.apkindex
import pmb.parse.arch
from pmb.core import Chroot, get_context from pmb.core import Chroot, get_context

View file

@ -4,6 +4,7 @@ import copy
from pathlib import Path from pathlib import Path
from typing import Dict, Optional from typing import Dict, Optional
from pmb.core import get_context from pmb.core import get_context
from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
import os import os
import pmb.config import pmb.config
@ -97,7 +98,7 @@ class Deviceinfo:
codename: str codename: str
year: str year: str
dtb: str dtb: str
arch: str arch: Arch
# device # device
chassis: str chassis: str
@ -222,10 +223,10 @@ class Deviceinfo:
if "arch" not in info or not info["arch"]: if "arch" not in info or not info["arch"]:
raise RuntimeError(f"Please add 'deviceinfo_arch' to: {path}") raise RuntimeError(f"Please add 'deviceinfo_arch' to: {path}")
arch = info["arch"] arch = Arch.from_str(info["arch"])
if (arch != pmb.config.arch_native and if (not arch.is_native() and
arch not in pmb.config.build_device_architectures): arch not in Arch.supported()):
raise ValueError("Arch '" + arch + "' is not available in" raise ValueError(f"Arch '{arch}' is not available in"
" postmarketOS. If you would like to add it, see:" " postmarketOS. If you would like to add it, see:"
" <https://postmarketos.org/newarch>") " <https://postmarketos.org/newarch>")
@ -257,7 +258,10 @@ class Deviceinfo:
# FIXME: something to turn on and fix in the future # FIXME: something to turn on and fix in the future
# if key not in Deviceinfo.__annotations__.keys(): # if key not in Deviceinfo.__annotations__.keys():
# logging.warning(f"deviceinfo: {key} is not a known attribute") # logging.warning(f"deviceinfo: {key} is not a known attribute")
setattr(self, key, value) if key == "arch":
setattr(self, key, Arch(value))
else:
setattr(self, key, value)
if not self.flash_method: if not self.flash_method:
self.flash_method = "none" self.flash_method = "none"

View file

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import subprocess import subprocess
from typing import Sequence from typing import Sequence
from pmb.core.arch import Arch
from pmb.core.context import get_context from pmb.core.context import get_context
from pmb.helpers import logging from pmb.helpers import logging
import os import os
@ -20,7 +21,6 @@ import pmb.config
import pmb.config.pmaports import pmb.config.pmaports
from pmb.types import PathString, PmbArgs from pmb.types import PathString, PmbArgs
import pmb.helpers.run import pmb.helpers.run
import pmb.parse.arch
import pmb.parse.cpuinfo import pmb.parse.cpuinfo
from pmb.core import Chroot, ChrootType from pmb.core import Chroot, ChrootType
@ -48,15 +48,15 @@ def create_second_storage(args: PmbArgs, device: str):
path = Chroot.native() / "home/pmos/rootfs" / f"{device}-2nd.img" path = Chroot.native() / "home/pmos/rootfs" / f"{device}-2nd.img"
pmb.helpers.run.root(["touch", path]) pmb.helpers.run.root(["touch", path])
pmb.helpers.run.root(["chmod", "a+w", path]) pmb.helpers.run.root(["chmod", "a+w", path])
resize_image(args, args.second_storage, path) resize_image(args.second_storage, path)
return path return path
def which_qemu(arch): def which_qemu(arch: Arch):
""" """
Finds the qemu executable or raises an exception otherwise Finds the qemu executable or raises an exception otherwise
""" """
executable = "qemu-system-" + arch executable = "qemu-system-" + arch.qemu()
if shutil.which(executable): if shutil.which(executable):
return executable return executable
else: else:
@ -88,11 +88,11 @@ def create_gdk_loader_cache(args: PmbArgs) -> Path:
return chroot_native / custom_cache_path return chroot_native / custom_cache_path
def command_qemu(args: PmbArgs, device: str, arch, img_path, img_path_2nd=None): def command_qemu(args: PmbArgs, device: str, arch: Arch, img_path, img_path_2nd=None):
""" """
Generate the full qemu command with arguments to run postmarketOS Generate the full qemu command with arguments to run postmarketOS
""" """
cmdline = pmb.parse.deviceinfo().kernel_cmdline cmdline = pmb.parse.deviceinfo().kernel_cmdline or ""
if args.cmdline: if args.cmdline:
cmdline = args.cmdline cmdline = args.cmdline
@ -126,7 +126,7 @@ def command_qemu(args: PmbArgs, device: str, arch, img_path, img_path_2nd=None):
# QEMU mach-virt's max CPU count is 8, limit it so it will work correctly # QEMU mach-virt's max CPU count is 8, limit it so it will work correctly
# on systems with more than 8 CPUs # on systems with more than 8 CPUs
if arch != pmb.config.arch_native and ncpus > 8: if not arch.is_native() and ncpus > 8:
ncpus = 8 ncpus = 8
if args.host_qemu: if args.host_qemu:
@ -149,7 +149,7 @@ def command_qemu(args: PmbArgs, device: str, arch, img_path, img_path_2nd=None):
])}) ])})
command = [] command = []
if pmb.config.arch_native in ["aarch64", "armv7"]: if Arch.native() in [Arch.aarch64, Arch.armv7]:
# Workaround for QEMU failing on aarch64 asymmetric multiprocessor # Workaround for QEMU failing on aarch64 asymmetric multiprocessor
# arch (big/little architecture # arch (big/little architecture
# https://en.wikipedia.org/wiki/ARM_big.LITTLE) see # https://en.wikipedia.org/wiki/ARM_big.LITTLE) see
@ -159,11 +159,11 @@ def command_qemu(args: PmbArgs, device: str, arch, img_path, img_path_2nd=None):
ncpus = ncpus_bl ncpus = ncpus_bl
logging.info("QEMU will run on big/little architecture on the" logging.info("QEMU will run on big/little architecture on the"
f" first {ncpus} cores (from /proc/cpuinfo)") f" first {ncpus} cores (from /proc/cpuinfo)")
command += [chroot_native / "lib" / f"ld-musl-{pmb.config.arch_native}.so.1"] command += [chroot_native / "lib" / f"ld-musl-{Arch.native()}.so.1"]
command += [chroot_native / "usr/bin/taskset"] command += [chroot_native / "usr/bin/taskset"]
command += ["-c", "0-" + str(ncpus - 1)] command += ["-c", "0-" + str(ncpus - 1)]
command += [chroot_native / "lib" / f"ld-musl-{pmb.config.arch_native}.so.1"] command += [chroot_native / "lib" / f"ld-musl-{Arch.native()}.so.1"]
command += ["--library-path=" + ":".join([ command += ["--library-path=" + ":".join([
str(chroot_native / "lib"), str(chroot_native / "lib"),
str(chroot_native / "usr/lib"), str(chroot_native / "usr/lib"),
@ -203,13 +203,13 @@ def command_qemu(args: PmbArgs, device: str, arch, img_path, img_path_2nd=None):
command += ["-netdev", f"user,id=net,hostfwd=tcp:127.0.0.1:{port_ssh}-:22"] command += ["-netdev", f"user,id=net,hostfwd=tcp:127.0.0.1:{port_ssh}-:22"]
command += ["-device", "virtio-net-pci,netdev=net"] command += ["-device", "virtio-net-pci,netdev=net"]
if arch == "x86_64": if arch == Arch.x86_64:
command += ["-device", "virtio-vga-gl"] command += ["-device", "virtio-vga-gl"]
elif arch == "aarch64": elif arch == Arch.aarch64:
command += ["-M", "virt"] command += ["-M", "virt"]
command += ["-cpu", "cortex-a57"] command += ["-cpu", "cortex-a57"]
command += ["-device", "virtio-gpu-pci"] command += ["-device", "virtio-gpu-pci"]
elif arch == "riscv64": elif arch == Arch.riscv64:
command += ["-M", "virt"] command += ["-M", "virt"]
command += ["-device", "virtio-gpu-pci"] command += ["-device", "virtio-gpu-pci"]
else: else:
@ -221,7 +221,7 @@ def command_qemu(args: PmbArgs, device: str, arch, img_path, img_path_2nd=None):
"if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF.fd"] "if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF.fd"]
# Kernel Virtual Machine (KVM) support # Kernel Virtual Machine (KVM) support
native = pmb.config.arch_native == pmb.parse.deviceinfo().arch native = pmb.parse.deviceinfo().arch.is_native()
if args.qemu_kvm and native and os.path.exists("/dev/kvm"): if args.qemu_kvm and native and os.path.exists("/dev/kvm"):
command += ["-enable-kvm"] command += ["-enable-kvm"]
command += ["-cpu", "host"] command += ["-cpu", "host"]
@ -246,7 +246,7 @@ def command_qemu(args: PmbArgs, device: str, arch, img_path, img_path_2nd=None):
return (command, env) return (command, env)
def resize_image(args: PmbArgs, img_size_new, img_path): def resize_image(img_size_new, img_path):
""" """
Truncates an image to a specific size. The value must be larger than the Truncates an image to a specific size. The value must be larger than the
current image size, and it must be specified in MiB or GiB units (powers of current image size, and it must be specified in MiB or GiB units (powers of
@ -292,7 +292,7 @@ def sigterm_handler(number, frame):
" and killed the QEMU VM it was running.") " and killed the QEMU VM it was running.")
def install_depends(args: PmbArgs, arch): def install_depends(args: PmbArgs, arch: Arch):
""" """
Install any necessary qemu dependencies in native chroot Install any necessary qemu dependencies in native chroot
""" """
@ -309,7 +309,7 @@ def install_depends(args: PmbArgs, arch):
"qemu-hw-display-virtio-gpu-pci", "qemu-hw-display-virtio-gpu-pci",
"qemu-hw-display-virtio-vga", "qemu-hw-display-virtio-vga",
"qemu-hw-display-virtio-vga-gl", "qemu-hw-display-virtio-vga-gl",
"qemu-system-" + arch, "qemu-system-" + arch.qemu(),
"qemu-ui-gtk", "qemu-ui-gtk",
"qemu-ui-opengl", "qemu-ui-opengl",
"qemu-ui-sdl", "qemu-ui-sdl",
@ -338,7 +338,7 @@ def run(args: PmbArgs):
raise RuntimeError("'pmbootstrap qemu' can be only used with one of " raise RuntimeError("'pmbootstrap qemu' can be only used with one of "
"the QEMU device packages. Run 'pmbootstrap init' " "the QEMU device packages. Run 'pmbootstrap init' "
"and select the 'qemu' vendor.") "and select the 'qemu' vendor.")
arch = pmb.parse.arch.alpine_to_qemu(pmb.parse.deviceinfo().arch) arch = pmb.parse.deviceinfo().arch
img_path = system_image(device) img_path = system_image(device)
img_path_2nd = None img_path_2nd = None
@ -347,7 +347,7 @@ def run(args: PmbArgs):
if not args.host_qemu: if not args.host_qemu:
install_depends(args, arch) install_depends(args, arch)
logging.info("Running postmarketOS in QEMU VM (" + arch + ")") logging.info("Running postmarketOS in QEMU VM (" + arch.qemu() + ")")
qemu, env = command_qemu(args, device, arch, img_path, img_path_2nd) qemu, env = command_qemu(args, device, arch, img_path, img_path_2nd)
@ -358,7 +358,7 @@ def run(args: PmbArgs):
# Resize the rootfs (or show hint) # Resize the rootfs (or show hint)
if args.image_size: if args.image_size:
resize_image(args, args.image_size, img_path) resize_image(args.image_size, img_path)
else: else:
logging.info("NOTE: Run 'pmbootstrap qemu --image-size 2G' to set" logging.info("NOTE: Run 'pmbootstrap qemu --image-size 2G' to set"
" the rootfs size when you run out of space!") " the rootfs size when you run out of space!")

View file

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import os import os
from typing import List from typing import List
from pmb.core.arch import Arch
from pmb.helpers import logging from pmb.helpers import logging
import shlex import shlex
@ -9,7 +10,6 @@ from pmb.types import PathString, PmbArgs
import pmb.helpers.run import pmb.helpers.run
import pmb.helpers.run_core import pmb.helpers.run_core
import pmb.parse.apkindex import pmb.parse.apkindex
import pmb.parse.arch
import pmb.config.pmaports import pmb.config.pmaports
import pmb.build import pmb.build
from pmb.core import get_context from pmb.core import get_context
@ -39,7 +39,7 @@ def scp_abuild_key(args: PmbArgs, user: str, host: str, port: str):
pmb.helpers.run.user(command, output="tui") pmb.helpers.run.user(command, output="tui")
def ssh_find_arch(args: PmbArgs, user: str, host: str, port: str) -> str: def ssh_find_arch(args: PmbArgs, user: str, host: str, port: str) -> Arch:
"""Connect to a device via ssh and query the architecture.""" """Connect to a device via ssh and query the architecture."""
logging.info(f"Querying architecture of {user}@{host}") logging.info(f"Querying architecture of {user}@{host}")
command = ["ssh", "-p", port, f"{user}@{host}", "uname -m"] command = ["ssh", "-p", port, f"{user}@{host}", "uname -m"]
@ -49,7 +49,7 @@ def ssh_find_arch(args: PmbArgs, user: str, host: str, port: str) -> str:
output_lines = output.strip().splitlines() output_lines = output.strip().splitlines()
# Pick out last line which should contain the foreign device's architecture # Pick out last line which should contain the foreign device's architecture
foreign_machine_type = output_lines[-1] foreign_machine_type = output_lines[-1]
alpine_architecture = pmb.parse.arch.machine_type_to_alpine(foreign_machine_type) alpine_architecture = Arch.from_machine_type(foreign_machine_type)
return alpine_architecture return alpine_architecture
@ -81,7 +81,7 @@ def ssh_install_apks(args: PmbArgs, user, host, port, paths):
pmb.helpers.run.user(command, output="tui") pmb.helpers.run.user(command, output="tui")
def sideload(args: PmbArgs, user: str, host: str, port: str, arch: str, copy_key: bool, pkgnames): def sideload(args: PmbArgs, user: str, host: str, port: str, arch: Arch, copy_key: bool, pkgnames):
""" Build packages if necessary and install them via SSH. """ Build packages if necessary and install them via SSH.
:param user: target device ssh username :param user: target device ssh username

View file

@ -5,6 +5,8 @@ from argparse import Namespace
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Tuple, TypedDict, Union from typing import Dict, List, Optional, Tuple, TypedDict, Union
from pmb.core.arch import Arch
PathString = Union[Path, str] PathString = Union[Path, str]
Env = Dict[str, PathString] Env = Dict[str, PathString]
@ -37,7 +39,7 @@ class PmbArgs(Namespace):
android_recovery_zip: str android_recovery_zip: str
aports: Optional[Path] aports: Optional[Path]
_aports_real: str _aports_real: str
arch: str arch: Arch
as_root: str as_root: str
assume_yes: str assume_yes: str
auto: str auto: str