blob: 69ca8987f3d6309ecca4a1b894de495e88a7ebd6 [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
261 for cmd in cmds.strip().split(';'):
262 cmd = cmd.strip()
263 if cmd != '':
264 bb.note("Executing %s ..." % cmd)
265 bb.build.exec_func(cmd, d)
266
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800267# For each item in items, call the function 'target' with item as the first
268# argument, extraargs as the other arguments and handle any exceptions in the
269# parent thread
270def multiprocess_launch(target, items, d, extraargs=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500271
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800272 class ProcessLaunch(multiprocessing.Process):
273 def __init__(self, *args, **kwargs):
274 multiprocessing.Process.__init__(self, *args, **kwargs)
275 self._pconn, self._cconn = multiprocessing.Pipe()
276 self._exception = None
277 self._result = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500278
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800279 def run(self):
280 try:
281 ret = self._target(*self._args, **self._kwargs)
282 self._cconn.send((None, ret))
283 except Exception as e:
284 tb = traceback.format_exc()
285 self._cconn.send((e, tb))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500286
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800287 def update(self):
288 if self._pconn.poll():
289 (e, tb) = self._pconn.recv()
290 if e is not None:
291 self._exception = (e, tb)
292 else:
293 self._result = tb
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500294
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800295 @property
296 def exception(self):
297 self.update()
298 return self._exception
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500299
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800300 @property
301 def result(self):
302 self.update()
303 return self._result
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500304
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800305 max_process = int(d.getVar("BB_NUMBER_THREADS") or os.cpu_count() or 1)
306 launched = []
307 errors = []
308 results = []
309 items = list(items)
310 while (items and not errors) or launched:
311 if not errors and items and len(launched) < max_process:
312 args = (items.pop(),)
313 if extraargs is not None:
314 args = args + extraargs
315 p = ProcessLaunch(target=target, args=args)
316 p.start()
317 launched.append(p)
318 for q in launched:
Brad Bishop19323692019-04-05 15:28:33 -0400319 # Have to manually call update() to avoid deadlocks. The pipe can be full and
320 # transfer stalled until we try and read the results object but the subprocess won't exit
321 # as it still has data to write (https://bugs.python.org/issue8426)
322 q.update()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800323 # The finished processes are joined when calling is_alive()
324 if not q.is_alive():
325 if q.exception:
326 errors.append(q.exception)
327 if q.result:
328 results.append(q.result)
329 launched.remove(q)
330 # Paranoia doesn't hurt
331 for p in launched:
332 p.join()
333 if errors:
334 msg = ""
335 for (e, tb) in errors:
Brad Bishopc342db32019-05-15 21:57:59 -0400336 if isinstance(e, subprocess.CalledProcessError) and e.output:
337 msg = msg + str(e) + "\n"
338 msg = msg + "Subprocess output:"
339 msg = msg + e.output.decode("utf-8", errors="ignore")
340 else:
341 msg = msg + str(e) + ": " + str(tb) + "\n"
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800342 bb.fatal("Fatal errors occurred in subprocesses:\n%s" % msg)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500343 return results
344
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500345def squashspaces(string):
346 import re
Brad Bishop19323692019-04-05 15:28:33 -0400347 return re.sub(r"\s+", " ", string).strip()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500348
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500349def rprovides_map(pkgdata_dir, pkg_dict):
350 # Map file -> pkg provider
351 rprov_map = {}
352
353 for pkg in pkg_dict:
354 path_to_pkgfile = os.path.join(pkgdata_dir, 'runtime-reverse', pkg)
355 if not os.path.isfile(path_to_pkgfile):
356 continue
357 with open(path_to_pkgfile) as f:
358 for line in f:
359 if line.startswith('RPROVIDES') or line.startswith('FILERPROVIDES'):
360 # List all components provided by pkg.
361 # Exclude version strings, i.e. those starting with (
362 provides = [x for x in line.split()[1:] if not x.startswith('(')]
363 for prov in provides:
364 if prov in rprov_map:
365 rprov_map[prov].append(pkg)
366 else:
367 rprov_map[prov] = [pkg]
368
369 return rprov_map
370
371def format_pkg_list(pkg_dict, ret_format=None, pkgdata_dir=None):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500372 output = []
373
374 if ret_format == "arch":
375 for pkg in sorted(pkg_dict):
376 output.append("%s %s" % (pkg, pkg_dict[pkg]["arch"]))
377 elif ret_format == "file":
378 for pkg in sorted(pkg_dict):
379 output.append("%s %s %s" % (pkg, pkg_dict[pkg]["filename"], pkg_dict[pkg]["arch"]))
380 elif ret_format == "ver":
381 for pkg in sorted(pkg_dict):
382 output.append("%s %s %s" % (pkg, pkg_dict[pkg]["arch"], pkg_dict[pkg]["ver"]))
383 elif ret_format == "deps":
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500384 rprov_map = rprovides_map(pkgdata_dir, pkg_dict)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500385 for pkg in sorted(pkg_dict):
386 for dep in pkg_dict[pkg]["deps"]:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500387 if dep in rprov_map:
388 # There could be multiple providers within the image
389 for pkg_provider in rprov_map[dep]:
390 output.append("%s|%s * %s [RPROVIDES]" % (pkg, pkg_provider, dep))
391 else:
392 output.append("%s|%s" % (pkg, dep))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500393 else:
394 for pkg in sorted(pkg_dict):
395 output.append(pkg)
396
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800397 output_str = '\n'.join(output)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500398
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800399 if output_str:
400 # make sure last line is newline terminated
401 output_str += '\n'
402
403 return output_str
404
Andrew Geissler82c905d2020-04-13 13:39:40 -0500405
406# Helper function to get the host compiler version
407# Do not assume the compiler is gcc
408def get_host_compiler_version(d, taskcontextonly=False):
409 import re, subprocess
410
411 if taskcontextonly and d.getVar('BB_WORKERCONTEXT') != '1':
412 return
413
414 compiler = d.getVar("BUILD_CC")
415 # Get rid of ccache since it is not present when parsing.
416 if compiler.startswith('ccache '):
417 compiler = compiler[7:]
418 try:
419 env = os.environ.copy()
420 # datastore PATH does not contain session PATH as set by environment-setup-...
421 # this breaks the install-buildtools use-case
422 # env["PATH"] = d.getVar("PATH")
423 output = subprocess.check_output("%s --version" % compiler, \
424 shell=True, env=env, stderr=subprocess.STDOUT).decode("utf-8")
425 except subprocess.CalledProcessError as e:
426 bb.fatal("Error running %s --version: %s" % (compiler, e.output.decode("utf-8")))
427
428 match = re.match(r".* (\d+\.\d+)\.\d+.*", output.split('\n')[0])
429 if not match:
430 bb.fatal("Can't get compiler version from %s --version output" % compiler)
431
432 version = match.group(1)
433 return compiler, version
434
435
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800436def host_gcc_version(d, taskcontextonly=False):
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500437 import re, subprocess
438
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800439 if taskcontextonly and d.getVar('BB_WORKERCONTEXT') != '1':
440 return
441
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500442 compiler = d.getVar("BUILD_CC")
Brad Bishop19323692019-04-05 15:28:33 -0400443 # Get rid of ccache since it is not present when parsing.
444 if compiler.startswith('ccache '):
445 compiler = compiler[7:]
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500446 try:
447 env = os.environ.copy()
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500448 env["PATH"] = d.getVar("PATH")
Brad Bishop19323692019-04-05 15:28:33 -0400449 output = subprocess.check_output("%s --version" % compiler, \
450 shell=True, env=env, stderr=subprocess.STDOUT).decode("utf-8")
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500451 except subprocess.CalledProcessError as e:
452 bb.fatal("Error running %s --version: %s" % (compiler, e.output.decode("utf-8")))
453
Andrew Geissler82c905d2020-04-13 13:39:40 -0500454 match = re.match(r".* (\d+\.\d+)\.\d+.*", output.split('\n')[0])
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500455 if not match:
456 bb.fatal("Can't get compiler version from %s --version output" % compiler)
457
458 version = match.group(1)
459 return "-%s" % version if version in ("4.8", "4.9") else ""
460
Brad Bishop316dfdd2018-06-25 12:45:53 -0400461
462def get_multilib_datastore(variant, d):
463 localdata = bb.data.createCopy(d)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800464 if variant:
465 overrides = localdata.getVar("OVERRIDES", False) + ":virtclass-multilib-" + variant
466 localdata.setVar("OVERRIDES", overrides)
467 localdata.setVar("MLPREFIX", variant + "-")
468 else:
469 origdefault = localdata.getVar("DEFAULTTUNE_MULTILIB_ORIGINAL")
470 if origdefault:
471 localdata.setVar("DEFAULTTUNE", origdefault)
472 overrides = localdata.getVar("OVERRIDES", False).split(":")
473 overrides = ":".join([x for x in overrides if not x.startswith("virtclass-multilib-")])
474 localdata.setVar("OVERRIDES", overrides)
475 localdata.setVar("MLPREFIX", "")
Brad Bishop316dfdd2018-06-25 12:45:53 -0400476 return localdata
477
Brad Bishop08902b02019-08-20 09:16:51 -0400478class ImageQAFailed(Exception):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600479 def __init__(self, description, name=None, logfile=None):
480 self.description = description
481 self.name = name
482 self.logfile=logfile
483
484 def __str__(self):
485 msg = 'Function failed: %s' % self.name
486 if self.description:
487 msg = msg + ' (%s)' % self.description
488
489 return msg
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800490
Brad Bishop19323692019-04-05 15:28:33 -0400491def sh_quote(string):
492 import shlex
493 return shlex.quote(string)
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500494
495def directory_size(root, blocksize=4096):
496 """
497 Calculate the size of the directory, taking into account hard links,
498 rounding up every size to multiples of the blocksize.
499 """
500 def roundup(size):
501 """
502 Round the size up to the nearest multiple of the block size.
503 """
504 import math
505 return math.ceil(size / blocksize) * blocksize
506
507 def getsize(filename):
508 """
509 Get the size of the filename, not following symlinks, taking into
510 account hard links.
511 """
512 stat = os.lstat(filename)
513 if stat.st_ino not in inodes:
514 inodes.add(stat.st_ino)
515 return stat.st_size
516 else:
517 return 0
518
519 inodes = set()
520 total = 0
521 for root, dirs, files in os.walk(root):
522 total += sum(roundup(getsize(os.path.join(root, name))) for name in files)
523 total += roundup(getsize(root))
524 return total