Files
omarchy-pkgs/helpers/package-metadata.sh
T
David Heinemeier HanssonandClaude Opus 5.5 88947c367b Auto-merge upstream updates for trusted packages (#892)
* Auto-merge upstream updates for trusted packages

The unattended lane already runs every upstream provider, not only branch
watches, so trusted release feeds can ship without waiting on the reviewed
sync PR. Opt in the browsers, large vendors' apps, Omacom's own projects,
and the vendor binaries that hold fresh releases for 24h: 39 packages,
including rawmakase-bin and gliff.

Drivers, firmware, the bootloader, core desktop components, beta and
nightly channels, and community projects stay on the reviewed lane.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Auto-merge grok-bot, perplexity, disktree-bin, cliamp, openclaw, voxtype-bin and ttfx

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:42:23 +02:00

655 lines
23 KiB
Bash

# Package metadata helpers for Omarchy package build tooling
#
# Expects package directories in $PKGBUILDS_DIR, each with:
# .omarchy/package.json
#
# Minimal schema (legacy source:aur remains readable for initial imports):
# { "source": "local" }
# { "source": "local", "sync": false }
# { "source": "local", "release_ring": "fast" }
# { "source": "local", "skip_build": true }
# { "source": "local", "rebuild_on": ["qt6-base"] }
# { "source": "local", "upstream": { "watch": { "github": "owner/repo", "pattern": "v(?P<version>[0-9.]+)" } } }
# { "source": "local", "channels": ["edge"] }
# { "source": "local", "channels": ["edge", "rc", "stable"] }
# { "source": "local", "min_release_age": "24h" }
# { "source": "local", "auto_merge": true, "upstream": { "watch": { "git_branch": "...", "branch": "main" } } }
# { "source": "local", "upstream": { "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "x86_64": ["name-{tag}-x64.tar.xz"] } } }
# { "source": "local", "upstream": { "github": "owner/repo", "digests": true, "assets": { "x86_64": "name-{tag}-x64.tar.xz" } } }
# { "source": "local", "upstream": { "git_tags": "https://example/repo.git", "tag_pattern": "v{pkgver}", "sources": { "any": ["https://example/archive/{tag}.tar.gz"] } } }
# { "source": "local", "upstream": { "npm": "@scope/package", "sources": { "any": ["{npm_tarball}"] } } }
# { "source": "local", "upstream": { "debian": "https://example/debian/dists/stable/main/binary-amd64/Packages", "package": "example", "sources": { "any": ["https://example/releases/{pkgver}.tar.gz"] } } }
#
# bin/import-aur records historical origin.aur and origin.commit;
# 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" ]]
}
# Quarantine window for upstream releases, in seconds. Accepts a bare number
# of seconds or a number suffixed s/m/h/d ("24h", "2d"). Unset means 0 (no
# hold); an unparseable value -- including a non-string/non-number JSON type
# like false -- returns 1 so callers fail closed instead of silently dropping
# the hold. At most 9 digits: enough for three decades in seconds, and small
# enough that no suffix multiplication can overflow 64-bit arithmetic.
package_min_release_age_seconds() {
local pkgdir="$1" metadata raw
metadata=$(metadata_file_for_dir "$pkgdir")
if [[ ! -f "$metadata" ]]; then
echo 0
return 0
fi
# A present-but-empty value maps to "unparseable", not to "absent": only a
# missing key means no hold, so '"min_release_age": ""' cannot silently
# disable the quarantine.
raw=$(jq -r '
if has("min_release_age") | not then ""
elif (.min_release_age | type) == "string" or (.min_release_age | type) == "number" then
.min_release_age | tostring | if . == "" then "unparseable" else . end
else "unparseable" end
' "$metadata")
if [[ -z "$raw" ]]; then
echo 0
return 0
fi
[[ "$raw" =~ ^([0-9]{1,9})([smhd]?)$ ]] || return 1
# Forced base 10: bash arithmetic would otherwise read "010" as octal.
local n=$((10#${BASH_REMATCH[1]}))
case "${BASH_REMATCH[2]}" in
""|s) echo "$n" ;;
m) echo $((n * 60)) ;;
h) echo $((n * 3600)) ;;
d) echo $((n * 86400)) ;;
esac
}
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" ]]
}
# Read one variable from a PKGBUILD the way makepkg would see it.
#
# makepkg always exports CARCH, so PKGBUILDs may branch on it at file scope
# (per-architecture sources, tarball suffixes, even `return` for an
# unsupported architecture). Sourcing without CARCH takes the wrong branch or
# aborts partway, which leaves pkgver and pkgrel empty — and an empty version
# never equals the published one, so the package is queued for a rebuild that
# promotion then refuses. Every read of a PKGBUILD goes through here.
#
# Prints the value; exit status is that of `source PKGBUILD` itself, so a
# caller can tell "variable empty" from "PKGBUILD could not be read".
package_pkgbuild_var() {
local pkgdir="$1"
local var="$2"
local arch="${3:-${ARCH:-x86_64}}"
(cd "$pkgdir" && env -u OMARCHY_SRC CARCH="$arch" bash -c '
source PKGBUILD >/dev/null 2>&1
rc=$?
printf "%s\n" "${!1:-}"
exit "$rc"
' _ "$var")
}
# The architectures declared by a PKGBUILD. Set CARCH while reading it so a
# conditional arch=() assignment is evaluated for the architecture we are
# actually checking, even when the repository host is a different one.
package_arches() {
local pkgdir="$1"
local arch="${2:-${ARCH:-x86_64}}"
(cd "$pkgdir" && env -u OMARCHY_SRC CARCH="$arch" bash -c '
source PKGBUILD >/dev/null 2>&1
printf "%s\n" "${arch[*]}"
')
}
package_supports_arch() {
local pkgdir="$1"
local target="${2:-${ARCH:-x86_64}}"
local arches
arches=$(package_arches "$pkgdir" "$target") || return 1
case " $arches " in
*" any "* | *" $target "*) return 0 ;;
*) return 1 ;;
esac
}
# The channel DB indexes only its newest version, but older published archives
# remain immutable. Both the scheduler and build planner must skip an existing
# filename even when the checkout differs from the version currently indexed.
package_version_is_published() {
local repo_dir="$1" package="$2" version="$3" target="$4" path
for path in "$repo_dir/$package-$version-$target.pkg.tar."* \
"$repo_dir/$package-$version-any.pkg.tar."*; do
[[ -f "$path" && "$path" != *.sig ]] && return 0
done
return 1
}
# Channel membership: where a package may be published. Packages without a
# `channels` key are members of every channel (they flow edge -> rc -> stable).
package_has_channels() {
local pkgdir="$1" metadata
metadata=$(metadata_file_for_dir "$pkgdir")
[[ -f "$metadata" ]] || return 1
jq -e 'has("channels")' "$metadata" >/dev/null
}
package_channels() {
local pkgdir="$1" metadata
metadata=$(metadata_file_for_dir "$pkgdir")
[[ -f "$metadata" ]] || return 0
jq -r '(.channels // [])[]' "$metadata"
}
package_in_channel() {
local pkgdir="$1" channel="$2"
package_has_channels "$pkgdir" || return 0
package_channels "$pkgdir" | grep -qx "$channel"
}
# A pinned package's version is set per release by the orchestrator on the rc
# branch, not by whatever the current checkout happens to say. Only a build
# running from that branch's worktree (OMARCHY_RC_PINS=1) may build it for rc;
# otherwise master's shipped pins would try to overwrite an in-flight RC with
# an older version.
package_is_pinned() {
local pkgdir="$1" metadata
metadata=$(metadata_file_for_dir "$pkgdir")
[[ -f "$metadata" ]] || return 1
[[ "$(jq -r 'if has("pinned") then .pinned else false end' "$metadata")" == "true" ]]
}
package_builds_for_mirror() {
local pkgdir="$1"
local mirror="$2"
package_has_pkgbuild "$pkgdir" || return 1
package_has_metadata "$pkgdir" || return 1
# An explicit `channels` key is the outer bound on where a package may be
# built at all.
if package_has_channels "$pkgdir" && ! package_in_channel "$pkgdir" "$mirror"; then
return 1
fi
case "$mirror" in
edge)
return 0
;;
rc)
# Fast-ring packages build for rc natively rather than being copied from
# stable: the rc channel's Arch base can be sitting anywhere between
# stable's snapshot and edge's, so an artifact linked against stable's
# libraries is not necessarily correct for rc.
package_is_pinned "$pkgdir" && { [[ -n "${OMARCHY_RC_PINS:-}" ]]; return; }
package_is_fast_ring "$pkgdir"
;;
stable)
package_is_pinned "$pkgdir" && return 1
package_is_fast_ring "$pkgdir"
;;
*)
return 1
;;
esac
}
# Whether `bin/repo advance` may carry this package into the given channel:
# a member of that channel that is not built there natively. Native builds are
# authoritative — advancing over them could pair a published filename with
# different bytes, which the R2 cache would never recover from.
package_moves_to_channel() {
local pkgdir="$1" channel="$2"
package_in_channel "$pkgdir" "$channel" || return 1
# Pinned packages build natively in rc from the release pin. That the
# advancing environment lacks OMARCHY_RC_PINS (so *it* may not build them)
# does not make the edge copy movable over the pin's artifact — edge's
# version can be ahead of the in-flight RC.
[[ "$channel" == "rc" ]] && package_is_pinned "$pkgdir" && return 1
! package_builds_for_mirror "$pkgdir" "$channel"
}
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" ]]
}
package_has_upstream_provider() {
local pkgdir="$1" metadata
metadata=$(metadata_file_for_dir "$pkgdir")
[[ -f "$metadata" ]] || return 1
# Any upstream key counts, valid or not: a malformed declaration must reach
# bin/sync-upstream and fail loudly there, not vanish from discovery.
jq -e 'has("upstream")' "$metadata" >/dev/null
}
packages_for_upstream_sync() {
package_dirs | while IFS= read -r pkgdir; do
if package_has_upstream_hook "$pkgdir" || package_has_upstream_provider "$pkgdir"; then
basename "$pkgdir"
fi
done
}
# Upstream updates travel in one of two lanes. The reviewed lane is the
# 6-hourly sync PR a maintainer reads before merging. A package that marks
# "auto_merge": true rides the unattended lane instead: its bump PR is opened
# and auto-merged by the branch tracker as soon as CI is green, which is how a
# package that follows a moving branch (omarchy-dev, omasnap-git) gets rebuilt
# without anyone clicking, and how trusted release feeds (browsers, large
# vendors, Omacom's own projects) ship without waiting for review. The lanes are disjoint so a branch tip can never
# hold up a reviewed vendor release, or the other way round.
package_auto_merge() {
local pkgdir="$1" metadata
metadata=$(metadata_file_for_dir "$pkgdir")
[[ -f "$metadata" ]] || return 1
[[ "$(jq -r 'if has("auto_merge") then .auto_merge else false end' "$metadata")" == "true" ]]
}
# package_in_lane <pkgdir> <reviewed|auto-merge|all>
package_in_lane() {
local pkgdir="$1" lane="$2"
case "$lane" in
all | "") return 0 ;;
auto-merge) package_auto_merge "$pkgdir" ;;
reviewed) ! package_auto_merge "$pkgdir" ;;
*) echo "invalid lane: $lane (expected reviewed, auto-merge, or all)" >&2; return 2 ;;
esac
}
# 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"
local arch="${2:-${ARCH:-x86_64}}"
package_dirs | while IFS= read -r pkgdir; do
if package_builds_for_mirror "$pkgdir" "$mirror" &&
! package_build_skipped "$pkgdir" &&
package_supports_arch "$pkgdir" "$arch"; 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
if ! jq -e 'if has("pinned") | not then true else (.pinned | type) == "boolean" end' "$metadata" >/dev/null; then
echo "invalid pinned for $(basename "$pkgdir"): must be boolean"
return 1
fi
if ! jq -e '
if has("channels") | not then true
else .channels | type == "array" and length > 0
and all(. == "edge" or . == "rc" or . == "stable")
and (unique | length) == length
end
' "$metadata" >/dev/null; then
echo "invalid channels for $(basename "$pkgdir"): must be a non-empty array of unique edge/rc/stable values"
return 1
fi
if ! package_min_release_age_seconds "$pkgdir" >/dev/null; then
echo "invalid min_release_age for $(basename "$pkgdir"): must be a number with optional s/m/h/d suffix"
return 1
fi
if ! jq -e 'if has("auto_merge") | not then true else (.auto_merge | type) == "boolean" end' "$metadata" >/dev/null; then
echo "invalid auto_merge for $(basename "$pkgdir"): must be boolean"
return 1
fi
if package_auto_merge "$pkgdir" && ! package_has_upstream_provider "$pkgdir" && ! package_has_upstream_hook "$pkgdir"; then
echo "invalid auto_merge for $(basename "$pkgdir"): only an upstream watch, provider, or hook can be auto-merged"
return 1
fi
# `has` rather than `// {}`: jq's // treats false as absent, which would
# let "upstream": false slip through as an empty declaration.
if ! jq -e '
def valid_sources:
type == "object" and length > 0 and (to_entries | all(
(.key | test("\\A[a-z0-9_]+\\z"))
and (.value | type == "array" and length > 0 and all(type == "string" and length > 0))
));
def valid_assets:
type == "object" and length > 0 and (to_entries | all(
(.key | test("\\A[a-z0-9_]+\\z"))
and (.value |
(type == "string" and length > 0)
or (type == "array" and length > 0 and all(type == "string" and length > 0) and (unique | length) == length)
)
));
if has("upstream") | not then true
elif (.upstream | type) != "object" then false
else .upstream |
([has("github"), has("git_tags"), has("npm"), has("debian"), has("watch")] | map(select(.)) | length) == 1
and if has("watch") then (.watch | type == "object")
elif has("github") then
(.github | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z"))
and (if has("checksums") then (.checksums | type == "string" and length > 0) else true end)
and (if has("digests") then (.digests | type == "boolean") else true end)
and (if has("latest_only") then (.latest_only | type == "boolean") else true end)
and (has("checksums") != (has("digests") and .digests == true))
and (.assets | valid_assets)
and (if has("sources") then
(.sources | valid_sources)
and ((.assets | keys) as $assets | (.sources | keys) as $sources | ($assets - $sources | length) == ($assets | length))
else true end)
elif has("git_tags") then
(.git_tags | type == "string" and test("\\Ahttps://[^[:space:]]+\\.git\\z"))
and (.tag_pattern | type == "string" and (split("{pkgver}") | length) == 2)
and (.sources | valid_sources)
elif has("npm") then
(.npm | type == "string" and test("\\A(@[a-z0-9_.-]+/)?[a-z0-9_.-]+\\z"))
and ((.dist_tag // "latest") | type == "string" and test("\\A[a-z0-9_.-]+\\z"))
and (.sources | valid_sources)
else
(.debian | type == "string" and test("\\Ahttps://[^[:space:]]+\\z"))
and (.package | type == "string" and test("\\A[a-z0-9][a-z0-9+.-]*\\z"))
and (.sources | valid_sources)
end
end
' "$metadata" >/dev/null; then
echo "invalid upstream for $(basename "$pkgdir"): configure exactly one valid github, git_tags, npm, debian, or watch provider"
return 1
fi
if jq -e '.upstream? | objects | has("watch")' "$metadata" >/dev/null; then
python3 "${BASH_SOURCE[0]%/*}/upstream-watch.py" validate "$pkgdir" || return 1
fi
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 '
def version_map:
type == "object" and (to_entries | all(.value | type == "string" and length > 0));
(.rebuilt_against // {}) as $record |
($record | version_map) or
(($record | type) == "object"
and ((($record | keys) - ["x86_64", "aarch64"]) | length == 0)
and ($record | to_entries | all(.value | version_map)))
' "$metadata" >/dev/null; then
echo "invalid rebuilt_against for $(basename "$pkgdir"): must map architectures to package-version maps"
return 1
fi
if ! jq -e '
(.rebuild_on // []) as $triggers |
(.rebuilt_against // {}) as $record |
if ($record | to_entries | all(.value | type == "string")) then
((($record | keys) - $triggers) | length == 0)
else
($record | to_entries | all((((.value | keys) - $triggers) | length) == 0))
end
' "$metadata" >/dev/null; then
echo "invalid rebuilt_against for $(basename "$pkgdir"): records a package that rebuild_on does not name"
return 1
fi
}