blob: 64a004d0d8c10e1a8c04010268725baeee69e6a5 [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
Andrew Geissler87f5cff2022-09-30 13:13:31 -050032import socket
33import struct
Patrick Williams92b42cb2022-09-03 06:53:57 -050034import tempfile
Patrick Williamsc0f7c042017-02-23 20:41:17 -060035from subprocess import getstatusoutput
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036from contextlib import contextmanager
37from ctypes import cdll
38
Patrick Williamsc124f4f2015-09-15 14:41:29 -050039logger = logging.getLogger("BitBake.Util")
Brad Bishop19323692019-04-05 15:28:33 -040040python_extensions = importlib.machinery.all_suffixes()
Patrick Williamsc0f7c042017-02-23 20:41:17 -060041
Patrick Williamsc124f4f2015-09-15 14:41:29 -050042
43def clean_context():
44 return {
45 "os": os,
46 "bb": bb,
47 "time": time,
48 }
49
50def get_context():
51 return _context
52
53
54def set_context(ctx):
55 _context = ctx
56
57# Context used in better_exec, eval
58_context = clean_context()
59
60class VersionStringException(Exception):
61 """Exception raised when an invalid version specification is found"""
62
63def explode_version(s):
64 r = []
Brad Bishop19323692019-04-05 15:28:33 -040065 alpha_regexp = re.compile(r'^([a-zA-Z]+)(.*)$')
66 numeric_regexp = re.compile(r'^(\d+)(.*)$')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050067 while (s != ''):
68 if s[0] in string.digits:
69 m = numeric_regexp.match(s)
70 r.append((0, int(m.group(1))))
71 s = m.group(2)
72 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -060073 if s[0] in string.ascii_letters:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050074 m = alpha_regexp.match(s)
75 r.append((1, m.group(1)))
76 s = m.group(2)
77 continue
78 if s[0] == '~':
79 r.append((-1, s[0]))
80 else:
81 r.append((2, s[0]))
82 s = s[1:]
83 return r
84
85def split_version(s):
86 """Split a version string into its constituent parts (PE, PV, PR)"""
87 s = s.strip(" <>=")
88 e = 0
89 if s.count(':'):
90 e = int(s.split(":")[0])
91 s = s.split(":")[1]
92 r = ""
93 if s.count('-'):
94 r = s.rsplit("-", 1)[1]
95 s = s.rsplit("-", 1)[0]
96 v = s
97 return (e, v, r)
98
99def vercmp_part(a, b):
100 va = explode_version(a)
101 vb = explode_version(b)
102 while True:
103 if va == []:
104 (oa, ca) = (0, None)
105 else:
106 (oa, ca) = va.pop(0)
107 if vb == []:
108 (ob, cb) = (0, None)
109 else:
110 (ob, cb) = vb.pop(0)
111 if (oa, ca) == (0, None) and (ob, cb) == (0, None):
112 return 0
113 if oa < ob:
114 return -1
115 elif oa > ob:
116 return 1
Brad Bishop19323692019-04-05 15:28:33 -0400117 elif ca is None:
118 return -1
119 elif cb is None:
120 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500121 elif ca < cb:
122 return -1
123 elif ca > cb:
124 return 1
125
126def vercmp(ta, tb):
127 (ea, va, ra) = ta
128 (eb, vb, rb) = tb
129
130 r = int(ea or 0) - int(eb or 0)
131 if (r == 0):
132 r = vercmp_part(va, vb)
133 if (r == 0):
134 r = vercmp_part(ra, rb)
135 return r
136
137def vercmp_string(a, b):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600138 """ Split version strings and compare them """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500139 ta = split_version(a)
140 tb = split_version(b)
141 return vercmp(ta, tb)
142
143def vercmp_string_op(a, b, op):
144 """
145 Compare two versions and check if the specified comparison operator matches the result of the comparison.
146 This function is fairly liberal about what operators it will accept since there are a variety of styles
147 depending on the context.
148 """
149 res = vercmp_string(a, b)
150 if op in ('=', '=='):
151 return res == 0
152 elif op == '<=':
153 return res <= 0
154 elif op == '>=':
155 return res >= 0
156 elif op in ('>', '>>'):
157 return res > 0
158 elif op in ('<', '<<'):
159 return res < 0
160 elif op == '!=':
161 return res != 0
162 else:
163 raise VersionStringException('Unsupported comparison operator "%s"' % op)
164
165def explode_deps(s):
166 """
167 Take an RDEPENDS style string of format:
168 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
169 and return a list of dependencies.
170 Version information is ignored.
171 """
172 r = []
173 l = s.split()
174 flag = False
175 for i in l:
176 if i[0] == '(':
177 flag = True
178 #j = []
179 if not flag:
180 r.append(i)
181 #else:
182 # j.append(i)
183 if flag and i.endswith(')'):
184 flag = False
185 # Ignore version
186 #r[-1] += ' ' + ' '.join(j)
187 return r
188
Brad Bishop316dfdd2018-06-25 12:45:53 -0400189def explode_dep_versions2(s, *, sort=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500190 """
191 Take an RDEPENDS style string of format:
192 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
193 and return a dictionary of dependencies and versions.
194 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600195 r = collections.OrderedDict()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500196 l = s.replace(",", "").split()
197 lastdep = None
198 lastcmp = ""
199 lastver = ""
200 incmp = False
201 inversion = False
202 for i in l:
203 if i[0] == '(':
204 incmp = True
205 i = i[1:].strip()
206 if not i:
207 continue
208
209 if incmp:
210 incmp = False
211 inversion = True
212 # This list is based on behavior and supported comparisons from deb, opkg and rpm.
213 #
214 # Even though =<, <<, ==, !=, =>, and >> may not be supported,
215 # we list each possibly valid item.
216 # The build system is responsible for validation of what it supports.
217 if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')):
218 lastcmp = i[0:2]
219 i = i[2:]
220 elif i.startswith(('<', '>', '=')):
221 lastcmp = i[0:1]
222 i = i[1:]
223 else:
224 # This is an unsupported case!
225 raise VersionStringException('Invalid version specification in "(%s" - invalid or missing operator' % i)
226 lastcmp = (i or "")
227 i = ""
228 i.strip()
229 if not i:
230 continue
231
232 if inversion:
233 if i.endswith(')'):
234 i = i[:-1] or ""
235 inversion = False
236 if lastver and i:
237 lastver += " "
238 if i:
239 lastver += i
240 if lastdep not in r:
241 r[lastdep] = []
242 r[lastdep].append(lastcmp + " " + lastver)
243 continue
244
245 #if not inversion:
246 lastdep = i
247 lastver = ""
248 lastcmp = ""
249 if not (i in r and r[i]):
250 r[lastdep] = []
251
Brad Bishop316dfdd2018-06-25 12:45:53 -0400252 if sort:
253 r = collections.OrderedDict(sorted(r.items(), key=lambda x: x[0]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254 return r
255
256def explode_dep_versions(s):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600257 """
258 Take an RDEPENDS style string of format:
259 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000260 skip null value and items appeared in dependency string multiple times
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600261 and return a dictionary of dependencies and versions.
262 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500263 r = explode_dep_versions2(s)
264 for d in r:
265 if not r[d]:
266 r[d] = None
267 continue
268 if len(r[d]) > 1:
269 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))
270 r[d] = r[d][0]
271 return r
272
273def join_deps(deps, commasep=True):
274 """
275 Take the result from explode_dep_versions and generate a dependency string
276 """
277 result = []
278 for dep in deps:
279 if deps[dep]:
280 if isinstance(deps[dep], list):
281 for v in deps[dep]:
282 result.append(dep + " (" + v + ")")
283 else:
284 result.append(dep + " (" + deps[dep] + ")")
285 else:
286 result.append(dep)
287 if commasep:
288 return ", ".join(result)
289 else:
290 return " ".join(result)
291
292def _print_trace(body, line):
293 """
294 Print the Environment of a Text Body
295 """
296 error = []
297 # print the environment of the method
298 min_line = max(1, line-4)
299 max_line = min(line + 4, len(body))
300 for i in range(min_line, max_line + 1):
301 if line == i:
302 error.append(' *** %.4d:%s' % (i, body[i-1].rstrip()))
303 else:
304 error.append(' %.4d:%s' % (i, body[i-1].rstrip()))
305 return error
306
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500307def better_compile(text, file, realfile, mode = "exec", lineno = 0):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500308 """
309 A better compile method. This method
310 will print the offending lines.
311 """
312 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500313 cache = bb.methodpool.compile_cache(text)
314 if cache:
315 return cache
316 # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though
317 text2 = "\n" * int(lineno) + text
318 code = compile(text2, realfile, mode)
319 bb.methodpool.compile_cache_add(text, code)
320 return code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500321 except Exception as e:
322 error = []
323 # split the text into lines again
324 body = text.split('\n')
Brad Bishop19323692019-04-05 15:28:33 -0400325 error.append("Error in compiling python function in %s, line %s:\n" % (realfile, e.lineno))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500326 if hasattr(e, "lineno"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500327 error.append("The code lines resulting in this error were:")
Brad Bishop19323692019-04-05 15:28:33 -0400328 # e.lineno: line's position in reaflile
329 # lineno: function name's "position -1" in realfile
330 # e.lineno - lineno: line's relative position in function
331 error.extend(_print_trace(body, e.lineno - lineno))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500332 else:
333 error.append("The function causing this error was:")
334 for line in body:
335 error.append(line)
336 error.append("%s: %s" % (e.__class__.__name__, str(e)))
337
338 logger.error("\n".join(error))
339
340 e = bb.BBHandledException(e)
341 raise e
342
343def _print_exception(t, value, tb, realfile, text, context):
344 error = []
345 try:
346 exception = traceback.format_exception_only(t, value)
347 error.append('Error executing a python function in %s:\n' % realfile)
348
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500349 # Strip 'us' from the stack (better_exec call) unless that was where the
350 # error came from
351 if tb.tb_next is not None:
352 tb = tb.tb_next
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500353
354 textarray = text.split('\n')
355
356 linefailed = tb.tb_lineno
357
358 tbextract = traceback.extract_tb(tb)
359 tbformat = traceback.format_list(tbextract)
360 error.append("The stack trace of python calls that resulted in this exception/failure was:")
361 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2]))
362 error.extend(_print_trace(textarray, linefailed))
363
364 # See if this is a function we constructed and has calls back into other functions in
365 # "text". If so, try and improve the context of the error by diving down the trace
366 level = 0
367 nexttb = tb.tb_next
368 while nexttb is not None and (level+1) < len(tbextract):
369 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]))
370 if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]:
371 # The code was possibly in the string we compiled ourselves
372 error.extend(_print_trace(textarray, tbextract[level+1][1]))
373 elif tbextract[level+1][0].startswith("/"):
374 # The code looks like it might be in a file, try and load it
375 try:
376 with open(tbextract[level+1][0], "r") as f:
377 text = f.readlines()
378 error.extend(_print_trace(text, tbextract[level+1][1]))
379 except:
380 error.append(tbformat[level+1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500381 else:
382 error.append(tbformat[level+1])
383 nexttb = tb.tb_next
384 level = level + 1
385
386 error.append("Exception: %s" % ''.join(exception))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600387
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000388 # If the exception is from spawning a task, let's be helpful and display
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600389 # the output (which hopefully includes stderr).
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500390 if isinstance(value, subprocess.CalledProcessError) and value.output:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600391 error.append("Subprocess output:")
392 error.append(value.output.decode("utf-8", errors="ignore"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500393 finally:
394 logger.error("\n".join(error))
395
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500396def better_exec(code, context, text = None, realfile = "<code>", pythonexception=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500397 """
398 Similiar to better_compile, better_exec will
399 print the lines that are responsible for the
400 error.
401 """
402 import bb.parse
403 if not text:
404 text = code
405 if not hasattr(code, "co_filename"):
406 code = better_compile(code, realfile, realfile)
407 try:
408 exec(code, get_context(), context)
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000409 except (bb.BBHandledException, bb.parse.SkipRecipe, bb.data_smart.ExpansionError, bb.process.ExecutionError):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500410 # Error already shown so passthrough, no need for traceback
411 raise
412 except Exception as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500413 if pythonexception:
414 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500415 (t, value, tb) = sys.exc_info()
416 try:
417 _print_exception(t, value, tb, realfile, text, context)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500418 except Exception as e2:
419 logger.error("Exception handler error: %s" % str(e2))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500420
421 e = bb.BBHandledException(e)
422 raise e
423
424def simple_exec(code, context):
425 exec(code, get_context(), context)
426
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600427def better_eval(source, locals, extraglobals = None):
428 ctx = get_context()
429 if extraglobals:
430 ctx = copy.copy(ctx)
431 for g in extraglobals:
432 ctx[g] = extraglobals[g]
433 return eval(source, ctx, locals)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500434
435@contextmanager
Patrick Williams92b42cb2022-09-03 06:53:57 -0500436def fileslocked(files, *args, **kwargs):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500437 """Context manager for locking and unlocking file locks."""
438 locks = []
439 if files:
440 for lockfile in files:
Patrick Williams92b42cb2022-09-03 06:53:57 -0500441 l = bb.utils.lockfile(lockfile, *args, **kwargs)
442 if l is not None:
443 locks.append(l)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500444
Andrew Geissler82c905d2020-04-13 13:39:40 -0500445 try:
446 yield
447 finally:
448 for lock in locks:
449 bb.utils.unlockfile(lock)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500450
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500451def lockfile(name, shared=False, retry=True, block=False):
452 """
453 Use the specified file as a lock file, return when the lock has
454 been acquired. Returns a variable to pass to unlockfile().
455 Parameters:
456 retry: True to re-try locking if it fails, False otherwise
457 block: True to block until the lock succeeds, False otherwise
458 The retry and block parameters are kind of equivalent unless you
459 consider the possibility of sending a signal to the process to break
460 out - at which point you want block=True rather than retry=True.
461 """
Andrew Geissler9aee5002022-03-30 16:27:02 +0000462 basename = os.path.basename(name)
463 if len(basename) > 255:
464 root, ext = os.path.splitext(basename)
465 basename = root[:255 - len(ext)] + ext
Andrew Geissler595f6302022-01-24 19:11:47 +0000466
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500467 dirname = os.path.dirname(name)
468 mkdirhier(dirname)
469
Andrew Geissler9aee5002022-03-30 16:27:02 +0000470 name = os.path.join(dirname, basename)
471
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500472 if not os.access(dirname, os.W_OK):
473 logger.error("Unable to acquire lock '%s', directory is not writable",
474 name)
475 sys.exit(1)
476
477 op = fcntl.LOCK_EX
478 if shared:
479 op = fcntl.LOCK_SH
480 if not retry and not block:
481 op = op | fcntl.LOCK_NB
482
483 while True:
484 # If we leave the lockfiles lying around there is no problem
485 # but we should clean up after ourselves. This gives potential
486 # for races though. To work around this, when we acquire the lock
487 # we check the file we locked was still the lock file on disk.
488 # by comparing inode numbers. If they don't match or the lockfile
489 # no longer exists, we start again.
490
491 # This implementation is unfair since the last person to request the
492 # lock is the most likely to win it.
493
494 try:
495 lf = open(name, 'a+')
496 fileno = lf.fileno()
497 fcntl.flock(fileno, op)
498 statinfo = os.fstat(fileno)
499 if os.path.exists(lf.name):
500 statinfo2 = os.stat(lf.name)
501 if statinfo.st_ino == statinfo2.st_ino:
502 return lf
503 lf.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800504 except OSError as e:
Andrew Geissler595f6302022-01-24 19:11:47 +0000505 if e.errno == errno.EACCES or e.errno == errno.ENAMETOOLONG:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800506 logger.error("Unable to acquire lock '%s', %s",
507 e.strerror, name)
508 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500509 try:
510 lf.close()
511 except Exception:
512 pass
513 pass
514 if not retry:
515 return None
516
517def unlockfile(lf):
518 """
519 Unlock a file locked using lockfile()
520 """
521 try:
522 # If we had a shared lock, we need to promote to exclusive before
523 # removing the lockfile. Attempt this, ignore failures.
524 fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
525 os.unlink(lf.name)
526 except (IOError, OSError):
527 pass
528 fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
529 lf.close()
530
Brad Bishop6dbb3162019-11-25 09:41:34 -0500531def _hasher(method, filename):
532 import mmap
533
534 with open(filename, "rb") as f:
535 try:
536 with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
537 for chunk in iter(lambda: mm.read(8192), b''):
538 method.update(chunk)
539 except ValueError:
540 # You can't mmap() an empty file so silence this exception
541 pass
542 return method.hexdigest()
543
544
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500545def md5_file(filename):
546 """
547 Return the hex string representation of the MD5 checksum of filename.
548 """
Brad Bishop6dbb3162019-11-25 09:41:34 -0500549 import hashlib
Patrick Williams2390b1b2022-11-03 13:47:49 -0500550 try:
551 sig = hashlib.new('MD5', usedforsecurity=False)
552 except TypeError:
553 # Some configurations don't appear to support two arguments
554 sig = hashlib.new('MD5')
555 return _hasher(sig, filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500556
557def sha256_file(filename):
558 """
559 Return the hex string representation of the 256-bit SHA checksum of
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500560 filename.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500561 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500562 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500563 return _hasher(hashlib.sha256(), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500564
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500565def sha1_file(filename):
566 """
567 Return the hex string representation of the SHA1 checksum of the filename
568 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500569 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500570 return _hasher(hashlib.sha1(), filename)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500571
Andrew Geissler82c905d2020-04-13 13:39:40 -0500572def sha384_file(filename):
573 """
574 Return the hex string representation of the SHA384 checksum of the filename
575 """
576 import hashlib
577 return _hasher(hashlib.sha384(), filename)
578
579def sha512_file(filename):
580 """
581 Return the hex string representation of the SHA512 checksum of the filename
582 """
583 import hashlib
584 return _hasher(hashlib.sha512(), filename)
585
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500586def preserved_envvars_exported():
587 """Variables which are taken from the environment and placed in and exported
588 from the metadata"""
589 return [
590 'BB_TASKHASH',
591 'HOME',
592 'LOGNAME',
593 'PATH',
594 'PWD',
595 'SHELL',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500596 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600597 'LC_ALL',
598 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500599 ]
600
601def preserved_envvars():
602 """Variables which are taken from the environment and placed in the metadata"""
603 v = [
604 'BBPATH',
605 'BB_PRESERVE_ENV',
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000606 'BB_ENV_PASSTHROUGH',
607 'BB_ENV_PASSTHROUGH_ADDITIONS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500608 ]
609 return v + preserved_envvars_exported()
610
611def filter_environment(good_vars):
612 """
613 Create a pristine environment for bitbake. This will remove variables that
614 are not known and may influence the build in a negative way.
615 """
616
617 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600618 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500619 if key in good_vars:
620 continue
621
622 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500623 del os.environ[key]
624
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600625 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
626 # access methods will use ascii. You can't change that mode once the interpreter is
627 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
628 # distros support that and we need to set something.
629 os.environ["LC_ALL"] = "en_US.UTF-8"
630
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500631 if removed_vars:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600632 logger.debug("Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500633
634 return removed_vars
635
636def approved_variables():
637 """
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000638 Determine and return the list of variables which are approved
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500639 to remain in the environment.
640 """
641 if 'BB_PRESERVE_ENV' in os.environ:
642 return os.environ.keys()
643 approved = []
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000644 if 'BB_ENV_PASSTHROUGH' in os.environ:
645 approved = os.environ['BB_ENV_PASSTHROUGH'].split()
646 approved.extend(['BB_ENV_PASSTHROUGH'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500647 else:
648 approved = preserved_envvars()
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000649 if 'BB_ENV_PASSTHROUGH_ADDITIONS' in os.environ:
650 approved.extend(os.environ['BB_ENV_PASSTHROUGH_ADDITIONS'].split())
651 if 'BB_ENV_PASSTHROUGH_ADDITIONS' not in approved:
652 approved.extend(['BB_ENV_PASSTHROUGH_ADDITIONS'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500653 return approved
654
655def clean_environment():
656 """
657 Clean up any spurious environment variables. This will remove any
658 variables the user hasn't chosen to preserve.
659 """
660 if 'BB_PRESERVE_ENV' not in os.environ:
661 good_vars = approved_variables()
662 return filter_environment(good_vars)
663
664 return {}
665
666def empty_environment():
667 """
668 Remove all variables from the environment.
669 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600670 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500671 os.unsetenv(s)
672 del os.environ[s]
673
674def build_environment(d):
675 """
676 Build an environment from all exported variables.
677 """
678 import bb.data
679 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500680 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500681 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500682 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500683
684def _check_unsafe_delete_path(path):
685 """
686 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
687 the caller should raise an exception with an appropriate message.
688 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
689 with potentially disastrous results.
690 """
691 extra = ''
692 # HOME might not be /home/something, so in case we can get it, check against it
693 homedir = os.environ.get('HOME', '')
694 if homedir:
695 extra = '|%s' % homedir
696 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
697 return True
698 return False
699
Brad Bishopa34c0302019-09-23 22:34:48 -0400700def remove(path, recurse=False, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500701 """Equivalent to rm -f or rm -rf"""
702 if not path:
703 return
704 if recurse:
705 for name in glob.glob(path):
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500706 if _check_unsafe_delete_path(name):
707 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500708 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishopa34c0302019-09-23 22:34:48 -0400709 cmd = []
710 if ionice:
711 cmd = ['ionice', '-c', '3']
712 subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500713 return
714 for name in glob.glob(path):
715 try:
716 os.unlink(name)
717 except OSError as exc:
718 if exc.errno != errno.ENOENT:
719 raise
720
Brad Bishopa34c0302019-09-23 22:34:48 -0400721def prunedir(topdir, ionice=False):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600722 """ Delete everything reachable from the directory named in 'topdir'. """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500723 # CAUTION: This is dangerous!
724 if _check_unsafe_delete_path(topdir):
725 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
Brad Bishopa34c0302019-09-23 22:34:48 -0400726 remove(topdir, recurse=True, ionice=ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500727
728#
729# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
730# but thats possibly insane and suffixes is probably going to be small
731#
732def prune_suffix(var, suffixes, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600733 """
734 See if var ends with any of the suffixes listed and
735 remove it if found
736 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500737 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -0400738 if suffix and var.endswith(suffix):
739 return var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500740 return var
741
742def mkdirhier(directory):
743 """Create a directory like 'mkdir -p', but does not complain if
744 directory already exists like os.makedirs
745 """
746
747 try:
748 os.makedirs(directory)
749 except OSError as e:
Brad Bishopc342db32019-05-15 21:57:59 -0400750 if e.errno != errno.EEXIST or not os.path.isdir(directory):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500751 raise e
752
753def movefile(src, dest, newmtime = None, sstat = None):
754 """Moves a file from src to dest, preserving all permissions and
755 attributes; mtime will be preserved even when moving across
756 filesystems. Returns true on success and false on failure. Move is
757 atomic.
758 """
759
760 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
761 try:
762 if not sstat:
763 sstat = os.lstat(src)
764 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500765 logger.warning("movefile: Stating source file failed...", e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500766 return None
767
768 destexists = 1
769 try:
770 dstat = os.lstat(dest)
771 except:
772 dstat = os.lstat(os.path.dirname(dest))
773 destexists = 0
774
775 if destexists:
776 if stat.S_ISLNK(dstat[stat.ST_MODE]):
777 try:
778 os.unlink(dest)
779 destexists = 0
780 except Exception as e:
781 pass
782
783 if stat.S_ISLNK(sstat[stat.ST_MODE]):
784 try:
785 target = os.readlink(src)
786 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
787 os.unlink(dest)
788 os.symlink(target, dest)
789 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
790 os.unlink(src)
791 return os.lstat(dest)
792 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500793 logger.warning("movefile: failed to properly create symlink:", dest, "->", target, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500794 return None
795
796 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500797 # os.rename needs to know the dest path ending with file name
798 # so append the file name to a path only if it's a dir specified
799 srcfname = os.path.basename(src)
800 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
801 else dest
802
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500803 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
804 try:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500805 bb.utils.rename(src, destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500806 renamefailed = 0
807 except Exception as e:
Brad Bishop79641f22019-09-10 07:20:22 -0400808 if e.errno != errno.EXDEV:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500809 # Some random error.
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500810 logger.warning("movefile: Failed to move", src, "to", dest, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500811 return None
812 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
813
814 if renamefailed:
815 didcopy = 0
816 if stat.S_ISREG(sstat[stat.ST_MODE]):
817 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500818 shutil.copyfile(src, destpath + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500819 bb.utils.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500820 didcopy = 1
821 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500822 logger.warning('movefile: copy', src, '->', dest, 'failed.', e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500823 return None
824 else:
825 #we don't yet handle special, so we need to fall back to /bin/mv
826 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
827 if a[0] != 0:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500828 logger.warning("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500829 return None # failure
830 try:
831 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400832 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
833 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500834 os.unlink(src)
835 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500836 logger.warning("movefile: Failed to chown/chmod/unlink", dest, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500837 return None
838
839 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500840 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500841 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500842 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500843 newmtime = sstat[stat.ST_MTIME]
844 return newmtime
845
846def copyfile(src, dest, newmtime = None, sstat = None):
847 """
848 Copies a file from src to dest, preserving all permissions and
849 attributes; mtime will be preserved even when moving across
850 filesystems. Returns true on success and false on failure.
851 """
852 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
853 try:
854 if not sstat:
855 sstat = os.lstat(src)
856 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600857 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500858 return False
859
860 destexists = 1
861 try:
862 dstat = os.lstat(dest)
863 except:
864 dstat = os.lstat(os.path.dirname(dest))
865 destexists = 0
866
867 if destexists:
868 if stat.S_ISLNK(dstat[stat.ST_MODE]):
869 try:
870 os.unlink(dest)
871 destexists = 0
872 except Exception as e:
873 pass
874
875 if stat.S_ISLNK(sstat[stat.ST_MODE]):
876 try:
877 target = os.readlink(src)
878 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
879 os.unlink(dest)
880 os.symlink(target, dest)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500881 os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500882 return os.lstat(dest)
883 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600884 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500885 return False
886
887 if stat.S_ISREG(sstat[stat.ST_MODE]):
888 try:
889 srcchown = False
890 if not os.access(src, os.R_OK):
891 # Make sure we can read it
892 srcchown = True
893 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
894
895 # For safety copy then move it over.
896 shutil.copyfile(src, dest + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500897 bb.utils.rename(dest + "#new", dest)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500898 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600899 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500900 return False
901 finally:
902 if srcchown:
903 os.chmod(src, sstat[stat.ST_MODE])
904 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
905
906 else:
907 #we don't yet handle special, so we need to fall back to /bin/mv
908 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
909 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600910 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500911 return False # failure
912 try:
913 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
914 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
915 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600916 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500917 return False
918
919 if newmtime:
920 os.utime(dest, (newmtime, newmtime))
921 else:
922 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
923 newmtime = sstat[stat.ST_MTIME]
924 return newmtime
925
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800926def break_hardlinks(src, sstat = None):
927 """
928 Ensures src is the only hardlink to this file. Other hardlinks,
929 if any, are not affected (other than in their st_nlink value, of
930 course). Returns true on success and false on failure.
931
932 """
933 try:
934 if not sstat:
935 sstat = os.lstat(src)
936 except Exception as e:
937 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
938 return False
939 if sstat[stat.ST_NLINK] == 1:
940 return True
941 return copyfile(src, src, sstat=sstat)
942
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500943def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500944 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500945 Locate `item` in the list of paths `path` (colon separated string like $PATH).
946 If `direction` is non-zero then the list is reversed.
947 If `history` is True then the list of candidates also returned as result,history.
948 If `executable` is True then the candidate has to be an executable file,
949 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500950 """
951
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500952 if executable:
953 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
954 else:
955 is_candidate = lambda p: os.path.exists(p)
956
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500957 hist = []
958 paths = (path or "").split(':')
959 if direction != 0:
960 paths.reverse()
961
962 for p in paths:
963 next = os.path.join(p, item)
964 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500965 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500966 if not os.path.isabs(next):
967 next = os.path.abspath(next)
968 if history:
969 return next, hist
970 return next
971
972 if history:
973 return "", hist
974 return ""
975
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500976@contextmanager
977def umask(new_mask):
978 """
979 Context manager to set the umask to a specific mask, and restore it afterwards.
980 """
981 current_mask = os.umask(new_mask)
982 try:
983 yield
984 finally:
985 os.umask(current_mask)
986
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500987def to_boolean(string, default=None):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600988 """
989 Check input string and return boolean value True/False/None
990 depending upon the checks
991 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500992 if not string:
993 return default
994
995 normalized = string.lower()
996 if normalized in ("y", "yes", "1", "true"):
997 return True
998 elif normalized in ("n", "no", "0", "false"):
999 return False
1000 else:
1001 raise ValueError("Invalid value for to_boolean: %s" % string)
1002
1003def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001004 """Check if a variable contains all the values specified.
1005
1006 Arguments:
1007
1008 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001009 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001010
1011 checkvalues -- if this is a string it is split on whitespace into a set(),
1012 otherwise coerced directly into a set().
1013
1014 truevalue -- the value to return if checkvalues is a subset of variable.
1015
1016 falsevalue -- the value to return if variable is empty or if checkvalues is
1017 not a subset of variable.
1018
1019 d -- the data store.
1020 """
1021
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001022 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001023 if not val:
1024 return falsevalue
1025 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001026 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001027 checkvalues = set(checkvalues.split())
1028 else:
1029 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001030 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001031 return truevalue
1032 return falsevalue
1033
1034def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001035 """Check if a variable contains any values specified.
1036
1037 Arguments:
1038
1039 variable -- the variable name. This will be fetched and expanded (using
1040 d.getVar(variable)) and then split into a set().
1041
1042 checkvalues -- if this is a string it is split on whitespace into a set(),
1043 otherwise coerced directly into a set().
1044
1045 truevalue -- the value to return if checkvalues is a subset of variable.
1046
1047 falsevalue -- the value to return if variable is empty or if checkvalues is
1048 not a subset of variable.
1049
1050 d -- the data store.
1051 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001052 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001053 if not val:
1054 return falsevalue
1055 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001056 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001057 checkvalues = set(checkvalues.split())
1058 else:
1059 checkvalues = set(checkvalues)
1060 if checkvalues & val:
1061 return truevalue
1062 return falsevalue
1063
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001064def filter(variable, checkvalues, d):
1065 """Return all words in the variable that are present in the checkvalues.
1066
1067 Arguments:
1068
1069 variable -- the variable name. This will be fetched and expanded (using
1070 d.getVar(variable)) and then split into a set().
1071
1072 checkvalues -- if this is a string it is split on whitespace into a set(),
1073 otherwise coerced directly into a set().
1074
1075 d -- the data store.
1076 """
1077
1078 val = d.getVar(variable)
1079 if not val:
1080 return ''
1081 val = set(val.split())
1082 if isinstance(checkvalues, str):
1083 checkvalues = set(checkvalues.split())
1084 else:
1085 checkvalues = set(checkvalues)
1086 return ' '.join(sorted(checkvalues & val))
1087
Andrew Geissler82c905d2020-04-13 13:39:40 -05001088
1089def get_referenced_vars(start_expr, d):
1090 """
1091 :return: names of vars referenced in start_expr (recursively), in quasi-BFS order (variables within the same level
1092 are ordered arbitrarily)
1093 """
1094
1095 seen = set()
1096 ret = []
1097
1098 # The first entry in the queue is the unexpanded start expression
1099 queue = collections.deque([start_expr])
1100 # Subsequent entries will be variable names, so we need to track whether or not entry requires getVar
1101 is_first = True
1102
1103 empty_data = bb.data.init()
1104 while queue:
1105 entry = queue.popleft()
1106 if is_first:
1107 # Entry is the start expression - no expansion needed
1108 is_first = False
1109 expression = entry
1110 else:
1111 # This is a variable name - need to get the value
1112 expression = d.getVar(entry, False)
1113 ret.append(entry)
1114
1115 # expandWithRefs is how we actually get the referenced variables in the expression. We call it using an empty
1116 # data store because we only want the variables directly used in the expression. It returns a set, which is what
1117 # dooms us to only ever be "quasi-BFS" rather than full BFS.
1118 new_vars = empty_data.expandWithRefs(expression, None).references - set(seen)
1119
1120 queue.extend(new_vars)
1121 seen.update(new_vars)
1122 return ret
1123
1124
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001125def cpu_count():
1126 return multiprocessing.cpu_count()
1127
1128def nonblockingfd(fd):
1129 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1130
1131def process_profilelog(fn, pout = None):
1132 # Either call with a list of filenames and set pout or a filename and optionally pout.
1133 if not pout:
1134 pout = fn + '.processed'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001135
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001136 with open(pout, 'w') as pout:
1137 import pstats
1138 if isinstance(fn, list):
1139 p = pstats.Stats(*fn, stream=pout)
1140 else:
1141 p = pstats.Stats(fn, stream=pout)
1142 p.sort_stats('time')
1143 p.print_stats()
1144 p.print_callers()
1145 p.sort_stats('cumulative')
1146 p.print_stats()
1147
1148 pout.flush()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001149
1150#
1151# Was present to work around multiprocessing pool bugs in python < 2.7.3
1152#
1153def multiprocessingpool(*args, **kwargs):
1154
1155 import multiprocessing.pool
1156 #import multiprocessing.util
1157 #multiprocessing.util.log_to_stderr(10)
1158 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1159 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1160 def wrapper(func):
1161 def wrap(self, timeout=None):
1162 return func(self, timeout=timeout if timeout is not None else 1e100)
1163 return wrap
1164 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1165
1166 return multiprocessing.Pool(*args, **kwargs)
1167
1168def exec_flat_python_func(func, *args, **kwargs):
1169 """Execute a flat python function (defined with def funcname(args):...)"""
1170 # Prepare a small piece of python code which calls the requested function
1171 # To do this we need to prepare two things - a set of variables we can use to pass
1172 # the values of arguments into the calling function, and the list of arguments for
1173 # the function being called
1174 context = {}
1175 funcargs = []
1176 # Handle unnamed arguments
1177 aidx = 1
1178 for arg in args:
1179 argname = 'arg_%s' % aidx
1180 context[argname] = arg
1181 funcargs.append(argname)
1182 aidx += 1
1183 # Handle keyword arguments
1184 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001185 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001186 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1187 comp = bb.utils.better_compile(code, '<string>', '<string>')
1188 bb.utils.better_exec(comp, context, code, '<string>')
1189 return context['retval']
1190
1191def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1192 """Edit lines from a recipe or config file and modify one or more
1193 specified variable values set in the file using a specified callback
1194 function. Lines are expected to have trailing newlines.
1195 Parameters:
1196 meta_lines: lines from the file; can be a list or an iterable
1197 (e.g. file pointer)
1198 variables: a list of variable names to look for. Functions
1199 may also be specified, but must be specified with '()' at
1200 the end of the name. Note that the function doesn't have
Patrick Williams213cb262021-08-07 19:21:33 -05001201 any intrinsic understanding of :append, :prepend, :remove,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001202 or overrides, so these are considered as part of the name.
1203 These values go into a regular expression, so regular
1204 expression syntax is allowed.
1205 varfunc: callback function called for every variable matching
1206 one of the entries in the variables parameter. The function
1207 should take four arguments:
1208 varname: name of variable matched
1209 origvalue: current value in file
1210 op: the operator (e.g. '+=')
1211 newlines: list of lines up to this point. You can use
1212 this to prepend lines before this variable setting
1213 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001214 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001215 newvalue: new value to substitute in, or None to drop
1216 the variable setting entirely. (If the removal
1217 results in two consecutive blank lines, one of the
1218 blank lines will also be dropped).
1219 newop: the operator to use - if you specify None here,
1220 the original operation will be used.
1221 indent: number of spaces to indent multi-line entries,
1222 or -1 to indent up to the level of the assignment
1223 and opening quote, or a string to use as the indent.
1224 minbreak: True to allow the first element of a
1225 multi-line value to continue on the same line as
1226 the assignment, False to indent before the first
1227 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001228 To clarify, if you wish not to change the value, then you
1229 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001230 match_overrides: True to match items with _overrides on the end,
1231 False otherwise
1232 Returns a tuple:
1233 updated:
1234 True if changes were made, False otherwise.
1235 newlines:
1236 Lines after processing
1237 """
1238
1239 var_res = {}
1240 if match_overrides:
Brad Bishop19323692019-04-05 15:28:33 -04001241 override_re = r'(_[a-zA-Z0-9-_$(){}]+)?'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001242 else:
1243 override_re = ''
1244 for var in variables:
1245 if var.endswith('()'):
Brad Bishop19323692019-04-05 15:28:33 -04001246 var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001247 else:
Brad Bishop19323692019-04-05 15:28:33 -04001248 var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001249
1250 updated = False
1251 varset_start = ''
1252 varlines = []
1253 newlines = []
1254 in_var = None
1255 full_value = ''
1256 var_end = ''
1257
1258 def handle_var_end():
1259 prerun_newlines = newlines[:]
1260 op = varset_start[len(in_var):].strip()
1261 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1262 changed = (prerun_newlines != newlines)
1263
1264 if newvalue is None:
1265 # Drop the value
1266 return True
1267 elif newvalue != full_value or (newop not in [None, op]):
1268 if newop not in [None, op]:
1269 # Callback changed the operator
1270 varset_new = "%s %s" % (in_var, newop)
1271 else:
1272 varset_new = varset_start
1273
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001274 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001275 if indent == -1:
1276 indentspc = ' ' * (len(varset_new) + 2)
1277 else:
1278 indentspc = ' ' * indent
1279 else:
1280 indentspc = indent
1281 if in_var.endswith('()'):
1282 # A function definition
1283 if isinstance(newvalue, list):
1284 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1285 else:
1286 if not newvalue.startswith('\n'):
1287 newvalue = '\n' + newvalue
1288 if not newvalue.endswith('\n'):
1289 newvalue = newvalue + '\n'
1290 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1291 else:
1292 # Normal variable
1293 if isinstance(newvalue, list):
1294 if not newvalue:
1295 # Empty list -> empty string
1296 newlines.append('%s ""\n' % varset_new)
1297 elif minbreak:
1298 # First item on first line
1299 if len(newvalue) == 1:
1300 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1301 else:
1302 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1303 for item in newvalue[1:]:
1304 newlines.append('%s%s \\\n' % (indentspc, item))
1305 newlines.append('%s"\n' % indentspc)
1306 else:
1307 # No item on first line
1308 newlines.append('%s " \\\n' % varset_new)
1309 for item in newvalue:
1310 newlines.append('%s%s \\\n' % (indentspc, item))
1311 newlines.append('%s"\n' % indentspc)
1312 else:
1313 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1314 return True
1315 else:
1316 # Put the old lines back where they were
1317 newlines.extend(varlines)
1318 # If newlines was touched by the function, we'll need to return True
1319 return changed
1320
1321 checkspc = False
1322
1323 for line in meta_lines:
1324 if in_var:
1325 value = line.rstrip()
1326 varlines.append(line)
1327 if in_var.endswith('()'):
1328 full_value += '\n' + value
1329 else:
1330 full_value += value[:-1]
1331 if value.endswith(var_end):
1332 if in_var.endswith('()'):
1333 if full_value.count('{') - full_value.count('}') >= 0:
1334 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001335 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001336 if handle_var_end():
1337 updated = True
1338 checkspc = True
1339 in_var = None
1340 else:
1341 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001342 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001343 res = var_re.match(line)
1344 if res:
1345 isfunc = varname.endswith('()')
1346 if isfunc:
1347 splitvalue = line.split('{', 1)
1348 var_end = '}'
1349 else:
1350 var_end = res.groups()[-1]
1351 splitvalue = line.split(var_end, 1)
1352 varset_start = splitvalue[0].rstrip()
1353 value = splitvalue[1].rstrip()
1354 if not isfunc and value.endswith('\\'):
1355 value = value[:-1]
1356 full_value = value
1357 varlines = [line]
1358 in_var = res.group(1)
1359 if isfunc:
1360 in_var += '()'
1361 if value.endswith(var_end):
1362 full_value = full_value[:-1]
1363 if handle_var_end():
1364 updated = True
1365 checkspc = True
1366 in_var = None
1367 skip = True
1368 break
1369 if not skip:
1370 if checkspc:
1371 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001372 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001373 # Squash blank line if there are two consecutive blanks after a removal
1374 continue
1375 newlines.append(line)
1376 return (updated, newlines)
1377
1378
1379def edit_metadata_file(meta_file, variables, varfunc):
1380 """Edit a recipe or config file and modify one or more specified
1381 variable values set in the file using a specified callback function.
1382 The file is only written to if the value(s) actually change.
1383 This is basically the file version of edit_metadata(), see that
1384 function's description for parameter/usage information.
1385 Returns True if the file was written to, False otherwise.
1386 """
1387 with open(meta_file, 'r') as f:
1388 (updated, newlines) = edit_metadata(f, variables, varfunc)
1389 if updated:
1390 with open(meta_file, 'w') as f:
1391 f.writelines(newlines)
1392 return updated
1393
1394
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001395def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001396 """Edit bblayers.conf, adding and/or removing layers
1397 Parameters:
1398 bblayers_conf: path to bblayers.conf file to edit
1399 add: layer path (or list of layer paths) to add; None or empty
1400 list to add nothing
1401 remove: layer path (or list of layer paths) to remove; None or
1402 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001403 edit_cb: optional callback function that will be called after
1404 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001405 Returns a tuple:
1406 notadded: list of layers specified to be added but weren't
1407 (because they were already in the list)
1408 notremoved: list of layers that were specified to be removed
1409 but weren't (because they weren't in the list)
1410 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001411
1412 import fnmatch
1413
1414 def remove_trailing_sep(pth):
1415 if pth and pth[-1] == os.sep:
1416 pth = pth[:-1]
1417 return pth
1418
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001419 approved = bb.utils.approved_variables()
1420 def canonicalise_path(pth):
1421 pth = remove_trailing_sep(pth)
1422 if 'HOME' in approved and '~' in pth:
1423 pth = os.path.expanduser(pth)
1424 return pth
1425
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001426 def layerlist_param(value):
1427 if not value:
1428 return []
1429 elif isinstance(value, list):
1430 return [remove_trailing_sep(x) for x in value]
1431 else:
1432 return [remove_trailing_sep(value)]
1433
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001434 addlayers = layerlist_param(add)
1435 removelayers = layerlist_param(remove)
1436
1437 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1438 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001439 removed = []
1440 plusequals = False
1441 orig_bblayers = []
1442
1443 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1444 bblayercalls.append(op)
1445 if op == '=':
1446 del orig_bblayers[:]
1447 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1448 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001449
1450 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001451 updated = False
1452 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1453 if removelayers:
1454 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001455 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001456 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001457 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001458 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001459 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001460 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001461 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001462 for addlayer in addlayers:
1463 if addlayer not in bblayers:
1464 updated = True
1465 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001466 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001467
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001468 if edit_cb:
1469 newlist = []
1470 for layer in bblayers:
1471 res = edit_cb(layer, canonicalise_path(layer))
1472 if res != layer:
1473 newlist.append(res)
1474 updated = True
1475 else:
1476 newlist.append(layer)
1477 bblayers = newlist
1478
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001479 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001480 if op == '+=' and not bblayers:
1481 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001482 return (bblayers, None, 2, False)
1483 else:
1484 return (origvalue, None, 2, False)
1485
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001486 with open(bblayers_conf, 'r') as f:
1487 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001488
1489 if not bblayercalls:
1490 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1491
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001492 # Try to do the "smart" thing depending on how the user has laid out
1493 # their bblayers.conf file
1494 if bblayercalls.count('+=') > 1:
1495 plusequals = True
1496
1497 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1498 notadded = []
1499 for layer in addlayers:
1500 layer_canon = canonicalise_path(layer)
1501 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1502 notadded.append(layer)
1503 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1504 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1505
1506 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1507 if addlayers:
1508 # Still need to add these
1509 for addlayer in addlayers:
1510 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1511 updated = True
1512
1513 if updated:
1514 with open(bblayers_conf, 'w') as f:
1515 f.writelines(newlines)
1516
1517 notremoved = list(set(removelayers) - set(removed))
1518
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001519 return (notadded, notremoved)
1520
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001521def get_collection_res(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001522 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001523 collection_res = {}
1524 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001525 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001526
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001527 return collection_res
1528
1529
1530def get_file_layer(filename, d, collection_res={}):
1531 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
1532 if not collection_res:
1533 collection_res = get_collection_res(d)
1534
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001535 def path_to_layer(path):
1536 # Use longest path so we handle nested layers
1537 matchlen = 0
1538 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001539 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001540 if len(regex) > matchlen and re.match(regex, path):
1541 matchlen = len(regex)
1542 match = collection
1543 return match
1544
1545 result = None
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001546 bbfiles = (d.getVar('BBFILES_PRIORITIZED') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001547 bbfilesmatch = False
1548 for bbfilesentry in bbfiles:
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001549 if fnmatch.fnmatchcase(filename, bbfilesentry):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001550 bbfilesmatch = True
1551 result = path_to_layer(bbfilesentry)
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001552 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001553
1554 if not bbfilesmatch:
1555 # Probably a bbclass
1556 result = path_to_layer(filename)
1557
1558 return result
1559
1560
1561# Constant taken from http://linux.die.net/include/linux/prctl.h
1562PR_SET_PDEATHSIG = 1
1563
1564class PrCtlError(Exception):
1565 pass
1566
1567def signal_on_parent_exit(signame):
1568 """
1569 Trigger signame to be sent when the parent process dies
1570 """
1571 signum = getattr(signal, signame)
1572 # http://linux.die.net/man/2/prctl
1573 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1574 if result != 0:
1575 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001576
1577#
1578# Manually call the ioprio syscall. We could depend on other libs like psutil
1579# however this gets us enough of what we need to bitbake for now without the
1580# dependency
1581#
1582_unamearch = os.uname()[4]
1583IOPRIO_WHO_PROCESS = 1
1584IOPRIO_CLASS_SHIFT = 13
1585
1586def ioprio_set(who, cls, value):
1587 NR_ioprio_set = None
1588 if _unamearch == "x86_64":
1589 NR_ioprio_set = 251
1590 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1591 NR_ioprio_set = 289
Brad Bishop19323692019-04-05 15:28:33 -04001592 elif _unamearch == "aarch64":
1593 NR_ioprio_set = 30
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001594
1595 if NR_ioprio_set:
1596 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1597 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1598 if rc != 0:
1599 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1600 else:
1601 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001602
1603def set_process_name(name):
1604 from ctypes import cdll, byref, create_string_buffer
1605 # This is nice to have for debugging, not essential
1606 try:
1607 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001608 buf = create_string_buffer(bytes(name, 'utf-8'))
1609 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001610 except:
1611 pass
1612
Andrew Geissler87f5cff2022-09-30 13:13:31 -05001613def enable_loopback_networking():
1614 # From bits/ioctls.h
1615 SIOCGIFFLAGS = 0x8913
1616 SIOCSIFFLAGS = 0x8914
1617 SIOCSIFADDR = 0x8916
1618 SIOCSIFNETMASK = 0x891C
1619
1620 # if.h
1621 IFF_UP = 0x1
1622 IFF_RUNNING = 0x40
1623
1624 # bits/socket.h
1625 AF_INET = 2
1626
1627 # char ifr_name[IFNAMSIZ=16]
1628 ifr_name = struct.pack("@16s", b"lo")
1629 def netdev_req(fd, req, data = b""):
1630 # Pad and add interface name
1631 data = ifr_name + data + (b'\x00' * (16 - len(data)))
1632 # Return all data after interface name
1633 return fcntl.ioctl(fd, req, data)[16:]
1634
1635 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_IP) as sock:
1636 fd = sock.fileno()
1637
1638 # struct sockaddr_in ifr_addr { unsigned short family; uint16_t sin_port ; uint32_t in_addr; }
1639 req = struct.pack("@H", AF_INET) + struct.pack("=H4B", 0, 127, 0, 0, 1)
1640 netdev_req(fd, SIOCSIFADDR, req)
1641
1642 # short ifr_flags
1643 flags = struct.unpack_from('@h', netdev_req(fd, SIOCGIFFLAGS))[0]
1644 flags |= IFF_UP | IFF_RUNNING
1645 netdev_req(fd, SIOCSIFFLAGS, struct.pack('@h', flags))
1646
1647 # struct sockaddr_in ifr_netmask
1648 req = struct.pack("@H", AF_INET) + struct.pack("=H4B", 0, 255, 0, 0, 0)
1649 netdev_req(fd, SIOCSIFNETMASK, req)
1650
Andrew Geissler595f6302022-01-24 19:11:47 +00001651def disable_network(uid=None, gid=None):
1652 """
1653 Disable networking in the current process if the kernel supports it, else
1654 just return after logging to debug. To do this we need to create a new user
1655 namespace, then map back to the original uid/gid.
1656 """
1657 libc = ctypes.CDLL('libc.so.6')
1658
1659 # From sched.h
1660 # New user namespace
1661 CLONE_NEWUSER = 0x10000000
1662 # New network namespace
1663 CLONE_NEWNET = 0x40000000
1664
1665 if uid is None:
1666 uid = os.getuid()
1667 if gid is None:
1668 gid = os.getgid()
1669
1670 ret = libc.unshare(CLONE_NEWNET | CLONE_NEWUSER)
1671 if ret != 0:
Andrew Geissler87f5cff2022-09-30 13:13:31 -05001672 logger.debug("System doesn't support disabling network without admin privs")
Andrew Geissler595f6302022-01-24 19:11:47 +00001673 return
1674 with open("/proc/self/uid_map", "w") as f:
1675 f.write("%s %s 1" % (uid, uid))
1676 with open("/proc/self/setgroups", "w") as f:
1677 f.write("deny")
1678 with open("/proc/self/gid_map", "w") as f:
1679 f.write("%s %s 1" % (gid, gid))
1680
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001681def export_proxies(d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001682 """ export common proxies variables from datastore to environment """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001683 import os
1684
1685 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001686 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1687 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001688 exported = False
1689
1690 for v in variables:
1691 if v in os.environ.keys():
1692 exported = True
1693 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001694 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001695 if v_proxy is not None:
1696 os.environ[v] = v_proxy
1697 exported = True
1698
1699 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001700
1701
1702def load_plugins(logger, plugins, pluginpath):
1703 def load_plugin(name):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001704 logger.debug('Loading plugin %s' % name)
Brad Bishop19323692019-04-05 15:28:33 -04001705 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1706 if spec:
Andrew Geissler595f6302022-01-24 19:11:47 +00001707 mod = importlib.util.module_from_spec(spec)
1708 spec.loader.exec_module(mod)
1709 return mod
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001710
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001711 logger.debug('Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001712
1713 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1714 for ext in python_extensions)
1715 files = itertools.chain.from_iterable(expanded)
1716 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1717 for name in names:
1718 if name != '__init__':
1719 plugin = load_plugin(name)
1720 if hasattr(plugin, 'plugin_init'):
1721 obj = plugin.plugin_init(plugins)
1722 plugins.append(obj or plugin)
1723 else:
1724 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001725
1726
1727class LogCatcher(logging.Handler):
1728 """Logging handler for collecting logged messages so you can check them later"""
1729 def __init__(self):
1730 self.messages = []
1731 logging.Handler.__init__(self, logging.WARNING)
1732 def emit(self, record):
1733 self.messages.append(bb.build.logformatter.format(record))
1734 def contains(self, message):
1735 return (message in self.messages)
Andrew Geissler82c905d2020-04-13 13:39:40 -05001736
1737def is_semver(version):
1738 """
1739 Is the version string following the semver semantic?
1740
1741 https://semver.org/spec/v2.0.0.html
1742 """
1743 regex = re.compile(
1744 r"""
1745 ^
1746 (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
1747 (?:-(
1748 (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
1749 (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
1750 ))?
1751 (?:\+(
1752 [0-9a-zA-Z-]+
1753 (?:\.[0-9a-zA-Z-]+)*
1754 ))?
1755 $
1756 """, re.VERBOSE)
1757
1758 if regex.match(version) is None:
1759 return False
1760
1761 return True
Andrew Geisslerc926e172021-05-07 16:11:35 -05001762
1763# Wrapper around os.rename which can handle cross device problems
1764# e.g. from container filesystems
1765def rename(src, dst):
1766 try:
1767 os.rename(src, dst)
1768 except OSError as err:
1769 if err.errno == 18:
1770 # Invalid cross-device link error
1771 shutil.move(src, dst)
1772 else:
1773 raise err
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001774
1775@contextmanager
1776def environment(**envvars):
1777 """
1778 Context manager to selectively update the environment with the specified mapping.
1779 """
1780 backup = dict(os.environ)
1781 try:
1782 os.environ.update(envvars)
1783 yield
1784 finally:
1785 for var in envvars:
1786 if var in backup:
1787 os.environ[var] = backup[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001788 elif var in os.environ:
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001789 del os.environ[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001790
1791def is_local_uid(uid=''):
1792 """
1793 Check whether uid is a local one or not.
1794 Can't use pwd module since it gets all UIDs, not local ones only.
1795 """
1796 if not uid:
1797 uid = os.getuid()
1798 with open('/etc/passwd', 'r') as f:
1799 for line in f:
1800 line_split = line.split(':')
1801 if len(line_split) < 3:
1802 continue
1803 if str(uid) == line_split[2]:
1804 return True
1805 return False
Patrick Williams92b42cb2022-09-03 06:53:57 -05001806
1807def mkstemp(suffix=None, prefix=None, dir=None, text=False):
1808 """
1809 Generates a unique filename, independent of time.
1810
1811 mkstemp() in glibc (at least) generates unique file names based on the
1812 current system time. When combined with highly parallel builds, and
1813 operating over NFS (e.g. shared sstate/downloads) this can result in
1814 conflicts and race conditions.
1815
1816 This function adds additional entropy to the file name so that a collision
1817 is independent of time and thus extremely unlikely.
1818 """
1819 entropy = "".join(random.choices("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", k=20))
1820 if prefix:
1821 prefix = prefix + entropy
1822 else:
1823 prefix = tempfile.gettempprefix() + entropy
1824 return tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir, text=text)