@@ -206,6 +206,10 @@ def sstate_state_fromvars(d, task = None):
ss['fixmedir'] = fixmedir
return ss
+def sstate_logrecords_path(d, tid):
+ recordfn, taskname = tid.rsplit(':', 1)
+ return bb.utils.logrecords_path(d, recordfn, taskname)
+
def sstate_log_strip_paths(d, message):
import re
@@ -239,6 +243,11 @@ def sstate_log_save(d, record):
levelno, message = record
return {'levelno': levelno, 'message': sstate_log_strip_paths(d, message)}
+def sstate_log_expand(d, logentry):
+ # Archived record (dict) -> raw logstore entry, with path placeholders
+ # expanded back. Inverse of sstate_log_save.
+ return [logentry['levelno'], sstate_log_expand_paths(d, logentry['message'])]
+
def sstate_log_emit(d, logentry):
levelno = logentry.get('levelno', 0)
if levelno < bb.msg.BBLogFormatter.WARNONCE or "message" not in logentry:
@@ -253,24 +262,84 @@ def sstate_log_emit(d, logentry):
else:
bb.warn(message)
-python sstate_save_task_log () {
+def sstate_replay_logs(d, logdata):
+ """Replay the log records of the tasks this setscene task was handed via
+ BB_SETSCENE_CLAIMED_TASKS, plus its own task.
+
+ Fold each task's records back into the shared logstore (always, even
+ empty, to clear a stale entry from an earlier native run), so an sstate
+ archive created later in this build still bundles them although the
+ tasks were restored rather than run natively. Then emit them for the
+ user.
+ """
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.
- 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)
+ claimed = (d.getVar('BB_SETSCENE_CLAIMED_TASKS') or '').split()
+ filename = d.getVar('BB_FILENAME', False)
+ claimed.append((filename + ":" if filename else "") +
+ d.getVar('BB_RUNTASK').removesuffix('_setscene'))
+
+ records = []
+ for task in claimed:
+ taskrecords = logdata.get('tasks', {}).get(task, [])
+ logrecordsfn = sstate_logrecords_path(d, task)
+ bb.utils.mkdirhier(os.path.dirname(logrecordsfn))
+ with open(logrecordsfn, 'w') as f:
+ json.dump([sstate_log_expand(d, e)
+ for e in taskrecords
+ if 'levelno' in e and 'message' in e], f)
+ records += taskrecords
+ for logentry in records:
+ sstate_log_emit(d, logentry)
+
+sstate_replay_logs[vardepsexclude] = "BB_SETSCENE_CLAIMED_TASKS BB_FILENAME BB_RUNTASK"
- tasks = {mytask: [sstate_log_save(d, record) for record in records]}
+python sstate_save_task_log () {
+ import json
- logfn = os.path.join(d.getVar('SSTATE_BUILDDIR'), "sstate-log.json")
- with open(logfn, 'w') as f:
- json.dump({'tasks': tasks}, f)
+ # Also bundle the messages of the non-sstate tasks we depend on, from this
+ # recipe or from others: they are skipped when restored from sstate, and
+ # bitbake picks which setscene task claims them (BB_SETSCENE_CLAIMED_TASKS).
+ taskdepdata = d.getVar('BB_TASKDEPDATA', False) or {}
+ sstatetasks = d.getVar('SSTATETASKS').split()
+ mytask = d.getVar('BB_RUNTASK')
+ # 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 = {start}
+ seen = {start}
+ todo = [start]
+ while todo:
+ for dep in taskdepdata[todo.pop()].deps:
+ t = taskdepdata[dep]
+ if dep in seen or t.taskname in sstatetasks:
+ continue
+ seen.add(dep)
+ todo.append(dep)
+ loggedtasks.add(dep)
+
+ # Every task writes its records right after its body, before its postfuncs, to
+ # the location returned by logrecords_path(), so the files of all dependencies,
+ # including from other recipes, exist here.
+ tasklogs = {}
+ for tid in loggedtasks:
+ recordsfn = sstate_logrecords_path(d, tid)
+ if os.path.exists(recordsfn):
+ with open(recordsfn) as f:
+ tasklogs[tid] = json.load(f)
+
+ tasks = {}
+ for tid, records in tasklogs.items():
+ tasks[tid] = [sstate_log_save(d, record) for record 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):
@@ -480,11 +549,7 @@ def sstate_installpkgdir(ss, d, replay=False):
logfp = os.path.join(sstateinst, "sstate-log.json")
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'), [])
- for logentry in records:
- sstate_log_emit(d, logentry)
-
+ sstate_replay_logs(d, json.load(logfile))
bb.utils.remove(logfp)
return True
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 non-sstate tasks this task depends on, from this recipe or from others, read from the location returned by logrecords_path(). On install, replay the records of the covered tasks bitbake assigned to this setscene task through BB_SETSCENE_CLAIMED_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 | 103 ++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 19 deletions(-)