diff mbox series

[RFC,2/3] sstate: Replay the records of tasks covered by setscene

Message ID 20261024-mathieu-debug-replay-v4-v1-2-fc591ec473a7@bootlin.com
State New
Headers show
Series sstate: Replay warnings and errors when restoring from sstate | expand

Commit Message

Mathieu Dubois-Briand Oct. 24, 2026, 12:34 p.m. UTC
Tasks which are not sstate tasks (do_configure, do_compile, do_install)
are skipped when covered by a setscene task, so their messages were never
shown again on sstate reuse.

When creating an sstate package, also bundle the records of the
same-recipe non-sstate tasks this task depends on, read from
${T}/logrecords.<task>.json. On install, replay the records of the
covered tasks bitbake assigned to this setscene task through
BB_SETSCENE_REPLAY_TASKS, so each covered task is replayed exactly once
even when several setscene tasks cover it.

AI-Generated: Uses opencode (DeepSeek V4 Flash)
Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
 meta/classes-global/sstate.bbclass | 67 ++++++++++++++++++++++++++++----------
 1 file changed, 50 insertions(+), 17 deletions(-)
diff mbox series

Patch

diff --git a/meta/classes-global/sstate.bbclass b/meta/classes-global/sstate.bbclass
index fe14022bc09f..a157ad5cb461 100644
--- a/meta/classes-global/sstate.bbclass
+++ b/meta/classes-global/sstate.bbclass
@@ -235,23 +235,50 @@  def sstate_log_expand_paths(d, message):
 python sstate_save_task_log () {
     import json
 
-    # Save the messages logged by this task, so they can be replayed when the
-    # task is restored from sstate. The records are written by bitbake right
-    # after the task body, before the postfuncs.
+    # Also bundle the messages of the non-sstate tasks of this recipe we depend
+    # on, as they are skipped when this task is restored from sstate. bitbake
+    # picks which setscene task replays them (BB_SETSCENE_REPLAY_TASKS).
+    taskdepdata = d.getVar('BB_TASKDEPDATA', False) or {}
+    sstatetasks = d.getVar('SSTATETASKS').split()
     mytask = d.getVar('BB_RUNTASK')
-    recordsfn = os.path.join(d.getVar('T'), 'logrecords.%s.json' % mytask)
-    if not os.path.exists(recordsfn):
-        return
-    with open(recordsfn) as f:
-        records = json.load(f)
-
-    tasks = {mytask: [{'levelno': levelno,
-                       'message': sstate_log_strip_paths(d, message)}
-                      for levelno, message in records]}
-
-    logfn = os.path.join(d.getVar('SSTATE_BUILDDIR'), "sstate-log.json")
-    with open(logfn, 'w') as f:
-        json.dump({'tasks': tasks}, f)
+    # The running task is always an entry in taskdepdata, under its own tid.
+    # Reconstruct that tid from BB_FILENAME and BB_RUNTASK.
+    filename = d.getVar('BB_FILENAME', False)
+    start = filename + ":" + mytask if filename else None
+    if start not in taskdepdata:
+        bb.fatal("sstate: cannot identify task %s in taskdepdata" % start)
+
+    loggedtasks = {mytask}
+    seen = {start}
+    todo = [start]
+    while todo:
+        for dep in taskdepdata[todo.pop()].deps:
+            t = taskdepdata[dep]
+            if dep in seen or t.fn != taskdepdata[start].fn or t.taskname in sstatetasks:
+                continue
+            seen.add(dep)
+            todo.append(dep)
+            loggedtasks.add(t.taskname)
+
+    # Every task writes its records to ${T} right after its body, before its
+    # postfuncs, so our own file and the dependency files all exist by now.
+    tasklogs = {}
+    for taskname in loggedtasks:
+        recordsfn = os.path.join(d.getVar('T'), 'logrecords.%s.json' % taskname)
+        if os.path.exists(recordsfn):
+            with open(recordsfn) as f:
+                tasklogs[taskname] = json.load(f)
+
+    tasks = {}
+    for task, records in tasklogs.items():
+        tasks[task] = [{'levelno': levelno,
+                        'message': sstate_log_strip_paths(d, message)}
+                       for levelno, message in records]
+
+    if tasks:
+        logfn = os.path.join(d.getVar('SSTATE_BUILDDIR'), "sstate-log.json")
+        with open(logfn, 'w') as f:
+            json.dump({'tasks': tasks}, f)
 }
 
 def sstate_add(ss, source, dest, d):
@@ -462,7 +489,11 @@  def sstate_installpkgdir(ss, d, replay=False):
         if os.path.exists(logfp):
             with open(logfp, 'r') as logfile:
                 logdata = json.load(logfile)
-            records = logdata.get('tasks', {}).get(d.getVar('BB_RUNTASK').removesuffix('_setscene'), [])
+            replaytasks = (d.getVar('BB_SETSCENE_REPLAY_TASKS') or '').split()
+            replaytasks.append(d.getVar('BB_RUNTASK').removesuffix('_setscene'))
+            records = []
+            for task in replaytasks:
+                records += logdata.get('tasks', {}).get(task, [])
             for logentry in records:
                 levelno = logentry.get('levelno', 0)
                 if levelno < bb.msg.BBLogFormatter.WARNONCE or "message" not in logentry:
@@ -481,6 +512,8 @@  def sstate_installpkgdir(ss, d, replay=False):
 
     return True
 
+sstate_installpkgdir[vardepsexclude] += "BB_SETSCENE_REPLAY_TASKS"
+
 python sstate_hardcode_path_unpack () {
     # Fixup hardcoded paths
     #