forked from Mirror/pmbootstrap
319 lines
12 KiB
Python
319 lines
12 KiB
Python
# Copyright 2024 Caleb Connolly
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import enum
|
|
from pathlib import Path
|
|
from pmb.core.pkgrepo import pkgrepo_name, pkgrepo_paths
|
|
import pmb.helpers.run
|
|
import pmb.chroot
|
|
|
|
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, CrossCompile, Env
|
|
|
|
|
|
class BootstrapStage(enum.IntEnum):
|
|
"""
|
|
Pass a BOOTSTRAP= environment variable with the given value to abuild. See
|
|
bootstrap_1 etc. at https://postmarketos.org/pmaports.cfg for details.
|
|
"""
|
|
|
|
NONE = 0
|
|
# We don't need explicit representations of the other numbers.
|
|
|
|
|
|
def override_source(
|
|
apkbuild: Apkbuild, pkgver: str, src: str | None, chroot: Chroot = Chroot.native()
|
|
) -> None:
|
|
"""Mount local source inside chroot and append new functions (prepare() etc.)
|
|
to the APKBUILD to make it use the local source.
|
|
"""
|
|
if not src:
|
|
return
|
|
|
|
# Mount source in chroot
|
|
mount_path = "/mnt/pmbootstrap/source-override/"
|
|
mount_path_outside = chroot / mount_path
|
|
pmb.helpers.mount.bind(src, mount_path_outside, umount=True)
|
|
|
|
# Delete existing append file
|
|
append_path = "/tmp/APKBUILD.append"
|
|
append_path_outside = chroot / append_path
|
|
if append_path_outside.exists():
|
|
pmb.chroot.root(["rm", append_path], chroot)
|
|
|
|
# Add src path to pkgdesc, cut it off after max length
|
|
pkgdesc = ("[" + src + "] " + apkbuild["pkgdesc"])[:127]
|
|
|
|
pkgname = apkbuild["pkgname"]
|
|
|
|
# Appended content
|
|
append = (
|
|
"""
|
|
# ** Overrides below appended by pmbootstrap for --src **
|
|
|
|
pkgver=\""""
|
|
+ pkgver
|
|
+ """\"
|
|
pkgdesc=\""""
|
|
+ pkgdesc
|
|
+ """\"
|
|
_pmb_src_copy="/tmp/pmbootstrap-local-source-copy"
|
|
|
|
# Empty $source avoids patching in prepare()
|
|
_pmb_source_original="$source"
|
|
source=""
|
|
sha512sums=""
|
|
|
|
fetch() {
|
|
# Update source copy
|
|
msg "Copying source from host system: """
|
|
+ src
|
|
+ """\"
|
|
local exclude_from=\""""
|
|
+ mount_path
|
|
+ """/.gitignore\"
|
|
local rsync_args=""
|
|
if [ -f "$exclude_from" ]; then
|
|
rsync_args="--exclude-from=\"$exclude_from\""
|
|
fi
|
|
if ! [ \""""
|
|
+ pkgname
|
|
+ """\" = "$(cat /tmp/src-pkgname)" ]; then
|
|
rsync_args="--delete $rsync_args"
|
|
fi
|
|
rsync -a --exclude=".git/" $rsync_args --ignore-errors --force \\
|
|
\""""
|
|
+ mount_path
|
|
+ """\" "$_pmb_src_copy" || true
|
|
|
|
# Link local source files (e.g. kernel config)
|
|
mkdir "$srcdir"
|
|
local s
|
|
for s in $_pmb_source_original; do
|
|
is_remote "$s" || ln -sf "$startdir/$s" "$srcdir/"
|
|
done
|
|
|
|
echo \""""
|
|
+ pkgname
|
|
+ """\" > /tmp/src-pkgname
|
|
}
|
|
|
|
unpack() {
|
|
ln -sv "$_pmb_src_copy" "$builddir"
|
|
}
|
|
"""
|
|
)
|
|
|
|
# Write and log append file
|
|
with open(append_path_outside, "w", encoding="utf-8") as handle:
|
|
for line in append.split("\n"):
|
|
handle.write(line[13:].replace(" " * 4, "\t") + "\n")
|
|
pmb.chroot.user(["cat", append_path], chroot)
|
|
|
|
# Append it to the APKBUILD
|
|
apkbuild_path = "/home/pmos/build/APKBUILD"
|
|
shell_cmd = "cat " + apkbuild_path + " " + append_path + " > " + append_path + "_"
|
|
pmb.chroot.user(["sh", "-c", shell_cmd], chroot)
|
|
pmb.chroot.user(["mv", append_path + "_", apkbuild_path], chroot)
|
|
|
|
|
|
def mount_pmaports(chroot: Chroot = Chroot.native()) -> dict[str, Path]:
|
|
"""
|
|
Mount pmaports.git in chroot.
|
|
|
|
:param chroot: chroot to target
|
|
:returns: dictionary mapping pkgrepo name to dest path
|
|
"""
|
|
dest_paths = {}
|
|
for repo in pkgrepo_paths(skip_extras=True):
|
|
destination = Path("/mnt") / pkgrepo_name(repo)
|
|
outside_destination = chroot / destination
|
|
pmb.helpers.mount.bind(repo, outside_destination, umount=True)
|
|
dest_paths[pkgrepo_name(repo)] = destination
|
|
|
|
return dest_paths
|
|
|
|
|
|
def link_to_git_dir(chroot: Chroot) -> None:
|
|
"""Make ``/home/pmos/build/.git`` point to the .git dir from pmaports.git, with a
|
|
symlink so abuild does not fail (#1841).
|
|
|
|
abuild expects the current working directory to be a subdirectory of a
|
|
cloned git repository (e.g. main/openrc from aports.git). If git is
|
|
installed, it will try to get the last git commit from that repository, and
|
|
place it in the resulting apk (.PKGINFO) as well as use the date from that
|
|
commit as SOURCE_DATE_EPOCH (for reproducible builds).
|
|
|
|
With that symlink, we actually make it use the last git commit from
|
|
pmaports.git for SOURCE_DATE_EPOCH and have that in the resulting apk's
|
|
.PKGINFO.
|
|
"""
|
|
# Mount pmaports.git in chroot, in case the user did not use pmbootstrap to
|
|
# clone it (e.g. how we build on sourcehut). Do this here and not at the
|
|
# initialization of the chroot, because the pmaports dir may not exist yet
|
|
# at that point. Use umount=True, so we don't have an old path mounted
|
|
# (some tests change the pmaports dir).
|
|
dest_paths = mount_pmaports(chroot)
|
|
|
|
# Create .git symlink
|
|
pmb.chroot.user(["mkdir", "-p", "/home/pmos/build"], chroot)
|
|
pmb.chroot.user(["ln", "-sf", dest_paths["pmaports"] / ".git", "/home/pmos/build/.git"], chroot)
|
|
|
|
|
|
def handle_csum_failure(apkbuild: Apkbuild, chroot: Chroot) -> None:
|
|
csum_fail_path = chroot / "tmp/apkbuild_verify_failed"
|
|
if not csum_fail_path.exists():
|
|
return
|
|
|
|
reason = csum_fail_path.open().read().strip()
|
|
if reason == "local":
|
|
logging.info(
|
|
"WARNING: Some checksums didn't match, run"
|
|
f" 'pmbootstrap checksum {apkbuild['pkgname']}' to fix them."
|
|
)
|
|
else:
|
|
logging.error(f"ERROR: Remote checksum mismatch for {apkbuild['pkgname']}")
|
|
logging.error("NOTE: If you just modified this package:")
|
|
logging.error(
|
|
f" * run 'pmbootstrap checksum {apkbuild['pkgname']}' to update the checksums."
|
|
)
|
|
logging.error("If you didn't modify it, try building again to re-download the sources.")
|
|
raise RuntimeError(f"Remote checksum mismatch for {apkbuild['pkgname']}")
|
|
|
|
|
|
def run_abuild(
|
|
context: Context,
|
|
apkbuild: Apkbuild,
|
|
pkgver: str,
|
|
channel: str,
|
|
arch: Arch,
|
|
cross: CrossCompile,
|
|
strict: bool = False,
|
|
force: bool = False,
|
|
src: str | None = None,
|
|
bootstrap_stage: int = BootstrapStage.NONE,
|
|
) -> None:
|
|
"""
|
|
Set up all environment variables and construct the abuild command (all
|
|
depending on the cross-compiler method and target architecture), copy
|
|
the aport to the chroot and execute abuild.
|
|
|
|
:param src: override source used to build the package with a local folder
|
|
:param bootstrap_stage: pass a BOOTSTRAP= env var with the value to abuild
|
|
:returns: (output, cmd, env), output is the destination apk path relative
|
|
to the package folder ("x86_64/hello-1-r2.apk"). cmd and env are
|
|
used by the test case, and they are the full abuild command and
|
|
the environment variables dict generated in this function.
|
|
"""
|
|
# Sanity check
|
|
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 == 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():
|
|
pmb.helpers.run.root(["mkdir", "-p", pkgdir])
|
|
pmb.helpers.run.root(
|
|
[
|
|
"chown",
|
|
"-R",
|
|
f"{pmb.config.chroot_uid_user}:{pmb.config.chroot_uid_user}",
|
|
pkgdir.parent,
|
|
]
|
|
)
|
|
|
|
pmb.chroot.rootm(
|
|
[
|
|
["mkdir", "-p", "/home/pmos/packages"],
|
|
["rm", "-f", "/home/pmos/packages/pmos"],
|
|
["ln", "-sf", f"/mnt/pmbootstrap/packages/{channel}", "/home/pmos/packages/pmos"],
|
|
],
|
|
buildchroot,
|
|
)
|
|
|
|
# Environment variables
|
|
env: Env = {"SUDO_APK": "abuild-apk --no-progress"}
|
|
if cross == CrossCompile.CROSS_NATIVE:
|
|
hostspec = arch.alpine_triple()
|
|
env["CROSS_COMPILE"] = hostspec + "-"
|
|
env["CC"] = hostspec + "-gcc"
|
|
if cross == CrossCompile.CROSS_NATIVE2:
|
|
env["CHOST"] = str(arch)
|
|
env["CBUILDROOT"] = "/mnt/sysroot"
|
|
env["CFLAGS"] = "-Wl,-rpath-link=/mnt/sysroot/usr/lib"
|
|
env["CGO_CFLAGS"] = "--sysroot=/mnt/sysroot"
|
|
env["CGO_LDFLAGS"] = "--sysroot=/mnt/sysroot"
|
|
try:
|
|
env["GOARCH"] = arch.go()
|
|
except ValueError:
|
|
logging.debug(f"Not setting $GOARCH for {arch}")
|
|
|
|
elif cross == CrossCompile.CROSSDIRECT:
|
|
env["PATH"] = ":".join([f"/native/usr/lib/crossdirect/{arch}", pmb.config.chroot_path])
|
|
else:
|
|
env["CARCH"] = str(arch)
|
|
if not context.ccache:
|
|
env["CCACHE_DISABLE"] = "1"
|
|
|
|
# Use sccache without crossdirect (crossdirect uses it via rustc.sh)
|
|
if context.ccache and cross != CrossCompile.CROSSDIRECT:
|
|
env["RUSTC_WRAPPER"] = "/usr/bin/sccache"
|
|
|
|
# Cache binary objects from go in this path (like ccache)
|
|
env["GOCACHE"] = "/home/pmos/.cache/go-build"
|
|
|
|
# Cache go modules (git repositories). Usually these should be bundled and
|
|
# it should not be required to download them at build time, in that case
|
|
# the APKBUILD sets the GOPATH (and therefore indirectly GOMODCACHE). But
|
|
# e.g. when using --src they are not bundled, in that case it makes sense
|
|
# to point GOMODCACHE at pmbootstrap's work dir so the modules are only
|
|
# downloaded once.
|
|
if context.go_mod_cache:
|
|
env["GOMODCACHE"] = "/home/pmos/go/pkg/mod"
|
|
|
|
if bootstrap_stage:
|
|
env["BOOTSTRAP"] = str(bootstrap_stage)
|
|
|
|
# Build the abuild command
|
|
# Since we install dependencies with pmb, disable dependency handling in abuild.
|
|
# This is also required so that abuild doesn't try to install base-build-$ARCH packages
|
|
# which don't exist
|
|
cmd = ["abuild", "-d", "-D", "postmarketOS"]
|
|
if force:
|
|
cmd += ["-f"]
|
|
if src:
|
|
# Keep build artifacts, so repeated invocations will do incremental
|
|
# building.
|
|
cmd += ["-K"]
|
|
|
|
# Copy the aport to the chroot and build it
|
|
pmb.build.copy_to_buildpath(apkbuild["pkgname"], buildchroot, no_override=strict)
|
|
if src and strict:
|
|
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, buildchroot, Path("/home/pmos/build"), env=env)
|
|
finally:
|
|
handle_csum_failure(apkbuild, buildchroot)
|
|
|
|
pmb.helpers.run.root(["umount", buildchroot / "/mnt/sysroot"], output="null", check=False)
|