blob: 2e8256101a1dfa2de8423bcbb75aedc0fc12ac07 [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 Geissler595f6302022-01-24 19:11:47 +0000456 if len(name) > 255:
457 root, ext = os.path.splitext(name)
458 name = root[:255 - len(ext)] + ext
459
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500460 dirname = os.path.dirname(name)
461 mkdirhier(dirname)
462
463 if not os.access(dirname, os.W_OK):
464 logger.error("Unable to acquire lock '%s', directory is not writable",
465 name)
466 sys.exit(1)
467
468 op = fcntl.LOCK_EX
469 if shared:
470 op = fcntl.LOCK_SH
471 if not retry and not block:
472 op = op | fcntl.LOCK_NB
473
474 while True:
475 # If we leave the lockfiles lying around there is no problem
476 # but we should clean up after ourselves. This gives potential
477 # for races though. To work around this, when we acquire the lock
478 # we check the file we locked was still the lock file on disk.
479 # by comparing inode numbers. If they don't match or the lockfile
480 # no longer exists, we start again.
481
482 # This implementation is unfair since the last person to request the
483 # lock is the most likely to win it.
484
485 try:
486 lf = open(name, 'a+')
487 fileno = lf.fileno()
488 fcntl.flock(fileno, op)
489 statinfo = os.fstat(fileno)
490 if os.path.exists(lf.name):
491 statinfo2 = os.stat(lf.name)
492 if statinfo.st_ino == statinfo2.st_ino:
493 return lf
494 lf.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800495 except OSError as e:
Andrew Geissler595f6302022-01-24 19:11:47 +0000496 if e.errno == errno.EACCES or e.errno == errno.ENAMETOOLONG:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800497 logger.error("Unable to acquire lock '%s', %s",
498 e.strerror, name)
499 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500500 try:
501 lf.close()
502 except Exception:
503 pass
504 pass
505 if not retry:
506 return None
507
508def unlockfile(lf):
509 """
510 Unlock a file locked using lockfile()
511 """
512 try:
513 # If we had a shared lock, we need to promote to exclusive before
514 # removing the lockfile. Attempt this, ignore failures.
515 fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
516 os.unlink(lf.name)
517 except (IOError, OSError):
518 pass
519 fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
520 lf.close()
521
Brad Bishop6dbb3162019-11-25 09:41:34 -0500522def _hasher(method, filename):
523 import mmap
524
525 with open(filename, "rb") as f:
526 try:
527 with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
528 for chunk in iter(lambda: mm.read(8192), b''):
529 method.update(chunk)
530 except ValueError:
531 # You can't mmap() an empty file so silence this exception
532 pass
533 return method.hexdigest()
534
535
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500536def md5_file(filename):
537 """
538 Return the hex string representation of the MD5 checksum of filename.
539 """
Brad Bishop6dbb3162019-11-25 09:41:34 -0500540 import hashlib
541 return _hasher(hashlib.md5(), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500542
543def sha256_file(filename):
544 """
545 Return the hex string representation of the 256-bit SHA checksum of
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500546 filename.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500547 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500548 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500549 return _hasher(hashlib.sha256(), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500550
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500551def sha1_file(filename):
552 """
553 Return the hex string representation of the SHA1 checksum of the filename
554 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500555 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500556 return _hasher(hashlib.sha1(), filename)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500557
Andrew Geissler82c905d2020-04-13 13:39:40 -0500558def sha384_file(filename):
559 """
560 Return the hex string representation of the SHA384 checksum of the filename
561 """
562 import hashlib
563 return _hasher(hashlib.sha384(), filename)
564
565def sha512_file(filename):
566 """
567 Return the hex string representation of the SHA512 checksum of the filename
568 """
569 import hashlib
570 return _hasher(hashlib.sha512(), filename)
571
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500572def preserved_envvars_exported():
573 """Variables which are taken from the environment and placed in and exported
574 from the metadata"""
575 return [
576 'BB_TASKHASH',
577 'HOME',
578 'LOGNAME',
579 'PATH',
580 'PWD',
581 'SHELL',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500582 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600583 'LC_ALL',
584 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500585 ]
586
587def preserved_envvars():
588 """Variables which are taken from the environment and placed in the metadata"""
589 v = [
590 'BBPATH',
591 'BB_PRESERVE_ENV',
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000592 'BB_ENV_PASSTHROUGH',
593 'BB_ENV_PASSTHROUGH_ADDITIONS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594 ]
595 return v + preserved_envvars_exported()
596
597def filter_environment(good_vars):
598 """
599 Create a pristine environment for bitbake. This will remove variables that
600 are not known and may influence the build in a negative way.
601 """
602
603 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600604 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500605 if key in good_vars:
606 continue
607
608 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500609 del os.environ[key]
610
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600611 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
612 # access methods will use ascii. You can't change that mode once the interpreter is
613 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
614 # distros support that and we need to set something.
615 os.environ["LC_ALL"] = "en_US.UTF-8"
616
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500617 if removed_vars:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600618 logger.debug("Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500619
620 return removed_vars
621
622def approved_variables():
623 """
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000624 Determine and return the list of variables which are approved
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500625 to remain in the environment.
626 """
627 if 'BB_PRESERVE_ENV' in os.environ:
628 return os.environ.keys()
629 approved = []
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000630 if 'BB_ENV_PASSTHROUGH' in os.environ:
631 approved = os.environ['BB_ENV_PASSTHROUGH'].split()
632 approved.extend(['BB_ENV_PASSTHROUGH'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500633 else:
634 approved = preserved_envvars()
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000635 if 'BB_ENV_PASSTHROUGH_ADDITIONS' in os.environ:
636 approved.extend(os.environ['BB_ENV_PASSTHROUGH_ADDITIONS'].split())
637 if 'BB_ENV_PASSTHROUGH_ADDITIONS' not in approved:
638 approved.extend(['BB_ENV_PASSTHROUGH_ADDITIONS'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500639 return approved
640
641def clean_environment():
642 """
643 Clean up any spurious environment variables. This will remove any
644 variables the user hasn't chosen to preserve.
645 """
646 if 'BB_PRESERVE_ENV' not in os.environ:
647 good_vars = approved_variables()
648 return filter_environment(good_vars)
649
650 return {}
651
652def empty_environment():
653 """
654 Remove all variables from the environment.
655 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600656 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500657 os.unsetenv(s)
658 del os.environ[s]
659
660def build_environment(d):
661 """
662 Build an environment from all exported variables.
663 """
664 import bb.data
665 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500666 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500668 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500669
670def _check_unsafe_delete_path(path):
671 """
672 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
673 the caller should raise an exception with an appropriate message.
674 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
675 with potentially disastrous results.
676 """
677 extra = ''
678 # HOME might not be /home/something, so in case we can get it, check against it
679 homedir = os.environ.get('HOME', '')
680 if homedir:
681 extra = '|%s' % homedir
682 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
683 return True
684 return False
685
Brad Bishopa34c0302019-09-23 22:34:48 -0400686def remove(path, recurse=False, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500687 """Equivalent to rm -f or rm -rf"""
688 if not path:
689 return
690 if recurse:
691 for name in glob.glob(path):
692 if _check_unsafe_delete_path(path):
693 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % path)
694 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishopa34c0302019-09-23 22:34:48 -0400695 cmd = []
696 if ionice:
697 cmd = ['ionice', '-c', '3']
698 subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500699 return
700 for name in glob.glob(path):
701 try:
702 os.unlink(name)
703 except OSError as exc:
704 if exc.errno != errno.ENOENT:
705 raise
706
Brad Bishopa34c0302019-09-23 22:34:48 -0400707def prunedir(topdir, ionice=False):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600708 """ Delete everything reachable from the directory named in 'topdir'. """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500709 # CAUTION: This is dangerous!
710 if _check_unsafe_delete_path(topdir):
711 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
Brad Bishopa34c0302019-09-23 22:34:48 -0400712 remove(topdir, recurse=True, ionice=ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500713
714#
715# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
716# but thats possibly insane and suffixes is probably going to be small
717#
718def prune_suffix(var, suffixes, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600719 """
720 See if var ends with any of the suffixes listed and
721 remove it if found
722 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500723 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -0400724 if suffix and var.endswith(suffix):
725 return var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500726 return var
727
728def mkdirhier(directory):
729 """Create a directory like 'mkdir -p', but does not complain if
730 directory already exists like os.makedirs
731 """
732
733 try:
734 os.makedirs(directory)
735 except OSError as e:
Brad Bishopc342db32019-05-15 21:57:59 -0400736 if e.errno != errno.EEXIST or not os.path.isdir(directory):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500737 raise e
738
739def movefile(src, dest, newmtime = None, sstat = None):
740 """Moves a file from src to dest, preserving all permissions and
741 attributes; mtime will be preserved even when moving across
742 filesystems. Returns true on success and false on failure. Move is
743 atomic.
744 """
745
746 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
747 try:
748 if not sstat:
749 sstat = os.lstat(src)
750 except Exception as e:
751 print("movefile: Stating source file failed...", e)
752 return None
753
754 destexists = 1
755 try:
756 dstat = os.lstat(dest)
757 except:
758 dstat = os.lstat(os.path.dirname(dest))
759 destexists = 0
760
761 if destexists:
762 if stat.S_ISLNK(dstat[stat.ST_MODE]):
763 try:
764 os.unlink(dest)
765 destexists = 0
766 except Exception as e:
767 pass
768
769 if stat.S_ISLNK(sstat[stat.ST_MODE]):
770 try:
771 target = os.readlink(src)
772 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
773 os.unlink(dest)
774 os.symlink(target, dest)
775 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
776 os.unlink(src)
777 return os.lstat(dest)
778 except Exception as e:
779 print("movefile: failed to properly create symlink:", dest, "->", target, e)
780 return None
781
782 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500783 # os.rename needs to know the dest path ending with file name
784 # so append the file name to a path only if it's a dir specified
785 srcfname = os.path.basename(src)
786 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
787 else dest
788
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500789 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
790 try:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500791 bb.utils.rename(src, destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500792 renamefailed = 0
793 except Exception as e:
Brad Bishop79641f22019-09-10 07:20:22 -0400794 if e.errno != errno.EXDEV:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500795 # Some random error.
796 print("movefile: Failed to move", src, "to", dest, e)
797 return None
798 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
799
800 if renamefailed:
801 didcopy = 0
802 if stat.S_ISREG(sstat[stat.ST_MODE]):
803 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500804 shutil.copyfile(src, destpath + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500805 bb.utils.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500806 didcopy = 1
807 except Exception as e:
808 print('movefile: copy', src, '->', dest, 'failed.', e)
809 return None
810 else:
811 #we don't yet handle special, so we need to fall back to /bin/mv
812 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
813 if a[0] != 0:
814 print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
815 return None # failure
816 try:
817 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400818 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
819 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500820 os.unlink(src)
821 except Exception as e:
822 print("movefile: Failed to chown/chmod/unlink", dest, e)
823 return None
824
825 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500826 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500827 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500828 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500829 newmtime = sstat[stat.ST_MTIME]
830 return newmtime
831
832def copyfile(src, dest, newmtime = None, sstat = None):
833 """
834 Copies a file from src to dest, preserving all permissions and
835 attributes; mtime will be preserved even when moving across
836 filesystems. Returns true on success and false on failure.
837 """
838 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
839 try:
840 if not sstat:
841 sstat = os.lstat(src)
842 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600843 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500844 return False
845
846 destexists = 1
847 try:
848 dstat = os.lstat(dest)
849 except:
850 dstat = os.lstat(os.path.dirname(dest))
851 destexists = 0
852
853 if destexists:
854 if stat.S_ISLNK(dstat[stat.ST_MODE]):
855 try:
856 os.unlink(dest)
857 destexists = 0
858 except Exception as e:
859 pass
860
861 if stat.S_ISLNK(sstat[stat.ST_MODE]):
862 try:
863 target = os.readlink(src)
864 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
865 os.unlink(dest)
866 os.symlink(target, dest)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500867 os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500868 return os.lstat(dest)
869 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600870 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500871 return False
872
873 if stat.S_ISREG(sstat[stat.ST_MODE]):
874 try:
875 srcchown = False
876 if not os.access(src, os.R_OK):
877 # Make sure we can read it
878 srcchown = True
879 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
880
881 # For safety copy then move it over.
882 shutil.copyfile(src, dest + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500883 bb.utils.rename(dest + "#new", dest)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500884 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600885 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500886 return False
887 finally:
888 if srcchown:
889 os.chmod(src, sstat[stat.ST_MODE])
890 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
891
892 else:
893 #we don't yet handle special, so we need to fall back to /bin/mv
894 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
895 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600896 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500897 return False # failure
898 try:
899 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
900 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
901 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600902 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500903 return False
904
905 if newmtime:
906 os.utime(dest, (newmtime, newmtime))
907 else:
908 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
909 newmtime = sstat[stat.ST_MTIME]
910 return newmtime
911
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800912def break_hardlinks(src, sstat = None):
913 """
914 Ensures src is the only hardlink to this file. Other hardlinks,
915 if any, are not affected (other than in their st_nlink value, of
916 course). Returns true on success and false on failure.
917
918 """
919 try:
920 if not sstat:
921 sstat = os.lstat(src)
922 except Exception as e:
923 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
924 return False
925 if sstat[stat.ST_NLINK] == 1:
926 return True
927 return copyfile(src, src, sstat=sstat)
928
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500929def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500930 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500931 Locate `item` in the list of paths `path` (colon separated string like $PATH).
932 If `direction` is non-zero then the list is reversed.
933 If `history` is True then the list of candidates also returned as result,history.
934 If `executable` is True then the candidate has to be an executable file,
935 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500936 """
937
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500938 if executable:
939 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
940 else:
941 is_candidate = lambda p: os.path.exists(p)
942
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500943 hist = []
944 paths = (path or "").split(':')
945 if direction != 0:
946 paths.reverse()
947
948 for p in paths:
949 next = os.path.join(p, item)
950 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500951 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500952 if not os.path.isabs(next):
953 next = os.path.abspath(next)
954 if history:
955 return next, hist
956 return next
957
958 if history:
959 return "", hist
960 return ""
961
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500962@contextmanager
963def umask(new_mask):
964 """
965 Context manager to set the umask to a specific mask, and restore it afterwards.
966 """
967 current_mask = os.umask(new_mask)
968 try:
969 yield
970 finally:
971 os.umask(current_mask)
972
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500973def to_boolean(string, default=None):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600974 """
975 Check input string and return boolean value True/False/None
976 depending upon the checks
977 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500978 if not string:
979 return default
980
981 normalized = string.lower()
982 if normalized in ("y", "yes", "1", "true"):
983 return True
984 elif normalized in ("n", "no", "0", "false"):
985 return False
986 else:
987 raise ValueError("Invalid value for to_boolean: %s" % string)
988
989def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500990 """Check if a variable contains all the values specified.
991
992 Arguments:
993
994 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500995 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500996
997 checkvalues -- if this is a string it is split on whitespace into a set(),
998 otherwise coerced directly into a set().
999
1000 truevalue -- the value to return if checkvalues is a subset of variable.
1001
1002 falsevalue -- the value to return if variable is empty or if checkvalues is
1003 not a subset of variable.
1004
1005 d -- the data store.
1006 """
1007
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001008 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001009 if not val:
1010 return falsevalue
1011 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001012 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001013 checkvalues = set(checkvalues.split())
1014 else:
1015 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001016 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001017 return truevalue
1018 return falsevalue
1019
1020def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001021 """Check if a variable contains any values specified.
1022
1023 Arguments:
1024
1025 variable -- the variable name. This will be fetched and expanded (using
1026 d.getVar(variable)) and then split into a set().
1027
1028 checkvalues -- if this is a string it is split on whitespace into a set(),
1029 otherwise coerced directly into a set().
1030
1031 truevalue -- the value to return if checkvalues is a subset of variable.
1032
1033 falsevalue -- the value to return if variable is empty or if checkvalues is
1034 not a subset of variable.
1035
1036 d -- the data store.
1037 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001038 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001039 if not val:
1040 return falsevalue
1041 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001042 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001043 checkvalues = set(checkvalues.split())
1044 else:
1045 checkvalues = set(checkvalues)
1046 if checkvalues & val:
1047 return truevalue
1048 return falsevalue
1049
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001050def filter(variable, checkvalues, d):
1051 """Return all words in the variable that are present in the checkvalues.
1052
1053 Arguments:
1054
1055 variable -- the variable name. This will be fetched and expanded (using
1056 d.getVar(variable)) and then split into a set().
1057
1058 checkvalues -- if this is a string it is split on whitespace into a set(),
1059 otherwise coerced directly into a set().
1060
1061 d -- the data store.
1062 """
1063
1064 val = d.getVar(variable)
1065 if not val:
1066 return ''
1067 val = set(val.split())
1068 if isinstance(checkvalues, str):
1069 checkvalues = set(checkvalues.split())
1070 else:
1071 checkvalues = set(checkvalues)
1072 return ' '.join(sorted(checkvalues & val))
1073
Andrew Geissler82c905d2020-04-13 13:39:40 -05001074
1075def get_referenced_vars(start_expr, d):
1076 """
1077 :return: names of vars referenced in start_expr (recursively), in quasi-BFS order (variables within the same level
1078 are ordered arbitrarily)
1079 """
1080
1081 seen = set()
1082 ret = []
1083
1084 # The first entry in the queue is the unexpanded start expression
1085 queue = collections.deque([start_expr])
1086 # Subsequent entries will be variable names, so we need to track whether or not entry requires getVar
1087 is_first = True
1088
1089 empty_data = bb.data.init()
1090 while queue:
1091 entry = queue.popleft()
1092 if is_first:
1093 # Entry is the start expression - no expansion needed
1094 is_first = False
1095 expression = entry
1096 else:
1097 # This is a variable name - need to get the value
1098 expression = d.getVar(entry, False)
1099 ret.append(entry)
1100
1101 # expandWithRefs is how we actually get the referenced variables in the expression. We call it using an empty
1102 # data store because we only want the variables directly used in the expression. It returns a set, which is what
1103 # dooms us to only ever be "quasi-BFS" rather than full BFS.
1104 new_vars = empty_data.expandWithRefs(expression, None).references - set(seen)
1105
1106 queue.extend(new_vars)
1107 seen.update(new_vars)
1108 return ret
1109
1110
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001111def cpu_count():
1112 return multiprocessing.cpu_count()
1113
1114def nonblockingfd(fd):
1115 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1116
1117def process_profilelog(fn, pout = None):
1118 # Either call with a list of filenames and set pout or a filename and optionally pout.
1119 if not pout:
1120 pout = fn + '.processed'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001121
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001122 with open(pout, 'w') as pout:
1123 import pstats
1124 if isinstance(fn, list):
1125 p = pstats.Stats(*fn, stream=pout)
1126 else:
1127 p = pstats.Stats(fn, stream=pout)
1128 p.sort_stats('time')
1129 p.print_stats()
1130 p.print_callers()
1131 p.sort_stats('cumulative')
1132 p.print_stats()
1133
1134 pout.flush()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001135
1136#
1137# Was present to work around multiprocessing pool bugs in python < 2.7.3
1138#
1139def multiprocessingpool(*args, **kwargs):
1140
1141 import multiprocessing.pool
1142 #import multiprocessing.util
1143 #multiprocessing.util.log_to_stderr(10)
1144 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1145 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1146 def wrapper(func):
1147 def wrap(self, timeout=None):
1148 return func(self, timeout=timeout if timeout is not None else 1e100)
1149 return wrap
1150 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1151
1152 return multiprocessing.Pool(*args, **kwargs)
1153
1154def exec_flat_python_func(func, *args, **kwargs):
1155 """Execute a flat python function (defined with def funcname(args):...)"""
1156 # Prepare a small piece of python code which calls the requested function
1157 # To do this we need to prepare two things - a set of variables we can use to pass
1158 # the values of arguments into the calling function, and the list of arguments for
1159 # the function being called
1160 context = {}
1161 funcargs = []
1162 # Handle unnamed arguments
1163 aidx = 1
1164 for arg in args:
1165 argname = 'arg_%s' % aidx
1166 context[argname] = arg
1167 funcargs.append(argname)
1168 aidx += 1
1169 # Handle keyword arguments
1170 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001171 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001172 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1173 comp = bb.utils.better_compile(code, '<string>', '<string>')
1174 bb.utils.better_exec(comp, context, code, '<string>')
1175 return context['retval']
1176
1177def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1178 """Edit lines from a recipe or config file and modify one or more
1179 specified variable values set in the file using a specified callback
1180 function. Lines are expected to have trailing newlines.
1181 Parameters:
1182 meta_lines: lines from the file; can be a list or an iterable
1183 (e.g. file pointer)
1184 variables: a list of variable names to look for. Functions
1185 may also be specified, but must be specified with '()' at
1186 the end of the name. Note that the function doesn't have
Patrick Williams213cb262021-08-07 19:21:33 -05001187 any intrinsic understanding of :append, :prepend, :remove,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001188 or overrides, so these are considered as part of the name.
1189 These values go into a regular expression, so regular
1190 expression syntax is allowed.
1191 varfunc: callback function called for every variable matching
1192 one of the entries in the variables parameter. The function
1193 should take four arguments:
1194 varname: name of variable matched
1195 origvalue: current value in file
1196 op: the operator (e.g. '+=')
1197 newlines: list of lines up to this point. You can use
1198 this to prepend lines before this variable setting
1199 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001200 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001201 newvalue: new value to substitute in, or None to drop
1202 the variable setting entirely. (If the removal
1203 results in two consecutive blank lines, one of the
1204 blank lines will also be dropped).
1205 newop: the operator to use - if you specify None here,
1206 the original operation will be used.
1207 indent: number of spaces to indent multi-line entries,
1208 or -1 to indent up to the level of the assignment
1209 and opening quote, or a string to use as the indent.
1210 minbreak: True to allow the first element of a
1211 multi-line value to continue on the same line as
1212 the assignment, False to indent before the first
1213 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001214 To clarify, if you wish not to change the value, then you
1215 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001216 match_overrides: True to match items with _overrides on the end,
1217 False otherwise
1218 Returns a tuple:
1219 updated:
1220 True if changes were made, False otherwise.
1221 newlines:
1222 Lines after processing
1223 """
1224
1225 var_res = {}
1226 if match_overrides:
Brad Bishop19323692019-04-05 15:28:33 -04001227 override_re = r'(_[a-zA-Z0-9-_$(){}]+)?'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001228 else:
1229 override_re = ''
1230 for var in variables:
1231 if var.endswith('()'):
Brad Bishop19323692019-04-05 15:28:33 -04001232 var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001233 else:
Brad Bishop19323692019-04-05 15:28:33 -04001234 var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001235
1236 updated = False
1237 varset_start = ''
1238 varlines = []
1239 newlines = []
1240 in_var = None
1241 full_value = ''
1242 var_end = ''
1243
1244 def handle_var_end():
1245 prerun_newlines = newlines[:]
1246 op = varset_start[len(in_var):].strip()
1247 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1248 changed = (prerun_newlines != newlines)
1249
1250 if newvalue is None:
1251 # Drop the value
1252 return True
1253 elif newvalue != full_value or (newop not in [None, op]):
1254 if newop not in [None, op]:
1255 # Callback changed the operator
1256 varset_new = "%s %s" % (in_var, newop)
1257 else:
1258 varset_new = varset_start
1259
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001260 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001261 if indent == -1:
1262 indentspc = ' ' * (len(varset_new) + 2)
1263 else:
1264 indentspc = ' ' * indent
1265 else:
1266 indentspc = indent
1267 if in_var.endswith('()'):
1268 # A function definition
1269 if isinstance(newvalue, list):
1270 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1271 else:
1272 if not newvalue.startswith('\n'):
1273 newvalue = '\n' + newvalue
1274 if not newvalue.endswith('\n'):
1275 newvalue = newvalue + '\n'
1276 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1277 else:
1278 # Normal variable
1279 if isinstance(newvalue, list):
1280 if not newvalue:
1281 # Empty list -> empty string
1282 newlines.append('%s ""\n' % varset_new)
1283 elif minbreak:
1284 # First item on first line
1285 if len(newvalue) == 1:
1286 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1287 else:
1288 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1289 for item in newvalue[1:]:
1290 newlines.append('%s%s \\\n' % (indentspc, item))
1291 newlines.append('%s"\n' % indentspc)
1292 else:
1293 # No item on first line
1294 newlines.append('%s " \\\n' % varset_new)
1295 for item in newvalue:
1296 newlines.append('%s%s \\\n' % (indentspc, item))
1297 newlines.append('%s"\n' % indentspc)
1298 else:
1299 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1300 return True
1301 else:
1302 # Put the old lines back where they were
1303 newlines.extend(varlines)
1304 # If newlines was touched by the function, we'll need to return True
1305 return changed
1306
1307 checkspc = False
1308
1309 for line in meta_lines:
1310 if in_var:
1311 value = line.rstrip()
1312 varlines.append(line)
1313 if in_var.endswith('()'):
1314 full_value += '\n' + value
1315 else:
1316 full_value += value[:-1]
1317 if value.endswith(var_end):
1318 if in_var.endswith('()'):
1319 if full_value.count('{') - full_value.count('}') >= 0:
1320 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001321 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001322 if handle_var_end():
1323 updated = True
1324 checkspc = True
1325 in_var = None
1326 else:
1327 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001328 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001329 res = var_re.match(line)
1330 if res:
1331 isfunc = varname.endswith('()')
1332 if isfunc:
1333 splitvalue = line.split('{', 1)
1334 var_end = '}'
1335 else:
1336 var_end = res.groups()[-1]
1337 splitvalue = line.split(var_end, 1)
1338 varset_start = splitvalue[0].rstrip()
1339 value = splitvalue[1].rstrip()
1340 if not isfunc and value.endswith('\\'):
1341 value = value[:-1]
1342 full_value = value
1343 varlines = [line]
1344 in_var = res.group(1)
1345 if isfunc:
1346 in_var += '()'
1347 if value.endswith(var_end):
1348 full_value = full_value[:-1]
1349 if handle_var_end():
1350 updated = True
1351 checkspc = True
1352 in_var = None
1353 skip = True
1354 break
1355 if not skip:
1356 if checkspc:
1357 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001358 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001359 # Squash blank line if there are two consecutive blanks after a removal
1360 continue
1361 newlines.append(line)
1362 return (updated, newlines)
1363
1364
1365def edit_metadata_file(meta_file, variables, varfunc):
1366 """Edit a recipe or config file and modify one or more specified
1367 variable values set in the file using a specified callback function.
1368 The file is only written to if the value(s) actually change.
1369 This is basically the file version of edit_metadata(), see that
1370 function's description for parameter/usage information.
1371 Returns True if the file was written to, False otherwise.
1372 """
1373 with open(meta_file, 'r') as f:
1374 (updated, newlines) = edit_metadata(f, variables, varfunc)
1375 if updated:
1376 with open(meta_file, 'w') as f:
1377 f.writelines(newlines)
1378 return updated
1379
1380
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001381def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001382 """Edit bblayers.conf, adding and/or removing layers
1383 Parameters:
1384 bblayers_conf: path to bblayers.conf file to edit
1385 add: layer path (or list of layer paths) to add; None or empty
1386 list to add nothing
1387 remove: layer path (or list of layer paths) to remove; None or
1388 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001389 edit_cb: optional callback function that will be called after
1390 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001391 Returns a tuple:
1392 notadded: list of layers specified to be added but weren't
1393 (because they were already in the list)
1394 notremoved: list of layers that were specified to be removed
1395 but weren't (because they weren't in the list)
1396 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001397
1398 import fnmatch
1399
1400 def remove_trailing_sep(pth):
1401 if pth and pth[-1] == os.sep:
1402 pth = pth[:-1]
1403 return pth
1404
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001405 approved = bb.utils.approved_variables()
1406 def canonicalise_path(pth):
1407 pth = remove_trailing_sep(pth)
1408 if 'HOME' in approved and '~' in pth:
1409 pth = os.path.expanduser(pth)
1410 return pth
1411
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001412 def layerlist_param(value):
1413 if not value:
1414 return []
1415 elif isinstance(value, list):
1416 return [remove_trailing_sep(x) for x in value]
1417 else:
1418 return [remove_trailing_sep(value)]
1419
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001420 addlayers = layerlist_param(add)
1421 removelayers = layerlist_param(remove)
1422
1423 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1424 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001425 removed = []
1426 plusequals = False
1427 orig_bblayers = []
1428
1429 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1430 bblayercalls.append(op)
1431 if op == '=':
1432 del orig_bblayers[:]
1433 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1434 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001435
1436 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001437 updated = False
1438 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1439 if removelayers:
1440 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001441 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001442 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001443 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001444 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001445 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001446 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001447 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001448 for addlayer in addlayers:
1449 if addlayer not in bblayers:
1450 updated = True
1451 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001452 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001453
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001454 if edit_cb:
1455 newlist = []
1456 for layer in bblayers:
1457 res = edit_cb(layer, canonicalise_path(layer))
1458 if res != layer:
1459 newlist.append(res)
1460 updated = True
1461 else:
1462 newlist.append(layer)
1463 bblayers = newlist
1464
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001465 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001466 if op == '+=' and not bblayers:
1467 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001468 return (bblayers, None, 2, False)
1469 else:
1470 return (origvalue, None, 2, False)
1471
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001472 with open(bblayers_conf, 'r') as f:
1473 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001474
1475 if not bblayercalls:
1476 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1477
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001478 # Try to do the "smart" thing depending on how the user has laid out
1479 # their bblayers.conf file
1480 if bblayercalls.count('+=') > 1:
1481 plusequals = True
1482
1483 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1484 notadded = []
1485 for layer in addlayers:
1486 layer_canon = canonicalise_path(layer)
1487 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1488 notadded.append(layer)
1489 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1490 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1491
1492 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1493 if addlayers:
1494 # Still need to add these
1495 for addlayer in addlayers:
1496 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1497 updated = True
1498
1499 if updated:
1500 with open(bblayers_conf, 'w') as f:
1501 f.writelines(newlines)
1502
1503 notremoved = list(set(removelayers) - set(removed))
1504
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001505 return (notadded, notremoved)
1506
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001507def get_collection_res(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001508 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001509 collection_res = {}
1510 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001511 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001512
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001513 return collection_res
1514
1515
1516def get_file_layer(filename, d, collection_res={}):
1517 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
1518 if not collection_res:
1519 collection_res = get_collection_res(d)
1520
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001521 def path_to_layer(path):
1522 # Use longest path so we handle nested layers
1523 matchlen = 0
1524 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001525 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001526 if len(regex) > matchlen and re.match(regex, path):
1527 matchlen = len(regex)
1528 match = collection
1529 return match
1530
1531 result = None
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001532 bbfiles = (d.getVar('BBFILES_PRIORITIZED') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001533 bbfilesmatch = False
1534 for bbfilesentry in bbfiles:
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001535 if fnmatch.fnmatchcase(filename, bbfilesentry):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001536 bbfilesmatch = True
1537 result = path_to_layer(bbfilesentry)
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001538 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001539
1540 if not bbfilesmatch:
1541 # Probably a bbclass
1542 result = path_to_layer(filename)
1543
1544 return result
1545
1546
1547# Constant taken from http://linux.die.net/include/linux/prctl.h
1548PR_SET_PDEATHSIG = 1
1549
1550class PrCtlError(Exception):
1551 pass
1552
1553def signal_on_parent_exit(signame):
1554 """
1555 Trigger signame to be sent when the parent process dies
1556 """
1557 signum = getattr(signal, signame)
1558 # http://linux.die.net/man/2/prctl
1559 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1560 if result != 0:
1561 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001562
1563#
1564# Manually call the ioprio syscall. We could depend on other libs like psutil
1565# however this gets us enough of what we need to bitbake for now without the
1566# dependency
1567#
1568_unamearch = os.uname()[4]
1569IOPRIO_WHO_PROCESS = 1
1570IOPRIO_CLASS_SHIFT = 13
1571
1572def ioprio_set(who, cls, value):
1573 NR_ioprio_set = None
1574 if _unamearch == "x86_64":
1575 NR_ioprio_set = 251
1576 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1577 NR_ioprio_set = 289
Brad Bishop19323692019-04-05 15:28:33 -04001578 elif _unamearch == "aarch64":
1579 NR_ioprio_set = 30
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001580
1581 if NR_ioprio_set:
1582 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1583 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1584 if rc != 0:
1585 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1586 else:
1587 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001588
1589def set_process_name(name):
1590 from ctypes import cdll, byref, create_string_buffer
1591 # This is nice to have for debugging, not essential
1592 try:
1593 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001594 buf = create_string_buffer(bytes(name, 'utf-8'))
1595 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001596 except:
1597 pass
1598
Andrew Geissler595f6302022-01-24 19:11:47 +00001599def disable_network(uid=None, gid=None):
1600 """
1601 Disable networking in the current process if the kernel supports it, else
1602 just return after logging to debug. To do this we need to create a new user
1603 namespace, then map back to the original uid/gid.
1604 """
1605 libc = ctypes.CDLL('libc.so.6')
1606
1607 # From sched.h
1608 # New user namespace
1609 CLONE_NEWUSER = 0x10000000
1610 # New network namespace
1611 CLONE_NEWNET = 0x40000000
1612
1613 if uid is None:
1614 uid = os.getuid()
1615 if gid is None:
1616 gid = os.getgid()
1617
1618 ret = libc.unshare(CLONE_NEWNET | CLONE_NEWUSER)
1619 if ret != 0:
1620 logger.debug("System doesn't suport disabling network without admin privs")
1621 return
1622 with open("/proc/self/uid_map", "w") as f:
1623 f.write("%s %s 1" % (uid, uid))
1624 with open("/proc/self/setgroups", "w") as f:
1625 f.write("deny")
1626 with open("/proc/self/gid_map", "w") as f:
1627 f.write("%s %s 1" % (gid, gid))
1628
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001629def export_proxies(d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001630 """ export common proxies variables from datastore to environment """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001631 import os
1632
1633 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001634 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1635 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001636 exported = False
1637
1638 for v in variables:
1639 if v in os.environ.keys():
1640 exported = True
1641 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001642 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001643 if v_proxy is not None:
1644 os.environ[v] = v_proxy
1645 exported = True
1646
1647 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001648
1649
1650def load_plugins(logger, plugins, pluginpath):
1651 def load_plugin(name):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001652 logger.debug('Loading plugin %s' % name)
Brad Bishop19323692019-04-05 15:28:33 -04001653 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1654 if spec:
Andrew Geissler595f6302022-01-24 19:11:47 +00001655 mod = importlib.util.module_from_spec(spec)
1656 spec.loader.exec_module(mod)
1657 return mod
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001658
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001659 logger.debug('Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001660
1661 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1662 for ext in python_extensions)
1663 files = itertools.chain.from_iterable(expanded)
1664 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1665 for name in names:
1666 if name != '__init__':
1667 plugin = load_plugin(name)
1668 if hasattr(plugin, 'plugin_init'):
1669 obj = plugin.plugin_init(plugins)
1670 plugins.append(obj or plugin)
1671 else:
1672 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001673
1674
1675class LogCatcher(logging.Handler):
1676 """Logging handler for collecting logged messages so you can check them later"""
1677 def __init__(self):
1678 self.messages = []
1679 logging.Handler.__init__(self, logging.WARNING)
1680 def emit(self, record):
1681 self.messages.append(bb.build.logformatter.format(record))
1682 def contains(self, message):
1683 return (message in self.messages)
Andrew Geissler82c905d2020-04-13 13:39:40 -05001684
1685def is_semver(version):
1686 """
1687 Is the version string following the semver semantic?
1688
1689 https://semver.org/spec/v2.0.0.html
1690 """
1691 regex = re.compile(
1692 r"""
1693 ^
1694 (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
1695 (?:-(
1696 (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
1697 (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
1698 ))?
1699 (?:\+(
1700 [0-9a-zA-Z-]+
1701 (?:\.[0-9a-zA-Z-]+)*
1702 ))?
1703 $
1704 """, re.VERBOSE)
1705
1706 if regex.match(version) is None:
1707 return False
1708
1709 return True
Andrew Geisslerc926e172021-05-07 16:11:35 -05001710
1711# Wrapper around os.rename which can handle cross device problems
1712# e.g. from container filesystems
1713def rename(src, dst):
1714 try:
1715 os.rename(src, dst)
1716 except OSError as err:
1717 if err.errno == 18:
1718 # Invalid cross-device link error
1719 shutil.move(src, dst)
1720 else:
1721 raise err
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001722
1723@contextmanager
1724def environment(**envvars):
1725 """
1726 Context manager to selectively update the environment with the specified mapping.
1727 """
1728 backup = dict(os.environ)
1729 try:
1730 os.environ.update(envvars)
1731 yield
1732 finally:
1733 for var in envvars:
1734 if var in backup:
1735 os.environ[var] = backup[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001736 elif var in os.environ:
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001737 del os.environ[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001738
1739def is_local_uid(uid=''):
1740 """
1741 Check whether uid is a local one or not.
1742 Can't use pwd module since it gets all UIDs, not local ones only.
1743 """
1744 if not uid:
1745 uid = os.getuid()
1746 with open('/etc/passwd', 'r') as f:
1747 for line in f:
1748 line_split = line.split(':')
1749 if len(line_split) < 3:
1750 continue
1751 if str(uid) == line_split[2]:
1752 return True
1753 return False