]> git.ipfire.org Git - thirdparty/openembedded/openembedded-core-contrib.git/commitdiff
classes: add class for retaining build results paule/retain
authorPaul Eggleton <paul.eggleton@microsoft.com>
Wed, 8 Jul 2020 04:46:17 +0000 (04:46 +0000)
committerPaul Eggleton <paul.eggleton@linux.microsoft.com>
Wed, 14 Oct 2020 02:24:40 +0000 (19:24 -0700)
If you are running your builds inside an environment where you don't
have access to the build tree (e.g. an autobuilder where you can only
download final artifacts such as images), then debugging build failures
can be difficult - you can't examine log files, the source tree or
output files. When enabled, this class does two things:

1) Triggers on task failure and saves a tarball of the work directory
   for the task's recipe
2) Optionally saves tarballs of a list of nominated directories

It puts these tarballs in a configurable location, where they can be
picked up by a separate process and made available as downloadable
artifacts.

Signed-off-by: Paul Eggleton <paul.eggleton@microsoft.com>
meta/classes/retain.bbclass [new file with mode: 0644]

diff --git a/meta/classes/retain.bbclass b/meta/classes/retain.bbclass
new file mode 100644 (file)
index 0000000..e98765f
--- /dev/null
@@ -0,0 +1,103 @@
+# Creates a tarball of the work directory for a recipe when one of its
+# tasks fails, as well as (optionally) other nominated directories.
+# Useful in cases where the environment in which builds are run is
+# ephemeral or otherwise inaccessible for examination during
+# debugging.
+#
+# To enable, simply add the following to your configuration:
+#
+# INHERIT += "retain"
+#
+# You can also specify extra directories to save at the end of the build
+# upon failure or always (space-separated) e.g.:
+#
+# RETAIN_EXTRADIRS_FAILURE = "${LOG_DIR} ${TMPDIR}/pkgdata"
+# RETAIN_EXTRADIRS_ALWAYS = "${BUILDSTATS_BASE}"
+#
+# If you wish to use a different tarball name prefix you can do so by
+# adding a : followed by the desired prefix (no spaces) e.g. to use
+# "buildlogs" for the tarball of ${LOG_DIR} you would do this:
+#
+# RETAIN_EXTRADIRS_FAILURE = "${LOG_DIR}:buildlogs ${TMPDIR}/pkgdata"
+#
+# Notes:
+# * For this to be useful you also need corresponding logic in your build
+#   orchestration tool to pick up any files written out to RETAIN_OUTDIR
+#   (with the other assumption being that no files are present there at
+#   the start of the build).
+# * Work directories can be quite large, so saving them can take some time
+#   and of course space.
+# * Extra directories must naturally be populated at the time the retain
+#   goes to save them (build completion); to try ensure this for things
+#   that are also saved on build completion (e.g. buildstats), put the
+#   INHERIT += "retain" after the INHERIT += lines for the class that
+#   is writing out the data that you wish to save.
+# * The tarballs have the tarball name as a top-level directory so that
+#   multiple tarballs can be extracted side-by-side easily.
+#
+# Copyright (c) 2020 Microsoft Corporation
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+RETAIN_OUTDIR ?= "${TMPDIR}/retained"
+RETAIN_EXTRADIRS_FAILURE ?= ""
+RETAIN_EXTRADIRS_ALWAYS ?= ""
+RETAIN_ENABLED ?= "1"
+
+
+def retain_retain_dir(desc, tarprefix, path, tarbasepath, d):
+    import datetime
+
+    outdir = d.getVar('RETAIN_OUTDIR')
+    bb.utils.mkdirhier(outdir)
+    tstamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+    tarname = '%s_%s' % (tarprefix, tstamp)
+    tarfp = os.path.join(outdir, '%s.tar.gz' % tarname)
+    tardir = os.path.relpath(path, tarbasepath)
+    cmdargs = ['tar', 'czf', tarfp]
+    # Prefix paths within the tarball with the tarball name so that
+    # multiple tarballs can be extracted side-by-side
+    cmdargs += ['--transform', 's:^:%s/:' % tarname]
+    cmdargs += [tardir]
+    bb.plain('NOTE: retain: saving %s to %s' % (desc, tarfp))
+    try:
+        bb.process.run(cmdargs, cwd=tarbasepath)
+    except bb.process.ExecutionError as e:
+        # It is possible for other tasks to be writing to the workdir
+        # while we are tarring it up, in which case tar will return 1,
+        # but we don't care in this situation (tar returns 2 for other
+        # errors so we we will see those)
+        if e.exitcode != 1:
+            bb.warn('retain: error saving %s: %s' % (desc, str(e)))
+
+
+addhandler retain_workdir_handler
+retain_workdir_handler[eventmask] = "bb.build.TaskFailed bb.event.BuildCompleted"
+
+python retain_workdir_handler() {
+    if d.getVar('RETAIN_ENABLED') != '1':
+        return
+
+    if isinstance(e, bb.build.TaskFailed):
+        pn = d.getVar('PN')
+        workdir = d.getVar('WORKDIR')
+        base_workdir = d.getVar('BASE_WORKDIR')
+        taskname = d.getVar('BB_CURRENTTASK')
+        desc = 'workdir for failed task %s.do_%s' % (pn, taskname)
+        retain_retain_dir(desc, 'workdir_%s' % pn, workdir, base_workdir, d)
+    elif isinstance(e, bb.event.BuildCompleted):
+        paths = d.getVar('RETAIN_EXTRADIRS_ALWAYS').split()
+        if e._failures:
+            paths += d.getVar('RETAIN_EXTRADIRS_FAILURE').split()
+
+        for path in list(set(paths)):
+            if ':' in path:
+                path, itemname = path.rsplit(':', 1)
+            else:
+                itemname = os.path.basename(path)
+            if os.path.exists(path):
+                retain_retain_dir(itemname, itemname, path, os.path.dirname(path), d)
+            else:
+                bb.warn('retain: extra directory %s does not currently exist' % path)
+}