diff --git a/packaging/alpine/APKBUILD.in b/packaging/alpine/APKBUILD.in new file mode 100644 index 0000000..c1db4b8 --- /dev/null +++ b/packaging/alpine/APKBUILD.in @@ -0,0 +1,120 @@ +# Maintainer: webbeef +pkgname=beaver +pkgver=@VERSION@ +pkgrel=0 +pkgdesc="A handmade web browser built on Servo" +url="https://tangled.org/me.webbeef.org/beaver" +arch="all" +license="AGPL-3.0-or-later" +depends=" + gstreamer + gst-plugins-base + gst-plugins-bad + gst-plugins-good + gst-libav + fontconfig + freetype + harfbuzz + gtk+3.0 + dbus-libs + libxkbcommon + wayland-libs-client + libxcb + openssl + ca-certificates + cage +" + +install="$pkgname.post-install $pkgname.post-deinstall" +makedepends=" + build-base + musl-dev + pkgconf + cmake + python3 + curl + clang + lld + llvm + imagemagick + gstreamer-dev + gst-plugins-base-dev + gst-plugins-bad-dev + fontconfig-dev + freetype-dev + harfbuzz-dev + gtk+3.0-dev + dbus-dev + libxkbcommon-dev + mesa-dev + mesa-gl + openssl-dev + wayland-dev + libxcb-dev + libxt-dev + pulseaudio-dev + eudev-dev + alsa-lib-dev +" +# Note: cargo + rust are NOT in makedepends. The Docker image installs +# them via rustup (Alpine's apk rust trails edition="2024" support); +# abuild can't see rustup binaries via `apk info -e` so we'd get a +# spurious "missing dependencies" error if we listed them here. +# Empty source — we build from the bind-mounted repo at /src instead of +# fetching a tarball. builddir is set to /src so build()/package() find +# the tree without the usual unpack step. +source="" +builddir="/src" +# Skip abuild's built-in checks: no test phase to run, and we strip the +# binary ourselves in package() to keep the .apk small. +options="!check" + +build() { + cd "$builddir" + # musl's default rust target is `*-unknown-linux-musl` with + # crt-static enabled. Build scripts (e.g. bindgen for mozjs/mozangle) + # need to dlopen libclang.so at runtime, which fails on a statically + # linked binary with "Dynamic loading not supported". Disable crt- + # static so build scripts and the final beavershell binary both link + # against musl dynamically — that's also the standard Alpine ABI for + # anything depending on gtk / gstreamer / etc. + export RUSTFLAGS="-C target-feature=-crt-static" + # Alpine's abuild.conf exports hardening CFLAGS / CXXFLAGS that + # include `-Werror=format-security`; mozjs's makefile rewrites flags + # in a way that drops the matching `-Wformat`, leaving gcc with a + # rejected combination. Clear the env so mozjs / mozangle / etc. use + # their own internal defaults — Rust's memory safety covers what + # those hardening flags would. + unset CFLAGS CXXFLAGS CPPFLAGS LDFLAGS + command -v sccache >/dev/null 2>&1 || unset RUSTC_WRAPPER + cargo build --release \ + -p beaver-shell \ + --no-default-features \ + --features="servo/clipboard,servo/gstreamer,js_jit,max_log_level,native-bluetooth,webgpu" +} + +package() { + cd "$builddir" + + install -Dm755 target/release/beavershell \ + "$pkgdir"/usr/bin/beavershell + install -Dm755 packaging/alpine/beaver-session \ + "$pkgdir"/usr/bin/beaver-session + + mkdir -p "$pkgdir"/usr/share/beaver + cp -a ui "$pkgdir"/usr/share/beaver/ui + cp -a resources "$pkgdir"/usr/share/beaver/resources + + install -Dm644 packaging/alpine/beaver.desktop \ + "$pkgdir"/usr/share/applications/beaver.desktop + install -Dm644 packaging/alpine/beaver-session.desktop \ + "$pkgdir"/usr/share/wayland-sessions/beaver.desktop + + # Render the icon at standard hicolor sizes so each shell — Phosh, + # Plasma Mobile, GNOME, KDE — picks a size that suits its DPI. + for size in 48 64 128 256 512; do + install -d "$pkgdir"/usr/share/icons/hicolor/${size}x${size}/apps + convert ui/system/logo_square.png -resize ${size}x${size} \ + "$pkgdir"/usr/share/icons/hicolor/${size}x${size}/apps/beaver.png + done +} diff --git a/packaging/alpine/README.md b/packaging/alpine/README.md new file mode 100644 index 0000000..cbfc9ef --- /dev/null +++ b/packaging/alpine/README.md @@ -0,0 +1,75 @@ +# Alpine packaging + +This directory holds the recipe for the Beaver `.apk`, intended to install +on postmarketOS devices. + +There are two ways to build the `.apk`, both producing +`dist/beaver__.apk`: + +- **Docker** (works on macOS): `scripts/build-apk.sh ` + from the repo root runs `cargo build` + `abuild` inside one of the Alpine + Docker images at `support/docker/Dockerfile.apk-{aarch64,x86_64}`. Each image + is single-arch native (no cross-compilation, no multi-arch dance). +- **pmbootstrap** (Linux hosts): `scripts/build-apk-pmbootstrap.sh + ` uses postmarketOS's own tooling instead of Docker. See the + section below. + +## Building with pmbootstrap (Linux hosts) + +`scripts/build-apk-pmbootstrap.sh ` drives an existing +pmbootstrap install rather than Docker. Prefer it on a Linux box already set up +for pmOS work; the Docker path stays the option for macOS. + +**Prerequisites:** + +- pmbootstrap installed, with `pmbootstrap init` already run once. +- An `edge`-based channel. The build chroot's toolchain comes only from the + APKBUILD's `makedepends` (there is no rustup layer like the Docker image has), + so apk's `cargo` must be new enough for `edition = "2024"`. + +**How it differs from the Docker path** : + +- It renders a pmbootstrap-specific APKBUILD from the same `APKBUILD.in`, with + two changes. It drops `builddir="/src"`, because pmbootstrap's `--src` rsyncs + your working tree into the chroot and symlinks it to abuild's default + `$builddir`. And it adds the makedepends that the Docker *image* installs on top + of the recipe: `cargo`, plus `clang-dev` / `clang-libclang` / `llvm-dev` (the + `libclang.so` that mozjs/mozangle bindgen dlopens), `git`, and the `gst-plugins-good` / + `gst-libav` runtime plugins that abuild verifies at build time. The `build()` + musl workarounds (`-crt-static`, cleared `CFLAGS`/`CXXFLAGS`/...) are unchanged. +- It stages a throwaway aports overlay in a temp dir (the recipe plus a copy of + your `pmaports.cfg`, so the channel matches) and passes + `-p ","`. Your real pmaports checkout is never touched. +- It stages a pruned copy of the working tree (via `rsync`, excluding `target/`, + `dist/`, `node_modules/`, `.git/`) and points `--src` at that, not the repo + directly. This is required for correctness, not just speed: pmbootstrap feeds + `/.gitignore` to rsync as `--exclude-from`, and our root `.gitignore` + lists `source/` (the Servo fork the workspace path-depends on) alongside the + build artifacts so `--src` on the repo would drop `source/` and the build + would fail. Staging keeps `source/` while excluding the artifacts (so a + multi-GB `target/` never reaches the chroot). +- Output lands in pmbootstrap's work dir at `/packages///`; + the script copies it into `dist/`. + +## Layout produced by the .apk + +``` +/usr/bin/beavershell +/usr/bin/beaver-session +/usr/share/beaver/ui/ +/usr/share/beaver/resources/ +/usr/share/applications/beaver.desktop +/usr/share/wayland-sessions/beaver.desktop +/usr/share/icons/hicolor/{48,64,128,256,512}x*/apps/beaver.png +``` + +## Files + +- `APKBUILD.in` — recipe; `@VERSION@` substituted at build time. +- `beaver.desktop` — application launcher (picked up by Phosh / Plasma + Mobile / GNOME / KDE app menus). +- `beaver-session.desktop` — Wayland session entry (display-manager picks + this up; runs `beaver-session`). +- `beaver-session` — `/usr/bin/beaver-session`, runs `cage -- beavershell`. +- `beaver.post-install` / `beaver.post-deinstall` — refresh desktop + + icon caches on install/removal. diff --git a/packaging/alpine/beaver-session b/packaging/alpine/beaver-session new file mode 100644 index 0000000..2ff2479 --- /dev/null +++ b/packaging/alpine/beaver-session @@ -0,0 +1,7 @@ +#!/bin/sh +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# Wraps Beaver in cage (a one-app Wayland compositor) so display managers +# can offer "Beaver" as a session at the login screen. + +exec cage -- /usr/bin/beavershell "$@" diff --git a/packaging/alpine/beaver-session.desktop b/packaging/alpine/beaver-session.desktop new file mode 100644 index 0000000..370f297 --- /dev/null +++ b/packaging/alpine/beaver-session.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Application +Name=Beaver +Comment=Web Based User Agent +Exec=/usr/bin/beaver-session +DesktopNames=Beaver diff --git a/packaging/alpine/beaver.desktop b/packaging/alpine/beaver.desktop new file mode 100644 index 0000000..ab39ef1 --- /dev/null +++ b/packaging/alpine/beaver.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Name=Beaver +GenericName=Web Browser +Comment=Web Based User Agent +Exec=beavershell %u +Icon=beaver +Terminal=false +Categories=Network;WebBrowser; +MimeType=text/html;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https; +StartupNotify=true +Keywords=web;browser;internet; diff --git a/packaging/alpine/beaver.post-deinstall b/packaging/alpine/beaver.post-deinstall new file mode 100644 index 0000000..4b615de --- /dev/null +++ b/packaging/alpine/beaver.post-deinstall @@ -0,0 +1,6 @@ +#!/bin/sh +[ -x /usr/bin/update-desktop-database ] && \ + update-desktop-database -q /usr/share/applications || true +[ -x /usr/bin/gtk-update-icon-cache ] && \ + gtk-update-icon-cache -q -t /usr/share/icons/hicolor || true +exit 0 diff --git a/packaging/alpine/beaver.post-install b/packaging/alpine/beaver.post-install new file mode 100644 index 0000000..4b615de --- /dev/null +++ b/packaging/alpine/beaver.post-install @@ -0,0 +1,6 @@ +#!/bin/sh +[ -x /usr/bin/update-desktop-database ] && \ + update-desktop-database -q /usr/share/applications || true +[ -x /usr/bin/gtk-update-icon-cache ] && \ + gtk-update-icon-cache -q -t /usr/share/icons/hicolor || true +exit 0 diff --git a/scripts/_build-apk-inside.sh b/scripts/_build-apk-inside.sh new file mode 100755 index 0000000..83822c6 --- /dev/null +++ b/scripts/_build-apk-inside.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# Runs *inside* the Dockerfile.apk-{aarch64,x86_64} container, as the +# non-root `builder` user (USER builder in the Dockerfile). Renders the +# APKBUILD from APKBUILD.in, runs abuild, and copies the resulting +# .apk to /src/dist/. + +set -e + +cd /src + +# The Docker volume mounted at /src/target is created with root +# ownership; the builder user (UID 1000) can't write to it without +# this chown. Cheap no-op once the volume is already builder-owned. +sudo chown -R builder:builder /src/target + +VERSION=$(grep '^version = ' Cargo.toml | head -1 | cut -d'"' -f2) + +echo "Beaver version: ${VERSION}" +echo "Target arch: ${ARCH}" + +# abuild looks for an APKBUILD in CWD. Our APKBUILD is templated, so +# stage the rendered version in a writable scratch dir under /tmp and +# cd there. +WORK=/tmp/apk-build +rm -rf "$WORK" +mkdir -p "$WORK" +sed -e "s/@VERSION@/${VERSION}/g" \ + packaging/alpine/APKBUILD.in > "$WORK/APKBUILD" +cp packaging/alpine/beaver.post-install "$WORK/beaver.post-install" +cp packaging/alpine/beaver.post-deinstall "$WORK/beaver.post-deinstall" +cd "$WORK" + +# Empty source= still wants a checksum line — `abuild checksum` writes +# an empty sha512sums. +abuild checksum + +# Build + package. abuild internally invokes fakeroot for the package +# step; we don't wrap it ourselves. +abuild -F + +# abuild stores .apks under $HOME/packages///, where +# is the basename of the parent directory of the APKBUILD's directory. +# Find ours instead of computing the path. +APK_PATH=$(find "$HOME/packages" -name "beaver-${VERSION}-r*.apk" | head -1) +if [ -z "$APK_PATH" ]; then + echo "abuild produced no .apk — check the log above" + exit 1 +fi + +# Copy to /src/dist/. macOS Docker Desktop typically allows builder +# (UID 1000) to write to bind-mounted dirs, but fall back to sudo if +# the bind-mount perms are restricted. +if ! mkdir -p /src/dist 2>/dev/null; then + sudo mkdir -p /src/dist + sudo chown builder:builder /src/dist +fi + +DEST="/src/dist/beaver_${VERSION}_${ARCH}.apk" +cp "$APK_PATH" "$DEST" 2>/dev/null || sudo cp "$APK_PATH" "$DEST" + +echo "Built ${DEST}" +ls -lh "$DEST" diff --git a/scripts/build-apk-pmbootstrap.sh b/scripts/build-apk-pmbootstrap.sh new file mode 100755 index 0000000..39028fc --- /dev/null +++ b/scripts/build-apk-pmbootstrap.sh @@ -0,0 +1,167 @@ +#!/bin/sh +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# Build an Alpine .apk for Beaver with pmbootstrap instead of Docker, using +# postmarketOS's native build tooling. Single-arch native (aarch64 runs under +# QEMU when the host is x86_64 — pmbootstrap sets that up itself). +# +# This is the pmbootstrap counterpart of scripts/build-apk.sh. Prefer it on a +# Linux host that already runs pmbootstrap; the Docker path stays for macOS. +# +# Prerequisites (on a LINUX host): +# - pmbootstrap installed and `pmbootstrap init` already run once. +# - an `edge`-based channel: the chroot's cargo/rust must be recent enough for +# edition = "2024" (Alpine stable trails; edge is current). +# +# Usage: +# ./scripts/build-apk-pmbootstrap.sh # defaults to aarch64 +# ./scripts/build-apk-pmbootstrap.sh aarch64 # for pmOS phones +# ./scripts/build-apk-pmbootstrap.sh x86_64 # for Alpine VM testing +# +# Output: dist/beaver__.apk + +set -eu + +ARCH="${1:-aarch64}" + +case "$ARCH" in + aarch64|x86_64) ;; + *) echo "Unknown arch: $ARCH (expected aarch64 or x86_64)"; exit 1 ;; +esac + +# Repo root = parent of this script's directory. +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +REPO_ROOT=$(cd -- "$SCRIPT_DIR/.." && pwd) + +command -v pmbootstrap >/dev/null 2>&1 || { + echo "pmbootstrap not found on PATH. Install it and run 'pmbootstrap init'." + exit 1 +} + +command -v rsync >/dev/null 2>&1 || { + echo "rsync not found on PATH (needed to stage the source tree)." + exit 1 +} + +# The work dir doubles as our "is pmbootstrap initialized?" probe. +WORK=$(pmbootstrap config work 2>/dev/null || true) +[ -n "$WORK" ] && [ -d "$WORK" ] || { + echo "pmbootstrap does not look initialized (no work dir). Run 'pmbootstrap init' first." + exit 1 +} + +# The official pmaports supplies the channel + repo/mirror config. Prefer the +# configured value; fall back to the standard cache location. +PMAPORTS=$(pmbootstrap config aports 2>/dev/null | head -1 || true) +[ -n "$PMAPORTS" ] && [ -d "$PMAPORTS" ] || PMAPORTS="$WORK/cache_git/pmaports" +[ -f "$PMAPORTS/pmaports.cfg" ] || { + echo "pmaports.cfg not found under '$PMAPORTS'. Run 'pmbootstrap init' first." + exit 1 +} + +VERSION=$(grep '^version = ' "$REPO_ROOT/Cargo.toml" | head -1 | cut -d'"' -f2) +[ -n "$VERSION" ] || { echo "Could not read version from Cargo.toml"; exit 1; } + +echo "Beaver ${VERSION} -> ${ARCH} via pmbootstrap" +echo " work: ${WORK}" +echo " pmaports: ${PMAPORTS}" + +# Stage a throwaway aports overlay rather than editing the user's pmaports (which +# would leave the pmaports git tree dirty). pmbootstrap discovers packages by +# globbing /*//APKBUILD and reads the channel from the overlay's own +# pmaports.cfg, so copy the official cfg verbatim (channel/version stay identical) +# and drop our recipe under temp/beaver/. +OVERLAY=$(mktemp -d) +STAGING=$(mktemp -d) +trap 'rm -rf "$OVERLAY" "$STAGING"' EXIT +cp "$PMAPORTS/pmaports.cfg" "$OVERLAY/pmaports.cfg" +PKGDIR="$OVERLAY/temp/beaver" +mkdir -p "$PKGDIR" + +# Render the APKBUILD from the shared recipe (packaging/alpine/APKBUILD.in), +# adapting it for pmbootstrap: +# - drop builddir="/src": with --src, pmbootstrap symlinks the copied source +# to abuild's default $builddir, so build()/package() find the tree there; +# a hardcoded /src would break that symlink. +# - extend makedepends: the Docker image installs these on top of the recipe's +# makedepends (Alpine's abuild chroot won't pull them in transitively), but +# pmbootstrap's chroot only has what makedepends lists. So add: +# * bash - mozjs-sys's makefile.cargo uses bash-isms (`[[ ]]`); +# * cargo - Rust toolchain (Docker used rustup instead). +# * clang-dev, +# clang-libclang, +# llvm-dev - libclang.so that mozjs/mozangle bindgen dlopens. +# * git - some build scripts shell out to it. +# * gst-plugins-good, +# gst-libav - runtime depends abuild verifies at build time; no +# -dev package pulls them in. +# source="" stays; pmbootstrap appends its own fetch()/unpack() overriding it. +# build()'s musl workarounds (-crt-static, cleared CFLAGS/...) come through +# unchanged. +sed \ + -e "s/@VERSION@/${VERSION}/g" \ + -e '/^builddir="\/src"$/d' \ + "$REPO_ROOT/packaging/alpine/APKBUILD.in" \ +| awk ' + { print } + /^makedepends="/ { + print "\tbash" + print "\tcargo" + print "\tclang-dev" + print "\tclang-libclang" + print "\tllvm-dev" + print "\tgit" + print "\tgst-plugins-good" + print "\tgst-libav" + } +' > "$PKGDIR/APKBUILD" + +# Install scripts named by install= must sit next to the APKBUILD. +cp "$REPO_ROOT/packaging/alpine/beaver.post-install" "$PKGDIR/" +cp "$REPO_ROOT/packaging/alpine/beaver.post-deinstall" "$PKGDIR/" + +# pmbootstrap's --src feeds `/.gitignore` to rsync as `--exclude-from`. Our +# root .gitignore lists `source/` (the Servo fork this workspace path-depends on, +# see Cargo.toml) alongside `target/` `dist/` `node_modules/`. Pointing --src +# straight at the repo would therefore DROP source/ and the build would fail. So +# stage a pruned copy that KEEPS source/ but drops the build artifacts (this is +# also where target/ gets excluded, so the chroot copy stays small), and --src +# that. The staged tree carries no .gitignore, so pmbootstrap copies it wholesale +# (minus its own hardcoded `.git/`). Excludes are unanchored, so they also prune +# nested `source/target/` and `source/.git/`. +echo "Staging source (excluding target/, dist/, node_modules/, .git/)..." +rsync -a \ + --exclude='.git/' \ + --exclude='target/' \ + --exclude='dist/' \ + --exclude='node_modules/' \ + "$REPO_ROOT/" "$STAGING/" +rm -f "$STAGING/.gitignore" + +echo +echo "Building via pmbootstrap. The first cross-arch build sets up QEMU, and the" +echo "abuild step compiles the whole workspace (Servo + Beaver) in the chroot." +echo + +# -p (global, before the subcommand): official pmaports first for channel/repo +# config, then our overlay for the beaver recipe. --src builds from the staged +# tree; --arch selects the target; --force always rebuilds. +pmbootstrap -p "${PMAPORTS},${OVERLAY}" \ + build --src "$STAGING" --arch "$ARCH" --force beaver + +# Output is /packages///. Find it rather than compute the +# channel (pmbootstrap can transform it, e.g. a systemd- prefix). +APK=$(find "$WORK/packages" -path "*/${ARCH}/beaver-${VERSION}-r*.apk" 2>/dev/null | head -1) +[ -n "$APK" ] || { + echo "Build produced no .apk under ${WORK}/packages — check the log above." + exit 1 +} + +mkdir -p "$REPO_ROOT/dist" +DEST="$REPO_ROOT/dist/beaver_${VERSION}_${ARCH}.apk" +cp "$APK" "$DEST" + +echo +echo "Done: ${DEST}" +ls -lh "$DEST" + diff --git a/scripts/build-apk.sh b/scripts/build-apk.sh new file mode 100755 index 0000000..349b14b --- /dev/null +++ b/scripts/build-apk.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# Build an Alpine .apk for Beaver inside a Docker container. +# Single-arch native — no cross-compile. +# +# Usage: +# ./scripts/build-apk.sh # defaults to aarch64 +# ./scripts/build-apk.sh aarch64 # for pmOS phones +# ./scripts/build-apk.sh x86_64 # for Alpine VM testing +# +# Output: dist/beaver__.apk + +set -e + +ARCH="${1:-aarch64}" +PROFILE="${2:-release}" + +case "$ARCH" in + aarch64) + DOCKERFILE=support/docker/Dockerfile.apk-aarch64 + IMAGE_NAME=beaver-apk-aarch64-builder + ;; + x86_64) + DOCKERFILE=support/docker/Dockerfile.apk-x86_64 + IMAGE_NAME=beaver-apk-x86_64-builder + ;; + *) + echo "Unknown arch: $ARCH (expected aarch64 or x86_64)"; exit 1 ;; +esac + +TARGET_VOLUME="beaver-apk-target-${ARCH}" + +if ! docker image inspect "$IMAGE_NAME" &>/dev/null; then + echo "Building packaging image (${IMAGE_NAME})..." + docker build -t "$IMAGE_NAME" -f "$DOCKERFILE" . +fi + +echo "Building beaver-shell .apk for ${ARCH} (profile: ${PROFILE})..." + +docker run --rm \ + --memory=24g \ + -v "$(pwd):/src" \ + -v "${TARGET_VOLUME}:/src/target" \ + -e ARCH="$ARCH" \ + -e PROFILE="$PROFILE" \ + -e CARGO_BUILD_JOBS="${CARGO_BUILD_JOBS:-8}" \ + "$IMAGE_NAME" \ + /src/scripts/_build-apk-inside.sh + +echo "Done. Output in dist/." +ls -lh dist/beaver_*_${ARCH}.apk 2>/dev/null || true diff --git a/support/docker/Dockerfile.apk-aarch64 b/support/docker/Dockerfile.apk-aarch64 new file mode 100644 index 0000000..71b18b4 --- /dev/null +++ b/support/docker/Dockerfile.apk-aarch64 @@ -0,0 +1,85 @@ +# Build environment for Beaver aarch64 .apks (Alpine / postmarketOS). +# Pinned to linux/arm64 so the build is native aarch64 inside the +# container. On Apple Silicon this runs natively (fast); on Intel macs +# and amd64 Linux hosts it runs under QEMU emulation. +# +# A separate Dockerfile.apk-x86_64 handles the dev/VM target. We keep +# each image single-arch native — no cross-compilation toolchain, no +# multi-arch package juggling. +# +# Usage: docker build -t beaver-apk-aarch64-builder \ +# -f support/docker/Dockerfile.apk-aarch64 . + +FROM --platform=linux/arm64 alpine:3.20 + +# Build deps (mirrors APKBUILD's makedepends + abuild itself). +# alpine-sdk pulls in abuild and the rest of the packaging toolchain. +# Rust comes from rustup (installed below as the `builder` user) — Alpine +# 3.20's apk-shipped rust is 1.78, too old for edition="2024". +RUN apk add --no-cache \ + alpine-sdk \ + sudo \ + bash \ + build-base \ + musl-dev \ + pkgconf \ + cmake \ + python3 \ + curl \ + git \ + clang \ + clang-dev \ + clang-libclang \ + lld \ + llvm \ + llvm-dev \ + imagemagick \ + gstreamer-dev \ + gst-plugins-base-dev \ + gst-plugins-bad-dev \ + fontconfig-dev \ + freetype-dev \ + harfbuzz-dev \ + gtk+3.0-dev \ + dbus-dev \ + libxkbcommon-dev \ + mesa-dev \ + mesa-gl \ + openssl-dev \ + wayland-dev \ + libxcb-dev \ + libxt-dev \ + pulseaudio-dev \ + eudev-dev \ + alsa-lib-dev \ + # Runtime-only GStreamer plugins listed in APKBUILD's depends=. + # abuild verifies depends= are installed in the build env (stricter + # than dpkg-deb), and these don't have -dev counterparts to pull + # them in transitively, so list them explicitly. + gst-plugins-good \ + gst-libav \ + ca-certificates + +# abuild refuses to run as root (its `id -u` check is unconditional — +# fakeroot doesn't fool it). The Alpine convention is a regular user +# in the abuild group; passwordless sudo gives that user a fallback for +# writing to bind-mounted dirs whose perms might not be permissive. +RUN adduser -D -u 1000 builder && \ + addgroup builder abuild && \ + echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +USER builder +WORKDIR /home/builder + +# Install Rust via rustup so we get a current stable (Alpine's apk rust +# trails the language). Default target on a musl host is the matching +# *-unknown-linux-musl, which is exactly what we want for the .apk. +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --default-toolchain stable +ENV PATH="/home/builder/.cargo/bin:${PATH}" + +# abuild expects signing keys at ~/.abuild/. Generate a throwaway pair +# at image-build time so containers don't have to re-key on every run. +RUN abuild-keygen -an + +WORKDIR /src diff --git a/support/docker/Dockerfile.apk-x86_64 b/support/docker/Dockerfile.apk-x86_64 new file mode 100644 index 0000000..ed6b217 --- /dev/null +++ b/support/docker/Dockerfile.apk-x86_64 @@ -0,0 +1,71 @@ +# Build environment for Beaver x86_64 .apks (Alpine, dev/VM). +# Pinned to linux/amd64 so the build is native x86_64 inside the +# container. On Intel macs and amd64 Linux hosts this runs natively; +# on Apple Silicon it runs under QEMU emulation. +# +# This image is mainly for testing the .apk in an Alpine VM before +# flashing to an aarch64 phone. The aarch64 .apk for actual pmOS +# devices is built by Dockerfile.apk-aarch64. +# +# Usage: docker build -t beaver-apk-x86_64-builder \ +# -f support/docker/Dockerfile.apk-x86_64 . + +FROM --platform=linux/amd64 alpine:3.20 + +RUN apk add --no-cache \ + alpine-sdk \ + sudo \ + bash \ + build-base \ + musl-dev \ + pkgconf \ + cmake \ + python3 \ + curl \ + git \ + clang \ + clang-dev \ + clang-libclang \ + lld \ + llvm \ + llvm-dev \ + imagemagick \ + gstreamer-dev \ + gst-plugins-base-dev \ + gst-plugins-bad-dev \ + fontconfig-dev \ + freetype-dev \ + harfbuzz-dev \ + gtk+3.0-dev \ + dbus-dev \ + libxkbcommon-dev \ + mesa-dev \ + mesa-gl \ + openssl-dev \ + wayland-dev \ + libxcb-dev \ + libxt-dev \ + pulseaudio-dev \ + eudev-dev \ + alsa-lib-dev \ + gst-plugins-good \ + gst-libav \ + ca-certificates + +# abuild refuses to run as root; create a non-root builder in the abuild +# group, with passwordless sudo as a fallback for bind-mount write perms. +RUN adduser -D -u 1000 builder && \ + addgroup builder abuild && \ + echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +USER builder +WORKDIR /home/builder + +# Rust via rustup — Alpine's apk rust is too old for edition="2024". +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ + sh -s -- -y --default-toolchain stable +ENV PATH="/home/builder/.cargo/bin:${PATH}" + +RUN abuild-keygen -an + +WORKDIR /src