mirror of
https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git
synced 2025-07-13 11:29:46 +03:00
Introduce a new "cache" subdirectory in the pmbootstrap workdir, all the cache and config bits go in here, anything that needs to be accessible from inside a chroot. The whole dir is then bind-mounted into the chroot as /cache with appropriate symlinks. This dir is in the config as config.cache. In addition, all the cache_* and other config dirs are renamed to be closer to the names of the equivalent dirs in the chroot (e.g. abuild-config) and to avoid redundant naming since they are now under a "cache" dir. Signed-off-by: Casey Connolly <kcxt@postmarketos.org>
226 lines
8 KiB
Python
226 lines
8 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
|
|
|
|
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.arch import Arch
|
|
from pmb.core.context import get_context
|
|
from pmb.types import Apkbuild
|
|
|
|
|
|
def copy_to_buildpath(
|
|
package: str, chroot: Chroot = Chroot.native(), no_override: bool = False
|
|
) -> None:
|
|
# 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) -> None:
|
|
"""Override some abuild functions by patching the APKBUILD file."""
|
|
|
|
if apkbuild.is_relative_to(get_context().config.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.Enum):
|
|
# 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 __str__(self) -> str:
|
|
return self.value
|
|
|
|
def necessary(self) -> bool:
|
|
return self in [BuildStatus.OUTDATED, BuildStatus.NEW]
|
|
|
|
|
|
def get_status(arch: Arch | None, apkbuild: Apkbuild) -> 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)
|
|
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"
|
|
f" 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: Arch | None = None) -> 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.cache / "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("/cache/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: bool = False) -> None:
|
|
"""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 + str(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: bool = False) -> None:
|
|
"""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.cache / f"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)
|