blob: e6e21e20fe0a4180d7921240bb444e1ae78e131c [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
Andrew Geissler9aee5002022-03-30 16:27:02 +0000550 return _hasher(hashlib.new('MD5', usedforsecurity=False), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500551
552def sha256_file(filename):
553 """
554 Return the hex string representation of the 256-bit SHA checksum of
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500555 filename.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500556 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500557 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500558 return _hasher(hashlib.sha256(), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500559
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500560def sha1_file(filename):
561 """
562 Return the hex string representation of the SHA1 checksum of the filename
563 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500564 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500565 return _hasher(hashlib.sha1(), filename)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500566
Andrew Geissler82c905d2020-04-13 13:39:40 -0500567def sha384_file(filename):
568 """
569 Return the hex string representation of the SHA384 checksum of the filename
570 """
571 import hashlib
572 return _hasher(hashlib.sha384(), filename)
573
574def sha512_file(filename):
575 """
576 Return the hex string representation of the SHA512 checksum of the filename
577 """
578 import hashlib
579 return _hasher(hashlib.sha512(), filename)
580
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500581def preserved_envvars_exported():
582 """Variables which are taken from the environment and placed in and exported
583 from the metadata"""
584 return [
585 'BB_TASKHASH',
586 'HOME',
587 'LOGNAME',
588 'PATH',
589 'PWD',
590 'SHELL',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500591 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600592 'LC_ALL',
593 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594 ]
595
596def preserved_envvars():
597 """Variables which are taken from the environment and placed in the metadata"""
598 v = [
599 'BBPATH',
600 'BB_PRESERVE_ENV',
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000601 'BB_ENV_PASSTHROUGH',
602 'BB_ENV_PASSTHROUGH_ADDITIONS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500603 ]
604 return v + preserved_envvars_exported()
605
606def filter_environment(good_vars):
607 """
608 Create a pristine environment for bitbake. This will remove variables that
609 are not known and may influence the build in a negative way.
610 """
611
612 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600613 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500614 if key in good_vars:
615 continue
616
617 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500618 del os.environ[key]
619
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600620 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
621 # access methods will use ascii. You can't change that mode once the interpreter is
622 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
623 # distros support that and we need to set something.
624 os.environ["LC_ALL"] = "en_US.UTF-8"
625
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500626 if removed_vars:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600627 logger.debug("Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500628
629 return removed_vars
630
631def approved_variables():
632 """
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000633 Determine and return the list of variables which are approved
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500634 to remain in the environment.
635 """
636 if 'BB_PRESERVE_ENV' in os.environ:
637 return os.environ.keys()
638 approved = []
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000639 if 'BB_ENV_PASSTHROUGH' in os.environ:
640 approved = os.environ['BB_ENV_PASSTHROUGH'].split()
641 approved.extend(['BB_ENV_PASSTHROUGH'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500642 else:
643 approved = preserved_envvars()
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000644 if 'BB_ENV_PASSTHROUGH_ADDITIONS' in os.environ:
645 approved.extend(os.environ['BB_ENV_PASSTHROUGH_ADDITIONS'].split())
646 if 'BB_ENV_PASSTHROUGH_ADDITIONS' not in approved:
647 approved.extend(['BB_ENV_PASSTHROUGH_ADDITIONS'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500648 return approved
649
650def clean_environment():
651 """
652 Clean up any spurious environment variables. This will remove any
653 variables the user hasn't chosen to preserve.
654 """
655 if 'BB_PRESERVE_ENV' not in os.environ:
656 good_vars = approved_variables()
657 return filter_environment(good_vars)
658
659 return {}
660
661def empty_environment():
662 """
663 Remove all variables from the environment.
664 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600665 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500666 os.unsetenv(s)
667 del os.environ[s]
668
669def build_environment(d):
670 """
671 Build an environment from all exported variables.
672 """
673 import bb.data
674 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500675 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500676 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500677 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500678
679def _check_unsafe_delete_path(path):
680 """
681 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
682 the caller should raise an exception with an appropriate message.
683 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
684 with potentially disastrous results.
685 """
686 extra = ''
687 # HOME might not be /home/something, so in case we can get it, check against it
688 homedir = os.environ.get('HOME', '')
689 if homedir:
690 extra = '|%s' % homedir
691 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
692 return True
693 return False
694
Brad Bishopa34c0302019-09-23 22:34:48 -0400695def remove(path, recurse=False, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500696 """Equivalent to rm -f or rm -rf"""
697 if not path:
698 return
699 if recurse:
700 for name in glob.glob(path):
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500701 if _check_unsafe_delete_path(name):
702 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500703 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishopa34c0302019-09-23 22:34:48 -0400704 cmd = []
705 if ionice:
706 cmd = ['ionice', '-c', '3']
707 subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500708 return
709 for name in glob.glob(path):
710 try:
711 os.unlink(name)
712 except OSError as exc:
713 if exc.errno != errno.ENOENT:
714 raise
715
Brad Bishopa34c0302019-09-23 22:34:48 -0400716def prunedir(topdir, ionice=False):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600717 """ Delete everything reachable from the directory named in 'topdir'. """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500718 # CAUTION: This is dangerous!
719 if _check_unsafe_delete_path(topdir):
720 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
Brad Bishopa34c0302019-09-23 22:34:48 -0400721 remove(topdir, recurse=True, ionice=ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500722
723#
724# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
725# but thats possibly insane and suffixes is probably going to be small
726#
727def prune_suffix(var, suffixes, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600728 """
729 See if var ends with any of the suffixes listed and
730 remove it if found
731 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500732 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -0400733 if suffix and var.endswith(suffix):
734 return var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500735 return var
736
737def mkdirhier(directory):
738 """Create a directory like 'mkdir -p', but does not complain if
739 directory already exists like os.makedirs
740 """
741
742 try:
743 os.makedirs(directory)
744 except OSError as e:
Brad Bishopc342db32019-05-15 21:57:59 -0400745 if e.errno != errno.EEXIST or not os.path.isdir(directory):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500746 raise e
747
748def movefile(src, dest, newmtime = None, sstat = None):
749 """Moves a file from src to dest, preserving all permissions and
750 attributes; mtime will be preserved even when moving across
751 filesystems. Returns true on success and false on failure. Move is
752 atomic.
753 """
754
755 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
756 try:
757 if not sstat:
758 sstat = os.lstat(src)
759 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500760 logger.warning("movefile: Stating source file failed...", e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500761 return None
762
763 destexists = 1
764 try:
765 dstat = os.lstat(dest)
766 except:
767 dstat = os.lstat(os.path.dirname(dest))
768 destexists = 0
769
770 if destexists:
771 if stat.S_ISLNK(dstat[stat.ST_MODE]):
772 try:
773 os.unlink(dest)
774 destexists = 0
775 except Exception as e:
776 pass
777
778 if stat.S_ISLNK(sstat[stat.ST_MODE]):
779 try:
780 target = os.readlink(src)
781 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
782 os.unlink(dest)
783 os.symlink(target, dest)
784 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
785 os.unlink(src)
786 return os.lstat(dest)
787 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500788 logger.warning("movefile: failed to properly create symlink:", dest, "->", target, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500789 return None
790
791 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500792 # os.rename needs to know the dest path ending with file name
793 # so append the file name to a path only if it's a dir specified
794 srcfname = os.path.basename(src)
795 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
796 else dest
797
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500798 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
799 try:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500800 bb.utils.rename(src, destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500801 renamefailed = 0
802 except Exception as e:
Brad Bishop79641f22019-09-10 07:20:22 -0400803 if e.errno != errno.EXDEV:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500804 # Some random error.
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500805 logger.warning("movefile: Failed to move", src, "to", dest, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500806 return None
807 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
808
809 if renamefailed:
810 didcopy = 0
811 if stat.S_ISREG(sstat[stat.ST_MODE]):
812 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500813 shutil.copyfile(src, destpath + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500814 bb.utils.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500815 didcopy = 1
816 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500817 logger.warning('movefile: copy', src, '->', dest, 'failed.', e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500818 return None
819 else:
820 #we don't yet handle special, so we need to fall back to /bin/mv
821 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
822 if a[0] != 0:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500823 logger.warning("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500824 return None # failure
825 try:
826 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400827 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
828 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500829 os.unlink(src)
830 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500831 logger.warning("movefile: Failed to chown/chmod/unlink", dest, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500832 return None
833
834 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500835 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500836 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500837 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500838 newmtime = sstat[stat.ST_MTIME]
839 return newmtime
840
841def copyfile(src, dest, newmtime = None, sstat = None):
842 """
843 Copies a file from src to dest, preserving all permissions and
844 attributes; mtime will be preserved even when moving across
845 filesystems. Returns true on success and false on failure.
846 """
847 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
848 try:
849 if not sstat:
850 sstat = os.lstat(src)
851 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600852 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500853 return False
854
855 destexists = 1
856 try:
857 dstat = os.lstat(dest)
858 except:
859 dstat = os.lstat(os.path.dirname(dest))
860 destexists = 0
861
862 if destexists:
863 if stat.S_ISLNK(dstat[stat.ST_MODE]):
864 try:
865 os.unlink(dest)
866 destexists = 0
867 except Exception as e:
868 pass
869
870 if stat.S_ISLNK(sstat[stat.ST_MODE]):
871 try:
872 target = os.readlink(src)
873 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
874 os.unlink(dest)
875 os.symlink(target, dest)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500876 os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500877 return os.lstat(dest)
878 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600879 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500880 return False
881
882 if stat.S_ISREG(sstat[stat.ST_MODE]):
883 try:
884 srcchown = False
885 if not os.access(src, os.R_OK):
886 # Make sure we can read it
887 srcchown = True
888 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
889
890 # For safety copy then move it over.
891 shutil.copyfile(src, dest + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500892 bb.utils.rename(dest + "#new", dest)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500893 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600894 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500895 return False
896 finally:
897 if srcchown:
898 os.chmod(src, sstat[stat.ST_MODE])
899 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
900
901 else:
902 #we don't yet handle special, so we need to fall back to /bin/mv
903 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
904 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600905 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500906 return False # failure
907 try:
908 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
909 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
910 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600911 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500912 return False
913
914 if newmtime:
915 os.utime(dest, (newmtime, newmtime))
916 else:
917 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
918 newmtime = sstat[stat.ST_MTIME]
919 return newmtime
920
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800921def break_hardlinks(src, sstat = None):
922 """
923 Ensures src is the only hardlink to this file. Other hardlinks,
924 if any, are not affected (other than in their st_nlink value, of
925 course). Returns true on success and false on failure.
926
927 """
928 try:
929 if not sstat:
930 sstat = os.lstat(src)
931 except Exception as e:
932 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
933 return False
934 if sstat[stat.ST_NLINK] == 1:
935 return True
936 return copyfile(src, src, sstat=sstat)
937
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500938def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500939 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500940 Locate `item` in the list of paths `path` (colon separated string like $PATH).
941 If `direction` is non-zero then the list is reversed.
942 If `history` is True then the list of candidates also returned as result,history.
943 If `executable` is True then the candidate has to be an executable file,
944 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500945 """
946
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500947 if executable:
948 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
949 else:
950 is_candidate = lambda p: os.path.exists(p)
951
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500952 hist = []
953 paths = (path or "").split(':')
954 if direction != 0:
955 paths.reverse()
956
957 for p in paths:
958 next = os.path.join(p, item)
959 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500960 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500961 if not os.path.isabs(next):
962 next = os.path.abspath(next)
963 if history:
964 return next, hist
965 return next
966
967 if history:
968 return "", hist
969 return ""
970
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500971@contextmanager
972def umask(new_mask):
973 """
974 Context manager to set the umask to a specific mask, and restore it afterwards.
975 """
976 current_mask = os.umask(new_mask)
977 try:
978 yield
979 finally:
980 os.umask(current_mask)
981
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500982def to_boolean(string, default=None):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600983 """
984 Check input string and return boolean value True/False/None
985 depending upon the checks
986 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500987 if not string:
988 return default
989
990 normalized = string.lower()
991 if normalized in ("y", "yes", "1", "true"):
992 return True
993 elif normalized in ("n", "no", "0", "false"):
994 return False
995 else:
996 raise ValueError("Invalid value for to_boolean: %s" % string)
997
998def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500999 """Check if a variable contains all the values specified.
1000
1001 Arguments:
1002
1003 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001004 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001005
1006 checkvalues -- if this is a string it is split on whitespace into a set(),
1007 otherwise coerced directly into a set().
1008
1009 truevalue -- the value to return if checkvalues is a subset of variable.
1010
1011 falsevalue -- the value to return if variable is empty or if checkvalues is
1012 not a subset of variable.
1013
1014 d -- the data store.
1015 """
1016
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001017 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001018 if not val:
1019 return falsevalue
1020 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001021 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001022 checkvalues = set(checkvalues.split())
1023 else:
1024 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001025 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001026 return truevalue
1027 return falsevalue
1028
1029def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001030 """Check if a variable contains any values specified.
1031
1032 Arguments:
1033
1034 variable -- the variable name. This will be fetched and expanded (using
1035 d.getVar(variable)) and then split into a set().
1036
1037 checkvalues -- if this is a string it is split on whitespace into a set(),
1038 otherwise coerced directly into a set().
1039
1040 truevalue -- the value to return if checkvalues is a subset of variable.
1041
1042 falsevalue -- the value to return if variable is empty or if checkvalues is
1043 not a subset of variable.
1044
1045 d -- the data store.
1046 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001047 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001048 if not val:
1049 return falsevalue
1050 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001051 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001052 checkvalues = set(checkvalues.split())
1053 else:
1054 checkvalues = set(checkvalues)
1055 if checkvalues & val:
1056 return truevalue
1057 return falsevalue
1058
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001059def filter(variable, checkvalues, d):
1060 """Return all words in the variable that are present in the checkvalues.
1061
1062 Arguments:
1063
1064 variable -- the variable name. This will be fetched and expanded (using
1065 d.getVar(variable)) and then split into a set().
1066
1067 checkvalues -- if this is a string it is split on whitespace into a set(),
1068 otherwise coerced directly into a set().
1069
1070 d -- the data store.
1071 """
1072
1073 val = d.getVar(variable)
1074 if not val:
1075 return ''
1076 val = set(val.split())
1077 if isinstance(checkvalues, str):
1078 checkvalues = set(checkvalues.split())
1079 else:
1080 checkvalues = set(checkvalues)
1081 return ' '.join(sorted(checkvalues & val))
1082
Andrew Geissler82c905d2020-04-13 13:39:40 -05001083
1084def get_referenced_vars(start_expr, d):
1085 """
1086 :return: names of vars referenced in start_expr (recursively), in quasi-BFS order (variables within the same level
1087 are ordered arbitrarily)
1088 """
1089
1090 seen = set()
1091 ret = []
1092
1093 # The first entry in the queue is the unexpanded start expression
1094 queue = collections.deque([start_expr])
1095 # Subsequent entries will be variable names, so we need to track whether or not entry requires getVar
1096 is_first = True
1097
1098 empty_data = bb.data.init()
1099 while queue:
1100 entry = queue.popleft()
1101 if is_first:
1102 # Entry is the start expression - no expansion needed
1103 is_first = False
1104 expression = entry
1105 else:
1106 # This is a variable name - need to get the value
1107 expression = d.getVar(entry, False)
1108 ret.append(entry)
1109
1110 # expandWithRefs is how we actually get the referenced variables in the expression. We call it using an empty
1111 # data store because we only want the variables directly used in the expression. It returns a set, which is what
1112 # dooms us to only ever be "quasi-BFS" rather than full BFS.
1113 new_vars = empty_data.expandWithRefs(expression, None).references - set(seen)
1114
1115 queue.extend(new_vars)
1116 seen.update(new_vars)
1117 return ret
1118
1119
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001120def cpu_count():
1121 return multiprocessing.cpu_count()
1122
1123def nonblockingfd(fd):
1124 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1125
1126def process_profilelog(fn, pout = None):
1127 # Either call with a list of filenames and set pout or a filename and optionally pout.
1128 if not pout:
1129 pout = fn + '.processed'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001130
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001131 with open(pout, 'w') as pout:
1132 import pstats
1133 if isinstance(fn, list):
1134 p = pstats.Stats(*fn, stream=pout)
1135 else:
1136 p = pstats.Stats(fn, stream=pout)
1137 p.sort_stats('time')
1138 p.print_stats()
1139 p.print_callers()
1140 p.sort_stats('cumulative')
1141 p.print_stats()
1142
1143 pout.flush()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001144
1145#
1146# Was present to work around multiprocessing pool bugs in python < 2.7.3
1147#
1148def multiprocessingpool(*args, **kwargs):
1149
1150 import multiprocessing.pool
1151 #import multiprocessing.util
1152 #multiprocessing.util.log_to_stderr(10)
1153 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1154 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1155 def wrapper(func):
1156 def wrap(self, timeout=None):
1157 return func(self, timeout=timeout if timeout is not None else 1e100)
1158 return wrap
1159 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1160
1161 return multiprocessing.Pool(*args, **kwargs)
1162
1163def exec_flat_python_func(func, *args, **kwargs):
1164 """Execute a flat python function (defined with def funcname(args):...)"""
1165 # Prepare a small piece of python code which calls the requested function
1166 # To do this we need to prepare two things - a set of variables we can use to pass
1167 # the values of arguments into the calling function, and the list of arguments for
1168 # the function being called
1169 context = {}
1170 funcargs = []
1171 # Handle unnamed arguments
1172 aidx = 1
1173 for arg in args:
1174 argname = 'arg_%s' % aidx
1175 context[argname] = arg
1176 funcargs.append(argname)
1177 aidx += 1
1178 # Handle keyword arguments
1179 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001180 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001181 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1182 comp = bb.utils.better_compile(code, '<string>', '<string>')
1183 bb.utils.better_exec(comp, context, code, '<string>')
1184 return context['retval']
1185
1186def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1187 """Edit lines from a recipe or config file and modify one or more
1188 specified variable values set in the file using a specified callback
1189 function. Lines are expected to have trailing newlines.
1190 Parameters:
1191 meta_lines: lines from the file; can be a list or an iterable
1192 (e.g. file pointer)
1193 variables: a list of variable names to look for. Functions
1194 may also be specified, but must be specified with '()' at
1195 the end of the name. Note that the function doesn't have
Patrick Williams213cb262021-08-07 19:21:33 -05001196 any intrinsic understanding of :append, :prepend, :remove,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001197 or overrides, so these are considered as part of the name.
1198 These values go into a regular expression, so regular
1199 expression syntax is allowed.
1200 varfunc: callback function called for every variable matching
1201 one of the entries in the variables parameter. The function
1202 should take four arguments:
1203 varname: name of variable matched
1204 origvalue: current value in file
1205 op: the operator (e.g. '+=')
1206 newlines: list of lines up to this point. You can use
1207 this to prepend lines before this variable setting
1208 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001209 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001210 newvalue: new value to substitute in, or None to drop
1211 the variable setting entirely. (If the removal
1212 results in two consecutive blank lines, one of the
1213 blank lines will also be dropped).
1214 newop: the operator to use - if you specify None here,
1215 the original operation will be used.
1216 indent: number of spaces to indent multi-line entries,
1217 or -1 to indent up to the level of the assignment
1218 and opening quote, or a string to use as the indent.
1219 minbreak: True to allow the first element of a
1220 multi-line value to continue on the same line as
1221 the assignment, False to indent before the first
1222 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001223 To clarify, if you wish not to change the value, then you
1224 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001225 match_overrides: True to match items with _overrides on the end,
1226 False otherwise
1227 Returns a tuple:
1228 updated:
1229 True if changes were made, False otherwise.
1230 newlines:
1231 Lines after processing
1232 """
1233
1234 var_res = {}
1235 if match_overrides:
Brad Bishop19323692019-04-05 15:28:33 -04001236 override_re = r'(_[a-zA-Z0-9-_$(){}]+)?'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001237 else:
1238 override_re = ''
1239 for var in variables:
1240 if var.endswith('()'):
Brad Bishop19323692019-04-05 15:28:33 -04001241 var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001242 else:
Brad Bishop19323692019-04-05 15:28:33 -04001243 var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001244
1245 updated = False
1246 varset_start = ''
1247 varlines = []
1248 newlines = []
1249 in_var = None
1250 full_value = ''
1251 var_end = ''
1252
1253 def handle_var_end():
1254 prerun_newlines = newlines[:]
1255 op = varset_start[len(in_var):].strip()
1256 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1257 changed = (prerun_newlines != newlines)
1258
1259 if newvalue is None:
1260 # Drop the value
1261 return True
1262 elif newvalue != full_value or (newop not in [None, op]):
1263 if newop not in [None, op]:
1264 # Callback changed the operator
1265 varset_new = "%s %s" % (in_var, newop)
1266 else:
1267 varset_new = varset_start
1268
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001269 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001270 if indent == -1:
1271 indentspc = ' ' * (len(varset_new) + 2)
1272 else:
1273 indentspc = ' ' * indent
1274 else:
1275 indentspc = indent
1276 if in_var.endswith('()'):
1277 # A function definition
1278 if isinstance(newvalue, list):
1279 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1280 else:
1281 if not newvalue.startswith('\n'):
1282 newvalue = '\n' + newvalue
1283 if not newvalue.endswith('\n'):
1284 newvalue = newvalue + '\n'
1285 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1286 else:
1287 # Normal variable
1288 if isinstance(newvalue, list):
1289 if not newvalue:
1290 # Empty list -> empty string
1291 newlines.append('%s ""\n' % varset_new)
1292 elif minbreak:
1293 # First item on first line
1294 if len(newvalue) == 1:
1295 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1296 else:
1297 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1298 for item in newvalue[1:]:
1299 newlines.append('%s%s \\\n' % (indentspc, item))
1300 newlines.append('%s"\n' % indentspc)
1301 else:
1302 # No item on first line
1303 newlines.append('%s " \\\n' % varset_new)
1304 for item in newvalue:
1305 newlines.append('%s%s \\\n' % (indentspc, item))
1306 newlines.append('%s"\n' % indentspc)
1307 else:
1308 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1309 return True
1310 else:
1311 # Put the old lines back where they were
1312 newlines.extend(varlines)
1313 # If newlines was touched by the function, we'll need to return True
1314 return changed
1315
1316 checkspc = False
1317
1318 for line in meta_lines:
1319 if in_var:
1320 value = line.rstrip()
1321 varlines.append(line)
1322 if in_var.endswith('()'):
1323 full_value += '\n' + value
1324 else:
1325 full_value += value[:-1]
1326 if value.endswith(var_end):
1327 if in_var.endswith('()'):
1328 if full_value.count('{') - full_value.count('}') >= 0:
1329 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001330 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001331 if handle_var_end():
1332 updated = True
1333 checkspc = True
1334 in_var = None
1335 else:
1336 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001337 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001338 res = var_re.match(line)
1339 if res:
1340 isfunc = varname.endswith('()')
1341 if isfunc:
1342 splitvalue = line.split('{', 1)
1343 var_end = '}'
1344 else:
1345 var_end = res.groups()[-1]
1346 splitvalue = line.split(var_end, 1)
1347 varset_start = splitvalue[0].rstrip()
1348 value = splitvalue[1].rstrip()
1349 if not isfunc and value.endswith('\\'):
1350 value = value[:-1]
1351 full_value = value
1352 varlines = [line]
1353 in_var = res.group(1)
1354 if isfunc:
1355 in_var += '()'
1356 if value.endswith(var_end):
1357 full_value = full_value[:-1]
1358 if handle_var_end():
1359 updated = True
1360 checkspc = True
1361 in_var = None
1362 skip = True
1363 break
1364 if not skip:
1365 if checkspc:
1366 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001367 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001368 # Squash blank line if there are two consecutive blanks after a removal
1369 continue
1370 newlines.append(line)
1371 return (updated, newlines)
1372
1373
1374def edit_metadata_file(meta_file, variables, varfunc):
1375 """Edit a recipe or config file and modify one or more specified
1376 variable values set in the file using a specified callback function.
1377 The file is only written to if the value(s) actually change.
1378 This is basically the file version of edit_metadata(), see that
1379 function's description for parameter/usage information.
1380 Returns True if the file was written to, False otherwise.
1381 """
1382 with open(meta_file, 'r') as f:
1383 (updated, newlines) = edit_metadata(f, variables, varfunc)
1384 if updated:
1385 with open(meta_file, 'w') as f:
1386 f.writelines(newlines)
1387 return updated
1388
1389
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001390def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001391 """Edit bblayers.conf, adding and/or removing layers
1392 Parameters:
1393 bblayers_conf: path to bblayers.conf file to edit
1394 add: layer path (or list of layer paths) to add; None or empty
1395 list to add nothing
1396 remove: layer path (or list of layer paths) to remove; None or
1397 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001398 edit_cb: optional callback function that will be called after
1399 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001400 Returns a tuple:
1401 notadded: list of layers specified to be added but weren't
1402 (because they were already in the list)
1403 notremoved: list of layers that were specified to be removed
1404 but weren't (because they weren't in the list)
1405 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001406
1407 import fnmatch
1408
1409 def remove_trailing_sep(pth):
1410 if pth and pth[-1] == os.sep:
1411 pth = pth[:-1]
1412 return pth
1413
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001414 approved = bb.utils.approved_variables()
1415 def canonicalise_path(pth):
1416 pth = remove_trailing_sep(pth)
1417 if 'HOME' in approved and '~' in pth:
1418 pth = os.path.expanduser(pth)
1419 return pth
1420
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001421 def layerlist_param(value):
1422 if not value:
1423 return []
1424 elif isinstance(value, list):
1425 return [remove_trailing_sep(x) for x in value]
1426 else:
1427 return [remove_trailing_sep(value)]
1428
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001429 addlayers = layerlist_param(add)
1430 removelayers = layerlist_param(remove)
1431
1432 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1433 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001434 removed = []
1435 plusequals = False
1436 orig_bblayers = []
1437
1438 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1439 bblayercalls.append(op)
1440 if op == '=':
1441 del orig_bblayers[:]
1442 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1443 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001444
1445 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001446 updated = False
1447 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1448 if removelayers:
1449 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001450 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001451 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001452 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001453 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001454 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001455 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001456 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001457 for addlayer in addlayers:
1458 if addlayer not in bblayers:
1459 updated = True
1460 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001461 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001462
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001463 if edit_cb:
1464 newlist = []
1465 for layer in bblayers:
1466 res = edit_cb(layer, canonicalise_path(layer))
1467 if res != layer:
1468 newlist.append(res)
1469 updated = True
1470 else:
1471 newlist.append(layer)
1472 bblayers = newlist
1473
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001474 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001475 if op == '+=' and not bblayers:
1476 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001477 return (bblayers, None, 2, False)
1478 else:
1479 return (origvalue, None, 2, False)
1480
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001481 with open(bblayers_conf, 'r') as f:
1482 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001483
1484 if not bblayercalls:
1485 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1486
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001487 # Try to do the "smart" thing depending on how the user has laid out
1488 # their bblayers.conf file
1489 if bblayercalls.count('+=') > 1:
1490 plusequals = True
1491
1492 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1493 notadded = []
1494 for layer in addlayers:
1495 layer_canon = canonicalise_path(layer)
1496 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1497 notadded.append(layer)
1498 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1499 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1500
1501 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1502 if addlayers:
1503 # Still need to add these
1504 for addlayer in addlayers:
1505 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1506 updated = True
1507
1508 if updated:
1509 with open(bblayers_conf, 'w') as f:
1510 f.writelines(newlines)
1511
1512 notremoved = list(set(removelayers) - set(removed))
1513
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001514 return (notadded, notremoved)
1515
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001516def get_collection_res(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001517 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001518 collection_res = {}
1519 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001520 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001521
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001522 return collection_res
1523
1524
1525def get_file_layer(filename, d, collection_res={}):
1526 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
1527 if not collection_res:
1528 collection_res = get_collection_res(d)
1529
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001530 def path_to_layer(path):
1531 # Use longest path so we handle nested layers
1532 matchlen = 0
1533 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001534 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001535 if len(regex) > matchlen and re.match(regex, path):
1536 matchlen = len(regex)
1537 match = collection
1538 return match
1539
1540 result = None
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001541 bbfiles = (d.getVar('BBFILES_PRIORITIZED') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001542 bbfilesmatch = False
1543 for bbfilesentry in bbfiles:
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001544 if fnmatch.fnmatchcase(filename, bbfilesentry):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001545 bbfilesmatch = True
1546 result = path_to_layer(bbfilesentry)
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001547 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001548
1549 if not bbfilesmatch:
1550 # Probably a bbclass
1551 result = path_to_layer(filename)
1552
1553 return result
1554
1555
1556# Constant taken from http://linux.die.net/include/linux/prctl.h
1557PR_SET_PDEATHSIG = 1
1558
1559class PrCtlError(Exception):
1560 pass
1561
1562def signal_on_parent_exit(signame):
1563 """
1564 Trigger signame to be sent when the parent process dies
1565 """
1566 signum = getattr(signal, signame)
1567 # http://linux.die.net/man/2/prctl
1568 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1569 if result != 0:
1570 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001571
1572#
1573# Manually call the ioprio syscall. We could depend on other libs like psutil
1574# however this gets us enough of what we need to bitbake for now without the
1575# dependency
1576#
1577_unamearch = os.uname()[4]
1578IOPRIO_WHO_PROCESS = 1
1579IOPRIO_CLASS_SHIFT = 13
1580
1581def ioprio_set(who, cls, value):
1582 NR_ioprio_set = None
1583 if _unamearch == "x86_64":
1584 NR_ioprio_set = 251
1585 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1586 NR_ioprio_set = 289
Brad Bishop19323692019-04-05 15:28:33 -04001587 elif _unamearch == "aarch64":
1588 NR_ioprio_set = 30
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001589
1590 if NR_ioprio_set:
1591 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1592 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1593 if rc != 0:
1594 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1595 else:
1596 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001597
1598def set_process_name(name):
1599 from ctypes import cdll, byref, create_string_buffer
1600 # This is nice to have for debugging, not essential
1601 try:
1602 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001603 buf = create_string_buffer(bytes(name, 'utf-8'))
1604 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001605 except:
1606 pass
1607
Andrew Geissler87f5cff2022-09-30 13:13:31 -05001608def enable_loopback_networking():
1609 # From bits/ioctls.h
1610 SIOCGIFFLAGS = 0x8913
1611 SIOCSIFFLAGS = 0x8914
1612 SIOCSIFADDR = 0x8916
1613 SIOCSIFNETMASK = 0x891C
1614
1615 # if.h
1616 IFF_UP = 0x1
1617 IFF_RUNNING = 0x40
1618
1619 # bits/socket.h
1620 AF_INET = 2
1621
1622 # char ifr_name[IFNAMSIZ=16]
1623 ifr_name = struct.pack("@16s", b"lo")
1624 def netdev_req(fd, req, data = b""):
1625 # Pad and add interface name
1626 data = ifr_name + data + (b'\x00' * (16 - len(data)))
1627 # Return all data after interface name
1628 return fcntl.ioctl(fd, req, data)[16:]
1629
1630 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_IP) as sock:
1631 fd = sock.fileno()
1632
1633 # struct sockaddr_in ifr_addr { unsigned short family; uint16_t sin_port ; uint32_t in_addr; }
1634 req = struct.pack("@H", AF_INET) + struct.pack("=H4B", 0, 127, 0, 0, 1)
1635 netdev_req(fd, SIOCSIFADDR, req)
1636
1637 # short ifr_flags
1638 flags = struct.unpack_from('@h', netdev_req(fd, SIOCGIFFLAGS))[0]
1639 flags |= IFF_UP | IFF_RUNNING
1640 netdev_req(fd, SIOCSIFFLAGS, struct.pack('@h', flags))
1641
1642 # struct sockaddr_in ifr_netmask
1643 req = struct.pack("@H", AF_INET) + struct.pack("=H4B", 0, 255, 0, 0, 0)
1644 netdev_req(fd, SIOCSIFNETMASK, req)
1645
Andrew Geissler595f6302022-01-24 19:11:47 +00001646def disable_network(uid=None, gid=None):
1647 """
1648 Disable networking in the current process if the kernel supports it, else
1649 just return after logging to debug. To do this we need to create a new user
1650 namespace, then map back to the original uid/gid.
1651 """
1652 libc = ctypes.CDLL('libc.so.6')
1653
1654 # From sched.h
1655 # New user namespace
1656 CLONE_NEWUSER = 0x10000000
1657 # New network namespace
1658 CLONE_NEWNET = 0x40000000
1659
1660 if uid is None:
1661 uid = os.getuid()
1662 if gid is None:
1663 gid = os.getgid()
1664
1665 ret = libc.unshare(CLONE_NEWNET | CLONE_NEWUSER)
1666 if ret != 0:
Andrew Geissler87f5cff2022-09-30 13:13:31 -05001667 logger.debug("System doesn't support disabling network without admin privs")
Andrew Geissler595f6302022-01-24 19:11:47 +00001668 return
1669 with open("/proc/self/uid_map", "w") as f:
1670 f.write("%s %s 1" % (uid, uid))
1671 with open("/proc/self/setgroups", "w") as f:
1672 f.write("deny")
1673 with open("/proc/self/gid_map", "w") as f:
1674 f.write("%s %s 1" % (gid, gid))
1675
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001676def export_proxies(d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001677 """ export common proxies variables from datastore to environment """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001678 import os
1679
1680 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001681 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1682 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001683 exported = False
1684
1685 for v in variables:
1686 if v in os.environ.keys():
1687 exported = True
1688 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001689 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001690 if v_proxy is not None:
1691 os.environ[v] = v_proxy
1692 exported = True
1693
1694 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001695
1696
1697def load_plugins(logger, plugins, pluginpath):
1698 def load_plugin(name):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001699 logger.debug('Loading plugin %s' % name)
Brad Bishop19323692019-04-05 15:28:33 -04001700 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1701 if spec:
Andrew Geissler595f6302022-01-24 19:11:47 +00001702 mod = importlib.util.module_from_spec(spec)
1703 spec.loader.exec_module(mod)
1704 return mod
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001705
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001706 logger.debug('Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001707
1708 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1709 for ext in python_extensions)
1710 files = itertools.chain.from_iterable(expanded)
1711 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1712 for name in names:
1713 if name != '__init__':
1714 plugin = load_plugin(name)
1715 if hasattr(plugin, 'plugin_init'):
1716 obj = plugin.plugin_init(plugins)
1717 plugins.append(obj or plugin)
1718 else:
1719 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001720
1721
1722class LogCatcher(logging.Handler):
1723 """Logging handler for collecting logged messages so you can check them later"""
1724 def __init__(self):
1725 self.messages = []
1726 logging.Handler.__init__(self, logging.WARNING)
1727 def emit(self, record):
1728 self.messages.append(bb.build.logformatter.format(record))
1729 def contains(self, message):
1730 return (message in self.messages)
Andrew Geissler82c905d2020-04-13 13:39:40 -05001731
1732def is_semver(version):
1733 """
1734 Is the version string following the semver semantic?
1735
1736 https://semver.org/spec/v2.0.0.html
1737 """
1738 regex = re.compile(
1739 r"""
1740 ^
1741 (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
1742 (?:-(
1743 (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
1744 (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
1745 ))?
1746 (?:\+(
1747 [0-9a-zA-Z-]+
1748 (?:\.[0-9a-zA-Z-]+)*
1749 ))?
1750 $
1751 """, re.VERBOSE)
1752
1753 if regex.match(version) is None:
1754 return False
1755
1756 return True
Andrew Geisslerc926e172021-05-07 16:11:35 -05001757
1758# Wrapper around os.rename which can handle cross device problems
1759# e.g. from container filesystems
1760def rename(src, dst):
1761 try:
1762 os.rename(src, dst)
1763 except OSError as err:
1764 if err.errno == 18:
1765 # Invalid cross-device link error
1766 shutil.move(src, dst)
1767 else:
1768 raise err
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001769
1770@contextmanager
1771def environment(**envvars):
1772 """
1773 Context manager to selectively update the environment with the specified mapping.
1774 """
1775 backup = dict(os.environ)
1776 try:
1777 os.environ.update(envvars)
1778 yield
1779 finally:
1780 for var in envvars:
1781 if var in backup:
1782 os.environ[var] = backup[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001783 elif var in os.environ:
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001784 del os.environ[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001785
1786def is_local_uid(uid=''):
1787 """
1788 Check whether uid is a local one or not.
1789 Can't use pwd module since it gets all UIDs, not local ones only.
1790 """
1791 if not uid:
1792 uid = os.getuid()
1793 with open('/etc/passwd', 'r') as f:
1794 for line in f:
1795 line_split = line.split(':')
1796 if len(line_split) < 3:
1797 continue
1798 if str(uid) == line_split[2]:
1799 return True
1800 return False
Patrick Williams92b42cb2022-09-03 06:53:57 -05001801
1802def mkstemp(suffix=None, prefix=None, dir=None, text=False):
1803 """
1804 Generates a unique filename, independent of time.
1805
1806 mkstemp() in glibc (at least) generates unique file names based on the
1807 current system time. When combined with highly parallel builds, and
1808 operating over NFS (e.g. shared sstate/downloads) this can result in
1809 conflicts and race conditions.
1810
1811 This function adds additional entropy to the file name so that a collision
1812 is independent of time and thus extremely unlikely.
1813 """
1814 entropy = "".join(random.choices("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", k=20))
1815 if prefix:
1816 prefix = prefix + entropy
1817 else:
1818 prefix = tempfile.gettempprefix() + entropy
1819 return tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir, text=text)