blob: ad13d04331234c3bad2a23181200575998d3e2b5 [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_ADDITIONS',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500608 ]
609 return v + preserved_envvars_exported()
610
Andrew Geissler517393d2023-01-13 08:55:19 -0600611def check_system_locale():
612 """Make sure the required system locale are available and configured"""
613 default_locale = locale.getlocale(locale.LC_CTYPE)
614
615 try:
616 locale.setlocale(locale.LC_CTYPE, ("en_US", "UTF-8"))
617 except:
618 sys.exit("Please make sure locale 'en_US.UTF-8' is available on your system")
619 else:
620 locale.setlocale(locale.LC_CTYPE, default_locale)
621
622 if sys.getfilesystemencoding() != "utf-8":
623 sys.exit("Please use a locale setting which supports UTF-8 (such as LANG=en_US.UTF-8).\n"
624 "Python can't change the filesystem locale after loading so we need a UTF-8 when Python starts or things won't work.")
625
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500626def filter_environment(good_vars):
627 """
628 Create a pristine environment for bitbake. This will remove variables that
629 are not known and may influence the build in a negative way.
630 """
631
632 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600633 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500634 if key in good_vars:
635 continue
636
637 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500638 del os.environ[key]
639
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600640 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
641 # access methods will use ascii. You can't change that mode once the interpreter is
642 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
643 # distros support that and we need to set something.
644 os.environ["LC_ALL"] = "en_US.UTF-8"
645
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500646 if removed_vars:
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600647 logger.debug("Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500648
649 return removed_vars
650
651def approved_variables():
652 """
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000653 Determine and return the list of variables which are approved
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500654 to remain in the environment.
655 """
656 if 'BB_PRESERVE_ENV' in os.environ:
657 return os.environ.keys()
658 approved = []
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000659 if 'BB_ENV_PASSTHROUGH' in os.environ:
660 approved = os.environ['BB_ENV_PASSTHROUGH'].split()
661 approved.extend(['BB_ENV_PASSTHROUGH'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500662 else:
663 approved = preserved_envvars()
Andrew Geissler7e0e3c02022-02-25 20:34:39 +0000664 if 'BB_ENV_PASSTHROUGH_ADDITIONS' in os.environ:
665 approved.extend(os.environ['BB_ENV_PASSTHROUGH_ADDITIONS'].split())
666 if 'BB_ENV_PASSTHROUGH_ADDITIONS' not in approved:
667 approved.extend(['BB_ENV_PASSTHROUGH_ADDITIONS'])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500668 return approved
669
670def clean_environment():
671 """
672 Clean up any spurious environment variables. This will remove any
673 variables the user hasn't chosen to preserve.
674 """
675 if 'BB_PRESERVE_ENV' not in os.environ:
676 good_vars = approved_variables()
677 return filter_environment(good_vars)
678
679 return {}
680
681def empty_environment():
682 """
683 Remove all variables from the environment.
684 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600685 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500686 os.unsetenv(s)
687 del os.environ[s]
688
689def build_environment(d):
690 """
691 Build an environment from all exported variables.
692 """
693 import bb.data
694 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500695 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500696 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500697 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500698
699def _check_unsafe_delete_path(path):
700 """
701 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
702 the caller should raise an exception with an appropriate message.
703 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
704 with potentially disastrous results.
705 """
706 extra = ''
707 # HOME might not be /home/something, so in case we can get it, check against it
708 homedir = os.environ.get('HOME', '')
709 if homedir:
710 extra = '|%s' % homedir
711 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
712 return True
713 return False
714
Brad Bishopa34c0302019-09-23 22:34:48 -0400715def remove(path, recurse=False, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500716 """Equivalent to rm -f or rm -rf"""
717 if not path:
718 return
719 if recurse:
720 for name in glob.glob(path):
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500721 if _check_unsafe_delete_path(name):
722 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % name)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500723 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishopa34c0302019-09-23 22:34:48 -0400724 cmd = []
725 if ionice:
726 cmd = ['ionice', '-c', '3']
727 subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500728 return
729 for name in glob.glob(path):
730 try:
731 os.unlink(name)
732 except OSError as exc:
733 if exc.errno != errno.ENOENT:
734 raise
735
Brad Bishopa34c0302019-09-23 22:34:48 -0400736def prunedir(topdir, ionice=False):
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600737 """ Delete everything reachable from the directory named in 'topdir'. """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500738 # CAUTION: This is dangerous!
739 if _check_unsafe_delete_path(topdir):
740 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
Brad Bishopa34c0302019-09-23 22:34:48 -0400741 remove(topdir, recurse=True, ionice=ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500742
743#
744# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
745# but thats possibly insane and suffixes is probably going to be small
746#
747def prune_suffix(var, suffixes, d):
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600748 """
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600749 See if var ends with any of the suffixes listed and
Patrick Williamsac13d5f2023-11-24 18:59:46 -0600750 remove it if found
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600751 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500752 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -0400753 if suffix and var.endswith(suffix):
754 return var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500755 return var
756
757def mkdirhier(directory):
758 """Create a directory like 'mkdir -p', but does not complain if
759 directory already exists like os.makedirs
760 """
Patrick Williams169d7bc2024-01-05 11:33:25 -0600761 if '${' in str(directory):
762 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 -0500763 try:
764 os.makedirs(directory)
765 except OSError as e:
Brad Bishopc342db32019-05-15 21:57:59 -0400766 if e.errno != errno.EEXIST or not os.path.isdir(directory):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500767 raise e
768
769def movefile(src, dest, newmtime = None, sstat = None):
770 """Moves a file from src to dest, preserving all permissions and
771 attributes; mtime will be preserved even when moving across
772 filesystems. Returns true on success and false on failure. Move is
773 atomic.
774 """
775
776 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
777 try:
778 if not sstat:
779 sstat = os.lstat(src)
780 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500781 logger.warning("movefile: Stating source file failed...", e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500782 return None
783
784 destexists = 1
785 try:
786 dstat = os.lstat(dest)
787 except:
788 dstat = os.lstat(os.path.dirname(dest))
789 destexists = 0
790
791 if destexists:
792 if stat.S_ISLNK(dstat[stat.ST_MODE]):
793 try:
794 os.unlink(dest)
795 destexists = 0
796 except Exception as e:
797 pass
798
799 if stat.S_ISLNK(sstat[stat.ST_MODE]):
800 try:
801 target = os.readlink(src)
802 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
803 os.unlink(dest)
804 os.symlink(target, dest)
805 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
806 os.unlink(src)
807 return os.lstat(dest)
808 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500809 logger.warning("movefile: failed to properly create symlink:", dest, "->", target, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500810 return None
811
812 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500813 # os.rename needs to know the dest path ending with file name
814 # so append the file name to a path only if it's a dir specified
815 srcfname = os.path.basename(src)
816 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
817 else dest
818
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500819 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
820 try:
Andrew Geisslerc926e172021-05-07 16:11:35 -0500821 bb.utils.rename(src, destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500822 renamefailed = 0
823 except Exception as e:
Brad Bishop79641f22019-09-10 07:20:22 -0400824 if e.errno != errno.EXDEV:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500825 # Some random error.
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500826 logger.warning("movefile: Failed to move", src, "to", dest, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500827 return None
828 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
829
830 if renamefailed:
831 didcopy = 0
832 if stat.S_ISREG(sstat[stat.ST_MODE]):
833 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500834 shutil.copyfile(src, destpath + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500835 bb.utils.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500836 didcopy = 1
837 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500838 logger.warning('movefile: copy', src, '->', dest, 'failed.', e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500839 return None
840 else:
841 #we don't yet handle special, so we need to fall back to /bin/mv
842 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
843 if a[0] != 0:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500844 logger.warning("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500845 return None # failure
846 try:
847 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400848 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
849 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500850 os.unlink(src)
851 except Exception as e:
Patrick Williamsdb4c27e2022-08-05 08:10:29 -0500852 logger.warning("movefile: Failed to chown/chmod/unlink", dest, e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500853 return None
854
855 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500856 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500857 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500858 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500859 newmtime = sstat[stat.ST_MTIME]
860 return newmtime
861
862def copyfile(src, dest, newmtime = None, sstat = None):
863 """
864 Copies a file from src to dest, preserving all permissions and
865 attributes; mtime will be preserved even when moving across
866 filesystems. Returns true on success and false on failure.
867 """
868 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
869 try:
870 if not sstat:
871 sstat = os.lstat(src)
872 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600873 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500874 return False
875
876 destexists = 1
877 try:
878 dstat = os.lstat(dest)
879 except:
880 dstat = os.lstat(os.path.dirname(dest))
881 destexists = 0
882
883 if destexists:
884 if stat.S_ISLNK(dstat[stat.ST_MODE]):
885 try:
886 os.unlink(dest)
887 destexists = 0
888 except Exception as e:
889 pass
890
891 if stat.S_ISLNK(sstat[stat.ST_MODE]):
892 try:
893 target = os.readlink(src)
894 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
895 os.unlink(dest)
896 os.symlink(target, dest)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500897 os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500898 return os.lstat(dest)
899 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600900 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500901 return False
902
903 if stat.S_ISREG(sstat[stat.ST_MODE]):
904 try:
905 srcchown = False
906 if not os.access(src, os.R_OK):
907 # Make sure we can read it
908 srcchown = True
909 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
910
911 # For safety copy then move it over.
912 shutil.copyfile(src, dest + "#new")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500913 bb.utils.rename(dest + "#new", dest)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500914 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600915 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500916 return False
917 finally:
918 if srcchown:
919 os.chmod(src, sstat[stat.ST_MODE])
920 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
921
922 else:
923 #we don't yet handle special, so we need to fall back to /bin/mv
924 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
925 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600926 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500927 return False # failure
928 try:
929 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
930 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
931 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600932 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500933 return False
934
935 if newmtime:
936 os.utime(dest, (newmtime, newmtime))
937 else:
938 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
939 newmtime = sstat[stat.ST_MTIME]
940 return newmtime
941
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800942def break_hardlinks(src, sstat = None):
943 """
944 Ensures src is the only hardlink to this file. Other hardlinks,
945 if any, are not affected (other than in their st_nlink value, of
946 course). Returns true on success and false on failure.
947
948 """
949 try:
950 if not sstat:
951 sstat = os.lstat(src)
952 except Exception as e:
953 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
954 return False
955 if sstat[stat.ST_NLINK] == 1:
956 return True
957 return copyfile(src, src, sstat=sstat)
958
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500959def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500960 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500961 Locate `item` in the list of paths `path` (colon separated string like $PATH).
962 If `direction` is non-zero then the list is reversed.
963 If `history` is True then the list of candidates also returned as result,history.
964 If `executable` is True then the candidate has to be an executable file,
965 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500966 """
967
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500968 if executable:
969 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
970 else:
971 is_candidate = lambda p: os.path.exists(p)
972
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500973 hist = []
974 paths = (path or "").split(':')
975 if direction != 0:
976 paths.reverse()
977
978 for p in paths:
979 next = os.path.join(p, item)
980 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500981 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500982 if not os.path.isabs(next):
983 next = os.path.abspath(next)
984 if history:
985 return next, hist
986 return next
987
988 if history:
989 return "", hist
990 return ""
991
Andrew Geisslerc3d88e42020-10-02 09:45:00 -0500992@contextmanager
993def umask(new_mask):
994 """
995 Context manager to set the umask to a specific mask, and restore it afterwards.
996 """
997 current_mask = os.umask(new_mask)
998 try:
999 yield
1000 finally:
1001 os.umask(current_mask)
1002
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001003def to_boolean(string, default=None):
Patrick Williamsac13d5f2023-11-24 18:59:46 -06001004 """
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001005 Check input string and return boolean value True/False/None
Patrick Williamsac13d5f2023-11-24 18:59:46 -06001006 depending upon the checks
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001007 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001008 if not string:
1009 return default
1010
Andrew Geissler517393d2023-01-13 08:55:19 -06001011 if isinstance(string, int):
1012 return string != 0
1013
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001014 normalized = string.lower()
1015 if normalized in ("y", "yes", "1", "true"):
1016 return True
1017 elif normalized in ("n", "no", "0", "false"):
1018 return False
1019 else:
1020 raise ValueError("Invalid value for to_boolean: %s" % string)
1021
1022def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001023 """Check if a variable contains all the values specified.
1024
1025 Arguments:
1026
1027 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001028 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001029
1030 checkvalues -- if this is a string it is split on whitespace into a set(),
1031 otherwise coerced directly into a set().
1032
1033 truevalue -- the value to return if checkvalues is a subset of variable.
1034
1035 falsevalue -- the value to return if variable is empty or if checkvalues is
1036 not a subset of variable.
1037
1038 d -- the data store.
1039 """
1040
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001041 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001042 if not val:
1043 return falsevalue
1044 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001045 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001046 checkvalues = set(checkvalues.split())
1047 else:
1048 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001049 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001050 return truevalue
1051 return falsevalue
1052
1053def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001054 """Check if a variable contains any values specified.
1055
1056 Arguments:
1057
1058 variable -- the variable name. This will be fetched and expanded (using
1059 d.getVar(variable)) and then split into a set().
1060
1061 checkvalues -- if this is a string it is split on whitespace into a set(),
1062 otherwise coerced directly into a set().
1063
1064 truevalue -- the value to return if checkvalues is a subset of variable.
1065
1066 falsevalue -- the value to return if variable is empty or if checkvalues is
1067 not a subset of variable.
1068
1069 d -- the data store.
1070 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001071 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001072 if not val:
1073 return falsevalue
1074 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001075 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001076 checkvalues = set(checkvalues.split())
1077 else:
1078 checkvalues = set(checkvalues)
1079 if checkvalues & val:
1080 return truevalue
1081 return falsevalue
1082
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001083def filter(variable, checkvalues, d):
1084 """Return all words in the variable that are present in the checkvalues.
1085
1086 Arguments:
1087
1088 variable -- the variable name. This will be fetched and expanded (using
1089 d.getVar(variable)) and then split into a set().
1090
1091 checkvalues -- if this is a string it is split on whitespace into a set(),
1092 otherwise coerced directly into a set().
1093
1094 d -- the data store.
1095 """
1096
1097 val = d.getVar(variable)
1098 if not val:
1099 return ''
1100 val = set(val.split())
1101 if isinstance(checkvalues, str):
1102 checkvalues = set(checkvalues.split())
1103 else:
1104 checkvalues = set(checkvalues)
1105 return ' '.join(sorted(checkvalues & val))
1106
Andrew Geissler82c905d2020-04-13 13:39:40 -05001107
1108def get_referenced_vars(start_expr, d):
1109 """
1110 :return: names of vars referenced in start_expr (recursively), in quasi-BFS order (variables within the same level
1111 are ordered arbitrarily)
1112 """
1113
1114 seen = set()
1115 ret = []
1116
1117 # The first entry in the queue is the unexpanded start expression
1118 queue = collections.deque([start_expr])
1119 # Subsequent entries will be variable names, so we need to track whether or not entry requires getVar
1120 is_first = True
1121
1122 empty_data = bb.data.init()
1123 while queue:
1124 entry = queue.popleft()
1125 if is_first:
1126 # Entry is the start expression - no expansion needed
1127 is_first = False
1128 expression = entry
1129 else:
1130 # This is a variable name - need to get the value
1131 expression = d.getVar(entry, False)
1132 ret.append(entry)
1133
1134 # expandWithRefs is how we actually get the referenced variables in the expression. We call it using an empty
1135 # data store because we only want the variables directly used in the expression. It returns a set, which is what
1136 # dooms us to only ever be "quasi-BFS" rather than full BFS.
1137 new_vars = empty_data.expandWithRefs(expression, None).references - set(seen)
1138
1139 queue.extend(new_vars)
1140 seen.update(new_vars)
1141 return ret
1142
1143
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001144def cpu_count():
1145 return multiprocessing.cpu_count()
1146
1147def nonblockingfd(fd):
1148 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1149
1150def process_profilelog(fn, pout = None):
1151 # Either call with a list of filenames and set pout or a filename and optionally pout.
1152 if not pout:
1153 pout = fn + '.processed'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001154
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001155 with open(pout, 'w') as pout:
1156 import pstats
1157 if isinstance(fn, list):
1158 p = pstats.Stats(*fn, stream=pout)
1159 else:
1160 p = pstats.Stats(fn, stream=pout)
1161 p.sort_stats('time')
1162 p.print_stats()
1163 p.print_callers()
1164 p.sort_stats('cumulative')
1165 p.print_stats()
1166
1167 pout.flush()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001168
1169#
1170# Was present to work around multiprocessing pool bugs in python < 2.7.3
1171#
1172def multiprocessingpool(*args, **kwargs):
1173
1174 import multiprocessing.pool
1175 #import multiprocessing.util
1176 #multiprocessing.util.log_to_stderr(10)
1177 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1178 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1179 def wrapper(func):
1180 def wrap(self, timeout=None):
1181 return func(self, timeout=timeout if timeout is not None else 1e100)
1182 return wrap
1183 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1184
1185 return multiprocessing.Pool(*args, **kwargs)
1186
1187def exec_flat_python_func(func, *args, **kwargs):
1188 """Execute a flat python function (defined with def funcname(args):...)"""
1189 # Prepare a small piece of python code which calls the requested function
1190 # To do this we need to prepare two things - a set of variables we can use to pass
1191 # the values of arguments into the calling function, and the list of arguments for
1192 # the function being called
1193 context = {}
1194 funcargs = []
1195 # Handle unnamed arguments
1196 aidx = 1
1197 for arg in args:
1198 argname = 'arg_%s' % aidx
1199 context[argname] = arg
1200 funcargs.append(argname)
1201 aidx += 1
1202 # Handle keyword arguments
1203 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001204 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001205 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1206 comp = bb.utils.better_compile(code, '<string>', '<string>')
1207 bb.utils.better_exec(comp, context, code, '<string>')
1208 return context['retval']
1209
1210def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1211 """Edit lines from a recipe or config file and modify one or more
1212 specified variable values set in the file using a specified callback
1213 function. Lines are expected to have trailing newlines.
1214 Parameters:
1215 meta_lines: lines from the file; can be a list or an iterable
1216 (e.g. file pointer)
1217 variables: a list of variable names to look for. Functions
1218 may also be specified, but must be specified with '()' at
1219 the end of the name. Note that the function doesn't have
Patrick Williams213cb262021-08-07 19:21:33 -05001220 any intrinsic understanding of :append, :prepend, :remove,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001221 or overrides, so these are considered as part of the name.
1222 These values go into a regular expression, so regular
1223 expression syntax is allowed.
1224 varfunc: callback function called for every variable matching
1225 one of the entries in the variables parameter. The function
1226 should take four arguments:
1227 varname: name of variable matched
1228 origvalue: current value in file
1229 op: the operator (e.g. '+=')
1230 newlines: list of lines up to this point. You can use
1231 this to prepend lines before this variable setting
1232 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001233 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001234 newvalue: new value to substitute in, or None to drop
1235 the variable setting entirely. (If the removal
1236 results in two consecutive blank lines, one of the
1237 blank lines will also be dropped).
1238 newop: the operator to use - if you specify None here,
1239 the original operation will be used.
1240 indent: number of spaces to indent multi-line entries,
1241 or -1 to indent up to the level of the assignment
1242 and opening quote, or a string to use as the indent.
1243 minbreak: True to allow the first element of a
1244 multi-line value to continue on the same line as
1245 the assignment, False to indent before the first
1246 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001247 To clarify, if you wish not to change the value, then you
1248 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001249 match_overrides: True to match items with _overrides on the end,
1250 False otherwise
1251 Returns a tuple:
1252 updated:
1253 True if changes were made, False otherwise.
1254 newlines:
1255 Lines after processing
1256 """
1257
1258 var_res = {}
1259 if match_overrides:
Brad Bishop19323692019-04-05 15:28:33 -04001260 override_re = r'(_[a-zA-Z0-9-_$(){}]+)?'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001261 else:
1262 override_re = ''
1263 for var in variables:
1264 if var.endswith('()'):
Brad Bishop19323692019-04-05 15:28:33 -04001265 var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001266 else:
Brad Bishop19323692019-04-05 15:28:33 -04001267 var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001268
1269 updated = False
1270 varset_start = ''
1271 varlines = []
1272 newlines = []
1273 in_var = None
1274 full_value = ''
1275 var_end = ''
1276
1277 def handle_var_end():
1278 prerun_newlines = newlines[:]
1279 op = varset_start[len(in_var):].strip()
1280 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1281 changed = (prerun_newlines != newlines)
1282
1283 if newvalue is None:
1284 # Drop the value
1285 return True
1286 elif newvalue != full_value or (newop not in [None, op]):
1287 if newop not in [None, op]:
1288 # Callback changed the operator
1289 varset_new = "%s %s" % (in_var, newop)
1290 else:
1291 varset_new = varset_start
1292
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001293 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001294 if indent == -1:
1295 indentspc = ' ' * (len(varset_new) + 2)
1296 else:
1297 indentspc = ' ' * indent
1298 else:
1299 indentspc = indent
1300 if in_var.endswith('()'):
1301 # A function definition
1302 if isinstance(newvalue, list):
1303 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1304 else:
1305 if not newvalue.startswith('\n'):
1306 newvalue = '\n' + newvalue
1307 if not newvalue.endswith('\n'):
1308 newvalue = newvalue + '\n'
1309 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1310 else:
1311 # Normal variable
1312 if isinstance(newvalue, list):
1313 if not newvalue:
1314 # Empty list -> empty string
1315 newlines.append('%s ""\n' % varset_new)
1316 elif minbreak:
1317 # First item on first line
1318 if len(newvalue) == 1:
1319 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1320 else:
1321 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1322 for item in newvalue[1:]:
1323 newlines.append('%s%s \\\n' % (indentspc, item))
1324 newlines.append('%s"\n' % indentspc)
1325 else:
1326 # No item on first line
1327 newlines.append('%s " \\\n' % varset_new)
1328 for item in newvalue:
1329 newlines.append('%s%s \\\n' % (indentspc, item))
1330 newlines.append('%s"\n' % indentspc)
1331 else:
1332 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1333 return True
1334 else:
1335 # Put the old lines back where they were
1336 newlines.extend(varlines)
1337 # If newlines was touched by the function, we'll need to return True
1338 return changed
1339
1340 checkspc = False
1341
1342 for line in meta_lines:
1343 if in_var:
1344 value = line.rstrip()
1345 varlines.append(line)
1346 if in_var.endswith('()'):
1347 full_value += '\n' + value
1348 else:
1349 full_value += value[:-1]
1350 if value.endswith(var_end):
1351 if in_var.endswith('()'):
1352 if full_value.count('{') - full_value.count('}') >= 0:
1353 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001354 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001355 if handle_var_end():
1356 updated = True
1357 checkspc = True
1358 in_var = None
1359 else:
1360 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001361 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001362 res = var_re.match(line)
1363 if res:
1364 isfunc = varname.endswith('()')
1365 if isfunc:
1366 splitvalue = line.split('{', 1)
1367 var_end = '}'
1368 else:
1369 var_end = res.groups()[-1]
1370 splitvalue = line.split(var_end, 1)
1371 varset_start = splitvalue[0].rstrip()
1372 value = splitvalue[1].rstrip()
1373 if not isfunc and value.endswith('\\'):
1374 value = value[:-1]
1375 full_value = value
1376 varlines = [line]
1377 in_var = res.group(1)
1378 if isfunc:
1379 in_var += '()'
1380 if value.endswith(var_end):
1381 full_value = full_value[:-1]
1382 if handle_var_end():
1383 updated = True
1384 checkspc = True
1385 in_var = None
1386 skip = True
1387 break
1388 if not skip:
1389 if checkspc:
1390 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001391 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001392 # Squash blank line if there are two consecutive blanks after a removal
1393 continue
1394 newlines.append(line)
1395 return (updated, newlines)
1396
1397
1398def edit_metadata_file(meta_file, variables, varfunc):
1399 """Edit a recipe or config file and modify one or more specified
1400 variable values set in the file using a specified callback function.
1401 The file is only written to if the value(s) actually change.
1402 This is basically the file version of edit_metadata(), see that
1403 function's description for parameter/usage information.
1404 Returns True if the file was written to, False otherwise.
1405 """
1406 with open(meta_file, 'r') as f:
1407 (updated, newlines) = edit_metadata(f, variables, varfunc)
1408 if updated:
1409 with open(meta_file, 'w') as f:
1410 f.writelines(newlines)
1411 return updated
1412
1413
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001414def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001415 """Edit bblayers.conf, adding and/or removing layers
1416 Parameters:
1417 bblayers_conf: path to bblayers.conf file to edit
1418 add: layer path (or list of layer paths) to add; None or empty
1419 list to add nothing
1420 remove: layer path (or list of layer paths) to remove; None or
1421 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001422 edit_cb: optional callback function that will be called after
1423 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001424 Returns a tuple:
1425 notadded: list of layers specified to be added but weren't
1426 (because they were already in the list)
1427 notremoved: list of layers that were specified to be removed
1428 but weren't (because they weren't in the list)
1429 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001430
1431 import fnmatch
1432
1433 def remove_trailing_sep(pth):
1434 if pth and pth[-1] == os.sep:
1435 pth = pth[:-1]
1436 return pth
1437
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001438 approved = bb.utils.approved_variables()
1439 def canonicalise_path(pth):
1440 pth = remove_trailing_sep(pth)
1441 if 'HOME' in approved and '~' in pth:
1442 pth = os.path.expanduser(pth)
1443 return pth
1444
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001445 def layerlist_param(value):
1446 if not value:
1447 return []
1448 elif isinstance(value, list):
1449 return [remove_trailing_sep(x) for x in value]
1450 else:
1451 return [remove_trailing_sep(value)]
1452
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001453 addlayers = layerlist_param(add)
1454 removelayers = layerlist_param(remove)
1455
1456 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1457 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001458 removed = []
1459 plusequals = False
1460 orig_bblayers = []
1461
1462 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1463 bblayercalls.append(op)
1464 if op == '=':
1465 del orig_bblayers[:]
1466 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1467 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001468
1469 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001470 updated = False
1471 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1472 if removelayers:
1473 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001474 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001475 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001476 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001477 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001478 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001479 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001480 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001481 for addlayer in addlayers:
1482 if addlayer not in bblayers:
1483 updated = True
1484 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001485 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001486
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001487 if edit_cb:
1488 newlist = []
1489 for layer in bblayers:
1490 res = edit_cb(layer, canonicalise_path(layer))
1491 if res != layer:
1492 newlist.append(res)
1493 updated = True
1494 else:
1495 newlist.append(layer)
1496 bblayers = newlist
1497
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001498 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001499 if op == '+=' and not bblayers:
1500 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001501 return (bblayers, None, 2, False)
1502 else:
1503 return (origvalue, None, 2, False)
1504
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001505 with open(bblayers_conf, 'r') as f:
1506 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001507
1508 if not bblayercalls:
1509 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1510
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001511 # Try to do the "smart" thing depending on how the user has laid out
1512 # their bblayers.conf file
1513 if bblayercalls.count('+=') > 1:
1514 plusequals = True
1515
1516 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1517 notadded = []
1518 for layer in addlayers:
1519 layer_canon = canonicalise_path(layer)
1520 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1521 notadded.append(layer)
1522 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1523 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1524
1525 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1526 if addlayers:
1527 # Still need to add these
1528 for addlayer in addlayers:
1529 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1530 updated = True
1531
1532 if updated:
1533 with open(bblayers_conf, 'w') as f:
1534 f.writelines(newlines)
1535
1536 notremoved = list(set(removelayers) - set(removed))
1537
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001538 return (notadded, notremoved)
1539
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001540def get_collection_res(d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001541 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001542 collection_res = {}
1543 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001544 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001545
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001546 return collection_res
1547
1548
1549def get_file_layer(filename, d, collection_res={}):
1550 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
1551 if not collection_res:
1552 collection_res = get_collection_res(d)
1553
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001554 def path_to_layer(path):
1555 # Use longest path so we handle nested layers
1556 matchlen = 0
1557 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001558 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001559 if len(regex) > matchlen and re.match(regex, path):
1560 matchlen = len(regex)
1561 match = collection
1562 return match
1563
1564 result = None
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001565 bbfiles = (d.getVar('BBFILES_PRIORITIZED') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001566 bbfilesmatch = False
1567 for bbfilesentry in bbfiles:
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001568 if fnmatch.fnmatchcase(filename, bbfilesentry):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001569 bbfilesmatch = True
1570 result = path_to_layer(bbfilesentry)
Andrew Geisslerc9f78652020-09-18 14:11:35 -05001571 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001572
1573 if not bbfilesmatch:
1574 # Probably a bbclass
1575 result = path_to_layer(filename)
1576
1577 return result
1578
1579
1580# Constant taken from http://linux.die.net/include/linux/prctl.h
1581PR_SET_PDEATHSIG = 1
1582
1583class PrCtlError(Exception):
1584 pass
1585
1586def signal_on_parent_exit(signame):
1587 """
1588 Trigger signame to be sent when the parent process dies
1589 """
1590 signum = getattr(signal, signame)
1591 # http://linux.die.net/man/2/prctl
1592 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1593 if result != 0:
1594 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001595
1596#
1597# Manually call the ioprio syscall. We could depend on other libs like psutil
1598# however this gets us enough of what we need to bitbake for now without the
1599# dependency
1600#
1601_unamearch = os.uname()[4]
1602IOPRIO_WHO_PROCESS = 1
1603IOPRIO_CLASS_SHIFT = 13
1604
1605def ioprio_set(who, cls, value):
1606 NR_ioprio_set = None
1607 if _unamearch == "x86_64":
1608 NR_ioprio_set = 251
1609 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1610 NR_ioprio_set = 289
Brad Bishop19323692019-04-05 15:28:33 -04001611 elif _unamearch == "aarch64":
1612 NR_ioprio_set = 30
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001613
1614 if NR_ioprio_set:
1615 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1616 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1617 if rc != 0:
1618 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1619 else:
1620 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001621
1622def set_process_name(name):
1623 from ctypes import cdll, byref, create_string_buffer
1624 # This is nice to have for debugging, not essential
1625 try:
1626 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001627 buf = create_string_buffer(bytes(name, 'utf-8'))
1628 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001629 except:
1630 pass
1631
Andrew Geissler87f5cff2022-09-30 13:13:31 -05001632def enable_loopback_networking():
1633 # From bits/ioctls.h
1634 SIOCGIFFLAGS = 0x8913
1635 SIOCSIFFLAGS = 0x8914
1636 SIOCSIFADDR = 0x8916
1637 SIOCSIFNETMASK = 0x891C
1638
1639 # if.h
1640 IFF_UP = 0x1
1641 IFF_RUNNING = 0x40
1642
1643 # bits/socket.h
1644 AF_INET = 2
1645
1646 # char ifr_name[IFNAMSIZ=16]
1647 ifr_name = struct.pack("@16s", b"lo")
1648 def netdev_req(fd, req, data = b""):
1649 # Pad and add interface name
1650 data = ifr_name + data + (b'\x00' * (16 - len(data)))
1651 # Return all data after interface name
1652 return fcntl.ioctl(fd, req, data)[16:]
1653
1654 with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_IP) as sock:
1655 fd = sock.fileno()
1656
1657 # struct sockaddr_in ifr_addr { unsigned short family; uint16_t sin_port ; uint32_t in_addr; }
1658 req = struct.pack("@H", AF_INET) + struct.pack("=H4B", 0, 127, 0, 0, 1)
1659 netdev_req(fd, SIOCSIFADDR, req)
1660
1661 # short ifr_flags
1662 flags = struct.unpack_from('@h', netdev_req(fd, SIOCGIFFLAGS))[0]
1663 flags |= IFF_UP | IFF_RUNNING
1664 netdev_req(fd, SIOCSIFFLAGS, struct.pack('@h', flags))
1665
1666 # struct sockaddr_in ifr_netmask
1667 req = struct.pack("@H", AF_INET) + struct.pack("=H4B", 0, 255, 0, 0, 0)
1668 netdev_req(fd, SIOCSIFNETMASK, req)
1669
Andrew Geissler595f6302022-01-24 19:11:47 +00001670def disable_network(uid=None, gid=None):
1671 """
1672 Disable networking in the current process if the kernel supports it, else
1673 just return after logging to debug. To do this we need to create a new user
1674 namespace, then map back to the original uid/gid.
1675 """
1676 libc = ctypes.CDLL('libc.so.6')
1677
1678 # From sched.h
1679 # New user namespace
1680 CLONE_NEWUSER = 0x10000000
1681 # New network namespace
1682 CLONE_NEWNET = 0x40000000
1683
1684 if uid is None:
1685 uid = os.getuid()
1686 if gid is None:
1687 gid = os.getgid()
1688
1689 ret = libc.unshare(CLONE_NEWNET | CLONE_NEWUSER)
1690 if ret != 0:
Andrew Geissler87f5cff2022-09-30 13:13:31 -05001691 logger.debug("System doesn't support disabling network without admin privs")
Andrew Geissler595f6302022-01-24 19:11:47 +00001692 return
1693 with open("/proc/self/uid_map", "w") as f:
1694 f.write("%s %s 1" % (uid, uid))
1695 with open("/proc/self/setgroups", "w") as f:
1696 f.write("deny")
1697 with open("/proc/self/gid_map", "w") as f:
1698 f.write("%s %s 1" % (gid, gid))
1699
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001700def export_proxies(d):
Andrew Geissler6aa7eec2023-03-03 12:41:14 -06001701 from bb.fetch2 import get_fetcher_environment
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001702 """ export common proxies variables from datastore to environment """
Andrew Geissler6aa7eec2023-03-03 12:41:14 -06001703 newenv = get_fetcher_environment(d)
1704 for v in newenv:
1705 os.environ[v] = newenv[v]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001706
1707def load_plugins(logger, plugins, pluginpath):
1708 def load_plugin(name):
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001709 logger.debug('Loading plugin %s' % name)
Brad Bishop19323692019-04-05 15:28:33 -04001710 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1711 if spec:
Andrew Geissler595f6302022-01-24 19:11:47 +00001712 mod = importlib.util.module_from_spec(spec)
1713 spec.loader.exec_module(mod)
1714 return mod
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001715
Andrew Geisslerd1e89492021-02-12 15:35:20 -06001716 logger.debug('Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001717
1718 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1719 for ext in python_extensions)
1720 files = itertools.chain.from_iterable(expanded)
1721 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1722 for name in names:
1723 if name != '__init__':
1724 plugin = load_plugin(name)
1725 if hasattr(plugin, 'plugin_init'):
1726 obj = plugin.plugin_init(plugins)
1727 plugins.append(obj or plugin)
1728 else:
1729 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001730
1731
1732class LogCatcher(logging.Handler):
1733 """Logging handler for collecting logged messages so you can check them later"""
1734 def __init__(self):
1735 self.messages = []
1736 logging.Handler.__init__(self, logging.WARNING)
1737 def emit(self, record):
1738 self.messages.append(bb.build.logformatter.format(record))
1739 def contains(self, message):
1740 return (message in self.messages)
Andrew Geissler82c905d2020-04-13 13:39:40 -05001741
1742def is_semver(version):
1743 """
1744 Is the version string following the semver semantic?
1745
1746 https://semver.org/spec/v2.0.0.html
1747 """
1748 regex = re.compile(
1749 r"""
1750 ^
1751 (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
1752 (?:-(
1753 (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
1754 (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
1755 ))?
1756 (?:\+(
1757 [0-9a-zA-Z-]+
1758 (?:\.[0-9a-zA-Z-]+)*
1759 ))?
1760 $
1761 """, re.VERBOSE)
1762
1763 if regex.match(version) is None:
1764 return False
1765
1766 return True
Andrew Geisslerc926e172021-05-07 16:11:35 -05001767
1768# Wrapper around os.rename which can handle cross device problems
1769# e.g. from container filesystems
1770def rename(src, dst):
1771 try:
1772 os.rename(src, dst)
1773 except OSError as err:
1774 if err.errno == 18:
1775 # Invalid cross-device link error
1776 shutil.move(src, dst)
1777 else:
1778 raise err
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001779
1780@contextmanager
1781def environment(**envvars):
1782 """
1783 Context manager to selectively update the environment with the specified mapping.
1784 """
1785 backup = dict(os.environ)
1786 try:
1787 os.environ.update(envvars)
1788 yield
1789 finally:
1790 for var in envvars:
1791 if var in backup:
1792 os.environ[var] = backup[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001793 elif var in os.environ:
Patrick Williams0ca19cc2021-08-16 14:03:13 -05001794 del os.environ[var]
Andrew Geissler7e0e3c02022-02-25 20:34:39 +00001795
1796def is_local_uid(uid=''):
1797 """
1798 Check whether uid is a local one or not.
1799 Can't use pwd module since it gets all UIDs, not local ones only.
1800 """
1801 if not uid:
1802 uid = os.getuid()
1803 with open('/etc/passwd', 'r') as f:
1804 for line in f:
1805 line_split = line.split(':')
1806 if len(line_split) < 3:
1807 continue
1808 if str(uid) == line_split[2]:
1809 return True
1810 return False
Patrick Williams92b42cb2022-09-03 06:53:57 -05001811
1812def mkstemp(suffix=None, prefix=None, dir=None, text=False):
1813 """
1814 Generates a unique filename, independent of time.
1815
1816 mkstemp() in glibc (at least) generates unique file names based on the
1817 current system time. When combined with highly parallel builds, and
1818 operating over NFS (e.g. shared sstate/downloads) this can result in
1819 conflicts and race conditions.
1820
1821 This function adds additional entropy to the file name so that a collision
1822 is independent of time and thus extremely unlikely.
1823 """
1824 entropy = "".join(random.choices("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", k=20))
1825 if prefix:
1826 prefix = prefix + entropy
1827 else:
1828 prefix = tempfile.gettempprefix() + entropy
1829 return tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir, text=text)
Andrew Geissler517393d2023-01-13 08:55:19 -06001830
Andrew Geissler220dafd2023-10-04 10:18:08 -05001831def path_is_descendant(descendant, ancestor):
1832 """
1833 Returns True if the path `descendant` is a descendant of `ancestor`
1834 (including being equivalent to `ancestor` itself). Otherwise returns False.
1835 Correctly accounts for symlinks, bind mounts, etc. by using
1836 os.path.samestat() to compare paths
1837
1838 May raise any exception that os.stat() raises
1839 """
1840
1841 ancestor_stat = os.stat(ancestor)
1842
1843 # Recurse up each directory component of the descendant to see if it is
1844 # equivalent to the ancestor
1845 check_dir = os.path.abspath(descendant).rstrip("/")
1846 while check_dir:
1847 check_stat = os.stat(check_dir)
1848 if os.path.samestat(check_stat, ancestor_stat):
1849 return True
1850 check_dir = os.path.dirname(check_dir).rstrip("/")
1851
1852 return False
1853
Andrew Geissler517393d2023-01-13 08:55:19 -06001854# If we don't have a timeout of some kind and a process/thread exits badly (for example
1855# OOM killed) and held a lock, we'd just hang in the lock futex forever. It is better
1856# we exit at some point than hang. 5 minutes with no progress means we're probably deadlocked.
1857@contextmanager
1858def lock_timeout(lock):
1859 held = lock.acquire(timeout=5*60)
1860 try:
1861 if not held:
1862 os._exit(1)
1863 yield held
1864 finally:
1865 lock.release()