diff --git a/meta-forgefirm/classes/forgefirm-image-manifest.bbclass b/meta-forgefirm/classes/forgefirm-image-manifest.bbclass index 5118cb5..70e4667 100644 --- a/meta-forgefirm/classes/forgefirm-image-manifest.bbclass +++ b/meta-forgefirm/classes/forgefirm-image-manifest.bbclass @@ -44,6 +44,13 @@ FORGEFIRM_MANIFEST_DIR ?= "${sysconfdir}/forgefirm-manifest.d" FORGEFIRM_MANIFEST_CONTENT_LAYERS ?= "meta-forgefirm meta-glowforge-bsp meta-openglow-core" 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" @@ -88,7 +95,8 @@ def forgefirm_manifest_layers(d): and dirty flag of every layer checkout (informational).""" import os, subprocess 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 = {}, {} for layer in (d.getVar('BBLAYERS') or '').split(): name = os.path.basename(layer.rstrip('/')) diff --git a/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb b/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb index 9287c22..62b33c5 100644 --- a/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb +++ b/meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb @@ -113,7 +113,12 @@ IMAGE_ROOTFS_MAXSIZE = "204800" # Release images carry the release version; the dev image overrides the # string with the build timestamp (the same DATETIME as the artifact # 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}" write_forgefirm_version() { diff --git a/meta-forgefirm/recipes-forgefirm/images/forgefirm-release.inc b/meta-forgefirm/recipes-forgefirm/images/forgefirm-release.inc new file mode 100644 index 0000000..1b958c9 --- /dev/null +++ b/meta-forgefirm/recipes-forgefirm/images/forgefirm-release.inc @@ -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" diff --git a/scripts/manifest-from-tree.py b/scripts/manifest-from-tree.py index fd27912..c0c5af1 100644 --- a/scripts/manifest-from-tree.py +++ b/scripts/manifest-from-tree.py @@ -52,8 +52,11 @@ CONTENT_LAYERS = {"meta-forgefirm": ("forgefirm", "meta-forgefirm"), "meta-glowforge-bsp": ("meta-openglow", "meta-glowforge-bsp"), "meta-openglow-core": ("meta-openglow", "meta-openglow-core")} # Left out of a layer's content, as in forgefirm-image-manifest.bbclass -# (FORGEFIRM_MANIFEST_PIN_SUFFIX): documentation and the component pin files. -LAYER_SKIP_SUFFIXES = (".md", "-pin.inc") +# (FORGEFIRM_MANIFEST_PIN_SUFFIX, FORGEFIRM_MANIFEST_VERSION_SUFFIX): +# 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): diff --git a/scripts/release.sh b/scripts/release.sh index 8dcd9ab..99e9a44 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -45,7 +45,7 @@ # forgefirm-source-v.tar.gz, and refuses to pack a bundle in which # a recipe that needs source has none. # -# Version contract: == FORGEFIRM_RELEASE in forgefirm-image.bb +# Version contract: == FORGEFIRM_RELEASE in forgefirm-release.inc # == /etc/forgefirm-version ("v") in the built rootfs == .fw # meta-version ("v") == release tag ("v"). @@ -55,6 +55,9 @@ REPO="$(cd "$(dirname "$0")/.." && pwd)" DEPLOY_ROOT="$REPO/build/tmp/deploy" DEPLOY="$DEPLOY_ROOT/images/glowforge" 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" WARN_BYTES=$((170 * 1024 * 1024)) FAIL_BYTES=$((195 * 1024 * 1024)) @@ -134,7 +137,7 @@ if [ "$MODE" = "dev" ]; then EXT4="${EXT4/forgefirm-image-glowforge/forgefirm-image-dev-glowforge}" [ -f "$EXT4" ] || die "dev rootfs not found: $EXT4" 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)" OUT="$DEPLOY/forgefirm-dev.fw" "$REPO/scripts/mkfw.sh" "$EXT4" "$DEVVER" "$OUT" "$KEY" @@ -163,9 +166,9 @@ else fi # 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" ] \ - || 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 # first release that is not. While the README carries the beta banner, a