blob: 8d40bcdf83669766835449caae8094367dafaa81 [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 Williamsd8c66bc2016-06-20 12:57:21 -050027import ast
Patrick Williamsc0f7c042017-02-23 20:41:17 -060028import collections
29import copy
30from subprocess import getstatusoutput
Patrick Williamsc124f4f2015-09-15 14:41:29 -050031from contextlib import contextmanager
32from ctypes import cdll
33
Patrick Williamsc124f4f2015-09-15 14:41:29 -050034logger = logging.getLogger("BitBake.Util")
Brad Bishop19323692019-04-05 15:28:33 -040035python_extensions = importlib.machinery.all_suffixes()
Patrick Williamsc0f7c042017-02-23 20:41:17 -060036
Patrick Williamsc124f4f2015-09-15 14:41:29 -050037
38def clean_context():
39 return {
40 "os": os,
41 "bb": bb,
42 "time": time,
43 }
44
45def get_context():
46 return _context
47
48
49def set_context(ctx):
50 _context = ctx
51
52# Context used in better_exec, eval
53_context = clean_context()
54
55class VersionStringException(Exception):
56 """Exception raised when an invalid version specification is found"""
57
58def explode_version(s):
59 r = []
Brad Bishop19323692019-04-05 15:28:33 -040060 alpha_regexp = re.compile(r'^([a-zA-Z]+)(.*)$')
61 numeric_regexp = re.compile(r'^(\d+)(.*)$')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050062 while (s != ''):
63 if s[0] in string.digits:
64 m = numeric_regexp.match(s)
65 r.append((0, int(m.group(1))))
66 s = m.group(2)
67 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -060068 if s[0] in string.ascii_letters:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050069 m = alpha_regexp.match(s)
70 r.append((1, m.group(1)))
71 s = m.group(2)
72 continue
73 if s[0] == '~':
74 r.append((-1, s[0]))
75 else:
76 r.append((2, s[0]))
77 s = s[1:]
78 return r
79
80def split_version(s):
81 """Split a version string into its constituent parts (PE, PV, PR)"""
82 s = s.strip(" <>=")
83 e = 0
84 if s.count(':'):
85 e = int(s.split(":")[0])
86 s = s.split(":")[1]
87 r = ""
88 if s.count('-'):
89 r = s.rsplit("-", 1)[1]
90 s = s.rsplit("-", 1)[0]
91 v = s
92 return (e, v, r)
93
94def vercmp_part(a, b):
95 va = explode_version(a)
96 vb = explode_version(b)
97 while True:
98 if va == []:
99 (oa, ca) = (0, None)
100 else:
101 (oa, ca) = va.pop(0)
102 if vb == []:
103 (ob, cb) = (0, None)
104 else:
105 (ob, cb) = vb.pop(0)
106 if (oa, ca) == (0, None) and (ob, cb) == (0, None):
107 return 0
108 if oa < ob:
109 return -1
110 elif oa > ob:
111 return 1
Brad Bishop19323692019-04-05 15:28:33 -0400112 elif ca is None:
113 return -1
114 elif cb is None:
115 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500116 elif ca < cb:
117 return -1
118 elif ca > cb:
119 return 1
120
121def vercmp(ta, tb):
122 (ea, va, ra) = ta
123 (eb, vb, rb) = tb
124
125 r = int(ea or 0) - int(eb or 0)
126 if (r == 0):
127 r = vercmp_part(va, vb)
128 if (r == 0):
129 r = vercmp_part(ra, rb)
130 return r
131
132def vercmp_string(a, b):
133 ta = split_version(a)
134 tb = split_version(b)
135 return vercmp(ta, tb)
136
137def vercmp_string_op(a, b, op):
138 """
139 Compare two versions and check if the specified comparison operator matches the result of the comparison.
140 This function is fairly liberal about what operators it will accept since there are a variety of styles
141 depending on the context.
142 """
143 res = vercmp_string(a, b)
144 if op in ('=', '=='):
145 return res == 0
146 elif op == '<=':
147 return res <= 0
148 elif op == '>=':
149 return res >= 0
150 elif op in ('>', '>>'):
151 return res > 0
152 elif op in ('<', '<<'):
153 return res < 0
154 elif op == '!=':
155 return res != 0
156 else:
157 raise VersionStringException('Unsupported comparison operator "%s"' % op)
158
159def explode_deps(s):
160 """
161 Take an RDEPENDS style string of format:
162 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
163 and return a list of dependencies.
164 Version information is ignored.
165 """
166 r = []
167 l = s.split()
168 flag = False
169 for i in l:
170 if i[0] == '(':
171 flag = True
172 #j = []
173 if not flag:
174 r.append(i)
175 #else:
176 # j.append(i)
177 if flag and i.endswith(')'):
178 flag = False
179 # Ignore version
180 #r[-1] += ' ' + ' '.join(j)
181 return r
182
Brad Bishop316dfdd2018-06-25 12:45:53 -0400183def explode_dep_versions2(s, *, sort=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500184 """
185 Take an RDEPENDS style string of format:
186 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
187 and return a dictionary of dependencies and versions.
188 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600189 r = collections.OrderedDict()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500190 l = s.replace(",", "").split()
191 lastdep = None
192 lastcmp = ""
193 lastver = ""
194 incmp = False
195 inversion = False
196 for i in l:
197 if i[0] == '(':
198 incmp = True
199 i = i[1:].strip()
200 if not i:
201 continue
202
203 if incmp:
204 incmp = False
205 inversion = True
206 # This list is based on behavior and supported comparisons from deb, opkg and rpm.
207 #
208 # Even though =<, <<, ==, !=, =>, and >> may not be supported,
209 # we list each possibly valid item.
210 # The build system is responsible for validation of what it supports.
211 if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')):
212 lastcmp = i[0:2]
213 i = i[2:]
214 elif i.startswith(('<', '>', '=')):
215 lastcmp = i[0:1]
216 i = i[1:]
217 else:
218 # This is an unsupported case!
219 raise VersionStringException('Invalid version specification in "(%s" - invalid or missing operator' % i)
220 lastcmp = (i or "")
221 i = ""
222 i.strip()
223 if not i:
224 continue
225
226 if inversion:
227 if i.endswith(')'):
228 i = i[:-1] or ""
229 inversion = False
230 if lastver and i:
231 lastver += " "
232 if i:
233 lastver += i
234 if lastdep not in r:
235 r[lastdep] = []
236 r[lastdep].append(lastcmp + " " + lastver)
237 continue
238
239 #if not inversion:
240 lastdep = i
241 lastver = ""
242 lastcmp = ""
243 if not (i in r and r[i]):
244 r[lastdep] = []
245
Brad Bishop316dfdd2018-06-25 12:45:53 -0400246 if sort:
247 r = collections.OrderedDict(sorted(r.items(), key=lambda x: x[0]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500248 return r
249
250def explode_dep_versions(s):
251 r = explode_dep_versions2(s)
252 for d in r:
253 if not r[d]:
254 r[d] = None
255 continue
256 if len(r[d]) > 1:
257 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))
258 r[d] = r[d][0]
259 return r
260
261def join_deps(deps, commasep=True):
262 """
263 Take the result from explode_dep_versions and generate a dependency string
264 """
265 result = []
266 for dep in deps:
267 if deps[dep]:
268 if isinstance(deps[dep], list):
269 for v in deps[dep]:
270 result.append(dep + " (" + v + ")")
271 else:
272 result.append(dep + " (" + deps[dep] + ")")
273 else:
274 result.append(dep)
275 if commasep:
276 return ", ".join(result)
277 else:
278 return " ".join(result)
279
280def _print_trace(body, line):
281 """
282 Print the Environment of a Text Body
283 """
284 error = []
285 # print the environment of the method
286 min_line = max(1, line-4)
287 max_line = min(line + 4, len(body))
288 for i in range(min_line, max_line + 1):
289 if line == i:
290 error.append(' *** %.4d:%s' % (i, body[i-1].rstrip()))
291 else:
292 error.append(' %.4d:%s' % (i, body[i-1].rstrip()))
293 return error
294
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500295def better_compile(text, file, realfile, mode = "exec", lineno = 0):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500296 """
297 A better compile method. This method
298 will print the offending lines.
299 """
300 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500301 cache = bb.methodpool.compile_cache(text)
302 if cache:
303 return cache
304 # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though
305 text2 = "\n" * int(lineno) + text
306 code = compile(text2, realfile, mode)
307 bb.methodpool.compile_cache_add(text, code)
308 return code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500309 except Exception as e:
310 error = []
311 # split the text into lines again
312 body = text.split('\n')
Brad Bishop19323692019-04-05 15:28:33 -0400313 error.append("Error in compiling python function in %s, line %s:\n" % (realfile, e.lineno))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500314 if hasattr(e, "lineno"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500315 error.append("The code lines resulting in this error were:")
Brad Bishop19323692019-04-05 15:28:33 -0400316 # e.lineno: line's position in reaflile
317 # lineno: function name's "position -1" in realfile
318 # e.lineno - lineno: line's relative position in function
319 error.extend(_print_trace(body, e.lineno - lineno))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500320 else:
321 error.append("The function causing this error was:")
322 for line in body:
323 error.append(line)
324 error.append("%s: %s" % (e.__class__.__name__, str(e)))
325
326 logger.error("\n".join(error))
327
328 e = bb.BBHandledException(e)
329 raise e
330
331def _print_exception(t, value, tb, realfile, text, context):
332 error = []
333 try:
334 exception = traceback.format_exception_only(t, value)
335 error.append('Error executing a python function in %s:\n' % realfile)
336
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500337 # Strip 'us' from the stack (better_exec call) unless that was where the
338 # error came from
339 if tb.tb_next is not None:
340 tb = tb.tb_next
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500341
342 textarray = text.split('\n')
343
344 linefailed = tb.tb_lineno
345
346 tbextract = traceback.extract_tb(tb)
347 tbformat = traceback.format_list(tbextract)
348 error.append("The stack trace of python calls that resulted in this exception/failure was:")
349 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2]))
350 error.extend(_print_trace(textarray, linefailed))
351
352 # See if this is a function we constructed and has calls back into other functions in
353 # "text". If so, try and improve the context of the error by diving down the trace
354 level = 0
355 nexttb = tb.tb_next
356 while nexttb is not None and (level+1) < len(tbextract):
357 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]))
358 if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]:
359 # The code was possibly in the string we compiled ourselves
360 error.extend(_print_trace(textarray, tbextract[level+1][1]))
361 elif tbextract[level+1][0].startswith("/"):
362 # The code looks like it might be in a file, try and load it
363 try:
364 with open(tbextract[level+1][0], "r") as f:
365 text = f.readlines()
366 error.extend(_print_trace(text, tbextract[level+1][1]))
367 except:
368 error.append(tbformat[level+1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500369 else:
370 error.append(tbformat[level+1])
371 nexttb = tb.tb_next
372 level = level + 1
373
374 error.append("Exception: %s" % ''.join(exception))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600375
376 # If the exception is from spwaning a task, let's be helpful and display
377 # the output (which hopefully includes stderr).
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500378 if isinstance(value, subprocess.CalledProcessError) and value.output:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600379 error.append("Subprocess output:")
380 error.append(value.output.decode("utf-8", errors="ignore"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500381 finally:
382 logger.error("\n".join(error))
383
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500384def better_exec(code, context, text = None, realfile = "<code>", pythonexception=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500385 """
386 Similiar to better_compile, better_exec will
387 print the lines that are responsible for the
388 error.
389 """
390 import bb.parse
391 if not text:
392 text = code
393 if not hasattr(code, "co_filename"):
394 code = better_compile(code, realfile, realfile)
395 try:
396 exec(code, get_context(), context)
Brad Bishop08902b02019-08-20 09:16:51 -0400397 except (bb.BBHandledException, bb.parse.SkipRecipe, bb.data_smart.ExpansionError):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500398 # Error already shown so passthrough, no need for traceback
399 raise
400 except Exception as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500401 if pythonexception:
402 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500403 (t, value, tb) = sys.exc_info()
404 try:
405 _print_exception(t, value, tb, realfile, text, context)
406 except Exception as e:
407 logger.error("Exception handler error: %s" % str(e))
408
409 e = bb.BBHandledException(e)
410 raise e
411
412def simple_exec(code, context):
413 exec(code, get_context(), context)
414
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600415def better_eval(source, locals, extraglobals = None):
416 ctx = get_context()
417 if extraglobals:
418 ctx = copy.copy(ctx)
419 for g in extraglobals:
420 ctx[g] = extraglobals[g]
421 return eval(source, ctx, locals)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500422
423@contextmanager
424def fileslocked(files):
425 """Context manager for locking and unlocking file locks."""
426 locks = []
427 if files:
428 for lockfile in files:
429 locks.append(bb.utils.lockfile(lockfile))
430
431 yield
432
433 for lock in locks:
434 bb.utils.unlockfile(lock)
435
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
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500559def preserved_envvars_exported():
560 """Variables which are taken from the environment and placed in and exported
561 from the metadata"""
562 return [
563 'BB_TASKHASH',
564 'HOME',
565 'LOGNAME',
566 'PATH',
567 'PWD',
568 'SHELL',
569 'TERM',
570 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600571 'LC_ALL',
572 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500573 ]
574
575def preserved_envvars():
576 """Variables which are taken from the environment and placed in the metadata"""
577 v = [
578 'BBPATH',
579 'BB_PRESERVE_ENV',
580 'BB_ENV_WHITELIST',
581 'BB_ENV_EXTRAWHITE',
582 ]
583 return v + preserved_envvars_exported()
584
585def filter_environment(good_vars):
586 """
587 Create a pristine environment for bitbake. This will remove variables that
588 are not known and may influence the build in a negative way.
589 """
590
591 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600592 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500593 if key in good_vars:
594 continue
595
596 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500597 del os.environ[key]
598
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600599 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
600 # access methods will use ascii. You can't change that mode once the interpreter is
601 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
602 # distros support that and we need to set something.
603 os.environ["LC_ALL"] = "en_US.UTF-8"
604
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500605 if removed_vars:
606 logger.debug(1, "Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
607
608 return removed_vars
609
610def approved_variables():
611 """
612 Determine and return the list of whitelisted variables which are approved
613 to remain in the environment.
614 """
615 if 'BB_PRESERVE_ENV' in os.environ:
616 return os.environ.keys()
617 approved = []
618 if 'BB_ENV_WHITELIST' in os.environ:
619 approved = os.environ['BB_ENV_WHITELIST'].split()
620 approved.extend(['BB_ENV_WHITELIST'])
621 else:
622 approved = preserved_envvars()
623 if 'BB_ENV_EXTRAWHITE' in os.environ:
624 approved.extend(os.environ['BB_ENV_EXTRAWHITE'].split())
625 if 'BB_ENV_EXTRAWHITE' not in approved:
626 approved.extend(['BB_ENV_EXTRAWHITE'])
627 return approved
628
629def clean_environment():
630 """
631 Clean up any spurious environment variables. This will remove any
632 variables the user hasn't chosen to preserve.
633 """
634 if 'BB_PRESERVE_ENV' not in os.environ:
635 good_vars = approved_variables()
636 return filter_environment(good_vars)
637
638 return {}
639
640def empty_environment():
641 """
642 Remove all variables from the environment.
643 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600644 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500645 os.unsetenv(s)
646 del os.environ[s]
647
648def build_environment(d):
649 """
650 Build an environment from all exported variables.
651 """
652 import bb.data
653 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500654 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500655 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500656 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500657
658def _check_unsafe_delete_path(path):
659 """
660 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
661 the caller should raise an exception with an appropriate message.
662 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
663 with potentially disastrous results.
664 """
665 extra = ''
666 # HOME might not be /home/something, so in case we can get it, check against it
667 homedir = os.environ.get('HOME', '')
668 if homedir:
669 extra = '|%s' % homedir
670 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
671 return True
672 return False
673
Brad Bishopa34c0302019-09-23 22:34:48 -0400674def remove(path, recurse=False, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500675 """Equivalent to rm -f or rm -rf"""
676 if not path:
677 return
678 if recurse:
679 for name in glob.glob(path):
680 if _check_unsafe_delete_path(path):
681 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % path)
682 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishopa34c0302019-09-23 22:34:48 -0400683 cmd = []
684 if ionice:
685 cmd = ['ionice', '-c', '3']
686 subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500687 return
688 for name in glob.glob(path):
689 try:
690 os.unlink(name)
691 except OSError as exc:
692 if exc.errno != errno.ENOENT:
693 raise
694
Brad Bishopa34c0302019-09-23 22:34:48 -0400695def prunedir(topdir, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500696 # Delete everything reachable from the directory named in 'topdir'.
697 # CAUTION: This is dangerous!
698 if _check_unsafe_delete_path(topdir):
699 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
Brad Bishopa34c0302019-09-23 22:34:48 -0400700 remove(topdir, recurse=True, ionice=ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500701
702#
703# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
704# but thats possibly insane and suffixes is probably going to be small
705#
706def prune_suffix(var, suffixes, d):
707 # See if var ends with any of the suffixes listed and
708 # remove it if found
709 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -0400710 if suffix and var.endswith(suffix):
711 return var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500712 return var
713
714def mkdirhier(directory):
715 """Create a directory like 'mkdir -p', but does not complain if
716 directory already exists like os.makedirs
717 """
718
719 try:
720 os.makedirs(directory)
721 except OSError as e:
Brad Bishopc342db32019-05-15 21:57:59 -0400722 if e.errno != errno.EEXIST or not os.path.isdir(directory):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500723 raise e
724
725def movefile(src, dest, newmtime = None, sstat = None):
726 """Moves a file from src to dest, preserving all permissions and
727 attributes; mtime will be preserved even when moving across
728 filesystems. Returns true on success and false on failure. Move is
729 atomic.
730 """
731
732 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
733 try:
734 if not sstat:
735 sstat = os.lstat(src)
736 except Exception as e:
737 print("movefile: Stating source file failed...", e)
738 return None
739
740 destexists = 1
741 try:
742 dstat = os.lstat(dest)
743 except:
744 dstat = os.lstat(os.path.dirname(dest))
745 destexists = 0
746
747 if destexists:
748 if stat.S_ISLNK(dstat[stat.ST_MODE]):
749 try:
750 os.unlink(dest)
751 destexists = 0
752 except Exception as e:
753 pass
754
755 if stat.S_ISLNK(sstat[stat.ST_MODE]):
756 try:
757 target = os.readlink(src)
758 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
759 os.unlink(dest)
760 os.symlink(target, dest)
761 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
762 os.unlink(src)
763 return os.lstat(dest)
764 except Exception as e:
765 print("movefile: failed to properly create symlink:", dest, "->", target, e)
766 return None
767
768 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500769 # os.rename needs to know the dest path ending with file name
770 # so append the file name to a path only if it's a dir specified
771 srcfname = os.path.basename(src)
772 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
773 else dest
774
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500775 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
776 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500777 os.rename(src, destpath)
778 renamefailed = 0
779 except Exception as e:
Brad Bishop79641f22019-09-10 07:20:22 -0400780 if e.errno != errno.EXDEV:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500781 # Some random error.
782 print("movefile: Failed to move", src, "to", dest, e)
783 return None
784 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
785
786 if renamefailed:
787 didcopy = 0
788 if stat.S_ISREG(sstat[stat.ST_MODE]):
789 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500790 shutil.copyfile(src, destpath + "#new")
791 os.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500792 didcopy = 1
793 except Exception as e:
794 print('movefile: copy', src, '->', dest, 'failed.', e)
795 return None
796 else:
797 #we don't yet handle special, so we need to fall back to /bin/mv
798 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
799 if a[0] != 0:
800 print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
801 return None # failure
802 try:
803 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400804 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
805 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500806 os.unlink(src)
807 except Exception as e:
808 print("movefile: Failed to chown/chmod/unlink", dest, e)
809 return None
810
811 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500812 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500813 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500814 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500815 newmtime = sstat[stat.ST_MTIME]
816 return newmtime
817
818def copyfile(src, dest, newmtime = None, sstat = None):
819 """
820 Copies a file from src to dest, preserving all permissions and
821 attributes; mtime will be preserved even when moving across
822 filesystems. Returns true on success and false on failure.
823 """
824 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
825 try:
826 if not sstat:
827 sstat = os.lstat(src)
828 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600829 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500830 return False
831
832 destexists = 1
833 try:
834 dstat = os.lstat(dest)
835 except:
836 dstat = os.lstat(os.path.dirname(dest))
837 destexists = 0
838
839 if destexists:
840 if stat.S_ISLNK(dstat[stat.ST_MODE]):
841 try:
842 os.unlink(dest)
843 destexists = 0
844 except Exception as e:
845 pass
846
847 if stat.S_ISLNK(sstat[stat.ST_MODE]):
848 try:
849 target = os.readlink(src)
850 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
851 os.unlink(dest)
852 os.symlink(target, dest)
853 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
854 return os.lstat(dest)
855 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600856 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500857 return False
858
859 if stat.S_ISREG(sstat[stat.ST_MODE]):
860 try:
861 srcchown = False
862 if not os.access(src, os.R_OK):
863 # Make sure we can read it
864 srcchown = True
865 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
866
867 # For safety copy then move it over.
868 shutil.copyfile(src, dest + "#new")
869 os.rename(dest + "#new", dest)
870 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600871 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500872 return False
873 finally:
874 if srcchown:
875 os.chmod(src, sstat[stat.ST_MODE])
876 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
877
878 else:
879 #we don't yet handle special, so we need to fall back to /bin/mv
880 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
881 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600882 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500883 return False # failure
884 try:
885 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
886 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
887 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600888 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500889 return False
890
891 if newmtime:
892 os.utime(dest, (newmtime, newmtime))
893 else:
894 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
895 newmtime = sstat[stat.ST_MTIME]
896 return newmtime
897
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800898def break_hardlinks(src, sstat = None):
899 """
900 Ensures src is the only hardlink to this file. Other hardlinks,
901 if any, are not affected (other than in their st_nlink value, of
902 course). Returns true on success and false on failure.
903
904 """
905 try:
906 if not sstat:
907 sstat = os.lstat(src)
908 except Exception as e:
909 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
910 return False
911 if sstat[stat.ST_NLINK] == 1:
912 return True
913 return copyfile(src, src, sstat=sstat)
914
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500915def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500916 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500917 Locate `item` in the list of paths `path` (colon separated string like $PATH).
918 If `direction` is non-zero then the list is reversed.
919 If `history` is True then the list of candidates also returned as result,history.
920 If `executable` is True then the candidate has to be an executable file,
921 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500922 """
923
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500924 if executable:
925 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
926 else:
927 is_candidate = lambda p: os.path.exists(p)
928
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500929 hist = []
930 paths = (path or "").split(':')
931 if direction != 0:
932 paths.reverse()
933
934 for p in paths:
935 next = os.path.join(p, item)
936 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500937 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500938 if not os.path.isabs(next):
939 next = os.path.abspath(next)
940 if history:
941 return next, hist
942 return next
943
944 if history:
945 return "", hist
946 return ""
947
948def to_boolean(string, default=None):
949 if not string:
950 return default
951
952 normalized = string.lower()
953 if normalized in ("y", "yes", "1", "true"):
954 return True
955 elif normalized in ("n", "no", "0", "false"):
956 return False
957 else:
958 raise ValueError("Invalid value for to_boolean: %s" % string)
959
960def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500961 """Check if a variable contains all the values specified.
962
963 Arguments:
964
965 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500966 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500967
968 checkvalues -- if this is a string it is split on whitespace into a set(),
969 otherwise coerced directly into a set().
970
971 truevalue -- the value to return if checkvalues is a subset of variable.
972
973 falsevalue -- the value to return if variable is empty or if checkvalues is
974 not a subset of variable.
975
976 d -- the data store.
977 """
978
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500979 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500980 if not val:
981 return falsevalue
982 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600983 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500984 checkvalues = set(checkvalues.split())
985 else:
986 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500987 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500988 return truevalue
989 return falsevalue
990
991def contains_any(variable, checkvalues, truevalue, falsevalue, d):
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)
1000 if checkvalues & val:
1001 return truevalue
1002 return falsevalue
1003
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001004def filter(variable, checkvalues, d):
1005 """Return all words in the variable that are present in the checkvalues.
1006
1007 Arguments:
1008
1009 variable -- the variable name. This will be fetched and expanded (using
1010 d.getVar(variable)) and then split into a set().
1011
1012 checkvalues -- if this is a string it is split on whitespace into a set(),
1013 otherwise coerced directly into a set().
1014
1015 d -- the data store.
1016 """
1017
1018 val = d.getVar(variable)
1019 if not val:
1020 return ''
1021 val = set(val.split())
1022 if isinstance(checkvalues, str):
1023 checkvalues = set(checkvalues.split())
1024 else:
1025 checkvalues = set(checkvalues)
1026 return ' '.join(sorted(checkvalues & val))
1027
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001028def cpu_count():
1029 return multiprocessing.cpu_count()
1030
1031def nonblockingfd(fd):
1032 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1033
1034def process_profilelog(fn, pout = None):
1035 # Either call with a list of filenames and set pout or a filename and optionally pout.
1036 if not pout:
1037 pout = fn + '.processed'
1038 pout = open(pout, 'w')
1039
1040 import pstats
1041 if isinstance(fn, list):
1042 p = pstats.Stats(*fn, stream=pout)
1043 else:
1044 p = pstats.Stats(fn, stream=pout)
1045 p.sort_stats('time')
1046 p.print_stats()
1047 p.print_callers()
1048 p.sort_stats('cumulative')
1049 p.print_stats()
1050
1051 pout.flush()
1052 pout.close()
1053
1054#
1055# Was present to work around multiprocessing pool bugs in python < 2.7.3
1056#
1057def multiprocessingpool(*args, **kwargs):
1058
1059 import multiprocessing.pool
1060 #import multiprocessing.util
1061 #multiprocessing.util.log_to_stderr(10)
1062 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1063 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1064 def wrapper(func):
1065 def wrap(self, timeout=None):
1066 return func(self, timeout=timeout if timeout is not None else 1e100)
1067 return wrap
1068 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1069
1070 return multiprocessing.Pool(*args, **kwargs)
1071
1072def exec_flat_python_func(func, *args, **kwargs):
1073 """Execute a flat python function (defined with def funcname(args):...)"""
1074 # Prepare a small piece of python code which calls the requested function
1075 # To do this we need to prepare two things - a set of variables we can use to pass
1076 # the values of arguments into the calling function, and the list of arguments for
1077 # the function being called
1078 context = {}
1079 funcargs = []
1080 # Handle unnamed arguments
1081 aidx = 1
1082 for arg in args:
1083 argname = 'arg_%s' % aidx
1084 context[argname] = arg
1085 funcargs.append(argname)
1086 aidx += 1
1087 # Handle keyword arguments
1088 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001089 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001090 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1091 comp = bb.utils.better_compile(code, '<string>', '<string>')
1092 bb.utils.better_exec(comp, context, code, '<string>')
1093 return context['retval']
1094
1095def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1096 """Edit lines from a recipe or config file and modify one or more
1097 specified variable values set in the file using a specified callback
1098 function. Lines are expected to have trailing newlines.
1099 Parameters:
1100 meta_lines: lines from the file; can be a list or an iterable
1101 (e.g. file pointer)
1102 variables: a list of variable names to look for. Functions
1103 may also be specified, but must be specified with '()' at
1104 the end of the name. Note that the function doesn't have
1105 any intrinsic understanding of _append, _prepend, _remove,
1106 or overrides, so these are considered as part of the name.
1107 These values go into a regular expression, so regular
1108 expression syntax is allowed.
1109 varfunc: callback function called for every variable matching
1110 one of the entries in the variables parameter. The function
1111 should take four arguments:
1112 varname: name of variable matched
1113 origvalue: current value in file
1114 op: the operator (e.g. '+=')
1115 newlines: list of lines up to this point. You can use
1116 this to prepend lines before this variable setting
1117 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001118 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001119 newvalue: new value to substitute in, or None to drop
1120 the variable setting entirely. (If the removal
1121 results in two consecutive blank lines, one of the
1122 blank lines will also be dropped).
1123 newop: the operator to use - if you specify None here,
1124 the original operation will be used.
1125 indent: number of spaces to indent multi-line entries,
1126 or -1 to indent up to the level of the assignment
1127 and opening quote, or a string to use as the indent.
1128 minbreak: True to allow the first element of a
1129 multi-line value to continue on the same line as
1130 the assignment, False to indent before the first
1131 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001132 To clarify, if you wish not to change the value, then you
1133 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001134 match_overrides: True to match items with _overrides on the end,
1135 False otherwise
1136 Returns a tuple:
1137 updated:
1138 True if changes were made, False otherwise.
1139 newlines:
1140 Lines after processing
1141 """
1142
1143 var_res = {}
1144 if match_overrides:
Brad Bishop19323692019-04-05 15:28:33 -04001145 override_re = r'(_[a-zA-Z0-9-_$(){}]+)?'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001146 else:
1147 override_re = ''
1148 for var in variables:
1149 if var.endswith('()'):
Brad Bishop19323692019-04-05 15:28:33 -04001150 var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001151 else:
Brad Bishop19323692019-04-05 15:28:33 -04001152 var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001153
1154 updated = False
1155 varset_start = ''
1156 varlines = []
1157 newlines = []
1158 in_var = None
1159 full_value = ''
1160 var_end = ''
1161
1162 def handle_var_end():
1163 prerun_newlines = newlines[:]
1164 op = varset_start[len(in_var):].strip()
1165 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1166 changed = (prerun_newlines != newlines)
1167
1168 if newvalue is None:
1169 # Drop the value
1170 return True
1171 elif newvalue != full_value or (newop not in [None, op]):
1172 if newop not in [None, op]:
1173 # Callback changed the operator
1174 varset_new = "%s %s" % (in_var, newop)
1175 else:
1176 varset_new = varset_start
1177
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001178 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001179 if indent == -1:
1180 indentspc = ' ' * (len(varset_new) + 2)
1181 else:
1182 indentspc = ' ' * indent
1183 else:
1184 indentspc = indent
1185 if in_var.endswith('()'):
1186 # A function definition
1187 if isinstance(newvalue, list):
1188 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1189 else:
1190 if not newvalue.startswith('\n'):
1191 newvalue = '\n' + newvalue
1192 if not newvalue.endswith('\n'):
1193 newvalue = newvalue + '\n'
1194 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1195 else:
1196 # Normal variable
1197 if isinstance(newvalue, list):
1198 if not newvalue:
1199 # Empty list -> empty string
1200 newlines.append('%s ""\n' % varset_new)
1201 elif minbreak:
1202 # First item on first line
1203 if len(newvalue) == 1:
1204 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1205 else:
1206 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1207 for item in newvalue[1:]:
1208 newlines.append('%s%s \\\n' % (indentspc, item))
1209 newlines.append('%s"\n' % indentspc)
1210 else:
1211 # No item on first line
1212 newlines.append('%s " \\\n' % varset_new)
1213 for item in newvalue:
1214 newlines.append('%s%s \\\n' % (indentspc, item))
1215 newlines.append('%s"\n' % indentspc)
1216 else:
1217 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1218 return True
1219 else:
1220 # Put the old lines back where they were
1221 newlines.extend(varlines)
1222 # If newlines was touched by the function, we'll need to return True
1223 return changed
1224
1225 checkspc = False
1226
1227 for line in meta_lines:
1228 if in_var:
1229 value = line.rstrip()
1230 varlines.append(line)
1231 if in_var.endswith('()'):
1232 full_value += '\n' + value
1233 else:
1234 full_value += value[:-1]
1235 if value.endswith(var_end):
1236 if in_var.endswith('()'):
1237 if full_value.count('{') - full_value.count('}') >= 0:
1238 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001239 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001240 if handle_var_end():
1241 updated = True
1242 checkspc = True
1243 in_var = None
1244 else:
1245 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001246 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001247 res = var_re.match(line)
1248 if res:
1249 isfunc = varname.endswith('()')
1250 if isfunc:
1251 splitvalue = line.split('{', 1)
1252 var_end = '}'
1253 else:
1254 var_end = res.groups()[-1]
1255 splitvalue = line.split(var_end, 1)
1256 varset_start = splitvalue[0].rstrip()
1257 value = splitvalue[1].rstrip()
1258 if not isfunc and value.endswith('\\'):
1259 value = value[:-1]
1260 full_value = value
1261 varlines = [line]
1262 in_var = res.group(1)
1263 if isfunc:
1264 in_var += '()'
1265 if value.endswith(var_end):
1266 full_value = full_value[:-1]
1267 if handle_var_end():
1268 updated = True
1269 checkspc = True
1270 in_var = None
1271 skip = True
1272 break
1273 if not skip:
1274 if checkspc:
1275 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001276 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001277 # Squash blank line if there are two consecutive blanks after a removal
1278 continue
1279 newlines.append(line)
1280 return (updated, newlines)
1281
1282
1283def edit_metadata_file(meta_file, variables, varfunc):
1284 """Edit a recipe or config file and modify one or more specified
1285 variable values set in the file using a specified callback function.
1286 The file is only written to if the value(s) actually change.
1287 This is basically the file version of edit_metadata(), see that
1288 function's description for parameter/usage information.
1289 Returns True if the file was written to, False otherwise.
1290 """
1291 with open(meta_file, 'r') as f:
1292 (updated, newlines) = edit_metadata(f, variables, varfunc)
1293 if updated:
1294 with open(meta_file, 'w') as f:
1295 f.writelines(newlines)
1296 return updated
1297
1298
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001299def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001300 """Edit bblayers.conf, adding and/or removing layers
1301 Parameters:
1302 bblayers_conf: path to bblayers.conf file to edit
1303 add: layer path (or list of layer paths) to add; None or empty
1304 list to add nothing
1305 remove: layer path (or list of layer paths) to remove; None or
1306 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001307 edit_cb: optional callback function that will be called after
1308 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001309 Returns a tuple:
1310 notadded: list of layers specified to be added but weren't
1311 (because they were already in the list)
1312 notremoved: list of layers that were specified to be removed
1313 but weren't (because they weren't in the list)
1314 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001315
1316 import fnmatch
1317
1318 def remove_trailing_sep(pth):
1319 if pth and pth[-1] == os.sep:
1320 pth = pth[:-1]
1321 return pth
1322
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001323 approved = bb.utils.approved_variables()
1324 def canonicalise_path(pth):
1325 pth = remove_trailing_sep(pth)
1326 if 'HOME' in approved and '~' in pth:
1327 pth = os.path.expanduser(pth)
1328 return pth
1329
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001330 def layerlist_param(value):
1331 if not value:
1332 return []
1333 elif isinstance(value, list):
1334 return [remove_trailing_sep(x) for x in value]
1335 else:
1336 return [remove_trailing_sep(value)]
1337
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001338 addlayers = layerlist_param(add)
1339 removelayers = layerlist_param(remove)
1340
1341 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1342 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001343 removed = []
1344 plusequals = False
1345 orig_bblayers = []
1346
1347 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1348 bblayercalls.append(op)
1349 if op == '=':
1350 del orig_bblayers[:]
1351 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1352 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001353
1354 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001355 updated = False
1356 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1357 if removelayers:
1358 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001359 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001360 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001361 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001362 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001363 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001364 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001365 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001366 for addlayer in addlayers:
1367 if addlayer not in bblayers:
1368 updated = True
1369 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001370 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001371
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001372 if edit_cb:
1373 newlist = []
1374 for layer in bblayers:
1375 res = edit_cb(layer, canonicalise_path(layer))
1376 if res != layer:
1377 newlist.append(res)
1378 updated = True
1379 else:
1380 newlist.append(layer)
1381 bblayers = newlist
1382
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001383 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001384 if op == '+=' and not bblayers:
1385 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001386 return (bblayers, None, 2, False)
1387 else:
1388 return (origvalue, None, 2, False)
1389
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001390 with open(bblayers_conf, 'r') as f:
1391 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001392
1393 if not bblayercalls:
1394 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1395
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001396 # Try to do the "smart" thing depending on how the user has laid out
1397 # their bblayers.conf file
1398 if bblayercalls.count('+=') > 1:
1399 plusequals = True
1400
1401 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1402 notadded = []
1403 for layer in addlayers:
1404 layer_canon = canonicalise_path(layer)
1405 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1406 notadded.append(layer)
1407 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1408 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1409
1410 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1411 if addlayers:
1412 # Still need to add these
1413 for addlayer in addlayers:
1414 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1415 updated = True
1416
1417 if updated:
1418 with open(bblayers_conf, 'w') as f:
1419 f.writelines(newlines)
1420
1421 notremoved = list(set(removelayers) - set(removed))
1422
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001423 return (notadded, notremoved)
1424
1425
1426def get_file_layer(filename, d):
1427 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001428 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001429 collection_res = {}
1430 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001431 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001432
1433 def path_to_layer(path):
1434 # Use longest path so we handle nested layers
1435 matchlen = 0
1436 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001437 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001438 if len(regex) > matchlen and re.match(regex, path):
1439 matchlen = len(regex)
1440 match = collection
1441 return match
1442
1443 result = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001444 bbfiles = (d.getVar('BBFILES') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001445 bbfilesmatch = False
1446 for bbfilesentry in bbfiles:
1447 if fnmatch.fnmatch(filename, bbfilesentry):
1448 bbfilesmatch = True
1449 result = path_to_layer(bbfilesentry)
1450
1451 if not bbfilesmatch:
1452 # Probably a bbclass
1453 result = path_to_layer(filename)
1454
1455 return result
1456
1457
1458# Constant taken from http://linux.die.net/include/linux/prctl.h
1459PR_SET_PDEATHSIG = 1
1460
1461class PrCtlError(Exception):
1462 pass
1463
1464def signal_on_parent_exit(signame):
1465 """
1466 Trigger signame to be sent when the parent process dies
1467 """
1468 signum = getattr(signal, signame)
1469 # http://linux.die.net/man/2/prctl
1470 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1471 if result != 0:
1472 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001473
1474#
1475# Manually call the ioprio syscall. We could depend on other libs like psutil
1476# however this gets us enough of what we need to bitbake for now without the
1477# dependency
1478#
1479_unamearch = os.uname()[4]
1480IOPRIO_WHO_PROCESS = 1
1481IOPRIO_CLASS_SHIFT = 13
1482
1483def ioprio_set(who, cls, value):
1484 NR_ioprio_set = None
1485 if _unamearch == "x86_64":
1486 NR_ioprio_set = 251
1487 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1488 NR_ioprio_set = 289
Brad Bishop19323692019-04-05 15:28:33 -04001489 elif _unamearch == "aarch64":
1490 NR_ioprio_set = 30
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001491
1492 if NR_ioprio_set:
1493 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1494 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1495 if rc != 0:
1496 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1497 else:
1498 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001499
1500def set_process_name(name):
1501 from ctypes import cdll, byref, create_string_buffer
1502 # This is nice to have for debugging, not essential
1503 try:
1504 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001505 buf = create_string_buffer(bytes(name, 'utf-8'))
1506 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001507 except:
1508 pass
1509
1510# export common proxies variables from datastore to environment
1511def export_proxies(d):
1512 import os
1513
1514 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001515 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1516 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001517 exported = False
1518
1519 for v in variables:
1520 if v in os.environ.keys():
1521 exported = True
1522 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001523 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001524 if v_proxy is not None:
1525 os.environ[v] = v_proxy
1526 exported = True
1527
1528 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001529
1530
1531def load_plugins(logger, plugins, pluginpath):
1532 def load_plugin(name):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001533 logger.debug(1, 'Loading plugin %s' % name)
Brad Bishop19323692019-04-05 15:28:33 -04001534 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1535 if spec:
1536 return spec.loader.load_module()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001537
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001538 logger.debug(1, 'Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001539
1540 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1541 for ext in python_extensions)
1542 files = itertools.chain.from_iterable(expanded)
1543 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1544 for name in names:
1545 if name != '__init__':
1546 plugin = load_plugin(name)
1547 if hasattr(plugin, 'plugin_init'):
1548 obj = plugin.plugin_init(plugins)
1549 plugins.append(obj or plugin)
1550 else:
1551 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001552
1553
1554class LogCatcher(logging.Handler):
1555 """Logging handler for collecting logged messages so you can check them later"""
1556 def __init__(self):
1557 self.messages = []
1558 logging.Handler.__init__(self, logging.WARNING)
1559 def emit(self, record):
1560 self.messages.append(bb.build.logformatter.format(record))
1561 def contains(self, message):
1562 return (message in self.messages)