mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
135 lines
5.9 KiB
Plaintext
135 lines
5.9 KiB
Plaintext
# SPDX-License-Identifier: MIT
|
|
|
|
# forgefirm-manifest.bbclass - source fingerprint of one ForgeFIRM component
|
|
#
|
|
# A component recipe that inherits this class records what source it was
|
|
# built from: the component name, the pinned revision, and one
|
|
# [path, blob-id] pair per source file (git blob ids, so equal content
|
|
# means equal ids regardless of the commit that carried it). The image
|
|
# collects the entries into /etc/forgefirm-manifest.json
|
|
# (forgefirm-image-manifest.bbclass); the acceptance tool (forgetest) and
|
|
# the release gate (scripts/acceptance-gate.py) read that file to decide
|
|
# which acceptance results still apply to a build.
|
|
#
|
|
# Modes (FORGEFIRM_MANIFEST_MODE):
|
|
# rootfs (default) the entry is installed as
|
|
# /etc/forgefirm-manifest.d/<PN>.json in the recipe's main package
|
|
# deploy the recipe writes the entry into ${DEPLOYDIR} itself (see the
|
|
# kernel-module bbappend: its .ko is packaged by kernel-module-split
|
|
# into a versioned package, so a rootfs file from the recipe would
|
|
# not reach the image); this class then only provides the helpers.
|
|
#
|
|
# Sources: a git checkout in ${S} is fingerprinted with `git ls-tree`
|
|
# (submodules recursed, the gitlink kept). Non-git sources (file:// recipes)
|
|
# set FORGEFIRM_MANIFEST_SRC to the directory to fingerprint; the ids are
|
|
# computed with `git hash-object`, so they compare with tree ids.
|
|
|
|
FORGEFIRM_MANIFEST_NAME ?= "${PN}"
|
|
FORGEFIRM_MANIFEST_MODE ?= "rootfs"
|
|
FORGEFIRM_MANIFEST_SRC ?= ""
|
|
FORGEFIRM_MANIFEST_DIR = "${sysconfdir}/forgefirm-manifest.d"
|
|
|
|
def forgefirm_manifest_git(args, cwd):
|
|
import subprocess
|
|
return subprocess.check_output(['git'] + args, cwd=cwd,
|
|
stderr=subprocess.STDOUT).decode('utf-8', 'replace')
|
|
|
|
def forgefirm_manifest_tree(files, repo, prefix):
|
|
import os
|
|
out = forgefirm_manifest_git(['ls-tree', '-r', '--full-tree', 'HEAD'], repo)
|
|
for line in out.splitlines():
|
|
if not line.strip():
|
|
continue
|
|
meta, path = line.split('\t', 1)
|
|
fields = meta.split()
|
|
typ, obj = fields[1], fields[2]
|
|
files.append([prefix + path, obj])
|
|
if typ == 'commit':
|
|
sub = os.path.join(repo, path)
|
|
if os.path.exists(os.path.join(sub, '.git')):
|
|
forgefirm_manifest_tree(files, sub, prefix + path + '/')
|
|
|
|
def forgefirm_manifest_git_prefix(cwd):
|
|
"""`git hash-object --stdin-paths` resolves paths against the repository
|
|
top level, not the cwd; this is the cwd's prefix inside the enclosing
|
|
repository ('' when there is none)."""
|
|
import subprocess
|
|
try:
|
|
return subprocess.check_output(['git', 'rev-parse', '--show-prefix'], cwd=cwd,
|
|
stderr=subprocess.DEVNULL).decode().strip()
|
|
except (subprocess.CalledProcessError, OSError):
|
|
return ''
|
|
|
|
def forgefirm_manifest_hash_dir(src):
|
|
import os, subprocess
|
|
paths = []
|
|
for root, dirs, fns in os.walk(src):
|
|
dirs[:] = sorted(x for x in dirs if x not in ('.git', '__pycache__'))
|
|
for fn in fns:
|
|
if fn.endswith(('.pyc', '.pyo')):
|
|
continue
|
|
p = os.path.join(root, fn)
|
|
if os.path.isfile(p) and not os.path.islink(p):
|
|
paths.append(os.path.relpath(p, src))
|
|
paths.sort()
|
|
if not paths:
|
|
return []
|
|
prefix = forgefirm_manifest_git_prefix(src)
|
|
out = subprocess.run(['git', 'hash-object', '--stdin-paths'], cwd=src,
|
|
input=('\n'.join(prefix + p for p in paths) + '\n').encode(),
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
|
|
ids = out.stdout.decode().split()
|
|
if len(ids) != len(paths):
|
|
bb.fatal("forgefirm-manifest: hash-object returned %d ids for %d paths"
|
|
% (len(ids), len(paths)))
|
|
return [[p, i] for p, i in zip(paths, ids)]
|
|
|
|
def forgefirm_manifest_entry(d):
|
|
import os
|
|
pn = d.getVar('PN')
|
|
entry = {'component': d.getVar('FORGEFIRM_MANIFEST_NAME'),
|
|
'recipe': pn, 'pv': d.getVar('PV'), 'files': []}
|
|
src = (d.getVar('FORGEFIRM_MANIFEST_SRC') or '').strip()
|
|
s = d.getVar('S')
|
|
if src:
|
|
if not os.path.isdir(src):
|
|
bb.fatal("forgefirm-manifest: %s: FORGEFIRM_MANIFEST_SRC '%s' is not a directory" % (pn, src))
|
|
entry['srcrev'] = None
|
|
entry['source'] = 'files'
|
|
entry['files'] = forgefirm_manifest_hash_dir(src)
|
|
elif os.path.exists(os.path.join(s, '.git')):
|
|
entry['srcrev'] = forgefirm_manifest_git(['rev-parse', 'HEAD'], s).strip()
|
|
uri = (d.getVar('SRC_URI') or '').split()
|
|
entry['source'] = uri[0].split(';')[0] if uri else None
|
|
forgefirm_manifest_tree(entry['files'], s, '')
|
|
else:
|
|
bb.fatal("forgefirm-manifest: %s: ${S} is not a git checkout and FORGEFIRM_MANIFEST_SRC is unset" % pn)
|
|
entry['files'].sort()
|
|
return entry
|
|
|
|
def forgefirm_manifest_write(entry, path):
|
|
import json, os
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
with open(path, 'w') as f:
|
|
json.dump(entry, f, sort_keys=True, separators=(',', ':'))
|
|
f.write('\n')
|
|
os.chmod(path, 0o644)
|
|
|
|
fakeroot python do_forgefirm_manifest() {
|
|
import os
|
|
entry = forgefirm_manifest_entry(d)
|
|
forgefirm_manifest_write(entry, os.path.join(d.getVar('D') + d.getVar('FORGEFIRM_MANIFEST_DIR'),
|
|
d.getVar('PN') + '.json'))
|
|
}
|
|
do_forgefirm_manifest[depends] += "virtual/fakeroot-native:do_populate_sysroot"
|
|
do_forgefirm_manifest[vardeps] += "FORGEFIRM_MANIFEST_NAME FORGEFIRM_MANIFEST_SRC SRCREV SRC_URI \
|
|
forgefirm_manifest_entry forgefirm_manifest_tree forgefirm_manifest_git \
|
|
forgefirm_manifest_hash_dir forgefirm_manifest_git_prefix forgefirm_manifest_write"
|
|
|
|
python __anonymous() {
|
|
if d.getVar('FORGEFIRM_MANIFEST_MODE') == 'rootfs':
|
|
bb.build.addtask('do_forgefirm_manifest', 'do_package do_populate_sysroot', 'do_install', d)
|
|
}
|
|
|
|
FILES:${PN}:append = " ${FORGEFIRM_MANIFEST_DIR}"
|