Build each package in an isolated container
This commit is contained in:
7 files changed
+361
-131
No files matched your search
@@ -7,6 +7,20 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-isolation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Prepare fixture builder
|
||||
run: docker build -t omarchy-build-isolation-test -f tests/build-isolation.Dockerfile tests
|
||||
- name: Verify isolated builds with real pacman transactions
|
||||
env:
|
||||
CONTAINER_ENGINE: docker
|
||||
TEST_BUILDER_IMAGE: omarchy-build-isolation-test
|
||||
run: tests/build-isolation.sh
|
||||
|
||||
self-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
src/
|
||||
logs/
|
||||
build-output/
|
||||
cache/
|
||||
pkgs.omarchy.org/
|
||||
pkgbuilds/*/.SRCINFO
|
||||
pkgbuilds/*/.gitignore
|
||||
|
||||
@@ -858,12 +858,34 @@ bin/repo sync --arch aarch64
|
||||
|
||||
The build system automatically handles inter-package dependencies:
|
||||
|
||||
1. Parses `depends=()` and `makedepends=()` from PKGBUILDs
|
||||
2. Builds in correct order
|
||||
3. Makes newly-built packages available via temporary `[omarchy-build]` repo
|
||||
1. Plans dependency order once, including `depends`, `makedepends`,
|
||||
`checkdepends`, and their architecture-specific arrays.
|
||||
2. Builds each package in a fresh container. Installed packages and changes
|
||||
to the container's system files cannot carry over to the next build.
|
||||
3. Shares successful artifacts through the temporary `[omarchy-build]` repo,
|
||||
installing newly built prerequisites in each consumer's container.
|
||||
4. Blocks consumers of a failed prerequisite while continuing independent
|
||||
builds. Any failure still prevents the release from publishing.
|
||||
|
||||
Example: If `aether` depends on `hyprshade`, `hyprshade` is built first.
|
||||
|
||||
Isolation also lets the release and dev Omarchy pairs build in the same run:
|
||||
Flea can install `omarchy` without preventing `omarchy-dev` from installing
|
||||
its conflicting settings package in a different container.
|
||||
|
||||
Pacman downloads are cached under `cache/pacman/<channel>/<arch>/` across
|
||||
containers and runs. The installed package database is never shared. Each
|
||||
container updates its base system before resolving build dependencies, so a
|
||||
cached builder image cannot cause a partial system upgrade. The existing
|
||||
`OMARCHY_KEEP_BUILD_WORKSPACE`, `OMARCHY_SKIP_BUILDER_IMAGE`, and
|
||||
`OMARCHY_DEFER_RUNTIME_DEPS` flags retain their behavior.
|
||||
|
||||
`tests/build-isolation.sh` exercises conflicting package pairs, failed
|
||||
prerequisites, resumed builds, cache replacement, and deferred dependencies
|
||||
using real containers and pacman transactions. It uses the prepared builder
|
||||
image, or an image named by `TEST_BUILDER_IMAGE`; CI builds the small fixture
|
||||
image in `tests/build-isolation.Dockerfile`.
|
||||
|
||||
## Version Management
|
||||
|
||||
Packages are only rebuilt if:
|
||||
|
||||
@@ -226,17 +226,24 @@ else
|
||||
build_docker_image "$BUILD_DIR" "$ARCH" "$MIRROR"
|
||||
fi
|
||||
|
||||
print_info "Running package build..."
|
||||
print_info "Planning isolated package builds..."
|
||||
|
||||
# Create output directories if they don't exist
|
||||
mkdir -p "$BUILD_OUTPUT_DIR"
|
||||
mkdir -p "$REPO_DIR"
|
||||
|
||||
# Share downloaded archives, never /var/lib/pacman or an installed root.
|
||||
# Channel/architecture separation preserves each mirror's dependency set.
|
||||
PACKAGE_CACHE_DIR="$BUILD_ROOT/cache/pacman/$MIRROR/$ARCH"
|
||||
mkdir -p "$PACKAGE_CACHE_DIR"
|
||||
PLAN_DIR=$(mktemp -d "$SRC_DIR/build-plan.XXXXXX")
|
||||
trap 'rm -rf "$PLAN_DIR"' EXIT
|
||||
|
||||
# Rootful Docker writes as the image uid, so retain its existing permission
|
||||
# workaround. Rootless Podman uses keep-id and must leave ownership/modes alone.
|
||||
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
|
||||
make_dir_writable "$BUILD_OUTPUT_DIR"
|
||||
make_dir_writable "$REPO_DIR"
|
||||
make_dir_writable "$PLAN_DIR"
|
||||
fi
|
||||
|
||||
# Build Docker arguments
|
||||
@@ -247,8 +254,11 @@ DOCKER_ARGS=(
|
||||
-e PACKAGES="$PACKAGES"
|
||||
-e OMARCHY_RC_PINS="${OMARCHY_RC_PINS:-}"
|
||||
-e DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS"
|
||||
-e BUILD_PLAN_DIR=/build-plan
|
||||
-v "$PLAN_DIR:/build-plan"
|
||||
-v "$PACKAGE_CACHE_DIR:/var/cache/pacman/pkg"
|
||||
-v "$BUILD_ROOT/build-output:/build-output"
|
||||
-v "$REPO_ROOT:/pkgs.omarchy.org"
|
||||
-v "$REPO_ROOT:/pkgs.omarchy.org:ro"
|
||||
-v "$BUILD_DIR:/build:ro"
|
||||
-v "$BUILD_ROOT/helpers:/helpers:ro"
|
||||
-v "$BUILD_ROOT/pkgbuilds:/pkgbuilds:ro"
|
||||
@@ -260,18 +270,67 @@ if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
|
||||
DOCKER_ARGS+=(-v "$SRC_DIR:/src")
|
||||
fi
|
||||
|
||||
# Run the builder with assembled args
|
||||
# Plan once against the published database, then keep that order throughout
|
||||
# the run. Each package sees the staged artifacts but starts with a fresh
|
||||
# pacman database and root filesystem, even after a failed build.
|
||||
PLATFORM_ARG=$(get_platform_arg "$ARCH")
|
||||
|
||||
"$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" "$PLATFORM_ARG" "${DOCKER_ARGS[@]}" "$IMAGE_TAG" /build/build.sh
|
||||
"$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" "$PLATFORM_ARG" "${DOCKER_ARGS[@]}" \
|
||||
-e DRY_RUN=true "$IMAGE_TAG" /build/build.sh
|
||||
|
||||
BUILD_RESULT=$?
|
||||
mapfile -t ORDERED_PACKAGES < "$PLAN_DIR/packages"
|
||||
mapfile -t SKIPPED_PACKAGES < "$PLAN_DIR/skipped"
|
||||
SUCCESSFUL_PACKAGES=()
|
||||
FAILED_PACKAGES=()
|
||||
BLOCKED_PACKAGES=()
|
||||
declare -A BUILD_STATUS=()
|
||||
|
||||
for package in "${ORDERED_PACKAGES[@]}"; do
|
||||
blocked_by=""
|
||||
while read -r consumer dependency; do
|
||||
if [[ "$consumer" == "$package" && "${BUILD_STATUS[$dependency]:-}" != success ]]; then
|
||||
blocked_by="$dependency"
|
||||
break
|
||||
fi
|
||||
done < "$PLAN_DIR/dependencies"
|
||||
|
||||
if [[ -n "$blocked_by" ]]; then
|
||||
print_warning "$package blocked by unsuccessful dependency: $blocked_by"
|
||||
BUILD_STATUS[$package]=blocked
|
||||
BLOCKED_PACKAGES+=("$package")
|
||||
continue
|
||||
fi
|
||||
|
||||
print_info "Building $package in a fresh container..."
|
||||
if "$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" "$PLATFORM_ARG" "${DOCKER_ARGS[@]}" \
|
||||
-e BUILD_PACKAGE="$package" "$IMAGE_TAG" /build/build.sh; then
|
||||
BUILD_STATUS[$package]=success
|
||||
SUCCESSFUL_PACKAGES+=("$package")
|
||||
else
|
||||
BUILD_STATUS[$package]=failed
|
||||
FAILED_PACKAGES+=("$package")
|
||||
fi
|
||||
done
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
if [[ $BUILD_RESULT -eq 0 ]]; then
|
||||
print_success "Build completed successfully!"
|
||||
else
|
||||
print_header "Build Summary"
|
||||
echo " Total packages: ${#ORDERED_PACKAGES[@]}"
|
||||
echo " Built: ${#SUCCESSFUL_PACKAGES[@]}"
|
||||
echo " Skipped: ${#SKIPPED_PACKAGES[@]} (up-to-date or excluded)"
|
||||
echo " Failed: ${#FAILED_PACKAGES[@]}"
|
||||
echo " Blocked: ${#BLOCKED_PACKAGES[@]}"
|
||||
|
||||
if (( ${#FAILED_PACKAGES[@]} + ${#BLOCKED_PACKAGES[@]} )); then
|
||||
if (( ${#FAILED_PACKAGES[@]} )); then
|
||||
echo "Failed packages:"
|
||||
printf ' - %s\n' "${FAILED_PACKAGES[@]}"
|
||||
fi
|
||||
if (( ${#BLOCKED_PACKAGES[@]} )); then
|
||||
echo "Packages blocked by failed dependencies:"
|
||||
printf ' - %s\n' "${BLOCKED_PACKAGES[@]}"
|
||||
fi
|
||||
print_warning "Some packages failed (see details above)"
|
||||
exit $BUILD_RESULT
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Build completed successfully!"
|
||||
+88
-117
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Build script - builds packages based on package metadata
|
||||
# Plan a run or build one planned package in an isolated container.
|
||||
# Unscoped edge builds exclude skip_build packages. Stable also requires the fast release ring.
|
||||
# Explicit --package selections may build packages with skip_build=true.
|
||||
|
||||
@@ -11,6 +11,10 @@ ARCH=${ARCH:-x86_64}
|
||||
export -n ARCH
|
||||
MIRROR=${MIRROR:-edge}
|
||||
DRY_RUN=${DRY_RUN:-false}
|
||||
# bin/build plans the whole run, then invokes this script once per package in
|
||||
# a fresh container. PACKAGES retains the original request for validation.
|
||||
BUILD_PACKAGE=${BUILD_PACKAGE:-}
|
||||
BUILD_PLAN_DIR=${BUILD_PLAN_DIR:-}
|
||||
PKGBUILDS_DIR=${PKGBUILDS_DIR:-/pkgbuilds}
|
||||
BUILD_OUTPUT_DIR=${BUILD_OUTPUT_DIR:-/build-output/$MIRROR/$ARCH}
|
||||
FINAL_OUTPUT_DIR=${FINAL_OUTPUT_DIR:-/pkgs.omarchy.org/$MIRROR/$ARCH}
|
||||
@@ -48,6 +52,14 @@ if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" != true ]]; then
|
||||
if [[ -z "$BUILD_PACKAGE" || -z "$BUILD_PLAN_DIR" ]]; then
|
||||
echo "Use bin/build to plan and run isolated package builds" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Fxq -- "$BUILD_PACKAGE" "$BUILD_PLAN_DIR/packages"; then
|
||||
echo "Package is not in the build plan: $BUILD_PACKAGE" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Import GPG keys
|
||||
/build/import-gpg-keys.sh || exit 1
|
||||
|
||||
@@ -59,7 +71,7 @@ if [[ "$DRY_RUN" != true ]]; then
|
||||
# that breaks the new packages (imagemagick wanting GLIBC_2.44, etc).
|
||||
# Done before the Omarchy repos are added so only core/extra participate.
|
||||
echo "==> Updating build container packages..."
|
||||
sudo pacman -Syu --noconfirm
|
||||
sudo pacman -Syu --noconfirm || exit 1
|
||||
|
||||
# Configure Omarchy repositories for dependency resolution
|
||||
echo "==> Configuring Omarchy repositories for dependency resolution..."
|
||||
@@ -72,22 +84,39 @@ if [[ "$DRY_RUN" != true ]]; then
|
||||
echo " -> omarchy-build (priority 1): $BUILD_OUTPUT_DIR"
|
||||
|
||||
# Initialize empty build database if it doesn't exist
|
||||
cd "$BUILD_OUTPUT_DIR"
|
||||
cd "$BUILD_OUTPUT_DIR" || exit 1
|
||||
if [[ ! -f "omarchy-build.db.tar.zst" ]]; then
|
||||
# Create an empty database
|
||||
repo-add omarchy-build.db.tar.zst >/dev/null 2>&1
|
||||
ln -sf omarchy-build.db.tar.zst omarchy-build.db
|
||||
repo-add omarchy-build.db.tar.zst >/dev/null 2>&1 || exit 1
|
||||
ln -sf omarchy-build.db.tar.zst omarchy-build.db || exit 1
|
||||
fi
|
||||
# Fold any packages already in the workspace into the database, whether
|
||||
# they came with an existing database or were dropped in by an earlier
|
||||
# workflow job (OMARCHY_KEEP_BUILD_WORKSPACE). Without this a seeded
|
||||
# workspace with no database would leave those packages invisible to
|
||||
# dependency resolution.
|
||||
if ls *.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | grep -q .; then
|
||||
echo "==> Rebuilding build database from existing packages..."
|
||||
ls *.pkg.tar.* | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | xargs -r repo-add omarchy-build.db.tar.zst >/dev/null 2>&1
|
||||
ln -sf omarchy-build.db.tar.zst omarchy-build.db
|
||||
staged_packages=()
|
||||
for staged in *.pkg.tar.*; do
|
||||
[[ -f "$staged" && "$staged" != *.sig ]] || continue
|
||||
staged_packages+=("$staged")
|
||||
done
|
||||
if (( ${#staged_packages[@]} )); then
|
||||
# Seed once per run. After that, only successful builds update the DB;
|
||||
# rescanning in every container could reintroduce a failed build's partial
|
||||
# outputs or overwrite the new version with an older kept artifact.
|
||||
if [[ ! -e "$BUILD_PLAN_DIR/repository-initialized" ]]; then
|
||||
echo "==> Rebuilding build database from existing packages..."
|
||||
repo-add omarchy-build.db.tar.zst "${staged_packages[@]}" >/dev/null 2>&1 || exit 1
|
||||
ln -sf omarchy-build.db.tar.zst omarchy-build.db || exit 1
|
||||
fi
|
||||
# A resumed/repeated build can produce different bytes under the same
|
||||
# filename. Never let the shared download cache substitute older bytes
|
||||
# for the staged artifacts described by this run's database.
|
||||
for staged in "${staged_packages[@]}"; do
|
||||
sudo rm -f "/var/cache/pacman/pkg/$staged" "/var/cache/pacman/pkg/$staged.sig" || exit 1
|
||||
done
|
||||
fi
|
||||
touch "$BUILD_PLAN_DIR/repository-initialized" || exit 1
|
||||
|
||||
# Add omarchy repo if it has a database (stable packages)
|
||||
if [[ -f "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" ]] || [[ -f "$FINAL_OUTPUT_DIR/omarchy.db" ]]; then
|
||||
@@ -96,7 +125,7 @@ if [[ "$DRY_RUN" != true ]]; then
|
||||
fi
|
||||
|
||||
# Sync pacman database
|
||||
sudo pacman -Sy
|
||||
sudo pacman -Sy || exit 1
|
||||
fi
|
||||
|
||||
echo "==> Package Builder"
|
||||
@@ -109,8 +138,6 @@ if [[ "$DRY_RUN" == true ]]; then
|
||||
echo "==> Dry run: yes (plan only; makepkg will not run)"
|
||||
fi
|
||||
|
||||
FAILED_PACKAGES=""
|
||||
SUCCESSFUL_PACKAGES=""
|
||||
SKIPPED_PACKAGES=""
|
||||
|
||||
# Find package directory
|
||||
@@ -262,19 +289,34 @@ install_deferred_build_dependencies() {
|
||||
# Build a package
|
||||
build_package() {
|
||||
local pkg="$1"
|
||||
local pkgdir=$(find_package_dir "$pkg")
|
||||
local pkgdir
|
||||
pkgdir=$(find_package_dir "$pkg") || return 1
|
||||
|
||||
echo ""
|
||||
echo " -> Processing: $pkg"
|
||||
|
||||
# Install this consumer's freshly built prerequisites in its own container.
|
||||
# Qualifying the repository also upgrades an older dependency baked into
|
||||
# the base image, even if that version would satisfy makepkg's check.
|
||||
if [[ "$DEFER_RUNTIME_DEPS" != true ]]; then
|
||||
local consumer dependency
|
||||
local -a built_deps=()
|
||||
while read -r consumer dependency; do
|
||||
[[ "$consumer" == "$pkg" ]] && built_deps+=("omarchy-build/$dependency")
|
||||
done < "$BUILD_PLAN_DIR/dependencies"
|
||||
if (( ${#built_deps[@]} )); then
|
||||
echo " Installing freshly built dependencies for $pkg..."
|
||||
sudo /usr/local/bin/pacman-for-makepkg -S --needed --noconfirm -- "${built_deps[@]}" || return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Copy to build directory
|
||||
cd /src
|
||||
rm -rf "$pkg"
|
||||
cp -r "$pkgdir" "$pkg"
|
||||
cd /src || return 1
|
||||
rm -rf "$pkg" || return 1
|
||||
cp -r "$pkgdir" "$pkg" || return 1
|
||||
cd "/src/$pkg" || return 1
|
||||
|
||||
refresh_vcs_pkgver_preserving_local_pkgrel "$pkg" || {
|
||||
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -283,7 +325,6 @@ build_package() {
|
||||
|
||||
if [[ -z "$pkgbuild_version" ]]; then
|
||||
echo " Failed to read PKGBUILD version"
|
||||
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -322,7 +363,6 @@ build_package() {
|
||||
# is installed in one verified transaction downstream. Only the
|
||||
# build-time dependencies are installed, then makepkg skips the check.
|
||||
install_deferred_build_dependencies "$pkg" || {
|
||||
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
|
||||
return 1
|
||||
}
|
||||
makepkg_flags=(-cf --noconfirm --nodeps)
|
||||
@@ -340,11 +380,9 @@ build_package() {
|
||||
|
||||
if [[ ${#package_files[@]} -eq 0 ]]; then
|
||||
echo " Makepkg produced no package files for $pkg"
|
||||
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local dependency_pkg_file=""
|
||||
local -a new_pkgs=()
|
||||
local pkg_path pkg_file
|
||||
for pkg_path in "${package_files[@]}"; do
|
||||
@@ -358,55 +396,27 @@ build_package() {
|
||||
fi
|
||||
|
||||
echo " Expected package file was not produced: $pkg_file"
|
||||
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cp "$pkg_file" "$BUILD_OUTPUT_DIR/"
|
||||
cp "$pkg_file" "$BUILD_OUTPUT_DIR/" || return 1
|
||||
new_pkgs+=("$pkg_file")
|
||||
|
||||
if [[ "$(bsdtar -xOf "$pkg_file" .PKGINFO 2>/dev/null | sed -n 's/^pkgname = //p')" == "$pkg" ]]; then
|
||||
dependency_pkg_file="$BUILD_OUTPUT_DIR/$pkg_file"
|
||||
fi
|
||||
done
|
||||
|
||||
cd "$BUILD_OUTPUT_DIR"
|
||||
cd "$BUILD_OUTPUT_DIR" || return 1
|
||||
|
||||
# Add every output from this build, including split packages.
|
||||
if [[ ${#new_pkgs[@]} -gt 0 ]]; then
|
||||
repo-add omarchy-build.db.tar.zst "${new_pkgs[@]}" >/dev/null 2>&1
|
||||
ln -sf omarchy-build.db.tar.zst omarchy-build.db
|
||||
sudo pacman -Sy >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
cd /src/$pkg
|
||||
|
||||
# A lower-priority official repository may contain an older package with
|
||||
# the same name. Install the exact artifact we just built before building
|
||||
# its consumers, so pacman cannot select that older provider instead.
|
||||
if [[ "${INSTALL_PACKAGES[$pkg]:-}" == "1" ]]; then
|
||||
if [[ -z "$dependency_pkg_file" ]]; then
|
||||
echo " Could not find the built $pkg package to install as a dependency"
|
||||
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " Installing freshly built $pkg for dependent packages..."
|
||||
if ! sudo /usr/local/bin/pacman-for-makepkg -U --needed --noconfirm "$dependency_pkg_file"; then
|
||||
echo " Failed to install freshly built dependency $pkg"
|
||||
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
|
||||
return 1
|
||||
fi
|
||||
repo-add omarchy-build.db.tar.zst "${new_pkgs[@]}" >/dev/null 2>&1 || return 1
|
||||
ln -sf omarchy-build.db.tar.zst omarchy-build.db || return 1
|
||||
fi
|
||||
|
||||
echo " Successfully built $pkg"
|
||||
SUCCESSFUL_PACKAGES="$SUCCESSFUL_PACKAGES $pkg"
|
||||
return 0
|
||||
else
|
||||
echo " Makepkg failed for $pkg"
|
||||
echo " DEBUG: Files in build directory:"
|
||||
ls -lah *.pkg.tar.* 2>&1 | head -20 || echo " No package files found"
|
||||
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@@ -421,11 +431,17 @@ get_package_deps() {
|
||||
return
|
||||
fi
|
||||
|
||||
# Extract depends and makedepends, filter for packages in our pkgbuilds/
|
||||
# Include test dependencies and target-specific arrays: each container must
|
||||
# receive its prerequisites through the repository, not a previous build.
|
||||
(
|
||||
CARCH="$ARCH"
|
||||
source "$pkgbuild" 2>/dev/null
|
||||
echo "${depends[@]} ${makedepends[@]}"
|
||||
) | tr ' ' '\n' | while read -r dep; do
|
||||
for kind in depends makedepends checkdepends; do
|
||||
generic="${kind}[@]"
|
||||
specific="${kind}_${CARCH}[@]"
|
||||
printf '%s\n' "${!generic}" "${!specific}"
|
||||
done
|
||||
) | awk 'NF && !seen[$0]++' | while read -r dep; do
|
||||
# Strip version constraints (e.g., 'hyprshade>=1.0' -> 'hyprshade')
|
||||
dep=$(echo "$dep" | sed 's/[<>=].*$//')
|
||||
# Check if this dependency exists in our pkgbuilds
|
||||
@@ -522,15 +538,17 @@ collect_packages() {
|
||||
|
||||
# Main execution
|
||||
if [[ "$DRY_RUN" != true ]]; then
|
||||
cd "$SRC_DIR"
|
||||
cd "$SRC_DIR" || exit 1
|
||||
build_package "$BUILD_PACKAGE"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
TOTAL_COUNT=0
|
||||
|
||||
echo "==> Checking which packages need building..."
|
||||
|
||||
# First pass: determine which packages need building
|
||||
PACKAGES_TO_BUILD=()
|
||||
ORDERED_PACKAGES=()
|
||||
PLANNED_DEPENDENCIES=()
|
||||
|
||||
# If PACKAGES is specified, only check those packages
|
||||
if [[ -n "$PACKAGES" ]]; then
|
||||
@@ -581,7 +599,7 @@ fi
|
||||
if [[ ${#PACKAGES_TO_BUILD[@]} -eq 0 ]]; then
|
||||
echo "==> All packages are up to date!"
|
||||
else
|
||||
echo "==> ${#PACKAGES_TO_BUILD[@]} package(s) need building: ${PACKAGES_TO_BUILD[@]}"
|
||||
echo "==> ${#PACKAGES_TO_BUILD[@]} package(s) need building: ${PACKAGES_TO_BUILD[*]}"
|
||||
echo "==> Determining build order based on dependencies..."
|
||||
|
||||
# Second pass: order only the packages that need building
|
||||
@@ -604,6 +622,7 @@ else
|
||||
((unmet_deps_count[$pkg]++))
|
||||
# Track that dep blocks pkg from building
|
||||
blocks_packages[$dep]="${blocks_packages[$dep]} $pkg"
|
||||
PLANNED_DEPENDENCIES+=("$pkg $dep")
|
||||
fi
|
||||
done
|
||||
done < <(get_package_deps "$pkg")
|
||||
@@ -618,7 +637,6 @@ else
|
||||
done
|
||||
|
||||
# Build packages as dependencies become available
|
||||
ORDERED_PACKAGES=()
|
||||
while [[ ${#ready_to_build[@]} -gt 0 ]]; do
|
||||
# Take the first ready package
|
||||
current="${ready_to_build[0]}"
|
||||
@@ -640,63 +658,16 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Build order: ${ORDERED_PACKAGES[@]}"
|
||||
echo "==> Build order: ${ORDERED_PACKAGES[*]}"
|
||||
fi
|
||||
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
echo ""
|
||||
echo "==> Dry run complete. Packages that would build: ${ORDERED_PACKAGES[@]}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Determine which packages need to be installed for other packages being built
|
||||
declare -A INSTALL_PACKAGES
|
||||
for pkg in "${ORDERED_PACKAGES[@]}"; do
|
||||
while IFS= read -r dep; do
|
||||
[[ -z "$dep" ]] && continue
|
||||
# Only install if it's being built in this run
|
||||
for build_pkg in "${ORDERED_PACKAGES[@]}"; do
|
||||
[[ "$dep" == "$build_pkg" ]] && INSTALL_PACKAGES["$dep"]=1
|
||||
done
|
||||
done < <(get_package_deps "$pkg")
|
||||
done
|
||||
|
||||
if [[ ${#INSTALL_PACKAGES[@]} -gt 0 ]]; then
|
||||
echo "==> Packages needed as dependencies: ${!INSTALL_PACKAGES[@]}"
|
||||
fi
|
||||
|
||||
# Build packages in dependency order
|
||||
for pkg in "${ORDERED_PACKAGES[@]}"; do
|
||||
((TOTAL_COUNT++))
|
||||
build_package "$pkg"
|
||||
done
|
||||
# The host consumes plain data, never shell code or parsed human log output.
|
||||
if [[ -n "$BUILD_PLAN_DIR" ]]; then
|
||||
mkdir -p "$BUILD_PLAN_DIR" || exit 1
|
||||
printf '%s\n' "${ORDERED_PACKAGES[@]}" | sed '/^$/d' > "$BUILD_PLAN_DIR/packages" || exit 1
|
||||
printf '%s\n' "${PLANNED_DEPENDENCIES[@]}" | sed '/^$/d' > "$BUILD_PLAN_DIR/dependencies" || exit 1
|
||||
printf '%s\n' $SKIPPED_PACKAGES | sed '/^$/d' > "$BUILD_PLAN_DIR/skipped" || exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "==> Build Summary"
|
||||
echo "========================================"
|
||||
|
||||
# Count results
|
||||
SUCCESS_COUNT=$(echo $SUCCESSFUL_PACKAGES | wc -w)
|
||||
SKIPPED_COUNT=$(echo $SKIPPED_PACKAGES | wc -w)
|
||||
FAILED_COUNT=$(echo $FAILED_PACKAGES | wc -w)
|
||||
|
||||
echo " Total packages: $TOTAL_COUNT"
|
||||
echo " Built: $SUCCESS_COUNT"
|
||||
echo " Skipped: $SKIPPED_COUNT (already up-to-date)"
|
||||
echo " Failed: $FAILED_COUNT"
|
||||
|
||||
# List failures if any
|
||||
if [[ -n "$FAILED_PACKAGES" ]]; then
|
||||
echo ""
|
||||
echo "Failed packages:"
|
||||
for pkg in $FAILED_PACKAGES; do
|
||||
echo " - $pkg"
|
||||
done
|
||||
echo ""
|
||||
echo "==> Some packages failed to build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==> All packages processed successfully!"
|
||||
echo "==> Plan complete. Packages that would build: ${ORDERED_PACKAGES[*]}"
|
||||
@@ -0,0 +1,11 @@
|
||||
# Only the build runner is under test; source-free fixtures need no Omarchy
|
||||
# bootstrap, signing key, mirror, or production repository.
|
||||
FROM archlinux:base-devel
|
||||
RUN pacman -Syu --noconfirm git jq sudo && \
|
||||
useradd -m -u 1000 builder && \
|
||||
echo 'builder ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/builder && \
|
||||
printf '#!/bin/bash\nexec /usr/bin/pacman --ask 4 "$@"\n' > /usr/local/bin/pacman-for-makepkg && \
|
||||
chmod +x /usr/local/bin/pacman-for-makepkg && \
|
||||
mkdir /src && chown builder:builder /src
|
||||
USER builder
|
||||
WORKDIR /src
|
||||
Executable
+152
@@ -0,0 +1,152 @@
|
||||
#!/bin/bash
|
||||
# Integration regression: uses real containers, makepkg, repo-add and pacman.
|
||||
# Requires the normal builder image (or TEST_BUILDER_IMAGE) for this platform.
|
||||
set -euo pipefail
|
||||
|
||||
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
|
||||
source "$BUILD_ROOT/helpers/message-helpers.sh"
|
||||
source "$BUILD_ROOT/helpers/docker-helpers.sh"
|
||||
check_engine
|
||||
TEST_ARCH=$(docker_native_arch)
|
||||
TEST_BUILDER_IMAGE=${TEST_BUILDER_IMAGE:-omarchy-pkg-builder:latest-$TEST_ARCH-edge}
|
||||
TEST_ROOT=$(mktemp -d)
|
||||
trap 'rm -rf "$TEST_ROOT"' EXIT
|
||||
mkdir -p "$TEST_ROOT/bin" "$TEST_ROOT/pkgbuilds" "$TEST_ROOT/build-output/edge/$TEST_ARCH"
|
||||
cp "$BUILD_ROOT/bin/build" "$TEST_ROOT/bin/"
|
||||
cp -r "$BUILD_ROOT/build" "$BUILD_ROOT/helpers" "$TEST_ROOT/"
|
||||
# No external verification keys are needed for source-free fixtures.
|
||||
: > "$TEST_ROOT/build/gpg-keys.txt"
|
||||
|
||||
# The runner expects the normal image tag. Use an engine wrapper to select
|
||||
# the supplied test image without overwriting an operator's builder image.
|
||||
TEST_ENGINE=$(command -v "$CONTAINER_ENGINE")
|
||||
mkdir "$TEST_ROOT/engine"
|
||||
cat > "$TEST_ROOT/engine/$CONTAINER_ENGINE" <<'ENGINE'
|
||||
#!/bin/bash
|
||||
args=("$@")
|
||||
for index in "${!args[@]}"; do
|
||||
if [[ "${args[$index]}" == omarchy-pkg-builder:latest-* ]]; then
|
||||
args[$index]="$TEST_BUILDER_IMAGE"
|
||||
fi
|
||||
done
|
||||
exec "$TEST_ENGINE" "${args[@]}"
|
||||
ENGINE
|
||||
chmod +x "$TEST_ROOT/engine/$CONTAINER_ENGINE"
|
||||
export TEST_ENGINE TEST_BUILDER_IMAGE
|
||||
export PATH="$TEST_ROOT/engine:$PATH"
|
||||
export OMARCHY_SKIP_BUILDER_IMAGE=1
|
||||
unset OMARCHY_REPO_ROOT OMARCHY_RC_PINS OMARCHY_DEFER_RUNTIME_DEPS OMARCHY_KEEP_BUILD_WORKSPACE
|
||||
|
||||
fixture() {
|
||||
local name=$1 metadata=$2
|
||||
mkdir -p "$TEST_ROOT/pkgbuilds/$name/.omarchy"
|
||||
printf '{"source":"local"}\n' > "$TEST_ROOT/pkgbuilds/$name/.omarchy/package.json"
|
||||
cat > "$TEST_ROOT/pkgbuilds/$name/PKGBUILD" <<EOF
|
||||
pkgname=$name
|
||||
pkgver=1
|
||||
pkgrel=1
|
||||
arch=('x86_64' 'aarch64')
|
||||
pkgdesc='Build isolation regression fixture'
|
||||
license=('MIT')
|
||||
options=('!debug')
|
||||
$metadata
|
||||
package() {
|
||||
[[ ! -e /tmp/omarchy-build-contamination ]] || return 1
|
||||
touch /tmp/omarchy-build-contamination
|
||||
install -Dm644 /dev/null "\$pkgdir/usr/share/build-isolation/\$pkgname"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
run_build() {
|
||||
"$TEST_ROOT/bin/build" --arch "$TEST_ARCH" --package "$@"
|
||||
}
|
||||
|
||||
fixture omarchy-settings ''
|
||||
fixture omarchy "depends=('omarchy-settings=1')"
|
||||
fixture omarchy-settings-dev "provides=('omarchy-settings'); conflicts=('omarchy-settings')"
|
||||
fixture omarchy-dev "depends=('omarchy-settings-dev'); provides=('omarchy'); conflicts=('omarchy')"
|
||||
fixture flea "depends=('omarchy')"
|
||||
|
||||
# Both pairs and their consumer in one run. Every package also leaves a
|
||||
# /tmp marker: even non-package filesystem changes must stay isolated.
|
||||
run_build omarchy-settings omarchy-settings-dev omarchy omarchy-dev flea > "$TEST_ROOT/pairs.log" 2>&1 || {
|
||||
cat "$TEST_ROOT/pairs.log"
|
||||
exit 1
|
||||
}
|
||||
for package in flea omarchy omarchy-dev omarchy-settings omarchy-settings-dev; do
|
||||
compgen -G "$TEST_ROOT/build-output/edge/$TEST_ARCH/$package-1-1-*.pkg.tar.zst" >/dev/null
|
||||
done
|
||||
printf 'PASS: release/dev pairs and Flea build together in isolated roots\n'
|
||||
|
||||
# Test-only and architecture-specific dependencies must precede consumers.
|
||||
# Failure must block both direct and transitive consumers, while an
|
||||
# unrelated package still builds in a clean container.
|
||||
fixture broken ''
|
||||
cat >> "$TEST_ROOT/pkgbuilds/broken/PKGBUILD" <<'EOF'
|
||||
prepare() { touch /tmp/omarchy-build-contamination; return 1; }
|
||||
EOF
|
||||
fixture consumer "checkdepends_${TEST_ARCH}=('broken')"
|
||||
fixture transitive "makedepends=('consumer')"
|
||||
fixture independent ''
|
||||
if run_build transitive consumer broken independent > "$TEST_ROOT/failure.log" 2>&1; then
|
||||
cat "$TEST_ROOT/failure.log"
|
||||
echo 'FAIL: a failed prerequisite did not fail the run' >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'consumer blocked by unsuccessful dependency: broken' "$TEST_ROOT/failure.log"
|
||||
grep -q 'transitive blocked by unsuccessful dependency: consumer' "$TEST_ROOT/failure.log"
|
||||
compgen -G "$TEST_ROOT/build-output/edge/$TEST_ARCH/independent-1-1-*.pkg.tar.zst" >/dev/null
|
||||
if compgen -G "$TEST_ROOT/build-output/edge/$TEST_ARCH/consumer-*.pkg.tar.zst" >/dev/null; then
|
||||
echo 'FAIL: consumer of a failed prerequisite was built' >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'PASS: failed prerequisite blocks consumers; independent build remains clean\n'
|
||||
|
||||
# A later job can resolve dependencies from artifacts kept by the caller.
|
||||
# Make consumer depend on the already staged fixture without selecting it.
|
||||
sed -i "s/^checkdepends_.*/depends=('independent')/" "$TEST_ROOT/pkgbuilds/consumer/PKGBUILD"
|
||||
OMARCHY_KEEP_BUILD_WORKSPACE=1 run_build consumer > "$TEST_ROOT/seeded.log" 2>&1 || {
|
||||
cat "$TEST_ROOT/seeded.log"
|
||||
exit 1
|
||||
}
|
||||
compgen -G "$TEST_ROOT/build-output/edge/$TEST_ARCH/consumer-1-1-*.pkg.tar.zst" >/dev/null
|
||||
printf 'PASS: kept workspace supplies dependencies to a later isolated build\n'
|
||||
|
||||
# A rebuild may keep its filename while changing its bytes. Pacman's shared
|
||||
# cache must not hide the replacement artifact from the next consumer.
|
||||
cat >> "$TEST_ROOT/pkgbuilds/independent/PKGBUILD" <<'EOF'
|
||||
package() {
|
||||
install -Dm644 /dev/null "$pkgdir/usr/share/build-isolation/independent"
|
||||
echo replacement > "$pkgdir/usr/share/build-isolation/independent"
|
||||
}
|
||||
EOF
|
||||
cat >> "$TEST_ROOT/pkgbuilds/consumer/PKGBUILD" <<'EOF'
|
||||
check() { [[ $(cat /usr/share/build-isolation/independent) == replacement ]]; }
|
||||
EOF
|
||||
OMARCHY_KEEP_BUILD_WORKSPACE=1 run_build independent consumer > "$TEST_ROOT/rebuilt.log" 2>&1 || {
|
||||
cat "$TEST_ROOT/rebuilt.log"
|
||||
exit 1
|
||||
}
|
||||
printf 'PASS: rebuilt artifacts supersede cached packages with the same filename\n'
|
||||
|
||||
# CI's deferred pair mode still accepts the complete request although each
|
||||
# half now builds in its own container. An unavailable runtime dependency
|
||||
# proves it was deferred; build/test dependencies still use pacman normally.
|
||||
sed -i "s/^depends=.*/depends=('omarchy-settings-dev' 'unavailable-runtime-fixture')/" "$TEST_ROOT/pkgbuilds/omarchy-dev/PKGBUILD"
|
||||
OMARCHY_DEFER_RUNTIME_DEPS=true run_build omarchy-dev omarchy-settings-dev > "$TEST_ROOT/deferred.log" 2>&1 || {
|
||||
cat "$TEST_ROOT/deferred.log"
|
||||
exit 1
|
||||
}
|
||||
compgen -G "$TEST_ROOT/build-output/edge/$TEST_ARCH/omarchy-dev-1-1-*.pkg.tar.zst" >/dev/null
|
||||
printf 'PASS: deferred pair mode works across isolated containers\n'
|
||||
|
||||
fixture excluded ''
|
||||
printf '{"source":"local","channels":["stable"]}\n' > "$TEST_ROOT/pkgbuilds/excluded/.omarchy/package.json"
|
||||
run_build excluded > "$TEST_ROOT/empty.log" 2>&1 || { cat "$TEST_ROOT/empty.log"; exit 1; }
|
||||
grep -q 'Total packages: 0' "$TEST_ROOT/empty.log"
|
||||
if grep -q 'in a fresh container' "$TEST_ROOT/empty.log"; then
|
||||
echo 'FAIL: an excluded package started a build container' >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'PASS: empty plans finish without starting a package build\n'
|
||||
Reference in new issue
Block a user