Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • postmarketOS/pmbootstrap
  • fossdd/pmbootstrap
  • Adrian/pmbootstrap
  • JustSoup321/pmbootstrap
  • longnoserob/pmbootstrap
  • sixthkrum/pmbootstrap
  • ollieparanoid/pmbootstrap
  • magdesign/pmbootstrap
  • anjandev/pmbootstrap
  • HenriDellal/pmbootstrap
  • Minecrell/pmbootstrap
  • chipiguay/pmbootstrap
  • ijiki16/pmbootstrap
  • whynothugo/pmbootstrap
  • amessier/pmbootstrap
  • Eisenbahnfan/pmbootstrap
  • user0-07161/pmbootstrap
  • SzczurekYT/pmbootstrap
  • neuschaefer/pmbootstrap
  • knuxify/pmbootstrap
  • Frieder.Hannenheim/pmbootstrap
21 results
Show changes
Commits on Source (170)
#!/bin/sh -e
# Description: create documentation with sphinx
# Options: native
# Artifacts: public/
# https://postmarketos.org/pmb-ci
# Ensure sphinx_rtd_theme is installed
# Install sphinx + extensions when running in CI
if [ "$(id -u)" = 0 ]; then
set -x
apk -q add \
py3-myst-parser \
py3-sphinx_rtd_theme \
py3-sphinxcontrib-autoprogram
exec su "${TESTUSER:-build}" -c "sh -e $0"
......
......@@ -4,10 +4,11 @@
if [ "$(id -u)" = 0 ]; then
set -x
apk -q add py3-argcomplete py3-mypy
apk -q add py3-argcomplete py3-pip
exec su "${TESTUSER:-build}" -c "sh -e $0"
fi
set -x
mypy pmbootstrap.py
pip install --break-system-packages --no-warn-script-location mypy
python -m mypy --color-output --check-untyped-defs pmbootstrap.py
......@@ -10,7 +10,7 @@ fi
# shellcheck disable=SC2046
vermin \
-t=3.9- \
-t=3.10- \
--backport argparse \
--backport configparser \
--backport enum \
......
......@@ -76,7 +76,6 @@ mypy:
docs:
stage: lint
script:
- apk add py3-sphinx py3-sphinx_rtd_theme
- ".ci/docs.sh"
artifacts:
paths:
......
# SC3043: "In POSIX sh, 'local' is undefined" - busybox sh and pretty much all
# other shells can handle it though and Alpine uses it too in lots of scripts.
disable=SC3043
......@@ -43,13 +43,29 @@ Issues are being tracked
* [Linux kernel 3.17 or higher](https://postmarketos.org/oldkernel)
* Note: kernel versions between 5.8.8 and 6.0 might
[have issues with parted](https://gitlab.com/postmarketOS/pmbootstrap/-/issues/2309).
* Python 3.9+
* For python3 <= 3.10: tomli
* Python 3.10+
* For python3 < 3.11: tomli
* OpenSSL
* git
* ps
* tar
## Relation to pmaports
For pmbootstrap to be useful, it needs to maintain a local copy of the
[pmaports](https://gitlab.com/postmarketOS/pmaports) repository where
postmarketOS-specific packages are maintained. This is set up automatically, but
the local copy of pmaports does not automatically get updated. To update it, you
can run `$ pmbootstrap pull`.
The latest pmbootstrap version works with currently
[active postmarketOS releases](https://wiki.postmarketos.org/wiki/Releases).
Attempting to use pmboostrap with old postmarketOS versions (old pmaports
branches) may result in failures and is not supported. See
`pmbootstrap_min_version` in
[pmaports.cfg](https://wiki.postmarketos.org/wiki/Pmaports.cfg_reference) for
the oldest supported pmbootstrap version for a given pmaports revision. The
upper bound is not documented.
## Usage Examples
Please refer to the [postmarketOS wiki](https://wiki.postmarketos.org) for
in-depth coverage of topics such as
......@@ -275,7 +291,7 @@ the `ssh_key_glob` configuration parameter in the pmbootstrap config file to a
string containing a glob that is to match the file or files you wish to
include.
For example, a `~/.config/pmbootstrap.cfg` may contain:
For example, a `~/.config/pmbootstrap_v3.cfg` may contain:
[pmbootstrap]
# ...
......
......@@ -34,6 +34,7 @@ version = ".".join(release.split(".")[:3])
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"myst_parser",
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"sphinx.ext.doctest",
......@@ -42,6 +43,7 @@ extensions = [
templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
source_suffix = [".rst", ".md"]
# -- Options for HTML output -------------------------------------------------
html_theme = "sphinx_rtd_theme"
......
......@@ -19,6 +19,7 @@ For further information, please check out the `postmarketOS-wiki`_.
installation
usage
api/modules
mirrors
......
# Mirror Configuration
A typical postmarketOS installation has one Alpine Linux mirror configured as well as one
postmarketOS mirror. As Alpine's CDN mirror is used by default, it should be suitable for most
users. The postmarketOS mirror can be configured interactively with `pmbootstrap init`, under
"additional options".
Find the currently selected mirrors in the output of `pmbootstrap status`, as well as in
`/etc/apk/repositories` for initialized chroots and finished installations.
## Advanced
Some advanced use cases are supported by configuring the mirrors directly, either by running the
non-interactive `pmbootstrap config` command or by editing `pmbootstrap_v3.cfg`. Find the lists of
mirrors at [mirrors.alpinelinux.org](https://mirrors.alpinelinux.org) and
[mirrors.postmarketos.org](https://mirrors.postmarketos.org).
### Change the mirrors non-interactively
```
$ pmbootstrap config mirrors.alpine http://uk.alpinelinux.org/alpine/
$ pmbootstrap config mirrors.pmaports http://postmarketos.craftyguy.net/
```
Reset to default works as with all config options:
```
$ pmbootstrap config -r mirrors.alpine
$ pmbootstrap config -r mirrors.pmaports
```
### Disable the postmarketOS mirror
This is useful to test bootstrapping from pure Alpine:
```
$ pmbootstrap config mirrors.pmaports none
$ pmbootstrap config mirrors.systemd none
```
### Use `_custom` mirrors
For all repositories, it is possible to add `_custom` entries, for example
`pmaports_custom` in addition to `pmaports`. If these are set, then pmbootstrap
creates addition entries infront of the real mirrors in
`/etc/apk/repositories`. This is used by [BPO](https://build.postmarketos.org)
to build packages with a WIP repository enabled in addition to the final
repository, but could also be used if you have another custom repository that
you want to use in addition to the postmarketOS binary package repository.
```
$ pmbootstrap config mirrors.pmaports_custom http://custom-repository-here
```
......@@ -41,8 +41,8 @@ pmbootstrap requires the following:
Kernel version 5.8 - 6.0 might have issues with loop-devices
* Python 3.9+
* For python3 <= 3.10: tomli
* Python 3.10+
* For python3 < 3.11: tomli
* OpenSSL
* git
* ps
......
......@@ -85,7 +85,7 @@ set_alias_pmbootstrap() {
return 1
fi
if [ -e "${XDG_CONFIG_HOME:-$HOME/.config}"/pmbootstrap.cfg ]; then
if [ -e "${XDG_CONFIG_HOME:-$HOME/.config}"/pmbootstrap_v3.cfg ]; then
"$pmbootstrap" work_migrate
else
echo "NOTE: First run of pmbootstrap, running 'pmbootstrap init'"
......
......@@ -5,6 +5,7 @@ import sys
import os
import traceback
from typing import Any, Optional, TYPE_CHECKING
from pathlib import Path
from pmb.helpers.exceptions import BuildFailedError, NonBugError
......@@ -27,9 +28,16 @@ from .commands import run_command
__version__ = "3.0.0_alpha"
# Python version check
# === CHECKLIST FOR UPGRADING THE REQUIRED PYTHON VERSION ===
# * .ci/vermin.sh
# * README.md
# * docs/usage.rst
# * pmb/__init__.py (you are here)
# * pyproject.toml
# * when upgrading to python 3.11: pmb/helpers/toml.py and remove this line
version = sys.version_info
if version < (3, 9):
print("You need at least Python 3.9 to run pmbootstrap")
if version < (3, 10):
print("You need at least Python 3.10 to run pmbootstrap")
print("(You are running it with Python " + str(version.major) + "." + str(version.minor) + ")")
sys.exit()
......@@ -68,19 +76,30 @@ def main() -> int:
# Initialize or require config
if args.action == "init":
return config_init.frontend(args)
config_init.frontend(args)
return 0
elif not os.path.exists(args.config):
raise RuntimeError(
"Please specify a config file, or run" " 'pmbootstrap init' to generate one."
if args.config != config.defaults["config"]:
raise NonBugError(f"Couldn't find file passed with --config: {args.config}")
old_config = (
Path(os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config"))
/ "pmbootstrap.cfg"
)
elif not os.path.exists(context.config.work):
raise RuntimeError(
"Work path not found, please run 'pmbootstrap" " init' to create it."
if os.path.exists(old_config):
raise NonBugError(
f"Thanks for upgrading to pmbootstrap {__version__}!"
" The config file format has changed, please generate a new config with"
" 'pmbootstrap init'."
)
raise NonBugError(
"Run 'pmbootstrap init' first to generate a config file (or use --config)."
)
elif not os.path.exists(context.config.work):
raise NonBugError("Work path not found, please run 'pmbootstrap init' to create it.")
# Migrate work folder if necessary
if args.action not in ["shutdown", "zap", "log"]:
other.migrate_work_folder(args)
other.migrate_work_folder()
# Run the function with the action's name (in pmb/helpers/frontend.py)
if args.action:
......
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import os
from pmb.core.arch import Arch
from pmb.core.context import get_context
from pmb.core.pkgrepo import pkgrepo_default_path
from pmb.helpers import logging
......@@ -16,13 +17,17 @@ from pmb.types import PmbArgs
import pmb.helpers.cli
def get_cross_package_arches(pkgname):
def get_cross_package_arches(pkgname: str) -> str:
"""
Get the arches for which we want to build cross packages.
:param pkgname: package name, e.g. "gcc-aarch64", "gcc-x86_64"
:returns: string of architecture(s) (space separated)
:returns: string of architecture(s) (space separated). It doesn't
necessarily make sense to use Arch here given that this value gets
used to write APKBUILD files, where the ``arch`` field can have values
that aren't necessarily valid arches like "!armhf", "noarch", or
"x86 x86_64".
"""
if pkgname.endswith("-x86_64"):
......@@ -59,7 +64,7 @@ def properties(pkgname):
raise ValueError("No generator available for " + pkgname + "!")
def generate(pkgname: str, fork_alpine: bool):
def generate(pkgname: str, fork_alpine: bool, fork_alpine_retain_branch: bool = False) -> None:
if fork_alpine:
prefix, folder, options = (pkgname, "temp", {"confirm_overwrite": True})
else:
......@@ -78,7 +83,9 @@ def generate(pkgname: str, fork_alpine: bool):
if os.path.exists(aportgen):
pmb.helpers.run.user(["rm", "-r", aportgen])
if fork_alpine:
upstream = pmb.aportgen.core.get_upstream_aport(pkgname)
upstream = pmb.aportgen.core.get_upstream_aport(
pkgname, retain_branch=fork_alpine_retain_branch
)
pmb.helpers.run.user(["cp", "-r", upstream, aportgen])
pmb.aportgen.core.rewrite(
pkgname, replace_simple={"# Contributor:*": None, "# Maintainer:*": None}
......
......@@ -8,16 +8,21 @@ import pmb.chroot.apk_static
import pmb.helpers.run
import pmb.parse.apkindex
from pmb.core import Chroot
from pmb.core.arch import Arch
from pmb.core.context import get_context
def generate(pkgname: str):
arch = pkgname.split("-")[2]
def generate(pkgname: str) -> None:
arch = Arch.from_str(pkgname.split("-")[2])
context = get_context()
# Parse version from APKINDEX
package_data = pmb.parse.apkindex.package("busybox")
version = package_data["version"]
if package_data is None:
raise RuntimeError("Couldn't find APKINDEX for busybox!")
version = package_data.version
pkgver = version.split("-r")[0]
pkgrel = version.split("-r")[1]
......
......@@ -2,14 +2,15 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import fnmatch
from pmb.helpers import logging
from pathlib import Path
import re
import pmb.helpers.git
import pmb.helpers.run
import pmb.helpers.args
from pmb.core.arch import Arch
from pmb.core.context import get_context
def indent_size(line):
def indent_size(line: str) -> int:
"""
Number of spaces at the beginning of a string.
"""
......@@ -19,7 +20,7 @@ def indent_size(line):
return 0
def format_function(name, body, remove_indent=4):
def format_function(name: str, body: str, remove_indent: int = 4) -> str:
"""
Format the body of a shell function passed to rewrite() below, so it fits
the format of the original APKBUILD.
......@@ -50,15 +51,15 @@ def format_function(name, body, remove_indent=4):
def rewrite(
pkgname,
path_original="",
fields={},
replace_pkgname=None,
replace_functions={},
replace_simple={},
below_header="",
remove_indent=4,
):
pkgname: str,
path_original: Path | str | None = None,
fields: dict[str, str] = {},
replace_pkgname: str | None = None,
replace_functions: dict[str, str | None] = {},
replace_simple: dict = {}, # Can't type this dictionary properly without fixing type errors
below_header: str = "",
remove_indent: int = 4,
) -> None:
"""
Append a header to $WORK/aportgen/APKBUILD, delete maintainer/contributor
lines (so they won't be bugged with issues regarding our generated aports),
......@@ -160,7 +161,7 @@ def rewrite(
handle.truncate()
def get_upstream_aport(pkgname: str, arch=None):
def get_upstream_aport(pkgname: str, arch: Arch | None = None, retain_branch: bool = False):
"""
Perform a git checkout of Alpine's aports and get the path to the aport.
......@@ -173,9 +174,7 @@ def get_upstream_aport(pkgname: str, arch=None):
pmb.helpers.git.clone("aports_upstream")
aports_upstream_path = get_context().config.work / "cache_git/aports_upstream"
args = pmb.helpers.args.please_i_really_need_args()
if getattr(args, "fork_alpine_retain_branch", False):
if retain_branch:
logging.info("Not changing aports branch as --fork-alpine-retain-branch was " "used.")
else:
# Checkout branch
......@@ -211,14 +210,17 @@ def get_upstream_aport(pkgname: str, arch=None):
index_path = pmb.helpers.repo.alpine_apkindex_path(repo, arch)
package = pmb.parse.apkindex.package(pkgname, indexes=[index_path])
if package is None:
raise RuntimeError(f"Couldn't find {pkgname} in APKINDEX!")
# Compare version (return when equal)
compare = pmb.parse.version.compare(apkbuild_version, package["version"])
compare = pmb.parse.version.compare(apkbuild_version, package.version)
# APKBUILD > binary: this is fine
if compare == 1:
logging.info(
f"NOTE: {pkgname} {arch} binary package has a lower"
f" version {package['version']} than the APKBUILD"
f" version {package.version} than the APKBUILD"
f" {apkbuild_version}"
)
return aport_path
......@@ -230,7 +232,7 @@ def get_upstream_aport(pkgname: str, arch=None):
" local checkout of Alpine's aports ("
+ apkbuild_version
+ ") compared to Alpine's binary package ("
+ package["version"]
+ package.version
+ ")!"
)
logging.info("NOTE: You can update your local checkout with: 'pmbootstrap pull'")
......
......@@ -3,6 +3,7 @@
from pmb.core.context import get_context
from pmb.core.arch import Arch
from pmb.helpers import logging
from pathlib import Path
import os
import pmb.helpers.cli
import pmb.helpers.run
......@@ -61,10 +62,6 @@ def ask_for_chassis():
)
def ask_for_keyboard() -> bool:
return pmb.helpers.cli.confirm("Does the device have a hardware" " keyboard?")
def ask_for_external_storage() -> bool:
return pmb.helpers.cli.confirm(
"Does the device have a sdcard or" " other external storage medium?"
......@@ -113,7 +110,7 @@ def ask_for_bootimg():
while True:
response = pmb.helpers.cli.ask("Path", None, "", False)
path = os.path.expanduser(response)
path = Path(os.path.expanduser(response))
if not path:
return None
try:
......@@ -190,7 +187,6 @@ def generate_deviceinfo(
year: str,
arch: Arch,
chassis: str,
has_keyboard: bool,
has_external_storage: bool,
flash_method: str,
bootimg=None,
......@@ -213,7 +209,6 @@ def generate_deviceinfo(
# Device related
deviceinfo_chassis="{chassis}"
deviceinfo_keyboard="{"true" if has_keyboard else "false"}"
deviceinfo_external_storage="{external_storage}"
# Bootloader related
......@@ -338,7 +333,6 @@ def generate(pkgname: str):
name = ask_for_name(manufacturer)
year = ask_for_year()
chassis = ask_for_chassis()
has_keyboard = ask_for_keyboard()
has_external_storage = ask_for_external_storage()
flash_method = ask_for_flash_method()
bootimg = None
......@@ -352,7 +346,6 @@ def generate(pkgname: str):
year,
arch,
chassis,
has_keyboard,
has_external_storage,
flash_method,
bootimg,
......
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import pmb.aportgen.core
from pmb.core.arch import Arch
from pmb.core.context import get_context
from pmb.core.pkgrepo import pkgrepo_default_path
import pmb.helpers.git
......@@ -10,7 +11,7 @@ import pmb.helpers.run
def generate(pkgname: str):
# Copy original aport
prefix = pkgname.split("-")[0]
arch = pkgname.split("-")[1]
arch = Arch.from_str(pkgname.split("-")[1])
context = get_context()
if prefix == "gcc":
upstream = pmb.aportgen.core.get_upstream_aport("gcc", arch)
......@@ -51,7 +52,7 @@ def generate(pkgname: str):
below_header = (
"CTARGET_ARCH="
+ arch
+ str(arch)
+ """
CTARGET="$(arch_to_hostspec ${CTARGET_ARCH})"
LANG_D=false
......
......@@ -12,12 +12,14 @@ from pmb.core import Chroot
from pmb.core.context import get_context
def generate(pkgname):
arch = "x86"
def generate(pkgname: str) -> None:
arch = Arch.x86
if pkgname != "grub-efi-x86":
raise RuntimeError("only grub-efi-x86 is available")
package_data = pmb.parse.apkindex.package("grub")
version = package_data["version"]
if package_data is None:
raise RuntimeError("Couldn't find package grub!")
version = package_data.version
pkgver = version.split("-r")[0]
pkgrel = version.split("-r")[1]
......
......@@ -8,15 +8,18 @@ import pmb.chroot.apk_static
import pmb.helpers.run
import pmb.parse.apkindex
from pmb.core import Chroot
from pmb.core.arch import Arch
from pmb.core.context import get_context
def generate(pkgname):
arch = pkgname.split("-")[1]
def generate(pkgname: str) -> None:
arch = Arch.from_str(pkgname.split("-")[1])
# Parse musl version from APKINDEX
package_data = pmb.parse.apkindex.package("musl")
version = package_data["version"]
if package_data is None:
raise RuntimeError("Couldn't find package musl!")
version = package_data.version
pkgver = version.split("-r")[0]
pkgrel = version.split("-r")[1]
......
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import datetime
from typing import Any, Callable, Optional, TypedDict
from typing import Any, TypedDict
from collections.abc import Callable
from pmb.build.other import BuildStatus
from pmb.core.arch import Arch
from pmb.core.context import Context
......@@ -21,7 +22,7 @@ import pmb.helpers.mount
import pmb.helpers.package
import pmb.parse
import pmb.parse.apkindex
from pmb.helpers.exceptions import BuildFailedError
from pmb.helpers.exceptions import BuildFailedError, NonBugError
from .backend import run_abuild
from .backend import BootstrapStage
......@@ -50,7 +51,7 @@ def check_build_for_arch(pkgname: str, arch: Arch):
pmaport_version = pmaport["pkgver"] + "-r" + pmaport["pkgrel"]
logging.debug(
pkgname + ": found pmaport (" + pmaport_version + ") and"
" binary package (" + binary["version"] + ", from"
" binary package (" + binary.version + ", from"
" postmarketOS or Alpine), but pmaport can't be built"
f" for {arch} -> using binary package"
)
......@@ -94,7 +95,7 @@ def get_depends(context: Context, apkbuild):
return ret
def get_pkgver(original_pkgver, original_source=False, now=None):
def get_pkgver(original_pkgver: str, original_source=False):
"""Get the original pkgver when using the original source.
Otherwise, get the pkgver with an appended suffix of current date and time.
......@@ -112,7 +113,7 @@ def get_pkgver(original_pkgver, original_source=False, now=None):
# Append current date
no_suffix = original_pkgver.split("_", 1)[0]
now = now if now else datetime.datetime.now()
now = datetime.datetime.now()
new_suffix = "_p" + now.strftime("%Y%m%d%H%M%S")
return no_suffix + new_suffix
......@@ -149,13 +150,22 @@ def finish(apkbuild, channel, arch, output: Path, chroot: Chroot, strict=False):
logging.info(f"@YELLOW@=>@END@ @BLUE@{channel}/{apkbuild['pkgname']}@END@: Done!")
# If we just built a package which is used to build other packages, then
# update the buildroot to use the newly built version.
if apkbuild["pkgname"] in pmb.config.build_packages:
logging.info(
f"NOTE: Updating package {apkbuild['pkgname']} in buildroot since it's"
" used for building..."
)
pmb.chroot.apk.install([apkbuild["pkgname"]], chroot, build=False, quiet=True)
_package_cache: dict[str, list[str]] = {}
def is_cached_or_cache(arch: Arch, pkgname: str) -> bool:
"""Check if a package is in the built packages cache, if not
then mark it as built. We must mark as built before building
"""Check if a package is in the visited packages cache, if not
then mark it as visited. We must mark as visited before building
to break cyclical dependency loops."""
global _package_cache
......@@ -163,12 +173,12 @@ def is_cached_or_cache(arch: Arch, pkgname: str) -> bool:
if key not in _package_cache:
_package_cache[key] = []
ret = pkgname in _package_cache[key]
if not ret:
visited = pkgname in _package_cache[key]
if not visited:
_package_cache[key].append(pkgname)
else:
logging.debug(f"{key}/{pkgname}: marked for build")
return ret
return visited
def get_apkbuild(pkgname):
......@@ -194,6 +204,8 @@ class BuildQueueItem(TypedDict):
arch: Arch # Arch to build for
aports: str
apkbuild: dict[str, Any]
has_binary: bool # A binary package exists (even if outdated)
pkgver: str
output_path: Path
channel: str
depends: list[str]
......@@ -201,11 +213,114 @@ class BuildQueueItem(TypedDict):
chroot: Chroot
def has_cyclical_dependency(unmet_deps: dict[str, list[str]], item: BuildQueueItem, dep: str):
pkgnames = [item["name"]] + list(item["apkbuild"]["subpackages"].keys())
for pkgname in pkgnames:
if pkgname in unmet_deps.get(dep, []):
return True
return False
def prioritise_build_queue(disarray: list[BuildQueueItem]) -> list[BuildQueueItem]:
"""
Figure out The Correct Order to build packages in, or bail.
"""
queue: list[BuildQueueItem] = []
# (name, unmet_dep) of all unmet dependencies
unmet_deps: dict[str, list[str]] = {}
# build our base build packages first. This relies on
# the build_packages array being in the correct order!
for pkgname in pmb.config.build_packages:
for item in disarray:
if item["name"] == pkgname:
queue.append(item)
disarray.remove(item)
break
all_pkgnames = []
for item in disarray:
all_pkgnames.append(item["name"])
all_pkgnames += item["apkbuild"]["subpackages"].keys()
def queue_item(item: BuildQueueItem):
queue.append(item)
disarray.remove(item)
all_pkgnames.remove(item["name"])
for subpkg in item["apkbuild"]["subpackages"].keys():
all_pkgnames.remove(subpkg)
unmet_deps.pop(item["name"], None)
stuck = False
while disarray and not stuck:
stuck = True
for item in disarray:
if not item["depends"]:
queue_item(item)
stuck = False
break
# If a dependency hasn't been queued yet, skip until it has been
missing_deps = False
for dep in item["depends"]:
# This might be a subpkgname, replace with the main pkgname
# (e.g."linux-pam-dev" -> "linux-pam")
dep_data = pmb.helpers.package.get(
dep, item["arch"], must_exist=False, try_other_arches=False
)
if not dep_data:
raise NonBugError(f"{item['name']}: dependency not found: {dep}")
dep = dep_data.pkgname
if dep in all_pkgnames:
unmet_deps.setdefault(item["name"], []).append(dep)
missing_deps = True
if has_cyclical_dependency(unmet_deps, item, dep):
# If a binary package exists for item, we can queue it
# safely and dep will be queued on a future iteration
if item["has_binary"]:
logging.warning(
f"WARNING: cyclical build dependency: building {item['name']} with binary package of {dep}"
)
queue_item(item)
stuck = False
break
else:
logging.warning(
f"WARNING: cyclical build dependency: can't build {item['name']}, no binary package for {dep}"
)
else:
logging.debug(
f"{item['name']}: missing dependency {dep}, trying to queue other packages first"
)
if missing_deps:
continue
# We're probably good to go??
queue_item(item)
stuck = False
break
if stuck:
logging.error("Remaining packages:")
for unmet_dep in unmet_deps:
logging.error(f"* {unmet_dep}")
raise NonBugError("Can't resolve build order of remaining packages!")
return queue
def process_package(
context: Context,
queue_build: Callable,
pkgname: str,
arch: Optional[Arch],
arch: Arch | None,
fallback_arch: Arch,
force: bool,
) -> list[str]:
......@@ -225,12 +340,13 @@ def process_package(
arch = pmb.build.autodetect.arch(base_apkbuild)
if is_cached_or_cache(arch, pkgname) and not force:
logging.verbose(f"Skipping build for {arch}/{pkgname}, already built")
logging.verbose(f"S{arch}/{pkgname}: already queued")
return []
logging.debug(f"{arch}/{pkgname}: Generating dependency tree")
# Add the package to the build queue
base_depends = get_depends(context, base_apkbuild)
depends = base_depends.copy()
base_build_status = BuildStatus.NEW if force else BuildStatus.UNNECESSARY
......@@ -252,6 +368,12 @@ def process_package(
if base_build_status.necessary():
queue_build(base_aports, base_apkbuild, base_depends)
# Also traverse subpackage depends, they shouldn't be included in base_depends since they
# aren't needed for building (and can conflict with depends for other subpackages)
depends += sum(
map(lambda sp: sp["depends"] if sp else [], base_apkbuild["subpackages"].values()), []
)
parent = pkgname
while len(depends):
# FIXME: pop(0) is really quite slow!
......@@ -276,7 +398,7 @@ def process_package(
)
bstatus = pmb.build.get_status(arch, apkbuild)
if bstatus.necessary():
if bstatus.necessary() and dep not in pmb.config.build_packages:
if context.no_depends:
raise RuntimeError(
f"Binary package for dependency '{dep}'"
......@@ -286,22 +408,18 @@ def process_package(
)
deps = get_depends(context, apkbuild)
# Preserve the old behaviour where we don't build second order dependencies by default
# unless they are NEW packages, in which case we
if base_build_status.necessary() or bstatus == BuildStatus.NEW:
logging.debug(
f"BUILDQUEUE: queue {dep} (dependency of {parent}) for build, reason: {bstatus}"
)
queue_build(aports, apkbuild, deps, cross)
else:
logging.info(
f"@YELLOW@SKIP:@END@ NOT building {arch}/{dep}: it is a"
f" dependency of {pkgname} which isn't marked for build."
f" Call with --force or consider building {dep} manually"
)
logging.debug(
f"BUILDQUEUE: queue {dep} (dependency of {parent}) for build, reason: {bstatus}"
)
queue_build(aports, apkbuild, deps, cross)
logging.verbose(f"{arch}/{dep}: Inserting {len(deps)} dependencies")
depends = deps + depends
subpkg_deps: list[str] = sum(
map(lambda sp: sp["depends"] if sp else [], apkbuild["subpackages"].values()), []
)
logging.verbose(
f"{arch}/{dep}: Inserting {len(deps)} dependencies and {len(subpkg_deps)} from subpackages"
)
depends = subpkg_deps + deps + depends
parent = dep
return depends
......@@ -310,12 +428,12 @@ def process_package(
def packages(
context: Context,
pkgnames: list[str],
arch: Optional[Arch] = None,
arch: Arch | None = None,
force=False,
strict=False,
src=None,
bootstrap_stage=BootstrapStage.NONE,
log_callback: Optional[Callable] = None,
log_callback: Callable | None = None,
) -> list[str]:
"""
Build a package and its dependencies with Alpine Linux' abuild.
......@@ -337,6 +455,11 @@ def packages(
build_queue: list[BuildQueueItem] = []
built_packages: set[str] = set()
# We want to build packages in the order they're given here. Due to how we
# walk the package dependencies, reverse the list so that when we later
# reverse the build queue we'll be back in the right order.
pkgnames.reverse()
# Add a package to the build queue, fetch it's dependency, and
# add record build helpers to installed (e.g. sccache)
def queue_build(
......@@ -353,16 +476,34 @@ def packages(
pkg_arch = pmb.build.autodetect.arch(apkbuild) if arch is None else arch
chroot = pmb.build.autodetect.chroot(apkbuild, pkg_arch)
cross = cross or pmb.build.autodetect.crosscompile(apkbuild, pkg_arch)
pkgver = get_pkgver(apkbuild["pkgver"], src is None)
channel = pmb.config.pmaports.read_config(aports)["channel"]
index_data = pmb.parse.apkindex.package(name, arch, False)
# Make sure we aren't building a package that will never be used! This can happen if
# building with --src with an outdated pmaports checkout.
if (
index_data
and pmb.parse.version.compare(index_data.version, f"{pkgver}-r{apkbuild['pkgrel']}")
== 1
):
raise NonBugError(
f"A binary package for {name} has a newer version ({index_data.version})"
f" than the source ({pkgver}-{apkbuild['pkgrel']}). Please ensure your pmaports branch is up"
" to date and that you don't have a newer version of the package in your local"
f" binary repo ({context.config.work / 'packages' / channel / pkg_arch})."
)
build_queue.append(
{
"name": name,
"arch": pkg_arch,
"aports": aports.name, # the pmaports source repo (e.g. "systemd")
"apkbuild": apkbuild,
"has_binary": bool(index_data),
"pkgver": pkgver,
"output_path": output_path(
pkg_arch, apkbuild["pkgname"], apkbuild["pkgver"], apkbuild["pkgrel"]
pkg_arch, apkbuild["pkgname"], pkgver, apkbuild["pkgrel"]
),
"channel": pmb.config.pmaports.read_config(aports)["channel"],
"channel": channel,
"depends": depends,
"chroot": chroot,
"cross": cross,
......@@ -379,7 +520,7 @@ def packages(
if src and len(pkgnames) > 1:
raise RuntimeError("Can't build multiple packages with --src")
logging.verbose(f"Attempting to build: {', '.join(pkgnames)}")
logging.debug(f"Attempting to build: {', '.join(pkgnames)}")
# We sorta-kind maybe supported building packages for multiple architectures in
# a single called to packages(). We need to do a check to make sure that the user
......@@ -401,21 +542,49 @@ def packages(
context, queue_build, pkgname, arch, fallback_arch, force
)
# If any of our common build packages need to be built and are missing, then add them
# to the queue so they're built first. This is necessary so that our abuild fork is
# always built first (for example). For now assume that if building in strict mode we
# should skip this step, but we might want to revisit this later.
if not src:
for pkgname in pmb.config.build_packages:
if pkgname not in pkgnames:
aport, apkbuild = get_apkbuild(pkgname)
if not aport:
continue
bstatus = pmb.build.get_status(arch, apkbuild)
if bstatus.necessary():
if strict:
raise RuntimeError(
f"Strict mode enabled and build package {pkgname} needs building."
" Please build it manually first or build without --strict to build"
" it automatically."
)
queue_build(aport, apkbuild, get_depends(context, apkbuild))
if not len(build_queue):
return []
# We built the queue by pushing each package before it's dependencies. Now we
# need to go backwards through the queue and build the dependencies first.
build_queue.reverse()
build_queue = prioritise_build_queue(build_queue)
qlen = len(build_queue)
logging.info(f"Building @BLUE@{qlen}@END@ package{'s' if qlen > 1 else ''}")
for item in build_queue:
logging.info(f" @BLUE@*@END@ {item['channel']}/{item['name']}")
if len(build_queue) > 1 and src:
raise RuntimeError(
"Additional packages need building, please build them first and then"
" build the package with --src again."
)
cross = None
prev_cross = None
total_pkgs = len(build_queue)
count = 0
for pkg in build_queue:
count += 1
chroot = pkg["chroot"]
pkg_arch = pkg["arch"]
......@@ -423,7 +592,7 @@ def packages(
output = pkg["output_path"]
if not log_callback:
logging.info(
f"@YELLOW@=>@END@ @BLUE@{channel}/{pkg['name']}@END@: Installing dependencies"
f"@YELLOW@=> ({count}/{total_pkgs})@END@ @BLUE@{channel}/{pkg['name']}@END@: Installing dependencies"
)
else:
log_callback(pkg)
......@@ -438,11 +607,11 @@ def packages(
if src:
pkg_depends.append("rsync")
# We only need to init cross compiler stuff once
if not cross:
cross = pmb.build.autodetect.crosscompile(pkg["apkbuild"], pkg_arch)
if cross:
pmb.build.init_compiler(context, pkg_depends, cross, pkg_arch)
# (re)-initialize the cross compiler stuff when cross method changes
prev_cross = cross
cross = pmb.build.autodetect.crosscompile(pkg["apkbuild"], pkg_arch)
if cross != prev_cross:
pmb.build.init_compiler(context, pkg_depends, cross, pkg_arch)
if cross == "crossdirect":
pmb.chroot.mount_native_into_foreign(chroot)
......@@ -455,6 +624,7 @@ def packages(
run_abuild(
context,
pkg["apkbuild"],
pkg["pkgver"],
channel,
pkg_arch,
strict,
......