mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-05 19:18:25 +00:00
Version release-candidate Debian packages as X.Y.Z~rcN
A tag's -rcN suffix became the .deb version X.Y.Z-rcN, which dpkg reads as a revision of X.Y.Z and sorts above the release, so the release never upgraded a host that had installed the candidate. git does not allow '~' in a tag, so the Linux package workflow now maps a pre-release suffix to ~ for the .deb Version only. The tarball, the workflow artifact and the .deb file name keep the tag's form: GitHub renames a release asset whose name has special characters, which would leave checksums-linux.txt naming a file the release does not carry. Release and branch builds are unchanged. A new check runs the workflow's own derivation on a release tag, a candidate tag and a branch, confirms with dpkg that the candidate sorts below the release, and checks that the output is declared, passed to the .deb build and that the file is renamed. Both local and GitHub CI run it.
This commit is contained in:
@@ -84,6 +84,11 @@ jobs:
|
||||
# a matrix suite.
|
||||
- name: Run convergence-gate unit tests
|
||||
run: bash testing/lib/wait-converge-test.sh
|
||||
# Runs package-linux.yml's own version derivation on a release tag, a
|
||||
# candidate tag and a branch. Not a matrix suite either, so it is kept in
|
||||
# step with ci-local.sh's run_deb_version by hand.
|
||||
- name: Check the Debian version derived for tags and branches
|
||||
run: bash testing/check-deb-version.sh
|
||||
|
||||
fmt:
|
||||
name: Format check
|
||||
|
||||
@@ -18,6 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
linux_package_version: ${{ steps.linux_version.outputs.linux_package_version }}
|
||||
deb_package_version: ${{ steps.linux_version.outputs.deb_package_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
@@ -42,7 +43,18 @@ jobs:
|
||||
VERSION="${BASE_VERSION}+${BRANCH}.${HEIGHT}.${HASH}"
|
||||
fi
|
||||
|
||||
# dpkg reads X.Y.Z-rcN as revision rcN of X.Y.Z and sorts it above
|
||||
# the release; X.Y.Z~rcN sorts below it. git refuses '~' in a ref
|
||||
# name, so the tag carries '-' and this maps it, for the .deb only.
|
||||
# testing/check-deb-version.sh runs this step's text.
|
||||
DEB_VERSION="$VERSION"
|
||||
if [[ "$GITHUB_REF" == refs/tags/* ]] \
|
||||
&& [[ "$VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-((alpha|beta|pre|rc)[0-9]*)$ ]]; then
|
||||
DEB_VERSION="${BASH_REMATCH[1]}~${BASH_REMATCH[2]}"
|
||||
fi
|
||||
|
||||
echo "linux_package_version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "deb_package_version=${DEB_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
build:
|
||||
name: Build Linux artifacts (${{ matrix.artifact_arch }})
|
||||
@@ -128,7 +140,7 @@ jobs:
|
||||
: ${GITHUB_OUTPUT:=/tmp/github_output}
|
||||
|
||||
packaging/debian/build-deb-container.sh \
|
||||
--version "${{ needs.determine-versioning.outputs.linux_package_version }}" \
|
||||
--version "${{ needs.determine-versioning.outputs.deb_package_version }}" \
|
||||
--output-dir deploy \
|
||||
--image-archive "$RUNNER_TEMP/deb-builder-image.tar" \
|
||||
| tee /tmp/build-deb-container.log
|
||||
@@ -149,6 +161,19 @@ jobs:
|
||||
;;
|
||||
esac
|
||||
|
||||
# A candidate's package Version carries '~' (see determine-versioning),
|
||||
# and cargo-deb puts it in the file name. GitHub renames a release
|
||||
# asset whose name has special characters, which would leave
|
||||
# checksums-linux.txt naming a file the release does not have. The
|
||||
# file takes the tag's '-' instead; the Version inside is unchanged.
|
||||
case "$DEB_FILE" in
|
||||
*~*)
|
||||
RENAMED="$(dirname "$DEB_FILE")/$(basename "$DEB_FILE" | tr '~' '-')"
|
||||
mv "$DEB_FILE" "$RENAMED"
|
||||
DEB_FILE="$RENAMED"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Record it relative to the checkout: upload-artifact derives the
|
||||
# archive layout from the common ancestor of its paths, and an
|
||||
# absolute path here would nest the package under directories the
|
||||
|
||||
Executable
+175
@@ -0,0 +1,175 @@
|
||||
#!/bin/bash
|
||||
# ── Debian package version derivation check ─────────────────────────────────
|
||||
# A release candidate is tagged vX.Y.Z-rcN, because git refuses '~' in a ref
|
||||
# name. dpkg reads X.Y.Z-rcN as revision rcN of X.Y.Z and sorts it ABOVE the
|
||||
# release, so a host that installed the candidate is never upgraded by the
|
||||
# release. package-linux.yml's "Derive Linux package version" step therefore
|
||||
# maps the tag's pre-release suffix to '~' for the .deb, which dpkg sorts below.
|
||||
#
|
||||
# This runs that step's own text, taken from the workflow, rather than a copy,
|
||||
# so the check and the workflow cannot drift apart. Cases:
|
||||
# refs/tags/v0.5.3 both versions 0.5.3
|
||||
# refs/tags/v0.5.3-rc1 deb 0.5.3~rc1, tarball and artifact 0.5.3-rc1, and
|
||||
# dpkg orders 0.5.2 < 0.5.3~rc1 < 0.5.3
|
||||
# refs/heads/maint deb equals the tarball version, which is
|
||||
# <Cargo version>+maint.<height>.<hash>
|
||||
#
|
||||
# It also checks the wiring the derivation needs to have any effect: the job
|
||||
# declares the deb_package_version output, the .deb build passes it as
|
||||
# --version, and that step renames a '~' in the package file name to '-' (a
|
||||
# GitHub release renames an asset with special characters in its name, which
|
||||
# would leave checksums-linux.txt naming a file the release does not have).
|
||||
# Those three are read from the text, not executed.
|
||||
#
|
||||
# Exit 0 = clean. Exit 1 = a case or a wiring check failed. Exit 2 = the check
|
||||
# could not run (no dpkg, no PyYAML, the step not found, or an output missing);
|
||||
# never treated as a pass.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
WORKFLOW="$PROJECT_ROOT/.github/workflows/package-linux.yml"
|
||||
|
||||
cant() {
|
||||
echo "check-deb-version: $*; cannot verify the Debian version derivation" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
[ -f "$WORKFLOW" ] || cant "missing $WORKFLOW"
|
||||
command -v dpkg >/dev/null 2>&1 || cant "dpkg not found"
|
||||
command -v python3 >/dev/null 2>&1 || cant "python3 not found"
|
||||
python3 -c "import yaml" >/dev/null 2>&1 || cant "python3 module 'yaml' not found"
|
||||
|
||||
WORK=$(mktemp -d) || cant "mktemp failed"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
# Pull the derivation step's run text, the job's declared outputs and the
|
||||
# .deb build step's run text out of the workflow.
|
||||
if ! python3 - "$WORKFLOW" "$WORK" <<'PY'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
workflow, work = sys.argv[1], Path(sys.argv[2])
|
||||
doc = yaml.safe_load(Path(workflow).read_text(encoding="utf-8"))
|
||||
jobs = doc.get("jobs") or {}
|
||||
|
||||
|
||||
def step_run(job, name):
|
||||
for step in (jobs.get(job) or {}).get("steps") or []:
|
||||
if step.get("name") == name:
|
||||
return step.get("run")
|
||||
return None
|
||||
|
||||
|
||||
derive = step_run("determine-versioning", "Derive Linux package version")
|
||||
build = step_run("build", "Build Debian package in the pinned container")
|
||||
if not derive or not build:
|
||||
missing = "derivation" if not derive else "Debian build"
|
||||
print(f"check-deb-version: the {missing} step was not found in {workflow}",
|
||||
file=sys.stderr)
|
||||
sys.exit(2)
|
||||
(work / "derive.sh").write_text(derive, encoding="utf-8")
|
||||
(work / "build.sh").write_text(build, encoding="utf-8")
|
||||
outputs = (jobs.get("determine-versioning") or {}).get("outputs") or {}
|
||||
(work / "outputs").write_text(
|
||||
"".join(f"{k}={v}\n" for k, v in outputs.items()), encoding="utf-8")
|
||||
PY
|
||||
then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
FAILED=0
|
||||
ok() { echo " PASS $*"; }
|
||||
bad() { echo " FAIL $*"; FAILED=$((FAILED + 1)); }
|
||||
|
||||
# Run the derivation for one ref and load its outputs into LINUX and DEB.
|
||||
# Exits 2 when the step fails or does not write both outputs: no version was
|
||||
# established, which is not a failed case.
|
||||
derive() {
|
||||
local ref="$1" out="$WORK/github_output"
|
||||
: > "$out"
|
||||
if ! (cd "$PROJECT_ROOT" && GITHUB_OUTPUT="$out" GITHUB_REF="$ref" \
|
||||
GITHUB_REF_NAME="${ref#refs/*/}" bash -eo pipefail "$WORK/derive.sh") >"$WORK/derive.log" 2>&1; then
|
||||
echo "check-deb-version: the derivation failed for $ref:" >&2
|
||||
cat "$WORK/derive.log" >&2
|
||||
exit 2
|
||||
fi
|
||||
LINUX=$(sed -n 's/^linux_package_version=//p' "$out")
|
||||
DEB=$(sed -n 's/^deb_package_version=//p' "$out")
|
||||
if [ -z "$LINUX" ] || [ -z "$DEB" ]; then
|
||||
cant "the derivation for $ref did not write both outputs (linux '$LINUX', deb '$DEB')"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Record whether dpkg orders $1 $2 $3 (lt, gt, ...).
|
||||
expect_order() {
|
||||
if dpkg --compare-versions "$1" "$2" "$3"; then
|
||||
ok "dpkg: $1 $2 $3"
|
||||
else
|
||||
bad "dpkg: $1 $2 $3 does not hold"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Record whether a derived version equals the expected one.
|
||||
expect_eq() {
|
||||
local label="$1" got="$2" want="$3"
|
||||
if [ "$got" = "$want" ]; then
|
||||
ok "$label: $got"
|
||||
else
|
||||
bad "$label: $got (want $want)"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
echo "Release tag refs/tags/v0.5.3"
|
||||
derive refs/tags/v0.5.3
|
||||
expect_eq "linux_package_version" "$LINUX" "0.5.3"
|
||||
expect_eq "deb_package_version" "$DEB" "0.5.3"
|
||||
|
||||
echo "Candidate tag refs/tags/v0.5.3-rc1"
|
||||
derive refs/tags/v0.5.3-rc1
|
||||
expect_eq "linux_package_version" "$LINUX" "0.5.3-rc1"
|
||||
expect_eq "deb_package_version" "$DEB" "0.5.3~rc1"
|
||||
expect_order "$DEB" lt 0.5.3
|
||||
expect_order "$DEB" gt 0.5.2
|
||||
|
||||
echo "Branch refs/heads/maint"
|
||||
derive refs/heads/maint
|
||||
CRATE=$(awk -F'"' '/^version = /{print $2; exit}' "$PROJECT_ROOT/Cargo.toml")
|
||||
HEIGHT=$(git -C "$PROJECT_ROOT" rev-list --count HEAD) || cant "git rev-list failed"
|
||||
HASH=$(git -C "$PROJECT_ROOT" rev-parse --short HEAD) || cant "git rev-parse failed"
|
||||
[ -n "$CRATE" ] || cant "no version in Cargo.toml"
|
||||
expect_eq "linux_package_version" "$LINUX" "${CRATE}+maint.${HEIGHT}.${HASH}"
|
||||
expect_eq "deb_package_version" "$DEB" "$LINUX"
|
||||
|
||||
echo "Wiring"
|
||||
# shellcheck disable=SC2016 # the ${{ }} expressions are workflow text, not shell
|
||||
if grep -qxF 'deb_package_version=${{ steps.linux_version.outputs.deb_package_version }}' "$WORK/outputs"; then
|
||||
ok "determine-versioning declares the deb_package_version output"
|
||||
else
|
||||
bad "determine-versioning does not declare deb_package_version from the derivation step"
|
||||
fi
|
||||
# shellcheck disable=SC2016
|
||||
if grep -qF -- '--version "${{ needs.determine-versioning.outputs.deb_package_version }}"' "$WORK/build.sh"; then
|
||||
ok "the .deb build passes deb_package_version as --version"
|
||||
else
|
||||
bad "the .deb build does not pass deb_package_version as --version"
|
||||
fi
|
||||
if grep -qF "tr '~' '-'" "$WORK/build.sh"; then
|
||||
ok "the .deb build renames a '~' in the package file name"
|
||||
else
|
||||
bad "the .deb build does not rename a '~' in the package file name"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
echo "check-deb-version: all checks passed"
|
||||
exit 0
|
||||
fi
|
||||
echo "check-deb-version: $FAILED check(s) failed"
|
||||
exit 1
|
||||
@@ -1572,6 +1572,17 @@ run_wait_converge() {
|
||||
record "wait-converge" $rc
|
||||
}
|
||||
|
||||
# The .deb version package-linux.yml derives for a release tag, a candidate
|
||||
# tag and a branch. A candidate must sort below its release under dpkg, which
|
||||
# the tag's -rcN does not, so the workflow maps it to ~rcN; this runs the
|
||||
# workflow's own step text. Static, about a second, and needs nothing built.
|
||||
run_deb_version() {
|
||||
local rc=0
|
||||
info "[deb-version] Checking the Debian version derived for tags and branches"
|
||||
bash "$SCRIPT_DIR/check-deb-version.sh" || rc=$?
|
||||
record "deb-version" $rc
|
||||
}
|
||||
|
||||
# ── Main ───────────────────────────────────────────────────────────────────
|
||||
|
||||
main() {
|
||||
@@ -1593,6 +1604,7 @@ main() {
|
||||
run_action_pins
|
||||
run_comment_refs
|
||||
run_wait_converge
|
||||
run_deb_version
|
||||
|
||||
if [[ "$TEST_ONLY" == true ]]; then
|
||||
run_tests
|
||||
|
||||
Reference in New Issue
Block a user