~martijnbraam/almobootstrap

c586871565d8d45c8a9472041bf48528485f8537 — Martijn Braam 5 months ago 01564e6 master
Rename internals for Almo
177 files changed, 3314 insertions(+), 3313 deletions(-)

M .b4-config
M .build.yml
M .ci/note.sh
M .ci/pytest.sh
M .ci/ruff.sh
M .ci/shellcheck.sh
M .ci/vermin.sh
M .gitignore
M CONTRIBUTING.md
M README.md
M amb/__init__.py
M amb/aportgen/__init__.py
M amb/aportgen/binutils.py
M amb/aportgen/busybox_static.py
M amb/aportgen/core.py
M amb/aportgen/device.py
M amb/aportgen/gcc.py
M amb/aportgen/grub_efi.py
M amb/aportgen/linux.py
M amb/aportgen/musl.py
M amb/build/__init__.py
M amb/build/_package.py
M amb/build/autodetect.py
M amb/build/checksum.py
M amb/build/envkernel.py
M amb/build/init.py
M amb/build/kconfig.py
M amb/build/newapkbuild.py
M amb/build/other.py
M amb/chroot/__init__.py
M amb/chroot/apk.py
M amb/chroot/apk_static.py
M amb/chroot/binfmt.py
M amb/chroot/distccd.py
M amb/chroot/init.py
M amb/chroot/initfs.py
M amb/chroot/initfs_hooks.py
M amb/chroot/mount.py
M amb/chroot/other.py
M amb/chroot/root.py
M amb/chroot/shutdown.py
M amb/chroot/user.py
M amb/chroot/zap.py
M amb/ci/__init__.py
M amb/config/__init__.py
R amb/config/{pmaports.py => almoports.py}
M amb/config/init.py
M amb/config/load.py
M amb/config/merge_with_args.py
M amb/config/sudo.py
M amb/config/workdir.py
M amb/data/keys/README
M amb/export/__init__.py
M amb/export/frontend.py
M amb/export/odin.py
M amb/export/symlinks.py
M amb/flasher/__init__.py
M amb/flasher/frontend.py
M amb/flasher/init.py
M amb/flasher/run.py
M amb/flasher/variables.py
R amb/helpers/{pmaports.py => almoports.py}
M amb/helpers/apk.py
M amb/helpers/aportupgrade.py
M amb/helpers/args.py
M amb/helpers/cli.py
M amb/helpers/devices.py
M amb/helpers/file.py
M amb/helpers/frontend.py
M amb/helpers/git.py
M amb/helpers/http.py
M amb/helpers/lint.py
M amb/helpers/logging.py
M amb/helpers/mount.py
M amb/helpers/other.py
M amb/helpers/package.py
M amb/helpers/pkgrel_bump.py
M amb/helpers/repo.py
M amb/helpers/repo_missing.py
M amb/helpers/run.py
M amb/helpers/run_core.py
M amb/helpers/status.py
M amb/helpers/ui.py
M amb/install/__init__.py
M amb/install/_install.py
M amb/install/blockdevice.py
M amb/install/format.py
M amb/install/losetup.py
M amb/install/partition.py
M amb/install/recovery.py
M amb/install/ui.py
M amb/netboot/__init__.py
M amb/parse/__init__.py
M amb/parse/_apkbuild.py
M amb/parse/apkindex.py
M amb/parse/arch.py
M amb/parse/arguments.py
M amb/parse/binfmt_info.py
M amb/parse/bootimg.py
M amb/parse/depends.py
M amb/parse/deviceinfo.py
M amb/parse/kconfig.py
M amb/parse/version.py
M amb/qemu/__init__.py
M amb/qemu/run.py
M amb/sideload/__init__.py
M helpers/envkernel.sh
M helpers/envsetup.sh
M test/pmb_test/const.py
M test/pmb_test/git.py
M test/test_apk.py
M test/test_apk_static.py
M test/test_aportgen.py
M test/test_aportgen_device_wizard.py
M test/test_arguments.py
M test/test_bootimg.py
M test/test_build_is_necessary.py
M test/test_build_package.py
M test/test_chroot_interactive_shell.py
M test/test_config_init.py
M test/test_config_pmaports.py
M test/test_config_user.py
M test/test_config_workdir.py
M test/test_cross_compile_distcc.py
M test/test_crossdirect.py
M test/test_envkernel.py
M test/test_file.py
M test/test_folder_size.py
M test/test_frontend.py
M test/test_helpers_git.py
M test/test_helpers_lint.py
M test/test_helpers_package.py
M test/test_helpers_pmaports.py
M test/test_helpers_repo.py
M test/test_helpers_repo_missing.py
M test/test_helpers_status.py
M test/test_helpers_ui.py
M test/test_install.py
M test/test_mount.py
M test/test_newapkbuild.py
M test/test_parse_apkbuild.py
M test/test_parse_apkindex.py
M test/test_parse_depends.py
M test/test_parse_deviceinfo.py
M test/test_parse_kconfig.py
M test/test_pkgrel_bump.py
M test/test_qemu_running_processes.py
M test/test_questions.py
M test/test_run_core.py
M test/test_shell_escape.py
M test/test_version.py
M test/test_version_validate.py
M test/test_zzz_keys.py
M test/testdata/apkbuild/APKBUILD.lint
M test/testdata/apkbuild/APKBUILD.missing-pkgdesc-in-subpackage
M test/testdata/apkindex/virtual_package
M test/testdata/aportgen/pmaports/cross/binutils-armhf/APKBUILD
M test/testdata/aportgen/pmaports/cross/gcc-armhf/APKBUILD
M test/testdata/bootimg/mtk_mkimage-boot-recovery.img
M test/testdata/build_local_src/APKBUILD
M test/testdata/channels.cfg
M test/testdata/deviceinfo/aports/device/testing/device-multiple-kernels/APKBUILD
M test/testdata/helpers_ui/pmaports/main/postmarketos-ui-plasma-mobile/APKBUILD
M test/testdata/init_questions_device/aports/device/testing/device-lg-mako/APKBUILD
M test/testdata/init_questions_device/aports/device/testing/device-lg-mako/deviceinfo
M test/testdata/init_questions_device/aports/device/testing/device-nonfree-firmware-and-userland/APKBUILD
M test/testdata/init_questions_device/aports/device/testing/device-nonfree-firmware/APKBUILD
M test/testdata/init_questions_device/aports/device/testing/device-nonfree-userland/APKBUILD
M test/testdata/init_questions_device/aports/device/testing/device-sony-amami/APKBUILD
M test/testdata/init_questions_device/aports/device/testing/device-wileyfox-crackling/APKBUILD
M test/testdata/init_questions_device/aports/main/postmarketos-ui-weston/APKBUILD
M test/testdata/pkgrel_bump/aports/testapp/APKBUILD
M test/testdata/pkgrel_bump/aports/testlib/APKBUILD
M test/testdata/pkgrel_bump/aports/testsubpkg/APKBUILD
M test/testdata/pmaports.cfg
M test/testdata/pmb_groups/main/postmarketos-ui-test/APKBUILD
M test/testdata/pmb_recommends/main/postmarketos-ui-test/APKBUILD
M .b4-config => .b4-config +4 -4
@@ 1,9 1,9 @@
# Allow this repository to be used with the 'b4' tool. See
# https://postmarketos.org/patch-review for details.
# https://almolinux.org/patch-review for details.

[b4]
  midmask = https://lists.sr.ht/~postmarketos/pmbootstrap-devel/%s
  linkmask = https://lists.sr.ht/~postmarketos/pmbootstrap-devel/%%3C%s%%3E
  send-series-to = ~postmarketos/pmbootstrap-devel@lists.sr.ht
  midmask = https://lists.sr.ht/~postmarketos/almobootstrap-devel/%s
  linkmask = https://lists.sr.ht/~postmarketos/almobootstrap-devel/%%3C%s%%3E
  send-series-to = ~postmarketos/almobootstrap-devel@lists.sr.ht
  send-endpoint-web = NONE
  backend = sourcehut

M .build.yml => .build.yml +8 -8
@@ 2,22 2,22 @@ image: alpine/edge
packages:
  - sudo
sources:
  - https://git.sr.ht/~postmarketos/pmbootstrap
  - https://git.sr.ht/~postmarketos/almobootstrap
tasks:
  - note: |
      pmbootstrap/.ci/note.sh
      almobootstrap/.ci/note.sh
  - shellcheck: |
      cd pmbootstrap
      cd almobootstrap
      sudo .ci/shellcheck.sh
  - ruff: |
      cd pmbootstrap
      cd almobootstrap
      sudo .ci/ruff.sh
  - vermin: |
      cd pmbootstrap
      cd almobootstrap
      sudo .ci/vermin.sh
  - pytest: |
      cd pmbootstrap
      cd almobootstrap
      sudo .ci/pytest.sh
artifacts:
  - ".local/var/pmbootstrap/log.txt"
  - ".local/var/pmbootstrap/log_testsuite.txt"
  - ".local/var/almobootstrap/log.txt"
  - ".local/var/almobootstrap/log_testsuite.txt"

M .ci/note.sh => .ci/note.sh +1 -1
@@ 2,5 2,5 @@

printf "\n"
printf "PROTIP: use"
printf " \e[1;32mpmbootstrap ci\e[0m"
printf " \e[1;32malmobootstrap ci\e[0m"
printf " to run these scripts locally.\n"

M .ci/pytest.sh => .ci/pytest.sh +17 -17
@@ 1,7 1,7 @@
#!/bin/sh -e
# Description: run pmbootstrap python testsuite
# Description: run almobootstrap python testsuite
# Options: native slow
# https://postmarketos.org/pmb-ci
# https://almolinux.org/amb-ci

if [ "$(id -u)" = 0 ]; then
	set -x


@@ 23,45 23,45 @@ fi
# Use pytest-cov if it is installed to display code coverage
cov_arg=""
if python -c "import pytest_cov" >/dev/null 2>&1; then
	cov_arg="--cov=pmb"
	cov_arg="--cov=amb"
fi

echo "Initializing pmbootstrap..."
if ! yes '' | ./pmbootstrap.py \
echo "Initializing almobootstrap..."
if ! yes '' | ./almobootstrap.py \
		--details-to-stdout \
		init \
		>/tmp/pmb_init 2>&1; then
	cat /tmp/pmb_init
		>/tmp/amb_init 2>&1; then
	cat /tmp/amb_init
	exit 1
fi

# Make sure that the work folder format is up to date, and that there are no
# mounts from aborted test cases (#1595)
./pmbootstrap.py work_migrate
./pmbootstrap.py -q shutdown
./almobootstrap.py work_migrate
./almobootstrap.py -q shutdown

# Make sure we have a valid device (#1128)
device="$(./pmbootstrap.py config device)"
pmaports="$(./pmbootstrap.py config aports)"
deviceinfo="$(ls -1 "$pmaports"/device/*/device-"$device"/deviceinfo)"
device="$(./almobootstrap.py config device)"
almoports="$(./almobootstrap.py config aports)"
deviceinfo="$(ls -1 "$almoports"/device/*/device-"$device"/deviceinfo)"
if ! [ -e "$deviceinfo" ]; then
	echo "ERROR: Could not find deviceinfo file for selected device:" \
		"$device"
	echo "Expected path: $deviceinfo"
	echo "Maybe you have switched to a branch where your device does not"
	echo "exist? Use 'pmbootstrap config device qemu-amd64' to switch to"
	echo "exist? Use 'almobootstrap config device qemu-amd64' to switch to"
	echo "a valid device."
	exit 1
fi

# Make sure pmaports is clean, some of the tests will fail otherwise
if [ -n "$(git -C "$pmaports" status --porcelain)" ]; then
	echo "ERROR: pmaports dir is not clean"
# Make sure almoports is clean, some of the tests will fail otherwise
if [ -n "$(git -C "$almoports" status --porcelain)" ]; then
	echo "ERROR: almoports dir is not clean"
	exit 1
fi

echo "Running pytest..."
echo "NOTE: use 'pmbootstrap log' to see the detailed log if running locally."
echo "NOTE: use 'almobootstrap log' to see the detailed log if running locally."
pytest \
	--color=yes \
	-vv \

M .ci/ruff.sh => .ci/ruff.sh +1 -1
@@ 1,6 1,6 @@
#!/bin/sh -e
# Description: lint all python scripts
# https://postmarketos.org/pmb-ci
# https://almolinux.org/amb-ci

if [ "$(id -u)" = 0 ]; then
	set -x

M .ci/shellcheck.sh => .ci/shellcheck.sh +1 -1
@@ 1,6 1,6 @@
#!/bin/sh -e
# Description: lint all shell scripts
# https://postmarketos.org/pmb-ci
# https://almolinux.org/amb-ci

if [ "$(id -u)" = 0 ]; then
	set -x

M .ci/vermin.sh => .ci/vermin.sh +1 -1
@@ 1,6 1,6 @@
#!/bin/sh -e
# Description: verify that we don't use too new python features
# https://postmarketos.org/pmb-ci
# https://almolinux.org/amb-ci

if [ "$(id -u)" = 0 ]; then
	set -x

M .gitignore => .gitignore +1 -1
@@ 1,4 1,4 @@
# pmbootstrap will clone "pmaports" and (if pmbootstrap is not installed system
# almobootstrap will clone "almoports" and (if almobootstrap is not installed system
# wide) create a symlink in the aports folder
/aports


M CONTRIBUTING.md => CONTRIBUTING.md +5 -5
@@ 1,11 1,11 @@
## Reporting issues
* Consider joining the [chat](https://wiki.postmarketos.org/wiki/Matrix_and_IRC) for instant help.
* Maybe your question is answered in the [wiki](https://wiki.postmarketos.org/) somewhere. [Search](https://wiki.postmarketos.org/index.php?search=&title=Special%3ASearch&go=Go) first!
* Otherwise, just ask what you want to know. We're happy if we can help you and glad that you're using `pmbootstrap`!
* Consider joining the [chat](https://wiki.almolinux.org/wiki/Matrix_and_IRC) for instant help.
* Maybe your question is answered in the [wiki](https://wiki.almolinux.org/) somewhere. [Search](https://wiki.almolinux.org/index.php?search=&title=Special%3ASearch&go=Go) first!
* Otherwise, just ask what you want to know. We're happy if we can help you and glad that you're using `almobootstrap`!

## Development

See pmbootstrap's [Development Guide](https://wiki.postmarketos.org/wiki/Development_guide).
See almobootstrap's [Development Guide](https://wiki.almolinux.org/wiki/Development_guide).

### Contributing code changes
* [Fork](https://docs.gitlab.com/ee/gitlab-basics/fork-project.html) this repository, commit your changes and then make a [Merge Request](https://docs.gitlab.com/ee/workflow/merge_requests.html).


@@ 34,4 34,4 @@ This is a reST style.
* If it is feasible for you, try to run the testsuite on code that you have changed. The `test/test_build.py` case will build full cross-compilers for `aarch64` and `armhf`, so it may take a long time. Testcases can be started with `pytest` and it's planned to run that automatically when making a new Merge Request (see #64).


**If you need any help, don't hesitate to open an [issue](https://gitlab.com/postmarketOS/pmbootstrap/issues) and ask!**
**If you need any help, don't hesitate to open an [issue](https://gitlab.com/postmarketOS/almobootstrap/issues) and ask!**

M README.md => README.md +67 -67
@@ 1,31 1,31 @@
# almobootstrap

Sophisticated chroot/build/flash tool to develop and install
[postmarketOS](https://postmarketos.org).
[postmarketOS](https://almolinux.org).

## Development

pmbootstrap is being developed on SourceHut
([what](https://postmarketos.org/blog/2022/07/25/considering-sourcehut/)):
almobootstrap is being developed on SourceHut
([what](https://almolinux.org/blog/2022/07/25/considering-sourcehut/)):

https://git.sr.ht/~postmarketos/pmbootstrap
https://git.sr.ht/~postmarketos/almobootstrap

Send patches via mail or web UI to
[pmbootstrap-devel](https://lists.sr.ht/~postmarketos/pmbootstrap-devel)
([subscribe](mailto:~postmarketos/pmbootstrap-devel+subscribe@lists.sr.ht)):
[almobootstrap-devel](https://lists.sr.ht/~postmarketos/almobootstrap-devel)
([subscribe](mailto:~postmarketos/almobootstrap-devel+subscribe@lists.sr.ht)):
```
~postmarketos/pmbootstrap-devel@lists.sr.ht
~postmarketos/almobootstrap-devel@lists.sr.ht
```

You can set the default values for sending email in the git checkout
```
$ git config sendemail.to "~postmarketos/pmbootstrap-devel@lists.sr.ht"
$ git config format.subjectPrefix "PATCH pmbootstrap"
$ git config sendemail.to "~postmarketos/almobootstrap-devel@lists.sr.ht"
$ git config format.subjectPrefix "PATCH almobootstrap"
```

Run CI scripts locally with:
```
$ pmbootstrap ci
$ almobootstrap ci
```

Run a single test file:


@@ 36,13 36,13 @@ $ pytest -vv ./test/test_keys.py
## Issues

Issues are being tracked
[here](https://gitlab.com/postmarketOS/pmbootstrap/-/issues).
[here](https://gitlab.com/postmarketOS/almobootstrap/-/issues).

## Requirements
* Linux distribution on the host system (`x86`, `x86_64`, or `aarch64`)
  * [Windows subsystem for Linux (WSL)](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux)
    does **not** work! Please use [VirtualBox](https://www.virtualbox.org/) instead.
  * [Linux kernel 3.17 or higher](https://postmarketos.org/oldkernel)
  * [Linux kernel 3.17 or higher](https://almolinux.org/oldkernel)
* Python 3.7+
* OpenSSL
* git


@@ 50,220 50,220 @@ Issues are being tracked
* tar

## Usage Examples
Please refer to the [postmarketOS wiki](https://wiki.postmarketos.org) for
Please refer to the [postmarketOS wiki](https://wiki.almolinux.org) for
in-depth coverage of topics such as
[porting to a new device](https://wiki.postmarketos.org/wiki/Porting_to_a_new_device)
or [installation](https://wiki.postmarketos.org/wiki/Installation_guide). The
help output (`pmbootstrap -h`) has detailed usage instructions for every
[porting to a new device](https://wiki.almolinux.org/wiki/Porting_to_a_new_device)
or [installation](https://wiki.almolinux.org/wiki/Installation_guide). The
help output (`almobootstrap -h`) has detailed usage instructions for every
command. Read on for some generic examples of what can be done with
`pmbootstrap`.
`almobootstrap`.

### Installing pmbootstrap
<https://wiki.postmarketos.org/wiki/Installing_pmbootstrap>
### Installing almobootstrap
<https://wiki.almolinux.org/wiki/Installing_almobootstrap>

### Basics
Initial setup:
```
$ pmbootstrap init
$ almobootstrap init
```

Run this in a second window to see all shell commands that get executed:
```
$ pmbootstrap log
$ almobootstrap log
```

Quick health check and config overview:
```
$ pmbootstrap status
$ almobootstrap status
```

### Packages
Build `aports/main/hello-world`:
```
$ pmbootstrap build hello-world
$ almobootstrap build hello-world
```

Cross-compile to `armhf`:
```
$ pmbootstrap build --arch=armhf hello-world
$ almobootstrap build --arch=armhf hello-world
```

Build with source code from local folder:
```
$ pmbootstrap build linux-postmarketos-mainline --src=~/code/linux
$ almobootstrap build linux-postmarketos-mainline --src=~/code/linux
```

Update checksums:
```
$ pmbootstrap checksum hello-world
$ almobootstrap checksum hello-world
```

Generate a template for a new package:
```
$ pmbootstrap newapkbuild "https://gitlab.com/postmarketOS/osk-sdl/-/archive/0.52/osk-sdl-0.52.tar.bz2"
$ almobootstrap newapkbuild "https://gitlab.com/postmarketOS/osk-sdl/-/archive/0.52/osk-sdl-0.52.tar.bz2"
```

#### Default architecture

Packages will be compiled for the architecture of the device running
pmbootstrap by default. For example, if your `x86_64` PC runs pmbootstrap, it
almobootstrap by default. For example, if your `x86_64` PC runs almobootstrap, it
would build a package for `x86_64` with this command:
```
$ pmbootstrap build hello-world
$ almobootstrap build hello-world
```

If you would rather build for the target device selected in `pmbootstrap init`
If you would rather build for the target device selected in `almobootstrap init`
by default, then use the `build_default_device_arch` option:
```
$ pmbootstrap config build_default_device_arch True
$ almobootstrap config build_default_device_arch True
```

If your target device is `pine64-pinephone` for example, pmbootstrap will now
If your target device is `pine64-pinephone` for example, almobootstrap will now
build this package for `aarch64`:
```
$ pmbootstrap build hello-world
$ almobootstrap build hello-world
```

### Chroots
Enter the `armhf` building chroot:
```
$ pmbootstrap chroot -b armhf
$ almobootstrap chroot -b armhf
```

Run a command inside a chroot:
```
$ pmbootstrap chroot -- echo test
$ almobootstrap chroot -- echo test
```

Safely delete all chroots:
```
$ pmbootstrap zap
$ almobootstrap zap
```

### Device Porting Assistance
Analyze Android
[`boot.img`](https://wiki.postmarketos.org/wiki/Glossary#boot.img) files (also
[`boot.img`](https://wiki.almolinux.org/wiki/Glossary#boot.img) files (also
works with recovery OS images like TWRP):
```
$ pmbootstrap bootimg_analyze ~/Downloads/twrp-3.2.1-0-fp2.img
$ almobootstrap bootimg_analyze ~/Downloads/twrp-3.2.1-0-fp2.img
```

Check kernel configs:
```
$ pmbootstrap kconfig check
$ almobootstrap kconfig check
```

Edit a kernel config:
```
$ pmbootstrap kconfig edit --arch=armhf postmarketos-mainline
$ almobootstrap kconfig edit --arch=armhf postmarketos-mainline
```

### Root File System
Build the rootfs:
```
$ pmbootstrap install
$ almobootstrap install
```

Build the rootfs with full disk encryption:
```
$ pmbootstrap install --fde
$ almobootstrap install --fde
```

Update existing installation on SD card:
```
$ pmbootstrap install --sdcard=/dev/mmcblk0 --rsync
$ almobootstrap install --sdcard=/dev/mmcblk0 --rsync
```

Run the image in QEMU:
```
$ pmbootstrap qemu --image-size=1G
$ almobootstrap qemu --image-size=1G
```

Flash to the device:
```
$ pmbootstrap flasher flash_kernel
$ pmbootstrap flasher flash_rootfs --partition=userdata
$ almobootstrap flasher flash_kernel
$ almobootstrap flasher flash_rootfs --partition=userdata
```

Export the rootfs, kernel, initramfs, `boot.img` etc.:
```
$ pmbootstrap export
$ almobootstrap export
```

Extract the initramfs
```
$ pmbootstrap initfs extract
$ almobootstrap initfs extract
```

Build and flash Android recovery zip:
```
$ pmbootstrap install --android-recovery-zip
$ pmbootstrap flasher --method=adb sideload
$ almobootstrap install --android-recovery-zip
$ almobootstrap flasher --method=adb sideload
```

### Repository Maintenance
List pmaports that don't have a binary package:
List almoports that don't have a binary package:
```
$ pmbootstrap repo_missing --arch=armhf --overview
$ almobootstrap repo_missing --arch=armhf --overview
```

Increase the `pkgrel` for each aport where the binary package has outdated
dependencies (e.g. after soname bumps):
```
$ pmbootstrap pkgrel_bump --auto
$ almobootstrap pkgrel_bump --auto
```

Generate cross-compiler aports based on the latest version from Alpine's
aports:
```
$ pmbootstrap aportgen binutils-armhf gcc-armhf
$ almobootstrap aportgen binutils-armhf gcc-armhf
```

Manually rebuild package index:
```
$ pmbootstrap index
$ almobootstrap index
```

Delete local binary packages without existing aport of same version:
```
$ pmbootstrap zap -m
$ almobootstrap zap -m
```

### Debugging
Use `-v` on any action to get verbose logging:
```
$ pmbootstrap -v build hello-world
$ almobootstrap -v build hello-world
```

Parse a single deviceinfo and return it as JSON:
```
$ pmbootstrap deviceinfo_parse pine64-pinephone
$ almobootstrap deviceinfo_parse pine64-pinephone
```

Parse a single APKBUILD and return it as JSON:
```
$ pmbootstrap apkbuild_parse hello-world
$ almobootstrap apkbuild_parse hello-world
```

Parse a package from an APKINDEX and return it as JSON:
```
$ pmbootstrap apkindex_parse $WORK/cache_apk_x86_64/APKINDEX.8b865e19.tar.gz hello-world
$ almobootstrap apkindex_parse $WORK/cache_apk_x86_64/APKINDEX.8b865e19.tar.gz hello-world
```

`ccache` statistics:
```
$ pmbootstrap stats --arch=armhf
$ almobootstrap stats --arch=armhf
```

`distccd` log:
```
$ pmbootstrap log_distccd
$ almobootstrap log_distccd
```

### Use alternative sudo

pmbootstrap supports `doas` and `sudo`.
If multiple sudo implementations are installed, pmbootstrap will use `doas`.
almobootstrap supports `doas` and `sudo`.
If multiple sudo implementations are installed, almobootstrap will use `doas`.
You can set the `PMB_SUDO` environmental variable to define the sudo
implementation you want to use.



@@ 275,13 275,13 @@ then all files matching the glob `~/.ssh/id_*.pub` will be placed in

Sometimes, for example if you have a large number of SSH keys, you may wish to
select a different set of public keys to include in an image. To do this, set
the `ssh_key_glob` configuration parameter in the pmbootstrap config file to a
the `ssh_key_glob` configuration parameter in the almobootstrap 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/almobootstrap.cfg` may contain:

    [pmbootstrap]
    [almobootstrap]
    # ...
    ssh_keys = True
    ssh_key_glob = ~/.ssh/postmarketos-dev.pub

M amb/__init__.py => amb/__init__.py +14 -14
@@ 10,17 10,17 @@ from . import config
from . import parse
from .config import init as config_init
from .helpers import frontend
from .helpers import logging as pmb_logging
from .helpers import logging as amb_logging
from .helpers import mount
from .helpers import other

# pmbootstrap version
# almobootstrap version
__version__ = "1.53.0"

# Python version check
version = sys.version_info
if version < (3, 7):
    print("You need at least Python 3.7 to run pmbootstrap")
    print("You need at least Python 3.7 to run almobootstrap")
    print("(You are running it with Python " + str(version.major) +
          "." + str(version.minor) + ")")
    sys.exit()


@@ 40,16 40,16 @@ def main():
        # Sanity checks
        other.check_grsec()
        if not args.as_root and os.geteuid() == 0:
            raise RuntimeError("Do not run pmbootstrap as root!")
            raise RuntimeError("Do not run almobootstrap as root!")

        # Initialize or require config
        if args.action == "init":
            return config_init.frontend(args)
        elif not os.path.exists(args.config):
            raise RuntimeError("Please specify a config file, or run"
                               " 'pmbootstrap init' to generate one.")
                               " 'almobootstrap init' to generate one.")
        elif not os.path.exists(args.work):
            raise RuntimeError("Work path not found, please run 'pmbootstrap"
            raise RuntimeError("Work path not found, please run 'almobootstrap"
                               " init' to create it.")

        other.check_old_devices(args)


@@ 58,15 58,15 @@ def main():
        if args.action not in ["shutdown", "zap", "log"]:
            other.migrate_work_folder(args)

        # Run the function with the action's name (in pmb/helpers/frontend.py)
        # Run the function with the action's name (in amb/helpers/frontend.py)
        if args.action:
            getattr(frontend, args.action)(args)
        else:
            logging.info("Run pmbootstrap -h for usage information.")
            logging.info("Run almobootstrap -h for usage information.")

        # Still active notice
        if mount.ismount(args.work + "/chroot_native/dev"):
            logging.info("NOTE: chroot is still active (use 'pmbootstrap"
            logging.info("NOTE: chroot is still active (use 'almobootstrap"
                         " shutdown' as necessary)")
        logging.info("DONE!")



@@ 80,22 80,22 @@ def main():
            logging.getLogger().setLevel(logging.DEBUG)

        logging.info("ERROR: " + str(e))
        logging.info("See also: <https://postmarketos.org/troubleshooting>")
        logging.info("See also: <https://almolinux.org/troubleshooting>")
        logging.debug(traceback.format_exc())

        # Hints about the log file (print to stdout only)
        log_hint = "Run 'pmbootstrap log' for details."
        log_hint = "Run 'almobootstrap log' for details."
        if not args or not os.path.exists(args.log):
            log_hint += (" Alternatively you can use '--details-to-stdout' to"
                         " get more output, e.g. 'pmbootstrap"
                         " get more output, e.g. 'almobootstrap"
                         " --details-to-stdout init'.")
        print()
        print(log_hint)
        print()
        print("Before you report this error, ensure that pmbootstrap is "
        print("Before you report this error, ensure that almobootstrap is "
              "up to date.")
        print("Find the latest version here:"
              " https://git.sr.ht/~postmarketos/pmbootstrap/refs")
              " https://git.sr.ht/~postmarketos/almobootstrap/refs")
        print(f"Your version: {__version__}")
        return 1


M amb/aportgen/__init__.py => amb/aportgen/__init__.py +21 -21
@@ 2,20 2,20 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import logging
import pmb.aportgen.binutils
import pmb.aportgen.busybox_static
import pmb.aportgen.device
import pmb.aportgen.gcc
import pmb.aportgen.linux
import pmb.aportgen.musl
import pmb.aportgen.grub_efi
import pmb.config
import pmb.helpers.cli
import amb.aportgen.binutils
import amb.aportgen.busybox_static
import amb.aportgen.device
import amb.aportgen.gcc
import amb.aportgen.linux
import amb.aportgen.musl
import amb.aportgen.grub_efi
import amb.config
import amb.helpers.cli


def properties(pkgname):
    """
    Get the `pmb.config.aportgen` properties for the aport generator, based on
    Get the `amb.config.aportgen` properties for the aport generator, based on
    the pkgname prefix.

    Example: "musl-armhf" => ("musl", "cross", {"confirm_overwrite": False})


@@ 23,7 23,7 @@ def properties(pkgname):
    :param pkgname: package name
    :returns: (prefix, folder, options)
    """
    for folder, options in pmb.config.aportgen.items():
    for folder, options in amb.config.aportgen.items():
        for prefix in options["prefixes"]:
            if pkgname.startswith(prefix):
                return (prefix, folder, options)


@@ 31,7 31,7 @@ def properties(pkgname):
                 " aports, such as the cross-compiler related packages"
                 " or the linux kernel fork packages.")
    logging.info("NOTE: If you wanted to package new software in general, try"
                 " 'pmbootstrap newapkbuild' to generate a template.")
                 " 'almobootstrap newapkbuild' to generate a template.")
    raise ValueError("No generator available for " + pkgname + "!")




@@ 47,25 47,25 @@ def generate(args, pkgname):
    if options["confirm_overwrite"] and os.path.exists(path_target):
        logging.warning("WARNING: Target folder already exists: "
                        f"{path_target}")
        if not pmb.helpers.cli.confirm(args, "Continue and overwrite?"):
        if not amb.helpers.cli.confirm(args, "Continue and overwrite?"):
            raise RuntimeError("Aborted.")

    if os.path.exists(args.work + "/aportgen"):
        pmb.helpers.run.user(args, ["rm", "-r", args.work + "/aportgen"])
        amb.helpers.run.user(args, ["rm", "-r", args.work + "/aportgen"])
    if args.fork_alpine:
        upstream = pmb.aportgen.core.get_upstream_aport(args, pkgname)
        pmb.helpers.run.user(args, ["cp", "-r", upstream,
        upstream = amb.aportgen.core.get_upstream_aport(args, pkgname)
        amb.helpers.run.user(args, ["cp", "-r", upstream,
                                    f"{args.work}/aportgen"])
        pmb.aportgen.core.rewrite(args, pkgname, replace_simple={
        amb.aportgen.core.rewrite(args, pkgname, replace_simple={
            "# Contributor:*": None, "# Maintainer:*": None})
    else:
        # Run pmb.aportgen.PREFIX.generate()
        getattr(pmb.aportgen, prefix.replace("-", "_")).generate(args, pkgname)
        # Run amb.aportgen.PREFIX.generate()
        getattr(amb.aportgen, prefix.replace("-", "_")).generate(args, pkgname)

    # Move to the aports folder
    if os.path.exists(path_target):
        pmb.helpers.run.user(args, ["rm", "-r", path_target])
    pmb.helpers.run.user(
        amb.helpers.run.user(args, ["rm", "-r", path_target])
    amb.helpers.run.user(
        args, ["mv", args.work + "/aportgen", path_target])

    logging.info("*** pmaport generated: " + path_target)

M amb/aportgen/binutils.py => amb/aportgen/binutils.py +8 -8
@@ 1,26 1,26 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import pmb.aportgen.core
import pmb.helpers.git
import pmb.helpers.run
import amb.aportgen.core
import amb.helpers.git
import amb.helpers.run


def generate(args, pkgname):
    # Copy original aport
    arch = pkgname.split("-")[1]
    upstream = pmb.aportgen.core.get_upstream_aport(args, "binutils")
    pmb.helpers.run.user(args, ["cp", "-r", upstream, args.work + "/aportgen"])
    upstream = amb.aportgen.core.get_upstream_aport(args, "binutils")
    amb.helpers.run.user(args, ["cp", "-r", upstream, args.work + "/aportgen"])

    # Rewrite APKBUILD
    fields = {
        "arch": pmb.config.arch_native,
        "arch": amb.config.arch_native,
        "makedepends_host": "zlib-dev jansson-dev zstd-dev",
        "pkgdesc": f"Tools necessary to build programs for {arch} targets",
        "pkgname": pkgname,
    }

    replace_simple = {
        "*--with-bugurl=*": "\t\t--with-bugurl=\"https://postmarketos.org/issues\" \\"
        "*--with-bugurl=*": "\t\t--with-bugurl=\"https://almolinux.org/issues\" \\"
    }

    below_header = """


@@ 28,6 28,6 @@ def generate(args, pkgname):
        CTARGET="$(arch_to_hostspec $CTARGET_ARCH)"
    """

    pmb.aportgen.core.rewrite(args, pkgname, "main/binutils", fields,
    amb.aportgen.core.rewrite(args, pkgname, "main/binutils", fields,
                              "binutils", replace_simple=replace_simple,
                              below_header=below_header)

M amb/aportgen/busybox_static.py => amb/aportgen/busybox_static.py +17 -17
@@ 1,37 1,37 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import pmb.aportgen.core
import pmb.build
import pmb.chroot.apk
import pmb.chroot.apk_static
import pmb.helpers.run
import pmb.parse.apkindex
import amb.aportgen.core
import amb.build
import amb.chroot.apk
import amb.chroot.apk_static
import amb.helpers.run
import amb.parse.apkindex


def generate(args, pkgname):
    arch = pkgname.split("-")[2]

    # Parse version from APKINDEX
    package_data = pmb.parse.apkindex.package(args, "busybox")
    package_data = amb.parse.apkindex.package(args, "busybox")
    version = package_data["version"]
    pkgver = version.split("-r")[0]
    pkgrel = version.split("-r")[1]

    # Prepare aportgen tempdir inside and outside of chroot
    tempdir = "/tmp/aportgen"
    pmb.chroot.root(args, ["rm", "-rf", tempdir])
    pmb.helpers.run.user(args, ["mkdir", "-p", f"{args.work}/aportgen",
    amb.chroot.root(args, ["rm", "-rf", tempdir])
    amb.helpers.run.user(args, ["mkdir", "-p", f"{args.work}/aportgen",
                                f"{args.work}/chroot_native/{tempdir}"])

    # Write the APKBUILD
    channel_cfg = pmb.config.pmaports.read_config_channel(args)
    channel_cfg = amb.config.almoports.read_config_channel(args)
    mirrordir = channel_cfg["mirrordir_alpine"]
    apkbuild_path = f"{args.work}/chroot_native/{tempdir}/APKBUILD"
    apk_name = f"busybox-static-$pkgver-r$pkgrel-$_arch-{mirrordir}.apk"
    with open(apkbuild_path, "w", encoding="utf-8") as handle:
        apkbuild = f"""\
            # Automatically generated aport, do not edit!
            # Generator: pmbootstrap aportgen {pkgname}
            # Generator: almobootstrap aportgen {pkgname}

            # Stub for apkbuild-lint
            if [ -z "$(type -t arch_to_hostspec)" ]; then


@@ 43,11 43,11 @@ def generate(args, pkgname):
            pkgrel={pkgrel}

            _arch="{arch}"
            _mirror="{pmb.config.aportgen_mirror_alpine}"
            _mirror="{amb.config.aportgen_mirror_alpine}"

            url="http://busybox.net"
            license="GPL2"
            arch="{pmb.config.arch_native}"
            arch="{amb.config.arch_native}"
            options="!check !strip"
            pkgdesc="Statically linked Busybox for $_arch"
            _target="$(arch_to_hostspec $_arch)"


@@ 67,7 67,7 @@ def generate(args, pkgname):
            handle.write(line[12:].replace(" " * 4, "\t") + "\n")

    # Generate checksums
    pmb.build.init(args)
    pmb.chroot.root(args, ["chown", "-R", "pmos:pmos", tempdir])
    pmb.chroot.user(args, ["abuild", "checksum"], working_dir=tempdir)
    pmb.helpers.run.user(args, ["cp", apkbuild_path, f"{args.work}/aportgen"])
    amb.build.init(args)
    amb.chroot.root(args, ["chown", "-R", "pmos:pmos", tempdir])
    amb.chroot.user(args, ["abuild", "checksum"], working_dir=tempdir)
    amb.helpers.run.user(args, ["cp", apkbuild_path, f"{args.work}/aportgen"])

M amb/aportgen/core.py => amb/aportgen/core.py +14 -14
@@ 4,7 4,7 @@ import fnmatch
import logging
import re
import glob
import pmb.helpers.git
import amb.helpers.git


def indent_size(line):


@@ 74,7 74,7 @@ def rewrite(args, pkgname, path_original="", fields={}, replace_pkgname=None,
    if path_original:
        lines_new = [
            "# Automatically generated aport, do not edit!\n",
            "# Generator: pmbootstrap aportgen " + pkgname + "\n",
            "# Generator: almobootstrap aportgen " + pkgname + "\n",
            "# Based on: " + path_original + "\n",
            "\n",
        ]


@@ 159,22 159,22 @@ def get_upstream_aport(args, pkgname, arch=None):
    :param pkgname: package name
    :param arch: Alpine architecture (e.g. "armhf"), defaults to native arch
    :returns: absolute path on disk where the Alpine aport is checked out
              example: /opt/pmbootstrap_work/cache_git/aports/upstream/main/gcc
              example: /opt/almobootstrap_work/cache_git/aports/upstream/main/gcc
    """
    # APKBUILD
    pmb.helpers.git.clone(args, "aports_upstream")
    amb.helpers.git.clone(args, "aports_upstream")
    aports_upstream_path = args.work + "/cache_git/aports_upstream"

    # Checkout branch
    channel_cfg = pmb.config.pmaports.read_config_channel(args)
    channel_cfg = amb.config.almoports.read_config_channel(args)
    branch = channel_cfg["branch_aports"]
    logging.info(f"Checkout aports.git branch: {branch}")
    if pmb.helpers.run.user(args, ["git", "checkout", branch],
    if amb.helpers.run.user(args, ["git", "checkout", branch],
                            aports_upstream_path, check=False):
        logging.info("NOTE: run 'pmbootstrap pull' and try again")
        logging.info("NOTE: run 'almobootstrap pull' and try again")
        logging.info("NOTE: if it still fails, your aports.git was cloned with"
                     " an older version of pmbootstrap, as shallow clone."
                     " Unshallow it, or remove it and let pmbootstrap clone it"
                     " an older version of almobootstrap, as shallow clone."
                     " Unshallow it, or remove it and let almobootstrap clone it"
                     f" again: {aports_upstream_path}")
        raise RuntimeError("Branch checkout failed.")



@@ 189,7 189,7 @@ def get_upstream_aport(args, pkgname, arch=None):
    aport_path = paths[0]

    # Parse APKBUILD
    apkbuild = pmb.parse.apkbuild(f"{aport_path}/APKBUILD",
    apkbuild = amb.parse.apkbuild(f"{aport_path}/APKBUILD",
                                  check_pkgname=False)
    apkbuild_version = apkbuild["pkgver"] + "-r" + apkbuild["pkgrel"]



@@ 197,11 197,11 @@ def get_upstream_aport(args, pkgname, arch=None):
    split = aport_path.split("/")
    repo = split[-2]
    pkgname = split[-1]
    index_path = pmb.helpers.repo.alpine_apkindex_path(args, repo, arch)
    package = pmb.parse.apkindex.package(args, pkgname, indexes=[index_path])
    index_path = amb.helpers.repo.alpine_apkindex_path(args, repo, arch)
    package = amb.parse.apkindex.package(args, pkgname, indexes=[index_path])

    # Compare version (return when equal)
    compare = pmb.parse.version.compare(apkbuild_version, package["version"])
    compare = amb.parse.version.compare(apkbuild_version, package["version"])
    if compare == 0:
        return aport_path



@@ 219,4 219,4 @@ def get_upstream_aport(args, pkgname, arch=None):
                  package["version"] + ")!")

    raise RuntimeError("You can update your local checkout with: "
                       "'pmbootstrap pull'")
                       "'almobootstrap pull'")

M amb/aportgen/device.py => amb/aportgen/device.py +33 -33
@@ 2,37 2,37 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import logging
import os
import pmb.helpers.cli
import pmb.helpers.run
import pmb.aportgen.core
import pmb.parse.apkindex
import pmb.parse.bootimg
import amb.helpers.cli
import amb.helpers.run
import amb.aportgen.core
import amb.parse.apkindex
import amb.parse.bootimg


def ask_for_architecture():
    architectures = pmb.config.build_device_architectures
    architectures = amb.config.build_device_architectures
    # Don't show armhf, new ports shouldn't use this architecture
    if "armhf" in architectures:
        architectures.remove("armhf")
    while True:
        ret = pmb.helpers.cli.ask("Device architecture", architectures,
        ret = amb.helpers.cli.ask("Device architecture", architectures,
                                  architectures[0], complete=architectures)
        if ret in architectures:
            return ret
        logging.fatal("ERROR: Invalid architecture specified. If you want to"
                      " add a new architecture, edit"
                      " build_device_architectures in"
                      " pmb/config/__init__.py.")
                      " amb/config/__init__.py.")


def ask_for_manufacturer():
    logging.info("Who produced the device (e.g. LG)?")
    return pmb.helpers.cli.ask("Manufacturer", None, None, False)
    return amb.helpers.cli.ask("Manufacturer", None, None, False)


def ask_for_name(manufacturer):
    logging.info("What is the official name (e.g. Google Nexus 5)?")
    ret = pmb.helpers.cli.ask("Name", None, None, False)
    ret = amb.helpers.cli.ask("Name", None, None, False)

    # Always add the manufacturer
    if not ret.startswith(manufacturer) and \


@@ 44,48 44,48 @@ def ask_for_name(manufacturer):
def ask_for_year():
    # Regex from https://stackoverflow.com/a/12240826
    logging.info("In what year was the device released (e.g. 2012)?")
    return pmb.helpers.cli.ask("Year", None, None, False,
    return amb.helpers.cli.ask("Year", None, None, False,
                               validation_regex=r'^[1-9]\d{3,}$')


def ask_for_chassis():
    types = pmb.config.deviceinfo_chassis_types
    types = amb.config.deviceinfo_chassis_types

    logging.info("What type of device is it?")
    logging.info("Valid types are: " + ", ".join(types))
    return pmb.helpers.cli.ask("Chassis", None, None, True,
    return amb.helpers.cli.ask("Chassis", None, None, True,
                               validation_regex='|'.join(types),
                               complete=types)


def ask_for_keyboard(args):
    return pmb.helpers.cli.confirm(args, "Does the device have a hardware"
    return amb.helpers.cli.confirm(args, "Does the device have a hardware"
                                   " keyboard?")


def ask_for_external_storage(args):
    return pmb.helpers.cli.confirm(args, "Does the device have a sdcard or"
    return amb.helpers.cli.confirm(args, "Does the device have a sdcard or"
                                   " other external storage medium?")


def ask_for_flash_method():
    while True:
        logging.info("Which flash method does the device support?")
        method = pmb.helpers.cli.ask("Flash method",
                                     pmb.config.flash_methods,
                                     pmb.config.flash_methods[0],
                                     complete=pmb.config.flash_methods)
        method = amb.helpers.cli.ask("Flash method",
                                     amb.config.flash_methods,
                                     amb.config.flash_methods[0],
                                     complete=amb.config.flash_methods)

        if method in pmb.config.flash_methods:
        if method in amb.config.flash_methods:
            if method == "heimdall":
                heimdall_types = ["isorec", "bootimg"]
                while True:
                    logging.info("Does the device use the \"isolated"
                                 " recovery\" or boot.img?")
                    logging.info("<https://wiki.postmarketos.org/wiki"
                    logging.info("<https://wiki.almolinux.org/wiki"
                                 "/Deviceinfo_flash_methods#Isorec_or_bootimg"
                                 ".3F>")
                    heimdall_type = pmb.helpers.cli.ask("Type",
                    heimdall_type = amb.helpers.cli.ask("Type",
                                                        heimdall_types,
                                                        heimdall_types[0])
                    if heimdall_type in heimdall_types:


@@ 96,7 96,7 @@ def ask_for_flash_method():

        logging.fatal("ERROR: Invalid flash method specified. If you want to"
                      " add a new flash method, edit flash_methods in"
                      " pmb/config/__init__.py.")
                      " amb/config/__init__.py.")


def ask_for_bootimg(args):


@@ 104,15 104,15 @@ def ask_for_bootimg(args):
                 " automatically fill out the flasher information for your"
                 " deviceinfo file. Either specify the path to an image or"
                 " press return to skip this step (you can do it later with"
                 " 'pmbootstrap bootimg_analyze').")
                 " 'almobootstrap bootimg_analyze').")

    while True:
        response = pmb.helpers.cli.ask("Path", None, "", False)
        response = amb.helpers.cli.ask("Path", None, "", False)
        path = os.path.expanduser(response)
        if not path:
            return None
        try:
            return pmb.parse.bootimg(args, path)
            return amb.parse.bootimg(args, path)
        except Exception as e:
            logging.fatal("ERROR: " + str(e) + ". Please try again.")



@@ 167,9 167,9 @@ def generate_deviceinfo(args, pkgname, name, manufacturer, year, arch,
                        flash_method, bootimg=None):
    codename = "-".join(pkgname.split("-")[1:])
    external_storage = "true" if has_external_storage else "false"
    # Note: New variables must be added to pmb/config/__init__.py as well
    # Note: New variables must be added to amb/config/__init__.py as well
    content = f"""\
        # Reference: <https://postmarketos.org/deviceinfo>
        # Reference: <https://almolinux.org/deviceinfo>
        # Please use double quotes only. You can source this file in shell
        # scripts.



@@ 225,7 225,7 @@ def generate_deviceinfo(args, pkgname, name, manufacturer, year, arch,
        content += content_uuu

    # Write to file
    pmb.helpers.run.user(args, ["mkdir", "-p", args.work + "/aportgen"])
    amb.helpers.run.user(args, ["mkdir", "-p", args.work + "/aportgen"])
    path = args.work + "/aportgen/deviceinfo"
    with open(path, "w", encoding="utf-8") as handle:
        for line in content.rstrip().split("\n"):


@@ 245,12 245,12 @@ def generate_apkbuild(args, pkgname, name, arch, flash_method):
    depends.sort()
    depends = ("\n" + " " * 12).join(depends)
    content = f"""\
        # Reference: <https://postmarketos.org/devicepkg>
        # Reference: <https://almolinux.org/devicepkg>
        pkgname={pkgname}
        pkgdesc="{name}"
        pkgver=0.1
        pkgrel=0
        url="https://postmarketos.org"
        url="https://almolinux.org"
        license="MIT"
        arch="{arch}"
        options="!check !archcheck"


@@ 268,11 268,11 @@ def generate_apkbuild(args, pkgname, name, arch, flash_method):
            devicepkg_package $startdir $pkgname
        }}

        sha512sums="(run 'pmbootstrap checksum {pkgname}' to fill)"
        sha512sums="(run 'almobootstrap checksum {pkgname}' to fill)"
        """

    # Write the file
    pmb.helpers.run.user(args, ["mkdir", "-p", args.work + "/aportgen"])
    amb.helpers.run.user(args, ["mkdir", "-p", args.work + "/aportgen"])
    path = args.work + "/aportgen/APKBUILD"
    with open(path, "w", encoding="utf-8") as handle:
        for line in content.rstrip().split("\n"):

M amb/aportgen/gcc.py => amb/aportgen/gcc.py +8 -8
@@ 1,8 1,8 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import pmb.aportgen.core
import pmb.helpers.git
import pmb.helpers.run
import amb.aportgen.core
import amb.helpers.git
import amb.helpers.run


def generate(args, pkgname):


@@ 10,7 10,7 @@ def generate(args, pkgname):
    prefix = pkgname.split("-")[0]
    arch = pkgname.split("-")[1]
    if prefix == "gcc":
        upstream = pmb.aportgen.core.get_upstream_aport(args, "gcc", arch)
        upstream = amb.aportgen.core.get_upstream_aport(args, "gcc", arch)
        based_on = "main/gcc (from Alpine)"
    elif prefix == "gcc4":
        upstream = f"{args.aports}/main/gcc4"


@@ 21,14 21,14 @@ def generate(args, pkgname):
    else:
        raise ValueError(f"Invalid prefix '{prefix}', expected gcc, gcc4 or"
                         " gcc6.")
    pmb.helpers.run.user(args, ["cp", "-r", upstream, f"{args.work}/aportgen"])
    amb.helpers.run.user(args, ["cp", "-r", upstream, f"{args.work}/aportgen"])

    # Rewrite APKBUILD (only building for native covers most use cases and
    # saves a lot of build time, can be changed on demand)
    fields = {
        "pkgname": pkgname,
        "pkgdesc": f"Stage2 cross-compiler for {arch}",
        "arch": pmb.config.arch_native,
        "arch": amb.config.arch_native,
        "depends": f"binutils-{arch} mpc1",
        "makedepends_build": "gcc g++ bison flex texinfo gawk zip"
                             " gmp-dev mpfr-dev mpc1-dev zlib-dev",


@@ 76,7 76,7 @@ def generate(args, pkgname):
    replace_simple = {
        # Do not package libstdc++, do not add "g++-$ARCH" here (already
        # did that explicitly in the subpackages variable above, so
        # pmbootstrap picks it up properly).
        # almobootstrap picks it up properly).
        '*subpackages="$subpackages libstdc++:libcxx:*': None,

        # We set the cross_configure variable at the beginning, so it does not


@@ 88,6 88,6 @@ def generate(args, pkgname):
        '_libgcc=true*': '_libgcc=false',
    }

    pmb.aportgen.core.rewrite(args, pkgname, based_on, fields,
    amb.aportgen.core.rewrite(args, pkgname, based_on, fields,
                              replace_simple=replace_simple,
                              below_header=below_header)

M amb/aportgen/grub_efi.py => amb/aportgen/grub_efi.py +17 -17
@@ 1,49 1,49 @@
# Copyright 2023 Nick Reitemeyer, Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import pmb.aportgen.core
import pmb.build
import pmb.chroot.apk
import pmb.chroot.apk_static
import pmb.helpers.run
import pmb.parse.apkindex
import amb.aportgen.core
import amb.build
import amb.chroot.apk
import amb.chroot.apk_static
import amb.helpers.run
import amb.parse.apkindex


def generate(args, pkgname):
    arch = "x86"
    if pkgname != "grub-efi-x86":
        raise RuntimeError("only grub-efi-x86 is available")
    package_data = pmb.parse.apkindex.package(args, "grub")
    package_data = amb.parse.apkindex.package(args, "grub")
    version = package_data["version"]
    pkgver = version.split("-r")[0]
    pkgrel = version.split("-r")[1]

    # Prepare aportgen tempdir inside and outside of chroot
    tempdir = "/tmp/aportgen"
    pmb.chroot.root(args, ["rm", "-rf", tempdir])
    pmb.helpers.run.user(args, ["mkdir", "-p", f"{args.work}/aportgen",
    amb.chroot.root(args, ["rm", "-rf", tempdir])
    amb.helpers.run.user(args, ["mkdir", "-p", f"{args.work}/aportgen",
                                f"{args.work}/chroot_native/{tempdir}"])

    # Write the APKBUILD
    channel_cfg = pmb.config.pmaports.read_config_channel(args)
    channel_cfg = amb.config.almoports.read_config_channel(args)
    mirrordir = channel_cfg["mirrordir_alpine"]
    apkbuild_path = f"{args.work}/chroot_native/{tempdir}/APKBUILD"
    apk_name = f'"$srcdir/grub-efi-$pkgver-r$pkgrel-$_arch-{mirrordir}.apk"'
    with open(apkbuild_path, "w", encoding="utf-8") as handle:
        apkbuild = f"""\
            # Automatically generated aport, do not edit!
            # Generator: pmbootstrap aportgen {pkgname}
            # Generator: almobootstrap aportgen {pkgname}

            pkgname={pkgname}
            pkgver={pkgver}
            pkgrel={pkgrel}

            _arch="{arch}"
            _mirror="{pmb.config.aportgen_mirror_alpine}"
            _mirror="{amb.config.aportgen_mirror_alpine}"

            pkgdesc="GRUB $_arch EFI files for every architecture"
            url="https://www.gnu.org/software/grub/"
            license="GPL-3.0-or-later"
            arch="{pmb.config.arch_native}"
            arch="{amb.config.arch_native}"
            source="grub-efi-$pkgver-r$pkgrel-$_arch-{mirrordir}.apk::$_mirror/{mirrordir}/main/$_arch/grub-efi-$pkgver-r$pkgrel.apk"

            package() {{


@@ 57,7 57,7 @@ def generate(args, pkgname):
            handle.write(line[12:].replace(" " * 4, "\t") + "\n")

    # Generate checksums
    pmb.build.init(args)
    pmb.chroot.root(args, ["chown", "-R", "pmos:pmos", tempdir])
    pmb.chroot.user(args, ["abuild", "checksum"], working_dir=tempdir)
    pmb.helpers.run.user(args, ["cp", apkbuild_path, f"{args.work}/aportgen"])
    amb.build.init(args)
    amb.chroot.root(args, ["chown", "-R", "pmos:pmos", tempdir])
    amb.chroot.user(args, ["abuild", "checksum"], working_dir=tempdir)
    amb.helpers.run.user(args, ["cp", apkbuild_path, f"{args.work}/aportgen"])

M amb/aportgen/linux.py => amb/aportgen/linux.py +12 -12
@@ 1,14 1,14 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import pmb.helpers.run
import pmb.aportgen.core
import pmb.parse.apkindex
import pmb.parse.arch
import amb.helpers.run
import amb.aportgen.core
import amb.parse.apkindex
import amb.parse.arch


def generate_apkbuild(args, pkgname, deviceinfo, patches):
    device = "-".join(pkgname.split("-")[1:])
    carch = pmb.parse.arch.alpine_to_kernel(deviceinfo["arch"])
    carch = amb.parse.arch.alpine_to_kernel(deviceinfo["arch"])

    makedepends = ["bash", "bc", "bison", "devicepkg-dev", "findutils", "flex",
                   "openssl-dev", "perl"]


@@ 32,7 32,7 @@ def generate_apkbuild(args, pkgname, deviceinfo, patches):
        build += """\n
            # Master DTB (deviceinfo_bootimg_qcdt)"""
        vendors = ["spreadtrum", "exynos", "other"]
        soc_vendor = pmb.helpers.cli.ask("SoC vendor", vendors,
        soc_vendor = amb.helpers.cli.ask("SoC vendor", vendors,
                                         vendors[-1], complete=vendors)
        if soc_vendor == "spreadtrum":
            makedepends.append("dtbtool-sprd")


@@ 60,7 60,7 @@ def generate_apkbuild(args, pkgname, deviceinfo, patches):
    makedepends = ("\n" + " " * 12).join(makedepends)
    patches = ("\n" + " " * 12).join(patches)
    content = f"""\
        # Reference: <https://postmarketos.org/vendorkernel>
        # Reference: <https://almolinux.org/vendorkernel>
        # Kernel config based on: arch/{carch}/configs/(CHANGEME!)

        pkgname={pkgname}


@@ 72,7 72,7 @@ def generate_apkbuild(args, pkgname, deviceinfo, patches):
        _flavor="{device}"
        url="https://kernel.org"
        license="GPL-2.0-only"
        options="!strip !check !tracedeps pmb:cross-native"
        options="!strip !check !tracedeps amb:cross-native"
        makedepends="
            {makedepends}
        "


@@ 100,7 100,7 @@ def generate_apkbuild(args, pkgname, deviceinfo, patches):
        package() {{{package}
        }}

        sha512sums="(run 'pmbootstrap checksum {pkgname}' to fill)"
        sha512sums="(run 'almobootstrap checksum {pkgname}' to fill)"
        """

    # Write the file


@@ 111,10 111,10 @@ def generate_apkbuild(args, pkgname, deviceinfo, patches):

def generate(args, pkgname):
    device = "-".join(pkgname.split("-")[1:])
    deviceinfo = pmb.parse.deviceinfo(args, device)
    deviceinfo = amb.parse.deviceinfo(args, device)

    # Symlink commonly used patches
    pmb.helpers.run.user(args, ["mkdir", "-p", args.work + "/aportgen"])
    amb.helpers.run.user(args, ["mkdir", "-p", args.work + "/aportgen"])
    patches = [
        "gcc7-give-up-on-ilog2-const-optimizations.patch",
        "gcc8-fix-put-user.patch",


@@ 122,7 122,7 @@ def generate(args, pkgname):
        "kernel-use-the-gnu89-standard-explicitly.patch",
    ]
    for patch in patches:
        pmb.helpers.run.user(args, ["ln", "-s",
        amb.helpers.run.user(args, ["ln", "-s",
                                    "../../.shared-patches/linux/" + patch,
                                    args.work + "/aportgen/" + patch])


M amb/aportgen/musl.py => amb/aportgen/musl.py +19 -19
@@ 1,30 1,30 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import pmb.aportgen.core
import pmb.build
import pmb.chroot.apk
import pmb.chroot.apk_static
import pmb.helpers.run
import pmb.parse.apkindex
import amb.aportgen.core
import amb.build
import amb.chroot.apk
import amb.chroot.apk_static
import amb.helpers.run
import amb.parse.apkindex


def generate(args, pkgname):
    arch = pkgname.split("-")[1]

    # Parse musl version from APKINDEX
    package_data = pmb.parse.apkindex.package(args, "musl")
    package_data = amb.parse.apkindex.package(args, "musl")
    version = package_data["version"]
    pkgver = version.split("-r")[0]
    pkgrel = version.split("-r")[1]

    # Prepare aportgen tempdir inside and outside of chroot
    tempdir = "/tmp/aportgen"
    pmb.chroot.root(args, ["rm", "-rf", tempdir])
    pmb.helpers.run.user(args, ["mkdir", "-p", f"{args.work}/aportgen",
    amb.chroot.root(args, ["rm", "-rf", tempdir])
    amb.helpers.run.user(args, ["mkdir", "-p", f"{args.work}/aportgen",
                                f"{args.work}/chroot_native/{tempdir}"])

    # Write the APKBUILD
    channel_cfg = pmb.config.pmaports.read_config_channel(args)
    channel_cfg = amb.config.almoports.read_config_channel(args)
    mirrordir = channel_cfg["mirrordir_alpine"]
    apkbuild_path = f"{args.work}/chroot_native/{tempdir}/APKBUILD"
    apk_name = f"$srcdir/musl-$pkgver-r$pkgrel-$_arch-{mirrordir}.apk"


@@ 32,7 32,7 @@ def generate(args, pkgname):
    with open(apkbuild_path, "w", encoding="utf-8") as handle:
        apkbuild = f"""\
            # Automatically generated aport, do not edit!
            # Generator: pmbootstrap aportgen {pkgname}
            # Generator: almobootstrap aportgen {pkgname}

            # Stub for apkbuild-lint
            if [ -z "$(type -t arch_to_hostspec)" ]; then


@@ 42,11 42,11 @@ def generate(args, pkgname):
            pkgname={pkgname}
            pkgver={pkgver}
            pkgrel={pkgrel}
            arch="{pmb.config.arch_native}"
            arch="{amb.config.arch_native}"
            subpackages="musl-dev-{arch}:package_dev"

            _arch="{arch}"
            _mirror="{pmb.config.aportgen_mirror_alpine}"
            _mirror="{amb.config.aportgen_mirror_alpine}"

            url="https://musl-libc.org"
            license="MIT"


@@ 64,7 64,7 @@ def generate(args, pkgname):
                mkdir -p "$pkgdir/usr/$_target"
                cd "$pkgdir/usr/$_target"
                # Use 'busybox tar' to avoid 'tar: Child returned status 141'
                # on some machines (builds.sr.ht, gitlab-ci). See pmaports#26.
                # on some machines (builds.sr.ht, gitlab-ci). See almoports#26.
                busybox tar -xf {apk_name}
                rm .PKGINFO .SIGN.*
            }}


@@ 72,7 72,7 @@ def generate(args, pkgname):
                mkdir -p "$subpkgdir/usr/$_target"
                cd "$subpkgdir/usr/$_target"
                # Use 'busybox tar' to avoid 'tar: Child returned status 141'
                # on some machines (builds.sr.ht, gitlab-ci). See pmaports#26.
                # on some machines (builds.sr.ht, gitlab-ci). See almoports#26.
                busybox tar -xf {apk_dev_name}
                rm .PKGINFO .SIGN.*



@@ 93,7 93,7 @@ def generate(args, pkgname):
            handle.write(line[12:].replace(" " * 4, "\t") + "\n")

    # Generate checksums
    pmb.build.init(args)
    pmb.chroot.root(args, ["chown", "-R", "pmos:pmos", tempdir])
    pmb.chroot.user(args, ["abuild", "checksum"], working_dir=tempdir)
    pmb.helpers.run.user(args, ["cp", apkbuild_path, f"{args.work}/aportgen"])
    amb.build.init(args)
    amb.chroot.root(args, ["chown", "-R", "pmos:pmos", tempdir])
    amb.chroot.user(args, ["abuild", "checksum"], working_dir=tempdir)
    amb.helpers.run.user(args, ["cp", apkbuild_path, f"{args.work}/aportgen"])

M amb/build/__init__.py => amb/build/__init__.py +6 -6
@@ 1,9 1,9 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
from pmb.build.init import init, init_abuild_minimal, init_compiler
from pmb.build.envkernel import package_kernel
from pmb.build.kconfig import menuconfig
from pmb.build.newapkbuild import newapkbuild
from pmb.build.other import copy_to_buildpath, is_necessary, \
from amb.build.init import init, init_abuild_minimal, init_compiler
from amb.build.envkernel import package_kernel
from amb.build.kconfig import menuconfig
from amb.build.newapkbuild import newapkbuild
from amb.build.other import copy_to_buildpath, is_necessary, \
    index_repo
from pmb.build._package import mount_pmaports, package
from amb.build._package import mount_almoports, package

M amb/build/_package.py => amb/build/_package.py +80 -80
@@ 4,15 4,15 @@ import datetime
import logging
import os

import pmb.build
import pmb.build.autodetect
import pmb.chroot
import pmb.chroot.apk
import pmb.chroot.distccd
import pmb.helpers.pmaports
import pmb.helpers.repo
import pmb.parse
import pmb.parse.arch
import amb.build
import amb.build.autodetect
import amb.chroot
import amb.chroot.apk
import amb.chroot.distccd
import amb.helpers.almoports
import amb.helpers.repo
import amb.parse
import amb.parse.arch


def skip_already_built(pkgname, arch):


@@ 22,13 22,13 @@ def skip_already_built(pkgname, arch):

    :returns: True when it can be skipped or False
    """
    if arch not in pmb.helpers.other.cache["built"]:
        pmb.helpers.other.cache["built"][arch] = []
    if pkgname in pmb.helpers.other.cache["built"][arch]:
    if arch not in amb.helpers.other.cache["built"]:
        amb.helpers.other.cache["built"][arch] = []
    if pkgname in amb.helpers.other.cache["built"][arch]:
        logging.verbose(pkgname + ": already checked this session,"
                        " no need to build it or its dependencies")
        return True
    pmb.helpers.other.cache["built"][arch].append(pkgname)
    amb.helpers.other.cache["built"][arch].append(pkgname)
    return False




@@ 41,13 41,13 @@ def get_apkbuild(args, pkgname, arch):
    :returns: None or parsed APKBUILD
    """
    # Get existing binary package indexes
    pmb.helpers.repo.update(args, arch)
    amb.helpers.repo.update(args, arch)

    # Get pmaport, skip upstream only packages
    pmaport = pmb.helpers.pmaports.get(args, pkgname, False)
    pmaport = amb.helpers.almoports.get(args, pkgname, False)
    if pmaport:
        return pmaport
    if pmb.parse.apkindex.providers(args, pkgname, arch, False):
    if amb.parse.apkindex.providers(args, pkgname, arch, False):
        return None
    raise RuntimeError("Package '" + pkgname + "': Could not find aport, and"
                       " could not find this package in any APKINDEX!")


@@ 63,13 63,13 @@ def check_build_for_arch(args, pkgname, arch):
             does not exist as binary package.
    """
    # Check for pmaport with arch
    if pmb.helpers.package.check_arch(args, pkgname, arch, False):
    if amb.helpers.package.check_arch(args, pkgname, arch, False):
        return True

    # Check for binary package
    binary = pmb.parse.apkindex.package(args, pkgname, arch, False)
    binary = amb.parse.apkindex.package(args, pkgname, arch, False)
    if binary:
        pmaport = pmb.helpers.pmaports.get(args, pkgname)
        pmaport = amb.helpers.almoports.get(args, pkgname)
        pmaport_version = pmaport["pkgver"] + "-r" + pmaport["pkgrel"]
        logging.debug(pkgname + ": found pmaport (" + pmaport_version + ") and"
                      " binary package (" + binary["version"] + ", from"


@@ 81,7 81,7 @@ def check_build_for_arch(args, pkgname, arch):
    logging.info("NOTE: You can edit the 'arch=' line inside the APKBUILD")
    if args.action == "build":
        logging.info("NOTE: Alternatively, use --arch to build for another"
                     " architecture ('pmbootstrap build --arch=armhf " +
                     " architecture ('almobootstrap build --arch=armhf " +
                     pkgname + "')")
    raise RuntimeError("Can't build '" + pkgname + "' for architecture " +
                       arch)


@@ 128,24 128,24 @@ def build_depends(args, apkbuild, arch, strict):
    # --no-depends: check for binary packages
    depends_built = []
    if "no_depends" in args and args.no_depends:
        pmb.helpers.repo.update(args, arch)
        amb.helpers.repo.update(args, arch)
        for depend in depends:
            # Ignore conflicting dependencies
            if depend.startswith("!"):
                continue
            # Check if binary package is missing
            if not pmb.parse.apkindex.package(args, depend, arch, False):
            if not amb.parse.apkindex.package(args, depend, arch, False):
                raise RuntimeError("Missing binary package for dependency '" +
                                   depend + "' of '" + pkgname + "', but"
                                   " pmbootstrap won't build any depends since"
                                   " almobootstrap won't build any depends since"
                                   " it was started with --no-depends.")
            # Check if binary package is outdated
            apkbuild_dep = get_apkbuild(args, depend, arch)
            if apkbuild_dep and \
               pmb.build.is_necessary(args, arch, apkbuild_dep):
               amb.build.is_necessary(args, arch, apkbuild_dep):
                raise RuntimeError(f"Binary package for dependency '{depend}'"
                                   f" of '{pkgname}' is outdated, but"
                                   f" pmbootstrap won't build any depends"
                                   f" almobootstrap won't build any depends"
                                   f" since it was started with --no-depends.")
    else:
        # Build the dependencies


@@ 171,7 171,7 @@ def is_necessary_warn_depends(args, apkbuild, arch, force, depends_built):

    # Check if necessary (this warns about binary version > aport version, so
    # call it even in force mode)
    ret = pmb.build.is_necessary(args, arch, apkbuild)
    ret = amb.build.is_necessary(args, arch, apkbuild)
    if force:
        ret = True



@@ 201,7 201,7 @@ def init_buildenv(args, apkbuild, arch, strict=False, force=False, cross=None,

    depends_arch = arch
    if cross == "native":
        depends_arch = pmb.config.arch_native
        depends_arch = amb.config.arch_native

    # Build dependencies
    depends, built = build_depends(args, apkbuild, depends_arch, strict)


@@ 212,29 212,29 @@ def init_buildenv(args, apkbuild, arch, strict=False, force=False, cross=None,

    # Install and configure abuild, ccache, gcc, dependencies
    if not skip_init_buildenv:
        pmb.build.init(args, suffix)
        pmb.build.other.configure_abuild(args, suffix)
        pmb.build.other.configure_ccache(args, suffix)
    if not strict and "pmb:strict" not in apkbuild["options"] and len(depends):
        pmb.chroot.apk.install(args, depends, suffix)
        amb.build.init(args, suffix)
        amb.build.other.configure_abuild(args, suffix)
        amb.build.other.configure_ccache(args, suffix)
    if not strict and "amb:strict" not in apkbuild["options"] and len(depends):
        amb.chroot.apk.install(args, depends, suffix)
    if src:
        pmb.chroot.apk.install(args, ["rsync"], suffix)
        amb.chroot.apk.install(args, ["rsync"], suffix)

    # Cross-compiler init
    if cross:
        pmb.build.init_compiler(args, depends, cross, arch)
        amb.build.init_compiler(args, depends, cross, arch)
    if cross == "distcc":
        pmb.chroot.distccd.start(args, arch)
        amb.chroot.distccd.start(args, arch)
    if cross == "crossdirect":
        pmb.chroot.mount_native_into_foreign(args, suffix)
        # Workaround: this specific version currently in pmaports.git master
        amb.chroot.mount_native_into_foreign(args, suffix)
        # Workaround: this specific version currently in almoports.git master
        # was built with !tracedeps, so it doesn't pull in the isl dependency
        # and we need to install it manually. Doing this is easier than bumping
        # the pkgrel and going out of sync with Alpine's gcc package. This
        # workaround can be removed once a newer gcc is in Alpine and we
        # rebuild our cross gcc based on the new APKBUILD. See pmaports#1732.
        # rebuild our cross gcc based on the new APKBUILD. See almoports#1732.
        if get_gcc_version(args, arch) == "12.2.1_git20220924-r3":
            pmb.chroot.apk.install(args, ["isl25"], build=False)
            amb.chroot.apk.install(args, ["isl25"], build=False)

    return True



@@ 248,8 248,8 @@ def get_gcc_version(args, arch):
    <https://linux.die.net/man/1/ccache>
    :returns: a string like "6.4.0-r5"
    """
    return pmb.parse.apkindex.package(args, "gcc-" + arch,
                                      pmb.config.arch_native)["version"]
    return amb.parse.apkindex.package(args, "gcc-" + arch,
                                      amb.config.arch_native)["version"]


def get_pkgver(original_pkgver, original_source=False, now=None):


@@ 284,29 284,29 @@ def override_source(args, apkbuild, pkgver, src, suffix="native"):
        return

    # Mount source in chroot
    mount_path = "/mnt/pmbootstrap-source-override/"
    mount_path = "/mnt/almobootstrap-source-override/"
    mount_path_outside = args.work + "/chroot_" + suffix + mount_path
    pmb.helpers.mount.bind(args, src, mount_path_outside, umount=True)
    amb.helpers.mount.bind(args, src, mount_path_outside, umount=True)

    # Delete existing append file
    append_path = "/tmp/APKBUILD.append"
    append_path_outside = args.work + "/chroot_" + suffix + append_path
    if os.path.exists(append_path_outside):
        pmb.chroot.root(args, ["rm", append_path], suffix)
        amb.chroot.root(args, ["rm", append_path], suffix)

    # Add src path to pkgdesc, cut it off after max length
    pkgdesc = ("[" + src + "] " + apkbuild["pkgdesc"])[:127]

    # Appended content
    append = """
             # ** Overrides below appended by pmbootstrap for --src **
             # ** Overrides below appended by almobootstrap for --src **

             pkgver=\"""" + pkgver + """\"
             pkgdesc=\"""" + pkgdesc + """\"
             _pmb_src_copy="/tmp/pmbootstrap-local-source-copy"
             _amb_src_copy="/tmp/almobootstrap-local-source-copy"

             # Empty $source avoids patching in prepare()
             _pmb_source_original="$source"
             _amb_source_original="$source"
             source=""
             sha512sums=""



@@ 314,18 314,18 @@ def override_source(args, apkbuild, pkgver, src, suffix="native"):
                 # Update source copy
                 msg "Copying source from host system: """ + src + """\"
                 rsync -a --exclude=".git/" --delete --ignore-errors --force \\
                     \"""" + mount_path + """\" "$_pmb_src_copy" || true
                     \"""" + mount_path + """\" "$_amb_src_copy" || true

                 # Link local source files (e.g. kernel config)
                 mkdir "$srcdir"
                 local s
                 for s in $_pmb_source_original; do
                 for s in $_amb_source_original; do
                     is_remote "$s" || ln -sf "$startdir/$s" "$srcdir/"
                 done
             }

             unpack() {
                 ln -sv "$_pmb_src_copy" "$builddir"
                 ln -sv "$_amb_src_copy" "$builddir"
             }
             """



@@ 333,29 333,29 @@ def override_source(args, apkbuild, pkgver, src, suffix="native"):
    with open(append_path_outside, "w", encoding="utf-8") as handle:
        for line in append.split("\n"):
            handle.write(line[13:].replace(" " * 4, "\t") + "\n")
    pmb.chroot.user(args, ["cat", append_path], suffix)
    amb.chroot.user(args, ["cat", append_path], suffix)

    # Append it to the APKBUILD
    apkbuild_path = "/home/pmos/build/APKBUILD"
    shell_cmd = ("cat " + apkbuild_path + " " + append_path + " > " +
                 append_path + "_")
    pmb.chroot.user(args, ["sh", "-c", shell_cmd], suffix)
    pmb.chroot.user(args, ["mv", append_path + "_", apkbuild_path], suffix)
    amb.chroot.user(args, ["sh", "-c", shell_cmd], suffix)
    amb.chroot.user(args, ["mv", append_path + "_", apkbuild_path], suffix)


def mount_pmaports(args, destination, suffix="native"):
def mount_almoports(args, destination, suffix="native"):
    """
    Mount pmaports.git in chroot.
    Mount almoports.git in chroot.

    :param destination: mount point inside the chroot
    """
    outside_destination = args.work + "/chroot_" + suffix + destination
    pmb.helpers.mount.bind(args, args.aports, outside_destination, umount=True)
    amb.helpers.mount.bind(args, args.aports, outside_destination, umount=True)


def link_to_git_dir(args, suffix):
    """
    Make /home/pmos/build/.git point to the .git dir from pmaports.git, with a
    Make /home/pmos/build/.git point to the .git dir from almoports.git, with a
    symlink so abuild does not fail (#1841).

    abuild expects the current working directory to be a subdirectory of a


@@ 365,20 365,20 @@ def link_to_git_dir(args, suffix):
    commit as SOURCE_DATE_EPOCH (for reproducible builds).

    With that symlink, we actually make it use the last git commit from
    pmaports.git for SOURCE_DATE_EPOCH and have that in the resulting apk's
    almoports.git for SOURCE_DATE_EPOCH and have that in the resulting apk's
    .PKGINFO.
    """
    # Mount pmaports.git in chroot, in case the user did not use pmbootstrap to
    # Mount almoports.git in chroot, in case the user did not use almobootstrap to
    # clone it (e.g. how we build on sourcehut). Do this here and not at the
    # initialization of the chroot, because the pmaports dir may not exist yet
    # initialization of the chroot, because the almoports dir may not exist yet
    # at that point. Use umount=True, so we don't have an old path mounted
    # (some tests change the pmaports dir).
    destination = "/mnt/pmaports"
    mount_pmaports(args, destination, suffix)
    # (some tests change the almoports dir).
    destination = "/mnt/almoports"
    mount_almoports(args, destination, suffix)

    # Create .git symlink
    pmb.chroot.user(args, ["mkdir", "-p", "/home/pmos/build"], suffix)
    pmb.chroot.user(args, ["ln", "-sf", destination + "/.git",
    amb.chroot.user(args, ["mkdir", "-p", "/home/pmos/build"], suffix)
    amb.chroot.user(args, ["ln", "-sf", destination + "/.git",
                           "/home/pmos/build/.git"], suffix)




@@ 415,7 415,7 @@ def run_abuild(args, apkbuild, arch, strict=False, force=False, cross=None,
    env = {"CARCH": arch,
           "SUDO_APK": "abuild-apk --no-progress"}
    if cross == "native":
        hostspec = pmb.parse.arch.alpine_to_hostspec(arch)
        hostspec = amb.parse.arch.alpine_to_hostspec(arch)
        env["CROSS_COMPILE"] = hostspec + "-"
        env["CC"] = hostspec + "-gcc"
    if cross == "distcc":


@@ 432,7 432,7 @@ def run_abuild(args, apkbuild, arch, strict=False, force=False, cross=None,
            env["DISTCC_VERBOSE"] = "1"
    if cross == "crossdirect":
        env["PATH"] = ":".join(["/native/usr/lib/crossdirect/" + arch,
                                pmb.config.chroot_path])
                                amb.config.chroot_path])
    if not args.ccache:
        env["CCACHE_DISABLE"] = "1"



@@ 443,16 443,16 @@ def run_abuild(args, apkbuild, arch, strict=False, force=False, cross=None,
    # it should not be required to download them at build time, in that case
    # the APKBUILD sets the GOPATH (and therefore indirectly GOMODCACHE). But
    # e.g. when using --src they are not bundled, in that case it makes sense
    # to point GOMODCACHE at pmbootstrap's work dir so the modules are only
    # to point GOMODCACHE at almobootstrap's work dir so the modules are only
    # downloaded once.
    if args.go_mod_cache:
        env["GOMODCACHE"] = "/home/pmos/go/pkg/mod"

    # Build the abuild command
    cmd = ["abuild", "-D", "postmarketOS"]
    if strict or "pmb:strict" in apkbuild["options"]:
    if strict or "amb:strict" in apkbuild["options"]:
        if not strict:
            logging.debug(apkbuild["pkgname"] + ": 'pmb:strict' found in"
            logging.debug(apkbuild["pkgname"] + ": 'amb:strict' found in"
                          " options, building in strict mode")
        cmd += ["-r"]  # install depends with abuild
    else:


@@ 461,10 461,10 @@ def run_abuild(args, apkbuild, arch, strict=False, force=False, cross=None,
        cmd += ["-f"]

    # Copy the aport to the chroot and build it
    pmb.build.copy_to_buildpath(args, apkbuild["pkgname"], suffix)
    amb.build.copy_to_buildpath(args, apkbuild["pkgname"], suffix)
    override_source(args, apkbuild, pkgver, src, suffix)
    link_to_git_dir(args, suffix)
    pmb.chroot.user(args, cmd, suffix, "/home/pmos/build", env=env)
    amb.chroot.user(args, cmd, suffix, "/home/pmos/build", env=env)
    return (output, cmd, env)




@@ 473,7 473,7 @@ def finish(args, apkbuild, arch, output, strict=False, suffix="native"):
    Various finishing tasks that need to be done after a build.
    """
    # Verify output file
    channel = pmb.config.pmaports.read_config(args)["channel"]
    channel = amb.config.almoports.read_config(args)["channel"]
    path = f"{args.work}/packages/{channel}/{output}"
    if not os.path.exists(path):
        raise RuntimeError("Package not found after build: " + path)


@@ 481,17 481,17 @@ def finish(args, apkbuild, arch, output, strict=False, suffix="native"):
    # Clear APKINDEX cache (we only parse APKINDEX files once per session and
    # cache the result for faster dependency resolving, but after we built a
    # package we need to parse it again)
    pmb.parse.apkindex.clear_cache(f"{args.work}/packages/{channel}"
    amb.parse.apkindex.clear_cache(f"{args.work}/packages/{channel}"
                                   f"/{arch}/APKINDEX.tar.gz")

    # Uninstall build dependencies (strict mode)
    if strict or "pmb:strict" in apkbuild["options"]:
    if strict or "amb:strict" in apkbuild["options"]:
        logging.info("(" + suffix + ") uninstall build dependencies")
        pmb.chroot.user(args, ["abuild", "undeps"], suffix, "/home/pmos/build",
        amb.chroot.user(args, ["abuild", "undeps"], suffix, "/home/pmos/build",
                        env={"SUDO_APK": "abuild-apk --no-progress"})
        # If the build depends contain postmarketos-keys or postmarketos-base,
        # abuild will have removed the postmarketOS repository key (pma#1230)
        pmb.chroot.init_keys(args)
        amb.chroot.init_keys(args)


def package(args, pkgname, arch=None, force=False, strict=False,


@@ 518,7 518,7 @@ def package(args, pkgname, arch=None, force=False, strict=False,
              output path relative to the packages folder ("armhf/ab-1-r2.apk")
    """
    # Once per session is enough
    arch = arch or pmb.config.arch_native
    arch = arch or amb.config.arch_native
    if skip_already_built(pkgname, arch):
        return



@@ 530,8 530,8 @@ def package(args, pkgname, arch=None, force=False, strict=False,
    # Detect the build environment (skip unnecessary builds)
    if not check_build_for_arch(args, pkgname, arch):
        return
    suffix = pmb.build.autodetect.suffix(apkbuild, arch)
    cross = pmb.build.autodetect.crosscompile(args, apkbuild, arch, suffix)
    suffix = amb.build.autodetect.suffix(apkbuild, arch)
    cross = amb.build.autodetect.crosscompile(args, apkbuild, arch, suffix)
    if not init_buildenv(args, apkbuild, arch, strict, force, cross, suffix,
                         skip_init_buildenv, src):
        return

M amb/build/autodetect.py => amb/build/autodetect.py +13 -13
@@ 3,10 3,10 @@
import logging
import os

import pmb.config
import pmb.chroot.apk
import pmb.helpers.pmaports
import pmb.parse.arch
import amb.config
import amb.chroot.apk
import amb.helpers.almoports
import amb.parse.arch


def arch_from_deviceinfo(args, pkgname, aport):


@@ 27,7 27,7 @@ def arch_from_deviceinfo(args, pkgname, aport):

    # Return its arch
    device = pkgname.split("-", 1)[1]
    arch = pmb.parse.deviceinfo(args, device)["arch"]
    arch = amb.parse.deviceinfo(args, device)["arch"]
    logging.verbose(pkgname + ": arch from deviceinfo: " + arch)
    return arch



@@ 43,19 43,19 @@ def arch(args, pkgname):
              * device arch (this will be preferred instead if build_default_device_arch is true)
              * first arch in the APKBUILD
    """
    aport = pmb.helpers.pmaports.find(args, pkgname)
    aport = amb.helpers.almoports.find(args, pkgname)
    ret = arch_from_deviceinfo(args, pkgname, aport)
    if ret:
        return ret

    apkbuild = pmb.parse.apkbuild(f"{aport}/APKBUILD")
    apkbuild = amb.parse.apkbuild(f"{aport}/APKBUILD")
    arches = apkbuild["arch"]

    if args.build_default_device_arch:
        preferred_arch = args.deviceinfo["arch"]
        preferred_arch_2nd = pmb.config.arch_native
        preferred_arch_2nd = amb.config.arch_native
    else:
        preferred_arch = pmb.config.arch_native
        preferred_arch = amb.config.arch_native
        preferred_arch_2nd = args.deviceinfo["arch"]

    if "noarch" in arches or "all" in arches or preferred_arch in arches:


@@ 71,10 71,10 @@ def arch(args, pkgname):


def suffix(apkbuild, arch):
    if arch == pmb.config.arch_native:
    if arch == amb.config.arch_native:
        return "native"

    if "pmb:cross-native" in apkbuild["options"]:
    if "amb:cross-native" in apkbuild["options"]:
        return "native"

    return "buildroot_" + arch


@@ 86,10 86,10 @@ def crosscompile(args, apkbuild, arch, suffix):
    """
    if not args.cross:
        return None
    if not pmb.parse.arch.cpu_emulation_required(arch):
    if not amb.parse.arch.cpu_emulation_required(arch):
        return None
    if suffix == "native":
        return "native"
    if args.no_crossdirect or "!pmb:crossdirect" in apkbuild["options"]:
    if args.no_crossdirect or "!amb:crossdirect" in apkbuild["options"]:
        return "distcc"
    return "crossdirect"

M amb/build/checksum.py => amb/build/checksum.py +12 -12
@@ 2,33 2,33 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import logging

import pmb.chroot
import pmb.build
import pmb.helpers.run
import pmb.helpers.pmaports
import amb.chroot
import amb.build
import amb.helpers.run
import amb.helpers.almoports


def update(args, pkgname):
    """ Fetch all sources and update the checksums in the APKBUILD. """
    pmb.build.init_abuild_minimal(args)
    pmb.build.copy_to_buildpath(args, pkgname)
    amb.build.init_abuild_minimal(args)
    amb.build.copy_to_buildpath(args, pkgname)
    logging.info("(native) generate checksums for " + pkgname)
    pmb.chroot.user(args, ["abuild", "checksum"],
    amb.chroot.user(args, ["abuild", "checksum"],
                    working_dir="/home/pmos/build")

    # Copy modified APKBUILD back
    source = args.work + "/chroot_native/home/pmos/build/APKBUILD"
    target = pmb.helpers.pmaports.find(args, pkgname) + "/"
    pmb.helpers.run.user(args, ["cp", source, target])
    target = amb.helpers.almoports.find(args, pkgname) + "/"
    amb.helpers.run.user(args, ["cp", source, target])


def verify(args, pkgname):
    """ Fetch all sources and verify their checksums. """
    pmb.build.init_abuild_minimal(args)
    pmb.build.copy_to_buildpath(args, pkgname)
    amb.build.init_abuild_minimal(args)
    amb.build.copy_to_buildpath(args, pkgname)
    logging.info("(native) verify checksums for " + pkgname)

    # Fetch and verify sources, "fetch" alone does not verify them:
    # https://github.com/alpinelinux/abuild/pull/86
    pmb.chroot.user(args, ["abuild", "fetch", "verify"],
    amb.chroot.user(args, ["abuild", "fetch", "verify"],
                    working_dir="/home/pmos/build")

M amb/build/envkernel.py => amb/build/envkernel.py +40 -40
@@ 4,12 4,12 @@ import logging
import os
import re

import pmb.aportgen
import pmb.build
import pmb.chroot
import pmb.helpers
import pmb.helpers.pmaports
import pmb.parse
import amb.aportgen
import amb.build
import amb.chroot
import amb.helpers
import amb.helpers.almoports
import amb.parse


def match_kbuild_out(word):


@@ 72,7 72,7 @@ def find_kbuild_output_dir(function_body):
    if first is None:
        raise RuntimeError("Couldn't find a kbuild out directory. Is your "
                           "APKBUILD messed up? If not, then consider "
                           "adjusting the patterns in pmb/build/envkernel.py "
                           "adjusting the patterns in amb/build/envkernel.py "
                           "to work with your APKBUILD, or submit an issue.")
    if all(first == rest for rest in it):
        return first


@@ 86,22 86,22 @@ def modify_apkbuild(args, pkgname, aport):
    Modify kernel APKBUILD to package build output from envkernel.sh
    """
    apkbuild_path = aport + "/APKBUILD"
    apkbuild = pmb.parse.apkbuild(apkbuild_path)
    apkbuild = amb.parse.apkbuild(apkbuild_path)
    if os.path.exists(args.work + "/aportgen"):
        pmb.helpers.run.user(args, ["rm", "-r", args.work + "/aportgen"])
        amb.helpers.run.user(args, ["rm", "-r", args.work + "/aportgen"])

    pmb.helpers.run.user(args, ["mkdir", args.work + "/aportgen"])
    pmb.helpers.run.user(args, ["cp", "-r", apkbuild_path,
    amb.helpers.run.user(args, ["mkdir", args.work + "/aportgen"])
    amb.helpers.run.user(args, ["cp", "-r", apkbuild_path,
                         args.work + "/aportgen"])

    pkgver = pmb.build._package.get_pkgver(apkbuild["pkgver"],
    pkgver = amb.build._package.get_pkgver(apkbuild["pkgver"],
                                           original_source=False)
    fields = {"pkgver": pkgver,
              "pkgrel": "0",
              "subpackages": "",
              "builddir": "/home/pmos/build/src"}

    pmb.aportgen.core.rewrite(args, pkgname, apkbuild_path, fields=fields)
    amb.aportgen.core.rewrite(args, pkgname, apkbuild_path, fields=fields)


def host_build_bindmount(args, chroot, flag_file, mount=False):


@@ 113,12 113,12 @@ def host_build_bindmount(args, chroot, flag_file, mount=False):
    flag_path = f"{chroot}/{flag_file}"
    if os.path.exists(flag_path):
        logging.info("Cleaning up kernel sources bind-mount")
        pmb.helpers.run.root(args, ["umount", chroot + "/mnt/linux"], check=False)
        pmb.helpers.run.root(args, ["rm", flag_path])
        amb.helpers.run.root(args, ["umount", chroot + "/mnt/linux"], check=False)
        amb.helpers.run.root(args, ["rm", flag_path])

    if mount:
        pmb.helpers.mount.bind(args, ".", f"{chroot}/mnt/linux")
        pmb.helpers.run.root(args, ["touch", flag_path])
        amb.helpers.mount.bind(args, ".", f"{chroot}/mnt/linux")
        amb.helpers.run.root(args, ["touch", flag_path])


def run_abuild(args, pkgname, arch, apkbuild_path, kbuild_out):


@@ 136,7 136,7 @@ def run_abuild(args, pkgname, arch, apkbuild_path, kbuild_out):

    # If the kernel was cross-compiled on the host rather than with the envkernel
    # helper, we can still use the envkernel logic to package the artifacts for
    # development, making it easy to quickly sideload a new kernel or pmbootstrap
    # development, making it easy to quickly sideload a new kernel or almobootstrap
    # to create a boot image
    # This handles bind mounting the current directory (assumed to be kernel sources)
    # into the chroot so we can run abuild against it for the currently selected


@@ 144,7 144,7 @@ def run_abuild(args, pkgname, arch, apkbuild_path, kbuild_out):
    flag_file = "envkernel-bind-mounted"
    host_build = False

    if not pmb.helpers.mount.ismount(chroot + "/mnt/linux"):
    if not amb.helpers.mount.ismount(chroot + "/mnt/linux"):
        logging.info("envkernel.sh hasn't run, assuming the kernel was cross compiled"
                     "on host and using current dir as source")
        host_build = True


@@ 154,35 154,35 @@ def run_abuild(args, pkgname, arch, apkbuild_path, kbuild_out):
    if not os.path.exists(chroot + kbuild_out_source):
        raise RuntimeError("No '.output' dir found in your kernel source dir. "
                           "Compile the " + args.device + " kernel first and "
                           "then try again. See https://postmarketos.org/envkernel"
                           "then try again. See https://almolinux.org/envkernel"
                           "for details. If building on your host and only using "
                           "--envkernel for packaging, make sure you have O=.output "
                           "as an argument to make.")

    # Create working directory for abuild
    pmb.build.copy_to_buildpath(args, pkgname)
    amb.build.copy_to_buildpath(args, pkgname)

    # Create symlink from abuild working directory to envkernel build directory
    build_output = "" if kbuild_out == "" else "/" + kbuild_out
    if build_output != "":
        if os.path.islink(chroot + "/mnt/linux/" + build_output) and \
                os.path.lexists(chroot + "/mnt/linux/" + build_output):
            pmb.chroot.root(args, ["rm", "/mnt/linux/" + build_output])
        pmb.chroot.root(args, ["ln", "-s", "/mnt/linux",
            amb.chroot.root(args, ["rm", "/mnt/linux/" + build_output])
        amb.chroot.root(args, ["ln", "-s", "/mnt/linux",
                        build_path + "/src"])
    pmb.chroot.root(args, ["ln", "-s", kbuild_out_source,
    amb.chroot.root(args, ["ln", "-s", kbuild_out_source,
                    build_path + "/src" + build_output])

    cmd = ["cp", apkbuild_path, chroot + build_path + "/APKBUILD"]
    pmb.helpers.run.root(args, cmd)
    amb.helpers.run.root(args, cmd)

    # Create the apk package
    env = {"CARCH": arch,
           "CHOST": arch,
           "CBUILD": pmb.config.arch_native,
           "CBUILD": amb.config.arch_native,
           "SUDO_APK": "abuild-apk --no-progress"}
    cmd = ["abuild", "rootpkg"]
    pmb.chroot.user(args, cmd, working_dir=build_path, env=env)
    amb.chroot.user(args, cmd, working_dir=build_path, env=env)

    # Clean up bindmount if needed
    host_build_bindmount(args, chroot, flag_file)


@@ 191,13 191,13 @@ def run_abuild(args, pkgname, arch, apkbuild_path, kbuild_out):
    if build_output != "":
        if os.path.islink(chroot + "/mnt/linux/" + build_output) and \
                os.path.lexists(chroot + "/mnt/linux/" + build_output):
            pmb.chroot.root(args, ["rm", "/mnt/linux/" + build_output])
    pmb.chroot.root(args, ["rm", build_path + "/src"])
            amb.chroot.root(args, ["rm", "/mnt/linux/" + build_output])
    amb.chroot.root(args, ["rm", build_path + "/src"])


def package_kernel(args):
    """
    Frontend for 'pmbootstrap build --envkernel': creates a package from
    Frontend for 'almobootstrap build --envkernel': creates a package from
    envkernel output.
    """
    pkgname = args.packages[0]


@@ 205,27 205,27 @@ def package_kernel(args):
        raise RuntimeError("--envkernel needs exactly one linux-* package as "
                           "argument.")

    aport = pmb.helpers.pmaports.find(args, pkgname)
    aport = amb.helpers.almoports.find(args, pkgname)

    modify_apkbuild(args, pkgname, aport)
    apkbuild_path = args.work + "/aportgen/APKBUILD"

    arch = args.deviceinfo["arch"]
    apkbuild = pmb.parse.apkbuild(apkbuild_path, check_pkgname=False)
    apkbuild = amb.parse.apkbuild(apkbuild_path, check_pkgname=False)
    if apkbuild["_outdir"]:
        kbuild_out = apkbuild["_outdir"]
    else:
        function_body = pmb.parse.function_body(aport + "/APKBUILD", "package")
        function_body = amb.parse.function_body(aport + "/APKBUILD", "package")
        kbuild_out = find_kbuild_output_dir(function_body)
    suffix = pmb.build.autodetect.suffix(apkbuild, arch)
    suffix = amb.build.autodetect.suffix(apkbuild, arch)

    # Install package dependencies
    depends, _ = pmb.build._package.build_depends(
        args, apkbuild, pmb.config.arch_native, strict=False)
    pmb.build.init(args, suffix)
    if pmb.parse.arch.cpu_emulation_required(arch):
    depends, _ = amb.build._package.build_depends(
        args, apkbuild, amb.config.arch_native, strict=False)
    amb.build.init(args, suffix)
    if amb.parse.arch.cpu_emulation_required(arch):
        depends.append("binutils-" + arch)
    pmb.chroot.apk.install(args, depends, suffix)
    amb.chroot.apk.install(args, depends, suffix)

    output = (arch + "/" + apkbuild["pkgname"] + "-" + apkbuild["pkgver"] +
              "-r" + apkbuild["pkgrel"] + ".apk")


@@ 233,4 233,4 @@ def package_kernel(args):
    logging.info(message)

    run_abuild(args, pkgname, arch, apkbuild_path, kbuild_out)
    pmb.build.other.index_repo(args, arch)
    amb.build.other.index_repo(args, arch)

M amb/build/init.py => amb/build/init.py +21 -21
@@ 5,58 5,58 @@ import logging
import os
import pathlib

import pmb.build
import pmb.config
import pmb.chroot
import pmb.chroot.apk
import pmb.helpers.run
import amb.build
import amb.config
import amb.chroot
import amb.chroot.apk
import amb.helpers.run


def init_abuild_minimal(args, suffix="native"):
    """ Initialize a minimal chroot with abuild where one can do
        'abuild checksum'. """
    marker = f"{args.work}/chroot_{suffix}/tmp/pmb_chroot_abuild_init_done"
    marker = f"{args.work}/chroot_{suffix}/tmp/amb_chroot_abuild_init_done"
    if os.path.exists(marker):
        return

    pmb.chroot.apk.install(args, ["abuild"], suffix, build=False)
    amb.chroot.apk.install(args, ["abuild"], suffix, build=False)

    # Fix permissions
    pmb.chroot.root(args, ["chown", "root:abuild",
    amb.chroot.root(args, ["chown", "root:abuild",
                           "/var/cache/distfiles"], suffix)
    pmb.chroot.root(args, ["chmod", "g+w",
    amb.chroot.root(args, ["chmod", "g+w",
                           "/var/cache/distfiles"], suffix)

    # Add user to group abuild
    pmb.chroot.root(args, ["adduser", "pmos", "abuild"], suffix)
    amb.chroot.root(args, ["adduser", "pmos", "abuild"], suffix)

    pathlib.Path(marker).touch()


def init(args, suffix="native"):
    """ Initialize a chroot for building packages with abuild. """
    marker = f"{args.work}/chroot_{suffix}/tmp/pmb_chroot_build_init_done"
    marker = f"{args.work}/chroot_{suffix}/tmp/amb_chroot_build_init_done"
    if os.path.exists(marker):
        return

    init_abuild_minimal(args, suffix)

    # Initialize chroot, install packages
    pmb.chroot.apk.install(args, pmb.config.build_packages, suffix,
    amb.chroot.apk.install(args, amb.config.build_packages, suffix,
                           build=False)

    # Generate package signing keys
    chroot = args.work + "/chroot_" + suffix
    if not os.path.exists(args.work + "/config_abuild/abuild.conf"):
        logging.info("(" + suffix + ") generate abuild keys")
        pmb.chroot.user(args, ["abuild-keygen", "-n", "-q", "-a"],
        amb.chroot.user(args, ["abuild-keygen", "-n", "-q", "-a"],
                        suffix, env={"PACKAGER": "pmos <pmos@local>"})

        # Copy package signing key to /etc/apk/keys
        for key in glob.glob(chroot +
                             "/mnt/pmbootstrap-abuild-config/*.pub"):
                             "/mnt/almobootstrap-abuild-config/*.pub"):
            key = key[len(chroot):]
            pmb.chroot.root(args, ["cp", key, "/etc/apk/keys/"], suffix)
            amb.chroot.root(args, ["cp", key, "/etc/apk/keys/"], suffix)

    # Add gzip wrapper that converts '-9' to '-1'
    if not os.path.exists(chroot + "/usr/local/bin/gzip"):


@@ 77,18 77,18 @@ def init(args, suffix="native"):
            for i in range(len(lines)):
                lines[i] = lines[i][16:]
            handle.write("\n".join(lines))
        pmb.chroot.root(args, ["cp", "/tmp/gzip_wrapper.sh",
        amb.chroot.root(args, ["cp", "/tmp/gzip_wrapper.sh",
                               "/usr/local/bin/gzip"], suffix)
        pmb.chroot.root(args, ["chmod", "+x", "/usr/local/bin/gzip"], suffix)
        amb.chroot.root(args, ["chmod", "+x", "/usr/local/bin/gzip"], suffix)

    # abuild.conf: Don't clean the build folder after building, so we can
    # inspect it afterwards for debugging
    pmb.chroot.root(args, ["sed", "-i", "-e", "s/^CLEANUP=.*/CLEANUP=''/",
    amb.chroot.root(args, ["sed", "-i", "-e", "s/^CLEANUP=.*/CLEANUP=''/",
                           "/etc/abuild.conf"], suffix)

    # abuild.conf: Don't clean up installed packages in strict mode, so
    # abuild exits directly when pressing ^C in pmbootstrap.
    pmb.chroot.root(args, ["sed", "-i", "-e",
    # abuild exits directly when pressing ^C in almobootstrap.
    amb.chroot.root(args, ["sed", "-i", "-e",
                           "s/^ERROR_CLEANUP=.*/ERROR_CLEANUP=''/",
                           "/etc/abuild.conf"], suffix)



@@ 110,4 110,4 @@ def init_compiler(args, depends, cross, arch):
        if "rust" in depends or "cargo" in depends:
            cross_pkgs += ["rust"]

    pmb.chroot.apk.install(args, cross_pkgs)
    amb.chroot.apk.install(args, cross_pkgs)

M amb/build/kconfig.py => amb/build/kconfig.py +29 -29
@@ 3,15 3,15 @@
import os
import logging

import pmb.build
import pmb.build.autodetect
import pmb.build.checksum
import pmb.chroot
import pmb.chroot.apk
import pmb.chroot.other
import pmb.helpers.pmaports
import pmb.helpers.run
import pmb.parse
import amb.build
import amb.build.autodetect
import amb.build.checksum
import amb.chroot
import amb.chroot.apk
import amb.chroot.other
import amb.helpers.almoports
import amb.helpers.run
import amb.parse


def get_arch(apkbuild):


@@ 52,14 52,14 @@ def get_outputdir(args, pkgname, apkbuild):
        logging.warning("*****")
        logging.warning("NOTE: The code in this linux APKBUILD is pretty old."
                        " Consider making a backup and migrating to a modern"
                        " version with: pmbootstrap aportgen " + pkgname)
                        " version with: almobootstrap aportgen " + pkgname)
        logging.warning("*****")

        return ret

    # New style ($builddir)
    cmd = "srcdir=/home/pmos/build/src source APKBUILD; echo $builddir"
    ret = pmb.chroot.user(args, ["sh", "-c", cmd],
    ret = amb.chroot.user(args, ["sh", "-c", cmd],
                          "native", "/home/pmos/build",
                          output_return=True).rstrip()
    if os.path.exists(chroot + ret + "/.config"):


@@ 75,15 75,15 @@ def get_outputdir(args, pkgname, apkbuild):
    # Not found
    raise RuntimeError("Could not find the kernel config. Consider making a"
                       " backup of your APKBUILD and recreating it from the"
                       " template with: pmbootstrap aportgen " + pkgname)
                       " template with: almobootstrap aportgen " + pkgname)


def extract_and_patch_sources(args, pkgname, arch):
    pmb.build.copy_to_buildpath(args, pkgname)
    amb.build.copy_to_buildpath(args, pkgname)
    logging.info("(native) extract kernel source")
    pmb.chroot.user(args, ["abuild", "unpack"], "native", "/home/pmos/build")
    amb.chroot.user(args, ["abuild", "unpack"], "native", "/home/pmos/build")
    logging.info("(native) apply patches")
    pmb.chroot.user(args, ["abuild", "prepare"], "native",
    amb.chroot.user(args, ["abuild", "prepare"], "native",
                    "/home/pmos/build", output="interactive",
                    env={"CARCH": arch})



@@ 94,17 94,17 @@ def menuconfig(args, pkgname, use_oldconfig):
        pkgname = "linux-" + pkgname

    # Read apkbuild
    aport = pmb.helpers.pmaports.find(args, pkgname)
    apkbuild = pmb.parse.apkbuild(f"{aport}/APKBUILD")
    aport = amb.helpers.almoports.find(args, pkgname)
    apkbuild = amb.parse.apkbuild(f"{aport}/APKBUILD")
    arch = args.arch or get_arch(apkbuild)
    suffix = pmb.build.autodetect.suffix(apkbuild, arch)
    cross = pmb.build.autodetect.crosscompile(args, apkbuild, arch, suffix)
    hostspec = pmb.parse.arch.alpine_to_hostspec(arch)
    suffix = amb.build.autodetect.suffix(apkbuild, arch)
    cross = amb.build.autodetect.crosscompile(args, apkbuild, arch, suffix)
    hostspec = amb.parse.arch.alpine_to_hostspec(arch)

    # Set up build tools and makedepends
    pmb.build.init(args, suffix)
    amb.build.init(args, suffix)
    if cross:
        pmb.build.init_compiler(args, [], cross, arch)
        amb.build.init_compiler(args, [], cross, arch)

    depends = apkbuild["makedepends"]
    copy_xauth = False


@@ 123,11 123,11 @@ def menuconfig(args, pkgname, use_oldconfig):
        else:
            depends += ["ncurses-dev"]

    pmb.chroot.apk.install(args, depends)
    amb.chroot.apk.install(args, depends)

    # Copy host's .xauthority into native
    if copy_xauth:
        pmb.chroot.other.copy_xauthority(args)
        amb.chroot.other.copy_xauthority(args)

    extract_and_patch_sources(args, pkgname, arch)



@@ 137,7 137,7 @@ def menuconfig(args, pkgname, use_oldconfig):
    # Run make menuconfig
    outputdir = get_outputdir(args, pkgname, apkbuild)
    logging.info("(native) make " + kopt)
    env = {"ARCH": pmb.parse.arch.alpine_to_kernel(arch),
    env = {"ARCH": amb.parse.arch.alpine_to_kernel(arch),
           "DISPLAY": os.environ.get("DISPLAY"),
           "XAUTHORITY": "/home/pmos/.Xauthority"}
    if cross:


@@ 145,7 145,7 @@ def menuconfig(args, pkgname, use_oldconfig):
        env["CC"] = f"{hostspec}-gcc"
    if color:
        env["MENUCONFIG_COLOR"] = color
    pmb.chroot.user(args, ["make", kopt], "native",
    amb.chroot.user(args, ["make", kopt], "native",
                    outputdir, output="tui", env=env)

    # Find the updated config


@@ 157,8 157,8 @@ def menuconfig(args, pkgname, use_oldconfig):
    logging.info("Copy kernel config back to aport-folder")
    config = "config-" + apkbuild["_flavor"] + "." + arch
    target = aport + "/" + config
    pmb.helpers.run.user(args, ["cp", source, target])
    pmb.build.checksum.update(args, pkgname)
    amb.helpers.run.user(args, ["cp", source, target])
    amb.build.checksum.update(args, pkgname)

    # Check config
    pmb.parse.kconfig.check(args, apkbuild["_flavor"], details=True)
    amb.parse.kconfig.check(args, apkbuild["_flavor"], details=True)

M amb/build/newapkbuild.py => amb/build/newapkbuild.py +14 -14
@@ 3,48 3,48 @@
import glob
import os
import logging
import pmb.chroot.user
import pmb.helpers.cli
import pmb.parse
import amb.chroot.user
import amb.helpers.cli
import amb.parse


def newapkbuild(args, folder, args_passed, force=False):
    # Initialize build environment and build folder
    pmb.build.init(args)
    amb.build.init(args)
    build = "/home/pmos/build"
    build_outside = args.work + "/chroot_native" + build
    if os.path.exists(build_outside):
        pmb.chroot.root(args, ["rm", "-r", build])
    pmb.chroot.user(args, ["mkdir", "-p", build])
        amb.chroot.root(args, ["rm", "-r", build])
    amb.chroot.user(args, ["mkdir", "-p", build])

    # Run newapkbuild
    pmb.chroot.user(args, ["newapkbuild"] + args_passed, working_dir=build)
    amb.chroot.user(args, ["newapkbuild"] + args_passed, working_dir=build)
    glob_result = glob.glob(build_outside + "/*/APKBUILD")
    if not len(glob_result):
        return

    # Paths for copying
    source_apkbuild = glob_result[0]
    pkgname = pmb.parse.apkbuild(source_apkbuild, False)["pkgname"]
    pkgname = amb.parse.apkbuild(source_apkbuild, False)["pkgname"]
    target = args.aports + "/" + folder + "/" + pkgname

    # Move /home/pmos/build/$pkgname/* to /home/pmos/build/*
    for path in glob.glob(build_outside + "/*/*"):
        path_inside = build + "/" + pkgname + "/" + os.path.basename(path)
        pmb.chroot.user(args, ["mv", path_inside, build])
    pmb.chroot.user(args, ["rmdir", build + "/" + pkgname])
        amb.chroot.user(args, ["mv", path_inside, build])
    amb.chroot.user(args, ["rmdir", build + "/" + pkgname])

    # Overwrite confirmation
    if os.path.exists(target):
        logging.warning("WARNING: Folder already exists: " + target)
        question = "Continue and delete its contents?"
        if not force and not pmb.helpers.cli.confirm(args, question):
        if not force and not amb.helpers.cli.confirm(args, question):
            raise RuntimeError("Aborted.")
        pmb.helpers.run.user(args, ["rm", "-r", target])
        amb.helpers.run.user(args, ["rm", "-r", target])

    # Copy the aport (without the extracted src folder)
    logging.info("Create " + target)
    pmb.helpers.run.user(args, ["mkdir", "-p", target])
    amb.helpers.run.user(args, ["mkdir", "-p", target])
    for path in glob.glob(build_outside + "/*"):
        if not os.path.isdir(path):
            pmb.helpers.run.user(args, ["cp", path, target])
            amb.helpers.run.user(args, ["cp", path, target])

M amb/build/other.py => amb/build/other.py +26 -26
@@ 6,18 6,18 @@ import os
import shlex
import datetime

import pmb.chroot
import pmb.helpers.file
import pmb.helpers.git
import pmb.helpers.pmaports
import pmb.helpers.run
import pmb.parse.apkindex
import pmb.parse.version
import amb.chroot
import amb.helpers.file
import amb.helpers.git
import amb.helpers.almoports
import amb.helpers.run
import amb.parse.apkindex
import amb.parse.version


def copy_to_buildpath(args, package, suffix="native"):
    # Sanity check
    aport = pmb.helpers.pmaports.find(args, package)
    aport = amb.helpers.almoports.find(args, package)
    if not os.path.exists(aport + "/APKBUILD"):
        raise ValueError("Path does not contain an APKBUILD file:" +
                         aport)


@@ 25,10 25,10 @@ def copy_to_buildpath(args, package, suffix="native"):
    # Clean up folder
    build = args.work + "/chroot_" + suffix + "/home/pmos/build"
    if os.path.exists(build):
        pmb.chroot.root(args, ["rm", "-rf", "/home/pmos/build"], suffix)
        amb.chroot.root(args, ["rm", "-rf", "/home/pmos/build"], suffix)

    # Copy aport contents with resolved symlinks
    pmb.helpers.run.root(args, ["mkdir", "-p", build])
    amb.helpers.run.root(args, ["mkdir", "-p", build])
    for entry in os.listdir(aport):
        # 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.


@@ 36,9 36,9 @@ def copy_to_buildpath(args, package, suffix="native"):
        if entry in ["src", "pkg"]:
            logging.warn(f"WARNING: Not copying {entry}, looks like a leftover from abuild")
            continue
        pmb.helpers.run.root(args, ["cp", "-rL", f"{aport}/{entry}", f"{build}/{entry}"])
        amb.helpers.run.root(args, ["cp", "-rL", f"{aport}/{entry}", f"{build}/{entry}"])

    pmb.chroot.root(args, ["chown", "-R", "pmos:pmos",
    amb.chroot.root(args, ["chown", "-R", "pmos:pmos",
                           "/home/pmos/build"], suffix)




@@ 48,7 48,7 @@ def is_necessary(args, arch, apkbuild, indexes=None):
    this check also works for different architectures.

    :param arch: package target architecture
    :param apkbuild: from pmb.parse.apkbuild()
    :param apkbuild: from amb.parse.apkbuild()
    :param indexes: list of APKINDEX.tar.gz paths
    :returns: boolean
    """


@@ 58,14 58,14 @@ def is_necessary(args, arch, apkbuild, indexes=None):
    msg = "Build is necessary for package '" + package + "': "

    # Get old version from APKINDEX
    index_data = pmb.parse.apkindex.package(args, package, arch, False,
    index_data = amb.parse.apkindex.package(args, package, arch, False,
                                            indexes)
    if not index_data:
        logging.debug(msg + "No binary package available")
        return True

    # Can't build pmaport for arch: use Alpine's package (#1897)
    if arch and not pmb.helpers.pmaports.check_arches(apkbuild["arch"], arch):
    if arch and not amb.helpers.almoports.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.")


@@ 73,11 73,11 @@ def is_necessary(args, arch, apkbuild, indexes=None):

    # a) Binary repo has a newer version
    version_old = index_data["version"]
    if pmb.parse.version.compare(version_old, version_new) == 1:
    if amb.parse.version.compare(version_old, version_new) == 1:
        logging.warning("WARNING: package {}: aport version {} is lower than"
                        " {} from the binary repository. {} will be used when"
                        " installing {}. See also:"
                        " <https://postmarketos.org/warning-repo2>"
                        " <https://almolinux.org/warning-repo2>"
                        "".format(package, version_new, version_old,
                                  version_old, package))
        return False


@@ 95,14 95,14 @@ def is_necessary(args, arch, apkbuild, indexes=None):
def index_repo(args, 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
    cache for that file for the current almobootstrap 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(args)
    amb.build.init(args)

    channel = pmb.config.pmaports.read_config(args)["channel"]
    channel = amb.config.almoports.read_config(args)["channel"]
    if arch:
        paths = [f"{args.work}/packages/{channel}/{arch}"]
    else:


@@ 123,10 123,10 @@ def index_repo(args, arch=None):
                ["mv", "APKINDEX.tar.gz_", "APKINDEX.tar.gz"]
            ]
            for command in commands:
                pmb.chroot.user(args, command, working_dir=path_repo_chroot)
                amb.chroot.user(args, command, working_dir=path_repo_chroot)
        else:
            logging.debug("NOTE: Can't build index for: " + path)
        pmb.parse.apkindex.clear_cache(f"{path}/APKINDEX.tar.gz")
        amb.parse.apkindex.clear_cache(f"{path}/APKINDEX.tar.gz")


def configure_abuild(args, suffix, verify=False):


@@ 146,13 146,13 @@ def configure_abuild(args, suffix, verify=False):
                    raise RuntimeError(f"Failed to configure abuild: {path}"
                                       "\nTry to delete the file"
                                       "(or zap the chroot).")
                pmb.chroot.root(args, ["sed", "-i", "-e",
                amb.chroot.root(args, ["sed", "-i", "-e",
                                       f"s/^{prefix}.*/{prefix}{args.jobs}/",
                                       "/etc/abuild.conf"],
                                suffix)
                configure_abuild(args, suffix, True)
            return
    pmb.chroot.root(args, ["sed", "-i", f"$ a\\{prefix}{args.jobs}", "/etc/abuild.conf"], suffix)
    amb.chroot.root(args, ["sed", "-i", f"$ a\\{prefix}{args.jobs}", "/etc/abuild.conf"], suffix)


def configure_ccache(args, suffix="native", verify=False):


@@ 162,7 162,7 @@ def configure_ccache(args, suffix="native", verify=False):
    :param verify: internally used to test if changing the config has worked.
    """
    # Check if the settings have been set already
    arch = pmb.parse.arch.from_chroot_suffix(args, suffix)
    arch = amb.parse.arch.from_chroot_suffix(args, suffix)
    path = args.work + "/cache_ccache_" + arch + "/ccache.conf"
    if os.path.exists(path):
        with open(path, encoding="utf-8") as handle:


@@ 174,6 174,6 @@ def configure_ccache(args, suffix="native", verify=False):
                           " delete the file (or zap the chroot).")

    # Set the size and verify
    pmb.chroot.user(args, ["ccache", "--max-size", args.ccache_size],
    amb.chroot.user(args, ["ccache", "--max-size", args.ccache_size],
                    suffix)
    configure_ccache(args, suffix, True)

M amb/chroot/__init__.py => amb/chroot/__init__.py +7 -7
@@ 1,9 1,9 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
from pmb.chroot.init import init, init_keys
from pmb.chroot.mount import mount, mount_native_into_foreign
from pmb.chroot.root import root
from pmb.chroot.user import user
from pmb.chroot.user import exists as user_exists
from pmb.chroot.shutdown import shutdown
from pmb.chroot.zap import zap
from amb.chroot.init import init, init_keys
from amb.chroot.mount import mount, mount_native_into_foreign
from amb.chroot.root import root
from amb.chroot.user import user
from amb.chroot.user import exists as user_exists
from amb.chroot.shutdown import shutdown
from amb.chroot.zap import zap

M amb/chroot/apk.py => amb/chroot/apk.py +43 -43
@@ 4,14 4,14 @@ import os
import logging
import shlex

import pmb.chroot
import pmb.config
import pmb.helpers.apk
import pmb.helpers.pmaports
import pmb.parse.apkindex
import pmb.parse.arch
import pmb.parse.depends
import pmb.parse.version
import amb.chroot
import amb.config
import amb.helpers.apk
import amb.helpers.almoports
import amb.parse.apkindex
import amb.parse.arch
import amb.parse.depends
import amb.parse.version


def update_repository_list(args, suffix="native", check=False):


@@ 25,7 25,7 @@ def update_repository_list(args, suffix="native", check=False):
                  True.
    """
    # Skip if we already did this
    if suffix in pmb.helpers.other.cache["apk_repository_list_updated"]:
    if suffix in amb.helpers.other.cache["apk_repository_list_updated"]:
        return

    # Read old entries or create folder structure


@@ 38,12 38,12 @@ def update_repository_list(args, suffix="native", check=False):
            for line in handle:
                lines_old.append(line[:-1])
    else:
        pmb.helpers.run.root(args, ["mkdir", "-p", os.path.dirname(path)])
        amb.helpers.run.root(args, ["mkdir", "-p", os.path.dirname(path)])

    # Up to date: Save cache, return
    lines_new = pmb.helpers.repo.urls(args)
    lines_new = amb.helpers.repo.urls(args)
    if lines_old == lines_new:
        pmb.helpers.other.cache["apk_repository_list_updated"].append(suffix)
        amb.helpers.other.cache["apk_repository_list_updated"].append(suffix)
        return

    # Check phase: raise error when still outdated


@@ 53,9 53,9 @@ def update_repository_list(args, suffix="native", check=False):
    # Update the file
    logging.debug(f"({suffix}) update /etc/apk/repositories")
    if os.path.exists(path):
        pmb.helpers.run.root(args, ["rm", path])
        amb.helpers.run.root(args, ["rm", path])
    for line in lines_new:
        pmb.helpers.run.root(args, ["sh", "-c", "echo "
        amb.helpers.run.root(args, ["sh", "-c", "echo "
                                    f"{shlex.quote(line)} >> {path}"])
    update_repository_list(args, suffix, True)



@@ 63,11 63,11 @@ def update_repository_list(args, suffix="native", check=False):
def check_min_version(args, suffix="native"):
    """
    Check the minimum apk version, before running it the first time in the
    current session (lifetime of one pmbootstrap call).
    current session (lifetime of one almobootstrap call).
    """

    # Skip if we already did this
    if suffix in pmb.helpers.other.cache["apk_min_version_checked"]:
    if suffix in amb.helpers.other.cache["apk_min_version_checked"]:
        return

    # Skip if apk is not installed yet


@@ 78,39 78,39 @@ def check_min_version(args, suffix="native"):

    # Compare
    version_installed = installed(args, suffix)["apk-tools"]["version"]
    pmb.helpers.apk.check_outdated(
    amb.helpers.apk.check_outdated(
        args, version_installed,
        "Delete your http cache and zap all chroots, then try again:"
        " 'pmbootstrap zap -hc'")
        " 'almobootstrap zap -hc'")

    # Mark this suffix as checked
    pmb.helpers.other.cache["apk_min_version_checked"].append(suffix)
    amb.helpers.other.cache["apk_min_version_checked"].append(suffix)


def install_build(args, package, arch):
    """
    Build an outdated package unless pmbootstrap was invoked with
    "pmbootstrap install" and the option to build packages during pmb install
    Build an outdated package unless almobootstrap was invoked with
    "almobootstrap install" and the option to build packages during amb install
    is disabled.

    :param package: name of the package to build
    :param arch: architecture of the package to build
    """
    # User may have disabled building packages during "pmbootstrap install"
    # User may have disabled building packages during "almobootstrap install"
    if args.action == "install" and not args.build_pkgs_on_install:
        if not pmb.parse.apkindex.package(args, package, arch, False):
        if not amb.parse.apkindex.package(args, package, arch, False):
            raise RuntimeError(f"{package}: no binary package found for"
                               f" {arch}, and compiling packages during"
                               " 'pmbootstrap install' has been disabled."
                               " 'almobootstrap install' has been disabled."
                               " Consider changing this option in"
                               " 'pmbootstrap init'.")
                               " 'almobootstrap init'.")
        # Use the existing binary package
        return

    # Build the package if it's in pmaports and there is no binary package
    # Build the package if it's in almoports and there is no binary package
    # with the same pkgver and pkgrel. This check is done in
    # pmb.build.is_necessary, which gets called in pmb.build.package.
    return pmb.build.package(args, package, arch)
    # amb.build.is_necessary, which gets called in amb.build.package.
    return amb.build.package(args, package, arch)


def packages_split_to_add_del(packages):


@@ 143,13 143,13 @@ def packages_get_locally_built_apks(args, packages, arch):
    :param packages: list of pkgnames
    :param arch: architecture that the locally built packages should have
    :returns: list of apk file paths that are valid inside the chroots, e.g.
              ["/mnt/pmbootstrap-packages/x86_64/hello-world-1-r6.apk", ...]
              ["/mnt/almobootstrap-packages/x86_64/hello-world-1-r6.apk", ...]
    """
    channel = pmb.config.pmaports.read_config(args)["channel"]
    channel = amb.config.almoports.read_config(args)["channel"]
    ret = []

    for package in packages:
        data_repo = pmb.parse.apkindex.package(args, package, arch, False)
        data_repo = amb.parse.apkindex.package(args, package, arch, False)
        if not data_repo:
            continue



@@ 157,7 157,7 @@ def packages_get_locally_built_apks(args, packages, arch):
        if not os.path.exists(f"{args.work}/packages/{channel}/{arch}/{apk_file}"):
            continue

        ret.append(f"/mnt/pmbootstrap-packages/{arch}/{apk_file}")
        ret.append(f"/mnt/almobootstrap-packages/{arch}/{apk_file}")

    return ret



@@ 185,8 185,8 @@ def install_run_apk(args, to_add, to_add_local, to_del, suffix):
    # Use a virtual package to mark only the explicitly requested packages as
    # explicitly installed, not the ones in to_add_local
    if to_add_local:
        commands += [["add", "-u", "--virtual", ".pmbootstrap"] + to_add_local,
                     ["del", ".pmbootstrap"]]
        commands += [["add", "-u", "--virtual", ".almobootstrap"] + to_add_local,
                     ["del", ".almobootstrap"]]

    if to_del:
        commands += [["del"] + to_del]


@@ 195,41 195,41 @@ def install_run_apk(args, to_add, to_add_local, to_del, suffix):
        if args.offline:
            command = ["--no-network"] + command
        if i == 0:
            pmb.helpers.apk.apk_with_progress(args, ["apk"] + command,
            amb.helpers.apk.apk_with_progress(args, ["apk"] + command,
                                              chroot=True, suffix=suffix)
        else:
            # Virtual package related commands don't actually install or remove
            # packages, but only mark the right ones as explicitly installed.
            # They finish up almost instantly, so don't display a progress bar.
            pmb.chroot.root(args, ["apk", "--no-progress"] + command,
            amb.chroot.root(args, ["apk", "--no-progress"] + command,
                            suffix=suffix)


def install(args, packages, suffix="native", build=True):
    """
    Install packages from pmbootstrap's local package index or the pmOS/Alpine
    Install packages from almobootstrap's local package index or the pmOS/Alpine
    binary package mirrors. Iterate over all dependencies recursively, and
    build missing packages as necessary.

    :param packages: list of pkgnames to be installed
    :param suffix: the chroot suffix, e.g. "native" or "rootfs_qemu-amd64"
    :param build: automatically build the package, when it does not exist yet
                  or needs to be updated, and it is inside pmaports. For the
                  or needs to be updated, and it is inside almoports. For the
                  special case that all packages are expected to be in Alpine's
                  repositories, set this to False for performance optimization.
    """
    arch = pmb.parse.arch.from_chroot_suffix(args, suffix)
    arch = amb.parse.arch.from_chroot_suffix(args, suffix)

    if not packages:
        logging.verbose("pmb.chroot.apk.install called with empty packages list,"
        logging.verbose("amb.chroot.apk.install called with empty packages list,"
                        " ignoring")
        return

    # Initialize chroot
    check_min_version(args, suffix)
    pmb.chroot.init(args, suffix)
    amb.chroot.init(args, suffix)

    packages_with_depends = pmb.parse.depends.recurse(args, packages, suffix)
    packages_with_depends = amb.parse.depends.recurse(args, packages, suffix)
    to_add, to_del = packages_split_to_add_del(packages_with_depends)

    if build:


@@ 259,4 259,4 @@ def installed(args, suffix="native"):
              }
    """
    path = f"{args.work}/chroot_{suffix}/lib/apk/db/installed"
    return pmb.parse.apkindex.parse(path, False)
    return amb.parse.apkindex.parse(path, False)

M amb/chroot/apk_static.py => amb/chroot/apk_static.py +20 -20
@@ 7,13 7,13 @@ import tarfile
import tempfile
import stat

import pmb.helpers.apk
import pmb.helpers.run
import pmb.config
import pmb.config.load
import pmb.parse.apkindex
import pmb.helpers.http
import pmb.parse.version
import amb.helpers.apk
import amb.helpers.run
import amb.config
import amb.config.load
import amb.parse.apkindex
import amb.helpers.http
import amb.parse.version


def read_signature_info(tar):


@@ 41,7 41,7 @@ def read_signature_info(tar):
    logging.debug(f"sigkey: {sigkey}")

    # Get path to keyfile on disk
    sigkey_path = f"{pmb.config.apk_keys_path}/{sigkey}"
    sigkey_path = f"{amb.config.apk_keys_path}/{sigkey}"
    if "/" in sigkey or not os.path.exists(sigkey_path):
        logging.debug(f"sigkey_path: {sigkey_path}")
        raise RuntimeError(f"Invalid signature key: {sigkey}")


@@ 66,7 66,7 @@ def extract_temp(tar, sigfilename):
    for ftype in ret.keys():
        member = tar.getmember(ret[ftype]["filename"])

        handle, path = tempfile.mkstemp(ftype, "pmbootstrap")
        handle, path = tempfile.mkstemp(ftype, "almobootstrap")
        handle = open(handle, "wb")
        ret[ftype]["temp_path"] = path
        shutil.copyfileobj(tar.extractfile(member), handle)


@@ 85,7 85,7 @@ def verify_signature(args, files, sigkey_path):
    """
    logging.debug(f"Verify apk.static signature with {sigkey_path}")
    try:
        pmb.helpers.run.user(args, ["openssl", "dgst", "-sha1", "-verify",
        amb.helpers.run.user(args, ["openssl", "dgst", "-sha1", "-verify",
                                    sigkey_path, "-signature", files[
                                        "sig"]["temp_path"],
                                    files["apk"]["temp_path"]])


@@ 94,7 94,7 @@ def verify_signature(args, files, sigkey_path):
        os.unlink(files["apk"]["temp_path"])
        raise RuntimeError("Failed to validate signature of apk.static."
                           " Either openssl is not installed, or the"
                           " download failed. Run 'pmbootstrap zap -hc' to"
                           " download failed. Run 'almobootstrap zap -hc' to"
                           " delete the download and try again.")




@@ 118,7 118,7 @@ def extract(args, version, apk_path):
    logging.debug("Verify the version reported by the apk.static binary"
                  f" (must match the package version {version})")
    os.chmod(temp_path, os.stat(temp_path).st_mode | stat.S_IEXEC)
    version_bin = pmb.helpers.run.user(args, [temp_path, "--version"],
    version_bin = amb.helpers.run.user(args, [temp_path, "--version"],
                                       output_return=True)
    version_bin = version_bin.split(" ")[1].split(",")[0]
    if not version.startswith(f"{version_bin}-r"):


@@ 139,10 139,10 @@ def download(args, file):
    """
    Download a single file from an Alpine mirror.
    """
    channel_cfg = pmb.config.pmaports.read_config_channel(args)
    channel_cfg = amb.config.almoports.read_config_channel(args)
    mirrordir = channel_cfg["mirrordir_alpine"]
    base_url = f"{args.mirror_alpine}{mirrordir}/main/{pmb.config.arch_native}"
    return pmb.helpers.http.download(args, f"{base_url}/{file}", file)
    base_url = f"{args.mirror_alpine}{mirrordir}/main/{amb.config.arch_native}"
    return amb.helpers.http.download(args, f"{base_url}/{file}", file)


def init(args):


@@ 150,14 150,14 @@ def init(args):
    Download, verify, extract $WORK/apk.static.
    """
    # Get and parse the APKINDEX
    apkindex = pmb.helpers.repo.alpine_apkindex_path(args, "main")
    index_data = pmb.parse.apkindex.package(args, "apk-tools-static",
    apkindex = amb.helpers.repo.alpine_apkindex_path(args, "main")
    index_data = amb.parse.apkindex.package(args, "apk-tools-static",
                                            indexes=[apkindex])
    version = index_data["version"]

    # Verify the apk-tools-static version
    pmb.helpers.apk.check_outdated(
        args, version, "Run 'pmbootstrap update', then try again.")
    amb.helpers.apk.check_outdated(
        args, version, "Run 'almobootstrap update', then try again.")

    # Download, extract, verify apk-tools-static
    apk_name = f"apk-tools-static-{version}.apk"


@@ 168,5 168,5 @@ def init(args):
def run(args, parameters):
    if args.offline:
        parameters = ["--no-network"] + parameters
    pmb.helpers.apk.apk_with_progress(
    amb.helpers.apk.apk_with_progress(
        args, [f"{args.work}/apk.static"] + parameters, chroot=False)

M amb/chroot/binfmt.py => amb/chroot/binfmt.py +12 -12
@@ 3,10 3,10 @@
import os
import logging

import pmb.helpers.run
import pmb.helpers.other
import pmb.parse
import pmb.parse.arch
import amb.helpers.run
import amb.helpers.other
import amb.parse
import amb.parse.arch


def is_registered(arch_qemu):


@@ 17,23 17,23 @@ def register(args, arch):
    """
    Get arch, magic, mask.
    """
    arch_qemu = pmb.parse.arch.alpine_to_qemu(arch)
    arch_qemu = amb.parse.arch.alpine_to_qemu(arch)

    # always make sure the qemu-<arch> binary is installed, since registering
    # may happen outside of this method (e.g. by OS)
    if f"qemu-{arch_qemu}" not in pmb.chroot.apk.installed(args):
        pmb.chroot.apk.install(args, ["qemu-" + arch_qemu])
    if f"qemu-{arch_qemu}" not in amb.chroot.apk.installed(args):
        amb.chroot.apk.install(args, ["qemu-" + arch_qemu])

    if is_registered(arch_qemu):
        return
    pmb.helpers.other.check_binfmt_misc(args)
    amb.helpers.other.check_binfmt_misc(args)

    # Don't continue if the actions from check_binfmt_misc caused the OS to
    # automatically register the target arch
    if is_registered(arch_qemu):
        return

    info = pmb.parse.binfmt_info(arch_qemu)
    info = amb.parse.binfmt_info(arch_qemu)

    # Build registration string
    # https://en.wikipedia.org/wiki/Binfmt_misc


@@ 51,14 51,14 @@ def register(args, arch):
    # Register in binfmt_misc
    logging.info("Register qemu binfmt (" + arch_qemu + ")")
    register = "/proc/sys/fs/binfmt_misc/register"
    pmb.helpers.run.root(
    amb.helpers.run.root(
        args, ["sh", "-c", 'echo "' + code + '" > ' + register])


def unregister(args, arch):
    arch_qemu = pmb.parse.arch.alpine_to_qemu(arch)
    arch_qemu = amb.parse.arch.alpine_to_qemu(arch)
    binfmt_file = "/proc/sys/fs/binfmt_misc/qemu-" + arch_qemu
    if not os.path.exists(binfmt_file):
        return
    logging.info("Unregister qemu binfmt (" + arch_qemu + ")")
    pmb.helpers.run.root(args, ["sh", "-c", "echo -1 > " + binfmt_file])
    amb.helpers.run.root(args, ["sh", "-c", "echo -1 > " + binfmt_file])

M amb/chroot/distccd.py => amb/chroot/distccd.py +22 -22
@@ 4,9 4,9 @@ import errno
import json
import logging
import os
import pmb.chroot
import pmb.config
import pmb.chroot.apk
import amb.chroot
import amb.config
import amb.chroot.apk

""" Packages for foreign architectures (e.g. armhf) get built in chroots
    running with QEMU. While this works, it is painfully slow. So we speed it


@@ 20,7 20,7 @@ import pmb.chroot.apk

    Using the SSH server instead of running distccd directly is a security
    measure. Distccd does not authenticate its clients and would therefore
    allow any process of the host system (not related to pmbootstrap) to
    allow any process of the host system (not related to almobootstrap) to
    execute compilers in the native chroot. By modifying the compiler's options
    or sending malicious data to the compiler, it is likely that the process
    can gain remote code execution [1]. That way, a compromised, but sandboxed


@@ 35,7 35,7 @@ def init_server(args):
    Install dependencies and generate keys for the server.
    """
    # Install dependencies
    pmb.chroot.apk.install(args, ["arch-bin-masquerade", "distcc",
    amb.chroot.apk.install(args, ["arch-bin-masquerade", "distcc",
                                  "openssh-server"])

    # Config folder (nothing to do if existing)


@@ 46,8 46,8 @@ def init_server(args):

    # Generate keys
    logging.info("(native) generate distcc-sshd server keys")
    pmb.chroot.user(args, ["mkdir", "-p", dir + "/etc/ssh"])
    pmb.chroot.user(args, ["ssh-keygen", "-A", "-f", dir])
    amb.chroot.user(args, ["mkdir", "-p", dir + "/etc/ssh"])
    amb.chroot.user(args, ["ssh-keygen", "-A", "-f", dir])


def init_client(args, suffix):


@@ 55,7 55,7 @@ def init_client(args, suffix):
    Install dependencies and generate keys for the client.
    """
    # Install dependencies
    pmb.chroot.apk.install(args, ["arch-bin-masquerade", "distcc",
    amb.chroot.apk.install(args, ["arch-bin-masquerade", "distcc",
                                  "openssh-client"], suffix)

    # Public key path (nothing to do if existing)


@@ 66,9 66,9 @@ def init_client(args, suffix):

    # Generate keys
    logging.info("(" + suffix + ") generate distcc-sshd client keys")
    pmb.chroot.user(args, ["ssh-keygen", "-t", "ed25519", "-N", "",
    amb.chroot.user(args, ["ssh-keygen", "-t", "ed25519", "-N", "",
                           "-f", "/home/pmos/.ssh/id_ed25519"], suffix)
    pmb.chroot.user(args, ["cp", "/home/pmos/.ssh/id_ed25519.pub", pub],
    amb.chroot.user(args, ["cp", "/home/pmos/.ssh/id_ed25519.pub", pub],
                    suffix)




@@ 80,7 80,7 @@ def configure_authorized_keys(args, suffix):
    auth_outside = args.work + "/chroot_native/" + auth
    pub = "/home/pmos/id_ed25519.pub"
    pub_outside = args.work + "/chroot_" + suffix + pub
    pmb.helpers.run.root(args, ["cp", pub_outside, auth_outside])
    amb.helpers.run.root(args, ["cp", pub_outside, auth_outside])


def configure_cmdlist(args, arch):


@@ 95,14 95,14 @@ def configure_cmdlist(args, arch):
        for cmd in ["c++", "cc", "cpp", "g++", "gcc"]:
            cmd_full = "/usr/lib/arch-bin-masquerade/" + arch + "/" + cmd
            handle.write(cmd_full + "\n")
    pmb.chroot.root(args, ["mv", "/tmp/cmdlist", dir + "/cmdlist"])
    pmb.chroot.user(args, ["cat", dir + "/cmdlist"])
    amb.chroot.root(args, ["mv", "/tmp/cmdlist", dir + "/cmdlist"])
    amb.chroot.user(args, ["cat", dir + "/cmdlist"])


def configure_distccd_wrapper(args):
    """
    Wrap distccd in a shell script, so we can pass the compiler whitelist and
    set the verbose flag (when pmbootstrap is running with --verbose).
    set the verbose flag (when almobootstrap is running with --verbose).
    """
    dir = "/home/pmos/.distcc-sshd"
    with open(args.work + "/chroot_native/tmp/wrapper", "w") as handle:


@@ 112,9 112,9 @@ def configure_distccd_wrapper(args):
        if args.verbose:
            handle.write(" --verbose")
        handle.write(" \"$@\"\n")
    pmb.chroot.root(args, ["mv", "/tmp/wrapper", dir + "/distccd"])
    pmb.chroot.user(args, ["cat", dir + "/distccd"])
    pmb.chroot.root(args, ["chmod", "+x", dir + "/distccd"])
    amb.chroot.root(args, ["mv", "/tmp/wrapper", dir + "/distccd"])
    amb.chroot.user(args, ["cat", dir + "/distccd"])
    amb.chroot.root(args, ["chmod", "+x", dir + "/distccd"])


def configure_sshd(args):


@@ 135,8 135,8 @@ def configure_sshd(args):
    with open(args.work + "/chroot_native/tmp/cfg", "w") as handle:
        for line in config.split("\n"):
            handle.write(line.lstrip() + "\n")
    pmb.chroot.root(args, ["mv", "/tmp/cfg", dir + "/sshd_config"])
    pmb.chroot.user(args, ["cat", dir + "/sshd_config"])
    amb.chroot.root(args, ["mv", "/tmp/cfg", dir + "/sshd_config"])
    amb.chroot.user(args, ["cat", dir + "/sshd_config"])


def get_running_pid(args):


@@ 156,7 156,7 @@ def get_running_pid(args):
        os.kill(pid, 0)
    except OSError as err:
        if err.errno == errno.ESRCH:  # no such process
            pmb.helpers.run.root(args, ["rm", pidfile_outside])
            amb.helpers.run.root(args, ["rm", pidfile_outside])
            return None
    return pid



@@ 218,7 218,7 @@ def stop(args):

    parameters = get_running_parameters(args)
    logging.info("(native) stop distcc-sshd (" + parameters["arch"] + ")")
    pmb.chroot.user(args, ["kill", str(pid)])
    amb.chroot.user(args, ["kill", str(pid)])


def start(args, arch):


@@ 245,6 245,6 @@ def start(args, arch):

    # Run
    dir = "/home/pmos/.distcc-sshd"
    pmb.chroot.user(args, ["/usr/sbin/sshd", "-f", dir + "/sshd_config",
    amb.chroot.user(args, ["/usr/sbin/sshd", "-f", dir + "/sshd_config",
                           "-E", dir + "/log.txt"])
    set_running_parameters(args, arch)

M amb/chroot/init.py => amb/chroot/init.py +37 -37
@@ 5,13 5,13 @@ import os
import glob
import filecmp

import pmb.chroot
import pmb.chroot.apk_static
import pmb.config
import pmb.config.workdir
import pmb.helpers.repo
import pmb.helpers.run
import pmb.parse.arch
import amb.chroot
import amb.chroot.apk_static
import amb.config
import amb.config.workdir
import amb.helpers.repo
import amb.helpers.run
import amb.parse.arch


def copy_resolv_conf(args, suffix="native"):


@@ 25,33 25,33 @@ def copy_resolv_conf(args, suffix="native"):
    chroot = f"{args.work}/chroot_{suffix}{host}"
    if os.path.exists(host):
        if not os.path.exists(chroot) or not filecmp.cmp(host, chroot):
            pmb.helpers.run.root(args, ["cp", host, chroot])
            amb.helpers.run.root(args, ["cp", host, chroot])
    else:
        pmb.helpers.run.root(args, ["touch", chroot])
        amb.helpers.run.root(args, ["touch", chroot])


def mark_in_chroot(args, suffix="native"):
    """
    Touch a flag so we can know when we're running in chroot (and
    don't accidentally flash partitions on our host). This marker
    gets removed in pmb.chroot.shutdown (pmbootstrap shutdown).
    gets removed in amb.chroot.shutdown (almobootstrap shutdown).
    """
    in_chroot_file = f"{args.work}/chroot_{suffix}/in-pmbootstrap"
    in_chroot_file = f"{args.work}/chroot_{suffix}/in-almobootstrap"
    if not os.path.exists(in_chroot_file):
        pmb.helpers.run.root(args, ["touch", in_chroot_file])
        amb.helpers.run.root(args, ["touch", in_chroot_file])


def setup_qemu_emulation(args, suffix):
    arch = pmb.parse.arch.from_chroot_suffix(args, suffix)
    if not pmb.parse.arch.cpu_emulation_required(arch):
    arch = amb.parse.arch.from_chroot_suffix(args, suffix)
    if not amb.parse.arch.cpu_emulation_required(arch):
        return

    chroot = f"{args.work}/chroot_{suffix}"
    arch_qemu = pmb.parse.arch.alpine_to_qemu(arch)
    arch_qemu = amb.parse.arch.alpine_to_qemu(arch)

    # mount --bind the qemu-user binary
    pmb.chroot.binfmt.register(args, arch)
    pmb.helpers.mount.bind_file(args, f"{args.work}/chroot_native"
    amb.chroot.binfmt.register(args, arch)
    amb.helpers.mount.bind_file(args, f"{args.work}/chroot_native"
                                      f"/usr/bin/qemu-{arch_qemu}",
                                f"{chroot}/usr/bin/qemu-{arch_qemu}-static",
                                create_folders=True)


@@ 59,7 59,7 @@ def setup_qemu_emulation(args, suffix):

def init_keys(args):
    """
    All Alpine and postmarketOS repository keys are shipped with pmbootstrap.
    All Alpine and postmarketOS repository keys are shipped with almobootstrap.
    Copy them into $WORK/config_apk_keys, which gets mounted inside the various
    chroots as /etc/apk/keys.



@@ 67,63 67,63 @@ def init_keys(args):
    files of binary repositories even though alpine-keys/postmarketos-keys are
    not installed yet.
    """
    for key in glob.glob(f"{pmb.config.apk_keys_path}/*.pub"):
    for key in glob.glob(f"{amb.config.apk_keys_path}/*.pub"):
        target = f"{args.work}/config_apk_keys/{os.path.basename(key)}"
        if not os.path.exists(target):
            # Copy as root, so the resulting files in chroots are owned by root
            pmb.helpers.run.root(args, ["cp", key, target])
            amb.helpers.run.root(args, ["cp", key, target])


def init(args, suffix="native"):
    # When already initialized: just prepare the chroot
    chroot = f"{args.work}/chroot_{suffix}"
    arch = pmb.parse.arch.from_chroot_suffix(args, suffix)
    arch = amb.parse.arch.from_chroot_suffix(args, suffix)

    pmb.chroot.mount(args, suffix)
    amb.chroot.mount(args, suffix)
    setup_qemu_emulation(args, suffix)
    mark_in_chroot(args, suffix)
    if os.path.islink(f"{chroot}/bin/sh"):
        pmb.config.workdir.chroot_check_channel(args, suffix)
        amb.config.workdir.chroot_check_channel(args, suffix)
        copy_resolv_conf(args, suffix)
        pmb.chroot.apk.update_repository_list(args, suffix)
        amb.chroot.apk.update_repository_list(args, suffix)
        return

    # Require apk-tools-static
    pmb.chroot.apk_static.init(args)
    amb.chroot.apk_static.init(args)

    logging.info(f"({suffix}) install alpine-base")

    # Initialize cache
    apk_cache = f"{args.work}/cache_apk_{arch}"
    pmb.helpers.run.root(args, ["ln", "-s", "-f", "/var/cache/apk",
    amb.helpers.run.root(args, ["ln", "-s", "-f", "/var/cache/apk",
                                f"{chroot}/etc/apk/cache"])

    # Initialize /etc/apk/keys/, resolv.conf, repositories
    init_keys(args)
    copy_resolv_conf(args, suffix)
    pmb.chroot.apk.update_repository_list(args, suffix)
    amb.chroot.apk.update_repository_list(args, suffix)

    pmb.config.workdir.chroot_save_init(args, suffix)
    amb.config.workdir.chroot_save_init(args, suffix)

    # Install alpine-base
    pmb.helpers.repo.update(args, arch)
    pmb.chroot.apk_static.run(args, ["--root", chroot,
    amb.helpers.repo.update(args, arch)
    amb.chroot.apk_static.run(args, ["--root", chroot,
                                     "--cache-dir", apk_cache,
                                     "--initdb", "--arch", arch,
                                     "add", "alpine-base"])

    # Building chroots: create "pmos" user, add symlinks to /home/pmos
    if not suffix.startswith("rootfs_"):
        pmb.chroot.root(args, ["adduser", "-D", "pmos", "-u",
                               pmb.config.chroot_uid_user],
        amb.chroot.root(args, ["adduser", "-D", "pmos", "-u",
                               amb.config.chroot_uid_user],
                        suffix, auto_init=False)

        # Create the links (with subfolders if necessary)
        for target, link_name in pmb.config.chroot_home_symlinks.items():
        for target, link_name in amb.config.chroot_home_symlinks.items():
            link_dir = os.path.dirname(link_name)
            if not os.path.exists(f"{chroot}{link_dir}"):
                pmb.chroot.user(args, ["mkdir", "-p", link_dir], suffix)
                amb.chroot.user(args, ["mkdir", "-p", link_dir], suffix)
            if not os.path.exists(f"{chroot}{target}"):
                pmb.chroot.root(args, ["mkdir", "-p", target], suffix)
            pmb.chroot.user(args, ["ln", "-s", target, link_name], suffix)
            pmb.chroot.root(args, ["chown", "pmos:pmos", target], suffix)
                amb.chroot.root(args, ["mkdir", "-p", target], suffix)
            amb.chroot.user(args, ["ln", "-s", target, link_name], suffix)
            amb.chroot.root(args, ["chown", "pmos:pmos", target], suffix)

M amb/chroot/initfs.py => amb/chroot/initfs.py +24 -24
@@ 2,29 2,29 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import logging
import pmb.chroot.initfs_hooks
import pmb.chroot.other
import pmb.chroot.apk
import pmb.config.pmaports
import pmb.helpers.cli
import amb.chroot.initfs_hooks
import amb.chroot.other
import amb.chroot.apk
import amb.config.almoports
import amb.helpers.cli


def build(args, flavor, suffix):
    # Update mkinitfs and hooks
    pmb.chroot.apk.install(args, ["postmarketos-mkinitfs"], suffix)
    pmb.chroot.initfs_hooks.update(args, suffix)
    pmaports_cfg = pmb.config.pmaports.read_config(args)
    amb.chroot.apk.install(args, ["postmarketos-mkinitfs"], suffix)
    amb.chroot.initfs_hooks.update(args, suffix)
    almoports_cfg = amb.config.almoports.read_config(args)

    # Call mkinitfs
    logging.info(f"({suffix}) mkinitfs {flavor}")
    if pmaports_cfg.get("supported_mkinitfs_without_flavors", False):
        pmb.chroot.root(args, ["mkinitfs"], suffix)
    if almoports_cfg.get("supported_mkinitfs_without_flavors", False):
        amb.chroot.root(args, ["mkinitfs"], suffix)
    else:
        release_file = (f"{args.work}/chroot_{suffix}/usr/share/kernel/"
                        f"{flavor}/kernel.release")
        with open(release_file, "r") as handle:
            release = handle.read().rstrip()
            pmb.chroot.root(args, ["mkinitfs", "-o",
            amb.chroot.root(args, ["mkinitfs", "-o",
                                   f"/boot/initramfs-{flavor}", release],
                            suffix)



@@ 37,8 37,8 @@ def extract(args, flavor, suffix, extra=False):
    # Extraction folder
    inside = "/tmp/initfs-extracted"

    pmaports_cfg = pmb.config.pmaports.read_config(args)
    if pmaports_cfg.get("supported_mkinitfs_without_flavors", False):
    almoports_cfg = amb.config.almoports.read_config(args)
    if almoports_cfg.get("supported_mkinitfs_without_flavors", False):
        initfs_file = "/boot/initramfs"
    else:
        initfs_file = f"/boot/initramfs-${flavor}"


@@ 48,14 48,14 @@ def extract(args, flavor, suffix, extra=False):

    outside = f"{args.work}/chroot_{suffix}{inside}"
    if os.path.exists(outside):
        if not pmb.helpers.cli.confirm(args, f"Extraction folder {outside}"
        if not amb.helpers.cli.confirm(args, f"Extraction folder {outside}"
                                       " already exists."
                                       " Do you want to overwrite it?"):
            raise RuntimeError("Aborted!")
        pmb.chroot.root(args, ["rm", "-r", inside], suffix)
        amb.chroot.root(args, ["rm", "-r", inside], suffix)

    # Extraction script (because passing a file to stdin is not allowed
    # in pmbootstrap's chroot/shell functions for security reasons)
    # in almobootstrap's chroot/shell functions for security reasons)
    with open(f"{args.work}/chroot_{suffix}/tmp/_extract.sh", "w") as handle:
        handle.write(
            "#!/bin/sh\n"


@@ 70,7 70,7 @@ def extract(args, flavor, suffix, extra=False):
                ["rm", "/tmp/_extract.sh", f"{inside}/_initfs"]
                ]
    for command in commands:
        pmb.chroot.root(args, command, suffix)
        amb.chroot.root(args, command, suffix)

    # Return outside path for logging
    return outside


@@ 81,14 81,14 @@ def ls(args, flavor, suffix, extra=False):
    if extra:
        tmp = "/tmp/initfs-extra-extracted"
    extract(args, flavor, suffix, extra)
    pmb.chroot.root(args, ["ls", "-lahR", "."], suffix, tmp, "stdout")
    pmb.chroot.root(args, ["rm", "-r", tmp], suffix)
    amb.chroot.root(args, ["ls", "-lahR", "."], suffix, tmp, "stdout")
    amb.chroot.root(args, ["rm", "-r", tmp], suffix)


def frontend(args):
    # Find the appropriate kernel flavor
    suffix = f"rootfs_{args.device}"
    flavor = pmb.chroot.other.kernel_flavor_installed(args, suffix)
    flavor = amb.chroot.other.kernel_flavor_installed(args, suffix)

    # Handle initfs actions
    action = args.action_initfs


@@ 107,16 107,16 @@ def frontend(args):

    # Handle hook actions
    elif action == "hook_ls":
        pmb.chroot.initfs_hooks.ls(args, suffix)
        amb.chroot.initfs_hooks.ls(args, suffix)
    else:
        if action == "hook_add":
            pmb.chroot.initfs_hooks.add(args, args.hook, suffix)
            amb.chroot.initfs_hooks.add(args, args.hook, suffix)
        elif action == "hook_del":
            pmb.chroot.initfs_hooks.delete(args, args.hook, suffix)
            amb.chroot.initfs_hooks.delete(args, args.hook, suffix)

        # Rebuild the initfs after adding/removing a hook
        build(args, flavor, suffix)

    if action in ["ls", "extract"]:
        link = "https://wiki.postmarketos.org/wiki/Initramfs_development"
        link = "https://wiki.almolinux.org/wiki/Initramfs_development"
        logging.info(f"See also: <{link}>")

M amb/chroot/initfs_hooks.py => amb/chroot/initfs_hooks.py +11 -11
@@ 4,14 4,14 @@ import os
import glob
import logging

import pmb.config
import pmb.chroot.apk
import amb.config
import amb.chroot.apk


def list_chroot(args, suffix, remove_prefix=True):
    ret = []
    prefix = pmb.config.initfs_hook_prefix
    for pkgname in pmb.chroot.apk.installed(args, suffix).keys():
    prefix = amb.config.initfs_hook_prefix
    for pkgname in amb.chroot.apk.installed(args, suffix).keys():
        if pkgname.startswith(prefix):
            if remove_prefix:
                ret.append(pkgname[len(prefix):])


@@ 22,7 22,7 @@ def list_chroot(args, suffix, remove_prefix=True):

def list_aports(args):
    ret = []
    prefix = pmb.config.initfs_hook_prefix
    prefix = amb.config.initfs_hook_prefix
    for path in glob.glob(f"{args.aports}/*/{prefix}*"):
        ret.append(os.path.basename(path)[len(prefix):])
    return ret


@@ 40,21 40,21 @@ def ls(args, suffix):
def add(args, hook, suffix):
    if hook not in list_aports(args):
        raise RuntimeError("Invalid hook name!"
                           " Run 'pmbootstrap initfs hook_ls'"
                           " Run 'almobootstrap initfs hook_ls'"
                           " to get a list of all hooks.")
    prefix = pmb.config.initfs_hook_prefix
    pmb.chroot.apk.install(args, [f"{prefix}{hook}"], suffix)
    prefix = amb.config.initfs_hook_prefix
    amb.chroot.apk.install(args, [f"{prefix}{hook}"], suffix)


def delete(args, hook, suffix):
    if hook not in list_chroot(args, suffix):
        raise RuntimeError("There is no such hook installed!")
    prefix = pmb.config.initfs_hook_prefix
    pmb.chroot.root(args, ["apk", "del", f"{prefix}{hook}"], suffix)
    prefix = amb.config.initfs_hook_prefix
    amb.chroot.root(args, ["apk", "del", f"{prefix}{hook}"], suffix)


def update(args, suffix):
    """
    Rebuild and update all hooks that are out of date
    """
    pmb.chroot.apk.install(args, list_chroot(args, suffix, False), suffix)
    amb.chroot.apk.install(args, list_chroot(args, suffix, False), suffix)

M amb/chroot/mount.py => amb/chroot/mount.py +18 -18
@@ 3,9 3,9 @@
import glob
import logging
import os
import pmb.config
import pmb.parse
import pmb.helpers.mount
import amb.config
import amb.parse
import amb.helpers.mount


def create_device_nodes(args, suffix):


@@ 16,10 16,10 @@ def create_device_nodes(args, suffix):
        chroot = args.work + "/chroot_" + suffix

        # Create all device nodes as specified in the config
        for dev in pmb.config.chroot_device_nodes:
        for dev in amb.config.chroot_device_nodes:
            path = chroot + "/dev/" + str(dev[4])
            if not os.path.exists(path):
                pmb.helpers.run.root(args, ["mknod",
                amb.helpers.run.root(args, ["mknod",
                                            "-m", str(dev[0]),  # permissions
                                            path,  # name
                                            str(dev[1]),  # type


@@ 28,7 28,7 @@ def create_device_nodes(args, suffix):
                                            ])

        # Verify major and minor numbers of created nodes
        for dev in pmb.config.chroot_device_nodes:
        for dev in amb.config.chroot_device_nodes:
            path = chroot + "/dev/" + str(dev[4])
            stat_result = os.stat(path)
            rdev = stat_result.st_rdev


@@ 56,24 56,24 @@ def mount_dev_tmpfs(args, suffix="native"):
    """
    # Do nothing when it is already mounted
    dev = args.work + "/chroot_" + suffix + "/dev"
    if pmb.helpers.mount.ismount(dev):
    if amb.helpers.mount.ismount(dev):
        return

    # Create the $chroot/dev folder and mount tmpfs there
    pmb.helpers.run.root(args, ["mkdir", "-p", dev])
    pmb.helpers.run.root(args, ["mount", "-t", "tmpfs",
    amb.helpers.run.root(args, ["mkdir", "-p", dev])
    amb.helpers.run.root(args, ["mount", "-t", "tmpfs",
                                "-o", "size=1M,noexec,dev",
                                "tmpfs", dev])

    # Create pts, shm folders and device nodes
    pmb.helpers.run.root(args, ["mkdir", "-p", dev + "/pts", dev + "/shm"])
    pmb.helpers.run.root(args, ["mount", "-t", "tmpfs",
    amb.helpers.run.root(args, ["mkdir", "-p", dev + "/pts", dev + "/shm"])
    amb.helpers.run.root(args, ["mount", "-t", "tmpfs",
                                "-o", "nodev,nosuid,noexec",
                                "tmpfs", dev + "/shm"])
    create_device_nodes(args, suffix)

    # Setup /dev/fd as a symlink
    pmb.helpers.run.root(args, ["ln", "-sf", "/proc/self/fd", f"{dev}/"])
    amb.helpers.run.root(args, ["ln", "-sf", "/proc/self/fd", f"{dev}/"])


def mount(args, suffix="native"):


@@ 81,10 81,10 @@ def mount(args, suffix="native"):
    mount_dev_tmpfs(args, suffix)

    # Get all mountpoints
    arch = pmb.parse.arch.from_chroot_suffix(args, suffix)
    channel = pmb.config.pmaports.read_config(args)["channel"]
    arch = amb.parse.arch.from_chroot_suffix(args, suffix)
    channel = amb.config.almoports.read_config(args)["channel"]
    mountpoints = {}
    for source, target in pmb.config.chroot_mount_bind.items():
    for source, target in amb.config.chroot_mount_bind.items():
        source = source.replace("$WORK", args.work)
        source = source.replace("$ARCH", arch)
        source = source.replace("$CHANNEL", channel)


@@ 93,16 93,16 @@ def mount(args, suffix="native"):
    # Mount if necessary
    for source, target in mountpoints.items():
        target_full = args.work + "/chroot_" + suffix + target
        pmb.helpers.mount.bind(args, source, target_full)
        amb.helpers.mount.bind(args, source, target_full)


def mount_native_into_foreign(args, suffix):
    source = args.work + "/chroot_native"
    target = args.work + "/chroot_" + suffix + "/native"
    pmb.helpers.mount.bind(args, source, target)
    amb.helpers.mount.bind(args, source, target)

    musl = os.path.basename(glob.glob(source + "/lib/ld-musl-*.so.1")[0])
    musl_link = args.work + "/chroot_" + suffix + "/lib/" + musl
    if not os.path.lexists(musl_link):
        pmb.helpers.run.root(args, ["ln", "-s", "/native/lib/" + musl,
        amb.helpers.run.root(args, ["ln", "-s", "/native/lib/" + musl,
                                    musl_link])

M amb/chroot/other.py => amb/chroot/other.py +10 -10
@@ 3,8 3,8 @@
import os
import glob
import logging
import pmb.chroot.apk
import pmb.install
import amb.chroot.apk
import amb.install


def kernel_flavor_installed(args, suffix, autoinstall=True):


@@ 21,8 21,8 @@ def kernel_flavor_installed(args, suffix, autoinstall=True):
    # Automatically install the selected kernel
    if autoinstall:
        packages = ([f"device-{args.device}"] +
                    pmb.install.get_kernel_package(args, args.device))
        pmb.chroot.apk.install(args, packages, suffix)
                    amb.install.get_kernel_package(args, args.device))
        amb.chroot.apk.install(args, packages, suffix)

    pattern = f"{args.work}/chroot_{suffix}/usr/share/kernel/*"
    glob_result = glob.glob(pattern)


@@ 40,8 40,8 @@ def tempfolder(args, path, suffix="native"):
    :returns: the path
    """
    if os.path.exists(args.work + "/chroot_" + suffix + path):
        pmb.chroot.root(args, ["rm", "-r", path])
    pmb.chroot.user(args, ["mkdir", "-p", path])
        amb.chroot.root(args, ["rm", "-r", path])
    amb.chroot.user(args, ["mkdir", "-p", path])
    return path




@@ 56,7 56,7 @@ def copy_xauthority(args):
        raise RuntimeError("Your $DISPLAY variable is not set. If you have an"
                           " X11 server running as your current user, try"
                           " 'export DISPLAY=:0' and run your last"
                           " pmbootstrap command again.")
                           " almobootstrap command again.")

    # Check $XAUTHORITY
    original = os.environ.get("XAUTHORITY")


@@ 70,6 70,6 @@ def copy_xauthority(args):
    # Copy to chroot and chown
    copy = args.work + "/chroot_native/home/pmos/.Xauthority"
    if os.path.exists(copy):
        pmb.helpers.run.root(args, ["rm", copy])
    pmb.helpers.run.root(args, ["cp", original, copy])
    pmb.chroot.root(args, ["chown", "pmos:pmos", "/home/pmos/.Xauthority"])
        amb.helpers.run.root(args, ["rm", copy])
    amb.helpers.run.root(args, ["cp", original, copy])
    amb.chroot.root(args, ["chown", "pmos:pmos", "/home/pmos/.Xauthority"])

M amb/chroot/root.py => amb/chroot/root.py +13 -13
@@ 3,11 3,11 @@
import os
import shutil

import pmb.config
import pmb.chroot
import pmb.chroot.binfmt
import pmb.helpers.run
import pmb.helpers.run_core
import amb.config
import amb.chroot
import amb.chroot.binfmt
import amb.helpers.run
import amb.helpers.run_core


def executables_absolute_path():


@@ 16,7 16,7 @@ def executables_absolute_path():
    """
    ret = {}
    for binary in ["sh", "chroot"]:
        path = shutil.which(binary, path=pmb.config.chroot_host_path)
        path = shutil.which(binary, path=amb.config.chroot_host_path)
        if not path:
            raise RuntimeError(f"Could not find the '{binary}'"
                               " executable. Make sure that it is in"


@@ 35,7 35,7 @@ def root(args, cmd, suffix="native", working_dir="/", output="log",
                {"JOBS": "5"}
    :param auto_init: automatically initialize the chroot

    See pmb.helpers.run_core.core() for a detailed description of all other
    See amb.helpers.run_core.core() for a detailed description of all other
    arguments and the return value.
    """
    # Initialize chroot


@@ 43,7 43,7 @@ def root(args, cmd, suffix="native", working_dir="/", output="log",
    if not auto_init and not os.path.islink(f"{chroot}/bin/sh"):
        raise RuntimeError(f"Chroot does not exist: {chroot}")
    if auto_init:
        pmb.chroot.init(args, suffix)
        amb.chroot.init(args, suffix)

    # Readable log message (without all the escaping)
    msg = f"({suffix}) % "


@@ 58,7 58,7 @@ def root(args, cmd, suffix="native", working_dir="/", output="log",
               "HISTFILE": "~/.ash_history",
               "HOME": "/root",
               "LANG": "UTF-8",
               "PATH": pmb.config.chroot_path,
               "PATH": amb.config.chroot_path,
               "PYTHONUNBUFFERED": "1",
               "SHELL": "/bin/ash",
               "TERM": "xterm"}


@@ 77,11 77,11 @@ def root(args, cmd, suffix="native", working_dir="/", output="log",
    # cmd_sudo: ["sudo", "env", "-i", "sh", "-c", "PATH=... /sbin/chroot ..."]
    executables = executables_absolute_path()
    cmd_chroot = [executables["chroot"], chroot, "/bin/sh", "-c",
                  pmb.helpers.run.flat_cmd(cmd, working_dir)]
    cmd_sudo = pmb.config.sudo([
                  amb.helpers.run.flat_cmd(cmd, working_dir)]
    cmd_sudo = amb.config.sudo([
        "env", "-i", executables["sh"], "-c",
        pmb.helpers.run.flat_cmd(cmd_chroot, env=env_all)]
        amb.helpers.run.flat_cmd(cmd_chroot, env=env_all)]
    )
    return pmb.helpers.run_core.core(args, msg, cmd_sudo, None, output,
    return amb.helpers.run_core.core(args, msg, cmd_sudo, None, output,
                                     output_return, check, True,
                                     disable_timeout)

M amb/chroot/shutdown.py => amb/chroot/shutdown.py +24 -24
@@ 6,11 6,11 @@ import os
import socket
from contextlib import closing

import pmb.chroot
import pmb.chroot.distccd
import pmb.helpers.mount
import pmb.install.losetup
import pmb.parse.arch
import amb.chroot
import amb.chroot.distccd
import amb.helpers.mount
import amb.install.losetup
import amb.parse.arch


def kill_adb(args):


@@ 20,17 20,17 @@ def kill_adb(args):
    port = 5038
    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
        if sock.connect_ex(("127.0.0.1", port)) == 0:
            pmb.chroot.root(args, ["adb", "-P", str(port), "kill-server"])
            amb.chroot.root(args, ["adb", "-P", str(port), "kill-server"])


def shutdown_cryptsetup_device(args, name):
    """
    :param name: cryptsetup device name, usually "pm_crypt" in pmbootstrap
    :param name: cryptsetup device name, usually "pm_crypt" in almobootstrap
    """
    if not os.path.exists(args.work + "/chroot_native/dev/mapper/" + name):
        return
    pmb.chroot.apk.install(args, ["cryptsetup"])
    status = pmb.chroot.root(args, ["cryptsetup", "status", name],
    amb.chroot.apk.install(args, ["cryptsetup"])
    status = amb.chroot.root(args, ["cryptsetup", "status", name],
                             output_return=True, check=False)
    if not status:
        logging.warning("WARNING: Failed to run cryptsetup to get the status"


@@ 39,57 39,57 @@ def shutdown_cryptsetup_device(args, name):
        return

    if status.startswith("/dev/mapper/" + name + " is active."):
        pmb.chroot.root(args, ["cryptsetup", "luksClose", name])
        amb.chroot.root(args, ["cryptsetup", "luksClose", name])
    elif status.startswith("/dev/mapper/" + name + " is inactive."):
        # When "cryptsetup status" fails, the device is not mounted and we
        # have a left over file (#83)
        pmb.chroot.root(args, ["rm", "/dev/mapper/" + name])
        amb.chroot.root(args, ["rm", "/dev/mapper/" + name])
    else:
        raise RuntimeError("Failed to parse 'cryptsetup status' output!")


def shutdown(args, only_install_related=False):
    pmb.chroot.distccd.stop(args)
    amb.chroot.distccd.stop(args)

    # Stop adb server
    kill_adb(args)

    # Umount installation-related paths (order is important!)
    pmb.helpers.mount.umount_all(args, args.work +
    amb.helpers.mount.umount_all(args, args.work +
                                 "/chroot_native/mnt/install")
    shutdown_cryptsetup_device(args, "pm_crypt")

    # Umount all losetup mounted images
    chroot = args.work + "/chroot_native"
    if pmb.helpers.mount.ismount(chroot + "/dev/loop-control"):
    if amb.helpers.mount.ismount(chroot + "/dev/loop-control"):
        pattern = chroot + "/home/pmos/rootfs/*.img"
        for path_outside in glob.glob(pattern):
            path = path_outside[len(chroot):]
            pmb.install.losetup.umount(args, path, auto_init=False)
            amb.install.losetup.umount(args, path, auto_init=False)

    # Umount device rootfs and installer chroots
    for prefix in ["rootfs", "installer"]:
        path = f"{args.work}/chroot_{prefix}_{args.device}"
        if os.path.exists(path):
            pmb.helpers.mount.umount_all(args, path)
            amb.helpers.mount.umount_all(args, path)

    # Remove "in-pmbootstrap" marker from all chroots. This marker indicates
    # that pmbootstrap has set up all mount points etc. to run programs inside
    # Remove "in-almobootstrap" marker from all chroots. This marker indicates
    # that almobootstrap has set up all mount points etc. to run programs inside
    # the chroots, but we want it gone afterwards (e.g. when the chroot
    # contents get copied to a rootfs / installer image, or if creating an
    # android recovery zip from its contents).
    for marker in glob.glob(f"{args.work}/chroot_*/in-pmbootstrap"):
        pmb.helpers.run.root(args, ["rm", marker])
    for marker in glob.glob(f"{args.work}/chroot_*/in-almobootstrap"):
        amb.helpers.run.root(args, ["rm", marker])

    if not only_install_related:
        # Umount all folders inside args.work
        # The folders are explicitly iterated over, so folders symlinked inside
        # args.work get umounted as well (used in test_pkgrel_bump.py, #1595)
        for path in glob.glob(args.work + "/*"):
            pmb.helpers.mount.umount_all(args, path)
            amb.helpers.mount.umount_all(args, path)

        # Clean up the rest
        for arch in pmb.config.build_device_architectures:
            if pmb.parse.arch.cpu_emulation_required(arch):
                pmb.chroot.binfmt.unregister(args, arch)
        for arch in amb.config.build_device_architectures:
            if amb.parse.arch.cpu_emulation_required(arch):
                amb.chroot.binfmt.unregister(args, arch)
        logging.debug("Shutdown complete")

M amb/chroot/user.py => amb/chroot/user.py +6 -6
@@ 1,7 1,7 @@
# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import pmb.chroot.root
import pmb.helpers.run
import amb.chroot.root
import amb.helpers.run


def user(args, cmd, suffix="native", working_dir="/", output="log",


@@ 15,15 15,15 @@ def user(args, cmd, suffix="native", working_dir="/", output="log",
                {"JOBS": "5"}
    :param auto_init: automatically initialize the chroot

    See pmb.helpers.run_core.core() for a detailed description of all other
    See amb.helpers.run_core.core() for a detailed description of all other
    arguments and the return value.
    """
    if "HOME" not in env:
        env["HOME"] = "/home/pmos"

    flat_cmd = pmb.helpers.run.flat_cmd(cmd, env=env)
    flat_cmd = amb.helpers.run.flat_cmd(cmd, env=env)
    cmd = ["busybox", "su", "pmos", "-c", flat_cmd]
    return pmb.chroot.root(args, cmd, suffix, working_dir, output,
    return amb.chroot.root(args, cmd, suffix, working_dir, output,
                           output_return, check, {}, auto_init)




@@ 34,6 34,6 @@ def exists(args, username, suffix="native"):
    :param username: User name
    :returns: bool
    """
    output = pmb.chroot.root(args, ["getent", "passwd", username],
    output = amb.chroot.root(args, ["getent", "passwd", username],
                             suffix, output_return=True, check=False)
    return len(output) > 0

M amb/chroot/zap.py => amb/chroot/zap.py +27 -27
@@ 5,12 5,12 @@ import logging
import math
import os

import pmb.chroot
import pmb.config.pmaports
import pmb.config.workdir
import pmb.helpers.pmaports
import pmb.helpers.run
import pmb.parse.apkindex
import amb.chroot
import amb.config.almoports
import amb.config.workdir
import amb.helpers.almoports
import amb.helpers.run
import amb.parse.apkindex


def zap(args, confirm=True, dry=False, pkgs_local=False, http=False,


@@ 31,14 31,14 @@ def zap(args, confirm=True, dry=False, pkgs_local=False, http=False,
    :param rust: Remove rust related caches
    :param netboot: Remove images for netboot

    NOTE: This function gets called in pmb/config/init.py, with only args.work
    NOTE: This function gets called in amb/config/init.py, with only args.work
    and args.device set!
    """
    # Get current work folder size
    if not dry:
        pmb.chroot.shutdown(args)
        amb.chroot.shutdown(args)
        logging.debug("Calculate work folder size")
        size_old = pmb.helpers.other.folder_size(args, args.work)
        size_old = amb.helpers.other.folder_size(args, args.work)

    # Delete packages with a different version compared to aports,
    # then re-index


@@ 49,7 49,7 @@ def zap(args, confirm=True, dry=False, pkgs_local=False, http=False,
    if pkgs_online_mismatch:
        zap_pkgs_online_mismatch(args, confirm, dry)

    pmb.chroot.shutdown(args)
    amb.chroot.shutdown(args)

    # Deletion patterns for folders inside args.work
    patterns = [


@@ 75,41 75,41 @@ def zap(args, confirm=True, dry=False, pkgs_local=False, http=False,
        matches = glob.glob(pattern)
        for match in matches:
            if (not confirm or
                    pmb.helpers.cli.confirm(args, f"Remove {match}?")):
                    amb.helpers.cli.confirm(args, f"Remove {match}?")):
                logging.info(f"% rm -rf {match}")
                if not dry:
                    pmb.helpers.run.root(args, ["rm", "-rf", match])
                    amb.helpers.run.root(args, ["rm", "-rf", match])

    # Remove config init dates for deleted chroots
    pmb.config.workdir.clean(args)
    amb.config.workdir.clean(args)

    # Chroots were zapped, so no repo lists exist anymore
    pmb.helpers.other.cache["apk_repository_list_updated"].clear()
    amb.helpers.other.cache["apk_repository_list_updated"].clear()

    # Print amount of cleaned up space
    if dry:
        logging.info("Dry run: nothing has been deleted")
    else:
        size_new = pmb.helpers.other.folder_size(args, args.work)
        size_new = amb.helpers.other.folder_size(args, args.work)
        mb = (size_old - size_new) / 1024
        logging.info(f"Cleared up ~{math.ceil(mb)} MB of space")


def zap_pkgs_local_mismatch(args, confirm=True, dry=False):
    channel = pmb.config.pmaports.read_config(args)["channel"]
    channel = amb.config.almoports.read_config(args)["channel"]
    if not os.path.exists(f"{args.work}/packages/{channel}"):
        return

    question = "Remove binary packages that are newer than the corresponding" \
               f" pmaports (channel '{channel}')?"
    if confirm and not pmb.helpers.cli.confirm(args, question):
               f" almoports (channel '{channel}')?"
    if confirm and not amb.helpers.cli.confirm(args, question):
        return

    reindex = False
    pattern = f"{args.work}/packages/{channel}/*/APKINDEX.tar.gz"
    for apkindex_path in glob.glob(pattern):
        # Delete packages without same version in aports
        blocks = pmb.parse.apkindex.parse_blocks(apkindex_path)
        blocks = amb.parse.apkindex.parse_blocks(apkindex_path)
        for block in blocks:
            pkgname = block["pkgname"]
            origin = block["origin"]


@@ 125,27 125,27 @@ def zap_pkgs_local_mismatch(args, confirm=True, dry=False):
                continue

            # Aport path
            aport_path = pmb.helpers.pmaports.find(args, origin, False)
            aport_path = amb.helpers.almoports.find(args, origin, False)
            if not aport_path:
                logging.info(f"% rm {apk_path_short}"
                             f" ({origin} aport not found)")
                if not dry:
                    pmb.helpers.run.root(args, ["rm", apk_path])
                    amb.helpers.run.root(args, ["rm", apk_path])
                    reindex = True
                continue

            # Clear out any binary apks that do not match what is in aports
            apkbuild = pmb.parse.apkbuild(f"{aport_path}/APKBUILD")
            apkbuild = amb.parse.apkbuild(f"{aport_path}/APKBUILD")
            version_aport = f"{apkbuild['pkgver']}-r{apkbuild['pkgrel']}"
            if version != version_aport:
                logging.info(f"% rm {apk_path_short}"
                             f" ({origin} aport: {version_aport})")
                if not dry:
                    pmb.helpers.run.root(args, ["rm", apk_path])
                    amb.helpers.run.root(args, ["rm", apk_path])
                    reindex = True

    if reindex:
        pmb.build.other.index_repo(args)
        amb.build.other.index_repo(args)


def zap_pkgs_online_mismatch(args, confirm=True, dry=False):


@@ 153,7 153,7 @@ def zap_pkgs_online_mismatch(args, confirm=True, dry=False):
    paths = glob.glob(f"{args.work}/cache_apk_*")
    if not len(paths):
        return
    if (confirm and not pmb.helpers.cli.confirm(args,
    if (confirm and not amb.helpers.cli.confirm(args,
                                                "Remove outdated"
                                                " binary packages?")):
        return


@@ 162,10 162,10 @@ def zap_pkgs_online_mismatch(args, confirm=True, dry=False):
    for path in paths:
        arch = os.path.basename(path).split("_", 2)[2]
        suffix = f"buildroot_{arch}"
        if arch == pmb.config.arch_native:
        if arch == amb.config.arch_native:
            suffix = "native"

        # Clean the cache with apk
        logging.info(f"({suffix}) apk -v cache clean")
        if not dry:
            pmb.chroot.root(args, ["apk", "-v", "cache", "clean"], suffix)
            amb.chroot.root(args, ["apk", "-v", "cache", "clean"], suffix)

M amb/ci/__init__.py => amb/ci/__init__.py +24 -24
@@ 5,30 5,30 @@ import glob
import logging
import os
import shlex
import pmb.chroot
import pmb.helpers.cli
import amb.chroot
import amb.helpers.cli


def get_ci_scripts(topdir):
    """ Find 'pmbootstrap ci'-compatible scripts inside a git repository, and
    """ Find 'almobootstrap ci'-compatible scripts inside a git repository, and
        parse their metadata (description, options). The reference is at:
        https://postmarketos.org/pmb-ci
        https://almolinux.org/amb-ci
        :param topdir: top directory of the git repository, get it with:
                       pmb.helpers.git.get_topdir()
                       amb.helpers.git.get_topdir()
        :returns: a dict of CI scripts found in the git repository, e.g.
                  {"ruff": {"description": "lint all python scripts",
                              "options": []},
                   ...} """
    ret = {}
    for script in glob.glob(f"{topdir}/.ci/*.sh"):
        is_pmb_ci_script = False
        is_amb_ci_script = False
        description = ""
        options = []

        with open(script) as handle:
            for line in handle:
                if line.startswith("# https://postmarketos.org/pmb-ci"):
                    is_pmb_ci_script = True
                if line.startswith("# https://almolinux.org/amb-ci"):
                    is_amb_ci_script = True
                elif line.startswith("# Description: "):
                    description = line.split(": ", 1)[1].rstrip()
                elif line.startswith("# Options: "):


@@ 37,7 37,7 @@ def get_ci_scripts(topdir):
                    # Stop parsing after the block of comments on top
                    break

        if not is_pmb_ci_script:
        if not is_amb_ci_script:
            continue

        if not description:


@@ 45,9 45,9 @@ def get_ci_scripts(topdir):
            exit(1)

        for option in options:
            if option not in pmb.config.ci_valid_options:
            if option not in amb.config.ci_valid_options:
                raise RuntimeError(f"{script}: unsupported option '{option}'."
                                   " Typo in script or pmbootstrap too old?")
                                   " Typo in script or almobootstrap too old?")

        short_name = os.path.basename(script).split(".", -1)[0]
        ret[short_name] = {"description": description,


@@ 94,7 94,7 @@ def ask_which_scripts_to_run(scripts_available):
        logging.info(f"* {script_name}: {script['description']}{extra}")
        choices += [script_name]

    selection = pmb.helpers.cli.ask("Which script?", None, "all",
    selection = amb.helpers.cli.ask("Which script?", None, "all",
                                    complete=choices)
    if selection == "all":
        return scripts_available


@@ 108,23 108,23 @@ def copy_git_repo_to_chroot(args, topdir):
    """ Create a tarball of the git repo (including unstaged changes and new
        files) and extract it in chroot_native.
        :param topdir: top directory of the git repository, get it with:
                       pmb.helpers.git.get_topdir() """
    pmb.chroot.init(args)
                       amb.helpers.git.get_topdir() """
    amb.chroot.init(args)
    tarball_path = f"{args.work}/chroot_native/tmp/git.tar.gz"
    files = pmb.helpers.git.get_files(args, topdir)
    files = amb.helpers.git.get_files(args, topdir)

    with open(f"{tarball_path}.files", "w") as handle:
        for file in files:
            handle.write(file)
            handle.write("\n")

    pmb.helpers.run.user(args, ["tar", "-cf", tarball_path, "-T",
    amb.helpers.run.user(args, ["tar", "-cf", tarball_path, "-T",
                                f"{tarball_path}.files"], topdir)

    ci_dir = "/home/pmos/ci"
    pmb.chroot.user(args, ["rm", "-rf", ci_dir])
    pmb.chroot.user(args, ["mkdir", ci_dir])
    pmb.chroot.user(args, ["tar", "-xf", "/tmp/git.tar.gz"],
    amb.chroot.user(args, ["rm", "-rf", ci_dir])
    amb.chroot.user(args, ["mkdir", ci_dir])
    amb.chroot.user(args, ["tar", "-xf", "/tmp/git.tar.gz"],
                    working_dir=ci_dir)




@@ 133,7 133,7 @@ def run_scripts(args, topdir, scripts):
        chroot. Display a progress message and stop on error (without printing
        a python stack trace).
        :param topdir: top directory of the git repository, get it with:
                       pmb.helpers.git.get_topdir()
                       amb.helpers.git.get_topdir()
        :param scripts: return of get_ci_scripts() """
    steps = len(scripts)
    step = 0


@@ 142,7 142,7 @@ def run_scripts(args, topdir, scripts):
    for script_name, script in scripts.items():
        step += 1

        where = "pmbootstrap chroot"
        where = "almobootstrap chroot"
        if "native" in script["options"]:
            where = "native"



@@ 151,17 151,17 @@ def run_scripts(args, topdir, scripts):
                     f" [{where}] ***")

        if "native" in script["options"]:
            rc = pmb.helpers.run.user(args, [script_path], topdir,
            rc = amb.helpers.run.user(args, [script_path], topdir,
                                      output="tui")
            continue
        else:
            # Run inside pmbootstrap chroot
            # Run inside almobootstrap chroot
            if not repo_copied:
                copy_git_repo_to_chroot(args, topdir)
                repo_copied = True

            env = {"TESTUSER": "pmos"}
            rc = pmb.chroot.root(args, [script_path], check=False, env=env,
            rc = amb.chroot.root(args, [script_path], check=False, env=env,
                                 working_dir="/home/pmos/ci",
                                 output="tui")
        if rc:

M amb/config/__init__.py => amb/config/__init__.py +68 -67
@@ 2,24 2,24 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import multiprocessing
import os
import pmb.parse.arch
import amb.parse.arch
import sys

#
# Exported functions
#
from pmb.config.load import load
from pmb.config.save import save
from pmb.config.merge_with_args import merge_with_args
from pmb.config.sudo import which_sudo
from amb.config.load import load
from amb.config.save import save
from amb.config.merge_with_args import merge_with_args
from amb.config.sudo import which_sudo


#
# Exported variables (internal configuration)
#
pmb_src = os.path.normpath(os.path.realpath(__file__) + "/../../..")
apk_keys_path = pmb_src + "/pmb/data/keys"
arch_native = pmb.parse.arch.alpine_native()
amb_src = os.path.normpath(os.path.realpath(__file__) + "/../../..")
apk_keys_path = amb_src + "/amb/data/keys"
arch_native = amb.parse.arch.alpine_native()

# apk-tools minimum version
# https://pkgs.alpinelinux.org/packages?name=apk-tools&branch=edge


@@ 35,21 35,21 @@ apk_tools_min_version = {"edge": "2.14.0-r5",
                         "v3.13": "2.12.7-r0",
                         "v3.12": "2.10.8-r1"}

# postmarketOS aports compatibility (checked against "version" in pmaports.cfg)
pmaports_min_version = "7"
# postmarketOS aports compatibility (checked against "version" in almoports.cfg)
almoports_min_version = "7"

# Version of the work folder (as asked during 'pmbootstrap init'). Increase
# Version of the work folder (as asked during 'almobootstrap init'). Increase
# this number, whenever migration is required and provide the migration code,
# see migrate_work_folder()).
work_version = 6

# Minimum required version of postmarketos-ondev (pmbootstrap install --ondev).
# Minimum required version of postmarketos-ondev (almobootstrap install --ondev).
# Try to support the current versions of all channels (edge, v21.03). When
# bumping > 0.4.0, remove compat code in pmb/install/_install.py (search for
# bumping > 0.4.0, remove compat code in amb/install/_install.py (search for
# get_ondev_pkgver).
ondev_min_version = "0.2.0"

# Programs that pmbootstrap expects to be available from the host system. Keep
# Programs that almobootstrap expects to be available from the host system. Keep
# in sync with README.md, and try to keep the list as small as possible. The
# idea is to run almost everything in Alpine chroots.
required_programs = [


@@ 69,7 69,7 @@ def sudo(cmd: list[str]) -> list[str]:
        return cmd


# Keys saved in the config file (mostly what we ask in 'pmbootstrap init')
# Keys saved in the config file (mostly what we ask in 'almobootstrap init')
config_keys = [
    "aports",
    "boot_size",


@@ 103,13 103,14 @@ config_keys = [
# Config file/commandline default values
# $WORK gets replaced with the actual value for args.work (which may be
# overridden on the commandline)
xdg_state_home = os.path.expanduser(os.environ.get('XDG_STATE_HOME', '~/.local/state'))
defaults = {
    "aports": "$WORK/cache_git/pmaports",
    "aports": "$WORK/cache_git/almoports",
    "ccache_size": "5G",
    "is_default_channel": True,
    "cipher": "aes-xts-plain64",
    "config": (os.environ.get('XDG_CONFIG_HOME') or
               os.path.expanduser("~/.config")) + "/pmbootstrap.cfg",
               os.path.expanduser("~/.config")) + "/almobootstrap.cfg",
    "device": "qemu-amd64",
    "extra_packages": "none",
    "fork_alpine": False,


@@ 127,7 128,7 @@ defaults = {
    "mirror_alpine": "http://dl-cdn.alpinelinux.org/alpine/",
    # NOTE: mirrors_postmarketos variable type is supposed to be
    #       comma-separated string, not a python list or any other type!
    "mirrors_postmarketos": "http://mirror.postmarketos.org/postmarketos/",
    "mirrors_postmarketos": "http://mirror.almolinux.org/postmarketos/",
    "nonfree_firmware": True,
    "nonfree_userland": False,
    "port_distccd": "33632",


@@ 137,7 138,7 @@ defaults = {
    "ui": "weston",
    "ui_extras": False,
    "user": "user",
    "work": os.path.expanduser("~") + "/.local/var/pmbootstrap",
    "work": os.path.join(xdg_state_home, 'almo'),
    "boot_size": "256",
    "extra_space": "0",
    "sudo_timer": False,


@@ 195,15 196,15 @@ filesystems = {"btrfs": "btrfs-progs",
               "fat16": "dosfstools",
               "fat32": "dosfstools"}

# Legacy channels and their new names (pmb#2015)
pmaports_channels_legacy = {"stable": "v20.05",
# Legacy channels and their new names (amb#2015)
almoports_channels_legacy = {"stable": "v20.05",
                            "stable-next": "v21.03"}
#
# CHROOT
#

# Usually the ID for the first user created is 1000. However, we want
# pmbootstrap to work even if the 'user' account inside the chroots has
# almobootstrap to work even if the 'user' account inside the chroots has
# another UID, so we force it to be different.
chroot_uid_user = "12345"



@@ 218,7 219,7 @@ chroot_path = ":".join([
])

# The PATH variable used on the host, to find the "chroot" and "sh"
# executables. As pmbootstrap runs as user, not as root, the location
# executables. As almobootstrap runs as user, not as root, the location
# for the chroot executable may not be in the PATH (Debian).
chroot_host_path = os.environ["PATH"] + ":/usr/sbin/"



@@ 230,15 231,15 @@ chroot_mount_bind = {
    "/proc": "/proc",
    "$WORK/cache_apk_$ARCH": "/var/cache/apk",
    "$WORK/cache_appstream/$ARCH/$CHANNEL": "/mnt/appstream-data",
    "$WORK/cache_ccache_$ARCH": "/mnt/pmbootstrap-ccache",
    "$WORK/cache_ccache_$ARCH": "/mnt/almobootstrap-ccache",
    "$WORK/cache_distfiles": "/var/cache/distfiles",
    "$WORK/cache_git": "/mnt/pmbootstrap-git",
    "$WORK/cache_go": "/mnt/pmbootstrap-go",
    "$WORK/cache_rust": "/mnt/pmbootstrap-rust",
    "$WORK/config_abuild": "/mnt/pmbootstrap-abuild-config",
    "$WORK/cache_git": "/mnt/almobootstrap-git",
    "$WORK/cache_go": "/mnt/almobootstrap-go",
    "$WORK/cache_rust": "/mnt/almobootstrap-rust",
    "$WORK/config_abuild": "/mnt/almobootstrap-abuild-config",
    "$WORK/config_apk_keys": "/etc/apk/keys",
    "$WORK/images_netboot": "/mnt/pmbootstrap-netboot",
    "$WORK/packages/$CHANNEL": "/mnt/pmbootstrap-packages",
    "$WORK/images_netboot": "/mnt/almobootstrap-netboot",
    "$WORK/packages/$CHANNEL": "/mnt/almobootstrap-packages",
}

# Building chroots (all chroots, except for the rootfs_ chroot) get symlinks in


@@ 254,14 255,14 @@ chroot_mount_bind = {
# rust depends caching described above) and to cache build artifacts (GOCACHE,
# similar to ccache).
chroot_home_symlinks = {
    "/mnt/pmbootstrap-abuild-config": "/home/pmos/.abuild",
    "/mnt/pmbootstrap-ccache": "/home/pmos/.ccache",
    "/mnt/pmbootstrap-packages": "/home/pmos/packages/pmos",
    "/mnt/pmbootstrap-go/gocache": "/home/pmos/.cache/go-build",
    "/mnt/pmbootstrap-go/gomodcache": "/home/pmos/go/pkg/mod",
    "/mnt/pmbootstrap-rust/registry/index": "/home/pmos/.cargo/registry/index",
    "/mnt/pmbootstrap-rust/registry/cache": "/home/pmos/.cargo/registry/cache",
    "/mnt/pmbootstrap-rust/git/db": "/home/pmos/.cargo/git/db",
    "/mnt/almobootstrap-abuild-config": "/home/pmos/.abuild",
    "/mnt/almobootstrap-ccache": "/home/pmos/.ccache",
    "/mnt/almobootstrap-packages": "/home/pmos/packages/pmos",
    "/mnt/almobootstrap-go/gocache": "/home/pmos/.cache/go-build",
    "/mnt/almobootstrap-go/gomodcache": "/home/pmos/go/pkg/mod",
    "/mnt/almobootstrap-rust/registry/index": "/home/pmos/.cargo/registry/index",
    "/mnt/almobootstrap-rust/registry/cache": "/home/pmos/.cargo/registry/cache",
    "/mnt/almobootstrap-rust/git/db": "/home/pmos/.cargo/git/db",
}

# Device nodes to be created in each chroot. Syntax for each entry:


@@ 275,7 276,7 @@ chroot_device_nodes = [
]

# Age in hours that we keep the APKINDEXes before downloading them again.
# You can force-update them with 'pmbootstrap update'.
# You can force-update them with 'almobootstrap update'.
apkindex_retention_time = 4




@@ 288,7 289,7 @@ chroot_outdated = 3600 * 24 * 2
# Officially supported host/target architectures for postmarketOS. Only
# specify architectures supported by Alpine here. For cross-compiling,
# we need to generate the "musl-$ARCH", "binutils-$ARCH" and "gcc-$ARCH"
# packages (use "pmbootstrap aportgen musl-armhf" etc.).
# packages (use "almobootstrap aportgen musl-armhf" etc.).
build_device_architectures = ["armhf", "armv7", "aarch64", "x86_64", "x86", "riscv64"]

# Packages that will be installed in a chroot before it builds packages


@@ 701,21 702,21 @@ apkbuild_package_attributes = {
    "install": {"array": True},
    "triggers": {"array": True},

    # UI meta-packages can specify apps in "_pmb_recommends" to be explicitly
    # UI meta-packages can specify apps in "_amb_recommends" to be explicitly
    # installed by default, and not implicitly as dependency of the UI meta-
    # package ("depends"). This makes these apps uninstallable, without
    # removing the meta-package. (#1933). To disable this feature, use:
    # "pmbootstrap install --no-recommends".
    "_pmb_recommends": {"array": True},
    # "almobootstrap install --no-recommends".
    "_amb_recommends": {"array": True},

    # UI meta-packages can specify groups to which the user must be added
    # to access specific hardware such as LED indicators.
    "_pmb_groups": {"array": True},
    "_amb_groups": {"array": True},

    # postmarketos-base, UI and device packages can use _pmb_select to provide
    # additional configuration options in "pmbootstrap init" that allow
    # postmarketos-base, UI and device packages can use _amb_select to provide
    # additional configuration options in "almobootstrap init" that allow
    # selecting alternative providers for a virtual APK package.
    "_pmb_select": {"array": True},
    "_amb_select": {"array": True},
}

# Variables in APKBUILD files that get parsed


@@ 764,24 765,24 @@ apkbuild_attributes = {
    "_pkgsnap": {}
}

# Reference: https://postmarketos.org/apkbuild-options
# Reference: https://almolinux.org/apkbuild-options
apkbuild_custom_valid_options = [
    "!pmb:crossdirect",
    "!pmb:kconfig-check",
    "pmb:kconfigcheck-community",
    "pmb:kconfigcheck-containers",
    "pmb:kconfigcheck-iwd",
    "pmb:kconfigcheck-netboot",
    "pmb:kconfigcheck-nftables",
    "pmb:kconfigcheck-uefi",
    "pmb:kconfigcheck-waydroid",
    "pmb:kconfigcheck-zram",
    "pmb:cross-native",
    "pmb:gpu-accel",
    "pmb:strict",
    "!amb:crossdirect",
    "!amb:kconfig-check",
    "amb:kconfigcheck-community",
    "amb:kconfigcheck-containers",
    "amb:kconfigcheck-iwd",
    "amb:kconfigcheck-netboot",
    "amb:kconfigcheck-nftables",
    "amb:kconfigcheck-uefi",
    "amb:kconfigcheck-waydroid",
    "amb:kconfigcheck-zram",
    "amb:cross-native",
    "amb:gpu-accel",
    "amb:strict",
]

# Variables from deviceinfo. Reference: <https://postmarketos.org/deviceinfo>
# Variables from deviceinfo. Reference: <https://almolinux.org/deviceinfo>
deviceinfo_attributes = [
    # general
    "format_version",


@@ 928,7 929,7 @@ uuu specific: $UUU_SCRIPT
"""
flashers = {
    "fastboot": {
        "depends": [],  # pmaports.cfg: supported_fastboot_depends
        "depends": [],  # almoports.cfg: supported_fastboot_depends
        "actions": {
            "list_devices": [["fastboot", "devices", "-l"]],
            "flash_rootfs": [["fastboot", "flash", "$PARTITION_SYSTEM",


@@ 990,7 991,7 @@ flashers = {
    # Some Samsung devices need a 'boot.img' file, just like the one generated
    # fastboot compatible devices. Example: s7562, n7100
    "heimdall-bootimg": {
        "depends": [],  # pmaports.cfg: supported_heimdall_depends
        "depends": [],  # almoports.cfg: supported_heimdall_depends
        "actions": {
            "list_devices": [["heimdall", "detect"]],
            "flash_rootfs": [


@@ 1054,7 1055,7 @@ flashers = {
#
git_repos = {
    "aports_upstream": "https://gitlab.alpinelinux.org/alpine/aports.git",
    "pmaports": "https://gitlab.com/postmarketOS/pmaports.git",
    "almoports": "https://git.sr.ht/~martijnbraam/almoports",
}

# When a git repository is considered outdated (in seconds)


@@ 1111,7 1112,7 @@ newapkbuild_arguments_switches_other = [
# UPGRADE
#
# Patterns of package names to ignore for automatic pmaport upgrading
# ("pmbootstrap aportupgrade --all")
# ("almobootstrap aportupgrade --all")
upgrade_ignore = ["device-*", "firmware-*", "linux-*", "postmarketos-*",
                  "*-aarch64", "*-armhf", "*-armv7", "*-riscv64"]



@@ 1123,5 1124,5 @@ sideload_sudo_prompt = "[sudo] password for %u@%h: "
#
# CI
#
# Valid options für 'pmbootstrap ci', see https://postmarketos.org/pmb-ci
# Valid options für 'almobootstrap ci', see https://almolinux.org/amb-ci
ci_valid_options = ["native", "slow"]

R amb/config/pmaports.py => amb/config/almoports.py +66 -66
@@ 5,144 5,144 @@ import logging
import os
import sys

import pmb.config
import pmb.helpers.git
import pmb.helpers.pmaports
import amb.config
import amb.helpers.git
import amb.helpers.almoports


def check_legacy_folder():
    # Existing pmbootstrap/aports must be a symlink
    link = pmb.config.pmb_src + "/aports"
    # Existing almobootstrap/aports must be a symlink
    link = amb.config.amb_src + "/aports"
    if os.path.exists(link) and not os.path.islink(link):
        raise RuntimeError("The path '" + link + "' should be a"
                           " symlink pointing to the new pmaports"
                           " symlink pointing to the new almoports"
                           " repository, which was split from the"
                           " pmbootstrap repository (#383). Consider"
                           " almobootstrap repository (#383). Consider"
                           " making a backup of that folder, then delete"
                           " it and run 'pmbootstrap init' again to let"
                           " pmbootstrap clone the pmaports repository and"
                           " it and run 'almobootstrap init' again to let"
                           " almobootstrap clone the almoports repository and"
                           " set up the symlink.")


def clone(args):
    logging.info("Setting up the native chroot and cloning the package build"
                 " recipes (pmaports)...")
                 " recipes (almoports)...")

    # Set up the native chroot and clone pmaports
    pmb.helpers.git.clone(args, "pmaports")
    # Set up the native chroot and clone almoports
    amb.helpers.git.clone(args, "almoports")


def symlink(args):
    # Create the symlink
    # This won't work when pmbootstrap was installed system wide, but that's
    # This won't work when almobootstrap was installed system wide, but that's
    # okay since the symlink is only intended to make the migration to the
    # pmaports repository easier.
    link = pmb.config.pmb_src + "/aports"
    # almoports repository easier.
    link = amb.config.amb_src + "/aports"
    try:
        os.symlink(args.aports, link)
        logging.info("NOTE: pmaports path: " + link)
        logging.info("NOTE: almoports path: " + link)
    except:
        logging.info("NOTE: pmaports path: " + args.aports)
        logging.info("NOTE: almoports path: " + args.aports)


def check_version_pmaports(real):
def check_version_almoports(real):
    # Compare versions
    min = pmb.config.pmaports_min_version
    if pmb.parse.version.compare(real, min) >= 0:
    min = amb.config.almoports_min_version
    if amb.parse.version.compare(real, min) >= 0:
        return

    # Outated error
    logging.info("NOTE: your pmaports folder has version " + real + ", but" +
    logging.info("NOTE: your almoports folder has version " + real + ", but" +
                 " version " + min + " is required.")
    raise RuntimeError("Run 'pmbootstrap pull' to update your pmaports.")
    raise RuntimeError("Run 'almobootstrap pull' to update your almoports.")


def check_version_pmbootstrap(min):
def check_version_almobootstrap(min):
    # Compare versions
    real = pmb.__version__
    if pmb.parse.version.compare(real, min) >= 0:
    real = amb.__version__
    if amb.parse.version.compare(real, min) >= 0:
        return

    # Show versions
    logging.info("NOTE: you are using pmbootstrap version " + real + ", but" +
    logging.info("NOTE: you are using almobootstrap version " + real + ", but" +
                 " version " + min + " is required.")

    # Error for git clone
    pmb_src = pmb.config.pmb_src
    if os.path.exists(pmb_src + "/.git"):
        raise RuntimeError("Please update your local pmbootstrap repository."
                           " Usually with: 'git -C \"" + pmb_src + "\" pull'")
    amb_src = amb.config.amb_src
    if os.path.exists(amb_src + "/.git"):
        raise RuntimeError("Please update your local almobootstrap repository."
                           " Usually with: 'git -C \"" + amb_src + "\" pull'")

    # Error for package manager installation
    raise RuntimeError("Please update your pmbootstrap version (with your"
    raise RuntimeError("Please update your almobootstrap version (with your"
                       " distribution's package manager, or with pip, "
                       " depending on how you have installed it). If that is"
                       " not possible, consider cloning the latest version"
                       " of pmbootstrap from git.")
                       " of almobootstrap from git.")


def read_config(args):
    """ Read and verify pmaports.cfg. """
    """ Read and verify almoports.cfg. """
    # Try cache first
    cache_key = "pmb.config.pmaports.read_config"
    if pmb.helpers.other.cache[cache_key]:
        return pmb.helpers.other.cache[cache_key]
    cache_key = "amb.config.almoports.read_config"
    if amb.helpers.other.cache[cache_key]:
        return amb.helpers.other.cache[cache_key]

    # Migration message
    if not os.path.exists(args.aports):
        logging.error(f"ERROR: pmaports dir not found: {args.aports}")
        logging.error("Did you run 'pmbootstrap init'?")
        logging.error(f"ERROR: almoports dir not found: {args.aports}")
        logging.error("Did you run 'almobootstrap init'?")
        sys.exit(1)

    # Require the config
    path_cfg = args.aports + "/pmaports.cfg"
    path_cfg = args.aports + "/almoports.cfg"
    if not os.path.exists(path_cfg):
        raise RuntimeError("Invalid pmaports repository, could not find the"
        raise RuntimeError("Invalid almoports repository, could not find the"
                           " config: " + path_cfg)

    # Load the config
    cfg = configparser.ConfigParser()
    cfg.read(path_cfg)
    ret = cfg["pmaports"]
    ret = cfg["almoports"]

    # Version checks
    check_version_pmaports(ret["version"])
    check_version_pmbootstrap(ret["pmbootstrap_min_version"])
    check_version_almoports(ret["version"])
    check_version_almobootstrap(ret["almobootstrap_min_version"])

    # Translate legacy channel names
    ret["channel"] = pmb.helpers.pmaports.get_channel_new(ret["channel"])
    ret["channel"] = amb.helpers.almoports.get_channel_new(ret["channel"])

    # Cache and return
    pmb.helpers.other.cache[cache_key] = ret
    amb.helpers.other.cache[cache_key] = ret
    return ret


def read_config_channel(args):
    """ Get the properties of the currently active channel in pmaports.git,
        as specified in channels.cfg (https://postmarketos.org/channels.cfg).
    """ Get the properties of the currently active channel in almoports.git,
        as specified in channels.cfg (https://almolinux.org/channels.cfg).
        :returns: {"description: ...,
                   "branch_pmaports": ...,
                   "branch_almoports": ...,
                   "branch_aports": ...,
                   "mirrordir_alpine": ...} """
    channel = read_config(args)["channel"]
    channels_cfg = pmb.helpers.git.parse_channels_cfg(args)
    channels_cfg = amb.helpers.git.parse_channels_cfg(args)

    if channel in channels_cfg["channels"]:
        return channels_cfg["channels"][channel]

    # Channel not in channels.cfg, try to be helpful
    branch = pmb.helpers.git.rev_parse(args, args.aports,
    branch = amb.helpers.git.rev_parse(args, args.aports,
                                       extra_args=["--abbrev-ref"])
    branches_official = pmb.helpers.git.get_branches_official(args, "pmaports")
    branches_official = amb.helpers.git.get_branches_official(args, "almoports")
    branches_official = ", ".join(branches_official)
    remote = pmb.helpers.git.get_upstream_remote(args, "pmaports")
    remote = amb.helpers.git.get_upstream_remote(args, "almoports")
    logging.info("NOTE: fix the error by rebasing or cherry picking relevant"
                 " commits from this branch onto a branch that is on a"
                 f" supported channel: {branches_official}")
    logging.info("NOTE: as workaround, you may pass --config-channels with a"
                 " custom channels.cfg. Reference:"
                 " https://postmarketos.org/channels.cfg")
    raise RuntimeError(f"Current branch '{branch}' of pmaports.git is on"
                 " https://almolinux.org/channels.cfg")
    raise RuntimeError(f"Current branch '{branch}' of almoports.git is on"
                       f" channel '{channel}', but this channel was not"
                       f" found in channels.cfg (of {remote}/master"
                       " branch). Looks like a very old branch.")


@@ 157,39 157,39 @@ def init(args):


def switch_to_channel_branch(args, channel_new):
    """ Checkout the channel's branch in pmaports.git.
    """ Checkout the channel's branch in almoports.git.
        :channel_new: channel name (e.g. "edge", "v21.03")
        :returns: True if another branch was checked out, False otherwise """
    # Check current pmaports branch channel
    # Check current almoports branch channel
    channel_current = read_config(args)["channel"]
    if channel_current == channel_new:
        return False

    # List current and new branches/channels
    channels_cfg = pmb.helpers.git.parse_channels_cfg(args)
    branch_new = channels_cfg["channels"][channel_new]["branch_pmaports"]
    branch_current = pmb.helpers.git.rev_parse(args, args.aports,
    channels_cfg = amb.helpers.git.parse_channels_cfg(args)
    branch_new = channels_cfg["channels"][channel_new]["branch_almoports"]
    branch_current = amb.helpers.git.rev_parse(args, args.aports,
                                               extra_args=["--abbrev-ref"])
    logging.info(f"Currently checked out branch '{branch_current}' of"
                 f" pmaports.git is on channel '{channel_current}'.")
                 f" almoports.git is on channel '{channel_current}'.")
    logging.info(f"Switching to branch '{branch_new}' on channel"
                 f" '{channel_new}'...")

    # Make sure we don't have mounts related to the old channel
    pmb.chroot.shutdown(args)
    amb.chroot.shutdown(args)

    # Attempt to switch branch (git gives a nice error message, mentioning
    # which files need to be committed/stashed, so just pass it through)
    if pmb.helpers.run.user(args, ["git", "checkout", branch_new],
    if amb.helpers.run.user(args, ["git", "checkout", branch_new],
                            args.aports, "interactive", check=False):
        raise RuntimeError("Failed to switch branch. Go to your pmaports and"
        raise RuntimeError("Failed to switch branch. Go to your almoports and"
                           " fix what git complained about, then try again: "
                           f"{args.aports}")

    # Invalidate all caches
    pmb.helpers.other.init_cache()
    amb.helpers.other.init_cache()

    # Verify pmaports.cfg on new branch
    # Verify almoports.cfg on new branch
    read_config(args)
    return True



@@ 204,5 204,5 @@ def install_githooks(args):
        # Use git default hooks dir so users can ignore our hooks
        # if they dislike them by setting "core.hooksPath" git config
        dst = os.path.join(args.aports, ".git", "hooks", h)
        if pmb.helpers.run.user(args, ["cp", src, dst], check=False):
        if amb.helpers.run.user(args, ["cp", src, dst], check=False):
            logging.warning(f"WARNING: Copying git hook failed: {dst}")

M amb/config/init.py => amb/config/init.py +134 -134
@@ 6,31 6,31 @@ import json
import os
import shutil

import pmb.aportgen
import pmb.config
import pmb.config.pmaports
import pmb.helpers.cli
import pmb.helpers.devices
import pmb.helpers.git
import pmb.helpers.http
import pmb.helpers.logging
import pmb.helpers.other
import pmb.helpers.pmaports
import pmb.helpers.run
import pmb.helpers.ui
import pmb.chroot.zap
import pmb.parse.deviceinfo
import pmb.parse._apkbuild
import amb.aportgen
import amb.config
import amb.config.almoports
import amb.helpers.cli
import amb.helpers.devices
import amb.helpers.git
import amb.helpers.http
import amb.helpers.logging
import amb.helpers.other
import amb.helpers.almoports
import amb.helpers.run
import amb.helpers.ui
import amb.chroot.zap
import amb.parse.deviceinfo
import amb.parse._apkbuild


def require_programs():
    missing = []
    for program in pmb.config.required_programs:
    for program in amb.config.required_programs:
        if not shutil.which(program):
            missing.append(program)
    if missing:
        raise RuntimeError("Can't find all programs required to run"
                           " pmbootstrap. Please install first:"
                           " almobootstrap. Please install first:"
                           f" {', '.join(missing)}")




@@ 41,7 41,7 @@ def ask_for_username(args):
    :returns: the username
    """
    while True:
        ret = pmb.helpers.cli.ask("Username", None, args.user, False,
        ret = amb.helpers.cli.ask("Username", None, args.user, False,
                                  "[a-z_][a-z0-9_-]*")
        if ret == "root":
            logging.fatal("ERROR: don't put \"root\" here. This is about"


@@ 65,16 65,16 @@ def ask_for_work_path(args):
                 " in there.")
    while True:
        try:
            work = os.path.expanduser(pmb.helpers.cli.ask(
            work = os.path.expanduser(amb.helpers.cli.ask(
                "Work path", None, args.work, False))
            work = os.path.realpath(work)
            exists = os.path.exists(work)

            # Work must not be inside the pmbootstrap path
            if (work == pmb.config.pmb_src or
                    work.startswith(f"{pmb.config.pmb_src}/")):
            # Work must not be inside the almobootstrap path
            if (work == amb.config.amb_src or
                    work.startswith(f"{amb.config.amb_src}/")):
                logging.fatal("ERROR: The work path must not be inside the"
                              " pmbootstrap path. Please specify another"
                              " almobootstrap path. Please specify another"
                              " location.")
                continue



@@ 88,10 88,10 @@ def ask_for_work_path(args):
            work_version_file = f"{work}/version"
            if not os.path.isfile(work_version_file):
                with open(work_version_file, "w") as handle:
                    handle.write(f"{pmb.config.work_version}\n")
                    handle.write(f"{amb.config.work_version}\n")

            # Create cache_git dir, so it is owned by the host system's user
            # (otherwise pmb.helpers.mount.bind would create it as root)
            # (otherwise amb.helpers.mount.bind would create it as root)
            os.makedirs(f"{work}/cache_git", 0o700, True)
            return (work, exists)
        except OSError:


@@ 101,10 101,10 @@ def ask_for_work_path(args):

def ask_for_channel(args):
    """ Ask for the postmarketOS release channel. The channel dictates, which
        pmaports branch pmbootstrap will check out, and which repository URLs
        almoports branch almobootstrap will check out, and which repository URLs
        will be used when initializing chroots.
        :returns: channel name (e.g. "edge", "v21.03") """
    channels_cfg = pmb.helpers.git.parse_channels_cfg(args)
    channels_cfg = amb.helpers.git.parse_channels_cfg(args)
    count = len(channels_cfg["channels"])

    # List channels


@@ 114,17 114,17 @@ def ask_for_channel(args):
        logging.info(f"* {channel}: {channel_data['description']}")

    # Default for first run: "recommended" from channels.cfg
    # Otherwise, if valid: channel from pmaports.cfg of current branch
    # The actual channel name is not saved in pmbootstrap.cfg, because then we
    # would need to sync it with what is checked out in pmaports.git.
    default = pmb.config.pmaports.read_config(args)["channel"]
    # Otherwise, if valid: channel from almoports.cfg of current branch
    # The actual channel name is not saved in almobootstrap.cfg, because then we
    # would need to sync it with what is checked out in almoports.git.
    default = amb.config.almoports.read_config(args)["channel"]
    choices = channels_cfg["channels"].keys()
    if args.is_default_channel or default not in choices:
        default = channels_cfg["meta"]["recommended"]

    # Ask until user gives valid channel
    while True:
        ret = pmb.helpers.cli.ask("Channel", None, default,
        ret = amb.helpers.cli.ask("Channel", None, default,
                                  complete=choices)
        if ret in choices:
            return ret


@@ 133,16 133,16 @@ def ask_for_channel(args):


def ask_for_ui(args, info):
    ui_list = pmb.helpers.ui.list(args, info["arch"])
    ui_list = amb.helpers.ui.list(args, info["arch"])
    hidden_ui_count = 0
    device_is_accelerated = info.get("gpu_accelerated") == "true"
    if not device_is_accelerated:
        for i in reversed(range(len(ui_list))):
            pkgname = f"postmarketos-ui-{ui_list[i][0]}"
            apkbuild = pmb.helpers.pmaports.get(args, pkgname,
            apkbuild = amb.helpers.almoports.get(args, pkgname,
                                                subpackages=False,
                                                must_exist=False)
            if apkbuild and "pmb:gpu-accel" in apkbuild["options"]:
            if apkbuild and "amb:gpu-accel" in apkbuild["options"]:
                ui_list.pop(i)
                hidden_ui_count += 1



@@ 155,10 155,10 @@ def ask_for_ui(args, info):
        logging.info(f"NOTE: {hidden_ui_count} user interfaces are not"
                     " available. If device supports GPU acceleration,"
                     " set \"deviceinfo_gpu_accelerated\" to make UIs"
                     " available. See: <https://wiki.postmarketos.org/wiki/"
                     " available. See: <https://wiki.almolinux.org/wiki/"
                     "Deviceinfo_reference")
    while True:
        ret = pmb.helpers.cli.ask("User interface", None, args.ui, True,
        ret = amb.helpers.cli.ask("User interface", None, args.ui, True,
                                  complete=ui_completion_list)
        if ret in dict(ui_list).keys():
            return ret


@@ 167,7 167,7 @@ def ask_for_ui(args, info):


def ask_for_ui_extras(args, ui):
    apkbuild = pmb.helpers.pmaports.get(args, f"postmarketos-ui-{ui}",
    apkbuild = amb.helpers.almoports.get(args, f"postmarketos-ui-{ui}",
                                        subpackages=False, must_exist=False)
    if not apkbuild:
        return False


@@ 179,7 179,7 @@ def ask_for_ui_extras(args, ui):
    logging.info("This user interface has an extra package:"
                 f" {extra['pkgdesc']}")

    return pmb.helpers.cli.confirm(args, "Enable this package?",
    return amb.helpers.cli.confirm(args, "Enable this package?",
                                   default=args.ui_extras)




@@ 193,7 193,7 @@ def ask_for_keymaps(args, info):
        args.keymap = options[0]

    while True:
        ret = pmb.helpers.cli.ask("Keymap", None, args.keymap,
        ret = amb.helpers.cli.ask("Keymap", None, args.keymap,
                                  True, complete=options)
        if ret in options:
            return ret


@@ 218,7 218,7 @@ def ask_for_timezone(args):
                    pass
        if tz:
            logging.info(f"Your host timezone: {tz}")
            if pmb.helpers.cli.confirm(args,
            if amb.helpers.cli.confirm(args,
                                       "Use this timezone instead of GMT?",
                                       default="y"):
                return tz


@@ 229,15 229,15 @@ def ask_for_timezone(args):

def ask_for_provider_select(args, apkbuild, providers_cfg):
    """
    Ask for selectable providers that are specified using "_pmb_select"
    Ask for selectable providers that are specified using "_amb_select"
    in a APKBUILD.

    :par