#!/bin/bash # Abort if anything fails set -e # Source common functions BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") source "$BUILD_ROOT/helpers/message-helpers.sh" source "$BUILD_ROOT/helpers/docker-helpers.sh" source "$BUILD_ROOT/helpers/paths.sh" print_header "Omarchy Package Builder" DRY_RUN=false # Knobs for builds driven from CI or resumed by hand. Each defaults to the # historical behaviour, so an unadorned `bin/build` is unchanged. # # OMARCHY_KEEP_BUILD_WORKSPACE=1 keep build-output/$MIRROR/$ARCH instead of # wiping it, so packages built by an earlier # job (or a previous, interrupted run) seed # the build database and resolve as # dependencies of what builds now. # OMARCHY_SKIP_BUILDER_IMAGE=1 use the omarchy-pkg-builder image already # present instead of building it; a workflow # that builds the image once with an external # BuildKit cache can then fan out over many # package jobs without each one rebuilding it. # OMARCHY_DEFER_RUNTIME_DEPS=true build the omarchy/omarchy-settings pair # with --nodeps (see build/build.sh); only # for a pipeline that verifies the install # transaction afterwards. KEEP_BUILD_WORKSPACE=${OMARCHY_KEEP_BUILD_WORKSPACE:-0} SKIP_BUILDER_IMAGE=${OMARCHY_SKIP_BUILDER_IMAGE:-0} DEFER_RUNTIME_DEPS=${OMARCHY_DEFER_RUNTIME_DEPS:-false} # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in --arch) ARCH="$2" update_arch_paths shift 2 ;; --mirror) MIRROR="$2" if ! validate_mirror "$MIRROR"; then print_error "Invalid mirror: $MIRROR (must be one of: $VALID_MIRRORS)" exit 1 fi update_arch_paths shift 2 ;; --package) shift PACKAGES="" while [[ $# -gt 0 && ! "$1" =~ ^-- ]]; do PACKAGES="$PACKAGES $1" shift done PACKAGES="${PACKAGES# }" ;; --dry-run) DRY_RUN=true shift ;; -h | --help) echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --arch Target architecture (x86_64 or aarch64, default: x86_64)" echo " --mirror Mirror to use (edge, rc, or stable, default: edge)" echo " --package Build only the specified package(s) (space-separated)" echo " --dry-run Show what would build without running makepkg" echo " -h, --help Show this help message" echo "" echo "This script builds packages from pkgbuilds/ based on .omarchy/package.json:" echo " --mirror edge: builds packages not marked skip_build=true" echo " --mirror stable: builds fast-ring packages not marked skip_build=true" echo " --package: explicitly builds selected packages, even with skip_build=true" echo "" echo "Or build specific packages with --package:" echo " Package names should match directories in pkgbuilds/" echo "" echo "Examples:" echo " $0 --arch aarch64" echo " $0 --mirror stable" echo " $0 --package yay" echo " $0 --package yay elephant cursor-bin" echo "" echo "Environment (for CI and resumed builds; defaults keep today's behaviour):" echo " OMARCHY_KEEP_BUILD_WORKSPACE=1 keep build-output and reuse packages already there" echo " OMARCHY_SKIP_BUILDER_IMAGE=1 use the existing builder image instead of building it" echo " OMARCHY_DEFER_RUNTIME_DEPS=true build the omarchy pair with --nodeps (transaction verified later)" echo "" exit 0 ;; *) print_error "Unknown option: $1" exit 1 ;; esac done if [[ $DEFER_RUNTIME_DEPS != "false" && $DEFER_RUNTIME_DEPS != "true" ]]; then print_error "OMARCHY_DEFER_RUNTIME_DEPS must be true or false" exit 1 fi # Deferring runtime dependencies is only sound for the omarchy pair, and only # when both halves are built together: the pair depends on each other and on # packages that a sharded pipeline builds in other jobs, and the consumer of # this mode installs the assembled set in one verified transaction. Check the # request here so a misuse fails before Docker starts. if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then deferred_runtime=0 deferred_settings=0 deferred_count=0 for package in $PACKAGES; do ((deferred_count += 1)) case $package in omarchy|omarchy-dev) deferred_runtime=1 ;; omarchy-settings|omarchy-settings-dev) deferred_settings=1 ;; *) print_error "OMARCHY_DEFER_RUNTIME_DEPS only applies to the omarchy/omarchy-settings pair, not $package" exit 1 ;; esac done if (( deferred_runtime != 1 || deferred_settings != 1 || deferred_count != 2 )); then print_error "OMARCHY_DEFER_RUNTIME_DEPS requires --package with exactly the omarchy pair" exit 1 fi fi # Show target architecture and mirror after parsing args print_info "Target architecture: $ARCH" print_info "Mirror: $MIRROR" print_info "Build workspace: $BUILD_OUTPUT_DIR" print_info "Final output: $REPO_DIR" if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then print_info "Runtime dependency checks: deferred to the install transaction" fi if [[ "$DRY_RUN" == true ]]; then print_warning "DRY RUN MODE - build plan only; no Docker or makepkg will run" ARCH="$ARCH" \ MIRROR="$MIRROR" \ PACKAGES="$PACKAGES" \ DRY_RUN=true \ DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS" \ PKGBUILDS_DIR="$PKGBUILDS_DIR" \ BUILD_OUTPUT_DIR="$BUILD_OUTPUT_DIR" \ FINAL_OUTPUT_DIR="$REPO_DIR" \ HELPERS_DIR="$BUILD_ROOT/helpers" \ SRC_DIR="$SRC_DIR" \ "$BUILD_ROOT/build/build.sh" exit $? fi # Create directories if they don't exist mkdir -p "$BUILD_OUTPUT_DIR" "$REPO_DIR" "$SRC_DIR" # Check Docker is available check_docker # A foreign target architecture runs under QEMU user emulation. Probe by # actually running a container for the target platform: that is the only # test that covers both "binfmt not registered" and "registered but broken". HOST_ARCH=$(uname -m) [[ "$HOST_ARCH" == "arm64" ]] && HOST_ARCH=aarch64 if [[ "$HOST_ARCH" != "$ARCH" ]]; then # shellcheck disable=SC2086 if ! docker run --rm $(get_platform_arg "$ARCH") alpine:3.21 /bin/true >/dev/null 2>&1; then print_info "Setting up QEMU for $ARCH emulation on this $HOST_ARCH host..." setup_qemu fi fi # Clean build-output directory to start fresh, unless the caller seeded it # with packages from an earlier job or is resuming an interrupted run. if [[ $KEEP_BUILD_WORKSPACE == "1" ]]; then print_info "Keeping existing build workspace..." else print_info "Cleaning build workspace..." rm -rf "$BUILD_OUTPUT_DIR"/* fi mkdir -p "$BUILD_OUTPUT_DIR" # Show package info if [[ -n "$PACKAGES" ]]; then print_info "Building packages: $PACKAGES" else print_info "Building unscoped packages for $MIRROR mirror" fi # Build/update the Docker image, unless the caller prepared the exact image # already (a workflow building it once with an external BuildKit cache). A # missing image is an error rather than a silent rebuild: the point of the # flag is that every job runs the same bytes. IMAGE_TAG="omarchy-pkg-builder:latest-$ARCH-$MIRROR" if [[ $SKIP_BUILDER_IMAGE == "1" ]]; then if ! docker image inspect "$IMAGE_TAG" >/dev/null 2>&1; then print_error "Prepared builder image is unavailable: $IMAGE_TAG" exit 1 fi print_info "Using prepared builder image: $IMAGE_TAG" else build_docker_image "$BUILD_DIR" "$ARCH" "$MIRROR" fi print_info "Running package build..." # Create output directories if they don't exist mkdir -p "$BUILD_OUTPUT_DIR" mkdir -p "$REPO_DIR" # Ensure output directories are writable by container user make_dir_writable "$BUILD_OUTPUT_DIR" make_dir_writable "$REPO_DIR" # Build Docker arguments DOCKER_ARGS=( --rm -e ARCH="$ARCH" -e MIRROR="$MIRROR" -e PACKAGES="$PACKAGES" -e OMARCHY_RC_PINS="${OMARCHY_RC_PINS:-}" -e DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS" -v "$BUILD_ROOT/build-output:/build-output" -v "$REPO_ROOT:/pkgs.omarchy.org" -v "$BUILD_DIR:/build:ro" -v "$BUILD_ROOT/helpers:/helpers:ro" -v "$BUILD_ROOT/pkgbuilds:/pkgbuilds:ro" ) # Run the builder with assembled args PLATFORM_ARG=$(get_platform_arg "$ARCH") docker run $PLATFORM_ARG "${DOCKER_ARGS[@]}" "$IMAGE_TAG" /build/build.sh BUILD_RESULT=$? # Summary echo "" if [[ $BUILD_RESULT -eq 0 ]]; then print_success "Build completed successfully!" else print_warning "Some packages failed (see details above)" exit $BUILD_RESULT fi