chroot: always run apk static (MR 2252)

Testing by building postmarketos-initramfs (which installs >100 packages
but is very fast to build, so a worst-case scenario) this results in a
~15-20% speedup (which everything cached and doing multiple back to back
runs). From 32 seconds down to 25.

Doing a full install with --no-image, this takes us from 70 seconds on
my laptop down to 40s!

This also lets us drastically simplify pmb/helpers/apk.py!

Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
This commit is contained in:
Caleb Connolly 2024-05-24 04:45:36 +02:00 committed by Oliver Smith
parent cf651e56d5
commit b82c4eb167
No known key found for this signature in database
GPG key ID: 5AE7F5513E0885CB
5 changed files with 41 additions and 43 deletions

View file

@ -1,5 +1,9 @@
# 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
from pathlib import Path
import pmb.chroot.apk_static
from pmb.core.chroot import ChrootType
from pmb.helpers import logging from pmb.helpers import logging
import shlex import shlex
from typing import List from typing import List
@ -11,6 +15,7 @@ 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.parse.apkindex import pmb.parse.apkindex
import pmb.parse.arch import pmb.parse.arch
import pmb.parse.depends import pmb.parse.depends
@ -140,7 +145,7 @@ def packages_split_to_add_del(packages):
return (to_add, to_del) return (to_add, to_del)
def packages_get_locally_built_apks(args: PmbArgs, packages, arch: str): def packages_get_locally_built_apks(args: PmbArgs, packages, arch: str) -> 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
@ -152,7 +157,7 @@ def packages_get_locally_built_apks(args: PmbArgs, packages, arch: str):
["/mnt/pmbootstrap/packages/x86_64/hello-world-1-r6.apk", ...] ["/mnt/pmbootstrap/packages/x86_64/hello-world-1-r6.apk", ...]
""" """
channel: str = pmb.config.pmaports.read_config(args)["channel"] channel: str = pmb.config.pmaports.read_config(args)["channel"]
ret = [] ret: List[Path] = []
for package in packages: for package in packages:
data_repo = pmb.parse.apkindex.package(args, package, arch, False) data_repo = pmb.parse.apkindex.package(args, package, arch, False)
@ -160,10 +165,11 @@ def packages_get_locally_built_apks(args: PmbArgs, packages, arch: str):
continue continue
apk_file = f"{package}-{data_repo['version']}.apk" apk_file = f"{package}-{data_repo['version']}.apk"
if not (pmb.config.work / "packages" / channel / arch / apk_file ).exists(): apk_path = pmb.config.work / "packages" / channel / arch / apk_file
if not apk_path.exists():
continue continue
ret.append(f"/mnt/pmbootstrap/packages/{arch}/{apk_file}") ret.append(apk_path)
return ret return ret
@ -182,7 +188,7 @@ def install_run_apk(args: PmbArgs, to_add, to_add_local, to_del, chroot: Chroot)
""" """
# Sanitize packages: don't allow '--allow-untrusted' and other options # Sanitize packages: don't allow '--allow-untrusted' and other options
# to be passed to apk! # to be passed to apk!
for package in to_add + to_add_local + to_del: for package in to_add + [os.fspath(p) for p in to_add_local] + to_del:
if package.startswith("-"): if package.startswith("-"):
raise ValueError(f"Invalid package name: {package}") raise ValueError(f"Invalid package name: {package}")
@ -197,10 +203,19 @@ def install_run_apk(args: PmbArgs, to_add, to_add_local, to_del, chroot: Chroot)
if to_del: if to_del:
commands += [["del"] + to_del] commands += [["del"] + to_del]
# For systemd we use a fork of apk-tools, to easily handle this
# we expect apk.static to be installed in the native chroot (which
# will be the systemd version if building for systemd) and run
# it from there.
apk_static = Chroot.native() / "sbin/apk.static"
arch = pmb.parse.arch.from_chroot_suffix(args, chroot)
apk_cache = pmb.config.work / f"cache_apk_{arch}"
for (i, command) in enumerate(commands): for (i, command) in enumerate(commands):
# --no-interactive is a parameter to `add`, so it must be appended or apk # --no-interactive is a parameter to `add`, so it must be appended or apk
# gets confused # gets confused
command += ["--no-interactive"] command += ["--no-interactive"]
command = ["--root", chroot.path, "--arch", arch, "--cache-dir", apk_cache] + command
# Ignore missing repos before initial build (bpo#137) # Ignore missing repos before initial build (bpo#137)
if os.getenv("PMB_APK_FORCE_MISSING_REPOSITORIES") == "1": if os.getenv("PMB_APK_FORCE_MISSING_REPOSITORIES") == "1":
@ -209,14 +224,12 @@ def install_run_apk(args: PmbArgs, to_add, to_add_local, to_del, chroot: Chroot)
if args.offline: if args.offline:
command = ["--no-network"] + command command = ["--no-network"] + command
if i == 0: if i == 0:
pmb.helpers.apk.apk_with_progress(args, ["apk"] + command, pmb.helpers.apk.apk_with_progress(args, [apk_static] + command)
run_in_chroot=True, chroot=chroot)
else: else:
# Virtual package related commands don't actually install or remove # Virtual package related commands don't actually install or remove
# packages, but only mark the right ones as explicitly installed. # packages, but only mark the right ones as explicitly installed.
# They finish up almost instantly, so don't display a progress bar. # They finish up almost instantly, so don't display a progress bar.
pmb.chroot.root(args, ["apk", "--no-progress"] + command, pmb.helpers.run.root(args, [apk_static, "--no-progress"] + command)
chroot=chroot)
def install(args: PmbArgs, packages, chroot: Chroot, build=True): def install(args: PmbArgs, packages, chroot: Chroot, build=True):

View file

@ -173,4 +173,4 @@ def run(args: PmbArgs, parameters):
if args.offline: if args.offline:
parameters = ["--no-network"] + parameters parameters = ["--no-network"] + parameters
pmb.helpers.apk.apk_with_progress( pmb.helpers.apk.apk_with_progress(
args, [pmb.config.work / "apk.static"] + parameters, run_in_chroot=False) args, [pmb.config.work / "apk.static"] + parameters)

View file

@ -160,10 +160,16 @@ def init(args: PmbArgs, chroot: Chroot=Chroot.native(), usr_merge=UsrMerge.AUTO,
# Install alpine-base # Install alpine-base
pmb.helpers.repo.update(args, arch) pmb.helpers.repo.update(args, arch)
pkgs = ["alpine-base"]
# install apk static in the native chroot so we can run it
# we have a forked apk for systemd and this is the easiest
# way to install/run it.
if chroot.type == ChrootType.NATIVE:
pkgs += ["apk-tools-static"]
pmb.chroot.apk_static.run(args, ["--root", chroot.path, pmb.chroot.apk_static.run(args, ["--root", chroot.path,
"--cache-dir", apk_cache, "--cache-dir", apk_cache,
"--initdb", "--arch", arch, "--initdb", "--arch", arch,
"add", "alpine-base"]) "add"] + pkgs)
# Merge /usr # Merge /usr
if usr_merge is UsrMerge.AUTO and pmb.config.is_systemd_selected(args): if usr_merge is UsrMerge.AUTO and pmb.config.is_systemd_selected(args):

View file

@ -11,27 +11,9 @@ import pmb.helpers.cli
import pmb.helpers.run import pmb.helpers.run
import pmb.helpers.run_core import pmb.helpers.run_core
import pmb.parse.version import pmb.parse.version
from pmb.core import Chroot
def _run(args: PmbArgs, command, run_in_chroot=False, chroot: Chroot=Chroot.native(), output="log"): def _prepare_fifo(args: PmbArgs):
"""Run a command.
:param command: command in list form
:param chroot: whether to run the command inside the chroot or on the host
:param suffix: chroot suffix. Only applies if the "chroot" parameter is
set to True.
See pmb.helpers.run_core.core() for a detailed description of all other
arguments and the return value.
"""
if run_in_chroot:
return pmb.chroot.root(args, command, output=output, chroot=chroot,
disable_timeout=True)
return pmb.helpers.run.root(args, command, output=output)
def _prepare_fifo(args: PmbArgs, run_in_chroot=False, chroot: Chroot=Chroot.native()):
"""Prepare the progress fifo for reading / writing. """Prepare the progress fifo for reading / writing.
:param chroot: whether to run the command inside the chroot or on the host :param chroot: whether to run the command inside the chroot or on the host
@ -42,15 +24,11 @@ def _prepare_fifo(args: PmbArgs, run_in_chroot=False, chroot: Chroot=Chroot.nati
path of the fifo as needed by cat to read from it (always path of the fifo as needed by cat to read from it (always
relative to the host) relative to the host)
""" """
if run_in_chroot: pmb.helpers.run.root(args, ["mkdir", "-p", pmb.config.work / "tmp"])
fifo = Path("/tmp/apk_progress_fifo") fifo = fifo_outside = pmb.config.work / "tmp/apk_progress_fifo"
fifo_outside = chroot / fifo
else:
_run(args, ["mkdir", "-p", pmb.config.work / "tmp"])
fifo = fifo_outside = pmb.config.work / "tmp/apk_progress_fifo"
if os.path.exists(fifo_outside): if os.path.exists(fifo_outside):
_run(args, ["rm", "-f", fifo_outside]) pmb.helpers.run.root(args, ["rm", "-f", fifo_outside])
_run(args, ["mkfifo", fifo_outside]) pmb.helpers.run.root(args, ["mkfifo", fifo_outside])
return (fifo, fifo_outside) return (fifo, fifo_outside)
@ -84,7 +62,7 @@ def _compute_progress(line):
return cur / tot if tot > 0 else 0 return cur / tot if tot > 0 else 0
def apk_with_progress(args: PmbArgs, command: Sequence[PathString], run_in_chroot=False, chroot: Chroot=Chroot.native()): def apk_with_progress(args: PmbArgs, command: Sequence[PathString]):
"""Run an apk subcommand while printing a progress bar to STDOUT. """Run an apk subcommand while printing a progress bar to STDOUT.
:param command: apk subcommand in list form :param command: apk subcommand in list form
@ -93,13 +71,13 @@ def apk_with_progress(args: PmbArgs, command: Sequence[PathString], run_in_chroo
set to True. set to True.
:raises RuntimeError: when the apk command fails :raises RuntimeError: when the apk command fails
""" """
fifo, fifo_outside = _prepare_fifo(args, run_in_chroot, chroot) fifo, fifo_outside = _prepare_fifo(args)
_command: List[str] = [os.fspath(c) for c in command] _command: List[str] = [os.fspath(c) for c in command]
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 _run(args, ['cat', fifo], run_in_chroot=run_in_chroot, chroot=chroot, with pmb.helpers.run.root(args, ['cat', fifo],
output="pipe") as p_cat: output="pipe") as p_cat:
with _run(args, command_with_progress, run_in_chroot=run_in_chroot, chroot=chroot, with pmb.helpers.run.root(args, command_with_progress,
output="background") as p_apk: output="background") as p_apk:
while p_apk.poll() is None: while p_apk.poll() is None:
line = p_cat.stdout.readline().decode('utf-8') line = p_cat.stdout.readline().decode('utf-8')

View file

@ -66,7 +66,8 @@ def urls(args: PmbArgs, user_repository=True, postmarketos_mirror=True, alpine=T
# Local user repository (for packages compiled with pmbootstrap) # Local user repository (for packages compiled with pmbootstrap)
if user_repository: if user_repository:
ret.append("/mnt/pmbootstrap/packages") channel = pmb.config.pmaports.read_config(args)["channel"]
ret.append(str(pmb.config.work / "packages" / channel))
# Upstream postmarketOS binary repository # Upstream postmarketOS binary repository
if postmarketos_mirror: if postmarketos_mirror: