forked from Mirror/pmbootstrap
There was an oversight when this API was originally created and it implicitly assumed that the pmaports repository was always named "pmaports". This unfortunately broke some peoples workflows. Introduce a new "pkgrepo_name()" function and adjust the codebase to use it, as well as adjusting pkgrepo internally to special case the "pmaports" repo so that it's always named pmaports no matter what the directory itself is named. This is probably more complexity than we should be dealing with here, we should probably create a new type to encode this behaviour. Fixes: #2412 Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
296 lines
11 KiB
Python
296 lines
11 KiB
Python
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, CrossCompileType, 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,
|
|
strict: bool = False,
|
|
force: bool = False,
|
|
cross: CrossCompileType = None,
|
|
suffix: Chroot = Chroot.native(),
|
|
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 cross: None, "native", or "crossdirect"
|
|
: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 == "native" and "!tracedeps" not in apkbuild["options"]:
|
|
logging.info(
|
|
"WARNING: Option !tracedeps is not set, but we're"
|
|
" cross-compiling in the native chroot. This will"
|
|
" probably fail!"
|
|
)
|
|
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"],
|
|
],
|
|
suffix,
|
|
)
|
|
|
|
# Environment variables
|
|
env: Env = {"CARCH": str(arch), "SUDO_APK": "abuild-apk --no-progress"}
|
|
if cross == "native":
|
|
hostspec = arch.alpine_triple()
|
|
env["CROSS_COMPILE"] = hostspec + "-"
|
|
env["CC"] = hostspec + "-gcc"
|
|
if cross == "crossdirect":
|
|
env["PATH"] = ":".join([f"/native/usr/lib/crossdirect/{arch}", pmb.config.chroot_path])
|
|
if not context.ccache:
|
|
env["CCACHE_DISABLE"] = "1"
|
|
|
|
# Use sccache without crossdirect (crossdirect uses it via rustc.sh)
|
|
if context.ccache and cross != "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
|
|
cmd = ["abuild", "-D", "postmarketOS"]
|
|
if strict or "pmb:strict" in apkbuild["options"]:
|
|
if not strict:
|
|
logging.debug(
|
|
apkbuild["pkgname"] + ": 'pmb:strict' found in" " options, building in strict mode"
|
|
)
|
|
cmd += ["-r"] # install depends with abuild
|
|
else:
|
|
cmd += ["-d"] # do not install depends with abuild
|
|
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"], suffix, no_override=strict)
|
|
if src and strict:
|
|
logging.debug(f"({suffix}) Ensuring previous build artifacts are removed")
|
|
pmb.chroot.root(["rm", "-rf", "/tmp/pmbootstrap-local-source-copy"], suffix)
|
|
override_source(apkbuild, pkgver, src, suffix)
|
|
link_to_git_dir(suffix)
|
|
|
|
try:
|
|
pmb.chroot.user(cmd, suffix, Path("/home/pmos/build"), env=env)
|
|
finally:
|
|
handle_csum_failure(apkbuild, suffix)
|