blob: 068b631c94391435edc6d35642ec591a53e4d158 [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
Andrew Geissler517393d2023-01-13 08:55:19 -060016import locale
Patrick Williamsc124f4f2015-09-15 14:41:29 -050017import multiprocessing
18import fcntl
Brad Bishop19323692019-04-05 15:28:33 -040019import importlib
Andrew Geissler595f6302022-01-24 19:11:47 +000020import importlib.machinery
21import importlib.util
Patrick Williamsc0f7c042017-02-23 20:41:17 -060022import itertools
Patrick Williamsc124f4f2015-09-15 14:41:29 -050023import subprocess
24import glob
25import fnmatch
26import traceback
27import errno
28import signal
Patrick Williamsc0f7c042017-02-23 20:41:17 -060029import collections
30import copy
Andrew Geissler595f6302022-01-24 19:11:47 +000031import ctypes
Patrick Williams92b42cb2022-09-03 06:53:57 -050032import random
Andrew Geissler87f5cff2022-09-30 13:13:31 -050033import socket
34import struct
Patrick Williams92b42cb2022-09-03 06:53:57 -050035import tempfile
Patrick Williamsc0f7c042017-02-23 20:41:17 -060036from subprocess import getstatusoutput
Patrick Williamsc124f4f2015-09-15 14:41:29 -050037from contextlib import contextmanager
38from ctypes import cdll
39
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040logger = logging.getLogger("BitBake.Util")
Brad Bishop19323692019-04-05 15:28:33 -040041python_extensions = importlib.machinery.all_suffixes()
Patrick Williamsc0f7c042017-02-23 20:41:17 -060042
Patrick Williamsc124f4f2015-09-15 14:41:29 -050043
44def clean_context():
45 return {
46 "os": os,
47 "bb": bb,
48 "time": time,
49 }
50
51def get_context():
52 return _context
Patrick Williamsac13d5f2023-11-24 18:59:46 -060053
Patrick Williamsc124f4f2015-09-15 14:41:29 -050054
55def set_context(ctx):
56 _context = ctx
57
58# Context used in better_exec, eval
59_context = clean_context()
60
61class VersionStringException(Exception):
62 """Exception raised when an invalid version specification is found"""
63
64def explode_version(s):
65 r = []
Brad Bishop19323692019-04-05 15:28:33 -040066 alpha_regexp = re.compile(r'^([a-zA-Z]+)(.*)$')
67 numeric_regexp = re.compile(r'^(\d+)(.*)$')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050068 while (s != ''):
69 if s[0] in string.digits:
70 m = numeric_regexp.match(s)
71 r.append((0, int(m.group(1))))
72 s = m.group(2)
73 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -060074 if s[0] in string.ascii_letters:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050075 m = alpha_regexp.match(s)
76 r.append((1, m.group(1)))
77 s = m.group(2)
78 continue
79 if s[0] == '~':
80 r.append((-1, s[0]))
81 else:
82 r.append((2, s[0]))
83 s = s[1:]
84 return r
85
86def split_version(s):
87 """Split a version string into its constituent parts (PE, PV, PR)"""
88 s = s.strip(" <>=")
89 e = 0
90 if s.count(':'):
91 e = int(s.split(":")[0])
92 s = s.split(":")[1]
93 r = ""
94 if s.count('-'):
95 r = s.rsplit("-", 1)[1]
96 s = s.rsplit("-", 1)[0]
97 v = s
98 return (e, v, r)
99
100def vercmp_part(a, b):
101 va = explode_version(a)
102 vb = explode_version(b)
103 while True:
104 if va == []:
105 (oa, ca) = (0, None)
106 else:
107 (oa, ca) = va.pop(0)
108 if vb == []:
109 (ob, cb) = (0, None)
110 else:
111 (ob, cb) = vb.pop(0)
112 if (oa, ca) == (0, None) and (ob, cb) == (0, None):
113 return 0
114 if oa < ob:
115 return -1
116 elif oa > ob:
117 return 1
Brad Bishop19323692019-04-05 15:28:33 -0400118 elif ca is None:
119 return -1
120 elif cb is None:
121 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500122 elif ca < cb:
123 return -1
124 elif ca > cb:
125 return 1
126
127def vercmp(ta, tb):
128 (ea, va, ra) = ta
129 (eb, vb, rb) = tb
130
131 r = int(ea or 0) - int(eb or 0)
132 if (r == 0):
133 r = vercmp_part(va, vb)
134 if (r == 0):
135 r = vercmp_part(ra, rb)
136 return r
137
138def vercmp_string(a, b):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600139 """ Split version strings and compare them """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500140 ta = split_version(a)
141 tb = split_version(b)
142 return vercmp(ta, tb)
143
144def vercmp_string_op(a, b, op):
145 """
146 Compare two versions and check if the specified comparison operator matches the result of the comparison.
147 This function is fairly liberal about what operators it will accept since there are a variety of styles
148 depending on the context.
149 """
150 res = vercmp_string(a, b)
151 if op in ('=', '=='):
152 return res == 0
153 elif op == '<=':
154 return res <= 0
155 elif op == '>=':
156 return res >= 0
157 elif op in ('>', '>>'):
158 return res > 0
159 elif op in ('<', '<<'):
160 return res < 0
161 elif op == '!=':
162 return res != 0
163 else:
164 raise VersionStringException('Unsupported comparison operator "%s"' % op)
165
166def explode_deps(s):
167 """
168 Take an RDEPENDS style string of format:
169 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
170 and return a list of dependencies.
171 Version information is ignored.
172 """
173 r = []
174 l = s.split()
175 flag = False
176 for i in l:
177 if i[0] == '(':
178 flag = True
179 #j = []
180 if not flag:
181 r.append(i)
182 #else:
183 # j.append(i)
184 if flag and i.endswith(')'):
185 flag = False
186 # Ignore version
187 #r[-1] += ' ' + ' '.join(j)
188 return r
189
Brad Bishop316dfdd2018-06-25 12:45:53 -0400190def explode_dep_versions2(s, *, sort=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500191 """
192 Take an RDEPENDS style string of format:
193 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
194 and return a dictionary of dependencies and versions.
195 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600196 r = collections.OrderedDict()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500197 l = s.replace(",", "").split()
198 lastdep = None
199 lastcmp = ""
200 lastver = ""
201 incmp = False
202 inversion = False
203 for i in l:
204 if i[0] == '(':
205 incmp = True
206 i = i[1:].strip()
207 if not i:
208 continue
209
210 if incmp:
211 incmp = False
212 inversion = True
213 # This list is based on behavior and supported comparisons from deb, opkg and rpm.
214 #
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600215 # Even though =<, <<, ==, !=, =>, and >> may not be supported,
216 # we list each possibly valid item.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500217 # The build system is responsible for validation of what it supports.
218 if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')):
219 lastcmp = i[0:2]
220 i = i[2:]
221 elif i.startswith(('<', '>', '=')):
222 lastcmp = i[0:1]
223 i = i[1:]
224 else:
225 # This is an unsupported case!
226 raise VersionStringException('Invalid version specification in "(%s" - invalid or missing operator' % i)
227 lastcmp = (i or "")
228 i = ""
229 i.strip()
230 if not i:
231 continue
232
233 if inversion:
234 if i.endswith(')'):
235 i = i[:-1] or ""
236 inversion = False
237 if lastver and i:
238 lastver += " "
239 if i:
240 lastver += i
241 if lastdep not in r:
242 r[lastdep] = []
243 r[lastdep].append(lastcmp + " " + lastver)
244 continue
245
246 #if not inversion:
247 lastdep = i
248 lastver = ""
249 lastcmp = ""
250 if not (i in r and r[i]):
251 r[lastdep] = []
252
Brad Bishop316dfdd2018-06-25 12:45:53 -0400253 if sort:
254 r = collections.OrderedDict(sorted(r.items(), key=lambda x: x[0]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500255 return r
256
257def explode_dep_versions(s):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600258 """
259 Take an RDEPENDS style string of format:
260 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000261 skip null value and items appeared in dependency string multiple times
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600262 and return a dictionary of dependencies and versions.
263 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500264 r = explode_dep_versions2(s)
265 for d in r:
266 if not r[d]:
267 r[d] = None
268 continue
269 if len(r[d]) > 1:
270 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))
271 r[d] = r[d][0]
272 return r
273
274def join_deps(deps, commasep=True):
275 """
276 Take the result from explode_dep_versions and generate a dependency string
277 """
278 result = []
279 for dep in deps:
280 if deps[dep]:
281 if isinstance(deps[dep], list):
282 for v in deps[dep]:
283 result.append(dep + " (" + v + ")")
284 else:
285 result.append(dep + " (" + deps[dep] + ")")
286 else:
287 result.append(dep)
288 if commasep:
289 return ", ".join(result)
290 else:
291 return " ".join(result)
292
293def _print_trace(body, line):
294 """
295 Print the Environment of a Text Body
296 """
297 error = []
298 # print the environment of the method
299 min_line = max(1, line-4)
300 max_line = min(line + 4, len(body))
301 for i in range(min_line, max_line + 1):
302 if line == i:
303 error.append(' *** %.4d:%s' % (i, body[i-1].rstrip()))
304 else:
305 error.append(' %.4d:%s' % (i, body[i-1].rstrip()))
306 return error
307
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500308def better_compile(text, file, realfile, mode = "exec", lineno = 0):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500309 """
310 A better compile method. This method
311 will print the offending lines.
312 """
313 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500314 cache = bb.methodpool.compile_cache(text)
315 if cache:
316 return cache
317 # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though
318 text2 = "\n" * int(lineno) + text
319 code = compile(text2, realfile, mode)
320 bb.methodpool.compile_cache_add(text, code)
321 return code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322 except Exception as e:
323 error = []
324 # split the text into lines again
325 body = text.split('\n')
Brad Bishop19323692019-04-05 15:28:33 -0400326 error.append("Error in compiling python function in %s, line %s:\n" % (realfile, e.lineno))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500327 if hasattr(e, "lineno"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500328 error.append("The code lines resulting in this error were:")
Brad Bishop19323692019-04-05 15:28:33 -0400329 # e.lineno: line's position in reaflile
330 # lineno: function name's "position -1" in realfile
331 # e.lineno - lineno: line's relative position in function
332 error.extend(_print_trace(body, e.lineno - lineno))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500333 else:
334 error.append("The function causing this error was:")
335 for line in body:
336 error.append(line)
337 error.append("%s: %s" % (e.__class__.__name__, str(e)))
338
339 logger.error("\n".join(error))
340
341 e = bb.BBHandledException(e)
342 raise e
343
344def _print_exception(t, value, tb, realfile, text, context):
345 error = []
346 try:
347 exception = traceback.format_exception_only(t, value)
348 error.append('Error executing a python function in %s:\n' % realfile)
349
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600350 # Strip 'us' from the stack (better_exec call) unless that was where the
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500351 # error came from
352 if tb.tb_next is not None:
353 tb = tb.tb_next
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500354
355 textarray = text.split('\n')
356
357 linefailed = tb.tb_lineno
358
359 tbextract = traceback.extract_tb(tb)
360 tbformat = traceback.format_list(tbextract)
361 error.append("The stack trace of python calls that resulted in this exception/failure was:")
362 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2]))
363 error.extend(_print_trace(textarray, linefailed))
364
365 # See if this is a function we constructed and has calls back into other functions in
366 # "text". If so, try and improve the context of the error by diving down the trace
367 level = 0
368 nexttb = tb.tb_next
369 while nexttb is not None and (level+1) < len(tbextract):
370 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]))
371 if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]:
372 # The code was possibly in the string we compiled ourselves
373 error.extend(_print_trace(textarray, tbextract[level+1][1]))
374 elif tbextract[level+1][0].startswith("/"):
375 # The code looks like it might be in a file, try and load it
376 try:
377 with open(tbextract[level+1][0], "r") as f:
378 text = f.readlines()
379 error.extend(_print_trace(text, tbextract[level+1][1]))
380 except:
381 error.append(tbformat[level+1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500382 else:
383 error.append(tbformat[level+1])
384 nexttb = tb.tb_next
385 level = level + 1
386
387 error.append("Exception: %s" % ''.join(exception))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600388
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000389 # If the exception is from spawning a task, let's be helpful and display
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600390 # the output (which hopefully includes stderr).
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500391 if isinstance(value, subprocess.CalledProcessError) and value.output:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600392 error.append("Subprocess output:")
393 error.append(value.output.decode("utf-8", errors="ignore"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500394 finally:
395 logger.error("\n".join(error))
396
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500397def better_exec(code, context, text = None, realfile = "<code>", pythonexception=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500398 """
399 Similiar to better_compile, better_exec will
400 print the lines that are responsible for the
401 error.
402 """
403 import bb.parse
404 if not text:
405 text = code
406 if not hasattr(code, "co_filename"):
407 code = better_compile(code, realfile, realfile)
408 try:
409 exec(code, get_context(), context)
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000410 except (bb.BBHandledException, bb.parse.SkipRecipe, bb.data_smart.ExpansionError, bb.process.ExecutionError):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500411 # Error already shown so passthrough, no need for traceback
412 raise
413 except Exception as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500414 if pythonexception:
415 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500416 (t, value, tb) = sys.exc_info()
417 try:
418 _print_exception(t, value, tb, realfile, text, context)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500419 except Exception as e2:
420 logger.error("Exception handler error: %s" % str(e2))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500421
422 e = bb.BBHandledException(e)
423 raise e
424
425def simple_exec(code, context):
426 exec(code, get_context(), context)
427
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600428def better_eval(source, locals, extraglobals = None):
429 ctx = get_context()
430 if extraglobals:
431 ctx = copy.copy(ctx)
432 for g in extraglobals:
433 ctx[g] = extraglobals[g]
434 return eval(source, ctx, locals)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500435
436@contextmanager
Patrick Williams92b42cb2022-09-03 06:53:57 -0500437def fileslocked(files, *args, **kwargs):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500438 """Context manager for locking and unlocking file locks."""
439 locks = []
440 if files:
441 for lockfile in files:
Patrick Williams92b42cb2022-09-03 06:53:57 -0500442 l = bb.utils.lockfile(lockfile, *args, **kwargs)
443 if l is not None:
444 locks.append(l)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500445
Andrew Geissler82c905d2020-04-13 13:39:40 -0500446 try:
447 yield
448 finally:
449 for lock in locks:
450 bb.utils.unlockfile(lock)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500451
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500452def lockfile(name, shared=False, retry=True, block=False):
453 """
454 Use the specified file as a lock file, return when the lock has
455 been acquired. Returns a variable to pass to unlockfile().
456 Parameters:
457 retry: True to re-try locking if it fails, False otherwise
458 block: True to block until the lock succeeds, False otherwise
459 The retry and block parameters are kind of equivalent unless you
460 consider the possibility of sending a signal to the process to break
461 out - at which point you want block=True rather than retry=True.
462 """
Andrew Geissler9aee5002022-03-30 16:27:02 +0000463 basename = os.path.basename(name)
464 if len(basename) > 255:
465 root, ext = os.path.splitext(basename)
466 basename = root[:255 - len(ext)] + ext
Andrew Geissler595f6302022-01-24 19:11:47 +0000467
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500468 dirname = os.path.dirname(name)
469 mkdirhier(dirname)
470
Andrew Geissler9aee5002022-03-30 16:27:02 +0000471 name = os.path.join(dirname, basename)
472
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500473 if not os.access(dirname, os.W_OK):
474 logger.error("Unable to acquire lock '%s', directory is not writable",
475 name)
476 sys.exit(1)
477
478 op = fcntl.LOCK_EX
479 if shared:
480 op = fcntl.LOCK_SH
481 if not retry and not block:
482 op = op | fcntl.LOCK_NB
483
484 while True:
485 # If we leave the lockfiles lying around there is no problem
486 # but we should clean up after ourselves. This gives potential
487 # for races though. To work around this, when we acquire the lock
488 # we check the file we locked was still the lock file on disk.
489 # by comparing inode numbers. If they don't match or the lockfile
490 # no longer exists, we start again.
491
492 # This implementation is unfair since the last person to request the
493 # lock is the most likely to win it.
494
495 try:
496 lf = open(name, 'a+')
497 fileno = lf.fileno()
498 fcntl.flock(fileno, op)
499 statinfo = os.fstat(fileno)
500 if os.path.exists(lf.name):
501 statinfo2 = os.stat(lf.name)
502 if statinfo.st_ino == statinfo2.st_ino:
503 return lf
504 lf.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800505 except OSError as e:
Andrew Geissler595f6302022-01-24 19:11:47 +0000506 if e.errno == errno.EACCES or e.errno == errno.ENAMETOOLONG:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800507 logger.error("Unable to acquire lock '%s', %s",
508 e.strerror, name)
509 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500510 try:
511 lf.close()
512 except Exception:
513 pass
514 pass
515 if not retry:
516 return None
517
518def unlockfile(lf):
519 """
520 Unlock a file locked using lockfile()
521 """
522 try:
523 # If we had a shared lock, we need to promote to exclusive before
524 # removing the lockfile. Attempt this, ignore failures.
525 fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
526 os.unlink(lf.name)
527 except (IOError, OSError):
528 pass
529 fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
530 lf.close()
531
Brad Bishop6dbb3162019-11-25 09:41:34 -0500532def _hasher(method, filename):
533 import mmap
534
535 with open(filename, "rb") as f:
536 try:
537 with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
538 for chunk in iter(lambda: mm.read(8192), b''):
539 method.update(chunk)
540 except ValueError:
541 # You can't mmap() an empty file so silence this exception
542 pass
543 return method.hexdigest()
544
545
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500546def md5_file(filename):
547 """
548 Return the hex string representation of the MD5 checksum of filename.
549 """
Brad Bishop6dbb3162019-11-25 09:41:34 -0500550 import hashlib
Patrick Williams2390b1b2022-11-03 13:47:49 -0500551 try:
552 sig = hashlib.new('MD5', usedforsecurity=False)
553 except TypeError:
554 # Some configurations don't appear to support two arguments
555 sig = hashlib.new('MD5')
556 return _hasher(sig, filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500557
558def sha256_file(filename):
559 """
560 Return the hex string representation of the 256-bit SHA checksum of
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500561 filename.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500562 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500563 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500564 return _hasher(hashlib.sha256(), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500565
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500566def sha1_file(filename):
567 """
568 Return the hex string representation of the SHA1 checksum of the filename
569 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500570 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500571 return _hasher(hashlib.sha1(), filename)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500572
Andrew Geissler82c905d2020-04-13 13:39:40 -0500573def sha384_file(filename):
574 """
575 Return the hex string representation of the SHA384 checksum of the filename
576 """
577 import hashlib
578 return _hasher(hashlib.sha384(), filename)
579
580def sha512_file(filename):
581 """
582 Return the hex string representation of the SHA512 checksum of the filename
583 """
584 import hashlib
585 return _hasher(hashlib.sha512(), filename)
586
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500587def preserved_envvars_exported():
588 """Variables which are taken from the environment and placed in and exported
589 from the metadata"""
590 return [
591 'BB_TASKHASH',
592 'HOME',
593 'LOGNAME',
594 'PATH',
595 'PWD',
596 'SHELL',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500597 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600598 'LC_ALL',
599 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500600 ]
601
602def preserved_envvars():
603 """Variables which are taken from the environment and placed in the metadata"""
604 v = [
605 'BBPATH',
606 'BB_PRESERVE_ENV',
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000607 'BB_ENV_PASSTHROUGH',
608 'BB_ENV_PASSTHROUGH_ADDITIONS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500609 ]
610 return v + preserved_envvars_exported()
611
Andrew Geissler517393d2023-01-13 08:55:19 -0600612def check_system_locale():
613 """Make sure the required system locale are available and configured"""
614 default_locale = locale.getlocale(locale.LC_CTYPE)
615
616 try:
617 locale.setlocale(locale.LC_CTYPE, ("en_US", "UTF-8"))
618 except:
619 sys.exit("Please make sure locale 'en_US.UTF-8' is available on your system")
620 else:
621 locale.setlocale(locale.LC_CTYPE, default_locale)
622
623 if sys.getfilesystemencoding() != "utf-8":
624 sys.exit("Please use a locale setting which supports UTF-8 (such as LANG=en_US.UTF-8).\n"
625 "Python can't change the filesystem locale after loading so we need a UTF-8 when Python starts or things won't work.")
626
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500627def filter_environment(good_vars):
628 """
629 Create a pristine environment for bitbake. This will remove variables that
630 are not known and may influence the build in a negative way.
631 """
632
633 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600634 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500635 if key in good_vars:
636 continue
637
638 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500639 del os.environ[key]
640
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600641 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
642 # access methods will use ascii. You can't change that mode once the interpreter is
643 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
644 # distros support that and we need to set something.
645 os.environ["LC_ALL"] = "en_US.UTF-8"
646
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500647 if removed_vars:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600648 logger.debug("Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500649
650 return removed_vars
651
652def approved_variables():
653 """
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000654 Determine and return the list of variables which are approved
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500655 to remain in the environment.
656 """
657 if 'BB_PRESERVE_ENV' in os.environ:
658 return os.environ.keys()
659 approved = []
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000660 if 'BB_ENV_PASSTHROUGH' in os.environ:
661 approved = os.environ['BB_ENV_PASSTHROUGH'].split()
662 approved.extend(['BB_ENV_PASSTHROUGH'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500663 else:
664 approved = preserved_envvars()
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000665 if 'BB_ENV_PASSTHROUGH_ADDITIONS' in os.environ:
666 approved.extend(os.environ['BB_ENV_PASSTHROUGH_ADDITIONS'].split())
667 if 'BB_ENV_PASSTHROUGH_ADDITIONS' not in approved:
668 approved.extend(['BB_ENV_PASSTHROUGH_ADDITIONS'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500669 return approved
670
671def clean_environment():
672 """
673 Clean up any spurious environment variables. This will remove any
674 variables the user hasn't chosen to preserve.
675 """
676 if 'BB_PRESERVE_ENV' not in os.environ:
677 good_vars = approved_variables()
678 return filter_environment(good_vars)
679
680 return {}
681
682def empty_environment():
683 """
684 Remove all variables from the environment.
685 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600686 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500687 os.unsetenv(s)
688 del os.environ[s]
689
690def build_environment(d):
691 """
692 Build an environment from all exported variables.
693 """
694 import bb.data
695 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500696 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500697 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500698 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500699
700def _check_unsafe_delete_path(path):
701 """
702 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
703 the caller should raise an exception with an appropriate message.
704 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
705 with potentially disastrous results.
706 """
707 extra = ''
708 # HOME might not be /home/something, so in case we can get it, check against it
709 homedir = os.environ.get('HOME', '')
710 if homedir:
711 extra = '|%s' % homedir
712 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
713 return True
714 return False
715
Brad Bishopa34c0302019-09-23 22:34:48 -0400716def remove(path, recurse=False, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500717 """Equivalent to rm -f or rm -rf"""
718 if not path:
719 return
720 if recurse:
721 for name in glob.glob(path):
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500722 if _check_unsafe_delete_path(name):
723 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500724 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishopa34c0302019-09-23 22:34:48 -0400725 cmd = []
726 if ionice:
727 cmd = ['ionice', '-c', '3']
728 subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500729 return
730 for name in glob.glob(path):
731 try:
732 os.unlink(name)
733 except OSError as exc:
734 if exc.errno != errno.ENOENT:
735 raise
736
Brad Bishopa34c0302019-09-23 22:34:48 -0400737def prunedir(topdir, ionice=False):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600738 """ Delete everything reachable from the directory named in 'topdir'. """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500739 # CAUTION: This is dangerous!
740 if _check_unsafe_delete_path(topdir):
741 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
Brad Bishopa34c0302019-09-23 22:34:48 -0400742 remove(topdir, recurse=True, ionice=ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500743
744#
745# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
746# but thats possibly insane and suffixes is probably going to be small
747#
748def prune_suffix(var, suffixes, d):
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600749 """
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600750 See if var ends with any of the suffixes listed and
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600751 remove it if found
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600752 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500753 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -0400754 if suffix and var.endswith(suffix):
755 return var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500756 return var
757
758def mkdirhier(directory):
759 """Create a directory like 'mkdir -p', but does not complain if
760 directory already exists like os.makedirs
761 """
Patrick Williams169d7bc2024-01-05 11:33:25 -0600762 if '${' in str(directory):
763 bb.fatal("Directory name {} contains unexpanded bitbake variable. This may cause build failures and WORKDIR polution.".format(directory))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500764 try:
765 os.makedirs(directory)
766 except OSError as e:
Brad Bishopc342db32019-05-15 21:57:59 -0400767 if e.errno != errno.EEXIST or not os.path.isdir(directory):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500768 raise e
769
770def movefile(src, dest, newmtime = None, sstat = None):
771 """Moves a file from src to dest, preserving all permissions and
772 attributes; mtime will be preserved even when moving across
773 filesystems. Returns true on success and false on failure. Move is
774 atomic.
775 """
776
777 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
778 try:
779 if not sstat:
780 sstat = os.lstat(src)
781 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500782 logger.warning("movefile: Stating source file failed...", e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500783 return None
784
785 destexists = 1
786 try:
787 dstat = os.lstat(dest)
788 except:
789 dstat = os.lstat(os.path.dirname(dest))
790 destexists = 0
791
792 if destexists:
793 if stat.S_ISLNK(dstat[stat.ST_MODE]):
794 try:
795 os.unlink(dest)
796 destexists = 0
797 except Exception as e:
798 pass
799
800 if stat.S_ISLNK(sstat[stat.ST_MODE]):
801 try:
802 target = os.readlink(src)
803 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
804 os.unlink(dest)
805 os.symlink(target, dest)
806 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
807 os.unlink(src)
808 return os.lstat(dest)
809 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500810 logger.warning("movefile: failed to properly create symlink:", dest, "->", target, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500811 return None
812
813 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500814 # os.rename needs to know the dest path ending with file name
815 # so append the file name to a path only if it's a dir specified
816 srcfname = os.path.basename(src)
817 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
818 else dest
819
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500820 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
821 try:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500822 bb.utils.rename(src, destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500823 renamefailed = 0
824 except Exception as e:
Brad Bishop79641f22019-09-10 07:20:22 -0400825 if e.errno != errno.EXDEV:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500826 # Some random error.
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500827 logger.warning("movefile: Failed to move", src, "to", dest, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500828 return None
829 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
830
831 if renamefailed:
832 didcopy = 0
833 if stat.S_ISREG(sstat[stat.ST_MODE]):
834 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500835 shutil.copyfile(src, destpath + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500836 bb.utils.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500837 didcopy = 1
838 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500839 logger.warning('movefile: copy', src, '->', dest, 'failed.', e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500840 return None
841 else:
842 #we don't yet handle special, so we need to fall back to /bin/mv
843 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
844 if a[0] != 0:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500845 logger.warning("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500846 return None # failure
847 try:
848 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400849 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
850 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500851 os.unlink(src)
852 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500853 logger.warning("movefile: Failed to chown/chmod/unlink", dest, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500854 return None
855
856 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500857 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500858 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500859 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500860 newmtime = sstat[stat.ST_MTIME]
861 return newmtime
862
863def copyfile(src, dest, newmtime = None, sstat = None):
864 """
865 Copies a file from src to dest, preserving all permissions and
866 attributes; mtime will be preserved even when moving across
867 filesystems. Returns true on success and false on failure.
868 """
869 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
870 try:
871 if not sstat:
872 sstat = os.lstat(src)
873 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600874 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500875 return False
876
877 destexists = 1
878 try:
879 dstat = os.lstat(dest)
880 except:
881 dstat = os.lstat(os.path.dirname(dest))
882 destexists = 0
883
884 if destexists:
885 if stat.S_ISLNK(dstat[stat.ST_MODE]):
886 try:
887 os.unlink(dest)
888 destexists = 0
889 except Exception as e:
890 pass
891
892 if stat.S_ISLNK(sstat[stat.ST_MODE]):
893 try:
894 target = os.readlink(src)
895 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
896 os.unlink(dest)
897 os.symlink(target, dest)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500898 os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500899 return os.lstat(dest)
900 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600901 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500902 return False
903
904 if stat.S_ISREG(sstat[stat.ST_MODE]):
905 try:
906 srcchown = False
907 if not os.access(src, os.R_OK):
908 # Make sure we can read it
909 srcchown = True
910 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
911
912 # For safety copy then move it over.
913 shutil.copyfile(src, dest + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500914 bb.utils.rename(dest + "#new", dest)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500915 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600916 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500917 return False
918 finally:
919 if srcchown:
920 os.chmod(src, sstat[stat.ST_MODE])
921 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
922
923 else:
924 #we don't yet handle special, so we need to fall back to /bin/mv
925 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
926 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600927 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500928 return False # failure
929 try:
930 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
931 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
932 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600933 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500934 return False
935
936 if newmtime:
937 os.utime(dest, (newmtime, newmtime))
938 else:
939 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
940 newmtime = sstat[stat.ST_MTIME]
941 return newmtime
942
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800943def break_hardlinks(src, sstat = None):
944 """
945 Ensures src is the only hardlink to this file. Other hardlinks,
946 if any, are not affected (other than in their st_nlink value, of
947 course). Returns true on success and false on failure.
948
949 """
950 try:
951 if not sstat:
952 sstat = os.lstat(src)
953 except Exception as e:
954 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
955 return False
956 if sstat[stat.ST_NLINK] == 1:
957 return True
958 return copyfile(src, src, sstat=sstat)
959
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500960def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500961 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500962 Locate `item` in the list of paths `path` (colon separated string like $PATH).
963 If `direction` is non-zero then the list is reversed.
964 If `history` is True then the list of candidates also returned as result,history.
965 If `executable` is True then the candidate has to be an executable file,
966 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500967 """
968
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500969 if executable:
970 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
971 else:
972 is_candidate = lambda p: os.path.exists(p)
973
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500974 hist = []
975 paths = (path or "").split(':')
976 if direction != 0:
977 paths.reverse()
978
979 for p in paths:
980 next = os.path.join(p, item)
981 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500982 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500983 if not os.path.isabs(next):
984 next = os.path.abspath(next)
985 if history:
986 return next, hist
987 return next
988
989 if history:
990 return "", hist
991 return ""
992
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500993@contextmanager
994def umask(new_mask):
995 """
996 Context manager to set the umask to a specific mask, and restore it afterwards.
997 """
998 current_mask = os.umask(new_mask)
999 try:
1000 yield
1001 finally:
1002 os.umask(current_mask)
1003
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001004def to_boolean(string, default=None):
Patrick Williamsac13d5f2023-11-24 18:59:46 -06001005 """
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001006 Check input string and return boolean value True/False/None
Patrick Williamsac13d5f2023-11-24 18:59:46 -06001007 depending upon the checks
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001008 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001009 if not string:
1010 return default
1011
Andrew Geissler517393d2023-01-13 08:55:19 -06001012 if isinstance(string, int):
1013 return string != 0
1014
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001015 normalized = string.lower()
1016 if normalized in ("y", "yes", "1", "true"):
1017 return True
1018 elif normalized in ("n", "no", "0", "false"):
1019 return False
1020 else:
1021 raise ValueError("Invalid value for to_boolean: %s" % string)
1022
1023def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001024 """Check if a variable contains all the values specified.
1025
1026 Arguments:
1027
1028 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001029 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001030
1031 checkvalues -- if this is a string it is split on whitespace into a set(),
1032 otherwise coerced directly into a set().
1033
1034 truevalue -- the value to return if checkvalues is a subset of variable.
1035
1036 falsevalue -- the value to return if variable is empty or if checkvalues is
1037 not a subset of variable.
1038
1039 d -- the data store.
1040 """
1041
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001042 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001043 if not val:
1044 return falsevalue
1045 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001046 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001047 checkvalues = set(checkvalues.split())
1048 else:
1049 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001050 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001051 return truevalue
1052 return falsevalue
1053
1054def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001055 """Check if a variable contains any values specified.
1056
1057 Arguments:
1058
1059 variable -- the variable name. This will be fetched and expanded (using
1060 d.getVar(variable)) and then split into a set().
1061
1062 checkvalues -- if this is a string it is split on whitespace into a set(),
1063 otherwise coerced directly into a set().
1064
1065 truevalue -- the value to return if checkvalues is a subset of variable.
1066
1067 falsevalue -- the value to return if variable is empty or if checkvalues is
1068 not a subset of variable.
1069
1070 d -- the data store.
1071 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001072 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001073 if not val:
1074 return falsevalue
1075 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001076 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001077 checkvalues = set(checkvalues.split())
1078 else:
1079 checkvalues = set(checkvalues)
1080 if checkvalues & val:
1081 return truevalue
1082 return falsevalue
1083
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001084def filter(variable, checkvalues, d):
1085 """Return all words in the variable that are present in the checkvalues.
1086
1087 Arguments:
1088
1089 variable -- the variable name. This will be fetched and expanded (using
1090 d.getVar(variable)) and then split into a set().
1091
1092 checkvalues -- if this is a string it is split on whitespace into a set(),
1093 otherwise coerced directly into a set().
1094
1095 d -- the data store.
1096 """
1097
1098 val = d.getVar(variable)
1099 if not val:
1100 return ''
1101 val = set(val.split())
1102 if isinstance(checkvalues, str):
1103 checkvalues = set(checkvalues.split())
1104 else:
1105 checkvalues = set(checkvalues)
1106 return ' '.join(sorted(checkvalues & val))
1107
Andrew Geissler82c905d2020-04-13 13:39:40 -05001108
1109def get_referenced_vars(start_expr, d):
1110 """
1111 :return: names of vars referenced in start_expr (recursively), in quasi-BFS order (variables within the same level
1112 are ordered arbitrarily)
1113 """
1114
1115 seen = set()
1116 ret = []
1117
1118 # The first entry in the queue is the unexpanded start expression
1119 queue = collections.deque([start_expr])
1120 # Subsequent entries will be variable names, so we need to track whether or not entry requires getVar
1121 is_first = True
1122
1123 empty_data = bb.data.init()
1124 while queue:
1125 entry = queue.popleft()
1126 if is_first:
1127 # Entry is the start expression - no expansion needed
1128 is_first = False
1129 expression = entry
1130 else:
1131 # This is a variable name - need to get the value
1132 expression = d.getVar(entry, False)
1133 ret.append(entry)
1134
1135 # expandWithRefs is how we actually get the referenced variables in the expression. We call it using an empty
1136 # data store because we only want the variables directly used in the expression. It returns a set, which is what
1137 # dooms us to only ever be "quasi-BFS" rather than full BFS.
1138 new_vars = empty_data.expandWithRefs(expression, None).references - set(seen)
1139
1140 queue.extend(new_vars)
1141 seen.update(new_vars)
1142 return ret
1143
1144
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001145def cpu_count():
1146 return multiprocessing.cpu_count()
1147
1148def nonblockingfd(fd):
1149 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1150
1151def process_profilelog(fn, pout = None):
1152 # Either call with a list of filenames and set pout or a filename and optionally pout.
1153 if not pout:
1154 pout = fn + '.processed'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001155
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001156 with open(pout, 'w') as pout:
1157 import pstats
1158 if isinstance(fn, list):
1159 p = pstats.Stats(*fn, stream=pout)
1160 else:
1161 p = pstats.Stats(fn, stream=pout)
1162 p.sort_stats('time')
1163 p.print_stats()
1164 p.print_callers()
1165 p.sort_stats('cumulative')
1166 p.print_stats()
1167
1168 pout.flush()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001169
1170#
1171# Was present to work around multiprocessing pool bugs in python < 2.7.3
1172#
1173def multiprocessingpool(*args, **kwargs):
1174
1175 import multiprocessing.pool
1176 #import multiprocessing.util
1177 #multiprocessing.util.log_to_stderr(10)
1178 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1179 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1180 def wrapper(func):
1181 def wrap(self, timeout=None):
1182 return func(self, timeout=timeout if timeout is not None else 1e100)
1183 return wrap
1184 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1185
1186 return multiprocessing.Pool(*args, **kwargs)
1187
1188def exec_flat_python_func(func, *args, **kwargs):
1189 """Execute a flat python function (defined with def funcname(args):...)"""
1190 # Prepare a small piece of python code which calls the requested function
1191 # To do this we need to prepare two things - a set of variables we can use to pass
1192 # the values of arguments into the calling function, and the list of arguments for
1193 # the function being called
1194 context = {}
1195 funcargs = []
1196 # Handle unnamed arguments
1197 aidx = 1
1198 for arg in args:
1199 argname = 'arg_%s' % aidx
1200 context[argname] = arg
1201 funcargs.append(argname)
1202 aidx += 1
1203 # Handle keyword arguments
1204 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001205 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001206 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1207 comp = bb.utils.better_compile(code, '<string>', '<string>')
1208 bb.utils.better_exec(comp, context, code, '<string>')
1209 return context['retval']
1210
1211def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1212 """Edit lines from a recipe or config file and modify one or more
1213 specified variable values set in the file using a specified callback
1214 function. Lines are expected to have trailing newlines.
1215 Parameters:
1216 meta_lines: lines from the file; can be a list or an iterable
1217 (e.g. file pointer)
1218 variables: a list of variable names to look for. Functions
1219 may also be specified, but must be specified with '()' at
1220 the end of the name. Note that the function doesn't have
Patrick Williams213cb262021-08-07 19:21:33 -05001221 any intrinsic understanding of :append, :prepend, :remove,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001222 or overrides, so these are considered as part of the name.
1223 These values go into a regular expression, so regular
1224 expression syntax is allowed.
1225 varfunc: callback function called for every variable matching
1226 one of the entries in the variables parameter. The function
1227 should take four arguments:
1228 varname: name of variable matched
1229 origvalue: current value in file
1230 op: the operator (e.g. '+=')
1231 newlines: list of lines up to this point. You can use
1232 this to prepend lines before this variable setting
1233 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001234 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001235 newvalue: new value to substitute in, or None to drop
1236 the variable setting entirely. (If the removal
1237 results in two consecutive blank lines, one of the
1238 blank lines will also be dropped).
1239 newop: the operator to use - if you specify None here,
1240 the original operation will be used.
1241 indent: number of spaces to indent multi-line entries,
1242 or -1 to indent up to the level of the assignment
1243 and opening quote, or a string to use as the indent.
1244 minbreak: True to allow the first element of a
1245 multi-line value to continue on the same line as
1246 the assignment, False to indent before the first
1247 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001248 To clarify, if you wish not to change the value, then you
1249 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001250 match_overrides: True to match items with _overrides on the end,
1251 False otherwise
1252 Returns a tuple:
1253 updated:
1254 True if changes were made, False otherwise.
1255 newlines:
1256 Lines after processing
1257 """
1258
1259 var_res = {}
1260 if match_overrides:
Brad Bishop19323692019-04-05 15:28:33 -04001261 override_re = r'(_[a-zA-Z0-9-_$(){}]+)?'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001262 else:
1263 override_re = ''
1264 for var in variables:
1265 if var.endswith('()'):
Brad Bishop19323692019-04-05 15:28:33 -04001266 var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001267 else:
Brad Bishop19323692019-04-05 15:28:33 -04001268 var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001269
1270 updated = False
1271 varset_start = ''
1272 varlines = []
1273 newlines = []
1274 in_var = None
1275 full_value = ''
1276 var_end = ''
1277
1278 def handle_var_end():
1279 prerun_newlines = newlines[:]
1280 op = varset_start[len(in_var):].strip()
1281 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1282 changed = (prerun_newlines != newlines)
1283
1284 if newvalue is None:
1285 # Drop the value
1286 return True
1287 elif newvalue != full_value or (newop not in [None, op]):
1288 if newop not in [None, op]:
1289 # Callback changed the operator
1290 varset_new = "%s %s" % (in_var, newop)
1291 else:
1292 varset_new = varset_start
1293
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001294 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001295 if indent == -1:
1296 indentspc = ' ' * (len(varset_new) + 2)
1297 else:
1298 indentspc = ' ' * indent
1299 else:
1300 indentspc = indent
1301 if in_var.endswith('()'):
1302 # A function definition
1303 if isinstance(newvalue, list):
1304 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1305 else:
1306 if not newvalue.startswith('\n'):
1307 newvalue = '\n' + newvalue
1308 if not newvalue.endswith('\n'):
1309 newvalue = newvalue + '\n'
1310 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1311 else:
1312 # Normal variable
1313 if isinstance(newvalue, list):
1314 if not newvalue:
1315 # Empty list -> empty string
1316 newlines.append('%s ""\n' % varset_new)
1317 elif minbreak:
1318 # First item on first line
1319 if len(newvalue) == 1:
1320 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1321 else:
1322 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1323 for item in newvalue[1:]:
1324 newlines.append('%s%s \\\n' % (indentspc, item))
1325 newlines.append('%s"\n' % indentspc)
1326 else:
1327 # No item on first line
1328 newlines.append('%s " \\\n' % varset_new)
1329 for item in newvalue:
1330 newlines.append('%s%s \\\n' % (indentspc, item))
1331 newlines.append('%s"\n' % indentspc)
1332 else:
1333 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1334 return True
1335 else:
1336 # Put the old lines back where they were
1337 newlines.extend(varlines)
1338 # If newlines was touched by the function, we'll need to return True
1339 return changed
1340
1341 checkspc = False
1342
1343 for line in meta_lines:
1344 if in_var:
1345 value = line.rstrip()
1346 varlines.append(line)
1347 if in_var.endswith('()'):
1348 full_value += '\n' + value
1349 else:
1350 full_value += value[:-1]
1351 if value.endswith(var_end):
1352 if in_var.endswith('()'):
1353 if full_value.count('{') - full_value.count('}') >= 0:
1354 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001355 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001356 if handle_var_end():
1357 updated = True
1358 checkspc = True
1359 in_var = None
1360 else:
1361 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001362 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001363 res = var_re.match(line)
1364 if res:
1365 isfunc = varname.endswith('()')
1366 if isfunc:
1367 splitvalue = line.split('{', 1)
1368 var_end = '}'
1369 else:
1370 var_end = res.groups()[-1]
1371 splitvalue = line.split(var_end, 1)
1372 varset_start = splitvalue[0].rstrip()
1373 value = splitvalue[1].rstrip()
1374 if not isfunc and value.endswith('\\'):
1375 value = value[:-1]
1376 full_value = value
1377 varlines = [line]
1378 in_var = res.group(1)
1379 if isfunc:
1380 in_var += '()'
1381 if value.endswith(var_end):
1382 full_value = full_value[:-1]
1383 if handle_var_end():
1384 updated = True
1385 checkspc = True
1386 in_var = None
1387 skip = True
1388 break
1389 if not skip:
1390 if checkspc:
1391 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001392 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001393 # Squash blank line if there are two consecutive blanks after a removal
1394 continue
1395 newlines.append(line)
1396 return (updated, newlines)
1397
1398
1399def edit_metadata_file(meta_file, variables, varfunc):
1400 """Edit a recipe or config file and modify one or more specified
1401 variable values set in the file using a specified callback function.
1402 The file is only written to if the value(s) actually change.
1403 This is basically the file version of edit_metadata(), see that
1404 function's description for parameter/usage information.
1405 Returns True if the file was written to, False otherwise.
1406 """
1407 with open(meta_file, 'r') as f:
1408 (updated, newlines) = edit_metadata(f, variables, varfunc)
1409 if updated:
1410 with open(meta_file, 'w') as f:
1411 f.writelines(newlines)
1412 return updated
1413
1414
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001415def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001416 """Edit bblayers.conf, adding and/or removing layers
1417 Parameters:
1418 bblayers_conf: path to bblayers.conf file to edit
1419 add: layer path (or list of layer paths) to add; None or empty
1420 list to add nothing
1421 remove: layer path (or list of layer paths) to remove; None or
1422 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001423 edit_cb: optional callback function that will be called after
1424 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001425 Returns a tuple:
1426 notadded: list of layers specified to be added but weren't
1427 (because they were already in the list)
1428 notremoved: list of layers that were specified to be removed
1429 but weren't (because they weren't in the list)
1430 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001431
1432 import fnmatch
1433
1434 def remove_trailing_sep(pth):
1435 if pth and pth[-1] == os.sep:
1436 pth = pth[:-1]
1437 return pth
1438
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001439 approved = bb.utils.approved_variables()
1440 def canonicalise_path(pth):
1441 pth = remove_trailing_sep(pth)
1442 if 'HOME' in approved and '~' in pth:
1443 pth = os.path.expanduser(pth)
1444 return pth
1445
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001446 def layerlist_param(value):
1447 if not value:
1448 return []
1449 elif isinstance(value, list):
1450 return [remove_trailing_sep(x) for x in value]
1451 else:
1452 return [remove_trailing_sep(value)]
1453
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001454 addlayers = layerlist_param(add)
1455 removelayers = layerlist_param(remove)
1456
1457 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1458 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001459 removed = []
1460 plusequals = False
1461 orig_bblayers = []
1462
1463 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1464 bblayercalls.append(op)
1465 if op == '=':
1466 del orig_bblayers[:]
1467 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1468 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001469
1470 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001471 updated = False
1472 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1473 if removelayers:
1474 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001475 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001476 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001477 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001478 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001479 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001480 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001481 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001482 for addlayer in addlayers:
1483 if addlayer not in bblayers:
1484 updated = True
1485 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001486 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001487
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001488 if edit_cb:
1489 newlist = []
1490 for layer in bblayers:
1491 res = edit_cb(layer, canonicalise_path(layer))
1492 if res != layer:
1493 newlist.append(res)
1494 updated = True
1495 else:
1496 newlist.append(layer)
1497 bblayers = newlist
1498
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001499 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001500 if op == '+=' and not bblayers:
1501 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001502 return (bblayers, None, 2, False)
1503 else:
1504 return (origvalue, None, 2, False)
1505
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001506 with open(bblayers_conf, 'r') as f:
1507 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001508
1509 if not bblayercalls:
1510 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1511
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001512 # Try to do the "smart" thing depending on how the user has laid out
1513 # their bblayers.conf file
1514 if bblayercalls.count('+=') > 1:
1515 plusequals = True
1516
1517 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1518 notadded = []
1519 for layer in addlayers:
1520 layer_canon = canonicalise_path(layer)
1521 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1522 notadded.append(layer)
1523 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1524 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1525
1526 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1527 if addlayers:
1528 # Still need to add these
1529 for addlayer in addlayers:
1530 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1531 updated = True
1532
1533 if updated:
1534 with open(bblayers_conf, 'w') as f:
1535 f.writelines(newlines)
1536
1537 notremoved = list(set(removelayers) - set(removed))
1538
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001539 return (notadded, notremoved)
1540
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001541def get_collection_res(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001542 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001543 collection_res = {}
1544 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001545 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001546
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001547 return collection_res
1548
1549
1550def get_file_layer(filename, d, collection_res={}):
1551 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
1552 if not collection_res:
1553 collection_res = get_collection_res(d)
1554
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001555 def path_to_layer(path):
1556 # Use longest path so we handle nested layers
1557 matchlen = 0
1558 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001559 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001560 if len(regex) > matchlen and re.match(regex, path):
1561 matchlen = len(regex)
1562 match = collection
1563 return match
1564
1565 result = None
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001566 bbfiles = (d.getVar('BBFILES_PRIORITIZED') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001567 bbfilesmatch = False
1568 for bbfilesentry in bbfiles:
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001569 if fnmatch.fnmatchcase(filename, bbfilesentry):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001570 bbfilesmatch = True
1571 result = path_to_layer(bbfilesentry)
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001572 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001573
1574 if not bbfilesmatch:
1575 # Probably a bbclass
1576 result = path_to_layer(filename)
1577
1578 return result
1579
1580
1581# Constant taken from http://linux.die.net/include/linux/prctl.h
1582PR_SET_PDEATHSIG = 1
1583
1584class PrCtlError(Exception):
1585 pass
1586
1587def signal_on_parent_exit(signame):
1588 """
1589 Trigger signame to be sent when the parent process dies
1590 """
1591 signum = getattr(signal, signame)
1592 # http://linux.die.net/man/2/prctl
1593 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1594 if result != 0:
1595 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001596
1597#
1598# Manually call the ioprio syscall. We could depend on other libs like psutil
1599# however this gets us enough of what we need to bitbake for now without the
1600# dependency
1601#
1602_unamearch = os.uname()[4]
1603IOPRIO_WHO_PROCESS = 1
1604IOPRIO_CLASS_SHIFT = 13
1605
1606def ioprio_set(who, cls, value):
1607 NR_ioprio_set = None
1608 if _unamearch == "x86_64":
1609 NR_ioprio_set = 251
1610 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1611 NR_ioprio_set = 289
Brad Bishop19323692019-04-05 15:28:33 -04001612 elif _unamearch == "aarch64":
1613 NR_ioprio_set = 30
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001614
1615 if NR_ioprio_set:
1616 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1617 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1618 if rc != 0:
1619 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1620 else:
1621 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001622
1623def set_process_name(name):
1624 from ctypes import cdll, byref, create_string_buffer
1625 # This is nice to have for debugging, not essential
1626 try:
1627 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001628 buf = create_string_buffer(bytes(name, 'utf-8'))
1629 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001630 except:
1631 pass
1632
Andrew Geissler87f5cff2022-09-30 13:13:31 -05001633def enable_loopback_networking():
1634 # From bits/ioctls.h
1635 SIOCGIFFLAGS = 0x8913
1636 SIOCSIFFLAGS = 0x8914
1637 SIOCSIFADDR = 0x8916
1638 SIOCSIFNETMASK = 0x891C
1639
1640 # if.h
1641 IFF_UP = 0x1
1642 IFF_RUNNING = 0x40
1643
1644 # bits/socket.h
1645 AF_INET = 2
1646
1647 # char ifr_name[IFNAMSIZ=16]
1648 ifr_name = struct.pack("@16s", b"lo")
1649 def netdev_req(fd, req, data = b""):
1650 # Pad and add interface name
1651 data = ifr_name + data + (b'\x00' * (16 - len(data)))
1652 # Return all data after interface name
1653 return fcntl.ioctl(fd, req, data)[16:]
1654
1655 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_IP) as sock:
1656 fd = sock.fileno()
1657
1658 # struct sockaddr_in ifr_addr { unsigned short family; uint16_t sin_port ; uint32_t in_addr; }
1659 req = struct.pack("@H", AF_INET) + struct.pack("=H4B", 0, 127, 0, 0, 1)
1660 netdev_req(fd, SIOCSIFADDR, req)
1661
1662 # short ifr_flags
1663 flags = struct.unpack_from('@h', netdev_req(fd, SIOCGIFFLAGS))[0]
1664 flags |= IFF_UP | IFF_RUNNING
1665 netdev_req(fd, SIOCSIFFLAGS, struct.pack('@h', flags))
1666
1667 # struct sockaddr_in ifr_netmask
1668 req = struct.pack("@H", AF_INET) + struct.pack("=H4B", 0, 255, 0, 0, 0)
1669 netdev_req(fd, SIOCSIFNETMASK, req)
1670
Andrew Geissler595f6302022-01-24 19:11:47 +00001671def disable_network(uid=None, gid=None):
1672 """
1673 Disable networking in the current process if the kernel supports it, else
1674 just return after logging to debug. To do this we need to create a new user
1675 namespace, then map back to the original uid/gid.
1676 """
1677 libc = ctypes.CDLL('libc.so.6')
1678
1679 # From sched.h
1680 # New user namespace
1681 CLONE_NEWUSER = 0x10000000
1682 # New network namespace
1683 CLONE_NEWNET = 0x40000000
1684
1685 if uid is None:
1686 uid = os.getuid()
1687 if gid is None:
1688 gid = os.getgid()
1689
1690 ret = libc.unshare(CLONE_NEWNET | CLONE_NEWUSER)
1691 if ret != 0:
Andrew Geissler87f5cff2022-09-30 13:13:31 -05001692 logger.debug("System doesn't support disabling network without admin privs")
Andrew Geissler595f6302022-01-24 19:11:47 +00001693 return
1694 with open("/proc/self/uid_map", "w") as f:
1695 f.write("%s %s 1" % (uid, uid))
1696 with open("/proc/self/setgroups", "w") as f:
1697 f.write("deny")
1698 with open("/proc/self/gid_map", "w") as f:
1699 f.write("%s %s 1" % (gid, gid))
1700
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001701def export_proxies(d):
Andrew Geissler6aa7eec2023-03-03 12:41:14 -06001702 from bb.fetch2 import get_fetcher_environment
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001703 """ export common proxies variables from datastore to environment """
Andrew Geissler6aa7eec2023-03-03 12:41:14 -06001704 newenv = get_fetcher_environment(d)
1705 for v in newenv:
1706 os.environ[v] = newenv[v]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001707
1708def load_plugins(logger, plugins, pluginpath):
1709 def load_plugin(name):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001710 logger.debug('Loading plugin %s' % name)
Brad Bishop19323692019-04-05 15:28:33 -04001711 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1712 if spec:
Andrew Geissler595f6302022-01-24 19:11:47 +00001713 mod = importlib.util.module_from_spec(spec)
1714 spec.loader.exec_module(mod)
1715 return mod
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001716
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001717 logger.debug('Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001718
1719 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1720 for ext in python_extensions)
1721 files = itertools.chain.from_iterable(expanded)
1722 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1723 for name in names:
1724 if name != '__init__':
1725 plugin = load_plugin(name)
1726 if hasattr(plugin, 'plugin_init'):
1727 obj = plugin.plugin_init(plugins)
1728 plugins.append(obj or plugin)
1729 else:
1730 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001731
1732
1733class LogCatcher(logging.Handler):
1734 """Logging handler for collecting logged messages so you can check them later"""
1735 def __init__(self):
1736 self.messages = []
1737 logging.Handler.__init__(self, logging.WARNING)
1738 def emit(self, record):
1739 self.messages.append(bb.build.logformatter.format(record))
1740 def contains(self, message):
1741 return (message in self.messages)
Andrew Geissler82c905d2020-04-13 13:39:40 -05001742
1743def is_semver(version):
1744 """
1745 Is the version string following the semver semantic?
1746
1747 https://semver.org/spec/v2.0.0.html
1748 """
1749 regex = re.compile(
1750 r"""
1751 ^
1752 (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
1753 (?:-(
1754 (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
1755 (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
1756 ))?
1757 (?:\+(
1758 [0-9a-zA-Z-]+
1759 (?:\.[0-9a-zA-Z-]+)*
1760 ))?
1761 $
1762 """, re.VERBOSE)
1763
1764 if regex.match(version) is None:
1765 return False
1766
1767 return True
Andrew Geisslerc926e172021-05-07 16:11:35 -05001768
1769# Wrapper around os.rename which can handle cross device problems
1770# e.g. from container filesystems
1771def rename(src, dst):
1772 try:
1773 os.rename(src, dst)
1774 except OSError as err:
1775 if err.errno == 18:
1776 # Invalid cross-device link error
1777 shutil.move(src, dst)
1778 else:
1779 raise err
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001780
1781@contextmanager
1782def environment(**envvars):
1783 """
1784 Context manager to selectively update the environment with the specified mapping.
1785 """
1786 backup = dict(os.environ)
1787 try:
1788 os.environ.update(envvars)
1789 yield
1790 finally:
1791 for var in envvars:
1792 if var in backup:
1793 os.environ[var] = backup[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001794 elif var in os.environ:
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001795 del os.environ[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001796
1797def is_local_uid(uid=''):
1798 """
1799 Check whether uid is a local one or not.
1800 Can't use pwd module since it gets all UIDs, not local ones only.
1801 """
1802 if not uid:
1803 uid = os.getuid()
1804 with open('/etc/passwd', 'r') as f:
1805 for line in f:
1806 line_split = line.split(':')
1807 if len(line_split) < 3:
1808 continue
1809 if str(uid) == line_split[2]:
1810 return True
1811 return False
Patrick Williams92b42cb2022-09-03 06:53:57 -05001812
1813def mkstemp(suffix=None, prefix=None, dir=None, text=False):
1814 """
1815 Generates a unique filename, independent of time.
1816
1817 mkstemp() in glibc (at least) generates unique file names based on the
1818 current system time. When combined with highly parallel builds, and
1819 operating over NFS (e.g. shared sstate/downloads) this can result in
1820 conflicts and race conditions.
1821
1822 This function adds additional entropy to the file name so that a collision
1823 is independent of time and thus extremely unlikely.
1824 """
1825 entropy = "".join(random.choices("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", k=20))
1826 if prefix:
1827 prefix = prefix + entropy
1828 else:
1829 prefix = tempfile.gettempprefix() + entropy
1830 return tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir, text=text)
Andrew Geissler517393d2023-01-13 08:55:19 -06001831
Andrew Geissler220dafd2023-10-04 10:18:08 -05001832def path_is_descendant(descendant, ancestor):
1833 """
1834 Returns True if the path `descendant` is a descendant of `ancestor`
1835 (including being equivalent to `ancestor` itself). Otherwise returns False.
1836 Correctly accounts for symlinks, bind mounts, etc. by using
1837 os.path.samestat() to compare paths
1838
1839 May raise any exception that os.stat() raises
1840 """
1841
1842 ancestor_stat = os.stat(ancestor)
1843
1844 # Recurse up each directory component of the descendant to see if it is
1845 # equivalent to the ancestor
1846 check_dir = os.path.abspath(descendant).rstrip("/")
1847 while check_dir:
1848 check_stat = os.stat(check_dir)
1849 if os.path.samestat(check_stat, ancestor_stat):
1850 return True
1851 check_dir = os.path.dirname(check_dir).rstrip("/")
1852
1853 return False
1854
Andrew Geissler517393d2023-01-13 08:55:19 -06001855# If we don't have a timeout of some kind and a process/thread exits badly (for example
1856# OOM killed) and held a lock, we'd just hang in the lock futex forever. It is better
1857# we exit at some point than hang. 5 minutes with no progress means we're probably deadlocked.
1858@contextmanager
1859def lock_timeout(lock):
1860 held = lock.acquire(timeout=5*60)
1861 try:
1862 if not held:
1863 os._exit(1)
1864 yield held
1865 finally:
1866 lock.release()