A package that links Qt private API has to be rebuilt whenever qt6-base moves, because Qt_6_PRIVATE_API symbols are not covered by the soname and pacman upgrades Qt out from under the installed binary while the dependency stays unversioned. Nothing here noticed. Both version gates ask whether the package's own source moved, and for a VCS package pinned to a commit that answer stays no through every Qt release.
Unlocking the build gate would not have been enough on its own. A rebuild that reuses the published version string produces a package pacman never offers anyone, so the trigger has to edit git and bump pkgrel, which is why it sits beside sync-aur and sync-upstream rather than inside check-versions or the builder. Once pkgrel moves, both existing gates already do the right thing untouched.
Packages opt in with rebuild_on in .omarchy/package.json. bin/sync-rebuilds records what each was last bumped for in rebuilt_against and compares that to core, extra and multilib, ignoring testing and kde-unstable because those are not what the builder links against. A package with no record yet is only recorded, never bumped: what its published build linked against is not knowable from here, so the first run establishes the baseline. For an AUR-synced package the bump is written as the dotted Omarchy pkgrel suffix in the metadata as well, since the next sync replaces the PKGBUILD wholesale and would otherwise drop it.
🤖 Generated by Opus 5 in Claude Code.
347 lines
9.5 KiB
Bash
347 lines
9.5 KiB
Bash
# Package metadata helpers for Omarchy package build tooling
|
|
#
|
|
# Expects package directories in $PKGBUILDS_DIR, each with:
|
|
# .omarchy/package.json
|
|
#
|
|
# Minimal schema:
|
|
# { "source": "aur" }
|
|
# { "source": "aur", "sync": false }
|
|
# { "source": "aur", "aur": "different-aur-name" }
|
|
# { "source": "aur", "release_ring": "fast" }
|
|
# { "source": "aur", "skip_build": true }
|
|
# { "source": "aur", "pkgrel": { "suffix": 1, "offset": 1 } }
|
|
# { "source": "aur", "rebuild_on": ["qt6-base"] }
|
|
# { "source": "local" }
|
|
#
|
|
# bin/sync-aur also writes upstream_commit for AUR-backed packages, and
|
|
# bin/sync-rebuilds writes rebuilt_against for packages declaring rebuild_on.
|
|
|
|
if [[ -z "${PKGBUILDS_DIR:-}" ]]; then
|
|
if [[ -n "${BUILD_ROOT:-}" ]]; then
|
|
PKGBUILDS_DIR="$BUILD_ROOT/pkgbuilds"
|
|
elif [[ -d /pkgbuilds ]]; then
|
|
PKGBUILDS_DIR="/pkgbuilds"
|
|
else
|
|
PKGBUILDS_DIR="pkgbuilds"
|
|
fi
|
|
fi
|
|
|
|
metadata_file_for_dir() {
|
|
local pkgdir="$1"
|
|
echo "$pkgdir/.omarchy/package.json"
|
|
}
|
|
|
|
package_dir_for_name() {
|
|
local package="$1"
|
|
local pkgdir="$PKGBUILDS_DIR/$package"
|
|
|
|
[[ -d "$pkgdir" && -f "$pkgdir/PKGBUILD" ]] || return 1
|
|
echo "$pkgdir"
|
|
}
|
|
|
|
package_metadata_value() {
|
|
local pkgdir="$1"
|
|
local jq_filter="$2"
|
|
local default="${3:-}"
|
|
local metadata
|
|
|
|
metadata=$(metadata_file_for_dir "$pkgdir")
|
|
if [[ ! -f "$metadata" ]]; then
|
|
echo "$default"
|
|
return 0
|
|
fi
|
|
|
|
jq -r --arg default "$default" "$jq_filter // \$default" "$metadata"
|
|
}
|
|
|
|
package_sync_enabled() {
|
|
local pkgdir="$1"
|
|
local metadata source sync
|
|
|
|
metadata=$(metadata_file_for_dir "$pkgdir")
|
|
[[ -f "$metadata" ]] || return 1
|
|
|
|
source=$(jq -r '.source // ""' "$metadata")
|
|
[[ "$source" == "aur" ]] || return 1
|
|
|
|
sync=$(jq -r 'if has("sync") then .sync else true end' "$metadata")
|
|
[[ "$sync" != "false" ]]
|
|
}
|
|
|
|
package_release_ring() {
|
|
local pkgdir="$1"
|
|
package_metadata_value "$pkgdir" '.release_ring' ""
|
|
}
|
|
|
|
package_is_fast_ring() {
|
|
local pkgdir="$1"
|
|
[[ "$(package_release_ring "$pkgdir")" == "fast" ]]
|
|
}
|
|
|
|
package_build_skipped() {
|
|
local pkgdir="$1"
|
|
local metadata skip_build
|
|
|
|
metadata=$(metadata_file_for_dir "$pkgdir")
|
|
[[ -f "$metadata" ]] || return 1
|
|
|
|
skip_build=$(jq -r 'if has("skip_build") then .skip_build else false end' "$metadata")
|
|
[[ "$skip_build" == "true" ]]
|
|
}
|
|
|
|
package_has_metadata() {
|
|
local pkgdir="$1"
|
|
[[ -f "$(metadata_file_for_dir "$pkgdir")" ]]
|
|
}
|
|
|
|
package_has_pkgbuild() {
|
|
local pkgdir="$1"
|
|
[[ -f "$pkgdir/PKGBUILD" ]]
|
|
}
|
|
|
|
package_builds_for_mirror() {
|
|
local pkgdir="$1"
|
|
local mirror="$2"
|
|
|
|
package_has_pkgbuild "$pkgdir" || return 1
|
|
package_has_metadata "$pkgdir" || return 1
|
|
|
|
case "$mirror" in
|
|
edge)
|
|
return 0
|
|
;;
|
|
stable)
|
|
package_is_fast_ring "$pkgdir"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
package_dirs() {
|
|
[[ -d "$PKGBUILDS_DIR" ]] || return 0
|
|
|
|
find "$PKGBUILDS_DIR" -mindepth 1 -maxdepth 1 -type d -print | sort | while IFS= read -r pkgdir; do
|
|
package_has_pkgbuild "$pkgdir" || continue
|
|
package_has_metadata "$pkgdir" || continue
|
|
echo "$pkgdir"
|
|
done
|
|
}
|
|
|
|
packages_for_aur_sync() {
|
|
package_dirs | while IFS= read -r pkgdir; do
|
|
if package_sync_enabled "$pkgdir"; then
|
|
basename "$pkgdir"
|
|
fi
|
|
done
|
|
}
|
|
|
|
package_has_upstream_hook() {
|
|
local pkgdir="$1"
|
|
[[ -f "$pkgdir/.omarchy/upstream.sh" ]]
|
|
}
|
|
|
|
packages_for_upstream_sync() {
|
|
package_dirs | while IFS= read -r pkgdir; do
|
|
if package_has_upstream_hook "$pkgdir"; then
|
|
basename "$pkgdir"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Packages that must be rebuilt when a dependency they link against changes,
|
|
# even though nothing in their own source moved. `rebuild_on` names those
|
|
# dependencies; `rebuilt_against` records the versions the checked-in pkgrel was
|
|
# last bumped for.
|
|
package_rebuild_triggers() {
|
|
local pkgdir="$1"
|
|
local metadata
|
|
|
|
metadata=$(metadata_file_for_dir "$pkgdir")
|
|
[[ -f "$metadata" ]] || return 0
|
|
|
|
jq -r '(.rebuild_on // [])[]' "$metadata"
|
|
}
|
|
|
|
package_has_rebuild_triggers() {
|
|
local pkgdir="$1"
|
|
[[ -n "$(package_rebuild_triggers "$pkgdir")" ]]
|
|
}
|
|
|
|
packages_for_rebuild_sync() {
|
|
package_dirs | while IFS= read -r pkgdir; do
|
|
if package_has_rebuild_triggers "$pkgdir"; then
|
|
basename "$pkgdir"
|
|
fi
|
|
done
|
|
}
|
|
|
|
packages_for_mirror() {
|
|
local mirror="$1"
|
|
|
|
package_dirs | while IFS= read -r pkgdir; do
|
|
if package_builds_for_mirror "$pkgdir" "$mirror"; then
|
|
basename "$pkgdir"
|
|
fi
|
|
done
|
|
}
|
|
|
|
packages_for_unscoped_build() {
|
|
local mirror="$1"
|
|
|
|
package_dirs | while IFS= read -r pkgdir; do
|
|
if package_builds_for_mirror "$pkgdir" "$mirror" && ! package_build_skipped "$pkgdir"; then
|
|
basename "$pkgdir"
|
|
fi
|
|
done
|
|
}
|
|
|
|
package_extract_vcs_hash_from_version() {
|
|
local version="$1"
|
|
local version_no_pkgrel="${version%-*}"
|
|
local candidate hash=""
|
|
|
|
# Drop an epoch prefix before scanning for commit-looking components.
|
|
if [[ "$version_no_pkgrel" == *:* ]]; then
|
|
version_no_pkgrel="${version_no_pkgrel#*:}"
|
|
fi
|
|
|
|
while IFS= read -r candidate; do
|
|
# Prefer candidates explicitly prefixed with `g`, but also accept bare hex
|
|
# hashes that contain at least one a-f character (e.g. r21251.626ee68).
|
|
if [[ "$candidate" == g* || "$candidate" =~ [a-f] ]]; then
|
|
hash="${candidate#g}"
|
|
fi
|
|
done < <(echo "$version_no_pkgrel" | grep -oE 'g?[a-f0-9]{7,40}')
|
|
|
|
[[ -n "$hash" ]] && echo "${hash:0:7}"
|
|
}
|
|
|
|
package_first_git_source() {
|
|
local pkgdir="$1"
|
|
|
|
(cd "$pkgdir" && env -u OMARCHY_SRC bash -c '
|
|
source PKGBUILD 2>/dev/null
|
|
for s in "${source[@]}"; do
|
|
url="${s#*::}"
|
|
[[ "$url" == git+* ]] && { echo "${url#git+}"; break; }
|
|
done')
|
|
}
|
|
|
|
package_git_upstream_hash() {
|
|
local pkgdir="$1"
|
|
local source_spec source_url fragment ref hash
|
|
|
|
source_spec=$(package_first_git_source "$pkgdir") || return 1
|
|
[[ -n "$source_spec" ]] || return 1
|
|
|
|
source_url="${source_spec%%#*}"
|
|
fragment=""
|
|
if [[ "$source_spec" == *"#"* ]]; then
|
|
fragment="${source_spec#*#}"
|
|
fi
|
|
|
|
case "$fragment" in
|
|
"")
|
|
hash=$(git ls-remote "$source_url" HEAD 2>/dev/null | awk 'NR == 1 { print substr($1, 1, 7) }')
|
|
;;
|
|
branch=*)
|
|
ref="${fragment#branch=}"
|
|
[[ -n "$ref" ]] || return 1
|
|
hash=$(git ls-remote "$source_url" "refs/heads/$ref" 2>/dev/null | awk 'NR == 1 { print substr($1, 1, 7) }')
|
|
;;
|
|
tag=*)
|
|
ref="${fragment#tag=}"
|
|
[[ -n "$ref" ]] || return 1
|
|
hash=$(git ls-remote "$source_url" "refs/tags/$ref^{}" "refs/tags/$ref" 2>/dev/null | awk '
|
|
$2 ~ /\^\{\}$/ { print substr($1, 1, 7); found=1; exit }
|
|
NR == 1 { first=substr($1, 1, 7) }
|
|
END { if (!found && first != "") print first }
|
|
')
|
|
;;
|
|
commit=*)
|
|
ref="${fragment#commit=}"
|
|
[[ "$ref" =~ ^[a-f0-9]{7,40}$ ]] || return 1
|
|
hash="${ref:0:7}"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
[[ -n "$hash" ]] || return 1
|
|
echo "$hash"
|
|
}
|
|
|
|
validate_package_metadata() {
|
|
local pkgdir="$1"
|
|
local metadata source sync skip_build aur ring pkgrel_type
|
|
|
|
metadata=$(metadata_file_for_dir "$pkgdir")
|
|
[[ -f "$metadata" ]] || { echo "missing metadata: $metadata"; return 1; }
|
|
|
|
jq empty "$metadata" >/dev/null || return 1
|
|
|
|
source=$(jq -r '.source // ""' "$metadata")
|
|
case "$source" in
|
|
aur|local) ;;
|
|
*) echo "invalid source for $(basename "$pkgdir"): $source"; return 1 ;;
|
|
esac
|
|
|
|
sync=$(jq -r 'if has("sync") then .sync | type else "missing" end' "$metadata")
|
|
case "$sync" in
|
|
boolean|missing) ;;
|
|
*) echo "invalid sync for $(basename "$pkgdir"): must be boolean"; return 1 ;;
|
|
esac
|
|
|
|
skip_build=$(jq -r 'if has("skip_build") then .skip_build | type else "missing" end' "$metadata")
|
|
case "$skip_build" in
|
|
boolean|missing) ;;
|
|
*) echo "invalid skip_build for $(basename "$pkgdir"): must be boolean"; return 1 ;;
|
|
esac
|
|
|
|
aur=$(jq -r 'if has("aur") then .aur | type else "missing" end' "$metadata")
|
|
case "$aur" in
|
|
string|missing) ;;
|
|
*) echo "invalid aur for $(basename "$pkgdir"): must be string"; return 1 ;;
|
|
esac
|
|
|
|
ring=$(jq -r '.release_ring // ""' "$metadata")
|
|
case "$ring" in
|
|
""|fast) ;;
|
|
*) echo "invalid release_ring for $(basename "$pkgdir"): $ring"; return 1 ;;
|
|
esac
|
|
|
|
pkgrel_type=$(jq -r 'if has("pkgrel") then .pkgrel | type else "missing" end' "$metadata")
|
|
case "$pkgrel_type" in
|
|
object|missing) ;;
|
|
*) echo "invalid pkgrel for $(basename "$pkgdir"): must be an object with optional suffix/offset"; return 1 ;;
|
|
esac
|
|
|
|
if ! jq -e '(.pkgrel // {}) | type == "object" and ((.suffix // 1) | type == "number" and floor == . and . >= 1) and ((.offset // 0) | type == "number" and floor == . and . >= 0)' "$metadata" >/dev/null; then
|
|
echo "invalid pkgrel for $(basename "$pkgdir"): must be an object with optional integer suffix >= 1 and offset >= 0"
|
|
return 1
|
|
fi
|
|
|
|
if ! jq -e '(.upstream_commit // "") | type == "string"' "$metadata" >/dev/null; then
|
|
echo "invalid upstream_commit for $(basename "$pkgdir"): must be a string"
|
|
return 1
|
|
fi
|
|
|
|
if ! jq -e '(.rebuild_on // []) | type == "array" and all(type == "string" and length > 0)' "$metadata" >/dev/null; then
|
|
echo "invalid rebuild_on for $(basename "$pkgdir"): must be an array of package names"
|
|
return 1
|
|
fi
|
|
|
|
if ! jq -e '(.rebuilt_against // {}) | type == "object" and (to_entries | all(.value | type == "string" and length > 0))' "$metadata" >/dev/null; then
|
|
echo "invalid rebuilt_against for $(basename "$pkgdir"): must be an object mapping package names to versions"
|
|
return 1
|
|
fi
|
|
|
|
if ! jq -e '((.rebuilt_against // {}) | keys) - (.rebuild_on // []) | length == 0' "$metadata" >/dev/null; then
|
|
echo "invalid rebuilt_against for $(basename "$pkgdir"): records a package that rebuild_on does not name"
|
|
return 1
|
|
fi
|
|
}
|