Give the release version its own file, outside the layer content hash

Setting the release number was a platform change. FORGEFIRM_RELEASE sat
in forgefirm-image.bb, the recipe hashes as content of meta-forgefirm,
and a change to the content of a layer invalidates every acceptance
result. So a version bump threw away the campaign that was meant to
authorize that very release, and the number therefore had to be decided
before the image the campaign ran on. Nothing said so: the release-flow
page went straight from the kas configuration to the artifact and the
pipeline, while the gate quietly required the recipe value, the rootfs
stamp, the archive's meta-version and the tag to agree. v0.0.1 was cut
on a tree whose number happened to be right; the next one would have
cost a second campaign to discover the rule.

The number moves to forgefirm-release.inc, which carries it and nothing
else, and the manifest leaves that file out of the layer content hash
exactly as it leaves out the component pin files
(FORGEFIRM_MANIFEST_VERSION_SUFFIX, and the same list in
scripts/manifest-from-tree.py, which computes the identity on a
workstation and must agree byte for byte). release.sh reads the number
from the new file.

The version is metadata, not platform content, and this only makes the
manifest say what it already meant: the version string was already
outside the identity hash, and it was the file carrying it that defeated
that. Nothing is weakened. release.sh still requires the number to equal
the rootfs stamp, the .fw meta-version and the release tag, and
image.health still compares the stamp on the running machine with the
manifest's.

Proven: the tree manifest is byte-identical across a bump from 0.0.1 to
0.0.2 (identity a64e51b8e5ecca0af683d4f0 either way, the meta-forgefirm
layer hash unchanged), where before the two differed. bitbake resolves
FORGEFIRM_RELEASE=0.0.1 and FORGEFIRM_VERSION_STRING=v0.0.1 for the
release image through the new require, and the dev image still overrides
the string with its build timestamp.
This commit is contained in:
ScottW514
2026-09-09 18:12:03 -04:00
parent 6967308485
commit d898b5659d
5 changed files with 45 additions and 8 deletions
@@ -44,6 +44,13 @@
FORGEFIRM_MANIFEST_DIR ?= "${sysconfdir}/forgefirm-manifest.d" FORGEFIRM_MANIFEST_DIR ?= "${sysconfdir}/forgefirm-manifest.d"
FORGEFIRM_MANIFEST_CONTENT_LAYERS ?= "meta-forgefirm meta-glowforge-bsp meta-openglow-core" FORGEFIRM_MANIFEST_CONTENT_LAYERS ?= "meta-forgefirm meta-glowforge-bsp meta-openglow-core"
FORGEFIRM_MANIFEST_PIN_SUFFIX ?= "-pin.inc" FORGEFIRM_MANIFEST_PIN_SUFFIX ?= "-pin.inc"
# The release version file. Like a pin file it is metadata, not platform
# content: it carries FORGEFIRM_RELEASE and nothing else, the version
# string is already outside the identity hash (below), and the release
# gate proves the number against the rootfs stamp, the .fw meta-version
# and the tag. Hashing it would make every version bump a platform
# change and invalidate the campaign that authorizes the release.
FORGEFIRM_MANIFEST_VERSION_SUFFIX ?= "forgefirm-release.inc"
do_rootfs[depends] += "virtual/kernel:do_deploy kernel-module-glowforge:do_deploy" do_rootfs[depends] += "virtual/kernel:do_deploy kernel-module-glowforge:do_deploy"
@@ -88,7 +95,8 @@ def forgefirm_manifest_layers(d):
and dirty flag of every layer checkout (informational).""" and dirty flag of every layer checkout (informational)."""
import os, subprocess import os, subprocess
content_layers = (d.getVar('FORGEFIRM_MANIFEST_CONTENT_LAYERS') or '').split() content_layers = (d.getVar('FORGEFIRM_MANIFEST_CONTENT_LAYERS') or '').split()
skip = ('.md',) + tuple((d.getVar('FORGEFIRM_MANIFEST_PIN_SUFFIX') or '').split()) skip = (('.md',) + tuple((d.getVar('FORGEFIRM_MANIFEST_PIN_SUFFIX') or '').split())
+ tuple((d.getVar('FORGEFIRM_MANIFEST_VERSION_SUFFIX') or '').split()))
identity, build = {}, {} identity, build = {}, {}
for layer in (d.getVar('BBLAYERS') or '').split(): for layer in (d.getVar('BBLAYERS') or '').split():
name = os.path.basename(layer.rstrip('/')) name = os.path.basename(layer.rstrip('/'))
@@ -113,7 +113,12 @@ IMAGE_ROOTFS_MAXSIZE = "204800"
# Release images carry the release version; the dev image overrides the # Release images carry the release version; the dev image overrides the
# string with the build timestamp (the same DATETIME as the artifact # string with the build timestamp (the same DATETIME as the artifact
# name) plus a dev tag. # name) plus a dev tag.
FORGEFIRM_RELEASE ?= "0.0.1" #
# FORGEFIRM_RELEASE lives in its own file, which the manifest leaves out
# of the layer content hash: the version is metadata, and a bump must not
# read as a platform change and invalidate a campaign
# (forgefirm-release.inc).
require forgefirm-release.inc
FORGEFIRM_VERSION_STRING ?= "v${FORGEFIRM_RELEASE}" FORGEFIRM_VERSION_STRING ?= "v${FORGEFIRM_RELEASE}"
write_forgefirm_version() { write_forgefirm_version() {
@@ -0,0 +1,18 @@
# The release version, and nothing else.
#
# This file is metadata, not platform content: the image manifest leaves
# it out of the layer content hash the way it leaves out the component
# pin files (forgefirm-image-manifest.bbclass,
# FORGEFIRM_MANIFEST_VERSION_SUFFIX). A version bump therefore changes
# the version and invalidates nothing, so the number can be set at the
# moment a release is cut rather than before the campaign that authorizes
# it. Setting it inside forgefirm-image.bb instead would hash as layer
# content and invalidate every acceptance result, which is what it did
# until v0.0.1.
#
# Nothing else belongs here. The version is still proven end to end:
# scripts/release.sh reads FORGEFIRM_RELEASE from this file and requires
# it to equal the rootfs stamp, the .fw meta-version and the release tag,
# and the acceptance test image.health compares the stamp on the running
# machine with the manifest's.
FORGEFIRM_RELEASE ?= "0.0.1"
+5 -2
View File
@@ -52,8 +52,11 @@ CONTENT_LAYERS = {"meta-forgefirm": ("forgefirm", "meta-forgefirm"),
"meta-glowforge-bsp": ("meta-openglow", "meta-glowforge-bsp"), "meta-glowforge-bsp": ("meta-openglow", "meta-glowforge-bsp"),
"meta-openglow-core": ("meta-openglow", "meta-openglow-core")} "meta-openglow-core": ("meta-openglow", "meta-openglow-core")}
# Left out of a layer's content, as in forgefirm-image-manifest.bbclass # Left out of a layer's content, as in forgefirm-image-manifest.bbclass
# (FORGEFIRM_MANIFEST_PIN_SUFFIX): documentation and the component pin files. # (FORGEFIRM_MANIFEST_PIN_SUFFIX, FORGEFIRM_MANIFEST_VERSION_SUFFIX):
LAYER_SKIP_SUFFIXES = (".md", "-pin.inc") # documentation, the component pin files, and the release version file.
# All three are metadata; hashing them would turn a pin bump or a version
# bump into a platform change and invalidate every acceptance result.
LAYER_SKIP_SUFFIXES = (".md", "-pin.inc", "forgefirm-release.inc")
def git(args, cwd=None, input=None): def git(args, cwd=None, input=None):
+7 -4
View File
@@ -45,7 +45,7 @@
# forgefirm-source-v<version>.tar.gz, and refuses to pack a bundle in which # forgefirm-source-v<version>.tar.gz, and refuses to pack a bundle in which
# a recipe that needs source has none. # a recipe that needs source has none.
# #
# Version contract: <version> == FORGEFIRM_RELEASE in forgefirm-image.bb # Version contract: <version> == FORGEFIRM_RELEASE in forgefirm-release.inc
# == /etc/forgefirm-version ("v<version>") in the built rootfs == .fw # == /etc/forgefirm-version ("v<version>") in the built rootfs == .fw
# meta-version ("v<version>") == release tag ("v<version>"). # meta-version ("v<version>") == release tag ("v<version>").
@@ -55,6 +55,9 @@ REPO="$(cd "$(dirname "$0")/.." && pwd)"
DEPLOY_ROOT="$REPO/build/tmp/deploy" DEPLOY_ROOT="$REPO/build/tmp/deploy"
DEPLOY="$DEPLOY_ROOT/images/glowforge" DEPLOY="$DEPLOY_ROOT/images/glowforge"
IMAGE_BB="$REPO/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb" IMAGE_BB="$REPO/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb"
# The release version, in its own file so a bump is not a platform change
# (the manifest leaves it out of the layer content hash).
RELEASE_INC="$REPO/meta-forgefirm/recipes-forgefirm/images/forgefirm-release.inc"
INSTALLER="$REPO/scripts/install-forgefirm.sh" INSTALLER="$REPO/scripts/install-forgefirm.sh"
WARN_BYTES=$((170 * 1024 * 1024)) WARN_BYTES=$((170 * 1024 * 1024))
FAIL_BYTES=$((195 * 1024 * 1024)) FAIL_BYTES=$((195 * 1024 * 1024))
@@ -134,7 +137,7 @@ if [ "$MODE" = "dev" ]; then
EXT4="${EXT4/forgefirm-image-glowforge/forgefirm-image-dev-glowforge}" EXT4="${EXT4/forgefirm-image-glowforge/forgefirm-image-dev-glowforge}"
[ -f "$EXT4" ] || die "dev rootfs not found: $EXT4" [ -f "$EXT4" ] || die "dev rootfs not found: $EXT4"
check_size check_size
REL=$(sed -n 's/^FORGEFIRM_RELEASE ?= "\(.*\)"/\1/p' "$IMAGE_BB") REL=$(sed -n 's/^FORGEFIRM_RELEASE ?= "\(.*\)"/\1/p' "$RELEASE_INC")
DEVVER="v${REL}-dev-$(date +%Y%m%d%H%M%S)" DEVVER="v${REL}-dev-$(date +%Y%m%d%H%M%S)"
OUT="$DEPLOY/forgefirm-dev.fw" OUT="$DEPLOY/forgefirm-dev.fw"
"$REPO/scripts/mkfw.sh" "$EXT4" "$DEVVER" "$OUT" "$KEY" "$REPO/scripts/mkfw.sh" "$EXT4" "$DEVVER" "$OUT" "$KEY"
@@ -163,9 +166,9 @@ else
fi fi
# Version single-source check. # Version single-source check.
BB_REL=$(sed -n 's/^FORGEFIRM_RELEASE ?= "\(.*\)"/\1/p' "$IMAGE_BB") BB_REL=$(sed -n 's/^FORGEFIRM_RELEASE ?= "\(.*\)"/\1/p' "$RELEASE_INC")
[ "$BB_REL" = "$VERSION" ] \ [ "$BB_REL" = "$VERSION" ] \
|| die "FORGEFIRM_RELEASE in forgefirm-image.bb is '$BB_REL', not '$VERSION'" || die "FORGEFIRM_RELEASE in forgefirm-release.inc is '$BB_REL', not '$VERSION'"
# The beta rule: every release below 0.1.0 is a beta, and 0.1.0 is the # The beta rule: every release below 0.1.0 is a beta, and 0.1.0 is the
# first release that is not. While the README carries the beta banner, a # first release that is not. While the README carries the beta banner, a