Three opt-in knobs for bin/build, each defaulting to today's behaviour: OMARCHY_KEEP_BUILD_WORKSPACE=1 keeps build-output/$MIRROR/$ARCH instead of wiping it, and build/build.sh now folds any packages already there into omarchy-build.db even when no database exists yet, so packages built by an earlier job (or a previous, interrupted run) resolve as dependencies of what builds next. OMARCHY_SKIP_BUILDER_IMAGE=1 uses the omarchy-pkg-builder image already present instead of building it, so a workflow can build the image once with an external BuildKit cache and fan out over package jobs that all run the same bytes. A missing image is an error, not a silent rebuild. OMARCHY_DEFER_RUNTIME_DEPS=true builds the omarchy/omarchy-settings pair with --nodeps, installing only their makedepends and checkdepends explicitly. The pair depends on each other and on packages a sharded pipeline builds in other jobs, so they cannot resolve in isolation; the assembled set is installed in one verified transaction downstream. The request is refused for anything but exactly that pair, on the host before Docker starts and again inside the container. Also fix make_dir_writable: chown -R can succeed on part of the tree and fail on files a previous container left behind as another uid, and the old `|| chmod` fallback only ran when chown failed outright. Always follow with chmod.
681 lines
23 KiB
Bash
Executable File
681 lines
23 KiB
Bash
Executable File
#!/bin/bash
|
|
# Build script - builds packages based on package metadata
|
|
# Unscoped edge builds exclude skip_build packages. Stable also requires the fast release ring.
|
|
# Explicit --package selections may build packages with skip_build=true.
|
|
|
|
# Setup directories
|
|
ARCH=${ARCH:-x86_64}
|
|
MIRROR=${MIRROR:-edge}
|
|
DRY_RUN=${DRY_RUN:-false}
|
|
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}
|
|
HELPERS_DIR=${HELPERS_DIR:-/helpers}
|
|
SRC_DIR=${SRC_DIR:-/src}
|
|
# Set by bin/build from OMARCHY_DEFER_RUNTIME_DEPS after it has checked the
|
|
# request; re-checked here so the container never trusts a stray value.
|
|
DEFER_RUNTIME_DEPS=${DEFER_RUNTIME_DEPS:-false}
|
|
|
|
source "$HELPERS_DIR/package-metadata.sh"
|
|
|
|
if [[ $DEFER_RUNTIME_DEPS != "false" && $DEFER_RUNTIME_DEPS != "true" ]]; then
|
|
echo "DEFER_RUNTIME_DEPS must be true or false" >&2
|
|
exit 1
|
|
fi
|
|
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 ;;
|
|
*)
|
|
echo "Runtime dependency deferral only applies to the omarchy pair, not $package" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
if (( deferred_runtime != 1 || deferred_settings != 1 || deferred_count != 2 )); then
|
|
echo "Runtime dependency deferral requires exactly the omarchy pair" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" != true ]]; then
|
|
# Import GPG keys
|
|
/build/import-gpg-keys.sh || exit 1
|
|
|
|
mkdir -p "$BUILD_OUTPUT_DIR" "$FINAL_OUTPUT_DIR"
|
|
|
|
# Bring the container up to date before any makedepends are installed. The
|
|
# image is layer-cached, so its glibc drifts behind the mirror while makepkg
|
|
# -s pulls makedepends from the freshly synced database -- a partial upgrade
|
|
# 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
|
|
|
|
# Configure Omarchy repositories for dependency resolution
|
|
echo "==> Configuring Omarchy repositories for dependency resolution..."
|
|
|
|
# Always add omarchy-build repo (for incremental builds)
|
|
# Packages in build-output are unsigned, so use SigLevel = Never
|
|
sudo tee -a /etc/pacman.conf > /dev/null <<EOF
|
|
|
|
[omarchy-build]
|
|
SigLevel = Never
|
|
Server = file://$BUILD_OUTPUT_DIR
|
|
EOF
|
|
echo " -> omarchy-build (priority 1): $BUILD_OUTPUT_DIR"
|
|
|
|
# Initialize empty build database if it doesn't exist
|
|
cd "$BUILD_OUTPUT_DIR"
|
|
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
|
|
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
|
|
fi
|
|
|
|
# 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
|
|
sudo tee -a /etc/pacman.conf > /dev/null <<EOF
|
|
|
|
[omarchy]
|
|
SigLevel = Optional TrustAll
|
|
Server = file://$FINAL_OUTPUT_DIR
|
|
EOF
|
|
echo " -> omarchy (priority 2): $FINAL_OUTPUT_DIR"
|
|
fi
|
|
|
|
# Sync pacman database
|
|
sudo pacman -Sy
|
|
fi
|
|
|
|
echo "==> Package Builder"
|
|
echo "==> Target architecture: $ARCH"
|
|
echo "==> Mirror: $MIRROR"
|
|
echo "==> Package root: $PKGBUILDS_DIR"
|
|
echo "==> Build workspace: $BUILD_OUTPUT_DIR"
|
|
echo "==> Final output: $FINAL_OUTPUT_DIR"
|
|
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
|
|
find_package_dir() {
|
|
local pkg="$1"
|
|
package_dir_for_name "$pkg"
|
|
}
|
|
|
|
# Get version from final output (production packages)
|
|
#
|
|
# Source package directories are named after the PKGBUILD pkgbase, but split
|
|
# packages are stored in the repo DB under their individual pkgname entries.
|
|
# Cache versions by both %NAME% and %BASE% so a pkgbase like
|
|
# libretro-vice-git can be found even though the DB only contains packages like
|
|
# libretro-vice-x64-git.
|
|
declare -A LOCAL_VERSION_BY_NAME=()
|
|
declare -A LOCAL_VERSION_BY_BASE=()
|
|
LOCAL_VERSION_CACHE_LOADED=false
|
|
LOCAL_VERSION_CACHE_DB=""
|
|
|
|
load_local_versions() {
|
|
local db="$FINAL_OUTPUT_DIR/omarchy.db.tar.zst"
|
|
|
|
if [[ ! -f "$db" ]]; then
|
|
db="$FINAL_OUTPUT_DIR/omarchy.db"
|
|
fi
|
|
|
|
[[ -f "$db" ]] || return 0
|
|
[[ "$LOCAL_VERSION_CACHE_LOADED" == true && "$LOCAL_VERSION_CACHE_DB" == "$db" ]] && return 0
|
|
|
|
LOCAL_VERSION_BY_NAME=()
|
|
LOCAL_VERSION_BY_BASE=()
|
|
|
|
local name base version
|
|
while IFS=$'\t' read -r name base version; do
|
|
[[ -n "$name" && -n "$version" ]] && LOCAL_VERSION_BY_NAME["$name"]="$version"
|
|
[[ -n "$base" && -n "$version" ]] && LOCAL_VERSION_BY_BASE["$base"]="$version"
|
|
done < <(
|
|
tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk '
|
|
function emit() {
|
|
if (name != "" && version != "") print name "\t" base "\t" version
|
|
name=""; base=""; version=""
|
|
}
|
|
$0 == "%FILENAME%" { emit(); next }
|
|
$0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next }
|
|
$0 == "%BASE%" { getline; base=$0; next }
|
|
$0 == "%VERSION%" { getline; version=$0; next }
|
|
END { emit() }
|
|
'
|
|
)
|
|
|
|
LOCAL_VERSION_CACHE_LOADED=true
|
|
LOCAL_VERSION_CACHE_DB="$db"
|
|
}
|
|
|
|
get_local_version() {
|
|
local pkg="$1"
|
|
|
|
load_local_versions
|
|
|
|
if [[ -n "${LOCAL_VERSION_BY_NAME[$pkg]:-}" ]]; then
|
|
echo "${LOCAL_VERSION_BY_NAME[$pkg]}"
|
|
elif [[ -n "${LOCAL_VERSION_BY_BASE[$pkg]:-}" ]]; then
|
|
echo "${LOCAL_VERSION_BY_BASE[$pkg]}"
|
|
fi
|
|
}
|
|
|
|
# Check if package should be built for current architecture
|
|
# Returns 0 (success) if should build, 1 if should skip
|
|
should_build_for_arch() {
|
|
local pkg="$1"
|
|
local current_arch="$ARCH"
|
|
local pkgdir=$(find_package_dir "$pkg")
|
|
local pkgbuild="$pkgdir/PKGBUILD"
|
|
|
|
[[ ! -f "$pkgbuild" ]] && return 1
|
|
|
|
# Check PKGBUILD arch=() array
|
|
local pkgbuild_archs=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${arch[@]}"')
|
|
|
|
# If arch=('any'), build for all architectures
|
|
if [[ "$pkgbuild_archs" == "any" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# Check if current arch is in PKGBUILD arch=()
|
|
if echo "$pkgbuild_archs" | grep -qw "$current_arch"; then
|
|
return 0 # Build
|
|
else
|
|
return 1 # Skip
|
|
fi
|
|
}
|
|
|
|
# For VCS packages, makepkg recalculates pkgver() before the build. If the
|
|
# recalculated pkgver differs from the static PKGBUILD value, makepkg resets
|
|
# pkgrel to 1. That is right for stock VCS packages, but wrong for Omarchy's
|
|
# patched AUR packages where sync-aur intentionally applies a dotted local
|
|
# pkgrel suffix (for example 1.1) to sort above the upstream/AUR package.
|
|
# Refresh pkgver once, then restore the local dotted pkgrel before the real
|
|
# build so the produced package filename carries the Omarchy revision.
|
|
refresh_vcs_pkgver_preserving_local_pkgrel() {
|
|
local pkg="$1"
|
|
local pkgbuild="PKGBUILD"
|
|
|
|
grep -qE '^pkgver[[:space:]]*\(\)' "$pkgbuild" || return 0
|
|
|
|
local original_pkgver original_pkgrel refreshed_pkgver refreshed_pkgrel
|
|
original_pkgver=$(bash -c 'source PKGBUILD 2>/dev/null; echo "${pkgver:-}"')
|
|
original_pkgrel=$(bash -c 'source PKGBUILD 2>/dev/null; echo "${pkgrel:-}"')
|
|
|
|
# Omarchy local rebuilds use dotted pkgrels (AUR pkgrel + .suffix). Plain
|
|
# integer pkgrels can keep makepkg's normal reset-to-1 behavior on new VCS
|
|
# revisions.
|
|
[[ "$original_pkgrel" == *.* ]] || return 0
|
|
|
|
echo " Refreshing VCS pkgver before build (preserving local pkgrel=$original_pkgrel)..."
|
|
if [[ -x /usr/local/bin/pacman-for-makepkg ]]; then
|
|
PACMAN=/usr/local/bin/pacman-for-makepkg makepkg --nobuild --nodeps --skipinteg --skippgpcheck --noprepare --noconfirm
|
|
else
|
|
makepkg --nobuild --nodeps --skipinteg --skippgpcheck --noprepare --noconfirm
|
|
fi
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
echo " Failed to refresh VCS pkgver for $pkg"
|
|
return 1
|
|
fi
|
|
|
|
refreshed_pkgver=$(bash -c 'source PKGBUILD 2>/dev/null; echo "${pkgver:-}"')
|
|
refreshed_pkgrel=$(bash -c 'source PKGBUILD 2>/dev/null; echo "${pkgrel:-}"')
|
|
|
|
if [[ "$refreshed_pkgrel" != "$original_pkgrel" ]]; then
|
|
sed -i "s/^pkgrel=.*/pkgrel=$original_pkgrel/" PKGBUILD
|
|
echo " Restored local pkgrel suffix: $refreshed_pkgrel -> $original_pkgrel"
|
|
fi
|
|
|
|
if [[ -n "$refreshed_pkgver" && "$refreshed_pkgver" != "$original_pkgver" ]]; then
|
|
echo " Refreshed VCS version: $original_pkgver -> $refreshed_pkgver"
|
|
fi
|
|
}
|
|
|
|
# With runtime dependency checks deferred, makepkg runs --nodeps, so the
|
|
# build-time dependencies it would normally install with -s have to be
|
|
# installed explicitly: makedepends and checkdepends, including the
|
|
# architecture-suffixed variants for the current CARCH.
|
|
install_deferred_build_dependencies() {
|
|
local pkg="$1"
|
|
local -a build_deps=()
|
|
|
|
mapfile -t build_deps < <(
|
|
CARCH="$ARCH" bash -c '
|
|
source PKGBUILD
|
|
arch_makedepends="makedepends_${CARCH}[@]"
|
|
arch_checkdepends="checkdepends_${CARCH}[@]"
|
|
printf "%s\n" \
|
|
"${makedepends[@]}" "${!arch_makedepends}" \
|
|
"${checkdepends[@]}" "${!arch_checkdepends}"
|
|
' | awk 'NF && !seen[$0]++'
|
|
)
|
|
|
|
if (( ${#build_deps[@]} )); then
|
|
echo " Installing build-only dependencies for $pkg..."
|
|
sudo pacman -S --needed --noconfirm -- "${build_deps[@]}"
|
|
fi
|
|
}
|
|
|
|
# Build a package
|
|
build_package() {
|
|
local pkg="$1"
|
|
local pkgdir=$(find_package_dir "$pkg")
|
|
|
|
echo ""
|
|
echo " -> Processing: $pkg"
|
|
|
|
# Copy to build directory
|
|
cd /src
|
|
rm -rf "$pkg"
|
|
cp -r "$pkgdir" "$pkg"
|
|
cd "/src/$pkg" || return 1
|
|
|
|
refresh_vcs_pkgver_preserving_local_pkgrel "$pkg" || {
|
|
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
|
|
return 1
|
|
}
|
|
|
|
# Get PKGBUILD version (including epoch if present)
|
|
local pkgbuild_version=$(bash -c 'source PKGBUILD; if [[ -n "$epoch" ]]; then echo "${epoch}:${pkgver}-${pkgrel}"; else echo "${pkgver}-${pkgrel}"; fi' 2>/dev/null)
|
|
|
|
if [[ -z "$pkgbuild_version" ]]; then
|
|
echo " Failed to read PKGBUILD version"
|
|
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
|
|
return 1
|
|
fi
|
|
|
|
# Show version info (version check already done in first pass)
|
|
local local_version=$(get_local_version "$pkg")
|
|
if [[ -n "$local_version" ]]; then
|
|
echo " Update available: $local_version -> $pkgbuild_version"
|
|
else
|
|
echo " New package (version: $pkgbuild_version)"
|
|
fi
|
|
|
|
# Import PGP keys from PKGBUILD validpgpkeys and keys/pgp/ directory
|
|
local pgp_keys=$(bash -c 'source PKGBUILD 2>/dev/null; echo "${validpgpkeys[@]}"')
|
|
if [[ -n "$pgp_keys" ]]; then
|
|
echo " Importing PGP keys from validpgpkeys..."
|
|
for key in $pgp_keys; do
|
|
gpg --receive-keys "$key" 2>/dev/null && echo " Received $key" || echo " Failed to receive $key"
|
|
done
|
|
fi
|
|
if [[ -d "keys/pgp" ]]; then
|
|
echo " Importing package-specific PGP keys..."
|
|
for keyfile in keys/pgp/*.asc; do
|
|
if [[ -f "$keyfile" ]]; then
|
|
gpg --import "$keyfile" 2>/dev/null && echo " Imported $(basename "$keyfile")" || echo " Failed to import $(basename "$keyfile")"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Build package without signing (signing is done separately)
|
|
# PACMAN override uses a wrapper that adds --ask 4 to auto-resolve conflicts
|
|
# (e.g. rustup replacing rust) since --noconfirm defaults to 'N' on those prompts
|
|
local -a makepkg_flags=(-scf --noconfirm)
|
|
if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then
|
|
# The pair's runtime dependencies (each other, and packages other jobs
|
|
# of the same pipeline build) are not resolvable here; the assembled set
|
|
# 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)
|
|
fi
|
|
|
|
if PACMAN=/usr/local/bin/pacman-for-makepkg makepkg "${makepkg_flags[@]}"; then
|
|
# Ensure output directory exists
|
|
mkdir -p "$BUILD_OUTPUT_DIR"
|
|
|
|
for pkg_file in *.pkg.tar.*; do
|
|
[[ -f "$pkg_file" ]] && cp "$pkg_file" "$BUILD_OUTPUT_DIR/"
|
|
done
|
|
|
|
cd "$BUILD_OUTPUT_DIR"
|
|
|
|
# Find ALL package files (handles split packages)
|
|
local new_pkgs=($(ls -t ${pkg}-*.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db'))
|
|
|
|
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
|
|
|
|
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
|
|
}
|
|
|
|
# Get package dependencies from PKGBUILD
|
|
get_package_deps() {
|
|
local pkg="$1"
|
|
local pkgdir=$(find_package_dir "$pkg")
|
|
local pkgbuild="$pkgdir/PKGBUILD"
|
|
|
|
if [[ ! -f "$pkgbuild" ]]; then
|
|
return
|
|
fi
|
|
|
|
# Extract depends and makedepends, filter for packages in our pkgbuilds/
|
|
(
|
|
source "$pkgbuild" 2>/dev/null
|
|
echo "${depends[@]} ${makedepends[@]}"
|
|
) | tr ' ' '\n' | 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
|
|
if find_package_dir "$dep" >/dev/null 2>&1; then
|
|
echo "$dep"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# For VCS packages (those with a pkgver() function), the static pkgver= in the
|
|
# PKGBUILD is just a placeholder; the real version is computed at build time
|
|
# from the git checkout. Without this check, version comparison always reports a
|
|
# mismatch and we rebuild on every run, producing a package with the same
|
|
# name+version as one already in production. Detect this by comparing the
|
|
# upstream commit hash to the hash suffix already in the production version
|
|
# (both `...gabcdef0` and `...abcdef0` styles are common). Returns 0 when
|
|
# upstream is unchanged (build can be skipped).
|
|
check_vcs_unchanged() {
|
|
local pkg="$1"
|
|
local pkgdir="$2"
|
|
local pkgbuild="$pkgdir/PKGBUILD"
|
|
|
|
grep -qE '^pkgver[[:space:]]*\(\)' "$pkgbuild" || return 1
|
|
|
|
local local_version=$(get_local_version "$pkg")
|
|
[[ -z "$local_version" ]] && return 1
|
|
|
|
# If epoch or pkgrel changed in PKGBUILD, rebuild even if upstream is unchanged
|
|
local pkgbuild_epoch=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${epoch:-}"')
|
|
local pkgbuild_pkgrel=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${pkgrel}"')
|
|
|
|
local prod_pkgrel="${local_version##*-}"
|
|
local prod_no_pkgrel="${local_version%-*}"
|
|
local prod_epoch=""
|
|
if [[ "$prod_no_pkgrel" == *:* ]]; then
|
|
prod_epoch="${prod_no_pkgrel%%:*}"
|
|
fi
|
|
|
|
[[ "$pkgbuild_epoch" != "$prod_epoch" ]] && return 1
|
|
[[ "$pkgbuild_pkgrel" != "$prod_pkgrel" ]] && return 1
|
|
|
|
# Compare the commit represented in the published version to the current
|
|
# upstream ref. Supports unfragmented git sources as well as #branch=,
|
|
# #tag=, and #commit= fragments.
|
|
local prod_hash=$(package_extract_vcs_hash_from_version "$local_version")
|
|
[[ -z "$prod_hash" ]] && return 1
|
|
|
|
local upstream_hash=$(package_git_upstream_hash "$pkgdir")
|
|
[[ -z "$upstream_hash" ]] && return 1
|
|
|
|
[[ "$prod_hash" == "$upstream_hash" ]]
|
|
}
|
|
|
|
# Check which packages need building (version check only)
|
|
check_needs_build() {
|
|
local pkg="$1"
|
|
local pkgdir=$(find_package_dir "$pkg")
|
|
local pkgbuild="$pkgdir/PKGBUILD"
|
|
|
|
[[ ! -f "$pkgbuild" ]] && return 1
|
|
|
|
# Get PKGBUILD version (including epoch if present)
|
|
local pkgbuild_version=$(cd "$pkgdir" && bash -c 'source PKGBUILD; if [[ -n "$epoch" ]]; then echo "${epoch}:${pkgver}-${pkgrel}"; else echo "${pkgver}-${pkgrel}"; fi' 2>/dev/null)
|
|
[[ -z "$pkgbuild_version" ]] && return 1
|
|
|
|
# Check if already built
|
|
local local_version=$(get_local_version "$pkg")
|
|
|
|
if grep -qE '^pkgver[[:space:]]*\(\)' "$pkgbuild"; then
|
|
if [[ -n "$local_version" && -n "$(package_extract_vcs_hash_from_version "$local_version")" ]]; then
|
|
if check_vcs_unchanged "$pkg" "$pkgdir"; then
|
|
return 1 # VCS upstream ref is already represented in the repo
|
|
else
|
|
return 0 # New VCS ref, missing repo package, or pkgrel/epoch changed
|
|
fi
|
|
elif [[ "$local_version" == "$pkgbuild_version" ]]; then
|
|
return 1 # VCS package does not expose a hash; fall back to static version
|
|
else
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [[ "$local_version" == "$pkgbuild_version" ]]; then
|
|
return 1 # Already up to date
|
|
else
|
|
return 0 # Needs building
|
|
fi
|
|
}
|
|
|
|
# Collect packages that should be built for the selected mirror
|
|
collect_packages() {
|
|
packages_for_unscoped_build "$MIRROR"
|
|
}
|
|
|
|
# Main execution
|
|
if [[ "$DRY_RUN" != true ]]; then
|
|
cd "$SRC_DIR"
|
|
fi
|
|
|
|
TOTAL_COUNT=0
|
|
|
|
echo "==> Checking which packages need building..."
|
|
|
|
# First pass: determine which packages need building
|
|
PACKAGES_TO_BUILD=()
|
|
|
|
# If PACKAGES is specified, only check those packages
|
|
if [[ -n "$PACKAGES" ]]; then
|
|
echo "==> Checking specified packages: $PACKAGES"
|
|
for pkg_name in $PACKAGES; do
|
|
pkgdir=$(find_package_dir "$pkg_name")
|
|
if [[ -z "$pkgdir" || ! -f "$pkgdir/PKGBUILD" ]]; then
|
|
echo "==> ERROR: Package '$pkg_name' not found in $PKGBUILDS_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
if ! package_builds_for_mirror "$pkgdir" "$MIRROR"; then
|
|
if [[ "$MIRROR" == "stable" ]]; then
|
|
echo " - $pkg_name - not in release_ring=fast; build edge and promote with repo migrate"
|
|
else
|
|
echo " - $pkg_name - not configured for direct $MIRROR builds"
|
|
fi
|
|
SKIPPED_PACKAGES="$SKIPPED_PACKAGES $pkg_name"
|
|
continue
|
|
fi
|
|
|
|
# Check if package should be built for this architecture
|
|
if ! should_build_for_arch "$pkg_name"; then
|
|
echo " - $pkg_name - not built for $ARCH"
|
|
SKIPPED_PACKAGES="$SKIPPED_PACKAGES $pkg_name"
|
|
continue
|
|
fi
|
|
|
|
if check_needs_build "$pkg_name"; then
|
|
PACKAGES_TO_BUILD+=("$pkg_name")
|
|
else
|
|
echo " + $pkg_name - already up to date"
|
|
SKIPPED_PACKAGES="$SKIPPED_PACKAGES $pkg_name"
|
|
fi
|
|
done
|
|
else
|
|
# Build all packages that need updates from the relevant directories
|
|
while IFS= read -r pkg; do
|
|
# Check if package should be built for this architecture
|
|
if ! should_build_for_arch "$pkg"; then
|
|
echo " - $pkg - not built for $ARCH"
|
|
SKIPPED_PACKAGES="$SKIPPED_PACKAGES $pkg"
|
|
continue
|
|
fi
|
|
|
|
if check_needs_build "$pkg"; then
|
|
PACKAGES_TO_BUILD+=("$pkg")
|
|
else
|
|
echo " + $pkg - already up to date"
|
|
SKIPPED_PACKAGES="$SKIPPED_PACKAGES $pkg"
|
|
fi
|
|
done < <(collect_packages)
|
|
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 "==> Determining build order based on dependencies..."
|
|
|
|
# Second pass: order only the packages that need building
|
|
# Strategy: build packages with no unmet dependencies first
|
|
declare -A unmet_deps_count # How many dependencies does this package still need?
|
|
declare -A blocks_packages # Which packages are waiting for this one?
|
|
|
|
# Count unmet dependencies for each package
|
|
for pkg in "${PACKAGES_TO_BUILD[@]}"; do
|
|
unmet_deps_count[$pkg]=0
|
|
done
|
|
|
|
# Build the dependency relationships
|
|
for pkg in "${PACKAGES_TO_BUILD[@]}"; do
|
|
while IFS= read -r dep; do
|
|
# Only care about deps that are being built in this run
|
|
for build_pkg in "${PACKAGES_TO_BUILD[@]}"; do
|
|
if [[ "$dep" == "$build_pkg" ]]; then
|
|
# pkg needs dep, so increment pkg's unmet count
|
|
((unmet_deps_count[$pkg]++))
|
|
# Track that dep blocks pkg from building
|
|
blocks_packages[$dep]="${blocks_packages[$dep]} $pkg"
|
|
fi
|
|
done
|
|
done < <(get_package_deps "$pkg")
|
|
done
|
|
|
|
# Start with packages that have all dependencies met (count = 0)
|
|
ready_to_build=()
|
|
for pkg in "${PACKAGES_TO_BUILD[@]}"; do
|
|
if [[ ${unmet_deps_count[$pkg]} -eq 0 ]]; then
|
|
ready_to_build+=("$pkg")
|
|
fi
|
|
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]}"
|
|
ready_to_build=("${ready_to_build[@]:1}")
|
|
ORDERED_PACKAGES+=("$current")
|
|
|
|
# This package is now built, so packages waiting for it can proceed
|
|
for blocked_pkg in ${blocks_packages[$current]}; do
|
|
((unmet_deps_count[$blocked_pkg]--))
|
|
if [[ ${unmet_deps_count[$blocked_pkg]} -eq 0 ]]; then
|
|
ready_to_build+=("$blocked_pkg")
|
|
fi
|
|
done
|
|
done
|
|
|
|
# Check for circular dependencies
|
|
if [[ ${#ORDERED_PACKAGES[@]} -ne ${#PACKAGES_TO_BUILD[@]} ]]; then
|
|
echo "ERROR: Circular dependency detected!"
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> Build order: ${ORDERED_PACKAGES[@]}"
|
|
|
|
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
|
|
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!"
|