blob: 50032e50c2401838ea604e4ad0a3c14fad410fec [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001"""
2BitBake Utility Functions
3"""
4
5# Copyright (C) 2004 Michael Lauer
6#
Brad Bishopc342db32019-05-15 21:57:59 -04007# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -05008#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05009
10import re, fcntl, os, string, stat, shutil, time
11import sys
12import errno
13import logging
14import bb
15import bb.msg
16import multiprocessing
17import fcntl
Brad Bishop19323692019-04-05 15:28:33 -040018import importlib
19from importlib import machinery
Patrick Williamsc0f7c042017-02-23 20:41:17 -060020import itertools
Patrick Williamsc124f4f2015-09-15 14:41:29 -050021import subprocess
22import glob
23import fnmatch
24import traceback
25import errno
26import signal
Patrick Williamsc0f7c042017-02-23 20:41:17 -060027import collections
28import copy
29from subprocess import getstatusoutput
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030from contextlib import contextmanager
31from ctypes import cdll
32
Patrick Williamsc124f4f2015-09-15 14:41:29 -050033logger = logging.getLogger("BitBake.Util")
Brad Bishop19323692019-04-05 15:28:33 -040034python_extensions = importlib.machinery.all_suffixes()
Patrick Williamsc0f7c042017-02-23 20:41:17 -060035
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036
37def clean_context():
38 return {
39 "os": os,
40 "bb": bb,
41 "time": time,
42 }
43
44def get_context():
45 return _context
46
47
48def set_context(ctx):
49 _context = ctx
50
51# Context used in better_exec, eval
52_context = clean_context()
53
54class VersionStringException(Exception):
55 """Exception raised when an invalid version specification is found"""
56
57def explode_version(s):
58 r = []
Brad Bishop19323692019-04-05 15:28:33 -040059 alpha_regexp = re.compile(r'^([a-zA-Z]+)(.*)$')
60 numeric_regexp = re.compile(r'^(\d+)(.*)$')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050061 while (s != ''):
62 if s[0] in string.digits:
63 m = numeric_regexp.match(s)
64 r.append((0, int(m.group(1))))
65 s = m.group(2)
66 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -060067 if s[0] in string.ascii_letters:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050068 m = alpha_regexp.match(s)
69 r.append((1, m.group(1)))
70 s = m.group(2)
71 continue
72 if s[0] == '~':
73 r.append((-1, s[0]))
74 else:
75 r.append((2, s[0]))
76 s = s[1:]
77 return r
78
79def split_version(s):
80 """Split a version string into its constituent parts (PE, PV, PR)"""
81 s = s.strip(" <>=")
82 e = 0
83 if s.count(':'):
84 e = int(s.split(":")[0])
85 s = s.split(":")[1]
86 r = ""
87 if s.count('-'):
88 r = s.rsplit("-", 1)[1]
89 s = s.rsplit("-", 1)[0]
90 v = s
91 return (e, v, r)
92
93def vercmp_part(a, b):
94 va = explode_version(a)
95 vb = explode_version(b)
96 while True:
97 if va == []:
98 (oa, ca) = (0, None)
99 else:
100 (oa, ca) = va.pop(0)
101 if vb == []:
102 (ob, cb) = (0, None)
103 else:
104 (ob, cb) = vb.pop(0)
105 if (oa, ca) == (0, None) and (ob, cb) == (0, None):
106 return 0
107 if oa < ob:
108 return -1
109 elif oa > ob:
110 return 1
Brad Bishop19323692019-04-05 15:28:33 -0400111 elif ca is None:
112 return -1
113 elif cb is None:
114 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500115 elif ca < cb:
116 return -1
117 elif ca > cb:
118 return 1
119
120def vercmp(ta, tb):
121 (ea, va, ra) = ta
122 (eb, vb, rb) = tb
123
124 r = int(ea or 0) - int(eb or 0)
125 if (r == 0):
126 r = vercmp_part(va, vb)
127 if (r == 0):
128 r = vercmp_part(ra, rb)
129 return r
130
131def vercmp_string(a, b):
132 ta = split_version(a)
133 tb = split_version(b)
134 return vercmp(ta, tb)
135
136def vercmp_string_op(a, b, op):
137 """
138 Compare two versions and check if the specified comparison operator matches the result of the comparison.
139 This function is fairly liberal about what operators it will accept since there are a variety of styles
140 depending on the context.
141 """
142 res = vercmp_string(a, b)
143 if op in ('=', '=='):
144 return res == 0
145 elif op == '<=':
146 return res <= 0
147 elif op == '>=':
148 return res >= 0
149 elif op in ('>', '>>'):
150 return res > 0
151 elif op in ('<', '<<'):
152 return res < 0
153 elif op == '!=':
154 return res != 0
155 else:
156 raise VersionStringException('Unsupported comparison operator "%s"' % op)
157
158def explode_deps(s):
159 """
160 Take an RDEPENDS style string of format:
161 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
162 and return a list of dependencies.
163 Version information is ignored.
164 """
165 r = []
166 l = s.split()
167 flag = False
168 for i in l:
169 if i[0] == '(':
170 flag = True
171 #j = []
172 if not flag:
173 r.append(i)
174 #else:
175 # j.append(i)
176 if flag and i.endswith(')'):
177 flag = False
178 # Ignore version
179 #r[-1] += ' ' + ' '.join(j)
180 return r
181
Brad Bishop316dfdd2018-06-25 12:45:53 -0400182def explode_dep_versions2(s, *, sort=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500183 """
184 Take an RDEPENDS style string of format:
185 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
186 and return a dictionary of dependencies and versions.
187 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600188 r = collections.OrderedDict()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500189 l = s.replace(",", "").split()
190 lastdep = None
191 lastcmp = ""
192 lastver = ""
193 incmp = False
194 inversion = False
195 for i in l:
196 if i[0] == '(':
197 incmp = True
198 i = i[1:].strip()
199 if not i:
200 continue
201
202 if incmp:
203 incmp = False
204 inversion = True
205 # This list is based on behavior and supported comparisons from deb, opkg and rpm.
206 #
207 # Even though =<, <<, ==, !=, =>, and >> may not be supported,
208 # we list each possibly valid item.
209 # The build system is responsible for validation of what it supports.
210 if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')):
211 lastcmp = i[0:2]
212 i = i[2:]
213 elif i.startswith(('<', '>', '=')):
214 lastcmp = i[0:1]
215 i = i[1:]
216 else:
217 # This is an unsupported case!
218 raise VersionStringException('Invalid version specification in "(%s" - invalid or missing operator' % i)
219 lastcmp = (i or "")
220 i = ""
221 i.strip()
222 if not i:
223 continue
224
225 if inversion:
226 if i.endswith(')'):
227 i = i[:-1] or ""
228 inversion = False
229 if lastver and i:
230 lastver += " "
231 if i:
232 lastver += i
233 if lastdep not in r:
234 r[lastdep] = []
235 r[lastdep].append(lastcmp + " " + lastver)
236 continue
237
238 #if not inversion:
239 lastdep = i
240 lastver = ""
241 lastcmp = ""
242 if not (i in r and r[i]):
243 r[lastdep] = []
244
Brad Bishop316dfdd2018-06-25 12:45:53 -0400245 if sort:
246 r = collections.OrderedDict(sorted(r.items(), key=lambda x: x[0]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500247 return r
248
249def explode_dep_versions(s):
250 r = explode_dep_versions2(s)
251 for d in r:
252 if not r[d]:
253 r[d] = None
254 continue
255 if len(r[d]) > 1:
256 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))
257 r[d] = r[d][0]
258 return r
259
260def join_deps(deps, commasep=True):
261 """
262 Take the result from explode_dep_versions and generate a dependency string
263 """
264 result = []
265 for dep in deps:
266 if deps[dep]:
267 if isinstance(deps[dep], list):
268 for v in deps[dep]:
269 result.append(dep + " (" + v + ")")
270 else:
271 result.append(dep + " (" + deps[dep] + ")")
272 else:
273 result.append(dep)
274 if commasep:
275 return ", ".join(result)
276 else:
277 return " ".join(result)
278
279def _print_trace(body, line):
280 """
281 Print the Environment of a Text Body
282 """
283 error = []
284 # print the environment of the method
285 min_line = max(1, line-4)
286 max_line = min(line + 4, len(body))
287 for i in range(min_line, max_line + 1):
288 if line == i:
289 error.append(' *** %.4d:%s' % (i, body[i-1].rstrip()))
290 else:
291 error.append(' %.4d:%s' % (i, body[i-1].rstrip()))
292 return error
293
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500294def better_compile(text, file, realfile, mode = "exec", lineno = 0):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500295 """
296 A better compile method. This method
297 will print the offending lines.
298 """
299 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500300 cache = bb.methodpool.compile_cache(text)
301 if cache:
302 return cache
303 # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though
304 text2 = "\n" * int(lineno) + text
305 code = compile(text2, realfile, mode)
306 bb.methodpool.compile_cache_add(text, code)
307 return code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500308 except Exception as e:
309 error = []
310 # split the text into lines again
311 body = text.split('\n')
Brad Bishop19323692019-04-05 15:28:33 -0400312 error.append("Error in compiling python function in %s, line %s:\n" % (realfile, e.lineno))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500313 if hasattr(e, "lineno"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500314 error.append("The code lines resulting in this error were:")
Brad Bishop19323692019-04-05 15:28:33 -0400315 # e.lineno: line's position in reaflile
316 # lineno: function name's "position -1" in realfile
317 # e.lineno - lineno: line's relative position in function
318 error.extend(_print_trace(body, e.lineno - lineno))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500319 else:
320 error.append("The function causing this error was:")
321 for line in body:
322 error.append(line)
323 error.append("%s: %s" % (e.__class__.__name__, str(e)))
324
325 logger.error("\n".join(error))
326
327 e = bb.BBHandledException(e)
328 raise e
329
330def _print_exception(t, value, tb, realfile, text, context):
331 error = []
332 try:
333 exception = traceback.format_exception_only(t, value)
334 error.append('Error executing a python function in %s:\n' % realfile)
335
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500336 # Strip 'us' from the stack (better_exec call) unless that was where the
337 # error came from
338 if tb.tb_next is not None:
339 tb = tb.tb_next
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500340
341 textarray = text.split('\n')
342
343 linefailed = tb.tb_lineno
344
345 tbextract = traceback.extract_tb(tb)
346 tbformat = traceback.format_list(tbextract)
347 error.append("The stack trace of python calls that resulted in this exception/failure was:")
348 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2]))
349 error.extend(_print_trace(textarray, linefailed))
350
351 # See if this is a function we constructed and has calls back into other functions in
352 # "text". If so, try and improve the context of the error by diving down the trace
353 level = 0
354 nexttb = tb.tb_next
355 while nexttb is not None and (level+1) < len(tbextract):
356 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]))
357 if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]:
358 # The code was possibly in the string we compiled ourselves
359 error.extend(_print_trace(textarray, tbextract[level+1][1]))
360 elif tbextract[level+1][0].startswith("/"):
361 # The code looks like it might be in a file, try and load it
362 try:
363 with open(tbextract[level+1][0], "r") as f:
364 text = f.readlines()
365 error.extend(_print_trace(text, tbextract[level+1][1]))
366 except:
367 error.append(tbformat[level+1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500368 else:
369 error.append(tbformat[level+1])
370 nexttb = tb.tb_next
371 level = level + 1
372
373 error.append("Exception: %s" % ''.join(exception))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600374
375 # If the exception is from spwaning a task, let's be helpful and display
376 # the output (which hopefully includes stderr).
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500377 if isinstance(value, subprocess.CalledProcessError) and value.output:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600378 error.append("Subprocess output:")
379 error.append(value.output.decode("utf-8", errors="ignore"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500380 finally:
381 logger.error("\n".join(error))
382
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500383def better_exec(code, context, text = None, realfile = "<code>", pythonexception=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500384 """
385 Similiar to better_compile, better_exec will
386 print the lines that are responsible for the
387 error.
388 """
389 import bb.parse
390 if not text:
391 text = code
392 if not hasattr(code, "co_filename"):
393 code = better_compile(code, realfile, realfile)
394 try:
395 exec(code, get_context(), context)
Brad Bishop08902b02019-08-20 09:16:51 -0400396 except (bb.BBHandledException, bb.parse.SkipRecipe, bb.data_smart.ExpansionError):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500397 # Error already shown so passthrough, no need for traceback
398 raise
399 except Exception as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500400 if pythonexception:
401 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500402 (t, value, tb) = sys.exc_info()
403 try:
404 _print_exception(t, value, tb, realfile, text, context)
405 except Exception as e:
406 logger.error("Exception handler error: %s" % str(e))
407
408 e = bb.BBHandledException(e)
409 raise e
410
411def simple_exec(code, context):
412 exec(code, get_context(), context)
413
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600414def better_eval(source, locals, extraglobals = None):
415 ctx = get_context()
416 if extraglobals:
417 ctx = copy.copy(ctx)
418 for g in extraglobals:
419 ctx[g] = extraglobals[g]
420 return eval(source, ctx, locals)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500421
422@contextmanager
423def fileslocked(files):
424 """Context manager for locking and unlocking file locks."""
425 locks = []
426 if files:
427 for lockfile in files:
428 locks.append(bb.utils.lockfile(lockfile))
429
Andrew Geissler82c905d2020-04-13 13:39:40 -0500430 try:
431 yield
432 finally:
433 for lock in locks:
434 bb.utils.unlockfile(lock)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500435
436@contextmanager
437def timeout(seconds):
438 def timeout_handler(signum, frame):
439 pass
440
441 original_handler = signal.signal(signal.SIGALRM, timeout_handler)
442
443 try:
444 signal.alarm(seconds)
445 yield
446 finally:
447 signal.alarm(0)
448 signal.signal(signal.SIGALRM, original_handler)
449
450def lockfile(name, shared=False, retry=True, block=False):
451 """
452 Use the specified file as a lock file, return when the lock has
453 been acquired. Returns a variable to pass to unlockfile().
454 Parameters:
455 retry: True to re-try locking if it fails, False otherwise
456 block: True to block until the lock succeeds, False otherwise
457 The retry and block parameters are kind of equivalent unless you
458 consider the possibility of sending a signal to the process to break
459 out - at which point you want block=True rather than retry=True.
460 """
461 dirname = os.path.dirname(name)
462 mkdirhier(dirname)
463
464 if not os.access(dirname, os.W_OK):
465 logger.error("Unable to acquire lock '%s', directory is not writable",
466 name)
467 sys.exit(1)
468
469 op = fcntl.LOCK_EX
470 if shared:
471 op = fcntl.LOCK_SH
472 if not retry and not block:
473 op = op | fcntl.LOCK_NB
474
475 while True:
476 # If we leave the lockfiles lying around there is no problem
477 # but we should clean up after ourselves. This gives potential
478 # for races though. To work around this, when we acquire the lock
479 # we check the file we locked was still the lock file on disk.
480 # by comparing inode numbers. If they don't match or the lockfile
481 # no longer exists, we start again.
482
483 # This implementation is unfair since the last person to request the
484 # lock is the most likely to win it.
485
486 try:
487 lf = open(name, 'a+')
488 fileno = lf.fileno()
489 fcntl.flock(fileno, op)
490 statinfo = os.fstat(fileno)
491 if os.path.exists(lf.name):
492 statinfo2 = os.stat(lf.name)
493 if statinfo.st_ino == statinfo2.st_ino:
494 return lf
495 lf.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800496 except OSError as e:
497 if e.errno == errno.EACCES:
498 logger.error("Unable to acquire lock '%s', %s",
499 e.strerror, name)
500 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500501 try:
502 lf.close()
503 except Exception:
504 pass
505 pass
506 if not retry:
507 return None
508
509def unlockfile(lf):
510 """
511 Unlock a file locked using lockfile()
512 """
513 try:
514 # If we had a shared lock, we need to promote to exclusive before
515 # removing the lockfile. Attempt this, ignore failures.
516 fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
517 os.unlink(lf.name)
518 except (IOError, OSError):
519 pass
520 fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
521 lf.close()
522
Brad Bishop6dbb3162019-11-25 09:41:34 -0500523def _hasher(method, filename):
524 import mmap
525
526 with open(filename, "rb") as f:
527 try:
528 with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
529 for chunk in iter(lambda: mm.read(8192), b''):
530 method.update(chunk)
531 except ValueError:
532 # You can't mmap() an empty file so silence this exception
533 pass
534 return method.hexdigest()
535
536
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500537def md5_file(filename):
538 """
539 Return the hex string representation of the MD5 checksum of filename.
540 """
Brad Bishop6dbb3162019-11-25 09:41:34 -0500541 import hashlib
542 return _hasher(hashlib.md5(), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500543
544def sha256_file(filename):
545 """
546 Return the hex string representation of the 256-bit SHA checksum of
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500547 filename.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500548 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500549 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500550 return _hasher(hashlib.sha256(), filename)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500551
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500552def sha1_file(filename):
553 """
554 Return the hex string representation of the SHA1 checksum of the filename
555 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500556 import hashlib
Brad Bishop6dbb3162019-11-25 09:41:34 -0500557 return _hasher(hashlib.sha1(), filename)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500558
Andrew Geissler82c905d2020-04-13 13:39:40 -0500559def sha384_file(filename):
560 """
561 Return the hex string representation of the SHA384 checksum of the filename
562 """
563 import hashlib
564 return _hasher(hashlib.sha384(), filename)
565
566def sha512_file(filename):
567 """
568 Return the hex string representation of the SHA512 checksum of the filename
569 """
570 import hashlib
571 return _hasher(hashlib.sha512(), filename)
572
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500573def preserved_envvars_exported():
574 """Variables which are taken from the environment and placed in and exported
575 from the metadata"""
576 return [
577 'BB_TASKHASH',
578 'HOME',
579 'LOGNAME',
580 'PATH',
581 'PWD',
582 'SHELL',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500583 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600584 'LC_ALL',
585 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500586 ]
587
588def preserved_envvars():
589 """Variables which are taken from the environment and placed in the metadata"""
590 v = [
591 'BBPATH',
592 'BB_PRESERVE_ENV',
593 'BB_ENV_WHITELIST',
594 'BB_ENV_EXTRAWHITE',
595 ]
596 return v + preserved_envvars_exported()
597
598def filter_environment(good_vars):
599 """
600 Create a pristine environment for bitbake. This will remove variables that
601 are not known and may influence the build in a negative way.
602 """
603
604 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600605 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500606 if key in good_vars:
607 continue
608
609 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500610 del os.environ[key]
611
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600612 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
613 # access methods will use ascii. You can't change that mode once the interpreter is
614 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
615 # distros support that and we need to set something.
616 os.environ["LC_ALL"] = "en_US.UTF-8"
617
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500618 if removed_vars:
619 logger.debug(1, "Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
620
621 return removed_vars
622
623def approved_variables():
624 """
625 Determine and return the list of whitelisted variables which are approved
626 to remain in the environment.
627 """
628 if 'BB_PRESERVE_ENV' in os.environ:
629 return os.environ.keys()
630 approved = []
631 if 'BB_ENV_WHITELIST' in os.environ:
632 approved = os.environ['BB_ENV_WHITELIST'].split()
633 approved.extend(['BB_ENV_WHITELIST'])
634 else:
635 approved = preserved_envvars()
636 if 'BB_ENV_EXTRAWHITE' in os.environ:
637 approved.extend(os.environ['BB_ENV_EXTRAWHITE'].split())
638 if 'BB_ENV_EXTRAWHITE' not in approved:
639 approved.extend(['BB_ENV_EXTRAWHITE'])
640 return approved
641
642def clean_environment():
643 """
644 Clean up any spurious environment variables. This will remove any
645 variables the user hasn't chosen to preserve.
646 """
647 if 'BB_PRESERVE_ENV' not in os.environ:
648 good_vars = approved_variables()
649 return filter_environment(good_vars)
650
651 return {}
652
653def empty_environment():
654 """
655 Remove all variables from the environment.
656 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600657 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500658 os.unsetenv(s)
659 del os.environ[s]
660
661def build_environment(d):
662 """
663 Build an environment from all exported variables.
664 """
665 import bb.data
666 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500667 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500668 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500669 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500670
671def _check_unsafe_delete_path(path):
672 """
673 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
674 the caller should raise an exception with an appropriate message.
675 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
676 with potentially disastrous results.
677 """
678 extra = ''
679 # HOME might not be /home/something, so in case we can get it, check against it
680 homedir = os.environ.get('HOME', '')
681 if homedir:
682 extra = '|%s' % homedir
683 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
684 return True
685 return False
686
Brad Bishopa34c0302019-09-23 22:34:48 -0400687def remove(path, recurse=False, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500688 """Equivalent to rm -f or rm -rf"""
689 if not path:
690 return
691 if recurse:
692 for name in glob.glob(path):
693 if _check_unsafe_delete_path(path):
694 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % path)
695 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishopa34c0302019-09-23 22:34:48 -0400696 cmd = []
697 if ionice:
698 cmd = ['ionice', '-c', '3']
699 subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500700 return
701 for name in glob.glob(path):
702 try:
703 os.unlink(name)
704 except OSError as exc:
705 if exc.errno != errno.ENOENT:
706 raise
707
Brad Bishopa34c0302019-09-23 22:34:48 -0400708def prunedir(topdir, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500709 # Delete everything reachable from the directory named in 'topdir'.
710 # CAUTION: This is dangerous!
711 if _check_unsafe_delete_path(topdir):
712 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
Brad Bishopa34c0302019-09-23 22:34:48 -0400713 remove(topdir, recurse=True, ionice=ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500714
715#
716# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
717# but thats possibly insane and suffixes is probably going to be small
718#
719def prune_suffix(var, suffixes, d):
720 # See if var ends with any of the suffixes listed and
721 # remove it if found
722 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -0400723 if suffix and var.endswith(suffix):
724 return var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500725 return var
726
727def mkdirhier(directory):
728 """Create a directory like 'mkdir -p', but does not complain if
729 directory already exists like os.makedirs
730 """
731
732 try:
733 os.makedirs(directory)
734 except OSError as e:
Brad Bishopc342db32019-05-15 21:57:59 -0400735 if e.errno != errno.EEXIST or not os.path.isdir(directory):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500736 raise e
737
738def movefile(src, dest, newmtime = None, sstat = None):
739 """Moves a file from src to dest, preserving all permissions and
740 attributes; mtime will be preserved even when moving across
741 filesystems. Returns true on success and false on failure. Move is
742 atomic.
743 """
744
745 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
746 try:
747 if not sstat:
748 sstat = os.lstat(src)
749 except Exception as e:
750 print("movefile: Stating source file failed...", e)
751 return None
752
753 destexists = 1
754 try:
755 dstat = os.lstat(dest)
756 except:
757 dstat = os.lstat(os.path.dirname(dest))
758 destexists = 0
759
760 if destexists:
761 if stat.S_ISLNK(dstat[stat.ST_MODE]):
762 try:
763 os.unlink(dest)
764 destexists = 0
765 except Exception as e:
766 pass
767
768 if stat.S_ISLNK(sstat[stat.ST_MODE]):
769 try:
770 target = os.readlink(src)
771 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
772 os.unlink(dest)
773 os.symlink(target, dest)
774 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
775 os.unlink(src)
776 return os.lstat(dest)
777 except Exception as e:
778 print("movefile: failed to properly create symlink:", dest, "->", target, e)
779 return None
780
781 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500782 # os.rename needs to know the dest path ending with file name
783 # so append the file name to a path only if it's a dir specified
784 srcfname = os.path.basename(src)
785 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
786 else dest
787
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500788 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
789 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500790 os.rename(src, destpath)
791 renamefailed = 0
792 except Exception as e:
Brad Bishop79641f22019-09-10 07:20:22 -0400793 if e.errno != errno.EXDEV:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500794 # Some random error.
795 print("movefile: Failed to move", src, "to", dest, e)
796 return None
797 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
798
799 if renamefailed:
800 didcopy = 0
801 if stat.S_ISREG(sstat[stat.ST_MODE]):
802 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500803 shutil.copyfile(src, destpath + "#new")
804 os.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500805 didcopy = 1
806 except Exception as e:
807 print('movefile: copy', src, '->', dest, 'failed.', e)
808 return None
809 else:
810 #we don't yet handle special, so we need to fall back to /bin/mv
811 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
812 if a[0] != 0:
813 print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
814 return None # failure
815 try:
816 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400817 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
818 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500819 os.unlink(src)
820 except Exception as e:
821 print("movefile: Failed to chown/chmod/unlink", dest, e)
822 return None
823
824 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500825 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500826 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500827 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500828 newmtime = sstat[stat.ST_MTIME]
829 return newmtime
830
831def copyfile(src, dest, newmtime = None, sstat = None):
832 """
833 Copies a file from src to dest, preserving all permissions and
834 attributes; mtime will be preserved even when moving across
835 filesystems. Returns true on success and false on failure.
836 """
837 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
838 try:
839 if not sstat:
840 sstat = os.lstat(src)
841 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600842 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500843 return False
844
845 destexists = 1
846 try:
847 dstat = os.lstat(dest)
848 except:
849 dstat = os.lstat(os.path.dirname(dest))
850 destexists = 0
851
852 if destexists:
853 if stat.S_ISLNK(dstat[stat.ST_MODE]):
854 try:
855 os.unlink(dest)
856 destexists = 0
857 except Exception as e:
858 pass
859
860 if stat.S_ISLNK(sstat[stat.ST_MODE]):
861 try:
862 target = os.readlink(src)
863 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
864 os.unlink(dest)
865 os.symlink(target, dest)
Andrew Geissler82c905d2020-04-13 13:39:40 -0500866 os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500867 return os.lstat(dest)
868 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600869 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500870 return False
871
872 if stat.S_ISREG(sstat[stat.ST_MODE]):
873 try:
874 srcchown = False
875 if not os.access(src, os.R_OK):
876 # Make sure we can read it
877 srcchown = True
878 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
879
880 # For safety copy then move it over.
881 shutil.copyfile(src, dest + "#new")
882 os.rename(dest + "#new", dest)
883 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600884 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500885 return False
886 finally:
887 if srcchown:
888 os.chmod(src, sstat[stat.ST_MODE])
889 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
890
891 else:
892 #we don't yet handle special, so we need to fall back to /bin/mv
893 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
894 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600895 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500896 return False # failure
897 try:
898 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
899 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
900 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600901 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500902 return False
903
904 if newmtime:
905 os.utime(dest, (newmtime, newmtime))
906 else:
907 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
908 newmtime = sstat[stat.ST_MTIME]
909 return newmtime
910
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800911def break_hardlinks(src, sstat = None):
912 """
913 Ensures src is the only hardlink to this file. Other hardlinks,
914 if any, are not affected (other than in their st_nlink value, of
915 course). Returns true on success and false on failure.
916
917 """
918 try:
919 if not sstat:
920 sstat = os.lstat(src)
921 except Exception as e:
922 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
923 return False
924 if sstat[stat.ST_NLINK] == 1:
925 return True
926 return copyfile(src, src, sstat=sstat)
927
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500928def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500929 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500930 Locate `item` in the list of paths `path` (colon separated string like $PATH).
931 If `direction` is non-zero then the list is reversed.
932 If `history` is True then the list of candidates also returned as result,history.
933 If `executable` is True then the candidate has to be an executable file,
934 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500935 """
936
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500937 if executable:
938 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
939 else:
940 is_candidate = lambda p: os.path.exists(p)
941
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500942 hist = []
943 paths = (path or "").split(':')
944 if direction != 0:
945 paths.reverse()
946
947 for p in paths:
948 next = os.path.join(p, item)
949 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500950 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500951 if not os.path.isabs(next):
952 next = os.path.abspath(next)
953 if history:
954 return next, hist
955 return next
956
957 if history:
958 return "", hist
959 return ""
960
961def to_boolean(string, default=None):
962 if not string:
963 return default
964
965 normalized = string.lower()
966 if normalized in ("y", "yes", "1", "true"):
967 return True
968 elif normalized in ("n", "no", "0", "false"):
969 return False
970 else:
971 raise ValueError("Invalid value for to_boolean: %s" % string)
972
973def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500974 """Check if a variable contains all the values specified.
975
976 Arguments:
977
978 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500979 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500980
981 checkvalues -- if this is a string it is split on whitespace into a set(),
982 otherwise coerced directly into a set().
983
984 truevalue -- the value to return if checkvalues is a subset of variable.
985
986 falsevalue -- the value to return if variable is empty or if checkvalues is
987 not a subset of variable.
988
989 d -- the data store.
990 """
991
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500992 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500993 if not val:
994 return falsevalue
995 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600996 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500997 checkvalues = set(checkvalues.split())
998 else:
999 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001000 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001001 return truevalue
1002 return falsevalue
1003
1004def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001005 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001006 if not val:
1007 return falsevalue
1008 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001009 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001010 checkvalues = set(checkvalues.split())
1011 else:
1012 checkvalues = set(checkvalues)
1013 if checkvalues & val:
1014 return truevalue
1015 return falsevalue
1016
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001017def filter(variable, checkvalues, d):
1018 """Return all words in the variable that are present in the checkvalues.
1019
1020 Arguments:
1021
1022 variable -- the variable name. This will be fetched and expanded (using
1023 d.getVar(variable)) and then split into a set().
1024
1025 checkvalues -- if this is a string it is split on whitespace into a set(),
1026 otherwise coerced directly into a set().
1027
1028 d -- the data store.
1029 """
1030
1031 val = d.getVar(variable)
1032 if not val:
1033 return ''
1034 val = set(val.split())
1035 if isinstance(checkvalues, str):
1036 checkvalues = set(checkvalues.split())
1037 else:
1038 checkvalues = set(checkvalues)
1039 return ' '.join(sorted(checkvalues & val))
1040
Andrew Geissler82c905d2020-04-13 13:39:40 -05001041
1042def get_referenced_vars(start_expr, d):
1043 """
1044 :return: names of vars referenced in start_expr (recursively), in quasi-BFS order (variables within the same level
1045 are ordered arbitrarily)
1046 """
1047
1048 seen = set()
1049 ret = []
1050
1051 # The first entry in the queue is the unexpanded start expression
1052 queue = collections.deque([start_expr])
1053 # Subsequent entries will be variable names, so we need to track whether or not entry requires getVar
1054 is_first = True
1055
1056 empty_data = bb.data.init()
1057 while queue:
1058 entry = queue.popleft()
1059 if is_first:
1060 # Entry is the start expression - no expansion needed
1061 is_first = False
1062 expression = entry
1063 else:
1064 # This is a variable name - need to get the value
1065 expression = d.getVar(entry, False)
1066 ret.append(entry)
1067
1068 # expandWithRefs is how we actually get the referenced variables in the expression. We call it using an empty
1069 # data store because we only want the variables directly used in the expression. It returns a set, which is what
1070 # dooms us to only ever be "quasi-BFS" rather than full BFS.
1071 new_vars = empty_data.expandWithRefs(expression, None).references - set(seen)
1072
1073 queue.extend(new_vars)
1074 seen.update(new_vars)
1075 return ret
1076
1077
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001078def cpu_count():
1079 return multiprocessing.cpu_count()
1080
1081def nonblockingfd(fd):
1082 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1083
1084def process_profilelog(fn, pout = None):
1085 # Either call with a list of filenames and set pout or a filename and optionally pout.
1086 if not pout:
1087 pout = fn + '.processed'
1088 pout = open(pout, 'w')
1089
1090 import pstats
1091 if isinstance(fn, list):
1092 p = pstats.Stats(*fn, stream=pout)
1093 else:
1094 p = pstats.Stats(fn, stream=pout)
1095 p.sort_stats('time')
1096 p.print_stats()
1097 p.print_callers()
1098 p.sort_stats('cumulative')
1099 p.print_stats()
1100
1101 pout.flush()
1102 pout.close()
1103
1104#
1105# Was present to work around multiprocessing pool bugs in python < 2.7.3
1106#
1107def multiprocessingpool(*args, **kwargs):
1108
1109 import multiprocessing.pool
1110 #import multiprocessing.util
1111 #multiprocessing.util.log_to_stderr(10)
1112 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1113 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1114 def wrapper(func):
1115 def wrap(self, timeout=None):
1116 return func(self, timeout=timeout if timeout is not None else 1e100)
1117 return wrap
1118 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1119
1120 return multiprocessing.Pool(*args, **kwargs)
1121
1122def exec_flat_python_func(func, *args, **kwargs):
1123 """Execute a flat python function (defined with def funcname(args):...)"""
1124 # Prepare a small piece of python code which calls the requested function
1125 # To do this we need to prepare two things - a set of variables we can use to pass
1126 # the values of arguments into the calling function, and the list of arguments for
1127 # the function being called
1128 context = {}
1129 funcargs = []
1130 # Handle unnamed arguments
1131 aidx = 1
1132 for arg in args:
1133 argname = 'arg_%s' % aidx
1134 context[argname] = arg
1135 funcargs.append(argname)
1136 aidx += 1
1137 # Handle keyword arguments
1138 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001139 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001140 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1141 comp = bb.utils.better_compile(code, '<string>', '<string>')
1142 bb.utils.better_exec(comp, context, code, '<string>')
1143 return context['retval']
1144
1145def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1146 """Edit lines from a recipe or config file and modify one or more
1147 specified variable values set in the file using a specified callback
1148 function. Lines are expected to have trailing newlines.
1149 Parameters:
1150 meta_lines: lines from the file; can be a list or an iterable
1151 (e.g. file pointer)
1152 variables: a list of variable names to look for. Functions
1153 may also be specified, but must be specified with '()' at
1154 the end of the name. Note that the function doesn't have
1155 any intrinsic understanding of _append, _prepend, _remove,
1156 or overrides, so these are considered as part of the name.
1157 These values go into a regular expression, so regular
1158 expression syntax is allowed.
1159 varfunc: callback function called for every variable matching
1160 one of the entries in the variables parameter. The function
1161 should take four arguments:
1162 varname: name of variable matched
1163 origvalue: current value in file
1164 op: the operator (e.g. '+=')
1165 newlines: list of lines up to this point. You can use
1166 this to prepend lines before this variable setting
1167 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001168 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001169 newvalue: new value to substitute in, or None to drop
1170 the variable setting entirely. (If the removal
1171 results in two consecutive blank lines, one of the
1172 blank lines will also be dropped).
1173 newop: the operator to use - if you specify None here,
1174 the original operation will be used.
1175 indent: number of spaces to indent multi-line entries,
1176 or -1 to indent up to the level of the assignment
1177 and opening quote, or a string to use as the indent.
1178 minbreak: True to allow the first element of a
1179 multi-line value to continue on the same line as
1180 the assignment, False to indent before the first
1181 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001182 To clarify, if you wish not to change the value, then you
1183 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001184 match_overrides: True to match items with _overrides on the end,
1185 False otherwise
1186 Returns a tuple:
1187 updated:
1188 True if changes were made, False otherwise.
1189 newlines:
1190 Lines after processing
1191 """
1192
1193 var_res = {}
1194 if match_overrides:
Brad Bishop19323692019-04-05 15:28:33 -04001195 override_re = r'(_[a-zA-Z0-9-_$(){}]+)?'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001196 else:
1197 override_re = ''
1198 for var in variables:
1199 if var.endswith('()'):
Brad Bishop19323692019-04-05 15:28:33 -04001200 var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001201 else:
Brad Bishop19323692019-04-05 15:28:33 -04001202 var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001203
1204 updated = False
1205 varset_start = ''
1206 varlines = []
1207 newlines = []
1208 in_var = None
1209 full_value = ''
1210 var_end = ''
1211
1212 def handle_var_end():
1213 prerun_newlines = newlines[:]
1214 op = varset_start[len(in_var):].strip()
1215 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1216 changed = (prerun_newlines != newlines)
1217
1218 if newvalue is None:
1219 # Drop the value
1220 return True
1221 elif newvalue != full_value or (newop not in [None, op]):
1222 if newop not in [None, op]:
1223 # Callback changed the operator
1224 varset_new = "%s %s" % (in_var, newop)
1225 else:
1226 varset_new = varset_start
1227
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001228 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001229 if indent == -1:
1230 indentspc = ' ' * (len(varset_new) + 2)
1231 else:
1232 indentspc = ' ' * indent
1233 else:
1234 indentspc = indent
1235 if in_var.endswith('()'):
1236 # A function definition
1237 if isinstance(newvalue, list):
1238 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1239 else:
1240 if not newvalue.startswith('\n'):
1241 newvalue = '\n' + newvalue
1242 if not newvalue.endswith('\n'):
1243 newvalue = newvalue + '\n'
1244 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1245 else:
1246 # Normal variable
1247 if isinstance(newvalue, list):
1248 if not newvalue:
1249 # Empty list -> empty string
1250 newlines.append('%s ""\n' % varset_new)
1251 elif minbreak:
1252 # First item on first line
1253 if len(newvalue) == 1:
1254 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1255 else:
1256 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1257 for item in newvalue[1:]:
1258 newlines.append('%s%s \\\n' % (indentspc, item))
1259 newlines.append('%s"\n' % indentspc)
1260 else:
1261 # No item on first line
1262 newlines.append('%s " \\\n' % varset_new)
1263 for item in newvalue:
1264 newlines.append('%s%s \\\n' % (indentspc, item))
1265 newlines.append('%s"\n' % indentspc)
1266 else:
1267 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1268 return True
1269 else:
1270 # Put the old lines back where they were
1271 newlines.extend(varlines)
1272 # If newlines was touched by the function, we'll need to return True
1273 return changed
1274
1275 checkspc = False
1276
1277 for line in meta_lines:
1278 if in_var:
1279 value = line.rstrip()
1280 varlines.append(line)
1281 if in_var.endswith('()'):
1282 full_value += '\n' + value
1283 else:
1284 full_value += value[:-1]
1285 if value.endswith(var_end):
1286 if in_var.endswith('()'):
1287 if full_value.count('{') - full_value.count('}') >= 0:
1288 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001289 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001290 if handle_var_end():
1291 updated = True
1292 checkspc = True
1293 in_var = None
1294 else:
1295 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001296 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001297 res = var_re.match(line)
1298 if res:
1299 isfunc = varname.endswith('()')
1300 if isfunc:
1301 splitvalue = line.split('{', 1)
1302 var_end = '}'
1303 else:
1304 var_end = res.groups()[-1]
1305 splitvalue = line.split(var_end, 1)
1306 varset_start = splitvalue[0].rstrip()
1307 value = splitvalue[1].rstrip()
1308 if not isfunc and value.endswith('\\'):
1309 value = value[:-1]
1310 full_value = value
1311 varlines = [line]
1312 in_var = res.group(1)
1313 if isfunc:
1314 in_var += '()'
1315 if value.endswith(var_end):
1316 full_value = full_value[:-1]
1317 if handle_var_end():
1318 updated = True
1319 checkspc = True
1320 in_var = None
1321 skip = True
1322 break
1323 if not skip:
1324 if checkspc:
1325 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001326 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001327 # Squash blank line if there are two consecutive blanks after a removal
1328 continue
1329 newlines.append(line)
1330 return (updated, newlines)
1331
1332
1333def edit_metadata_file(meta_file, variables, varfunc):
1334 """Edit a recipe or config file and modify one or more specified
1335 variable values set in the file using a specified callback function.
1336 The file is only written to if the value(s) actually change.
1337 This is basically the file version of edit_metadata(), see that
1338 function's description for parameter/usage information.
1339 Returns True if the file was written to, False otherwise.
1340 """
1341 with open(meta_file, 'r') as f:
1342 (updated, newlines) = edit_metadata(f, variables, varfunc)
1343 if updated:
1344 with open(meta_file, 'w') as f:
1345 f.writelines(newlines)
1346 return updated
1347
1348
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001349def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001350 """Edit bblayers.conf, adding and/or removing layers
1351 Parameters:
1352 bblayers_conf: path to bblayers.conf file to edit
1353 add: layer path (or list of layer paths) to add; None or empty
1354 list to add nothing
1355 remove: layer path (or list of layer paths) to remove; None or
1356 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001357 edit_cb: optional callback function that will be called after
1358 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001359 Returns a tuple:
1360 notadded: list of layers specified to be added but weren't
1361 (because they were already in the list)
1362 notremoved: list of layers that were specified to be removed
1363 but weren't (because they weren't in the list)
1364 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001365
1366 import fnmatch
1367
1368 def remove_trailing_sep(pth):
1369 if pth and pth[-1] == os.sep:
1370 pth = pth[:-1]
1371 return pth
1372
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001373 approved = bb.utils.approved_variables()
1374 def canonicalise_path(pth):
1375 pth = remove_trailing_sep(pth)
1376 if 'HOME' in approved and '~' in pth:
1377 pth = os.path.expanduser(pth)
1378 return pth
1379
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001380 def layerlist_param(value):
1381 if not value:
1382 return []
1383 elif isinstance(value, list):
1384 return [remove_trailing_sep(x) for x in value]
1385 else:
1386 return [remove_trailing_sep(value)]
1387
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001388 addlayers = layerlist_param(add)
1389 removelayers = layerlist_param(remove)
1390
1391 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1392 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001393 removed = []
1394 plusequals = False
1395 orig_bblayers = []
1396
1397 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1398 bblayercalls.append(op)
1399 if op == '=':
1400 del orig_bblayers[:]
1401 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1402 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001403
1404 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001405 updated = False
1406 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1407 if removelayers:
1408 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001409 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001410 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001411 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001412 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001413 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001414 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001415 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001416 for addlayer in addlayers:
1417 if addlayer not in bblayers:
1418 updated = True
1419 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001420 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001421
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001422 if edit_cb:
1423 newlist = []
1424 for layer in bblayers:
1425 res = edit_cb(layer, canonicalise_path(layer))
1426 if res != layer:
1427 newlist.append(res)
1428 updated = True
1429 else:
1430 newlist.append(layer)
1431 bblayers = newlist
1432
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001433 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001434 if op == '+=' and not bblayers:
1435 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001436 return (bblayers, None, 2, False)
1437 else:
1438 return (origvalue, None, 2, False)
1439
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001440 with open(bblayers_conf, 'r') as f:
1441 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001442
1443 if not bblayercalls:
1444 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1445
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001446 # Try to do the "smart" thing depending on how the user has laid out
1447 # their bblayers.conf file
1448 if bblayercalls.count('+=') > 1:
1449 plusequals = True
1450
1451 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1452 notadded = []
1453 for layer in addlayers:
1454 layer_canon = canonicalise_path(layer)
1455 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1456 notadded.append(layer)
1457 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1458 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1459
1460 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1461 if addlayers:
1462 # Still need to add these
1463 for addlayer in addlayers:
1464 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1465 updated = True
1466
1467 if updated:
1468 with open(bblayers_conf, 'w') as f:
1469 f.writelines(newlines)
1470
1471 notremoved = list(set(removelayers) - set(removed))
1472
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001473 return (notadded, notremoved)
1474
1475
1476def get_file_layer(filename, d):
1477 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001478 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001479 collection_res = {}
1480 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001481 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001482
1483 def path_to_layer(path):
1484 # Use longest path so we handle nested layers
1485 matchlen = 0
1486 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001487 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001488 if len(regex) > matchlen and re.match(regex, path):
1489 matchlen = len(regex)
1490 match = collection
1491 return match
1492
1493 result = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001494 bbfiles = (d.getVar('BBFILES') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001495 bbfilesmatch = False
1496 for bbfilesentry in bbfiles:
1497 if fnmatch.fnmatch(filename, bbfilesentry):
1498 bbfilesmatch = True
1499 result = path_to_layer(bbfilesentry)
1500
1501 if not bbfilesmatch:
1502 # Probably a bbclass
1503 result = path_to_layer(filename)
1504
1505 return result
1506
1507
1508# Constant taken from http://linux.die.net/include/linux/prctl.h
1509PR_SET_PDEATHSIG = 1
1510
1511class PrCtlError(Exception):
1512 pass
1513
1514def signal_on_parent_exit(signame):
1515 """
1516 Trigger signame to be sent when the parent process dies
1517 """
1518 signum = getattr(signal, signame)
1519 # http://linux.die.net/man/2/prctl
1520 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1521 if result != 0:
1522 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001523
1524#
1525# Manually call the ioprio syscall. We could depend on other libs like psutil
1526# however this gets us enough of what we need to bitbake for now without the
1527# dependency
1528#
1529_unamearch = os.uname()[4]
1530IOPRIO_WHO_PROCESS = 1
1531IOPRIO_CLASS_SHIFT = 13
1532
1533def ioprio_set(who, cls, value):
1534 NR_ioprio_set = None
1535 if _unamearch == "x86_64":
1536 NR_ioprio_set = 251
1537 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1538 NR_ioprio_set = 289
Brad Bishop19323692019-04-05 15:28:33 -04001539 elif _unamearch == "aarch64":
1540 NR_ioprio_set = 30
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001541
1542 if NR_ioprio_set:
1543 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1544 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1545 if rc != 0:
1546 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1547 else:
1548 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001549
1550def set_process_name(name):
1551 from ctypes import cdll, byref, create_string_buffer
1552 # This is nice to have for debugging, not essential
1553 try:
1554 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001555 buf = create_string_buffer(bytes(name, 'utf-8'))
1556 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001557 except:
1558 pass
1559
1560# export common proxies variables from datastore to environment
1561def export_proxies(d):
1562 import os
1563
1564 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001565 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1566 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001567 exported = False
1568
1569 for v in variables:
1570 if v in os.environ.keys():
1571 exported = True
1572 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001573 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001574 if v_proxy is not None:
1575 os.environ[v] = v_proxy
1576 exported = True
1577
1578 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001579
1580
1581def load_plugins(logger, plugins, pluginpath):
1582 def load_plugin(name):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001583 logger.debug(1, 'Loading plugin %s' % name)
Brad Bishop19323692019-04-05 15:28:33 -04001584 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1585 if spec:
1586 return spec.loader.load_module()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001587
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001588 logger.debug(1, 'Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001589
1590 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1591 for ext in python_extensions)
1592 files = itertools.chain.from_iterable(expanded)
1593 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1594 for name in names:
1595 if name != '__init__':
1596 plugin = load_plugin(name)
1597 if hasattr(plugin, 'plugin_init'):
1598 obj = plugin.plugin_init(plugins)
1599 plugins.append(obj or plugin)
1600 else:
1601 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001602
1603
1604class LogCatcher(logging.Handler):
1605 """Logging handler for collecting logged messages so you can check them later"""
1606 def __init__(self):
1607 self.messages = []
1608 logging.Handler.__init__(self, logging.WARNING)
1609 def emit(self, record):
1610 self.messages.append(bb.build.logformatter.format(record))
1611 def contains(self, message):
1612 return (message in self.messages)
Andrew Geissler82c905d2020-04-13 13:39:40 -05001613
1614def is_semver(version):
1615 """
1616 Is the version string following the semver semantic?
1617
1618 https://semver.org/spec/v2.0.0.html
1619 """
1620 regex = re.compile(
1621 r"""
1622 ^
1623 (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
1624 (?:-(
1625 (?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
1626 (?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*
1627 ))?
1628 (?:\+(
1629 [0-9a-zA-Z-]+
1630 (?:\.[0-9a-zA-Z-]+)*
1631 ))?
1632 $
1633 """, re.VERBOSE)
1634
1635 if regex.match(version) is None:
1636 return False
1637
1638 return True