Generalise pmb.helpers.other.cache with a more python decorator.
The Cache decorator takes a list of function arguments to use as cache
keys, keyword args can be used to restrict caching so that it is skipped
entirely unless the attribute has a specific value.
For example, pmb.helpers.pmaports.get() has the decorator:
@Cache("pkgname", subpackages=True)
This means the return value will be cached only when subpackages is
True, otherwise it will always miss.
Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
Move pmb/parse/arch.py over to core and refactor it as an Arch type,
similar to how Chroot was done. Fix all the uses (that I can find) of
arch in the codebase that need adjusting.
The new Arch type is an Enum, making it clear what architectures can be
represented and making it much easier to reason about. Since we support
~5 (kinda) different representations of an Architecture (Alpine, Kernel,
target triple, platform, and QEMU), we now formalise that the Alpine
format is what we represent internally, with methods to convert to any
of the others as-needed.
Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
find() gets the pmaports directory of a package, get() parses the
APKBUILD inside that directory.
Instead of having the extra code path in get() for not searching
subpackages, which duplicated part of find(), add a subpackages
parameter to get().
Besides the deduplication done now, this is in preparation for code that
will extend get() to handle the extra-repos/systemd directory - without
this patch, the changes would need to be duplicated in both functions.
The repo_bootstrap command is totally standalone and has it's own state,
making it a good candidate for the pmb.commands submodule. Port it over
and move the require_bootstrap() helper function over to
pmb/helpers/pmaports.py
We also fix the call to pmb.build.package() which broke during rework.
Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
Cease merging pmbootstrap.cfg into args, implement a Context type to let
us pull globals out of thin air (as an intermediate workaround) and rip
args out of a lot of the codebase.
This is just a first pass, after this we can split all the state that
leaked over into Context into types with narrower scopes (like a
BuildContext(), etc).
Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
With the new chroot type, we can now write fancy paths in the pythonic
way. Convert most of the codebase over, as well as adding various other
type hints.
Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
We use a custom verbose log level in pmbootstrap, unfortunately it isn't
possible to correctly type this due to some limitations in the logging
library [1], [2].
Given that our usecase is fairly simple, we can just wrap the module
with our own so we only have to tell mypy to ignore the error once
instead of at every callsite.
[1]: https://github.com/cryptax/droidlysis/issues/15
[2]: https://github.com/python/typing/discussions/980
Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
Introduce a new module: pmb.core to contain explicitly typed pmbootstrap
API. The first component being Suffix and SuffixType. This explicitly
defines what suffixes are possible, future changes should aim to further
constrain this API (e.g. by validating against available device
codenames or architectures for buildroot suffixes).
Additionally, migrate the entire codebase over to using pathlib.Path.
This is a relatively new part of the Python standard library that uses a
more object oriented model for path handling. It also uses strong type
hinting and has other features that make it much cleaner and easier to
work with than pure f-strings. The Chroot class overloads the "/"
operator the same way the Path object does, allowing one to write paths
relative to a given chroot as:
builddir = chroot / "home/pmos/build"
The Chroot class also has a string representation ("native", or
"rootfs_valve-jupiter"), and a .path property for directly accessing the
absolute path (as a Path object).
The general idea here is to encapsulate common patterns into type hinted
code, and gradually reduce the amount of assumptions made around the
codebase so that future changes are easier to implement.
As the chroot suffixes are now part of the Chroot class, we also
implement validation for them, this encodes the rules on suffix naming
and will cause a runtime exception if a suffix doesn't follow the rules.
If we found an APKBUILD already, with the exact name of the package we
are looking for, then don't also guess what other package could contain
the package as subpackage.
The guess was thrown away with the previous logic, but it caused
misleading verbose logs like:
lomiri-history-service: guessed to be a subpackage of lomiri
Also this is slightly faster. While at it, tweak the related comments
to make this easier to understand.
Link: https://lists.sr.ht/~postmarketos/pmbootstrap-devel/%3C20231121222457.2871-1-ollieparanoid@postmarketos.org%3E
Fix that APKBUILDs mentioning other packages with an operator could not
be found. As we are building the initial branch of v23.12, this
currently happens with postmarketos-mkinitfs: it depends on
devicepkg-utils>=0.2.0 and currently pmbootstrap will only remove the
>=0.2.0 when looking for the pkgname in the APKINDEX of binary packages
(which is why it works on master). But it does not yet do that when
looking for the pkgname in pmaports.
Move the code for stripping the operator to a common place and use it
for getting packages from pmaports too.
Change the order of operators while at it, try to find <= before =, as
otherwise it would cut off example<=1.2.3 as "example<" instead of
"example".
Reviewed-by: Caleb Connolly <kc@postmarketos.org> (via chat)
Replace "args.cache" with a global variable in order to
avoid passing "args" to all functions. This is a step to get rid of this
args-passed-to-all-functions pattern in pmbootstrap.
The provider selection for "pmbootstrap init" added in this commit
is a flexible way to offer UI/device-specific configuration options
in "pmbootstrap init", without hardcoding them in pmbootstrap.
Instead, the options are defined entirely in pmaports using APK's
virtual package provider mechanism. The code in pmbootstrap searches
for available providers and displays them together with their pkgdesc.
There are many possible use cases for this but I have tested two so far:
1. Selecting root provider (sudo vs doas). This can be defined entirely
in postmarketos-base, without having to handle this specifically in
pmbootstrap.
$ pmbootstrap init
[...]
Available providers for postmarketos-root (2):
* sudo: Use sudo to run root commands (**default**)
* doas: Use doas (minimal replacement for sudo) to run root commands
(Note: Does not support all functionality of sudo)
Provider [default]: doas
2. Device-specific options. My main motivation for working on this
feature is a new configuration option for the MSM8916-based devices.
It allows more control about which firmware to enable:
$ pmbootstrap init
[...]
Available providers for soc-qcom-msm8916-rproc (3):
* all: Enable all remote processors (audio goes through modem) (default)
* no-modem: Disable only modem (audio bypasses modem, ~80 MiB more RAM)
* none: Disable all remote processors (no WiFi/BT/modem, ~90 MiB more RAM)
Provider [default]: no-modem
The configuration prompts show up dynamically by defining
_pmb_select="<virtual packages>" in postmarketos-base, a UI PKGBUILD
or the device APKBUILD. Selecting "default" (just pressing enter)
means that no provider is selected. This allows APK to choose it
automatically based on the "provider_priority". It also provides
compatibility with existing installation; APK will just choose the
default provider when upgrading. The selection can still be changed
after installation by installing another provider using "apk".
Note that at the end this is just a more convenient interface for the
already existing "extra packages" prompt. When using pmbootstrap in
automated scripts the providers (e.g. "postmarketos-root-doas") can be
simply selected through the existing "extra_packages" option.
At the moment, "provides" are only checked in the root package and not
in subpackages of APKBUILDs. Fix this by looking through the subpackages
as well.
At the moment we have to parse all APKBUILDs to find subpackages,
even if they are guessed easily shortly after. To speed this up,
let's guess first but verify the guess by only parsing that particular
APKBUILD. If the subpackage/provides is in there we seem to have found it.
Translate the pmaports channels "stable" to "v20.05" and "stable-next"
to "v21.03", so these have the same channel name as the pmaports.git
branch name.
The original plan was to switch the "stable" channel from the "v20.05"
branch to the "v21.03" branch when the release is done. However, now
that we are close to that, I'm realizing that this would not be useful.
It would lead to conflicts in the dir with locally built packages
(default: ~/.local/var/pmbootstrap/packages/$CHANNEL). And it would make
it awkward to go back to a previous branch (we may name it old-stable
for the time being, but what after that, old-old-stable?).
The ui-extras questions will attempt to find a postmarketos-ui-<ui>
package in pmaports. If the package does not exist as "root" APKBUILD
it currently attempts to parse all APKBUILDs in case it is somewhere
defined as a subpackage. This is really slow (up to 2-3 seconds),
which feels weird during "pmbootstrap init".
For the UI packages we specifically look for the root UI package,
not the subpackage, so let's skip searching for subpackages in this
case. This makes selecting the "none" UI nice and fast again.
In the future, device ports will be located in a subdirectory
below device/... (e.g. device/testing/device-...).
Replace all occurrences of device/* with a glob that checks the
subdirectories instead.
Note: To ensure that this always works properly we should also add some
checks that all devices are indeed located under one of the supported
subdirectories (i.e. testing/community/main).
Change the glob for pmaports to <aports>/**/APKBUILD.
This allows using subdirectories for organization outside of device/
as well.
Fix finding subpackages in pmaports, if no binary repo exists (-mp "" makes
pmbootstrap not use a binary packages mirror for pmOS packages). Discovered
with failing "test_depends_SLOW_40s" in bpo.
$ pmbootstrap -v -mp "" repo_missing --built --arch x86_64
[18:40:51] Calculate packages that need to be built (all packages, x86_64)
[18:40:56] ERROR: Package 'musl-dev-aarch64': Could not find aport, and could not find this package in any APKINDEX!
While at it, also remove unnecessary "#!/usr/bin/env python3" in files
that only get imported, and adjust other empty/comment lines in the
beginnings of the files for consistency.
This makes files easier to read, and makes the pmbootstrap codebase more
consistent with the build.postmarketos.org codebase.
At the moment we have a simple subpkgdesc() function that can only
parse "pkgdesc" from subpackages, without support for any variables.
But we have a quite nice variable parser now that can be extended
to work for subpackages.
Simply put this works by:
- Finding the lines that belong to the subpackage function
- Stripping indentation (tab)
- Parsing relevant attributes similar to the apkbuild() function
The "subpackages" in the parsed APKBUILD are replaced by a dict
of subpkgname: {"pkgdesc": "...", "depends": "..."} which are
parsed from the subpackage function (if found).
This makes it possible to get the "depends" of a subpackage.
The APKBUILD reference for "provides" [1] is not worded clearly; but
after reading it over and over again, my understanding is:
* package with provides='foo=1.2' will be automatically installed if
user requests installing "foo"
* package with provides='foo' (without version) will NOT get
automatically installed if user requests installing "foo"
For pmbootstrap, this means, that we must not attempt to build a package
where the pkgname mentioned in provides matches what we are currently
resolving, unless there is an equals sign in the provides entry.
Fixes: #1862, pmaports#404
[1] https://wiki.alpinelinux.org/wiki/APKBUILD_Reference#provides says:
"List of package names (and optionally version info) this package
provides.
If package with a version is provided (provides='foo=1.2') apk will
consider it as an alternate name and it will automatically consider the
package for installation by the alternate name, and conflict with other
packages having the same name, or provides.
If version is not provided (provides='foo'), apk will consider it as
virtual package name. Several package with same non-versioned provides
can be installed simultaneously. However, none of them will be installed
by default when requested by the virtual name - instead, error message
is given and user is asked to choose which package providing the virtual
name should be installed."
Packages ending in -dev: just assume that the originating aport has the
same pkgname, except for the -dev at the end. Otherwise we may end up
with the wrong package.
For example, if something depends on plasma-framework-dev, and
plasma-framework is in Alpine, but plasma is in pmaports, then the
regular guess_main() algorithm below would pick plasma instead of
plasma-framework.
Fixes: build.postmarketos.org#52
Split the part that actually checks the arch against the arches list
into pmb.helpers.pmaports.check_arches(arches, arch), and call it
from pmb.helpers.package.check_arch(args, pkgname, arch, binary).
This will be used in a follow up commit, where we have already resolved
the package data and only need to check the architecture.
Do not fail anymore when attempting to start a new binary repository
build without any existing binary packages:
pmbootstrap -mp="" repo_missing
Find subpackages defined with subpackage functions:
subpackages="dev:mydevfunc"
Find provides defined with specific versions:
provides="mkbootimg=1.0.0"
Add a new action that lists all aports, for which no binary packages
exist. Only list packages that can be built for the relevant arch
(specified with --arch). This works recursively: when a package can be
built for a certain arch, but one of its dependencies
(or their depends) can not be built for that arch, then don't list it.
This action will be used for the new sr.ht based build infrastructure,
to figure out which packages need to be built ahead of time (so we can
trigger each of them as single build job). Determining the order of the
packages to be built is not determined with pmbootstrap, the serverside
code of build.postmarketos.org takes care of that.
For testing purposes, a single package can also be specified and the
action will list if it can be built for that arch with its
dependencies, and what needs to be built exactly.
Add pmb/helpers/package.py to hold functions that work on both pmaports
and (binary package) repos - in contrary to the existing
pmb/helpers/pmaports.py (see previous commit) and pmb/helpers/repo.py,
which only work with one of those.
Refactoring:
* pmb/helpers/pmaports.py: add a get_list() function, which lists all
aports and use it instead of writing the same glob loop over and over
* add pmb.helpers.pmaports.get(), which finds an APKBUILD and parses it
in one step.
* rename pmb.build._package.check_arch to ...check_arch_abort to
distinguish it from the other check_arch function
Move find_aport() and find_aport_guess_main() from pmb/build/other.py
to the new file pmb/helpers/pmaports.py.
Finding aports is not only needed when building packages, hence it
makes sense to move it out of pmb.build. The pmb/helpers/pmaports.py
file will have more pmaports related functions in a follow up commit.