mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 01:01:12 -07:00
manifest: component pins are not layer content
A component pin bump counted as a platform change: the layer content hash in the platform identity covered the recipe carrying the SRCREV, the platform is folded into every acceptance fingerprint, so every image that carried any component update invalidated the whole catalog (dev image 20260816191951: every test domain-changed after a one-line forgectrl bump; the two manifests differ only in platform.layers.meta-forgefirm). The component entry already identifies the pinned source file by file; the pin double-counted it. Component pins now live in <recipe>-pin.inc (SRCREV and the PV that moves with it, nothing else) - forgectrl, grblhal-glowforge and forgefirm-app here, the BSP components in meta-openglow - and forgefirm-image-manifest.bbclass leaves *-pin.inc out of the layer content (FORGEFIRM_MANIFEST_PIN_SUFFIX). Recipe bodies, patches, config fragments, init scripts and third-party pins with no manifest entry stay layer content; a pin written into a recipe body still hashes (the safe direction). manifest-from-tree.py mirrors the rule and reads pins through the recipe's requires; test_tree_manifest.py proves both (pin bump: hash unchanged; recipe body or inline pin: changed). Bitbake resolves the same SRCREV/PV for every pinned recipe. Docs: ACCEPTANCE.md (what layer content is), kas/README.md (the pin files in the push order), BRINGUP.md (the finding and the bench consequence: the first image built with the pin files is itself a platform change, so its campaign is a full one; pin bumps inherit after it). No catalog consequence: nothing in the image's behavior changes; the change is to the acceptance identity computation, proven by the unit tests and the CI lint on the tree manifest.
This commit is contained in:
+10
-1
@@ -53,7 +53,16 @@ Consequences:
|
|||||||
A panel-only change reruns the core plus the panel tests, not the
|
A panel-only change reruns the core plus the panel tests, not the
|
||||||
cooling drills.
|
cooling drills.
|
||||||
- A platform change (kernel, device tree, a layer's content) invalidates
|
- A platform change (kernel, device tree, a layer's content) invalidates
|
||||||
everything.
|
everything. Layer content is every file under `meta-forgefirm`,
|
||||||
|
`meta-glowforge-bsp` and `meta-openglow-core` except documentation
|
||||||
|
(`*.md`) and the component pin files (`<recipe>-pin.inc`, holding only a
|
||||||
|
component's `SRCREV` and the `PV` that moves with it). A pin bump is the
|
||||||
|
component's change, and the component entry already carries it file by
|
||||||
|
file, so it invalidates the tests that cover the component - not the
|
||||||
|
bench. A recipe-body change (build flags, patches, config fragments,
|
||||||
|
init scripts, a third-party pin with no manifest entry) is layer content
|
||||||
|
and invalidates everything; so does a pin written into a recipe body
|
||||||
|
instead of its pin file (the safe direction).
|
||||||
- A change to a test's implementation invalidates that test's earlier
|
- A change to a test's implementation invalidates that test's earlier
|
||||||
passes and no other.
|
passes and no other.
|
||||||
- "Touched" is computed from content hashes carried in the image, never
|
- "Touched" is computed from content hashes carried in the image, never
|
||||||
|
|||||||
+46
-9
@@ -1592,9 +1592,43 @@ the probe's own writes and the controller's init writes once. **Images
|
|||||||
`20260816191951` (forgefirm-image-dev) are built on that tree - forgectrl
|
`20260816191951` (forgefirm-image-dev) are built on that tree - forgectrl
|
||||||
`c8f6558`, the day's forgetest, acceptance identity `c72448c2…` equal on
|
`c8f6558`, the day's forgetest, acceptance identity `c72448c2…` equal on
|
||||||
both - and archived under `images/20260816191838/` with checksums (the
|
both - and archived under `images/20260816191838/` with checksums (the
|
||||||
previous pair, `215236`/`215332`, under `images/20260815215236/`). The
|
previous pair, `215236`/`215332`, under `images/20260815215236/`).**
|
||||||
confirmation campaign runs on the flashed dev image; every domain
|
|
||||||
forgectrl does not touch inherits.**
|
**2026-08-16, dev image `20260816191951` flashed: every test came up
|
||||||
|
`domain-changed`, none inherited - the expectation above ("every domain
|
||||||
|
forgectrl does not touch inherits") was wrong, and the tool was right.**
|
||||||
|
The two dev-image manifests differ in exactly one platform field:
|
||||||
|
`platform.layers.meta-forgefirm.content_sha256` (`b2d13d87…` →
|
||||||
|
`7ef5555d…`); machine, kernel modules and DTB hashes are equal, and the
|
||||||
|
only components that moved are forgectrl (7 files) and the dev-only
|
||||||
|
forgetest. The only non-`.md` change in `meta-forgefirm` between the two
|
||||||
|
builds is the one-line SRCREV bump in `forgectrl.bb` (`1d9b553`) - the
|
||||||
|
layer is content-hashed into the platform identity, the platform is
|
||||||
|
folded into every fingerprint, so the pin bump counted as a platform
|
||||||
|
change and invalidated the whole catalog. Structural, not a fluke: every
|
||||||
|
component update that ships in an image rides a pin bump in a
|
||||||
|
content-hashed layer, so under that rule every image with any component
|
||||||
|
change was an invalidate-all and the per-domain inheritance the contract
|
||||||
|
promises could never hold across images (the component entry already
|
||||||
|
carries the change file by file; the pin double-counted it). Fixed the
|
||||||
|
same day: component pins live in `<recipe>-pin.inc` (SRCREV + the PV that
|
||||||
|
moves with it, nothing else) and `forgefirm-image-manifest.bbclass` leaves
|
||||||
|
`*-pin.inc` out of the layer content (`FORGEFIRM_MANIFEST_PIN_SUFFIX`),
|
||||||
|
mirrored in `scripts/manifest-from-tree.py` and proven by
|
||||||
|
`forgetest/tests/test_tree_manifest.py` (pin bump → hash unchanged; recipe
|
||||||
|
body change or a pin written into the recipe → hash changed, the safe
|
||||||
|
direction); the six component recipes (forgectrl, grblhal-glowforge,
|
||||||
|
forgefirm-app in meta-forgefirm; kernel-module-glowforge,
|
||||||
|
python3-gfhardware, python3-gfutilities in meta-openglow) require their
|
||||||
|
pin files and resolve the same SRCREV/PV under bitbake. A second, smaller
|
||||||
|
contributor stays as designed: a test's implementation hash is its suite
|
||||||
|
module, so the day's edits to `suite/{cloud,cooling,forgectrl,kernel,
|
||||||
|
motion}.py` alone would have re-required 16 of the 26. **Consequence for
|
||||||
|
the bench: the fix changes the layer content itself, so the first image
|
||||||
|
built with it is a platform change against everything recorded so far -
|
||||||
|
that image's campaign is a full one, unavoidably; from then on a
|
||||||
|
component pin bump re-requires only the tests covering that component.
|
||||||
|
Run the full campaign on the first pin-file image, not on `191951`.**
|
||||||
|
|
||||||
## Hardware facts bank (measured)
|
## Hardware facts bank (measured)
|
||||||
|
|
||||||
@@ -2924,12 +2958,15 @@ forgectrl does not touch inherits.**
|
|||||||
core, FAIL/ERROR closing a campaign, implementation and component
|
core, FAIL/ERROR closing a campaign, implementation and component
|
||||||
changes invalidating exactly their domains) behaved as specified
|
changes invalidating exactly their domains) behaved as specified
|
||||||
across the day's closures; the baseline rule was added on the way
|
across the day's closures; the baseline rule was added on the way
|
||||||
(record in "Release acceptance" above). Remaining: (a) the
|
(record in "Release acceptance" above). The flash of `20260816191951`
|
||||||
confirmation campaign on the **flashed dev image `20260816191951`**
|
exposed the layer-hash over-invalidation (a component pin bump counted
|
||||||
(built; `images/20260816191838/`; it carries the day's tool fixes and
|
as a platform change; fixed - pins in `<recipe>-pin.inc`, left out of
|
||||||
the forgectrl changes; every domain forgectrl does not touch
|
the layer content; record in "Release acceptance" above). Remaining:
|
||||||
inherits) - the tool on the bench is the tree, but a hot-patched image
|
(a) the confirmation campaign - a **full** one, on the **first image
|
||||||
is not the image that ships; (b) the remaining
|
built with the pin files** (that build is a platform change against
|
||||||
|
every result so far; after it, a component pin bump re-requires only
|
||||||
|
the tests covering that component) - the tool on the bench is the
|
||||||
|
tree, but a hot-patched image is not the image that ships; (b) the remaining
|
||||||
bench-tab ports (scope tools, host-side flow characterization, the
|
bench-tab ports (scope tools, host-side flow characterization, the
|
||||||
live drills - the catalog carries their acceptance forms); (c) the
|
live drills - the catalog carries their acceptance forms); (c) the
|
||||||
first release runs the full campaign and commits
|
first release runs the full campaign and commits
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
"""scripts/manifest-from-tree.py - the workstation/CI mirror of the image
|
||||||
|
manifest: recipe pins are read through their pin files, and a layer's
|
||||||
|
content hash leaves the pin files out (a component bump is not a platform
|
||||||
|
change; a recipe-body change is)."""
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import helpers # noqa: F401 (sys.path)
|
||||||
|
|
||||||
|
REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
SCRIPT = os.path.join(REPO, "scripts", "manifest-from-tree.py")
|
||||||
|
|
||||||
|
|
||||||
|
def load_script():
|
||||||
|
spec = importlib.util.spec_from_file_location("manifest_from_tree", SCRIPT)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
return mod
|
||||||
|
|
||||||
|
|
||||||
|
def git_ok():
|
||||||
|
try:
|
||||||
|
subprocess.run(["git", "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
|
||||||
|
return True
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
RECIPE = '''DESCRIPTION = "component"
|
||||||
|
LICENSE = "MIT"
|
||||||
|
SRC_URI = "git://github.com/example/comp.git;protocol=https;branch=main"
|
||||||
|
# SRCREV and PV live in the pin file.
|
||||||
|
require comp-pin.inc
|
||||||
|
S = "${WORKDIR}/git"
|
||||||
|
'''
|
||||||
|
PIN_A = 'SRCREV = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\nPV = "1.0"\n'
|
||||||
|
PIN_B = 'SRCREV = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\nPV = "1.1"\n'
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipUnless(git_ok(), "git not available")
|
||||||
|
class TreeManifestTests(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.mod = load_script()
|
||||||
|
self.tmp = tempfile.mkdtemp(prefix="forgetest-tree-")
|
||||||
|
self.layer = os.path.join(self.tmp, "meta-x")
|
||||||
|
self.rdir = os.path.join(self.layer, "recipes-x", "comp")
|
||||||
|
os.makedirs(self.rdir)
|
||||||
|
subprocess.run(["git", "init", "-q"], cwd=self.tmp, check=True,
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
self.write("comp.bb", RECIPE)
|
||||||
|
self.write("comp-pin.inc", PIN_A)
|
||||||
|
self.write("README.md", "# docs\n")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
shutil.rmtree(self.tmp, ignore_errors=True)
|
||||||
|
|
||||||
|
def write(self, name, text):
|
||||||
|
with open(os.path.join(self.rdir, name), "w", encoding="utf-8", newline="\n") as f:
|
||||||
|
f.write(text)
|
||||||
|
|
||||||
|
def test_parse_recipe_follows_the_pin_file(self):
|
||||||
|
url, rev = self.mod.parse_recipe(os.path.join(self.rdir, "comp.bb"))
|
||||||
|
self.assertEqual(url, "https://github.com/example/comp.git")
|
||||||
|
self.assertEqual(rev, "a" * 40)
|
||||||
|
self.write("comp-pin.inc", PIN_B)
|
||||||
|
self.assertEqual(self.mod.parse_recipe(os.path.join(self.rdir, "comp.bb"))[1], "b" * 40)
|
||||||
|
|
||||||
|
def test_parse_recipe_inline_pin_still_works(self):
|
||||||
|
self.write("comp.bb", RECIPE.replace("require comp-pin.inc\n", PIN_A))
|
||||||
|
os.remove(os.path.join(self.rdir, "comp-pin.inc"))
|
||||||
|
self.assertEqual(self.mod.parse_recipe(os.path.join(self.rdir, "comp.bb"))[1], "a" * 40)
|
||||||
|
|
||||||
|
def test_layer_hash_ignores_pin_bumps_and_docs(self):
|
||||||
|
base = self.mod.layer_content(self.layer)
|
||||||
|
self.assertIsNotNone(base)
|
||||||
|
self.write("comp-pin.inc", PIN_B)
|
||||||
|
self.assertEqual(self.mod.layer_content(self.layer), base, "a pin bump is not layer content")
|
||||||
|
self.write("README.md", "# docs, revised\n")
|
||||||
|
self.assertEqual(self.mod.layer_content(self.layer), base, "documentation is not layer content")
|
||||||
|
|
||||||
|
def test_layer_hash_sees_recipe_body_changes(self):
|
||||||
|
base = self.mod.layer_content(self.layer)
|
||||||
|
self.write("comp.bb", RECIPE + 'EXTRA_OEMAKE += "KCFLAGS=-Werror"\n')
|
||||||
|
self.assertNotEqual(self.mod.layer_content(self.layer), base)
|
||||||
|
|
||||||
|
def test_layer_hash_sees_a_pin_written_into_the_recipe(self):
|
||||||
|
# The safe direction: a pin that bypasses its pin file still hashes.
|
||||||
|
base = self.mod.layer_content(self.layer)
|
||||||
|
self.write("comp.bb", RECIPE + PIN_B)
|
||||||
|
self.assertNotEqual(self.mod.layer_content(self.layer), base)
|
||||||
|
|
||||||
|
def test_layer_hash_matches_the_bbclass_rule(self):
|
||||||
|
# The same skip list as FORGEFIRM_MANIFEST_PIN_SUFFIX + *.md in
|
||||||
|
# forgefirm-image-manifest.bbclass.
|
||||||
|
bbclass = os.path.join(REPO, "meta-forgefirm", "classes", "forgefirm-image-manifest.bbclass")
|
||||||
|
with open(bbclass, encoding="utf-8") as f:
|
||||||
|
text = f.read()
|
||||||
|
self.assertIn('FORGEFIRM_MANIFEST_PIN_SUFFIX ?= "-pin.inc"', text)
|
||||||
|
self.assertEqual(self.mod.LAYER_SKIP_SUFFIXES, (".md", "-pin.inc"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
+9
-3
@@ -93,9 +93,15 @@ config move in the right order. The sequence, with current status:
|
|||||||
(`kernel-module-glowforge`, `python3-gfhardware`, `Glowforge-Utilities`,
|
(`kernel-module-glowforge`, `python3-gfhardware`, `Glowforge-Utilities`,
|
||||||
`grblHAL-glowforge`, `forgectrl`) is on GitHub and its recipe pins an exact
|
`grblHAL-glowforge`, `forgectrl`) is on GitHub and its recipe pins an exact
|
||||||
`SRCREV` — no `AUTOREV` anywhere. Whenever a source repo changes: push it,
|
`SRCREV` — no `AUTOREV` anywhere. Whenever a source repo changes: push it,
|
||||||
then bump the recipe `SRCREV` deliberately (BSP recipes in meta-openglow,
|
then bump the pin deliberately (BSP recipes in meta-openglow, ForgeFIRM
|
||||||
ForgeFIRM components in meta-forgefirm) and re-verify with
|
components in meta-forgefirm) and re-verify with
|
||||||
`bitbake -c fetch <recipe>`.
|
`bitbake -c fetch <recipe>`. A component's `SRCREV` (and the `PV` that
|
||||||
|
moves with it) lives in `<recipe>-pin.inc` next to the recipe, nothing
|
||||||
|
else goes in that file: the image manifest leaves `*-pin.inc` out of the
|
||||||
|
layer content hash, so a pin bump changes the component's fingerprint
|
||||||
|
and only that (`docs/ACCEPTANCE.md`) — a pin written into the recipe body
|
||||||
|
still builds, but counts as a platform change and forces a full
|
||||||
|
acceptance campaign.
|
||||||
2. **meta-openglow pushed** — **DONE.** The Scarthgap port lives on
|
2. **meta-openglow pushed** — **DONE.** The Scarthgap port lives on
|
||||||
the **`scarthgap` branch** (Yocto layer convention; the Dunfell-era `master`
|
the **`scarthgap` branch** (Yocto layer convention; the Dunfell-era `master`
|
||||||
is untouched). Development continues on the local sibling checkout; push /
|
is untouched). Development continues on the local sibling checkout; push /
|
||||||
|
|||||||
@@ -22,6 +22,19 @@
|
|||||||
# host-specific (paths, user, time) is recorded: the manifest travels in
|
# host-specific (paths, user, time) is recorded: the manifest travels in
|
||||||
# a public release artifact.
|
# a public release artifact.
|
||||||
#
|
#
|
||||||
|
# Two kinds of file are left out of a layer's content: documentation
|
||||||
|
# (*.md) and component pin files (*FORGEFIRM_MANIFEST_PIN_SUFFIX,
|
||||||
|
# "<recipe>-pin.inc"). A pin file holds nothing but the SRCREV (and the PV
|
||||||
|
# that rides with it) of a component that has its own manifest entry; the
|
||||||
|
# entry already identifies that source file by file, so hashing the pin as
|
||||||
|
# layer content would turn every component update into a platform change
|
||||||
|
# and invalidate every acceptance result instead of the tests that cover
|
||||||
|
# the component. Everything else a recipe carries - build flags, patches,
|
||||||
|
# config fragments, init scripts, the pins of third-party sources that
|
||||||
|
# have no manifest entry - is layer content and stays in the hash. A pin
|
||||||
|
# written into a recipe body instead of its pin file still hashes: the
|
||||||
|
# safe direction (a full campaign, not a missed one).
|
||||||
|
#
|
||||||
# content_sha256 is the identity of the build's inputs: sha256 over the
|
# content_sha256 is the identity of the build's inputs: sha256 over the
|
||||||
# canonical JSON (sorted keys, no whitespace) of {"components", "platform"}.
|
# canonical JSON (sorted keys, no whitespace) of {"components", "platform"}.
|
||||||
# The image name, version string and the build section are metadata
|
# The image name, version string and the build section are metadata
|
||||||
@@ -30,19 +43,22 @@
|
|||||||
|
|
||||||
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"
|
||||||
|
|
||||||
do_rootfs[depends] += "virtual/kernel:do_deploy kernel-module-glowforge:do_deploy"
|
do_rootfs[depends] += "virtual/kernel:do_deploy kernel-module-glowforge:do_deploy"
|
||||||
|
|
||||||
ROOTFS_POSTPROCESS_COMMAND += "forgefirm_manifest_assemble;"
|
ROOTFS_POSTPROCESS_COMMAND += "forgefirm_manifest_assemble;"
|
||||||
forgefirm_manifest_assemble[vardepsexclude] += "DATETIME"
|
forgefirm_manifest_assemble[vardepsexclude] += "DATETIME"
|
||||||
|
|
||||||
def forgefirm_manifest_layer_content(path):
|
def forgefirm_manifest_layer_content(path, skip_suffixes=('.md',)):
|
||||||
|
"""sha256 over (path, git blob id) of every file under the layer
|
||||||
|
directory except those whose name ends in one of skip_suffixes."""
|
||||||
import hashlib, os, subprocess
|
import hashlib, os, subprocess
|
||||||
out = subprocess.run(['git', 'ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', '.'],
|
out = subprocess.run(['git', 'ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', '.'],
|
||||||
cwd=path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True).stdout
|
cwd=path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True).stdout
|
||||||
paths = sorted(set(p.decode('utf-8', 'replace') for p in out.split(b'\0') if p))
|
paths = sorted(set(p.decode('utf-8', 'replace') for p in out.split(b'\0') if p))
|
||||||
paths = [p for p in paths
|
paths = [p for p in paths
|
||||||
if os.path.isfile(os.path.join(path, p)) and not p.endswith('.md')]
|
if os.path.isfile(os.path.join(path, p)) and not p.endswith(tuple(skip_suffixes))]
|
||||||
if not paths:
|
if not paths:
|
||||||
return None
|
return None
|
||||||
# hash-object --stdin-paths resolves against the repository top level
|
# hash-object --stdin-paths resolves against the repository top level
|
||||||
@@ -68,6 +84,7 @@ 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())
|
||||||
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('/'))
|
||||||
@@ -83,7 +100,7 @@ def forgefirm_manifest_layers(d):
|
|||||||
if name in content_layers:
|
if name in content_layers:
|
||||||
if rev is None:
|
if rev is None:
|
||||||
bb.fatal("forgefirm-image-manifest: layer %s must be a git checkout to be content-hashed" % name)
|
bb.fatal("forgefirm-image-manifest: layer %s must be a git checkout to be content-hashed" % name)
|
||||||
identity[name] = {'content_sha256': forgefirm_manifest_layer_content(layer)}
|
identity[name] = {'content_sha256': forgefirm_manifest_layer_content(layer, skip)}
|
||||||
else:
|
else:
|
||||||
identity[name] = {'rev': rev}
|
identity[name] = {'rev': rev}
|
||||||
return identity, build
|
return identity, build
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# forgectrl pin. Bump deliberately after pushing forgectrl changes; keep
|
||||||
|
# only SRCREV and PV here - the image manifest leaves *-pin.inc out of the
|
||||||
|
# layer content hash because the component entry already identifies the
|
||||||
|
# pinned source (forgefirm-image-manifest.bbclass).
|
||||||
|
SRCREV = "c8f655813ba2b9a44fdf032e84435d9454909d7c"
|
||||||
|
PV = "0.1.0"
|
||||||
@@ -5,11 +5,10 @@ LICENSE = "MIT"
|
|||||||
LIC_FILES_CHKSUM = "file://LICENSE;md5=19ed4e3e8c28a4311c16b0b2b91357ec"
|
LIC_FILES_CHKSUM = "file://LICENSE;md5=19ed4e3e8c28a4311c16b0b2b91357ec"
|
||||||
|
|
||||||
PE = "1"
|
PE = "1"
|
||||||
PV = "0.1.0"
|
|
||||||
|
|
||||||
SRC_URI = "git://github.com/ScottW514/forgectrl.git;protocol=https;branch=main"
|
SRC_URI = "git://github.com/ScottW514/forgectrl.git;protocol=https;branch=main"
|
||||||
# Pinned; bump deliberately after pushing forgectrl changes.
|
# SRCREV and PV live in the pin file (forgefirm-image-manifest.bbclass).
|
||||||
SRCREV = "c8f655813ba2b9a44fdf032e84435d9454909d7c"
|
require forgectrl-pin.inc
|
||||||
|
|
||||||
S = "${WORKDIR}/git"
|
S = "${WORKDIR}/git"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# forgefirm-app pin (python3-gfhardware repository, forgefirm-app/ tree).
|
||||||
|
# Bump deliberately (AUTOREV is not reproducible); keep only SRCREV and PV
|
||||||
|
# here - the image manifest leaves *-pin.inc out of the layer content hash
|
||||||
|
# because the component entry already identifies the pinned source
|
||||||
|
# (forgefirm-image-manifest.bbclass). The python3-gfhardware recipe in
|
||||||
|
# meta-glowforge-bsp pins the same repository; move both together.
|
||||||
|
SRCREV = "a0a174d8e9a6b4b6e3abe63acc51018199df4202"
|
||||||
|
|
||||||
|
# Bump PV with every SRCREV move: the hash-derived package version is not
|
||||||
|
# monotonic on its own and buildhistory QA fails the build when it sorts
|
||||||
|
# backwards.
|
||||||
|
PV = "0.1.6+git"
|
||||||
@@ -10,13 +10,8 @@ LICENSE = "MIT"
|
|||||||
LIC_FILES_CHKSUM = "file://LICENSE;md5=62f8bb455fcc4bf177ecab380f71cd5d"
|
LIC_FILES_CHKSUM = "file://LICENSE;md5=62f8bb455fcc4bf177ecab380f71cd5d"
|
||||||
|
|
||||||
SRC_URI = "git://github.com/ScottW514/python3-gfhardware.git;protocol=https;branch=master"
|
SRC_URI = "git://github.com/ScottW514/python3-gfhardware.git;protocol=https;branch=master"
|
||||||
# Pinned; bump deliberately (AUTOREV is not reproducible).
|
# SRCREV and PV live in the pin file (forgefirm-image-manifest.bbclass).
|
||||||
SRCREV = "a0a174d8e9a6b4b6e3abe63acc51018199df4202"
|
require forgefirm-app-pin.inc
|
||||||
|
|
||||||
# Bump PV with every SRCREV move: the hash-derived package version is not
|
|
||||||
# monotonic on its own and buildhistory QA fails the build when it sorts
|
|
||||||
# backwards.
|
|
||||||
PV = "0.1.6+git"
|
|
||||||
|
|
||||||
S = "${WORKDIR}/git"
|
S = "${WORKDIR}/git"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# grblHAL-glowforge pin. Bump deliberately after pushing grblHAL-glowforge
|
||||||
|
# changes; keep only SRCREV and PV here - the image manifest leaves
|
||||||
|
# *-pin.inc out of the layer content hash because the component entry
|
||||||
|
# already identifies the pinned source (forgefirm-image-manifest.bbclass).
|
||||||
|
SRCREV = "67d026d39ac03286937774f5e28946b88c44d490"
|
||||||
|
PV = "0.1.0"
|
||||||
@@ -4,14 +4,12 @@ HOMEPAGE = "https://github.com/ScottW514/grblHAL-glowforge"
|
|||||||
LICENSE = "GPL-3.0-or-later"
|
LICENSE = "GPL-3.0-or-later"
|
||||||
LIC_FILES_CHKSUM = "file://COPYING;md5=3237e48bcef3455c7bea5c0ce16206f6"
|
LIC_FILES_CHKSUM = "file://COPYING;md5=3237e48bcef3455c7bea5c0ce16206f6"
|
||||||
|
|
||||||
PV = "0.1.0"
|
|
||||||
|
|
||||||
# gitsm: the grblHAL core rides as a submodule (ScottW514/core, branch
|
# gitsm: the grblHAL core rides as a submodule (ScottW514/core, branch
|
||||||
# forgefirm = upstream master + the step_us_min buffer fix pending
|
# forgefirm = upstream master + the step_us_min buffer fix pending
|
||||||
# upstream).
|
# upstream).
|
||||||
SRC_URI = "gitsm://github.com/ScottW514/grblHAL-glowforge.git;protocol=https;branch=main"
|
SRC_URI = "gitsm://github.com/ScottW514/grblHAL-glowforge.git;protocol=https;branch=main"
|
||||||
# Pinned; bump deliberately after pushing grblHAL-glowforge changes.
|
# SRCREV and PV live in the pin file (forgefirm-image-manifest.bbclass).
|
||||||
SRCREV = "67d026d39ac03286937774f5e28946b88c44d490"
|
require grblhal-glowforge-pin.inc
|
||||||
|
|
||||||
SRC_URI += "file://grblhal.init"
|
SRC_URI += "file://grblhal.init"
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,13 @@
|
|||||||
# [--cache DIR] [--kernel-srcrev REV]
|
# [--cache DIR] [--kernel-srcrev REV]
|
||||||
#
|
#
|
||||||
# Component revisions come from the recipes in meta-forgefirm and the sibling
|
# Component revisions come from the recipes in meta-forgefirm and the sibling
|
||||||
# meta-openglow checkout (default ../meta-openglow relative to this repo).
|
# meta-openglow checkout (default ../meta-openglow relative to this repo); a
|
||||||
# Each pinned commit is fetched shallowly into --cache (default
|
# recipe's `require`d files in its own directory are read too, which is
|
||||||
# .manifest-cache/, gitignored) and listed with `git ls-tree`; a submodule
|
# where the pins live (<recipe>-pin.inc). Each pinned commit is fetched
|
||||||
# gitlink is followed through .gitmodules.
|
# shallowly into --cache (default .manifest-cache/, gitignored) and listed
|
||||||
|
# with `git ls-tree`; a submodule gitlink is followed through .gitmodules.
|
||||||
|
# The layer content hash mirrors forgefirm-image-manifest.bbclass: every
|
||||||
|
# file under the layer except *.md and *-pin.inc.
|
||||||
import argparse
|
import argparse
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
@@ -43,6 +46,9 @@ RECIPES = [
|
|||||||
CONTENT_LAYERS = {"meta-forgefirm": ("forgefirm", "meta-forgefirm"),
|
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
|
||||||
|
# (FORGEFIRM_MANIFEST_PIN_SUFFIX): documentation and the component pin files.
|
||||||
|
LAYER_SKIP_SUFFIXES = (".md", "-pin.inc")
|
||||||
|
|
||||||
|
|
||||||
def git(args, cwd=None, input=None):
|
def git(args, cwd=None, input=None):
|
||||||
@@ -50,8 +56,27 @@ def git(args, cwd=None, input=None):
|
|||||||
stderr=subprocess.PIPE, check=True).stdout
|
stderr=subprocess.PIPE, check=True).stdout
|
||||||
|
|
||||||
|
|
||||||
|
def recipe_text(path, seen=None):
|
||||||
|
"""The recipe's text with its `require`/`include`d files from the same
|
||||||
|
directory appended (bitbake resolves a relative name against the
|
||||||
|
including file's directory first). Only local files are followed;
|
||||||
|
anything else is left to BBPATH and skipped here."""
|
||||||
|
seen = seen if seen is not None else set()
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
if path in seen:
|
||||||
|
return ""
|
||||||
|
seen.add(path)
|
||||||
|
with open(path, encoding="utf-8") as f:
|
||||||
|
text = f.read()
|
||||||
|
for m in re.finditer(r'^\s*(?:require|include)\s+(\S+)\s*$', text, re.M):
|
||||||
|
cand = os.path.join(os.path.dirname(path), m.group(1))
|
||||||
|
if os.path.isfile(cand):
|
||||||
|
text += "\n" + recipe_text(cand, seen)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
def parse_recipe(path):
|
def parse_recipe(path):
|
||||||
text = open(path, encoding="utf-8").read()
|
text = recipe_text(path)
|
||||||
uri = re.search(r'^SRC_URI\s*\+?=\s*"([^"]+)"', text, re.M)
|
uri = re.search(r'^SRC_URI\s*\+?=\s*"([^"]+)"', text, re.M)
|
||||||
rev = re.search(r'^SRCREV\s*\??=\s*"([0-9a-fA-F]+)"', text, re.M)
|
rev = re.search(r'^SRCREV\s*\??=\s*"([0-9a-fA-F]+)"', text, re.M)
|
||||||
if not uri or not rev:
|
if not uri or not rev:
|
||||||
@@ -116,7 +141,8 @@ def ls_tree(repo, rev, url, cache, prefix, files):
|
|||||||
def layer_content(path):
|
def layer_content(path):
|
||||||
out = git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", "."], cwd=path)
|
out = git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", "."], cwd=path)
|
||||||
paths = sorted(set(p.decode("utf-8", "replace") for p in out.split(b"\0") if p))
|
paths = sorted(set(p.decode("utf-8", "replace") for p in out.split(b"\0") if p))
|
||||||
paths = [p for p in paths if os.path.isfile(os.path.join(path, p)) and not p.endswith(".md")]
|
paths = [p for p in paths
|
||||||
|
if os.path.isfile(os.path.join(path, p)) and not p.endswith(LAYER_SKIP_SUFFIXES)]
|
||||||
if not paths:
|
if not paths:
|
||||||
return None
|
return None
|
||||||
# hash-object --stdin-paths resolves against the repository top level
|
# hash-object --stdin-paths resolves against the repository top level
|
||||||
|
|||||||
Reference in New Issue
Block a user