diff options
Diffstat (limited to 'meta/classes-global/retain.bbclass')
-rw-r--r-- | meta/classes-global/retain.bbclass | 182 |
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 | |||
55 | RETAIN_OUTDIR ?= "${TMPDIR}/retained" | ||
56 | RETAIN_DIRS_FAILURE ?= "${WORKDIR};prefix=workdir" | ||
57 | RETAIN_DIRS_ALWAYS ?= "" | ||
58 | RETAIN_DIRS_GLOBAL_FAILURE ?= "" | ||
59 | RETAIN_DIRS_GLOBAL_ALWAYS ?= "" | ||
60 | RETAIN_TARBALL_SUFFIX ?= "${DATETIME}.tar.gz" | ||
61 | RETAIN_ENABLED ?= "1" | ||
62 | |||
63 | |||
64 | def 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 | |||
92 | addhandler retain_task_handler | ||
93 | retain_task_handler[eventmask] = "bb.build.TaskFailed bb.build.TaskSucceeded" | ||
94 | |||
95 | addhandler retain_build_handler | ||
96 | retain_build_handler[eventmask] = "bb.event.BuildStarted bb.event.BuildCompleted" | ||
97 | |||
98 | python 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 | |||
118 | python 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 | } | ||