blob: a3b1bb1087d368414634b8cf07dadeb1a0d0f9b1 [file] [log] [blame]
Brad Bishopc342db32019-05-15 21:57:59 -04001#
Patrick Williams92b42cb2022-09-03 06:53:57 -05002# Copyright OpenEmbedded Contributors
3#
Brad Bishopc342db32019-05-15 21:57:59 -04004# SPDX-License-Identifier: GPL-2.0-only
5#
6
Brad Bishop6e60e8b2018-02-01 10:27:11 -05007import subprocess
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08008import multiprocessing
9import traceback
Patrick Williamsc124f4f2015-09-15 14:41:29 -050010
11def read_file(filename):
12 try:
13 f = open( filename, "r" )
14 except IOError as reason:
15 return "" # WARNING: can't raise an error now because of the new RDEPENDS handling. This is a bit ugly. :M:
16 else:
17 data = f.read().strip()
18 f.close()
19 return data
20 return None
21
22def ifelse(condition, iftrue = True, iffalse = False):
23 if condition:
24 return iftrue
25 else:
26 return iffalse
27
28def conditional(variable, checkvalue, truevalue, falsevalue, d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -050029 if d.getVar(variable) == checkvalue:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030 return truevalue
31 else:
32 return falsevalue
33
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080034def vartrue(var, iftrue, iffalse, d):
35 import oe.types
36 if oe.types.boolean(d.getVar(var)):
37 return iftrue
38 else:
39 return iffalse
40
Patrick Williamsc124f4f2015-09-15 14:41:29 -050041def less_or_equal(variable, checkvalue, truevalue, falsevalue, d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -050042 if float(d.getVar(variable)) <= float(checkvalue):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050043 return truevalue
44 else:
45 return falsevalue
46
47def version_less_or_equal(variable, checkvalue, truevalue, falsevalue, d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -050048 result = bb.utils.vercmp_string(d.getVar(variable), checkvalue)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050049 if result <= 0:
50 return truevalue
51 else:
52 return falsevalue
53
54def both_contain(variable1, variable2, checkvalue, d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -050055 val1 = d.getVar(variable1)
56 val2 = d.getVar(variable2)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050057 val1 = set(val1.split())
58 val2 = set(val2.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -060059 if isinstance(checkvalue, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050060 checkvalue = set(checkvalue.split())
61 else:
62 checkvalue = set(checkvalue)
63 if checkvalue.issubset(val1) and checkvalue.issubset(val2):
64 return " ".join(checkvalue)
65 else:
66 return ""
67
68def set_intersect(variable1, variable2, d):
69 """
70 Expand both variables, interpret them as lists of strings, and return the
71 intersection as a flattened string.
72
73 For example:
74 s1 = "a b c"
75 s2 = "b c d"
76 s3 = set_intersect(s1, s2)
77 => s3 = "b c"
78 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -050079 val1 = set(d.getVar(variable1).split())
80 val2 = set(d.getVar(variable2).split())
Patrick Williamsc124f4f2015-09-15 14:41:29 -050081 return " ".join(val1 & val2)
82
83def prune_suffix(var, suffixes, d):
84 # See if var ends with any of the suffixes listed and
85 # remove it if found
86 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -040087 if suffix and var.endswith(suffix):
88 var = var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -050089
Brad Bishop6e60e8b2018-02-01 10:27:11 -050090 prefix = d.getVar("MLPREFIX")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050091 if prefix and var.startswith(prefix):
Brad Bishopd89cb5f2019-04-10 09:02:41 -040092 var = var[len(prefix):]
Patrick Williamsc124f4f2015-09-15 14:41:29 -050093
94 return var
95
96def str_filter(f, str, d):
97 from re import match
Patrick Williamsc0f7c042017-02-23 20:41:17 -060098 return " ".join([x for x in str.split() if match(f, x, 0)])
Patrick Williamsc124f4f2015-09-15 14:41:29 -050099
100def str_filter_out(f, str, d):
101 from re import match
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600102 return " ".join([x for x in str.split() if not match(f, x, 0)])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500103
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500104def build_depends_string(depends, task):
105 """Append a taskname to a string of dependencies as used by the [depends] flag"""
106 return " ".join(dep + ":" + task for dep in depends.split())
107
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500108def inherits(d, *classes):
109 """Return True if the metadata inherits any of the specified classes"""
110 return any(bb.data.inherits_class(cls, d) for cls in classes)
111
112def features_backfill(var,d):
113 # This construct allows the addition of new features to variable specified
114 # as var
115 # Example for var = "DISTRO_FEATURES"
116 # This construct allows the addition of new features to DISTRO_FEATURES
117 # that if not present would disable existing functionality, without
118 # disturbing distributions that have already set DISTRO_FEATURES.
119 # Distributions wanting to elide a value in DISTRO_FEATURES_BACKFILL should
120 # add the feature to DISTRO_FEATURES_BACKFILL_CONSIDERED
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500121 features = (d.getVar(var) or "").split()
122 backfill = (d.getVar(var+"_BACKFILL") or "").split()
123 considered = (d.getVar(var+"_BACKFILL_CONSIDERED") or "").split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500124
125 addfeatures = []
126 for feature in backfill:
127 if feature not in features and feature not in considered:
128 addfeatures.append(feature)
129
130 if addfeatures:
131 d.appendVar(var, " " + " ".join(addfeatures))
132
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500133def all_distro_features(d, features, truevalue="1", falsevalue=""):
134 """
135 Returns truevalue if *all* given features are set in DISTRO_FEATURES,
136 else falsevalue. The features can be given as single string or anything
137 that can be turned into a set.
138
139 This is a shorter, more flexible version of
140 bb.utils.contains("DISTRO_FEATURES", features, truevalue, falsevalue, d).
141
142 Without explicit true/false values it can be used directly where
143 Python expects a boolean:
144 if oe.utils.all_distro_features(d, "foo bar"):
145 bb.fatal("foo and bar are mutually exclusive DISTRO_FEATURES")
146
147 With just a truevalue, it can be used to include files that are meant to be
148 used only when requested via DISTRO_FEATURES:
149 require ${@ oe.utils.all_distro_features(d, "foo bar", "foo-and-bar.inc")
150 """
151 return bb.utils.contains("DISTRO_FEATURES", features, truevalue, falsevalue, d)
152
153def any_distro_features(d, features, truevalue="1", falsevalue=""):
154 """
155 Returns truevalue if at least *one* of the given features is set in DISTRO_FEATURES,
156 else falsevalue. The features can be given as single string or anything
157 that can be turned into a set.
158
159 This is a shorter, more flexible version of
160 bb.utils.contains_any("DISTRO_FEATURES", features, truevalue, falsevalue, d).
161
162 Without explicit true/false values it can be used directly where
163 Python expects a boolean:
164 if not oe.utils.any_distro_features(d, "foo bar"):
165 bb.fatal("foo, bar or both must be set in DISTRO_FEATURES")
166
167 With just a truevalue, it can be used to include files that are meant to be
168 used only when requested via DISTRO_FEATURES:
169 require ${@ oe.utils.any_distro_features(d, "foo bar", "foo-or-bar.inc")
170
171 """
172 return bb.utils.contains_any("DISTRO_FEATURES", features, truevalue, falsevalue, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500173
Andrew Geissler82c905d2020-04-13 13:39:40 -0500174def parallel_make(d, makeinst=False):
Brad Bishop316dfdd2018-06-25 12:45:53 -0400175 """
176 Return the integer value for the number of parallel threads to use when
177 building, scraped out of PARALLEL_MAKE. If no parallelization option is
178 found, returns None
179
180 e.g. if PARALLEL_MAKE = "-j 10", this will return 10 as an integer.
181 """
Andrew Geissler82c905d2020-04-13 13:39:40 -0500182 if makeinst:
183 pm = (d.getVar('PARALLEL_MAKEINST') or '').split()
184 else:
185 pm = (d.getVar('PARALLEL_MAKE') or '').split()
Brad Bishop316dfdd2018-06-25 12:45:53 -0400186 # look for '-j' and throw other options (e.g. '-l') away
187 while pm:
188 opt = pm.pop(0)
189 if opt == '-j':
190 v = pm.pop(0)
191 elif opt.startswith('-j'):
192 v = opt[2:].strip()
193 else:
194 continue
195
196 return int(v)
197
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600198 return ''
Brad Bishop316dfdd2018-06-25 12:45:53 -0400199
Andrew Geissler82c905d2020-04-13 13:39:40 -0500200def parallel_make_argument(d, fmt, limit=None, makeinst=False):
Brad Bishop316dfdd2018-06-25 12:45:53 -0400201 """
202 Helper utility to construct a parallel make argument from the number of
203 parallel threads specified in PARALLEL_MAKE.
204
205 Returns the input format string `fmt` where a single '%d' will be expanded
206 with the number of parallel threads to use. If `limit` is specified, the
207 number of parallel threads will be no larger than it. If no parallelization
208 option is found in PARALLEL_MAKE, returns an empty string
209
210 e.g. if PARALLEL_MAKE = "-j 10", parallel_make_argument(d, "-n %d") will return
211 "-n 10"
212 """
Andrew Geissler82c905d2020-04-13 13:39:40 -0500213 v = parallel_make(d, makeinst)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400214 if v:
215 if limit:
216 v = min(limit, v)
217 return fmt % v
218 return ''
219
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500220def packages_filter_out_system(d):
221 """
222 Return a list of packages from PACKAGES with the "system" packages such as
223 PN-dbg PN-doc PN-locale-eb-gb removed.
224 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500225 pn = d.getVar('PN')
Andrew Geissler9aee5002022-03-30 16:27:02 +0000226 pkgfilter = [pn + suffix for suffix in ('', '-dbg', '-dev', '-doc', '-locale', '-staticdev', '-src')]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500227 localepkg = pn + "-locale-"
228 pkgs = []
229
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500230 for pkg in d.getVar('PACKAGES').split():
Andrew Geissler9aee5002022-03-30 16:27:02 +0000231 if pkg not in pkgfilter and localepkg not in pkg:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500232 pkgs.append(pkg)
233 return pkgs
234
235def getstatusoutput(cmd):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500236 return subprocess.getstatusoutput(cmd)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500237
238
239def trim_version(version, num_parts=2):
240 """
241 Return just the first <num_parts> of <version>, split by periods. For
242 example, trim_version("1.2.3", 2) will return "1.2".
243 """
244 if type(version) is not str:
245 raise TypeError("Version should be a string")
246 if num_parts < 1:
247 raise ValueError("Cannot split to parts < 1")
248
249 parts = version.split(".")
250 trimmed = ".".join(parts[:num_parts])
251 return trimmed
252
Andrew Geissler595f6302022-01-24 19:11:47 +0000253def cpu_count(at_least=1, at_most=64):
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500254 cpus = len(os.sched_getaffinity(0))
Andrew Geissler595f6302022-01-24 19:11:47 +0000255 return max(min(cpus, at_most), at_least)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500256
257def execute_pre_post_process(d, cmds):
258 if cmds is None:
259 return
260
Andrew Geissler5082cc72023-09-11 08:41:39 -0400261 cmds = cmds.replace(";", " ")
262
263 for cmd in cmds.split():
264 bb.note("Executing %s ..." % cmd)
265 bb.build.exec_func(cmd, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500266
Andrew Geissler220dafd2023-10-04 10:18:08 -0500267def get_bb_number_threads(d):
268 return int(d.getVar("BB_NUMBER_THREADS") or os.cpu_count() or 1)
269
270def multiprocess_launch(target, items, d, extraargs=None):
271 max_process = get_bb_number_threads(d)
272 return multiprocess_launch_mp(target, items, max_process, extraargs)
273
274# For each item in items, call the function 'target' with item as the first
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800275# argument, extraargs as the other arguments and handle any exceptions in the
276# parent thread
Andrew Geissler220dafd2023-10-04 10:18:08 -0500277def multiprocess_launch_mp(target, items, max_process, extraargs=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500278
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800279 class ProcessLaunch(multiprocessing.Process):
280 def __init__(self, *args, **kwargs):
281 multiprocessing.Process.__init__(self, *args, **kwargs)
282 self._pconn, self._cconn = multiprocessing.Pipe()
283 self._exception = None
284 self._result = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500285
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800286 def run(self):
287 try:
288 ret = self._target(*self._args, **self._kwargs)
289 self._cconn.send((None, ret))
290 except Exception as e:
291 tb = traceback.format_exc()
292 self._cconn.send((e, tb))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500293
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800294 def update(self):
295 if self._pconn.poll():
296 (e, tb) = self._pconn.recv()
297 if e is not None:
298 self._exception = (e, tb)
299 else:
300 self._result = tb
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500301
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800302 @property
303 def exception(self):
304 self.update()
305 return self._exception
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500306
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800307 @property
308 def result(self):
309 self.update()
310 return self._result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500311
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800312 launched = []
313 errors = []
314 results = []
315 items = list(items)
316 while (items and not errors) or launched:
317 if not errors and items and len(launched) < max_process:
318 args = (items.pop(),)
319 if extraargs is not None:
320 args = args + extraargs
321 p = ProcessLaunch(target=target, args=args)
322 p.start()
323 launched.append(p)
324 for q in launched:
Brad Bishop19323692019-04-05 15:28:33 -0400325 # Have to manually call update() to avoid deadlocks. The pipe can be full and
326 # transfer stalled until we try and read the results object but the subprocess won't exit
327 # as it still has data to write (https://bugs.python.org/issue8426)
328 q.update()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800329 # The finished processes are joined when calling is_alive()
330 if not q.is_alive():
331 if q.exception:
332 errors.append(q.exception)
333 if q.result:
334 results.append(q.result)
335 launched.remove(q)
336 # Paranoia doesn't hurt
337 for p in launched:
338 p.join()
339 if errors:
340 msg = ""
341 for (e, tb) in errors:
Brad Bishopc342db32019-05-15 21:57:59 -0400342 if isinstance(e, subprocess.CalledProcessError) and e.output:
343 msg = msg + str(e) + "\n"
344 msg = msg + "Subprocess output:"
345 msg = msg + e.output.decode("utf-8", errors="ignore")
346 else:
347 msg = msg + str(e) + ": " + str(tb) + "\n"
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800348 bb.fatal("Fatal errors occurred in subprocesses:\n%s" % msg)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500349 return results
350
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500351def squashspaces(string):
352 import re
Brad Bishop19323692019-04-05 15:28:33 -0400353 return re.sub(r"\s+", " ", string).strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500354
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500355def rprovides_map(pkgdata_dir, pkg_dict):
356 # Map file -> pkg provider
357 rprov_map = {}
358
359 for pkg in pkg_dict:
360 path_to_pkgfile = os.path.join(pkgdata_dir, 'runtime-reverse', pkg)
361 if not os.path.isfile(path_to_pkgfile):
362 continue
363 with open(path_to_pkgfile) as f:
364 for line in f:
365 if line.startswith('RPROVIDES') or line.startswith('FILERPROVIDES'):
366 # List all components provided by pkg.
367 # Exclude version strings, i.e. those starting with (
368 provides = [x for x in line.split()[1:] if not x.startswith('(')]
369 for prov in provides:
370 if prov in rprov_map:
371 rprov_map[prov].append(pkg)
372 else:
373 rprov_map[prov] = [pkg]
374
375 return rprov_map
376
377def format_pkg_list(pkg_dict, ret_format=None, pkgdata_dir=None):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500378 output = []
379
380 if ret_format == "arch":
381 for pkg in sorted(pkg_dict):
382 output.append("%s %s" % (pkg, pkg_dict[pkg]["arch"]))
383 elif ret_format == "file":
384 for pkg in sorted(pkg_dict):
385 output.append("%s %s %s" % (pkg, pkg_dict[pkg]["filename"], pkg_dict[pkg]["arch"]))
386 elif ret_format == "ver":
387 for pkg in sorted(pkg_dict):
388 output.append("%s %s %s" % (pkg, pkg_dict[pkg]["arch"], pkg_dict[pkg]["ver"]))
389 elif ret_format == "deps":
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500390 rprov_map = rprovides_map(pkgdata_dir, pkg_dict)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500391 for pkg in sorted(pkg_dict):
392 for dep in pkg_dict[pkg]["deps"]:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500393 if dep in rprov_map:
394 # There could be multiple providers within the image
395 for pkg_provider in rprov_map[dep]:
396 output.append("%s|%s * %s [RPROVIDES]" % (pkg, pkg_provider, dep))
397 else:
398 output.append("%s|%s" % (pkg, dep))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500399 else:
400 for pkg in sorted(pkg_dict):
401 output.append(pkg)
402
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800403 output_str = '\n'.join(output)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500404
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800405 if output_str:
406 # make sure last line is newline terminated
407 output_str += '\n'
408
409 return output_str
410
Andrew Geissler82c905d2020-04-13 13:39:40 -0500411
412# Helper function to get the host compiler version
413# Do not assume the compiler is gcc
414def get_host_compiler_version(d, taskcontextonly=False):
415 import re, subprocess
416
417 if taskcontextonly and d.getVar('BB_WORKERCONTEXT') != '1':
418 return
419
420 compiler = d.getVar("BUILD_CC")
421 # Get rid of ccache since it is not present when parsing.
422 if compiler.startswith('ccache '):
423 compiler = compiler[7:]
424 try:
425 env = os.environ.copy()
426 # datastore PATH does not contain session PATH as set by environment-setup-...
427 # this breaks the install-buildtools use-case
428 # env["PATH"] = d.getVar("PATH")
429 output = subprocess.check_output("%s --version" % compiler, \
430 shell=True, env=env, stderr=subprocess.STDOUT).decode("utf-8")
431 except subprocess.CalledProcessError as e:
432 bb.fatal("Error running %s --version: %s" % (compiler, e.output.decode("utf-8")))
433
434 match = re.match(r".* (\d+\.\d+)\.\d+.*", output.split('\n')[0])
435 if not match:
436 bb.fatal("Can't get compiler version from %s --version output" % compiler)
437
438 version = match.group(1)
439 return compiler, version
440
441
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800442def host_gcc_version(d, taskcontextonly=False):
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500443 import re, subprocess
444
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800445 if taskcontextonly and d.getVar('BB_WORKERCONTEXT') != '1':
446 return
447
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500448 compiler = d.getVar("BUILD_CC")
Brad Bishop19323692019-04-05 15:28:33 -0400449 # Get rid of ccache since it is not present when parsing.
450 if compiler.startswith('ccache '):
451 compiler = compiler[7:]
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500452 try:
453 env = os.environ.copy()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500454 env["PATH"] = d.getVar("PATH")
Brad Bishop19323692019-04-05 15:28:33 -0400455 output = subprocess.check_output("%s --version" % compiler, \
456 shell=True, env=env, stderr=subprocess.STDOUT).decode("utf-8")
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500457 except subprocess.CalledProcessError as e:
458 bb.fatal("Error running %s --version: %s" % (compiler, e.output.decode("utf-8")))
459
Andrew Geissler82c905d2020-04-13 13:39:40 -0500460 match = re.match(r".* (\d+\.\d+)\.\d+.*", output.split('\n')[0])
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500461 if not match:
462 bb.fatal("Can't get compiler version from %s --version output" % compiler)
463
464 version = match.group(1)
465 return "-%s" % version if version in ("4.8", "4.9") else ""
466
Brad Bishop316dfdd2018-06-25 12:45:53 -0400467
468def get_multilib_datastore(variant, d):
469 localdata = bb.data.createCopy(d)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800470 if variant:
471 overrides = localdata.getVar("OVERRIDES", False) + ":virtclass-multilib-" + variant
472 localdata.setVar("OVERRIDES", overrides)
473 localdata.setVar("MLPREFIX", variant + "-")
474 else:
475 origdefault = localdata.getVar("DEFAULTTUNE_MULTILIB_ORIGINAL")
476 if origdefault:
477 localdata.setVar("DEFAULTTUNE", origdefault)
478 overrides = localdata.getVar("OVERRIDES", False).split(":")
479 overrides = ":".join([x for x in overrides if not x.startswith("virtclass-multilib-")])
480 localdata.setVar("OVERRIDES", overrides)
481 localdata.setVar("MLPREFIX", "")
Brad Bishop316dfdd2018-06-25 12:45:53 -0400482 return localdata
483
Brad Bishop08902b02019-08-20 09:16:51 -0400484class ImageQAFailed(Exception):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600485 def __init__(self, description, name=None, logfile=None):
486 self.description = description
487 self.name = name
488 self.logfile=logfile
489
490 def __str__(self):
491 msg = 'Function failed: %s' % self.name
492 if self.description:
493 msg = msg + ' (%s)' % self.description
494
495 return msg
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800496
Brad Bishop19323692019-04-05 15:28:33 -0400497def sh_quote(string):
498 import shlex
499 return shlex.quote(string)
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500500
501def directory_size(root, blocksize=4096):
502 """
503 Calculate the size of the directory, taking into account hard links,
504 rounding up every size to multiples of the blocksize.
505 """
506 def roundup(size):
507 """
508 Round the size up to the nearest multiple of the block size.
509 """
510 import math
511 return math.ceil(size / blocksize) * blocksize
512
513 def getsize(filename):
514 """
515 Get the size of the filename, not following symlinks, taking into
516 account hard links.
517 """
518 stat = os.lstat(filename)
519 if stat.st_ino not in inodes:
520 inodes.add(stat.st_ino)
521 return stat.st_size
522 else:
523 return 0
524
525 inodes = set()
526 total = 0
527 for root, dirs, files in os.walk(root):
528 total += sum(roundup(getsize(os.path.join(root, name))) for name in files)
529 total += roundup(getsize(root))
530 return total