summaryrefslogtreecommitdiffstats
path: root/meta/classes-global
diff options
context:
space:
mode:
authorPaul Eggleton <paul.eggleton@microsoft.com>2024-07-31 21:10:22 -0700
committerRichard Purdie <richard.purdie@linuxfoundation.org>2024-08-03 07:56:10 +0100
commit2dded9958328d9e5a801dd3cb2d0084f538cfef6 (patch)
treec918cc2b9ae4e2158e1e26ac722e94e277532480 /meta/classes-global
parent9fd5ab769c55a310fe28a43ab8bc87232c6a7ce9 (diff)
downloadpoky-2dded9958328d9e5a801dd3cb2d0084f538cfef6.tar.gz
classes: add new retain class for retaining build results
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, by default this class will retain the work directory for any recipe that has a task failure in the form of a tarball, and can also be configured to save other directories on failure or always. It puts these tarballs in a configurable location (${TMPDIR}/retained by default), where they can be picked up by a separate process and made available as downloadable artifacts. (From OE-Core rev: e2030c0d747eb990b9ad10098c6b74d6f8f4e74e) Signed-off-by: Paul Eggleton <paul.eggleton@microsoft.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/classes-global')
-rw-r--r--meta/classes-global/retain.bbclass182
1 files changed, 182 insertions, 0 deletions
diff --git a/meta/classes-global/retain.bbclass b/meta/classes-global/retain.bbclass
new file mode 100644
index 0000000000..46e8c256cf
--- /dev/null
+++ b/meta/classes-global/retain.bbclass
@@ -0,0 +1,182 @@
1# Creates a tarball of the work directory for a recipe when one of its
2# tasks fails, or any other nominated directories.
3# Useful in cases where the environment in which builds are run is
4# ephemeral or otherwise inaccessible for examination during
5# debugging.
6#
7# To enable, simply add the following to your configuration:
8#
9# INHERIT += "retain"
10#
11# You can specify the recipe-specific directories to save upon failure
12# or always (space-separated) e.g.:
13#
14# RETAIN_DIRS_FAILURE = "${WORKDIR};prefix=workdir" # default
15# RETAIN_DIRS_ALWAYS = "${T}"
16#
17# Naturally you can use overrides to limit it to a specific recipe:
18# RETAIN_DIRS_ALWAYS:pn-somerecipe = "${T}"
19#
20# You can also specify global (non-recipe-specific) directories to save:
21#
22# RETAIN_DIRS_GLOBAL_FAILURE = "${LOG_DIR}"
23# RETAIN_DIRS_GLOBAL_ALWAYS = "${BUILDSTATS_BASE}"
24#
25# If you wish to use a different tarball name prefix than the default of
26# the directory name, you can do so by specifying a ;prefix= followed by
27# the desired prefix (no spaces) in any of the RETAIN_DIRS_* variables.
28# e.g. to always save the log files with a "recipelogs" as the prefix for
29# the tarball of ${T} you would do this:
30#
31# RETAIN_DIRS_ALWAYS = "${T};prefix=recipelogs"
32#
33# Notes:
34# * For this to be useful you also need corresponding logic in your build
35# orchestration tool to pick up any files written out to RETAIN_OUTDIR
36# (with the other assumption being that no files are present there at
37# the start of the build, since there is no logic to purge old files).
38# * Work directories can be quite large, so saving them can take some time
39# and of course space.
40# * Tarball creation is deferred to the end of the build, thus you will
41# get the state at the end, not immediately upon failure.
42# * Extra directories must naturally be populated at the time the retain
43# class goes to save them (build completion); to try ensure this for
44# things that are also saved on build completion (e.g. buildstats), put
45# the INHERIT += "retain" after the INHERIT += lines for the class that
46# is writing out the data that you wish to save.
47# * The tarballs have the tarball name as a top-level directory so that
48# multiple tarballs can be extracted side-by-side easily.
49#
50# Copyright (c) 2020, 2024 Microsoft Corporation
51#
52# SPDX-License-Identifier: GPL-2.0-only
53#
54
55RETAIN_OUTDIR ?= "${TMPDIR}/retained"
56RETAIN_DIRS_FAILURE ?= "${WORKDIR};prefix=workdir"
57RETAIN_DIRS_ALWAYS ?= ""
58RETAIN_DIRS_GLOBAL_FAILURE ?= ""
59RETAIN_DIRS_GLOBAL_ALWAYS ?= ""
60RETAIN_TARBALL_SUFFIX ?= "${DATETIME}.tar.gz"
61RETAIN_ENABLED ?= "1"
62
63
64def retain_retain_dir(desc, tarprefix, path, tarbasepath, d):
65 import datetime
66
67 outdir = d.getVar('RETAIN_OUTDIR')
68 bb.utils.mkdirhier(outdir)
69 suffix = d.getVar('RETAIN_TARBALL_SUFFIX')
70 tarname = '%s_%s' % (tarprefix, suffix)
71 tarfp = os.path.join(outdir, '%s' % tarname)
72 tardir = os.path.relpath(path, tarbasepath)
73 cmdargs = ['tar', 'cfa', tarfp]
74 # Prefix paths within the tarball with the tarball name so that
75 # multiple tarballs can be extracted side-by-side
76 tarname_noext = os.path.splitext(tarname)[0]
77 if tarname_noext.endswith('.tar'):
78 tarname_noext = tarname_noext[:-4]
79 cmdargs += ['--transform', 's:^:%s/:' % tarname_noext]
80 cmdargs += [tardir]
81 try:
82 bb.process.run(cmdargs, cwd=tarbasepath)
83 except bb.process.ExecutionError as e:
84 # It is possible for other tasks to be writing to the workdir
85 # while we are tarring it up, in which case tar will return 1,
86 # but we don't care in this situation (tar returns 2 for other
87 # errors so we we will see those)
88 if e.exitcode != 1:
89 bb.warn('retain: error saving %s: %s' % (desc, str(e)))
90
91
92addhandler retain_task_handler
93retain_task_handler[eventmask] = "bb.build.TaskFailed bb.build.TaskSucceeded"
94
95addhandler retain_build_handler
96retain_build_handler[eventmask] = "bb.event.BuildStarted bb.event.BuildCompleted"
97
98python retain_task_handler() {
99 if d.getVar('RETAIN_ENABLED') != '1':
100 return
101
102 dirs = d.getVar('RETAIN_DIRS_ALWAYS')
103 if isinstance(e, bb.build.TaskFailed):
104 dirs += ' ' + d.getVar('RETAIN_DIRS_FAILURE')
105
106 dirs = dirs.strip().split()
107 if dirs:
108 outdir = d.getVar('RETAIN_OUTDIR')
109 bb.utils.mkdirhier(outdir)
110 dirlist_file = os.path.join(outdir, 'retain_dirs.list')
111 pn = d.getVar('PN')
112 taskname = d.getVar('BB_CURRENTTASK')
113 with open(dirlist_file, 'a') as f:
114 for entry in dirs:
115 f.write('%s %s %s\n' % (pn, taskname, entry))
116}
117
118python retain_build_handler() {
119 outdir = d.getVar('RETAIN_OUTDIR')
120 dirlist_file = os.path.join(outdir, 'retain_dirs.list')
121
122 if isinstance(e, bb.event.BuildStarted):
123 if os.path.exists(dirlist_file):
124 os.remove(dirlist_file)
125 return
126
127 if d.getVar('RETAIN_ENABLED') != '1':
128 return
129
130 savedirs = {}
131 try:
132 with open(dirlist_file, 'r') as f:
133 for line in f:
134 pn, _, path = line.rstrip().split()
135 if not path in savedirs:
136 savedirs[path] = pn
137 os.remove(dirlist_file)
138 except FileNotFoundError:
139 pass
140
141 if e.getFailures():
142 for path in (d.getVar('RETAIN_DIRS_GLOBAL_FAILURE') or '').strip().split():
143 savedirs[path] = ''
144
145 for path in (d.getVar('RETAIN_DIRS_GLOBAL_ALWAYS') or '').strip().split():
146 savedirs[path] = ''
147
148 if savedirs:
149 bb.plain('NOTE: retain: retaining build output...')
150 count = 0
151 for path, pn in savedirs.items():
152 prefix = None
153 if ';' in path:
154 pathsplit = path.split(';')
155 path = pathsplit[0]
156 for param in pathsplit[1:]:
157 if '=' in param:
158 name, value = param.split('=', 1)
159 if name == 'prefix':
160 prefix = value
161 else:
162 bb.error('retain: invalid parameter "%s" in RETAIN_* variable value' % param)
163 return
164 else:
165 bb.error('retain: parameter "%s" missing value in RETAIN_* variable value' % param)
166 return
167 if prefix:
168 itemname = prefix
169 else:
170 itemname = os.path.basename(path)
171 if pn:
172 # Always add the recipe name in front
173 itemname = pn + '_' + itemname
174 if os.path.exists(path):
175 retain_retain_dir(itemname, itemname, path, os.path.dirname(path), d)
176 count += 1
177 else:
178 bb.warn('retain: path %s does not currently exist' % path)
179 if count:
180 item = 'archive' if count == 1 else 'archives'
181 bb.plain('NOTE: retain: saved %d %s to %s' % (count, item, outdir))
182}