yams/scripts/local-ci/package-lane.sh
trvon e58aef6cf5
fix(ci): isolate arch package builds
- stop arch lanes from reusing build/release
   - fix local arch lane quoting
   - align release arch lane with isolated build dirs
2026-07-01 10:02:54 -04:00

177 lines
5.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# yams/scripts/local-ci/package-lane.sh
#
# One-command local lane: build the Linux packages in a cached Docker builder,
# then install + smoke-test them in clean systemd containers. This is the local
# equivalent of the release CI package job.
#
# build : run scripts/build-deb.sh inside docker/local-ci/debian-package.Dockerfile
# -> produces yams-*.deb and yams-*.rpm into the build dir
# validate: run scripts/local-ci/package-validate.sh against those artifacts
#
# Requirements on host:
# - Docker on PATH, on a Linux host (or Docker Desktop's Linux VM).
# - The validate phase needs privileged systemd-in-docker (cgroups). On macOS
# Docker Desktop the build phase works; the validate phase may not boot
# systemd -- use --build-only there and run validate on a Linux runner.
#
# Examples:
# bash scripts/local-ci/package-lane.sh # build + validate (deb + rpm)
# bash scripts/local-ci/package-lane.sh --only deb # deb lane only
# bash scripts/local-ci/package-lane.sh --build-only # just produce packages
# bash scripts/local-ci/package-lane.sh --validate-only # reuse existing packages
# bash scripts/local-ci/package-lane.sh --no-cache # rebuild the builder image
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
IMAGE="yams/ci-debian-package:trixie"
DOCKERFILE="${REPO_ROOT}/docker/local-ci/debian-package.Dockerfile"
ARCH_IMAGE="yams/ci-arch-package:latest"
ARCH_DOCKERFILE="${REPO_ROOT}/docker/local-ci/arch-package.Dockerfile"
BUILD_DIR="build/release-pkg" # relative to repo root; kept separate from dev build/
ONLY="all"
DO_BUILD=1
DO_VALIDATE=1
NO_CACHE=0
VERSION=""
log() { printf '\033[1;34m[lane]\033[0m %s\n' "$*"; }
fail() { printf '\033[1;31m[fail]\033[0m %s\n' "$*" >&2; }
usage() { sed -n '2,30p' "${BASH_SOURCE[0]}"; }
# ── Arch build lane ───────────────────────────────────────────────────────
build_arch_lane() {
local arch_platform="${ARCH_DOCKER_PLATFORM:-linux/amd64}"
log "building cached Arch builder image ${ARCH_IMAGE} (${arch_platform})"
arch_build_args=(-f "${ARCH_DOCKERFILE}" -t "${ARCH_IMAGE}")
[ "${NO_CACHE}" -eq 1 ] && arch_build_args+=(--no-cache)
arch_build_args+=("${REPO_ROOT}")
docker build --platform="${arch_platform}" "${arch_build_args[@]}"
log "building Arch packages (version=${VERSION}) into ${BUILD_DIR}"
mkdir -p "${ABS_BUILD_DIR}"
docker run --rm \
--platform="${arch_platform}" \
-v "${REPO_ROOT}:/workspace/yams" \
-v yams-conan-cache-arch:/root/.conan2 \
-w /workspace \
-e RELEASE_CHANNEL=nightly \
-e GIT_REF="" \
-e BUILD_DIR="${BUILD_DIR}" \
-e MESON_EXTRA_ARGS="${MESON_EXTRA_ARGS:-}" \
-e CONAN_EXTRA_OPTS="${CONAN_EXTRA_OPTS:-}" \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
"${ARCH_IMAGE}" \
bash -lc "set -euo pipefail; /workspace/yams/scripts/build-arch-pkg.sh build; rc=\$?; chown -R \"\${HOST_UID}:\${HOST_GID}\" \"/workspace/yams/${BUILD_DIR}\" 2>/dev/null || true; exit \$rc"
log "built Arch artifacts:"
find "${ABS_BUILD_DIR}" -maxdepth 4 -type f \( -name 'yams-*.pkg.tar.zst' -o -name 'yams.db' -o -name 'yams.files' \) -print
}
# ── dispatch ──────────────────────────────────────────────────────────────
while [ "$#" -gt 0 ]; do
case "$1" in
--only)
ONLY="$2"
shift 2
;;
--build-dir)
BUILD_DIR="$2"
shift 2
;;
--version)
VERSION="$2"
shift 2
;;
--build-only)
DO_VALIDATE=0
shift
;;
--validate-only)
DO_BUILD=0
shift
;;
--no-cache)
NO_CACHE=1
shift
;;
-h | --help)
usage
exit 0
;;
*)
fail "unknown argument: $1"
usage
exit 2
;;
esac
done
command -v docker >/dev/null 2>&1 || {
fail "docker not found on PATH"
exit 2
}
# Resolve a version for the build (build-deb.sh packages only for tag/channel
# builds, so the build phase passes RELEASE_CHANNEL=nightly to force packaging).
if [ -z "${VERSION}" ]; then
VERSION="$(git -C "${REPO_ROOT}" describe --tags --abbrev=7 2>/dev/null || echo 0.0.0-dev)"
fi
ABS_BUILD_DIR="${BUILD_DIR}"
case "${ABS_BUILD_DIR}" in
/*) : ;;
*) ABS_BUILD_DIR="${REPO_ROOT}/${BUILD_DIR}" ;;
esac
if [ "${DO_BUILD}" -eq 1 ]; then
case "${ONLY}" in
arch)
build_arch_lane
;;
*)
log "building cached builder image ${IMAGE}"
build_args=(-f "${DOCKERFILE}" -t "${IMAGE}")
[ "${NO_CACHE}" -eq 1 ] && build_args+=(--no-cache)
build_args+=("${REPO_ROOT}")
docker build "${build_args[@]}"
log "building packages (version=${VERSION}) into ${BUILD_DIR}"
mkdir -p "${ABS_BUILD_DIR}"
# Run the real build inside the container; chown artifacts back to the host
# user so the build dir isn't left root-owned on the bind mount.
# Persist the Conan cache across runs so dependencies aren't rebuilt from
# source every time (the container itself is --rm). BASE_VERSION is left unset
# so build-deb.sh derives a digit-leading version (dpkg requires that).
docker run --rm \
-v "${REPO_ROOT}:/workspace/yams" \
-v yams-conan-cache:/root/.conan2 \
-w /workspace \
-e RELEASE_CHANNEL=nightly \
-e GIT_REF="" \
-e BUILD_DIR="${BUILD_DIR}" \
-e MESON_EXTRA_ARGS="${MESON_EXTRA_ARGS:-}" \
-e CONAN_EXTRA_OPTS="${CONAN_EXTRA_OPTS:-}" \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
"${IMAGE}" \
bash -lc '/workspace/yams/scripts/build-deb.sh; rc=$?; chown -R "${HOST_UID}:${HOST_GID}" "/workspace/yams/'"${BUILD_DIR}"'" 2>/dev/null || true; exit $rc'
log "built artifacts:"
find "${ABS_BUILD_DIR}" -maxdepth 1 -type f \( -name 'yams-*.deb' -o -name 'yams-*.rpm' \) -print
;;
esac
fi
if [ "${DO_VALIDATE}" -eq 1 ]; then
log "validating packages from ${BUILD_DIR}"
bash "${SCRIPT_DIR}/package-validate.sh" --build-dir "${ABS_BUILD_DIR}" --only "${ONLY}"
fi
log "done"