Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,6 @@ so3/so3/scripts/kconfig/zconf.tab.c
# do_attach_infrabase dirty-tree guard manifests (sibling of each attached
# component tree, e.g. avz.attach.sha256, u-boot.attach.sha256)
*.attach.sha256
*.attach.pf
__pycache__/
/scripts.old/
77 changes: 75 additions & 2 deletions build/meta/classes/base.bbclass
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,16 @@ do_attach_infrabase () {
# *added* source files are not tracked by the manifest — the ${IB_TARGET}.back
# copy remains the last-resort net for that case.
if [ -d "${IB_TARGET}" ] && [ -f "$ib_manifest" ] && [ "${IB_FORCE_ATTACH}" != "1" ]; then
ib_dirty=$(cd "${IB_TARGET}" && LC_ALL=C sha256sum -c --quiet "$ib_manifest" 2>/dev/null | sed -n 's/: FAILED.*$//p')
# The quilt staging left by do_patch (patches/, series, .pc) is a
# build product, not source -- it is regenerated on every patch run
# and is gitignored. Recorded in the manifest it made the guard fire
# on its own artefacts and refuse a perfectly clean tree. Filtered
# here as well as pruned below, so manifests recorded before this
# fix stop blocking too. `|| true`: grep exits 1 when it filters
# everything away, and bitbake runs shell tasks under `set -e`.
ib_dirty=$(cd "${IB_TARGET}" && LC_ALL=C sha256sum -c --quiet "$ib_manifest" 2>/dev/null \
| sed -n 's/: FAILED.*$//p' \
| grep -vE '(^|/)(\.pc|patches)/' || true)
if [ -n "$ib_dirty" ]; then
bbwarn "Local modifications detected in ${IB_TARGET}, not captured in the ${PN} patch set:"
echo "$ib_dirty" | sed 's|^\./| |' >&2
Expand All @@ -223,9 +232,73 @@ do_attach_infrabase () {
cp -r ${S}/. ${IB_TARGET}

# Record what we just wrote so the next attach can detect local edits.
( cd "${IB_TARGET}" && find . -type f -print0 | LC_ALL=C sort -z | xargs -0 sha256sum ) > "$ib_manifest" 2>/dev/null || rm -f "$ib_manifest"
( cd "${IB_TARGET}" && find . \( -name .pc -o -name patches \) -prune -o -type f -print0 \
| LC_ALL=C sort -z | xargs -0 sha256sum ) > "$ib_manifest" 2>/dev/null || rm -f "$ib_manifest"

# Record WHICH recipe the tree now belongs to. Several recipes can share
# one IB_TARGET -- uboot_2022.04 and uboot_2024.07 both attach to
# u-boot/ -- and each has its own do_attach_infrabase stamp. Without
# this marker, attaching one and then building the other finds a valid
# stamp, skips the attach, and compiles the wrong source tree. See
# do_check_attach() in this class.
echo "${PF}" > "${IB_TARGET}.attach.pf"
}

# Refuse to build against a tree that belongs to another recipe.
#
# IB_TARGET is a path in the source tree, not under tmp/, and more than one
# recipe can claim it: uboot_2022.04 and uboot_2024.07 both attach to
# u-boot/, selected by PREFERRED_VERSION per platform. do_attach_infrabase
# is stamped per recipe, so after building platform A and switching to
# platform B, B's recipe finds its own stamp from an earlier session, skips
# the attach, and runs do_configure against A's sources.
#
# That fails loudly when the defconfig is missing -- rpi4_64_defconfig in a
# 2024.07 tree -- and SILENTLY when it is not. A virt64 image was built with
# U-Boot 2022.04 instead of 2024.07; the two differ in
# CONFIG_POSITION_INDEPENDENT, so ATF loaded BL33 at 0x60000000 and jumped
# into a binary linked for 0x0. No output, no error, and a whole secure boot
# chain to bisect before the cause turned out to be the build system.
#
# The marker written by do_attach_infrabase names the recipe that owns the
# tree. This task is nostamp, so it is the one thing that always runs, and it
# refuses rather than repairing: re-attaching means deleting a tree the user
# may have edited, and that decision is theirs. A tree with no marker was
# attached before markers existed and is adopted silently.

do_check_attach[nostamp] = "1"
python do_check_attach() {
import os

target = d.getVar('IB_TARGET')
pf = d.getVar('PF')
if not target or not pf or not os.path.isdir(target):
return

try:
with open(target + '.attach.pf') as f:
owner = f.read().strip()
except OSError:
return

if owner == pf:
return

bb.fatal(
"%s is attached to %s, not to %s.\n"
"Both recipes share this directory, and each has its own attach "
"stamp, so building now would compile the other recipe's sources.\n"
"Re-attach before continuing:\n"
" rm -f %s.do_attach_infrabase\n"
" IB_FORCE_ATTACH=1 bitbake %s -c attach_infrabase\n"
"Anything you edited in %s that is not in the patch series will be "
"lost; the previous tree is kept as %s.back."
% (target, owner, pf, d.getVar('STAMP'), d.getVar('PN'), target, target)
)
}

addtask do_check_attach after do_attach_infrabase before do_configure

addtask cleansstate after do_clean
python do_cleansstate() {
sstate_clean_cachefiles(d)
Expand Down
44 changes: 42 additions & 2 deletions build/meta/classes/patch.bbclass
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,15 @@ def patch_identifier(path):
"+++ b/<path>" header line. Patches produced by do_diffcompose always
carry git-style a/<rel> b/<rel> labels, so this identifier uniquely
designates the source file the patch targets, independent of the
patch's own filename or sequence number."""
patch's own filename or sequence number.

Older patches in these series were produced by a plain `diff` run from
the target directory and carry "+++ ./<rel>" instead, with an absolute
path on the "---" side. Both spellings have to normalise to the same
identifier: when they do not, EVERY existing patch looks unmatched to
do_updiff, which then appends a duplicate for every file in the tree.
Seen on a u-boot series: 25190 patches appended onto 33. So the "./"
strip below is load-bearing, not cosmetic."""
src = None
dst = None
try:
Expand All @@ -433,7 +441,9 @@ def patch_identifier(path):
if not s or s == '/dev/null':
continue
if s.startswith('a/') or s.startswith('b/'):
return s[2:]
s = s[2:]
while s.startswith('./'):
s = s[2:]
return s
return None

Expand Down Expand Up @@ -544,6 +554,36 @@ python do_updiff() {
pass
next_number = max(used_numbers, default=0) + 1

# Sanity gate before touching the series.
#
# do_diffcompose compares the pristine snapshot against IB_TARGET, and
# IB_TARGET is a working tree the user may well have run `make` in. The
# exclude list above catches the build artefacts we know about, but it
# is a blacklist: a tree built with a configuration nobody anticipated,
# or an attach stale enough to predate half the series, produces
# thousands of "new file" patches, appended one per artefact.
#
# A legitimate updiff touches a handful of files, so the threshold is
# deliberately low: a single `build.sh uboot` was measured leaving 49
# artefacts the blacklist does not name. Refuse and say what to do,
# rather than writing the series and leaving the user to `git checkout`
# their way out.
limit = int(d.getVar('IB_UPDIFF_MAX_NEW') or 20)
new_count = sum(1 for pid in staging_idx if pid not in target_idx)
if new_count > limit:
bb.fatal(
f"do_updiff: {new_count} files in {d.getVar('IB_TARGET')} have no "
f"patch in the series yet (limit {limit}).\n"
f"That is what a BUILT tree looks like to do_diffcompose, not a "
f"set of edits: build output the exclude list does not name is "
f"being mistaken for new source.\n"
f"Clean the tree and re-attach it before folding edits back in:\n"
f" rm -f ${{BUILDDIR}}/tmp/stamps/{pf}.do_attach_infrabase\n"
f" IB_FORCE_ATTACH=1 bitbake {d.getVar('PN')} -c attach_infrabase\n"
f"then redo the edit and re-run updiff. If the count is genuinely "
f"right -- a large import, say -- raise IB_UPDIFF_MAX_NEW."
)

kept = consolidated = appended = skipped_chain = 0

# Per-file: CONSOLIDATE (overwrite the single existing patch),
Expand Down
Loading