blob: 92d44c5260c065a6f8ced0836a91c9e8f394eeb0 [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 Williams92b42cb2022-09-03 06:53:57 -050031import random
32import tempfile
Patrick Williamsc0f7c042017-02-23 20:41:17 -060033from subprocess import getstatusoutput
Patrick Williamsc124f4f2015-09-15 14:41:29 -050034from contextlib import contextmanager
35from ctypes import cdll
36
Patrick Williamsc124f4f2015-09-15 14:41:29 -050037logger = logging.getLogger("BitBake.Util")
Brad Bishop19323692019-04-05 15:28:33 -040038python_extensions = importlib.machinery.all_suffixes()
Patrick Williamsc0f7c042017-02-23 20:41:17 -060039
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040
41def clean_context():
42 return {
43 "os": os,
44 "bb": bb,
45 "time": time,
46 }
47
48def get_context():
49 return _context
50
51
52def set_context(ctx):
53 _context = ctx
54
55# Context used in better_exec, eval
56_context = clean_context()
57
58class VersionStringException(Exception):
59 """Exception raised when an invalid version specification is found"""
60
61def explode_version(s):
62 r = []
Brad Bishop19323692019-04-05 15:28:33 -040063 alpha_regexp = re.compile(r'^([a-zA-Z]+)(.*)$')
64 numeric_regexp = re.compile(r'^(\d+)(.*)$')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065 while (s != ''):
66 if s[0] in string.digits:
67 m = numeric_regexp.match(s)
68 r.append((0, int(m.group(1))))
69 s = m.group(2)
70 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -060071 if s[0] in string.ascii_letters:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050072 m = alpha_regexp.match(s)
73 r.append((1, m.group(1)))
74 s = m.group(2)
75 continue
76 if s[0] == '~':
77 r.append((-1, s[0]))
78 else:
79 r.append((2, s[0]))
80 s = s[1:]
81 return r
82
83def split_version(s):
84 """Split a version string into its constituent parts (PE, PV, PR)"""
85 s = s.strip(" <>=")
86 e = 0
87 if s.count(':'):
88 e = int(s.split(":")[0])
89 s = s.split(":")[1]
90 r = ""
91 if s.count('-'):
92 r = s.rsplit("-", 1)[1]
93 s = s.rsplit("-", 1)[0]
94 v = s
95 return (e, v, r)
96
97def vercmp_part(a, b):
98 va = explode_version(a)
99 vb = explode_version(b)
100 while True:
101 if va == []:
102 (oa, ca) = (0, None)
103 else:
104 (oa, ca) = va.pop(0)
105 if vb == []:
106 (ob, cb) = (0, None)
107 else:
108 (ob, cb) = vb.pop(0)
109 if (oa, ca) == (0, None) and (ob, cb) == (0, None):
110 return 0
111 if oa < ob:
112 return -1
113 elif oa > ob:
114 return 1
Brad Bishop19323692019-04-05 15:28:33 -0400115 elif ca is None:
116 return -1
117 elif cb is None:
118 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500119 elif ca < cb:
120 return -1
121 elif ca > cb:
122 return 1
123
124def vercmp(ta, tb):
125 (ea, va, ra) = ta
126 (eb, vb, rb) = tb
127
128 r = int(ea or 0) - int(eb or 0)
129 if (r == 0):
130 r = vercmp_part(va, vb)
131 if (r == 0):
132 r = vercmp_part(ra, rb)
133 return r
134
135def vercmp_string(a, b):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600136 """ Split version strings and compare them """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500137 ta = split_version(a)
138 tb = split_version(b)
139 return vercmp(ta, tb)
140
141def vercmp_string_op(a, b, op):
142 """
143 Compare two versions and check if the specified comparison operator matches the result of the comparison.
144 This function is fairly liberal about what operators it will accept since there are a variety of styles
145 depending on the context.
146 """
147 res = vercmp_string(a, b)
148 if op in ('=', '=='):
149 return res == 0
150 elif op == '<=':
151 return res <= 0
152 elif op == '>=':
153 return res >= 0
154 elif op in ('>', '>>'):
155 return res > 0
156 elif op in ('<', '<<'):
157 return res < 0
158 elif op == '!=':
159 return res != 0
160 else:
161 raise VersionStringException('Unsupported comparison operator "%s"' % op)
162
163def explode_deps(s):
164 """
165 Take an RDEPENDS style string of format:
166 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
167 and return a list of dependencies.
168 Version information is ignored.
169 """
170 r = []
171 l = s.split()
172 flag = False
173 for i in l:
174 if i[0] == '(':
175 flag = True
176 #j = []
177 if not flag:
178 r.append(i)
179 #else:
180 # j.append(i)
181 if flag and i.endswith(')'):
182 flag = False
183 # Ignore version
184 #r[-1] += ' ' + ' '.join(j)
185 return r
186
Brad Bishop316dfdd2018-06-25 12:45:53 -0400187def explode_dep_versions2(s, *, sort=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500188 """
189 Take an RDEPENDS style string of format:
190 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
191 and return a dictionary of dependencies and versions.
192 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600193 r = collections.OrderedDict()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500194 l = s.replace(",", "").split()
195 lastdep = None
196 lastcmp = ""
197 lastver = ""
198 incmp = False
199 inversion = False
200 for i in l:
201 if i[0] == '(':
202 incmp = True
203 i = i[1:].strip()
204 if not i:
205 continue
206
207 if incmp:
208 incmp = False
209 inversion = True
210 # This list is based on behavior and supported comparisons from deb, opkg and rpm.
211 #
212 # Even though =<, <<, ==, !=, =>, and >> may not be supported,
213 # we list each possibly valid item.
214 # The build system is responsible for validation of what it supports.
215 if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')):
216 lastcmp = i[0:2]
217 i = i[2:]
218 elif i.startswith(('<', '>', '=')):
219 lastcmp = i[0:1]
220 i = i[1:]
221 else:
222 # This is an unsupported case!
223 raise VersionStringException('Invalid version specification in "(%s" - invalid or missing operator' % i)
224 lastcmp = (i or "")
225 i = ""
226 i.strip()
227 if not i:
228 continue
229
230 if inversion:
231 if i.endswith(')'):
232 i = i[:-1] or ""
233 inversion = False
234 if lastver and i:
235 lastver += " "
236 if i:
237 lastver += i
238 if lastdep not in r:
239 r[lastdep] = []
240 r[lastdep].append(lastcmp + " " + lastver)
241 continue
242
243 #if not inversion:
244 lastdep = i
245 lastver = ""
246 lastcmp = ""
247 if not (i in r and r[i]):
248 r[lastdep] = []
249
Brad Bishop316dfdd2018-06-25 12:45:53 -0400250 if sort:
251 r = collections.OrderedDict(sorted(r.items(), key=lambda x: x[0]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500252 return r
253
254def explode_dep_versions(s):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600255 """
256 Take an RDEPENDS style string of format:
257 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000258 skip null value and items appeared in dependency string multiple times
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600259 and return a dictionary of dependencies and versions.
260 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500261 r = explode_dep_versions2(s)
262 for d in r:
263 if not r[d]:
264 r[d] = None
265 continue
266 if len(r[d]) > 1:
267 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))
268 r[d] = r[d][0]
269 return r
270
271def join_deps(deps, commasep=True):
272 """
273 Take the result from explode_dep_versions and generate a dependency string
274 """
275 result = []
276 for dep in deps:
277 if deps[dep]:
278 if isinstance(deps[dep], list):
279 for v in deps[dep]:
280 result.append(dep + " (" + v + ")")
281 else:
282 result.append(dep + " (" + deps[dep] + ")")
283 else:
284 result.append(dep)
285 if commasep:
286 return ", ".join(result)
287 else:
288 return " ".join(result)
289
290def _print_trace(body, line):
291 """
292 Print the Environment of a Text Body
293 """
294 error = []
295 # print the environment of the method
296 min_line = max(1, line-4)
297 max_line = min(line + 4, len(body))
298 for i in range(min_line, max_line + 1):
299 if line == i:
300 error.append(' *** %.4d:%s' % (i, body[i-1].rstrip()))
301 else:
302 error.append(' %.4d:%s' % (i, body[i-1].rstrip()))
303 return error
304
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500305def better_compile(text, file, realfile, mode = "exec", lineno = 0):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500306 """
307 A better compile method. This method
308 will print the offending lines.
309 """
310 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500311 cache = bb.methodpool.compile_cache(text)
312 if cache:
313 return cache
314 # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though
315 text2 = "\n" * int(lineno) + text
316 code = compile(text2, realfile, mode)
317 bb.methodpool.compile_cache_add(text, code)
318 return code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319 except Exception as e:
320 error = []
321 # split the text into lines again
322 body = text.split('\n')
Brad Bishop19323692019-04-05 15:28:33 -0400323 error.append("Error in compiling python function in %s, line %s:\n" % (realfile, e.lineno))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500324 if hasattr(e, "lineno"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500325 error.append("The code lines resulting in this error were:")
Brad Bishop19323692019-04-05 15:28:33 -0400326 # e.lineno: line's position in reaflile
327 # lineno: function name's "position -1" in realfile
328 # e.lineno - lineno: line's relative position in function
329 error.extend(_print_trace(body, e.lineno - lineno))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500330 else:
331 error.append("The function causing this error was:")
332 for line in body:
333 error.append(line)
334 error.append("%s: %s" % (e.__class__.__name__, str(e)))
335
336 logger.error("\n".join(error))
337
338 e = bb.BBHandledException(e)
339 raise e
340
341def _print_exception(t, value, tb, realfile, text, context):
342 error = []
343 try:
344 exception = traceback.format_exception_only(t, value)
345 error.append('Error executing a python function in %s:\n' % realfile)
346
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500347 # Strip 'us' from the stack (better_exec call) unless that was where the
348 # error came from
349 if tb.tb_next is not None:
350 tb = tb.tb_next
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500351
352 textarray = text.split('\n')
353
354 linefailed = tb.tb_lineno
355
356 tbextract = traceback.extract_tb(tb)
357 tbformat = traceback.format_list(tbextract)
358 error.append("The stack trace of python calls that resulted in this exception/failure was:")
359 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2]))
360 error.extend(_print_trace(textarray, linefailed))
361
362 # See if this is a function we constructed and has calls back into other functions in
363 # "text". If so, try and improve the context of the error by diving down the trace
364 level = 0
365 nexttb = tb.tb_next
366 while nexttb is not None and (level+1) < len(tbextract):
367 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]))
368 if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]:
369 # The code was possibly in the string we compiled ourselves
370 error.extend(_print_trace(textarray, tbextract[level+1][1]))
371 elif tbextract[level+1][0].startswith("/"):
372 # The code looks like it might be in a file, try and load it
373 try:
374 with open(tbextract[level+1][0], "r") as f:
375 text = f.readlines()
376 error.extend(_print_trace(text, tbextract[level+1][1]))
377 except:
378 error.append(tbformat[level+1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500379 else:
380 error.append(tbformat[level+1])
381 nexttb = tb.tb_next
382 level = level + 1
383
384 error.append("Exception: %s" % ''.join(exception))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600385
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000386 # If the exception is from spawning a task, let's be helpful and display
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600387 # the output (which hopefully includes stderr).
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500388 if isinstance(value, subprocess.CalledProcessError) and value.output:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600389 error.append("Subprocess output:")
390 error.append(value.output.decode("utf-8", errors="ignore"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500391 finally:
392 logger.error("\n".join(error))
393
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500394def better_exec(code, context, text = None, realfile = "<code>", pythonexception=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500395 """
396 Similiar to better_compile, better_exec will
397 print the lines that are responsible for the
398 error.
399 """
400 import bb.parse
401 if not text:
402 text = code
403 if not hasattr(code, "co_filename"):
404 code = better_compile(code, realfile, realfile)
405 try:
406 exec(code, get_context(), context)
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000407 except (bb.BBHandledException, bb.parse.SkipRecipe, bb.data_smart.ExpansionError, bb.process.ExecutionError):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500408 # Error already shown so passthrough, no need for traceback
409 raise
410 except Exception as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500411 if pythonexception:
412 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500413 (t, value, tb) = sys.exc_info()
414 try:
415 _print_exception(t, value, tb, realfile, text, context)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500416 except Exception as e2:
417 logger.error("Exception handler error: %s" % str(e2))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500418
419 e = bb.BBHandledException(e)
420 raise e
421
422def simple_exec(code, context):
423 exec(code, get_context(), context)
424
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600425def better_eval(source, locals, extraglobals = None):
426 ctx = get_context()
427 if extraglobals:
428 ctx = copy.copy(ctx)
429 for g in extraglobals:
430 ctx[g] = extraglobals[g]
431 return eval(source, ctx, locals)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500432
433@contextmanager
Patrick Williams92b42cb2022-09-03 06:53:57 -0500434def fileslocked(files, *args, **kwargs):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500435 """Context manager for locking and unlocking file locks."""
436 locks = []
437 if files:
438 for lockfile in files:
Patrick Williams92b42cb2022-09-03 06:53:57 -0500439 l = bb.utils.lockfile(lockfile, *args, **kwargs)
440 if l is not None:
441 locks.append(l)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500442
Andrew Geissler82c905d2020-04-13 13:39:40 -0500443 try:
444 yield
445 finally:
446 for lock in locks:
447 bb.utils.unlockfile(lock)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500448
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500449def lockfile(name, shared=False, retry=True, block=False):
450 """
451 Use the specified file as a lock file, return when the lock has
452 been acquired. Returns a variable to pass to unlockfile().
453 Parameters:
454 retry: True to re-try locking if it fails, False otherwise
455 block: True to block until the lock succeeds, False otherwise
456 The retry and block parameters are kind of equivalent unless you
457 consider the possibility of sending a signal to the process to break
458 out - at which point you want block=True rather than retry=True.
459 """
Andrew Geissler9aee5002022-03-30 16:27:02 +0000460 basename = os.path.basename(name)
461 if len(basename) > 255:
462 root, ext = os.path.splitext(basename)
463 basename = root[:255 - len(ext)] + ext
Andrew Geissler595f6302022-01-24 19:11:47 +0000464
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500465 dirname = os.path.dirname(name)
466 mkdirhier(dirname)
467
Andrew Geissler9aee5002022-03-30 16:27:02 +0000468 name = os.path.join(dirname, basename)
469
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500470 if not os.access(dirname, os.W_OK):
471 logger.error("Unable to acquire lock '%s', directory is not writable",
472 name)
473 sys.exit(1)
474
475 op = fcntl.LOCK_EX
476 if shared:
477 op = fcntl.LOCK_SH
478 if not retry and not block:
479 op = op | fcntl.LOCK_NB
480
481 while True:
482 # If we leave the lockfiles lying around there is no problem
483 # but we should clean up after ourselves. This gives potential
484 # for races though. To work around this, when we acquire the lock
485 # we check the file we locked was still the lock file on disk.
486 # by comparing inode numbers. If they don't match or the lockfile
487 # no longer exists, we start again.
488
489 # This implementation is unfair since the last person to request the
490 # lock is the most likely to win it.
491
492 try:
493 lf = open(name, 'a+')
494 fileno = lf.fileno()
495 fcntl.flock(fileno, op)
496 statinfo = os.fstat(fileno)
497 if os.path.exists(lf.name):
498 statinfo2 = os.stat(lf.name)
499 if statinfo.st_ino == statinfo2.st_ino:
500 return lf
501 lf.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800502 except OSError as e:
Andrew Geissler595f6302022-01-24 19:11:47 +0000503 if e.errno == errno.EACCES or e.errno == errno.ENAMETOOLONG:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800504 logger.error("Unable to acquire lock '%s', %s",
505 e.strerror, name)
506 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500507 try:
508 lf.close()
509 except Exception:
510 pass
511 pass
512 if not retry:
513 return None
514
515def unlockfile(lf):
516 """
517 Unlock a file locked using lockfile()
518 """
519 try:
520 # If we had a shared lock, we need to promote to exclusive before
521 # removing the lockfile. Attempt this, ignore failures.
522 fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
523 os.unlink(lf.name)
524 except (IOError, OSError):
525 pass
526 fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
527 lf.close()
528
Brad Bishop6dbb3162019-11-25 09:41:34 -0500529def _hasher(method, filename):
530 import mmap
531
532 with open(filename, "rb") as f:
533 try:
534 with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
535 for chunk in iter(lambda: mm.read(8192), b''):
536 method.update(chunk)
537 except ValueError:
538 # You can't mmap() an empty file so silence this exception
539 pass
540 return method.hexdigest()
541
542
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500543def md5_file(filename):
544 """
545 Return the hex string representation of the MD5 checksum of filename.
546 """
Brad Bishop6dbb3162019-11-25 09:41:34 -0500547 import hashlib
Andrew Geissler9aee5002022-03-30 16:27:02 +0000548 return _hasher(hashlib.new('MD5', usedforsecurity=False), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500549
550def sha256_file(filename):
551 """
552 Return the hex string representation of the 256-bit SHA checksum of
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500553 filename.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500554 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500555 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500556 return _hasher(hashlib.sha256(), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500557
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500558def sha1_file(filename):
559 """
560 Return the hex string representation of the SHA1 checksum of the filename
561 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500562 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500563 return _hasher(hashlib.sha1(), filename)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500564
Andrew Geissler82c905d2020-04-13 13:39:40 -0500565def sha384_file(filename):
566 """
567 Return the hex string representation of the SHA384 checksum of the filename
568 """
569 import hashlib
570 return _hasher(hashlib.sha384(), filename)
571
572def sha512_file(filename):
573 """
574 Return the hex string representation of the SHA512 checksum of the filename
575 """
576 import hashlib
577 return _hasher(hashlib.sha512(), filename)
578
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500579def preserved_envvars_exported():
580 """Variables which are taken from the environment and placed in and exported
581 from the metadata"""
582 return [
583 'BB_TASKHASH',
584 'HOME',
585 'LOGNAME',
586 'PATH',
587 'PWD',
588 'SHELL',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500589 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600590 'LC_ALL',
591 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500592 ]
593
594def preserved_envvars():
595 """Variables which are taken from the environment and placed in the metadata"""
596 v = [
597 'BBPATH',
598 'BB_PRESERVE_ENV',
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000599 'BB_ENV_PASSTHROUGH',
600 'BB_ENV_PASSTHROUGH_ADDITIONS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500601 ]
602 return v + preserved_envvars_exported()
603
604def filter_environment(good_vars):
605 """
606 Create a pristine environment for bitbake. This will remove variables that
607 are not known and may influence the build in a negative way.
608 """
609
610 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600611 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500612 if key in good_vars:
613 continue
614
615 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500616 del os.environ[key]
617
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600618 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
619 # access methods will use ascii. You can't change that mode once the interpreter is
620 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
621 # distros support that and we need to set something.
622 os.environ["LC_ALL"] = "en_US.UTF-8"
623
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500624 if removed_vars:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600625 logger.debug("Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500626
627 return removed_vars
628
629def approved_variables():
630 """
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000631 Determine and return the list of variables which are approved
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500632 to remain in the environment.
633 """
634 if 'BB_PRESERVE_ENV' in os.environ:
635 return os.environ.keys()
636 approved = []
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000637 if 'BB_ENV_PASSTHROUGH' in os.environ:
638 approved = os.environ['BB_ENV_PASSTHROUGH'].split()
639 approved.extend(['BB_ENV_PASSTHROUGH'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500640 else:
641 approved = preserved_envvars()
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000642 if 'BB_ENV_PASSTHROUGH_ADDITIONS' in os.environ:
643 approved.extend(os.environ['BB_ENV_PASSTHROUGH_ADDITIONS'].split())
644 if 'BB_ENV_PASSTHROUGH_ADDITIONS' not in approved:
645 approved.extend(['BB_ENV_PASSTHROUGH_ADDITIONS'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500646 return approved
647
648def clean_environment():
649 """
650 Clean up any spurious environment variables. This will remove any
651 variables the user hasn't chosen to preserve.
652 """
653 if 'BB_PRESERVE_ENV' not in os.environ:
654 good_vars = approved_variables()
655 return filter_environment(good_vars)
656
657 return {}
658
659def empty_environment():
660 """
661 Remove all variables from the environment.
662 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600663 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500664 os.unsetenv(s)
665 del os.environ[s]
666
667def build_environment(d):
668 """
669 Build an environment from all exported variables.
670 """
671 import bb.data
672 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500673 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500674 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500675 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500676
677def _check_unsafe_delete_path(path):
678 """
679 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
680 the caller should raise an exception with an appropriate message.
681 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
682 with potentially disastrous results.
683 """
684 extra = ''
685 # HOME might not be /home/something, so in case we can get it, check against it
686 homedir = os.environ.get('HOME', '')
687 if homedir:
688 extra = '|%s' % homedir
689 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
690 return True
691 return False
692
Brad Bishopa34c0302019-09-23 22:34:48 -0400693def remove(path, recurse=False, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500694 """Equivalent to rm -f or rm -rf"""
695 if not path:
696 return
697 if recurse:
698 for name in glob.glob(path):
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500699 if _check_unsafe_delete_path(name):
700 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500701 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishopa34c0302019-09-23 22:34:48 -0400702 cmd = []
703 if ionice:
704 cmd = ['ionice', '-c', '3']
705 subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500706 return
707 for name in glob.glob(path):
708 try:
709 os.unlink(name)
710 except OSError as exc:
711 if exc.errno != errno.ENOENT:
712 raise
713
Brad Bishopa34c0302019-09-23 22:34:48 -0400714def prunedir(topdir, ionice=False):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600715 """ Delete everything reachable from the directory named in 'topdir'. """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500716 # CAUTION: This is dangerous!
717 if _check_unsafe_delete_path(topdir):
718 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
Brad Bishopa34c0302019-09-23 22:34:48 -0400719 remove(topdir, recurse=True, ionice=ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500720
721#
722# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
723# but thats possibly insane and suffixes is probably going to be small
724#
725def prune_suffix(var, suffixes, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600726 """
727 See if var ends with any of the suffixes listed and
728 remove it if found
729 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500730 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -0400731 if suffix and var.endswith(suffix):
732 return var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500733 return var
734
735def mkdirhier(directory):
736 """Create a directory like 'mkdir -p', but does not complain if
737 directory already exists like os.makedirs
738 """
739
740 try:
741 os.makedirs(directory)
742 except OSError as e:
Brad Bishopc342db32019-05-15 21:57:59 -0400743 if e.errno != errno.EEXIST or not os.path.isdir(directory):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500744 raise e
745
746def movefile(src, dest, newmtime = None, sstat = None):
747 """Moves a file from src to dest, preserving all permissions and
748 attributes; mtime will be preserved even when moving across
749 filesystems. Returns true on success and false on failure. Move is
750 atomic.
751 """
752
753 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
754 try:
755 if not sstat:
756 sstat = os.lstat(src)
757 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500758 logger.warning("movefile: Stating source file failed...", e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500759 return None
760
761 destexists = 1
762 try:
763 dstat = os.lstat(dest)
764 except:
765 dstat = os.lstat(os.path.dirname(dest))
766 destexists = 0
767
768 if destexists:
769 if stat.S_ISLNK(dstat[stat.ST_MODE]):
770 try:
771 os.unlink(dest)
772 destexists = 0
773 except Exception as e:
774 pass
775
776 if stat.S_ISLNK(sstat[stat.ST_MODE]):
777 try:
778 target = os.readlink(src)
779 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
780 os.unlink(dest)
781 os.symlink(target, dest)
782 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
783 os.unlink(src)
784 return os.lstat(dest)
785 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500786 logger.warning("movefile: failed to properly create symlink:", dest, "->", target, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500787 return None
788
789 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500790 # os.rename needs to know the dest path ending with file name
791 # so append the file name to a path only if it's a dir specified
792 srcfname = os.path.basename(src)
793 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
794 else dest
795
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500796 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
797 try:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500798 bb.utils.rename(src, destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500799 renamefailed = 0
800 except Exception as e:
Brad Bishop79641f22019-09-10 07:20:22 -0400801 if e.errno != errno.EXDEV:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500802 # Some random error.
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500803 logger.warning("movefile: Failed to move", src, "to", dest, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500804 return None
805 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
806
807 if renamefailed:
808 didcopy = 0
809 if stat.S_ISREG(sstat[stat.ST_MODE]):
810 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500811 shutil.copyfile(src, destpath + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500812 bb.utils.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500813 didcopy = 1
814 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500815 logger.warning('movefile: copy', src, '->', dest, 'failed.', e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500816 return None
817 else:
818 #we don't yet handle special, so we need to fall back to /bin/mv
819 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
820 if a[0] != 0:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500821 logger.warning("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500822 return None # failure
823 try:
824 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400825 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
826 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500827 os.unlink(src)
828 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500829 logger.warning("movefile: Failed to chown/chmod/unlink", dest, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500830 return None
831
832 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500833 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500834 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500835 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500836 newmtime = sstat[stat.ST_MTIME]
837 return newmtime
838
839def copyfile(src, dest, newmtime = None, sstat = None):
840 """
841 Copies a file from src to dest, preserving all permissions and
842 attributes; mtime will be preserved even when moving across
843 filesystems. Returns true on success and false on failure.
844 """
845 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
846 try:
847 if not sstat:
848 sstat = os.lstat(src)
849 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600850 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500851 return False
852
853 destexists = 1
854 try:
855 dstat = os.lstat(dest)
856 except:
857 dstat = os.lstat(os.path.dirname(dest))
858 destexists = 0
859
860 if destexists:
861 if stat.S_ISLNK(dstat[stat.ST_MODE]):
862 try:
863 os.unlink(dest)
864 destexists = 0
865 except Exception as e:
866 pass
867
868 if stat.S_ISLNK(sstat[stat.ST_MODE]):
869 try:
870 target = os.readlink(src)
871 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
872 os.unlink(dest)
873 os.symlink(target, dest)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500874 os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500875 return os.lstat(dest)
876 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600877 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500878 return False
879
880 if stat.S_ISREG(sstat[stat.ST_MODE]):
881 try:
882 srcchown = False
883 if not os.access(src, os.R_OK):
884 # Make sure we can read it
885 srcchown = True
886 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
887
888 # For safety copy then move it over.
889 shutil.copyfile(src, dest + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500890 bb.utils.rename(dest + "#new", dest)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500891 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600892 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500893 return False
894 finally:
895 if srcchown:
896 os.chmod(src, sstat[stat.ST_MODE])
897 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
898
899 else:
900 #we don't yet handle special, so we need to fall back to /bin/mv
901 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
902 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600903 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500904 return False # failure
905 try:
906 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
907 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
908 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600909 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500910 return False
911
912 if newmtime:
913 os.utime(dest, (newmtime, newmtime))
914 else:
915 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
916 newmtime = sstat[stat.ST_MTIME]
917 return newmtime
918
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800919def break_hardlinks(src, sstat = None):
920 """
921 Ensures src is the only hardlink to this file. Other hardlinks,
922 if any, are not affected (other than in their st_nlink value, of
923 course). Returns true on success and false on failure.
924
925 """
926 try:
927 if not sstat:
928 sstat = os.lstat(src)
929 except Exception as e:
930 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
931 return False
932 if sstat[stat.ST_NLINK] == 1:
933 return True
934 return copyfile(src, src, sstat=sstat)
935
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500936def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500937 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500938 Locate `item` in the list of paths `path` (colon separated string like $PATH).
939 If `direction` is non-zero then the list is reversed.
940 If `history` is True then the list of candidates also returned as result,history.
941 If `executable` is True then the candidate has to be an executable file,
942 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500943 """
944
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500945 if executable:
946 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
947 else:
948 is_candidate = lambda p: os.path.exists(p)
949
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500950 hist = []
951 paths = (path or "").split(':')
952 if direction != 0:
953 paths.reverse()
954
955 for p in paths:
956 next = os.path.join(p, item)
957 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500958 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500959 if not os.path.isabs(next):
960 next = os.path.abspath(next)
961 if history:
962 return next, hist
963 return next
964
965 if history:
966 return "", hist
967 return ""
968
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500969@contextmanager
970def umask(new_mask):
971 """
972 Context manager to set the umask to a specific mask, and restore it afterwards.
973 """
974 current_mask = os.umask(new_mask)
975 try:
976 yield
977 finally:
978 os.umask(current_mask)
979
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500980def to_boolean(string, default=None):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600981 """
982 Check input string and return boolean value True/False/None
983 depending upon the checks
984 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500985 if not string:
986 return default
987
988 normalized = string.lower()
989 if normalized in ("y", "yes", "1", "true"):
990 return True
991 elif normalized in ("n", "no", "0", "false"):
992 return False
993 else:
994 raise ValueError("Invalid value for to_boolean: %s" % string)
995
996def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500997 """Check if a variable contains all the values specified.
998
999 Arguments:
1000
1001 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001002 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001003
1004 checkvalues -- if this is a string it is split on whitespace into a set(),
1005 otherwise coerced directly into a set().
1006
1007 truevalue -- the value to return if checkvalues is a subset of variable.
1008
1009 falsevalue -- the value to return if variable is empty or if checkvalues is
1010 not a subset of variable.
1011
1012 d -- the data store.
1013 """
1014
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001015 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001016 if not val:
1017 return falsevalue
1018 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001019 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001020 checkvalues = set(checkvalues.split())
1021 else:
1022 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001023 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001024 return truevalue
1025 return falsevalue
1026
1027def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001028 """Check if a variable contains any values specified.
1029
1030 Arguments:
1031
1032 variable -- the variable name. This will be fetched and expanded (using
1033 d.getVar(variable)) and then split into a set().
1034
1035 checkvalues -- if this is a string it is split on whitespace into a set(),
1036 otherwise coerced directly into a set().
1037
1038 truevalue -- the value to return if checkvalues is a subset of variable.
1039
1040 falsevalue -- the value to return if variable is empty or if checkvalues is
1041 not a subset of variable.
1042
1043 d -- the data store.
1044 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001045 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001046 if not val:
1047 return falsevalue
1048 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001049 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001050 checkvalues = set(checkvalues.split())
1051 else:
1052 checkvalues = set(checkvalues)
1053 if checkvalues & val:
1054 return truevalue
1055 return falsevalue
1056
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001057def filter(variable, checkvalues, d):
1058 """Return all words in the variable that are present in the checkvalues.
1059
1060 Arguments:
1061
1062 variable -- the variable name. This will be fetched and expanded (using
1063 d.getVar(variable)) and then split into a set().
1064
1065 checkvalues -- if this is a string it is split on whitespace into a set(),
1066 otherwise coerced directly into a set().
1067
1068 d -- the data store.
1069 """
1070
1071 val = d.getVar(variable)
1072 if not val:
1073 return ''
1074 val = set(val.split())
1075 if isinstance(checkvalues, str):
1076 checkvalues = set(checkvalues.split())
1077 else:
1078 checkvalues = set(checkvalues)
1079 return ' '.join(sorted(checkvalues & val))
1080
Andrew Geissler82c905d2020-04-13 13:39:40 -05001081
1082def get_referenced_vars(start_expr, d):
1083 """
1084 :return: names of vars referenced in start_expr (recursively), in quasi-BFS order (variables within the same level
1085 are ordered arbitrarily)
1086 """
1087
1088 seen = set()
1089 ret = []
1090
1091 # The first entry in the queue is the unexpanded start expression
1092 queue = collections.deque([start_expr])
1093 # Subsequent entries will be variable names, so we need to track whether or not entry requires getVar
1094 is_first = True
1095
1096 empty_data = bb.data.init()
1097 while queue:
1098 entry = queue.popleft()
1099 if is_first:
1100 # Entry is the start expression - no expansion needed
1101 is_first = False
1102 expression = entry
1103 else:
1104 # This is a variable name - need to get the value
1105 expression = d.getVar(entry, False)
1106 ret.append(entry)
1107
1108 # expandWithRefs is how we actually get the referenced variables in the expression. We call it using an empty
1109 # data store because we only want the variables directly used in the expression. It returns a set, which is what
1110 # dooms us to only ever be "quasi-BFS" rather than full BFS.
1111 new_vars = empty_data.expandWithRefs(expression, None).references - set(seen)
1112
1113 queue.extend(new_vars)
1114 seen.update(new_vars)
1115 return ret
1116
1117
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001118def cpu_count():
1119 return multiprocessing.cpu_count()
1120
1121def nonblockingfd(fd):
1122 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1123
1124def process_profilelog(fn, pout = None):
1125 # Either call with a list of filenames and set pout or a filename and optionally pout.
1126 if not pout:
1127 pout = fn + '.processed'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001128
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001129 with open(pout, 'w') as pout:
1130 import pstats
1131 if isinstance(fn, list):
1132 p = pstats.Stats(*fn, stream=pout)
1133 else:
1134 p = pstats.Stats(fn, stream=pout)
1135 p.sort_stats('time')
1136 p.print_stats()
1137 p.print_callers()
1138 p.sort_stats('cumulative')
1139 p.print_stats()
1140
1141 pout.flush()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001142
1143#
1144# Was present to work around multiprocessing pool bugs in python < 2.7.3
1145#
1146def multiprocessingpool(*args, **kwargs):
1147
1148 import multiprocessing.pool
1149 #import multiprocessing.util
1150 #multiprocessing.util.log_to_stderr(10)
1151 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1152 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1153 def wrapper(func):
1154 def wrap(self, timeout=None):
1155 return func(self, timeout=timeout if timeout is not None else 1e100)
1156 return wrap
1157 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1158
1159 return multiprocessing.Pool(*args, **kwargs)
1160
1161def exec_flat_python_func(func, *args, **kwargs):
1162 """Execute a flat python function (defined with def funcname(args):...)"""
1163 # Prepare a small piece of python code which calls the requested function
1164 # To do this we need to prepare two things - a set of variables we can use to pass
1165 # the values of arguments into the calling function, and the list of arguments for
1166 # the function being called
1167 context = {}
1168 funcargs = []
1169 # Handle unnamed arguments
1170 aidx = 1
1171 for arg in args:
1172 argname = 'arg_%s' % aidx
1173 context[argname] = arg
1174 funcargs.append(argname)
1175 aidx += 1
1176 # Handle keyword arguments
1177 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001178 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001179 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1180 comp = bb.utils.better_compile(code, '<string>', '<string>')
1181 bb.utils.better_exec(comp, context, code, '<string>')
1182 return context['retval']
1183
1184def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1185 """Edit lines from a recipe or config file and modify one or more
1186 specified variable values set in the file using a specified callback
1187 function. Lines are expected to have trailing newlines.
1188 Parameters:
1189 meta_lines: lines from the file; can be a list or an iterable
1190 (e.g. file pointer)
1191 variables: a list of variable names to look for. Functions
1192 may also be specified, but must be specified with '()' at
1193 the end of the name. Note that the function doesn't have
Patrick Williams213cb262021-08-07 19:21:33 -05001194 any intrinsic understanding of :append, :prepend, :remove,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001195 or overrides, so these are considered as part of the name.
1196 These values go into a regular expression, so regular
1197 expression syntax is allowed.
1198 varfunc: callback function called for every variable matching
1199 one of the entries in the variables parameter. The function
1200 should take four arguments:
1201 varname: name of variable matched
1202 origvalue: current value in file
1203 op: the operator (e.g. '+=')
1204 newlines: list of lines up to this point. You can use
1205 this to prepend lines before this variable setting
1206 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001207 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001208 newvalue: new value to substitute in, or None to drop
1209 the variable setting entirely. (If the removal
1210 results in two consecutive blank lines, one of the
1211 blank lines will also be dropped).
1212 newop: the operator to use - if you specify None here,
1213 the original operation will be used.
1214 indent: number of spaces to indent multi-line entries,
1215 or -1 to indent up to the level of the assignment
1216 and opening quote, or a string to use as the indent.
1217 minbreak: True to allow the first element of a
1218 multi-line value to continue on the same line as
1219 the assignment, False to indent before the first
1220 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001221 To clarify, if you wish not to change the value, then you
1222 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001223 match_overrides: True to match items with _overrides on the end,
1224 False otherwise
1225 Returns a tuple:
1226 updated:
1227 True if changes were made, False otherwise.
1228 newlines:
1229 Lines after processing
1230 """
1231
1232 var_res = {}
1233 if match_overrides:
Brad Bishop19323692019-04-05 15:28:33 -04001234 override_re = r'(_[a-zA-Z0-9-_$(){}]+)?'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001235 else:
1236 override_re = ''
1237 for var in variables:
1238 if var.endswith('()'):
Brad Bishop19323692019-04-05 15:28:33 -04001239 var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001240 else:
Brad Bishop19323692019-04-05 15:28:33 -04001241 var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001242
1243 updated = False
1244 varset_start = ''
1245 varlines = []
1246 newlines = []
1247 in_var = None
1248 full_value = ''
1249 var_end = ''
1250
1251 def handle_var_end():
1252 prerun_newlines = newlines[:]
1253 op = varset_start[len(in_var):].strip()
1254 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1255 changed = (prerun_newlines != newlines)
1256
1257 if newvalue is None:
1258 # Drop the value
1259 return True
1260 elif newvalue != full_value or (newop not in [None, op]):
1261 if newop not in [None, op]:
1262 # Callback changed the operator
1263 varset_new = "%s %s" % (in_var, newop)
1264 else:
1265 varset_new = varset_start
1266
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001267 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001268 if indent == -1:
1269 indentspc = ' ' * (len(varset_new) + 2)
1270 else:
1271 indentspc = ' ' * indent
1272 else:
1273 indentspc = indent
1274 if in_var.endswith('()'):
1275 # A function definition
1276 if isinstance(newvalue, list):
1277 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1278 else:
1279 if not newvalue.startswith('\n'):
1280 newvalue = '\n' + newvalue
1281 if not newvalue.endswith('\n'):
1282 newvalue = newvalue + '\n'
1283 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1284 else:
1285 # Normal variable
1286 if isinstance(newvalue, list):
1287 if not newvalue:
1288 # Empty list -> empty string
1289 newlines.append('%s ""\n' % varset_new)
1290 elif minbreak:
1291 # First item on first line
1292 if len(newvalue) == 1:
1293 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1294 else:
1295 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1296 for item in newvalue[1:]:
1297 newlines.append('%s%s \\\n' % (indentspc, item))
1298 newlines.append('%s"\n' % indentspc)
1299 else:
1300 # No item on first line
1301 newlines.append('%s " \\\n' % varset_new)
1302 for item in newvalue:
1303 newlines.append('%s%s \\\n' % (indentspc, item))
1304 newlines.append('%s"\n' % indentspc)
1305 else:
1306 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1307 return True
1308 else:
1309 # Put the old lines back where they were
1310 newlines.extend(varlines)
1311 # If newlines was touched by the function, we'll need to return True
1312 return changed
1313
1314 checkspc = False
1315
1316 for line in meta_lines:
1317 if in_var:
1318 value = line.rstrip()
1319 varlines.append(line)
1320 if in_var.endswith('()'):
1321 full_value += '\n' + value
1322 else:
1323 full_value += value[:-1]
1324 if value.endswith(var_end):
1325 if in_var.endswith('()'):
1326 if full_value.count('{') - full_value.count('}') >= 0:
1327 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001328 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001329 if handle_var_end():
1330 updated = True
1331 checkspc = True
1332 in_var = None
1333 else:
1334 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001335 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001336 res = var_re.match(line)
1337 if res:
1338 isfunc = varname.endswith('()')
1339 if isfunc:
1340 splitvalue = line.split('{', 1)
1341 var_end = '}'
1342 else:
1343 var_end = res.groups()[-1]
1344 splitvalue = line.split(var_end, 1)
1345 varset_start = splitvalue[0].rstrip()
1346 value = splitvalue[1].rstrip()
1347 if not isfunc and value.endswith('\\'):
1348 value = value[:-1]
1349 full_value = value
1350 varlines = [line]
1351 in_var = res.group(1)
1352 if isfunc:
1353 in_var += '()'
1354 if value.endswith(var_end):
1355 full_value = full_value[:-1]
1356 if handle_var_end():
1357 updated = True
1358 checkspc = True
1359 in_var = None
1360 skip = True
1361 break
1362 if not skip:
1363 if checkspc:
1364 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001365 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001366 # Squash blank line if there are two consecutive blanks after a removal
1367 continue
1368 newlines.append(line)
1369 return (updated, newlines)
1370
1371
1372def edit_metadata_file(meta_file, variables, varfunc):
1373 """Edit a recipe or config file and modify one or more specified
1374 variable values set in the file using a specified callback function.
1375 The file is only written to if the value(s) actually change.
1376 This is basically the file version of edit_metadata(), see that
1377 function's description for parameter/usage information.
1378 Returns True if the file was written to, False otherwise.
1379 """
1380 with open(meta_file, 'r') as f:
1381 (updated, newlines) = edit_metadata(f, variables, varfunc)
1382 if updated:
1383 with open(meta_file, 'w') as f:
1384 f.writelines(newlines)
1385 return updated
1386
1387
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001388def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001389 """Edit bblayers.conf, adding and/or removing layers
1390 Parameters:
1391 bblayers_conf: path to bblayers.conf file to edit
1392 add: layer path (or list of layer paths) to add; None or empty
1393 list to add nothing
1394 remove: layer path (or list of layer paths) to remove; None or
1395 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001396 edit_cb: optional callback function that will be called after
1397 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001398 Returns a tuple:
1399 notadded: list of layers specified to be added but weren't
1400 (because they were already in the list)
1401 notremoved: list of layers that were specified to be removed
1402 but weren't (because they weren't in the list)
1403 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001404
1405 import fnmatch
1406
1407 def remove_trailing_sep(pth):
1408 if pth and pth[-1] == os.sep:
1409 pth = pth[:-1]
1410 return pth
1411
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001412 approved = bb.utils.approved_variables()
1413 def canonicalise_path(pth):
1414 pth = remove_trailing_sep(pth)
1415 if 'HOME' in approved and '~' in pth:
1416 pth = os.path.expanduser(pth)
1417 return pth
1418
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001419 def layerlist_param(value):
1420 if not value:
1421 return []
1422 elif isinstance(value, list):
1423 return [remove_trailing_sep(x) for x in value]
1424 else:
1425 return [remove_trailing_sep(value)]
1426
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001427 addlayers = layerlist_param(add)
1428 removelayers = layerlist_param(remove)
1429
1430 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1431 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001432 removed = []
1433 plusequals = False
1434 orig_bblayers = []
1435
1436 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1437 bblayercalls.append(op)
1438 if op == '=':
1439 del orig_bblayers[:]
1440 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1441 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001442
1443 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001444 updated = False
1445 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1446 if removelayers:
1447 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001448 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001449 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001450 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001451 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001452 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001453 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001454 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001455 for addlayer in addlayers:
1456 if addlayer not in bblayers:
1457 updated = True
1458 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001459 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001460
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001461 if edit_cb:
1462 newlist = []
1463 for layer in bblayers:
1464 res = edit_cb(layer, canonicalise_path(layer))
1465 if res != layer:
1466 newlist.append(res)
1467 updated = True
1468 else:
1469 newlist.append(layer)
1470 bblayers = newlist
1471
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001472 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001473 if op == '+=' and not bblayers:
1474 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001475 return (bblayers, None, 2, False)
1476 else:
1477 return (origvalue, None, 2, False)
1478
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001479 with open(bblayers_conf, 'r') as f:
1480 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001481
1482 if not bblayercalls:
1483 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1484
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001485 # Try to do the "smart" thing depending on how the user has laid out
1486 # their bblayers.conf file
1487 if bblayercalls.count('+=') > 1:
1488 plusequals = True
1489
1490 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1491 notadded = []
1492 for layer in addlayers:
1493 layer_canon = canonicalise_path(layer)
1494 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1495 notadded.append(layer)
1496 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1497 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1498
1499 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1500 if addlayers:
1501 # Still need to add these
1502 for addlayer in addlayers:
1503 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1504 updated = True
1505
1506 if updated:
1507 with open(bblayers_conf, 'w') as f:
1508 f.writelines(newlines)
1509
1510 notremoved = list(set(removelayers) - set(removed))
1511
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001512 return (notadded, notremoved)
1513
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001514def get_collection_res(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001515 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001516 collection_res = {}
1517 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001518 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001519
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001520 return collection_res
1521
1522
1523def get_file_layer(filename, d, collection_res={}):
1524 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
1525 if not collection_res:
1526 collection_res = get_collection_res(d)
1527
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001528 def path_to_layer(path):
1529 # Use longest path so we handle nested layers
1530 matchlen = 0
1531 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001532 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001533 if len(regex) > matchlen and re.match(regex, path):
1534 matchlen = len(regex)
1535 match = collection
1536 return match
1537
1538 result = None
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001539 bbfiles = (d.getVar('BBFILES_PRIORITIZED') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001540 bbfilesmatch = False
1541 for bbfilesentry in bbfiles:
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001542 if fnmatch.fnmatchcase(filename, bbfilesentry):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001543 bbfilesmatch = True
1544 result = path_to_layer(bbfilesentry)
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001545 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001546
1547 if not bbfilesmatch:
1548 # Probably a bbclass
1549 result = path_to_layer(filename)
1550
1551 return result
1552
1553
1554# Constant taken from http://linux.die.net/include/linux/prctl.h
1555PR_SET_PDEATHSIG = 1
1556
1557class PrCtlError(Exception):
1558 pass
1559
1560def signal_on_parent_exit(signame):
1561 """
1562 Trigger signame to be sent when the parent process dies
1563 """
1564 signum = getattr(signal, signame)
1565 # http://linux.die.net/man/2/prctl
1566 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1567 if result != 0:
1568 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001569
1570#
1571# Manually call the ioprio syscall. We could depend on other libs like psutil
1572# however this gets us enough of what we need to bitbake for now without the
1573# dependency
1574#
1575_unamearch = os.uname()[4]
1576IOPRIO_WHO_PROCESS = 1
1577IOPRIO_CLASS_SHIFT = 13
1578
1579def ioprio_set(who, cls, value):
1580 NR_ioprio_set = None
1581 if _unamearch == "x86_64":
1582 NR_ioprio_set = 251
1583 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1584 NR_ioprio_set = 289
Brad Bishop19323692019-04-05 15:28:33 -04001585 elif _unamearch == "aarch64":
1586 NR_ioprio_set = 30
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001587
1588 if NR_ioprio_set:
1589 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1590 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1591 if rc != 0:
1592 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1593 else:
1594 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001595
1596def set_process_name(name):
1597 from ctypes import cdll, byref, create_string_buffer
1598 # This is nice to have for debugging, not essential
1599 try:
1600 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001601 buf = create_string_buffer(bytes(name, 'utf-8'))
1602 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001603 except:
1604 pass
1605
Andrew Geissler595f6302022-01-24 19:11:47 +00001606def disable_network(uid=None, gid=None):
1607 """
1608 Disable networking in the current process if the kernel supports it, else
1609 just return after logging to debug. To do this we need to create a new user
1610 namespace, then map back to the original uid/gid.
1611 """
1612 libc = ctypes.CDLL('libc.so.6')
1613
1614 # From sched.h
1615 # New user namespace
1616 CLONE_NEWUSER = 0x10000000
1617 # New network namespace
1618 CLONE_NEWNET = 0x40000000
1619
1620 if uid is None:
1621 uid = os.getuid()
1622 if gid is None:
1623 gid = os.getgid()
1624
1625 ret = libc.unshare(CLONE_NEWNET | CLONE_NEWUSER)
1626 if ret != 0:
1627 logger.debug("System doesn't suport disabling network without admin privs")
1628 return
1629 with open("/proc/self/uid_map", "w") as f:
1630 f.write("%s %s 1" % (uid, uid))
1631 with open("/proc/self/setgroups", "w") as f:
1632 f.write("deny")
1633 with open("/proc/self/gid_map", "w") as f:
1634 f.write("%s %s 1" % (gid, gid))
1635
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001636def export_proxies(d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001637 """ export common proxies variables from datastore to environment """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001638 import os
1639
1640 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001641 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1642 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001643 exported = False
1644
1645 for v in variables:
1646 if v in os.environ.keys():
1647 exported = True
1648 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001649 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001650 if v_proxy is not None:
1651 os.environ[v] = v_proxy
1652 exported = True
1653
1654 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001655
1656
1657def load_plugins(logger, plugins, pluginpath):
1658 def load_plugin(name):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001659 logger.debug('Loading plugin %s' % name)
Brad Bishop19323692019-04-05 15:28:33 -04001660 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1661 if spec:
Andrew Geissler595f6302022-01-24 19:11:47 +00001662 mod = importlib.util.module_from_spec(spec)
1663 spec.loader.exec_module(mod)
1664 return mod
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001665
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001666 logger.debug('Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001667
1668 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1669 for ext in python_extensions)
1670 files = itertools.chain.from_iterable(expanded)
1671 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1672 for name in names:
1673 if name != '__init__':
1674 plugin = load_plugin(name)
1675 if hasattr(plugin, 'plugin_init'):
1676 obj = plugin.plugin_init(plugins)
1677 plugins.append(obj or plugin)
1678 else:
1679 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001680
1681
1682class LogCatcher(logging.Handler):
1683 """Logging handler for collecting logged messages so you can check them later"""
1684 def __init__(self):
1685 self.messages = []
1686 logging.Handler.__init__(self, logging.WARNING)
1687 def emit(self, record):
1688 self.messages.append(bb.build.logformatter.format(record))
1689 def contains(self, message):
1690 return (message in self.messages)
Andrew Geissler82c905d2020-04-13 13:39:40 -05001691
1692def is_semver(version):
1693 """
1694 Is the version string following the semver semantic?
1695
1696 https://semver.org/spec/v2.0.0.html
1697 """
1698 regex = re.compile(
1699 r"""
1700 ^
1701 (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
1702 (?:-(
1703 (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
1704 (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
1705 ))?
1706 (?:\+(
1707 [0-9a-zA-Z-]+
1708 (?:\.[0-9a-zA-Z-]+)*
1709 ))?
1710 $
1711 """, re.VERBOSE)
1712
1713 if regex.match(version) is None:
1714 return False
1715
1716 return True
Andrew Geisslerc926e172021-05-07 16:11:35 -05001717
1718# Wrapper around os.rename which can handle cross device problems
1719# e.g. from container filesystems
1720def rename(src, dst):
1721 try:
1722 os.rename(src, dst)
1723 except OSError as err:
1724 if err.errno == 18:
1725 # Invalid cross-device link error
1726 shutil.move(src, dst)
1727 else:
1728 raise err
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001729
1730@contextmanager
1731def environment(**envvars):
1732 """
1733 Context manager to selectively update the environment with the specified mapping.
1734 """
1735 backup = dict(os.environ)
1736 try:
1737 os.environ.update(envvars)
1738 yield
1739 finally:
1740 for var in envvars:
1741 if var in backup:
1742 os.environ[var] = backup[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001743 elif var in os.environ:
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001744 del os.environ[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001745
1746def is_local_uid(uid=''):
1747 """
1748 Check whether uid is a local one or not.
1749 Can't use pwd module since it gets all UIDs, not local ones only.
1750 """
1751 if not uid:
1752 uid = os.getuid()
1753 with open('/etc/passwd', 'r') as f:
1754 for line in f:
1755 line_split = line.split(':')
1756 if len(line_split) < 3:
1757 continue
1758 if str(uid) == line_split[2]:
1759 return True
1760 return False
Patrick Williams92b42cb2022-09-03 06:53:57 -05001761
1762def mkstemp(suffix=None, prefix=None, dir=None, text=False):
1763 """
1764 Generates a unique filename, independent of time.
1765
1766 mkstemp() in glibc (at least) generates unique file names based on the
1767 current system time. When combined with highly parallel builds, and
1768 operating over NFS (e.g. shared sstate/downloads) this can result in
1769 conflicts and race conditions.
1770
1771 This function adds additional entropy to the file name so that a collision
1772 is independent of time and thus extremely unlikely.
1773 """
1774 entropy = "".join(random.choices("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", k=20))
1775 if prefix:
1776 prefix = prefix + entropy
1777 else:
1778 prefix = tempfile.gettempprefix() + entropy
1779 return tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir, text=text)