blob: d11da978d728e9592688c108e90fb3c27ce4daa7 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001"""
2BitBake Utility Functions
3"""
4
5# Copyright (C) 2004 Michael Lauer
6#
Brad Bishopc342db32019-05-15 21:57:59 -04007# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -05008#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05009
10import re, fcntl, os, string, stat, shutil, time
11import sys
12import errno
13import logging
14import bb
15import bb.msg
16import multiprocessing
17import fcntl
Brad Bishop19323692019-04-05 15:28:33 -040018import importlib
Andrew Geissler595f6302022-01-24 19:11:47 +000019import importlib.machinery
20import importlib.util
Patrick Williamsc0f7c042017-02-23 20:41:17 -060021import itertools
Patrick Williamsc124f4f2015-09-15 14:41:29 -050022import subprocess
23import glob
24import fnmatch
25import traceback
26import errno
27import signal
Patrick Williamsc0f7c042017-02-23 20:41:17 -060028import collections
29import copy
Andrew Geissler595f6302022-01-24 19:11:47 +000030import ctypes
Patrick Williamsc0f7c042017-02-23 20:41:17 -060031from subprocess import getstatusoutput
Patrick Williamsc124f4f2015-09-15 14:41:29 -050032from contextlib import contextmanager
33from ctypes import cdll
34
Patrick Williamsc124f4f2015-09-15 14:41:29 -050035logger = logging.getLogger("BitBake.Util")
Brad Bishop19323692019-04-05 15:28:33 -040036python_extensions = importlib.machinery.all_suffixes()
Patrick Williamsc0f7c042017-02-23 20:41:17 -060037
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038
39def clean_context():
40 return {
41 "os": os,
42 "bb": bb,
43 "time": time,
44 }
45
46def get_context():
47 return _context
48
49
50def set_context(ctx):
51 _context = ctx
52
53# Context used in better_exec, eval
54_context = clean_context()
55
56class VersionStringException(Exception):
57 """Exception raised when an invalid version specification is found"""
58
59def explode_version(s):
60 r = []
Brad Bishop19323692019-04-05 15:28:33 -040061 alpha_regexp = re.compile(r'^([a-zA-Z]+)(.*)$')
62 numeric_regexp = re.compile(r'^(\d+)(.*)$')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050063 while (s != ''):
64 if s[0] in string.digits:
65 m = numeric_regexp.match(s)
66 r.append((0, int(m.group(1))))
67 s = m.group(2)
68 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -060069 if s[0] in string.ascii_letters:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050070 m = alpha_regexp.match(s)
71 r.append((1, m.group(1)))
72 s = m.group(2)
73 continue
74 if s[0] == '~':
75 r.append((-1, s[0]))
76 else:
77 r.append((2, s[0]))
78 s = s[1:]
79 return r
80
81def split_version(s):
82 """Split a version string into its constituent parts (PE, PV, PR)"""
83 s = s.strip(" <>=")
84 e = 0
85 if s.count(':'):
86 e = int(s.split(":")[0])
87 s = s.split(":")[1]
88 r = ""
89 if s.count('-'):
90 r = s.rsplit("-", 1)[1]
91 s = s.rsplit("-", 1)[0]
92 v = s
93 return (e, v, r)
94
95def vercmp_part(a, b):
96 va = explode_version(a)
97 vb = explode_version(b)
98 while True:
99 if va == []:
100 (oa, ca) = (0, None)
101 else:
102 (oa, ca) = va.pop(0)
103 if vb == []:
104 (ob, cb) = (0, None)
105 else:
106 (ob, cb) = vb.pop(0)
107 if (oa, ca) == (0, None) and (ob, cb) == (0, None):
108 return 0
109 if oa < ob:
110 return -1
111 elif oa > ob:
112 return 1
Brad Bishop19323692019-04-05 15:28:33 -0400113 elif ca is None:
114 return -1
115 elif cb is None:
116 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500117 elif ca < cb:
118 return -1
119 elif ca > cb:
120 return 1
121
122def vercmp(ta, tb):
123 (ea, va, ra) = ta
124 (eb, vb, rb) = tb
125
126 r = int(ea or 0) - int(eb or 0)
127 if (r == 0):
128 r = vercmp_part(va, vb)
129 if (r == 0):
130 r = vercmp_part(ra, rb)
131 return r
132
133def vercmp_string(a, b):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600134 """ Split version strings and compare them """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500135 ta = split_version(a)
136 tb = split_version(b)
137 return vercmp(ta, tb)
138
139def vercmp_string_op(a, b, op):
140 """
141 Compare two versions and check if the specified comparison operator matches the result of the comparison.
142 This function is fairly liberal about what operators it will accept since there are a variety of styles
143 depending on the context.
144 """
145 res = vercmp_string(a, b)
146 if op in ('=', '=='):
147 return res == 0
148 elif op == '<=':
149 return res <= 0
150 elif op == '>=':
151 return res >= 0
152 elif op in ('>', '>>'):
153 return res > 0
154 elif op in ('<', '<<'):
155 return res < 0
156 elif op == '!=':
157 return res != 0
158 else:
159 raise VersionStringException('Unsupported comparison operator "%s"' % op)
160
161def explode_deps(s):
162 """
163 Take an RDEPENDS style string of format:
164 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
165 and return a list of dependencies.
166 Version information is ignored.
167 """
168 r = []
169 l = s.split()
170 flag = False
171 for i in l:
172 if i[0] == '(':
173 flag = True
174 #j = []
175 if not flag:
176 r.append(i)
177 #else:
178 # j.append(i)
179 if flag and i.endswith(')'):
180 flag = False
181 # Ignore version
182 #r[-1] += ' ' + ' '.join(j)
183 return r
184
Brad Bishop316dfdd2018-06-25 12:45:53 -0400185def explode_dep_versions2(s, *, sort=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500186 """
187 Take an RDEPENDS style string of format:
188 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
189 and return a dictionary of dependencies and versions.
190 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600191 r = collections.OrderedDict()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500192 l = s.replace(",", "").split()
193 lastdep = None
194 lastcmp = ""
195 lastver = ""
196 incmp = False
197 inversion = False
198 for i in l:
199 if i[0] == '(':
200 incmp = True
201 i = i[1:].strip()
202 if not i:
203 continue
204
205 if incmp:
206 incmp = False
207 inversion = True
208 # This list is based on behavior and supported comparisons from deb, opkg and rpm.
209 #
210 # Even though =<, <<, ==, !=, =>, and >> may not be supported,
211 # we list each possibly valid item.
212 # The build system is responsible for validation of what it supports.
213 if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')):
214 lastcmp = i[0:2]
215 i = i[2:]
216 elif i.startswith(('<', '>', '=')):
217 lastcmp = i[0:1]
218 i = i[1:]
219 else:
220 # This is an unsupported case!
221 raise VersionStringException('Invalid version specification in "(%s" - invalid or missing operator' % i)
222 lastcmp = (i or "")
223 i = ""
224 i.strip()
225 if not i:
226 continue
227
228 if inversion:
229 if i.endswith(')'):
230 i = i[:-1] or ""
231 inversion = False
232 if lastver and i:
233 lastver += " "
234 if i:
235 lastver += i
236 if lastdep not in r:
237 r[lastdep] = []
238 r[lastdep].append(lastcmp + " " + lastver)
239 continue
240
241 #if not inversion:
242 lastdep = i
243 lastver = ""
244 lastcmp = ""
245 if not (i in r and r[i]):
246 r[lastdep] = []
247
Brad Bishop316dfdd2018-06-25 12:45:53 -0400248 if sort:
249 r = collections.OrderedDict(sorted(r.items(), key=lambda x: x[0]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500250 return r
251
252def explode_dep_versions(s):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600253 """
254 Take an RDEPENDS style string of format:
255 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000256 skip null value and items appeared in dependency string multiple times
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600257 and return a dictionary of dependencies and versions.
258 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500259 r = explode_dep_versions2(s)
260 for d in r:
261 if not r[d]:
262 r[d] = None
263 continue
264 if len(r[d]) > 1:
265 bb.warn("explode_dep_versions(): Item %s appeared in dependency string '%s' multiple times with different values. explode_dep_versions cannot cope with this." % (d, s))
266 r[d] = r[d][0]
267 return r
268
269def join_deps(deps, commasep=True):
270 """
271 Take the result from explode_dep_versions and generate a dependency string
272 """
273 result = []
274 for dep in deps:
275 if deps[dep]:
276 if isinstance(deps[dep], list):
277 for v in deps[dep]:
278 result.append(dep + " (" + v + ")")
279 else:
280 result.append(dep + " (" + deps[dep] + ")")
281 else:
282 result.append(dep)
283 if commasep:
284 return ", ".join(result)
285 else:
286 return " ".join(result)
287
288def _print_trace(body, line):
289 """
290 Print the Environment of a Text Body
291 """
292 error = []
293 # print the environment of the method
294 min_line = max(1, line-4)
295 max_line = min(line + 4, len(body))
296 for i in range(min_line, max_line + 1):
297 if line == i:
298 error.append(' *** %.4d:%s' % (i, body[i-1].rstrip()))
299 else:
300 error.append(' %.4d:%s' % (i, body[i-1].rstrip()))
301 return error
302
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500303def better_compile(text, file, realfile, mode = "exec", lineno = 0):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500304 """
305 A better compile method. This method
306 will print the offending lines.
307 """
308 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500309 cache = bb.methodpool.compile_cache(text)
310 if cache:
311 return cache
312 # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though
313 text2 = "\n" * int(lineno) + text
314 code = compile(text2, realfile, mode)
315 bb.methodpool.compile_cache_add(text, code)
316 return code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500317 except Exception as e:
318 error = []
319 # split the text into lines again
320 body = text.split('\n')
Brad Bishop19323692019-04-05 15:28:33 -0400321 error.append("Error in compiling python function in %s, line %s:\n" % (realfile, e.lineno))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500322 if hasattr(e, "lineno"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500323 error.append("The code lines resulting in this error were:")
Brad Bishop19323692019-04-05 15:28:33 -0400324 # e.lineno: line's position in reaflile
325 # lineno: function name's "position -1" in realfile
326 # e.lineno - lineno: line's relative position in function
327 error.extend(_print_trace(body, e.lineno - lineno))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500328 else:
329 error.append("The function causing this error was:")
330 for line in body:
331 error.append(line)
332 error.append("%s: %s" % (e.__class__.__name__, str(e)))
333
334 logger.error("\n".join(error))
335
336 e = bb.BBHandledException(e)
337 raise e
338
339def _print_exception(t, value, tb, realfile, text, context):
340 error = []
341 try:
342 exception = traceback.format_exception_only(t, value)
343 error.append('Error executing a python function in %s:\n' % realfile)
344
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500345 # Strip 'us' from the stack (better_exec call) unless that was where the
346 # error came from
347 if tb.tb_next is not None:
348 tb = tb.tb_next
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500349
350 textarray = text.split('\n')
351
352 linefailed = tb.tb_lineno
353
354 tbextract = traceback.extract_tb(tb)
355 tbformat = traceback.format_list(tbextract)
356 error.append("The stack trace of python calls that resulted in this exception/failure was:")
357 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2]))
358 error.extend(_print_trace(textarray, linefailed))
359
360 # See if this is a function we constructed and has calls back into other functions in
361 # "text". If so, try and improve the context of the error by diving down the trace
362 level = 0
363 nexttb = tb.tb_next
364 while nexttb is not None and (level+1) < len(tbextract):
365 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]))
366 if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]:
367 # The code was possibly in the string we compiled ourselves
368 error.extend(_print_trace(textarray, tbextract[level+1][1]))
369 elif tbextract[level+1][0].startswith("/"):
370 # The code looks like it might be in a file, try and load it
371 try:
372 with open(tbextract[level+1][0], "r") as f:
373 text = f.readlines()
374 error.extend(_print_trace(text, tbextract[level+1][1]))
375 except:
376 error.append(tbformat[level+1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500377 else:
378 error.append(tbformat[level+1])
379 nexttb = tb.tb_next
380 level = level + 1
381
382 error.append("Exception: %s" % ''.join(exception))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600383
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000384 # If the exception is from spawning a task, let's be helpful and display
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600385 # the output (which hopefully includes stderr).
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500386 if isinstance(value, subprocess.CalledProcessError) and value.output:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600387 error.append("Subprocess output:")
388 error.append(value.output.decode("utf-8", errors="ignore"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500389 finally:
390 logger.error("\n".join(error))
391
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500392def better_exec(code, context, text = None, realfile = "<code>", pythonexception=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500393 """
394 Similiar to better_compile, better_exec will
395 print the lines that are responsible for the
396 error.
397 """
398 import bb.parse
399 if not text:
400 text = code
401 if not hasattr(code, "co_filename"):
402 code = better_compile(code, realfile, realfile)
403 try:
404 exec(code, get_context(), context)
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000405 except (bb.BBHandledException, bb.parse.SkipRecipe, bb.data_smart.ExpansionError, bb.process.ExecutionError):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500406 # Error already shown so passthrough, no need for traceback
407 raise
408 except Exception as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500409 if pythonexception:
410 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500411 (t, value, tb) = sys.exc_info()
412 try:
413 _print_exception(t, value, tb, realfile, text, context)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500414 except Exception as e2:
415 logger.error("Exception handler error: %s" % str(e2))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500416
417 e = bb.BBHandledException(e)
418 raise e
419
420def simple_exec(code, context):
421 exec(code, get_context(), context)
422
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600423def better_eval(source, locals, extraglobals = None):
424 ctx = get_context()
425 if extraglobals:
426 ctx = copy.copy(ctx)
427 for g in extraglobals:
428 ctx[g] = extraglobals[g]
429 return eval(source, ctx, locals)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500430
431@contextmanager
432def fileslocked(files):
433 """Context manager for locking and unlocking file locks."""
434 locks = []
435 if files:
436 for lockfile in files:
437 locks.append(bb.utils.lockfile(lockfile))
438
Andrew Geissler82c905d2020-04-13 13:39:40 -0500439 try:
440 yield
441 finally:
442 for lock in locks:
443 bb.utils.unlockfile(lock)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500444
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500445def lockfile(name, shared=False, retry=True, block=False):
446 """
447 Use the specified file as a lock file, return when the lock has
448 been acquired. Returns a variable to pass to unlockfile().
449 Parameters:
450 retry: True to re-try locking if it fails, False otherwise
451 block: True to block until the lock succeeds, False otherwise
452 The retry and block parameters are kind of equivalent unless you
453 consider the possibility of sending a signal to the process to break
454 out - at which point you want block=True rather than retry=True.
455 """
Andrew Geissler9aee5002022-03-30 16:27:02 +0000456 basename = os.path.basename(name)
457 if len(basename) > 255:
458 root, ext = os.path.splitext(basename)
459 basename = root[:255 - len(ext)] + ext
Andrew Geissler595f6302022-01-24 19:11:47 +0000460
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500461 dirname = os.path.dirname(name)
462 mkdirhier(dirname)
463
Andrew Geissler9aee5002022-03-30 16:27:02 +0000464 name = os.path.join(dirname, basename)
465
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500466 if not os.access(dirname, os.W_OK):
467 logger.error("Unable to acquire lock '%s', directory is not writable",
468 name)
469 sys.exit(1)
470
471 op = fcntl.LOCK_EX
472 if shared:
473 op = fcntl.LOCK_SH
474 if not retry and not block:
475 op = op | fcntl.LOCK_NB
476
477 while True:
478 # If we leave the lockfiles lying around there is no problem
479 # but we should clean up after ourselves. This gives potential
480 # for races though. To work around this, when we acquire the lock
481 # we check the file we locked was still the lock file on disk.
482 # by comparing inode numbers. If they don't match or the lockfile
483 # no longer exists, we start again.
484
485 # This implementation is unfair since the last person to request the
486 # lock is the most likely to win it.
487
488 try:
489 lf = open(name, 'a+')
490 fileno = lf.fileno()
491 fcntl.flock(fileno, op)
492 statinfo = os.fstat(fileno)
493 if os.path.exists(lf.name):
494 statinfo2 = os.stat(lf.name)
495 if statinfo.st_ino == statinfo2.st_ino:
496 return lf
497 lf.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800498 except OSError as e:
Andrew Geissler595f6302022-01-24 19:11:47 +0000499 if e.errno == errno.EACCES or e.errno == errno.ENAMETOOLONG:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800500 logger.error("Unable to acquire lock '%s', %s",
501 e.strerror, name)
502 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500503 try:
504 lf.close()
505 except Exception:
506 pass
507 pass
508 if not retry:
509 return None
510
511def unlockfile(lf):
512 """
513 Unlock a file locked using lockfile()
514 """
515 try:
516 # If we had a shared lock, we need to promote to exclusive before
517 # removing the lockfile. Attempt this, ignore failures.
518 fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
519 os.unlink(lf.name)
520 except (IOError, OSError):
521 pass
522 fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
523 lf.close()
524
Brad Bishop6dbb3162019-11-25 09:41:34 -0500525def _hasher(method, filename):
526 import mmap
527
528 with open(filename, "rb") as f:
529 try:
530 with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
531 for chunk in iter(lambda: mm.read(8192), b''):
532 method.update(chunk)
533 except ValueError:
534 # You can't mmap() an empty file so silence this exception
535 pass
536 return method.hexdigest()
537
538
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500539def md5_file(filename):
540 """
541 Return the hex string representation of the MD5 checksum of filename.
542 """
Brad Bishop6dbb3162019-11-25 09:41:34 -0500543 import hashlib
Andrew Geissler9aee5002022-03-30 16:27:02 +0000544 return _hasher(hashlib.new('MD5', usedforsecurity=False), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500545
546def sha256_file(filename):
547 """
548 Return the hex string representation of the 256-bit SHA checksum of
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500549 filename.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500550 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500551 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500552 return _hasher(hashlib.sha256(), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500553
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500554def sha1_file(filename):
555 """
556 Return the hex string representation of the SHA1 checksum of the filename
557 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500558 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500559 return _hasher(hashlib.sha1(), filename)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500560
Andrew Geissler82c905d2020-04-13 13:39:40 -0500561def sha384_file(filename):
562 """
563 Return the hex string representation of the SHA384 checksum of the filename
564 """
565 import hashlib
566 return _hasher(hashlib.sha384(), filename)
567
568def sha512_file(filename):
569 """
570 Return the hex string representation of the SHA512 checksum of the filename
571 """
572 import hashlib
573 return _hasher(hashlib.sha512(), filename)
574
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500575def preserved_envvars_exported():
576 """Variables which are taken from the environment and placed in and exported
577 from the metadata"""
578 return [
579 'BB_TASKHASH',
580 'HOME',
581 'LOGNAME',
582 'PATH',
583 'PWD',
584 'SHELL',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500585 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600586 'LC_ALL',
587 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500588 ]
589
590def preserved_envvars():
591 """Variables which are taken from the environment and placed in the metadata"""
592 v = [
593 'BBPATH',
594 'BB_PRESERVE_ENV',
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000595 'BB_ENV_PASSTHROUGH',
596 'BB_ENV_PASSTHROUGH_ADDITIONS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500597 ]
598 return v + preserved_envvars_exported()
599
600def filter_environment(good_vars):
601 """
602 Create a pristine environment for bitbake. This will remove variables that
603 are not known and may influence the build in a negative way.
604 """
605
606 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600607 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500608 if key in good_vars:
609 continue
610
611 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500612 del os.environ[key]
613
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600614 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
615 # access methods will use ascii. You can't change that mode once the interpreter is
616 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
617 # distros support that and we need to set something.
618 os.environ["LC_ALL"] = "en_US.UTF-8"
619
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500620 if removed_vars:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600621 logger.debug("Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500622
623 return removed_vars
624
625def approved_variables():
626 """
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000627 Determine and return the list of variables which are approved
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500628 to remain in the environment.
629 """
630 if 'BB_PRESERVE_ENV' in os.environ:
631 return os.environ.keys()
632 approved = []
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000633 if 'BB_ENV_PASSTHROUGH' in os.environ:
634 approved = os.environ['BB_ENV_PASSTHROUGH'].split()
635 approved.extend(['BB_ENV_PASSTHROUGH'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500636 else:
637 approved = preserved_envvars()
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000638 if 'BB_ENV_PASSTHROUGH_ADDITIONS' in os.environ:
639 approved.extend(os.environ['BB_ENV_PASSTHROUGH_ADDITIONS'].split())
640 if 'BB_ENV_PASSTHROUGH_ADDITIONS' not in approved:
641 approved.extend(['BB_ENV_PASSTHROUGH_ADDITIONS'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500642 return approved
643
644def clean_environment():
645 """
646 Clean up any spurious environment variables. This will remove any
647 variables the user hasn't chosen to preserve.
648 """
649 if 'BB_PRESERVE_ENV' not in os.environ:
650 good_vars = approved_variables()
651 return filter_environment(good_vars)
652
653 return {}
654
655def empty_environment():
656 """
657 Remove all variables from the environment.
658 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600659 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500660 os.unsetenv(s)
661 del os.environ[s]
662
663def build_environment(d):
664 """
665 Build an environment from all exported variables.
666 """
667 import bb.data
668 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500669 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500670 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500671 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500672
673def _check_unsafe_delete_path(path):
674 """
675 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
676 the caller should raise an exception with an appropriate message.
677 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
678 with potentially disastrous results.
679 """
680 extra = ''
681 # HOME might not be /home/something, so in case we can get it, check against it
682 homedir = os.environ.get('HOME', '')
683 if homedir:
684 extra = '|%s' % homedir
685 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
686 return True
687 return False
688
Brad Bishopa34c0302019-09-23 22:34:48 -0400689def remove(path, recurse=False, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500690 """Equivalent to rm -f or rm -rf"""
691 if not path:
692 return
693 if recurse:
694 for name in glob.glob(path):
695 if _check_unsafe_delete_path(path):
696 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % path)
697 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishopa34c0302019-09-23 22:34:48 -0400698 cmd = []
699 if ionice:
700 cmd = ['ionice', '-c', '3']
701 subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500702 return
703 for name in glob.glob(path):
704 try:
705 os.unlink(name)
706 except OSError as exc:
707 if exc.errno != errno.ENOENT:
708 raise
709
Brad Bishopa34c0302019-09-23 22:34:48 -0400710def prunedir(topdir, ionice=False):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600711 """ Delete everything reachable from the directory named in 'topdir'. """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500712 # CAUTION: This is dangerous!
713 if _check_unsafe_delete_path(topdir):
714 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
Brad Bishopa34c0302019-09-23 22:34:48 -0400715 remove(topdir, recurse=True, ionice=ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500716
717#
718# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
719# but thats possibly insane and suffixes is probably going to be small
720#
721def prune_suffix(var, suffixes, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600722 """
723 See if var ends with any of the suffixes listed and
724 remove it if found
725 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500726 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -0400727 if suffix and var.endswith(suffix):
728 return var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500729 return var
730
731def mkdirhier(directory):
732 """Create a directory like 'mkdir -p', but does not complain if
733 directory already exists like os.makedirs
734 """
735
736 try:
737 os.makedirs(directory)
738 except OSError as e:
Brad Bishopc342db32019-05-15 21:57:59 -0400739 if e.errno != errno.EEXIST or not os.path.isdir(directory):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500740 raise e
741
742def movefile(src, dest, newmtime = None, sstat = None):
743 """Moves a file from src to dest, preserving all permissions and
744 attributes; mtime will be preserved even when moving across
745 filesystems. Returns true on success and false on failure. Move is
746 atomic.
747 """
748
749 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
750 try:
751 if not sstat:
752 sstat = os.lstat(src)
753 except Exception as e:
754 print("movefile: Stating source file failed...", e)
755 return None
756
757 destexists = 1
758 try:
759 dstat = os.lstat(dest)
760 except:
761 dstat = os.lstat(os.path.dirname(dest))
762 destexists = 0
763
764 if destexists:
765 if stat.S_ISLNK(dstat[stat.ST_MODE]):
766 try:
767 os.unlink(dest)
768 destexists = 0
769 except Exception as e:
770 pass
771
772 if stat.S_ISLNK(sstat[stat.ST_MODE]):
773 try:
774 target = os.readlink(src)
775 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
776 os.unlink(dest)
777 os.symlink(target, dest)
778 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
779 os.unlink(src)
780 return os.lstat(dest)
781 except Exception as e:
782 print("movefile: failed to properly create symlink:", dest, "->", target, e)
783 return None
784
785 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500786 # os.rename needs to know the dest path ending with file name
787 # so append the file name to a path only if it's a dir specified
788 srcfname = os.path.basename(src)
789 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
790 else dest
791
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500792 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
793 try:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500794 bb.utils.rename(src, destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500795 renamefailed = 0
796 except Exception as e:
Brad Bishop79641f22019-09-10 07:20:22 -0400797 if e.errno != errno.EXDEV:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500798 # Some random error.
799 print("movefile: Failed to move", src, "to", dest, e)
800 return None
801 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
802
803 if renamefailed:
804 didcopy = 0
805 if stat.S_ISREG(sstat[stat.ST_MODE]):
806 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500807 shutil.copyfile(src, destpath + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500808 bb.utils.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500809 didcopy = 1
810 except Exception as e:
811 print('movefile: copy', src, '->', dest, 'failed.', e)
812 return None
813 else:
814 #we don't yet handle special, so we need to fall back to /bin/mv
815 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
816 if a[0] != 0:
817 print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
818 return None # failure
819 try:
820 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400821 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
822 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500823 os.unlink(src)
824 except Exception as e:
825 print("movefile: Failed to chown/chmod/unlink", dest, e)
826 return None
827
828 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500829 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500830 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500831 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500832 newmtime = sstat[stat.ST_MTIME]
833 return newmtime
834
835def copyfile(src, dest, newmtime = None, sstat = None):
836 """
837 Copies a file from src to dest, preserving all permissions and
838 attributes; mtime will be preserved even when moving across
839 filesystems. Returns true on success and false on failure.
840 """
841 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
842 try:
843 if not sstat:
844 sstat = os.lstat(src)
845 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600846 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500847 return False
848
849 destexists = 1
850 try:
851 dstat = os.lstat(dest)
852 except:
853 dstat = os.lstat(os.path.dirname(dest))
854 destexists = 0
855
856 if destexists:
857 if stat.S_ISLNK(dstat[stat.ST_MODE]):
858 try:
859 os.unlink(dest)
860 destexists = 0
861 except Exception as e:
862 pass
863
864 if stat.S_ISLNK(sstat[stat.ST_MODE]):
865 try:
866 target = os.readlink(src)
867 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
868 os.unlink(dest)
869 os.symlink(target, dest)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500870 os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500871 return os.lstat(dest)
872 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600873 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500874 return False
875
876 if stat.S_ISREG(sstat[stat.ST_MODE]):
877 try:
878 srcchown = False
879 if not os.access(src, os.R_OK):
880 # Make sure we can read it
881 srcchown = True
882 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
883
884 # For safety copy then move it over.
885 shutil.copyfile(src, dest + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500886 bb.utils.rename(dest + "#new", dest)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500887 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600888 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500889 return False
890 finally:
891 if srcchown:
892 os.chmod(src, sstat[stat.ST_MODE])
893 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
894
895 else:
896 #we don't yet handle special, so we need to fall back to /bin/mv
897 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
898 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600899 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500900 return False # failure
901 try:
902 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
903 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
904 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600905 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500906 return False
907
908 if newmtime:
909 os.utime(dest, (newmtime, newmtime))
910 else:
911 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
912 newmtime = sstat[stat.ST_MTIME]
913 return newmtime
914
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800915def break_hardlinks(src, sstat = None):
916 """
917 Ensures src is the only hardlink to this file. Other hardlinks,
918 if any, are not affected (other than in their st_nlink value, of
919 course). Returns true on success and false on failure.
920
921 """
922 try:
923 if not sstat:
924 sstat = os.lstat(src)
925 except Exception as e:
926 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
927 return False
928 if sstat[stat.ST_NLINK] == 1:
929 return True
930 return copyfile(src, src, sstat=sstat)
931
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500932def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500933 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500934 Locate `item` in the list of paths `path` (colon separated string like $PATH).
935 If `direction` is non-zero then the list is reversed.
936 If `history` is True then the list of candidates also returned as result,history.
937 If `executable` is True then the candidate has to be an executable file,
938 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500939 """
940
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500941 if executable:
942 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
943 else:
944 is_candidate = lambda p: os.path.exists(p)
945
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500946 hist = []
947 paths = (path or "").split(':')
948 if direction != 0:
949 paths.reverse()
950
951 for p in paths:
952 next = os.path.join(p, item)
953 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500954 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500955 if not os.path.isabs(next):
956 next = os.path.abspath(next)
957 if history:
958 return next, hist
959 return next
960
961 if history:
962 return "", hist
963 return ""
964
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500965@contextmanager
966def umask(new_mask):
967 """
968 Context manager to set the umask to a specific mask, and restore it afterwards.
969 """
970 current_mask = os.umask(new_mask)
971 try:
972 yield
973 finally:
974 os.umask(current_mask)
975
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500976def to_boolean(string, default=None):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600977 """
978 Check input string and return boolean value True/False/None
979 depending upon the checks
980 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500981 if not string:
982 return default
983
984 normalized = string.lower()
985 if normalized in ("y", "yes", "1", "true"):
986 return True
987 elif normalized in ("n", "no", "0", "false"):
988 return False
989 else:
990 raise ValueError("Invalid value for to_boolean: %s" % string)
991
992def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500993 """Check if a variable contains all the values specified.
994
995 Arguments:
996
997 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500998 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500999
1000 checkvalues -- if this is a string it is split on whitespace into a set(),
1001 otherwise coerced directly into a set().
1002
1003 truevalue -- the value to return if checkvalues is a subset of variable.
1004
1005 falsevalue -- the value to return if variable is empty or if checkvalues is
1006 not a subset of variable.
1007
1008 d -- the data store.
1009 """
1010
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001011 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001012 if not val:
1013 return falsevalue
1014 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001015 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001016 checkvalues = set(checkvalues.split())
1017 else:
1018 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001019 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001020 return truevalue
1021 return falsevalue
1022
1023def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001024 """Check if a variable contains any values specified.
1025
1026 Arguments:
1027
1028 variable -- the variable name. This will be fetched and expanded (using
1029 d.getVar(variable)) and then split into a set().
1030
1031 checkvalues -- if this is a string it is split on whitespace into a set(),
1032 otherwise coerced directly into a set().
1033
1034 truevalue -- the value to return if checkvalues is a subset of variable.
1035
1036 falsevalue -- the value to return if variable is empty or if checkvalues is
1037 not a subset of variable.
1038
1039 d -- the data store.
1040 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001041 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001042 if not val:
1043 return falsevalue
1044 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001045 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001046 checkvalues = set(checkvalues.split())
1047 else:
1048 checkvalues = set(checkvalues)
1049 if checkvalues & val:
1050 return truevalue
1051 return falsevalue
1052
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001053def filter(variable, checkvalues, d):
1054 """Return all words in the variable that are present in the checkvalues.
1055
1056 Arguments:
1057
1058 variable -- the variable name. This will be fetched and expanded (using
1059 d.getVar(variable)) and then split into a set().
1060
1061 checkvalues -- if this is a string it is split on whitespace into a set(),
1062 otherwise coerced directly into a set().
1063
1064 d -- the data store.
1065 """
1066
1067 val = d.getVar(variable)
1068 if not val:
1069 return ''
1070 val = set(val.split())
1071 if isinstance(checkvalues, str):
1072 checkvalues = set(checkvalues.split())
1073 else:
1074 checkvalues = set(checkvalues)
1075 return ' '.join(sorted(checkvalues & val))
1076
Andrew Geissler82c905d2020-04-13 13:39:40 -05001077
1078def get_referenced_vars(start_expr, d):
1079 """
1080 :return: names of vars referenced in start_expr (recursively), in quasi-BFS order (variables within the same level
1081 are ordered arbitrarily)
1082 """
1083
1084 seen = set()
1085 ret = []
1086
1087 # The first entry in the queue is the unexpanded start expression
1088 queue = collections.deque([start_expr])
1089 # Subsequent entries will be variable names, so we need to track whether or not entry requires getVar
1090 is_first = True
1091
1092 empty_data = bb.data.init()
1093 while queue:
1094 entry = queue.popleft()
1095 if is_first:
1096 # Entry is the start expression - no expansion needed
1097 is_first = False
1098 expression = entry
1099 else:
1100 # This is a variable name - need to get the value
1101 expression = d.getVar(entry, False)
1102 ret.append(entry)
1103
1104 # expandWithRefs is how we actually get the referenced variables in the expression. We call it using an empty
1105 # data store because we only want the variables directly used in the expression. It returns a set, which is what
1106 # dooms us to only ever be "quasi-BFS" rather than full BFS.
1107 new_vars = empty_data.expandWithRefs(expression, None).references - set(seen)
1108
1109 queue.extend(new_vars)
1110 seen.update(new_vars)
1111 return ret
1112
1113
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001114def cpu_count():
1115 return multiprocessing.cpu_count()
1116
1117def nonblockingfd(fd):
1118 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1119
1120def process_profilelog(fn, pout = None):
1121 # Either call with a list of filenames and set pout or a filename and optionally pout.
1122 if not pout:
1123 pout = fn + '.processed'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001124
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001125 with open(pout, 'w') as pout:
1126 import pstats
1127 if isinstance(fn, list):
1128 p = pstats.Stats(*fn, stream=pout)
1129 else:
1130 p = pstats.Stats(fn, stream=pout)
1131 p.sort_stats('time')
1132 p.print_stats()
1133 p.print_callers()
1134 p.sort_stats('cumulative')
1135 p.print_stats()
1136
1137 pout.flush()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001138
1139#
1140# Was present to work around multiprocessing pool bugs in python < 2.7.3
1141#
1142def multiprocessingpool(*args, **kwargs):
1143
1144 import multiprocessing.pool
1145 #import multiprocessing.util
1146 #multiprocessing.util.log_to_stderr(10)
1147 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1148 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1149 def wrapper(func):
1150 def wrap(self, timeout=None):
1151 return func(self, timeout=timeout if timeout is not None else 1e100)
1152 return wrap
1153 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1154
1155 return multiprocessing.Pool(*args, **kwargs)
1156
1157def exec_flat_python_func(func, *args, **kwargs):
1158 """Execute a flat python function (defined with def funcname(args):...)"""
1159 # Prepare a small piece of python code which calls the requested function
1160 # To do this we need to prepare two things - a set of variables we can use to pass
1161 # the values of arguments into the calling function, and the list of arguments for
1162 # the function being called
1163 context = {}
1164 funcargs = []
1165 # Handle unnamed arguments
1166 aidx = 1
1167 for arg in args:
1168 argname = 'arg_%s' % aidx
1169 context[argname] = arg
1170 funcargs.append(argname)
1171 aidx += 1
1172 # Handle keyword arguments
1173 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001174 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001175 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1176 comp = bb.utils.better_compile(code, '<string>', '<string>')
1177 bb.utils.better_exec(comp, context, code, '<string>')
1178 return context['retval']
1179
1180def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1181 """Edit lines from a recipe or config file and modify one or more
1182 specified variable values set in the file using a specified callback
1183 function. Lines are expected to have trailing newlines.
1184 Parameters:
1185 meta_lines: lines from the file; can be a list or an iterable
1186 (e.g. file pointer)
1187 variables: a list of variable names to look for. Functions
1188 may also be specified, but must be specified with '()' at
1189 the end of the name. Note that the function doesn't have
Patrick Williams213cb262021-08-07 19:21:33 -05001190 any intrinsic understanding of :append, :prepend, :remove,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001191 or overrides, so these are considered as part of the name.
1192 These values go into a regular expression, so regular
1193 expression syntax is allowed.
1194 varfunc: callback function called for every variable matching
1195 one of the entries in the variables parameter. The function
1196 should take four arguments:
1197 varname: name of variable matched
1198 origvalue: current value in file
1199 op: the operator (e.g. '+=')
1200 newlines: list of lines up to this point. You can use
1201 this to prepend lines before this variable setting
1202 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001203 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001204 newvalue: new value to substitute in, or None to drop
1205 the variable setting entirely. (If the removal
1206 results in two consecutive blank lines, one of the
1207 blank lines will also be dropped).
1208 newop: the operator to use - if you specify None here,
1209 the original operation will be used.
1210 indent: number of spaces to indent multi-line entries,
1211 or -1 to indent up to the level of the assignment
1212 and opening quote, or a string to use as the indent.
1213 minbreak: True to allow the first element of a
1214 multi-line value to continue on the same line as
1215 the assignment, False to indent before the first
1216 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001217 To clarify, if you wish not to change the value, then you
1218 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001219 match_overrides: True to match items with _overrides on the end,
1220 False otherwise
1221 Returns a tuple:
1222 updated:
1223 True if changes were made, False otherwise.
1224 newlines:
1225 Lines after processing
1226 """
1227
1228 var_res = {}
1229 if match_overrides:
Brad Bishop19323692019-04-05 15:28:33 -04001230 override_re = r'(_[a-zA-Z0-9-_$(){}]+)?'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001231 else:
1232 override_re = ''
1233 for var in variables:
1234 if var.endswith('()'):
Brad Bishop19323692019-04-05 15:28:33 -04001235 var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001236 else:
Brad Bishop19323692019-04-05 15:28:33 -04001237 var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001238
1239 updated = False
1240 varset_start = ''
1241 varlines = []
1242 newlines = []
1243 in_var = None
1244 full_value = ''
1245 var_end = ''
1246
1247 def handle_var_end():
1248 prerun_newlines = newlines[:]
1249 op = varset_start[len(in_var):].strip()
1250 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1251 changed = (prerun_newlines != newlines)
1252
1253 if newvalue is None:
1254 # Drop the value
1255 return True
1256 elif newvalue != full_value or (newop not in [None, op]):
1257 if newop not in [None, op]:
1258 # Callback changed the operator
1259 varset_new = "%s %s" % (in_var, newop)
1260 else:
1261 varset_new = varset_start
1262
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001263 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001264 if indent == -1:
1265 indentspc = ' ' * (len(varset_new) + 2)
1266 else:
1267 indentspc = ' ' * indent
1268 else:
1269 indentspc = indent
1270 if in_var.endswith('()'):
1271 # A function definition
1272 if isinstance(newvalue, list):
1273 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1274 else:
1275 if not newvalue.startswith('\n'):
1276 newvalue = '\n' + newvalue
1277 if not newvalue.endswith('\n'):
1278 newvalue = newvalue + '\n'
1279 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1280 else:
1281 # Normal variable
1282 if isinstance(newvalue, list):
1283 if not newvalue:
1284 # Empty list -> empty string
1285 newlines.append('%s ""\n' % varset_new)
1286 elif minbreak:
1287 # First item on first line
1288 if len(newvalue) == 1:
1289 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1290 else:
1291 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1292 for item in newvalue[1:]:
1293 newlines.append('%s%s \\\n' % (indentspc, item))
1294 newlines.append('%s"\n' % indentspc)
1295 else:
1296 # No item on first line
1297 newlines.append('%s " \\\n' % varset_new)
1298 for item in newvalue:
1299 newlines.append('%s%s \\\n' % (indentspc, item))
1300 newlines.append('%s"\n' % indentspc)
1301 else:
1302 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1303 return True
1304 else:
1305 # Put the old lines back where they were
1306 newlines.extend(varlines)
1307 # If newlines was touched by the function, we'll need to return True
1308 return changed
1309
1310 checkspc = False
1311
1312 for line in meta_lines:
1313 if in_var:
1314 value = line.rstrip()
1315 varlines.append(line)
1316 if in_var.endswith('()'):
1317 full_value += '\n' + value
1318 else:
1319 full_value += value[:-1]
1320 if value.endswith(var_end):
1321 if in_var.endswith('()'):
1322 if full_value.count('{') - full_value.count('}') >= 0:
1323 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001324 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001325 if handle_var_end():
1326 updated = True
1327 checkspc = True
1328 in_var = None
1329 else:
1330 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001331 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001332 res = var_re.match(line)
1333 if res:
1334 isfunc = varname.endswith('()')
1335 if isfunc:
1336 splitvalue = line.split('{', 1)
1337 var_end = '}'
1338 else:
1339 var_end = res.groups()[-1]
1340 splitvalue = line.split(var_end, 1)
1341 varset_start = splitvalue[0].rstrip()
1342 value = splitvalue[1].rstrip()
1343 if not isfunc and value.endswith('\\'):
1344 value = value[:-1]
1345 full_value = value
1346 varlines = [line]
1347 in_var = res.group(1)
1348 if isfunc:
1349 in_var += '()'
1350 if value.endswith(var_end):
1351 full_value = full_value[:-1]
1352 if handle_var_end():
1353 updated = True
1354 checkspc = True
1355 in_var = None
1356 skip = True
1357 break
1358 if not skip:
1359 if checkspc:
1360 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001361 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001362 # Squash blank line if there are two consecutive blanks after a removal
1363 continue
1364 newlines.append(line)
1365 return (updated, newlines)
1366
1367
1368def edit_metadata_file(meta_file, variables, varfunc):
1369 """Edit a recipe or config file and modify one or more specified
1370 variable values set in the file using a specified callback function.
1371 The file is only written to if the value(s) actually change.
1372 This is basically the file version of edit_metadata(), see that
1373 function's description for parameter/usage information.
1374 Returns True if the file was written to, False otherwise.
1375 """
1376 with open(meta_file, 'r') as f:
1377 (updated, newlines) = edit_metadata(f, variables, varfunc)
1378 if updated:
1379 with open(meta_file, 'w') as f:
1380 f.writelines(newlines)
1381 return updated
1382
1383
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001384def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001385 """Edit bblayers.conf, adding and/or removing layers
1386 Parameters:
1387 bblayers_conf: path to bblayers.conf file to edit
1388 add: layer path (or list of layer paths) to add; None or empty
1389 list to add nothing
1390 remove: layer path (or list of layer paths) to remove; None or
1391 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001392 edit_cb: optional callback function that will be called after
1393 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001394 Returns a tuple:
1395 notadded: list of layers specified to be added but weren't
1396 (because they were already in the list)
1397 notremoved: list of layers that were specified to be removed
1398 but weren't (because they weren't in the list)
1399 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001400
1401 import fnmatch
1402
1403 def remove_trailing_sep(pth):
1404 if pth and pth[-1] == os.sep:
1405 pth = pth[:-1]
1406 return pth
1407
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001408 approved = bb.utils.approved_variables()
1409 def canonicalise_path(pth):
1410 pth = remove_trailing_sep(pth)
1411 if 'HOME' in approved and '~' in pth:
1412 pth = os.path.expanduser(pth)
1413 return pth
1414
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001415 def layerlist_param(value):
1416 if not value:
1417 return []
1418 elif isinstance(value, list):
1419 return [remove_trailing_sep(x) for x in value]
1420 else:
1421 return [remove_trailing_sep(value)]
1422
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001423 addlayers = layerlist_param(add)
1424 removelayers = layerlist_param(remove)
1425
1426 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1427 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001428 removed = []
1429 plusequals = False
1430 orig_bblayers = []
1431
1432 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1433 bblayercalls.append(op)
1434 if op == '=':
1435 del orig_bblayers[:]
1436 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1437 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001438
1439 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001440 updated = False
1441 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1442 if removelayers:
1443 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001444 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001445 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001446 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001447 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001448 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001449 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001450 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001451 for addlayer in addlayers:
1452 if addlayer not in bblayers:
1453 updated = True
1454 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001455 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001456
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001457 if edit_cb:
1458 newlist = []
1459 for layer in bblayers:
1460 res = edit_cb(layer, canonicalise_path(layer))
1461 if res != layer:
1462 newlist.append(res)
1463 updated = True
1464 else:
1465 newlist.append(layer)
1466 bblayers = newlist
1467
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001468 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001469 if op == '+=' and not bblayers:
1470 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001471 return (bblayers, None, 2, False)
1472 else:
1473 return (origvalue, None, 2, False)
1474
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001475 with open(bblayers_conf, 'r') as f:
1476 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001477
1478 if not bblayercalls:
1479 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1480
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001481 # Try to do the "smart" thing depending on how the user has laid out
1482 # their bblayers.conf file
1483 if bblayercalls.count('+=') > 1:
1484 plusequals = True
1485
1486 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1487 notadded = []
1488 for layer in addlayers:
1489 layer_canon = canonicalise_path(layer)
1490 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1491 notadded.append(layer)
1492 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1493 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1494
1495 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1496 if addlayers:
1497 # Still need to add these
1498 for addlayer in addlayers:
1499 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1500 updated = True
1501
1502 if updated:
1503 with open(bblayers_conf, 'w') as f:
1504 f.writelines(newlines)
1505
1506 notremoved = list(set(removelayers) - set(removed))
1507
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001508 return (notadded, notremoved)
1509
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001510def get_collection_res(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001511 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001512 collection_res = {}
1513 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001514 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001515
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001516 return collection_res
1517
1518
1519def get_file_layer(filename, d, collection_res={}):
1520 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
1521 if not collection_res:
1522 collection_res = get_collection_res(d)
1523
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001524 def path_to_layer(path):
1525 # Use longest path so we handle nested layers
1526 matchlen = 0
1527 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001528 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001529 if len(regex) > matchlen and re.match(regex, path):
1530 matchlen = len(regex)
1531 match = collection
1532 return match
1533
1534 result = None
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001535 bbfiles = (d.getVar('BBFILES_PRIORITIZED') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001536 bbfilesmatch = False
1537 for bbfilesentry in bbfiles:
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001538 if fnmatch.fnmatchcase(filename, bbfilesentry):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001539 bbfilesmatch = True
1540 result = path_to_layer(bbfilesentry)
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001541 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001542
1543 if not bbfilesmatch:
1544 # Probably a bbclass
1545 result = path_to_layer(filename)
1546
1547 return result
1548
1549
1550# Constant taken from http://linux.die.net/include/linux/prctl.h
1551PR_SET_PDEATHSIG = 1
1552
1553class PrCtlError(Exception):
1554 pass
1555
1556def signal_on_parent_exit(signame):
1557 """
1558 Trigger signame to be sent when the parent process dies
1559 """
1560 signum = getattr(signal, signame)
1561 # http://linux.die.net/man/2/prctl
1562 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1563 if result != 0:
1564 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001565
1566#
1567# Manually call the ioprio syscall. We could depend on other libs like psutil
1568# however this gets us enough of what we need to bitbake for now without the
1569# dependency
1570#
1571_unamearch = os.uname()[4]
1572IOPRIO_WHO_PROCESS = 1
1573IOPRIO_CLASS_SHIFT = 13
1574
1575def ioprio_set(who, cls, value):
1576 NR_ioprio_set = None
1577 if _unamearch == "x86_64":
1578 NR_ioprio_set = 251
1579 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1580 NR_ioprio_set = 289
Brad Bishop19323692019-04-05 15:28:33 -04001581 elif _unamearch == "aarch64":
1582 NR_ioprio_set = 30
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001583
1584 if NR_ioprio_set:
1585 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1586 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1587 if rc != 0:
1588 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1589 else:
1590 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001591
1592def set_process_name(name):
1593 from ctypes import cdll, byref, create_string_buffer
1594 # This is nice to have for debugging, not essential
1595 try:
1596 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001597 buf = create_string_buffer(bytes(name, 'utf-8'))
1598 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001599 except:
1600 pass
1601
Andrew Geissler595f6302022-01-24 19:11:47 +00001602def disable_network(uid=None, gid=None):
1603 """
1604 Disable networking in the current process if the kernel supports it, else
1605 just return after logging to debug. To do this we need to create a new user
1606 namespace, then map back to the original uid/gid.
1607 """
1608 libc = ctypes.CDLL('libc.so.6')
1609
1610 # From sched.h
1611 # New user namespace
1612 CLONE_NEWUSER = 0x10000000
1613 # New network namespace
1614 CLONE_NEWNET = 0x40000000
1615
1616 if uid is None:
1617 uid = os.getuid()
1618 if gid is None:
1619 gid = os.getgid()
1620
1621 ret = libc.unshare(CLONE_NEWNET | CLONE_NEWUSER)
1622 if ret != 0:
1623 logger.debug("System doesn't suport disabling network without admin privs")
1624 return
1625 with open("/proc/self/uid_map", "w") as f:
1626 f.write("%s %s 1" % (uid, uid))
1627 with open("/proc/self/setgroups", "w") as f:
1628 f.write("deny")
1629 with open("/proc/self/gid_map", "w") as f:
1630 f.write("%s %s 1" % (gid, gid))
1631
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001632def export_proxies(d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001633 """ export common proxies variables from datastore to environment """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001634 import os
1635
1636 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001637 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1638 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001639 exported = False
1640
1641 for v in variables:
1642 if v in os.environ.keys():
1643 exported = True
1644 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001645 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001646 if v_proxy is not None:
1647 os.environ[v] = v_proxy
1648 exported = True
1649
1650 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001651
1652
1653def load_plugins(logger, plugins, pluginpath):
1654 def load_plugin(name):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001655 logger.debug('Loading plugin %s' % name)
Brad Bishop19323692019-04-05 15:28:33 -04001656 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1657 if spec:
Andrew Geissler595f6302022-01-24 19:11:47 +00001658 mod = importlib.util.module_from_spec(spec)
1659 spec.loader.exec_module(mod)
1660 return mod
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001661
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001662 logger.debug('Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001663
1664 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1665 for ext in python_extensions)
1666 files = itertools.chain.from_iterable(expanded)
1667 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1668 for name in names:
1669 if name != '__init__':
1670 plugin = load_plugin(name)
1671 if hasattr(plugin, 'plugin_init'):
1672 obj = plugin.plugin_init(plugins)
1673 plugins.append(obj or plugin)
1674 else:
1675 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001676
1677
1678class LogCatcher(logging.Handler):
1679 """Logging handler for collecting logged messages so you can check them later"""
1680 def __init__(self):
1681 self.messages = []
1682 logging.Handler.__init__(self, logging.WARNING)
1683 def emit(self, record):
1684 self.messages.append(bb.build.logformatter.format(record))
1685 def contains(self, message):
1686 return (message in self.messages)
Andrew Geissler82c905d2020-04-13 13:39:40 -05001687
1688def is_semver(version):
1689 """
1690 Is the version string following the semver semantic?
1691
1692 https://semver.org/spec/v2.0.0.html
1693 """
1694 regex = re.compile(
1695 r"""
1696 ^
1697 (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
1698 (?:-(
1699 (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
1700 (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
1701 ))?
1702 (?:\+(
1703 [0-9a-zA-Z-]+
1704 (?:\.[0-9a-zA-Z-]+)*
1705 ))?
1706 $
1707 """, re.VERBOSE)
1708
1709 if regex.match(version) is None:
1710 return False
1711
1712 return True
Andrew Geisslerc926e172021-05-07 16:11:35 -05001713
1714# Wrapper around os.rename which can handle cross device problems
1715# e.g. from container filesystems
1716def rename(src, dst):
1717 try:
1718 os.rename(src, dst)
1719 except OSError as err:
1720 if err.errno == 18:
1721 # Invalid cross-device link error
1722 shutil.move(src, dst)
1723 else:
1724 raise err
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001725
1726@contextmanager
1727def environment(**envvars):
1728 """
1729 Context manager to selectively update the environment with the specified mapping.
1730 """
1731 backup = dict(os.environ)
1732 try:
1733 os.environ.update(envvars)
1734 yield
1735 finally:
1736 for var in envvars:
1737 if var in backup:
1738 os.environ[var] = backup[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001739 elif var in os.environ:
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001740 del os.environ[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001741
1742def is_local_uid(uid=''):
1743 """
1744 Check whether uid is a local one or not.
1745 Can't use pwd module since it gets all UIDs, not local ones only.
1746 """
1747 if not uid:
1748 uid = os.getuid()
1749 with open('/etc/passwd', 'r') as f:
1750 for line in f:
1751 line_split = line.split(':')
1752 if len(line_split) < 3:
1753 continue
1754 if str(uid) == line_split[2]:
1755 return True
1756 return False