forked from Mirror/pmbootstrap
We can fairly easily patch abuild by appending anything we want to the APKBUILD file after copying it to the chroot. Leverage this to override the verify() function so that it doesn't die when checksums don't match. Instead let's build the package anyway, but set a flag and print a warning with instructions on how to generate the checksums. We should continue to require APKBUILDs in pmaports to have valid checksums. Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
209 lines
7.9 KiB
Python
209 lines
7.9 KiB
Python
# Copyright 2023 Oliver Smith
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
import enum
|
|
from pmb.helpers import logging
|
|
import os
|
|
from pathlib import Path
|
|
import shlex
|
|
import datetime
|
|
from typing import List
|
|
|
|
import pmb.chroot
|
|
import pmb.config.pmaports
|
|
import pmb.build
|
|
import pmb.helpers.file
|
|
import pmb.helpers.git
|
|
import pmb.helpers.pmaports
|
|
import pmb.helpers.run
|
|
import pmb.parse.apkindex
|
|
import pmb.parse.version
|
|
from pmb.core import Chroot
|
|
from pmb.core.context import get_context
|
|
|
|
|
|
def copy_to_buildpath(package, chroot: Chroot=Chroot.native(), no_override: bool=False):
|
|
# Sanity check
|
|
aport = pmb.helpers.pmaports.find(package)
|
|
if not os.path.exists(aport / "APKBUILD"):
|
|
raise ValueError(f"Path does not contain an APKBUILD file: {aport}")
|
|
|
|
# Clean up folder
|
|
build = chroot / "home/pmos/build"
|
|
if build.exists():
|
|
pmb.helpers.run.root(["rm", "-rf", build])
|
|
|
|
# Copy aport contents with resolved symlinks
|
|
pmb.helpers.run.root(["mkdir", "-p", build])
|
|
for entry in aport.iterdir():
|
|
file = entry.name
|
|
# Don't copy those dirs, as those have probably been generated by running `abuild`
|
|
# on the host system directly and not cleaning up after itself.
|
|
# Those dirs might contain broken symlinks and cp fails resolving them.
|
|
if file in ["src", "pkg"]:
|
|
logging.warning(f"WARNING: Not copying {file}, looks like a leftover from abuild")
|
|
continue
|
|
pmb.helpers.run.root(["cp", "-rL", aport / file, build / file])
|
|
|
|
if not no_override:
|
|
abuild_overrides(build / "APKBUILD")
|
|
|
|
pmb.chroot.root(["chown", "-R", "pmos:pmos",
|
|
"/home/pmos/build"], chroot)
|
|
|
|
|
|
def abuild_overrides(apkbuild: Path):
|
|
"""Override some abuild functions by patching the APKBUILD file."""
|
|
|
|
if apkbuild.is_relative_to(get_context().config.work / "cache_git"):
|
|
raise ValueError(f"Refusing to patch file in pmaports repo: {apkbuild}")
|
|
|
|
# Patch the APKBUILD file
|
|
override_path = pmb.config.pmb_src / "pmb/data/abuild_overrides.sh"
|
|
pmb.helpers.run.root(["sh", "-c", f"cat {override_path} >> {apkbuild}"])
|
|
|
|
|
|
class BuildStatus(enum.StrEnum):
|
|
# The binary package is outdated
|
|
OUTDATED = "outdated"
|
|
# There is no binary package
|
|
NEW = "new"
|
|
# Source package can't be built
|
|
CANT_BUILD = "cant_build"
|
|
# Building isn't needed
|
|
UNNECESSARY = "unnecessary"
|
|
|
|
def necessary(self):
|
|
return self in [BuildStatus.OUTDATED, BuildStatus.NEW]
|
|
|
|
def get_status(arch, apkbuild, indexes=None) -> BuildStatus:
|
|
"""Check if the package has already been built.
|
|
|
|
Compared to abuild's check, this check also works for different architectures.
|
|
|
|
:param arch: package target architecture
|
|
:param apkbuild: from pmb.parse.apkbuild()
|
|
:param indexes: list of APKINDEX.tar.gz paths
|
|
:returns: boolean
|
|
"""
|
|
package = apkbuild["pkgname"]
|
|
version_pmaports = apkbuild["pkgver"] + "-r" + apkbuild["pkgrel"]
|
|
msg = "Build is necessary for package '" + package + "': "
|
|
|
|
# Get version from APKINDEX
|
|
index_data = pmb.parse.apkindex.package(package, arch, False,
|
|
indexes)
|
|
if not index_data:
|
|
logging.debug(msg + "No binary package available")
|
|
return BuildStatus.NEW
|
|
|
|
# Can't build pmaport for arch: use Alpine's package (#1897)
|
|
if arch and not pmb.helpers.pmaports.check_arches(apkbuild["arch"], arch):
|
|
logging.verbose(f"{package}: build is not necessary, because pmaport"
|
|
" can't be built for {arch}. Using Alpine's binary"
|
|
" package.")
|
|
return BuildStatus.CANT_BUILD
|
|
|
|
# a) Binary repo has a newer version
|
|
version_binary = index_data["version"]
|
|
if pmb.parse.version.compare(version_binary, version_pmaports) == 1:
|
|
logging.warning(f"WARNING: about to install {package} {version_binary}"
|
|
f" (local pmaports: {version_pmaports}, consider"
|
|
" 'pmbootstrap pull')")
|
|
return BuildStatus.UNNECESSARY
|
|
|
|
# b) Local pmaports has a newer version
|
|
if version_pmaports != version_binary:
|
|
logging.debug(f"{msg}binary package out of date (binary: "
|
|
f"{version_binary}, local pmaports: {version_pmaports})")
|
|
return BuildStatus.OUTDATED
|
|
|
|
# Local pmaports and binary repo have the same version
|
|
return BuildStatus.UNNECESSARY
|
|
|
|
|
|
def index_repo(arch=None):
|
|
"""Recreate the APKINDEX.tar.gz for a specific repo, and clear the parsing
|
|
cache for that file for the current pmbootstrap session (to prevent
|
|
rebuilding packages twice, in case the rebuild takes less than a second).
|
|
|
|
:param arch: when not defined, re-index all repos
|
|
"""
|
|
pmb.build.init()
|
|
|
|
paths: List[Path] = []
|
|
|
|
for channel in pmb.config.pmaports.all_channels():
|
|
pkgdir: Path = (get_context().config.work / "packages" / channel)
|
|
if arch:
|
|
paths.append(pkgdir / arch)
|
|
else:
|
|
paths += list(pkgdir.glob("*"))
|
|
|
|
for path in paths:
|
|
if path.is_dir():
|
|
path_channel, path_arch = path.parts[-2:]
|
|
path_repo_chroot = Path("/mnt/pmbootstrap/packages") / path_channel / path_arch
|
|
logging.debug(f"(native) index {path_channel}/{path_arch} repository")
|
|
description = str(datetime.datetime.now())
|
|
commands = [
|
|
# Wrap the index command with sh so we can use '*.apk'
|
|
["sh", "-c", "apk -q index --output APKINDEX.tar.gz_"
|
|
" --description " + shlex.quote(description) + ""
|
|
" --rewrite-arch " + shlex.quote(path_arch) + " *.apk"],
|
|
["abuild-sign", "APKINDEX.tar.gz_"],
|
|
["mv", "APKINDEX.tar.gz_", "APKINDEX.tar.gz"]
|
|
]
|
|
pmb.chroot.userm(commands, working_dir=path_repo_chroot)
|
|
else:
|
|
logging.debug(f"NOTE: Can't build index for: {path}")
|
|
pmb.parse.apkindex.clear_cache(path / "APKINDEX.tar.gz")
|
|
|
|
|
|
def configure_abuild(chroot: Chroot, verify=False):
|
|
"""Set the correct JOBS count in ``abuild.conf``.
|
|
|
|
:param verify: internally used to test if changing the config has worked.
|
|
"""
|
|
jobs = get_context().config.jobs
|
|
path = chroot / "etc/abuild.conf"
|
|
prefix = "export JOBS="
|
|
with path.open(encoding="utf-8") as handle:
|
|
for line in handle:
|
|
if not line.startswith(prefix):
|
|
continue
|
|
if line != (prefix + jobs + "\n"):
|
|
if verify:
|
|
raise RuntimeError(f"Failed to configure abuild: {path}"
|
|
"\nTry to delete the file"
|
|
"(or zap the chroot).")
|
|
pmb.chroot.root(["sed", "-i", "-e",
|
|
f"s/^{prefix}.*/{prefix}{jobs}/",
|
|
"/etc/abuild.conf"],
|
|
chroot)
|
|
configure_abuild(chroot, True)
|
|
return
|
|
pmb.chroot.root(["sed", "-i", f"$ a\\{prefix}{jobs}", "/etc/abuild.conf"], chroot)
|
|
|
|
|
|
def configure_ccache(chroot: Chroot=Chroot.native(), verify=False):
|
|
"""Set the maximum ccache size.
|
|
|
|
:param verify: internally used to test if changing the config has worked.
|
|
"""
|
|
# Check if the settings have been set already
|
|
arch = chroot.arch
|
|
config = get_context().config
|
|
path = config.work / f"cache_ccache_{arch}" / "ccache.conf"
|
|
if os.path.exists(path):
|
|
with open(path, encoding="utf-8") as handle:
|
|
for line in handle:
|
|
if line == ("max_size = " + config.ccache_size + "\n"):
|
|
return
|
|
if verify:
|
|
raise RuntimeError(f"Failed to configure ccache: {path}\nTry to"
|
|
" delete the file (or zap the chroot).")
|
|
|
|
# Set the size and verify
|
|
pmb.chroot.user(["ccache", "--max-size", config.ccache_size],
|
|
chroot)
|
|
configure_ccache(chroot, True)
|