blob: d035949b3d6bdc947e9f1e9fc84647ca951ecd61 [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
523def md5_file(filename):
524 """
525 Return the hex string representation of the MD5 checksum of filename.
526 """
Brad Bishop220d5532018-08-14 00:59:39 +0100527 import hashlib, mmap
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500528
529 with open(filename, "rb") as f:
Brad Bishop220d5532018-08-14 00:59:39 +0100530 m = hashlib.md5()
531 try:
532 with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
533 for chunk in iter(lambda: mm.read(8192), b''):
534 m.update(chunk)
535 except ValueError:
536 # You can't mmap() an empty file so silence this exception
537 pass
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500538 return m.hexdigest()
539
540def sha256_file(filename):
541 """
542 Return the hex string representation of the 256-bit SHA checksum of
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500543 filename.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500544 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500545 import hashlib
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500546
547 s = hashlib.sha256()
548 with open(filename, "rb") as f:
549 for line in f:
550 s.update(line)
551 return s.hexdigest()
552
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500553def sha1_file(filename):
554 """
555 Return the hex string representation of the SHA1 checksum of the filename
556 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500557 import hashlib
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500558
559 s = hashlib.sha1()
560 with open(filename, "rb") as f:
561 for line in f:
562 s.update(line)
563 return s.hexdigest()
564
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500565def preserved_envvars_exported():
566 """Variables which are taken from the environment and placed in and exported
567 from the metadata"""
568 return [
569 'BB_TASKHASH',
570 'HOME',
571 'LOGNAME',
572 'PATH',
573 'PWD',
574 'SHELL',
575 'TERM',
576 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600577 'LC_ALL',
578 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500579 ]
580
581def preserved_envvars():
582 """Variables which are taken from the environment and placed in the metadata"""
583 v = [
584 'BBPATH',
585 'BB_PRESERVE_ENV',
586 'BB_ENV_WHITELIST',
587 'BB_ENV_EXTRAWHITE',
588 ]
589 return v + preserved_envvars_exported()
590
591def filter_environment(good_vars):
592 """
593 Create a pristine environment for bitbake. This will remove variables that
594 are not known and may influence the build in a negative way.
595 """
596
597 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600598 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500599 if key in good_vars:
600 continue
601
602 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500603 del os.environ[key]
604
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600605 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
606 # access methods will use ascii. You can't change that mode once the interpreter is
607 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
608 # distros support that and we need to set something.
609 os.environ["LC_ALL"] = "en_US.UTF-8"
610
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500611 if removed_vars:
612 logger.debug(1, "Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
613
614 return removed_vars
615
616def approved_variables():
617 """
618 Determine and return the list of whitelisted variables which are approved
619 to remain in the environment.
620 """
621 if 'BB_PRESERVE_ENV' in os.environ:
622 return os.environ.keys()
623 approved = []
624 if 'BB_ENV_WHITELIST' in os.environ:
625 approved = os.environ['BB_ENV_WHITELIST'].split()
626 approved.extend(['BB_ENV_WHITELIST'])
627 else:
628 approved = preserved_envvars()
629 if 'BB_ENV_EXTRAWHITE' in os.environ:
630 approved.extend(os.environ['BB_ENV_EXTRAWHITE'].split())
631 if 'BB_ENV_EXTRAWHITE' not in approved:
632 approved.extend(['BB_ENV_EXTRAWHITE'])
633 return approved
634
635def clean_environment():
636 """
637 Clean up any spurious environment variables. This will remove any
638 variables the user hasn't chosen to preserve.
639 """
640 if 'BB_PRESERVE_ENV' not in os.environ:
641 good_vars = approved_variables()
642 return filter_environment(good_vars)
643
644 return {}
645
646def empty_environment():
647 """
648 Remove all variables from the environment.
649 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600650 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500651 os.unsetenv(s)
652 del os.environ[s]
653
654def build_environment(d):
655 """
656 Build an environment from all exported variables.
657 """
658 import bb.data
659 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500660 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500661 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500662 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500663
664def _check_unsafe_delete_path(path):
665 """
666 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
667 the caller should raise an exception with an appropriate message.
668 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
669 with potentially disastrous results.
670 """
671 extra = ''
672 # HOME might not be /home/something, so in case we can get it, check against it
673 homedir = os.environ.get('HOME', '')
674 if homedir:
675 extra = '|%s' % homedir
676 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
677 return True
678 return False
679
Brad Bishopa34c0302019-09-23 22:34:48 -0400680def remove(path, recurse=False, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500681 """Equivalent to rm -f or rm -rf"""
682 if not path:
683 return
684 if recurse:
685 for name in glob.glob(path):
686 if _check_unsafe_delete_path(path):
687 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % path)
688 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishopa34c0302019-09-23 22:34:48 -0400689 cmd = []
690 if ionice:
691 cmd = ['ionice', '-c', '3']
692 subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500693 return
694 for name in glob.glob(path):
695 try:
696 os.unlink(name)
697 except OSError as exc:
698 if exc.errno != errno.ENOENT:
699 raise
700
Brad Bishopa34c0302019-09-23 22:34:48 -0400701def prunedir(topdir, ionice=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500702 # Delete everything reachable from the directory named in 'topdir'.
703 # CAUTION: This is dangerous!
704 if _check_unsafe_delete_path(topdir):
705 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
Brad Bishopa34c0302019-09-23 22:34:48 -0400706 remove(topdir, recurse=True, ionice=ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500707
708#
709# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
710# but thats possibly insane and suffixes is probably going to be small
711#
712def prune_suffix(var, suffixes, d):
713 # See if var ends with any of the suffixes listed and
714 # remove it if found
715 for suffix in suffixes:
Brad Bishopd89cb5f2019-04-10 09:02:41 -0400716 if suffix and var.endswith(suffix):
717 return var[:-len(suffix)]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500718 return var
719
720def mkdirhier(directory):
721 """Create a directory like 'mkdir -p', but does not complain if
722 directory already exists like os.makedirs
723 """
724
725 try:
726 os.makedirs(directory)
727 except OSError as e:
Brad Bishopc342db32019-05-15 21:57:59 -0400728 if e.errno != errno.EEXIST or not os.path.isdir(directory):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500729 raise e
730
731def movefile(src, dest, newmtime = None, sstat = None):
732 """Moves a file from src to dest, preserving all permissions and
733 attributes; mtime will be preserved even when moving across
734 filesystems. Returns true on success and false on failure. Move is
735 atomic.
736 """
737
738 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
739 try:
740 if not sstat:
741 sstat = os.lstat(src)
742 except Exception as e:
743 print("movefile: Stating source file failed...", e)
744 return None
745
746 destexists = 1
747 try:
748 dstat = os.lstat(dest)
749 except:
750 dstat = os.lstat(os.path.dirname(dest))
751 destexists = 0
752
753 if destexists:
754 if stat.S_ISLNK(dstat[stat.ST_MODE]):
755 try:
756 os.unlink(dest)
757 destexists = 0
758 except Exception as e:
759 pass
760
761 if stat.S_ISLNK(sstat[stat.ST_MODE]):
762 try:
763 target = os.readlink(src)
764 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
765 os.unlink(dest)
766 os.symlink(target, dest)
767 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
768 os.unlink(src)
769 return os.lstat(dest)
770 except Exception as e:
771 print("movefile: failed to properly create symlink:", dest, "->", target, e)
772 return None
773
774 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500775 # os.rename needs to know the dest path ending with file name
776 # so append the file name to a path only if it's a dir specified
777 srcfname = os.path.basename(src)
778 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
779 else dest
780
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500781 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
782 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500783 os.rename(src, destpath)
784 renamefailed = 0
785 except Exception as e:
Brad Bishop79641f22019-09-10 07:20:22 -0400786 if e.errno != errno.EXDEV:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500787 # Some random error.
788 print("movefile: Failed to move", src, "to", dest, e)
789 return None
790 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
791
792 if renamefailed:
793 didcopy = 0
794 if stat.S_ISREG(sstat[stat.ST_MODE]):
795 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500796 shutil.copyfile(src, destpath + "#new")
797 os.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500798 didcopy = 1
799 except Exception as e:
800 print('movefile: copy', src, '->', dest, 'failed.', e)
801 return None
802 else:
803 #we don't yet handle special, so we need to fall back to /bin/mv
804 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
805 if a[0] != 0:
806 print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
807 return None # failure
808 try:
809 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400810 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
811 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500812 os.unlink(src)
813 except Exception as e:
814 print("movefile: Failed to chown/chmod/unlink", dest, e)
815 return None
816
817 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500818 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500819 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500820 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500821 newmtime = sstat[stat.ST_MTIME]
822 return newmtime
823
824def copyfile(src, dest, newmtime = None, sstat = None):
825 """
826 Copies a file from src to dest, preserving all permissions and
827 attributes; mtime will be preserved even when moving across
828 filesystems. Returns true on success and false on failure.
829 """
830 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
831 try:
832 if not sstat:
833 sstat = os.lstat(src)
834 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600835 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500836 return False
837
838 destexists = 1
839 try:
840 dstat = os.lstat(dest)
841 except:
842 dstat = os.lstat(os.path.dirname(dest))
843 destexists = 0
844
845 if destexists:
846 if stat.S_ISLNK(dstat[stat.ST_MODE]):
847 try:
848 os.unlink(dest)
849 destexists = 0
850 except Exception as e:
851 pass
852
853 if stat.S_ISLNK(sstat[stat.ST_MODE]):
854 try:
855 target = os.readlink(src)
856 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
857 os.unlink(dest)
858 os.symlink(target, dest)
859 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
860 return os.lstat(dest)
861 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600862 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500863 return False
864
865 if stat.S_ISREG(sstat[stat.ST_MODE]):
866 try:
867 srcchown = False
868 if not os.access(src, os.R_OK):
869 # Make sure we can read it
870 srcchown = True
871 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
872
873 # For safety copy then move it over.
874 shutil.copyfile(src, dest + "#new")
875 os.rename(dest + "#new", dest)
876 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600877 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500878 return False
879 finally:
880 if srcchown:
881 os.chmod(src, sstat[stat.ST_MODE])
882 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
883
884 else:
885 #we don't yet handle special, so we need to fall back to /bin/mv
886 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
887 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600888 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500889 return False # failure
890 try:
891 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
892 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
893 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600894 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500895 return False
896
897 if newmtime:
898 os.utime(dest, (newmtime, newmtime))
899 else:
900 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
901 newmtime = sstat[stat.ST_MTIME]
902 return newmtime
903
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800904def break_hardlinks(src, sstat = None):
905 """
906 Ensures src is the only hardlink to this file. Other hardlinks,
907 if any, are not affected (other than in their st_nlink value, of
908 course). Returns true on success and false on failure.
909
910 """
911 try:
912 if not sstat:
913 sstat = os.lstat(src)
914 except Exception as e:
915 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
916 return False
917 if sstat[stat.ST_NLINK] == 1:
918 return True
919 return copyfile(src, src, sstat=sstat)
920
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500921def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500922 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500923 Locate `item` in the list of paths `path` (colon separated string like $PATH).
924 If `direction` is non-zero then the list is reversed.
925 If `history` is True then the list of candidates also returned as result,history.
926 If `executable` is True then the candidate has to be an executable file,
927 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500928 """
929
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500930 if executable:
931 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
932 else:
933 is_candidate = lambda p: os.path.exists(p)
934
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500935 hist = []
936 paths = (path or "").split(':')
937 if direction != 0:
938 paths.reverse()
939
940 for p in paths:
941 next = os.path.join(p, item)
942 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500943 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500944 if not os.path.isabs(next):
945 next = os.path.abspath(next)
946 if history:
947 return next, hist
948 return next
949
950 if history:
951 return "", hist
952 return ""
953
954def to_boolean(string, default=None):
955 if not string:
956 return default
957
958 normalized = string.lower()
959 if normalized in ("y", "yes", "1", "true"):
960 return True
961 elif normalized in ("n", "no", "0", "false"):
962 return False
963 else:
964 raise ValueError("Invalid value for to_boolean: %s" % string)
965
966def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500967 """Check if a variable contains all the values specified.
968
969 Arguments:
970
971 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500972 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500973
974 checkvalues -- if this is a string it is split on whitespace into a set(),
975 otherwise coerced directly into a set().
976
977 truevalue -- the value to return if checkvalues is a subset of variable.
978
979 falsevalue -- the value to return if variable is empty or if checkvalues is
980 not a subset of variable.
981
982 d -- the data store.
983 """
984
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500985 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500986 if not val:
987 return falsevalue
988 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600989 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500990 checkvalues = set(checkvalues.split())
991 else:
992 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500993 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500994 return truevalue
995 return falsevalue
996
997def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500998 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500999 if not val:
1000 return falsevalue
1001 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001002 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001003 checkvalues = set(checkvalues.split())
1004 else:
1005 checkvalues = set(checkvalues)
1006 if checkvalues & val:
1007 return truevalue
1008 return falsevalue
1009
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001010def filter(variable, checkvalues, d):
1011 """Return all words in the variable that are present in the checkvalues.
1012
1013 Arguments:
1014
1015 variable -- the variable name. This will be fetched and expanded (using
1016 d.getVar(variable)) and then split into a set().
1017
1018 checkvalues -- if this is a string it is split on whitespace into a set(),
1019 otherwise coerced directly into a set().
1020
1021 d -- the data store.
1022 """
1023
1024 val = d.getVar(variable)
1025 if not val:
1026 return ''
1027 val = set(val.split())
1028 if isinstance(checkvalues, str):
1029 checkvalues = set(checkvalues.split())
1030 else:
1031 checkvalues = set(checkvalues)
1032 return ' '.join(sorted(checkvalues & val))
1033
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001034def cpu_count():
1035 return multiprocessing.cpu_count()
1036
1037def nonblockingfd(fd):
1038 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1039
1040def process_profilelog(fn, pout = None):
1041 # Either call with a list of filenames and set pout or a filename and optionally pout.
1042 if not pout:
1043 pout = fn + '.processed'
1044 pout = open(pout, 'w')
1045
1046 import pstats
1047 if isinstance(fn, list):
1048 p = pstats.Stats(*fn, stream=pout)
1049 else:
1050 p = pstats.Stats(fn, stream=pout)
1051 p.sort_stats('time')
1052 p.print_stats()
1053 p.print_callers()
1054 p.sort_stats('cumulative')
1055 p.print_stats()
1056
1057 pout.flush()
1058 pout.close()
1059
1060#
1061# Was present to work around multiprocessing pool bugs in python < 2.7.3
1062#
1063def multiprocessingpool(*args, **kwargs):
1064
1065 import multiprocessing.pool
1066 #import multiprocessing.util
1067 #multiprocessing.util.log_to_stderr(10)
1068 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1069 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1070 def wrapper(func):
1071 def wrap(self, timeout=None):
1072 return func(self, timeout=timeout if timeout is not None else 1e100)
1073 return wrap
1074 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1075
1076 return multiprocessing.Pool(*args, **kwargs)
1077
1078def exec_flat_python_func(func, *args, **kwargs):
1079 """Execute a flat python function (defined with def funcname(args):...)"""
1080 # Prepare a small piece of python code which calls the requested function
1081 # To do this we need to prepare two things - a set of variables we can use to pass
1082 # the values of arguments into the calling function, and the list of arguments for
1083 # the function being called
1084 context = {}
1085 funcargs = []
1086 # Handle unnamed arguments
1087 aidx = 1
1088 for arg in args:
1089 argname = 'arg_%s' % aidx
1090 context[argname] = arg
1091 funcargs.append(argname)
1092 aidx += 1
1093 # Handle keyword arguments
1094 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001095 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001096 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1097 comp = bb.utils.better_compile(code, '<string>', '<string>')
1098 bb.utils.better_exec(comp, context, code, '<string>')
1099 return context['retval']
1100
1101def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1102 """Edit lines from a recipe or config file and modify one or more
1103 specified variable values set in the file using a specified callback
1104 function. Lines are expected to have trailing newlines.
1105 Parameters:
1106 meta_lines: lines from the file; can be a list or an iterable
1107 (e.g. file pointer)
1108 variables: a list of variable names to look for. Functions
1109 may also be specified, but must be specified with '()' at
1110 the end of the name. Note that the function doesn't have
1111 any intrinsic understanding of _append, _prepend, _remove,
1112 or overrides, so these are considered as part of the name.
1113 These values go into a regular expression, so regular
1114 expression syntax is allowed.
1115 varfunc: callback function called for every variable matching
1116 one of the entries in the variables parameter. The function
1117 should take four arguments:
1118 varname: name of variable matched
1119 origvalue: current value in file
1120 op: the operator (e.g. '+=')
1121 newlines: list of lines up to this point. You can use
1122 this to prepend lines before this variable setting
1123 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001124 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001125 newvalue: new value to substitute in, or None to drop
1126 the variable setting entirely. (If the removal
1127 results in two consecutive blank lines, one of the
1128 blank lines will also be dropped).
1129 newop: the operator to use - if you specify None here,
1130 the original operation will be used.
1131 indent: number of spaces to indent multi-line entries,
1132 or -1 to indent up to the level of the assignment
1133 and opening quote, or a string to use as the indent.
1134 minbreak: True to allow the first element of a
1135 multi-line value to continue on the same line as
1136 the assignment, False to indent before the first
1137 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001138 To clarify, if you wish not to change the value, then you
1139 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001140 match_overrides: True to match items with _overrides on the end,
1141 False otherwise
1142 Returns a tuple:
1143 updated:
1144 True if changes were made, False otherwise.
1145 newlines:
1146 Lines after processing
1147 """
1148
1149 var_res = {}
1150 if match_overrides:
Brad Bishop19323692019-04-05 15:28:33 -04001151 override_re = r'(_[a-zA-Z0-9-_$(){}]+)?'
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001152 else:
1153 override_re = ''
1154 for var in variables:
1155 if var.endswith('()'):
Brad Bishop19323692019-04-05 15:28:33 -04001156 var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001157 else:
Brad Bishop19323692019-04-05 15:28:33 -04001158 var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001159
1160 updated = False
1161 varset_start = ''
1162 varlines = []
1163 newlines = []
1164 in_var = None
1165 full_value = ''
1166 var_end = ''
1167
1168 def handle_var_end():
1169 prerun_newlines = newlines[:]
1170 op = varset_start[len(in_var):].strip()
1171 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1172 changed = (prerun_newlines != newlines)
1173
1174 if newvalue is None:
1175 # Drop the value
1176 return True
1177 elif newvalue != full_value or (newop not in [None, op]):
1178 if newop not in [None, op]:
1179 # Callback changed the operator
1180 varset_new = "%s %s" % (in_var, newop)
1181 else:
1182 varset_new = varset_start
1183
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001184 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001185 if indent == -1:
1186 indentspc = ' ' * (len(varset_new) + 2)
1187 else:
1188 indentspc = ' ' * indent
1189 else:
1190 indentspc = indent
1191 if in_var.endswith('()'):
1192 # A function definition
1193 if isinstance(newvalue, list):
1194 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1195 else:
1196 if not newvalue.startswith('\n'):
1197 newvalue = '\n' + newvalue
1198 if not newvalue.endswith('\n'):
1199 newvalue = newvalue + '\n'
1200 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1201 else:
1202 # Normal variable
1203 if isinstance(newvalue, list):
1204 if not newvalue:
1205 # Empty list -> empty string
1206 newlines.append('%s ""\n' % varset_new)
1207 elif minbreak:
1208 # First item on first line
1209 if len(newvalue) == 1:
1210 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1211 else:
1212 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1213 for item in newvalue[1:]:
1214 newlines.append('%s%s \\\n' % (indentspc, item))
1215 newlines.append('%s"\n' % indentspc)
1216 else:
1217 # No item on first line
1218 newlines.append('%s " \\\n' % varset_new)
1219 for item in newvalue:
1220 newlines.append('%s%s \\\n' % (indentspc, item))
1221 newlines.append('%s"\n' % indentspc)
1222 else:
1223 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1224 return True
1225 else:
1226 # Put the old lines back where they were
1227 newlines.extend(varlines)
1228 # If newlines was touched by the function, we'll need to return True
1229 return changed
1230
1231 checkspc = False
1232
1233 for line in meta_lines:
1234 if in_var:
1235 value = line.rstrip()
1236 varlines.append(line)
1237 if in_var.endswith('()'):
1238 full_value += '\n' + value
1239 else:
1240 full_value += value[:-1]
1241 if value.endswith(var_end):
1242 if in_var.endswith('()'):
1243 if full_value.count('{') - full_value.count('}') >= 0:
1244 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001245 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001246 if handle_var_end():
1247 updated = True
1248 checkspc = True
1249 in_var = None
1250 else:
1251 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001252 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001253 res = var_re.match(line)
1254 if res:
1255 isfunc = varname.endswith('()')
1256 if isfunc:
1257 splitvalue = line.split('{', 1)
1258 var_end = '}'
1259 else:
1260 var_end = res.groups()[-1]
1261 splitvalue = line.split(var_end, 1)
1262 varset_start = splitvalue[0].rstrip()
1263 value = splitvalue[1].rstrip()
1264 if not isfunc and value.endswith('\\'):
1265 value = value[:-1]
1266 full_value = value
1267 varlines = [line]
1268 in_var = res.group(1)
1269 if isfunc:
1270 in_var += '()'
1271 if value.endswith(var_end):
1272 full_value = full_value[:-1]
1273 if handle_var_end():
1274 updated = True
1275 checkspc = True
1276 in_var = None
1277 skip = True
1278 break
1279 if not skip:
1280 if checkspc:
1281 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001282 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001283 # Squash blank line if there are two consecutive blanks after a removal
1284 continue
1285 newlines.append(line)
1286 return (updated, newlines)
1287
1288
1289def edit_metadata_file(meta_file, variables, varfunc):
1290 """Edit a recipe or config file and modify one or more specified
1291 variable values set in the file using a specified callback function.
1292 The file is only written to if the value(s) actually change.
1293 This is basically the file version of edit_metadata(), see that
1294 function's description for parameter/usage information.
1295 Returns True if the file was written to, False otherwise.
1296 """
1297 with open(meta_file, 'r') as f:
1298 (updated, newlines) = edit_metadata(f, variables, varfunc)
1299 if updated:
1300 with open(meta_file, 'w') as f:
1301 f.writelines(newlines)
1302 return updated
1303
1304
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001305def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001306 """Edit bblayers.conf, adding and/or removing layers
1307 Parameters:
1308 bblayers_conf: path to bblayers.conf file to edit
1309 add: layer path (or list of layer paths) to add; None or empty
1310 list to add nothing
1311 remove: layer path (or list of layer paths) to remove; None or
1312 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001313 edit_cb: optional callback function that will be called after
1314 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001315 Returns a tuple:
1316 notadded: list of layers specified to be added but weren't
1317 (because they were already in the list)
1318 notremoved: list of layers that were specified to be removed
1319 but weren't (because they weren't in the list)
1320 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001321
1322 import fnmatch
1323
1324 def remove_trailing_sep(pth):
1325 if pth and pth[-1] == os.sep:
1326 pth = pth[:-1]
1327 return pth
1328
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001329 approved = bb.utils.approved_variables()
1330 def canonicalise_path(pth):
1331 pth = remove_trailing_sep(pth)
1332 if 'HOME' in approved and '~' in pth:
1333 pth = os.path.expanduser(pth)
1334 return pth
1335
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001336 def layerlist_param(value):
1337 if not value:
1338 return []
1339 elif isinstance(value, list):
1340 return [remove_trailing_sep(x) for x in value]
1341 else:
1342 return [remove_trailing_sep(value)]
1343
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001344 addlayers = layerlist_param(add)
1345 removelayers = layerlist_param(remove)
1346
1347 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1348 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001349 removed = []
1350 plusequals = False
1351 orig_bblayers = []
1352
1353 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1354 bblayercalls.append(op)
1355 if op == '=':
1356 del orig_bblayers[:]
1357 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1358 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001359
1360 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001361 updated = False
1362 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1363 if removelayers:
1364 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001365 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001366 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001367 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001368 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001369 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001370 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001371 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001372 for addlayer in addlayers:
1373 if addlayer not in bblayers:
1374 updated = True
1375 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001376 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001377
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001378 if edit_cb:
1379 newlist = []
1380 for layer in bblayers:
1381 res = edit_cb(layer, canonicalise_path(layer))
1382 if res != layer:
1383 newlist.append(res)
1384 updated = True
1385 else:
1386 newlist.append(layer)
1387 bblayers = newlist
1388
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001389 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001390 if op == '+=' and not bblayers:
1391 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001392 return (bblayers, None, 2, False)
1393 else:
1394 return (origvalue, None, 2, False)
1395
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001396 with open(bblayers_conf, 'r') as f:
1397 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001398
1399 if not bblayercalls:
1400 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1401
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001402 # Try to do the "smart" thing depending on how the user has laid out
1403 # their bblayers.conf file
1404 if bblayercalls.count('+=') > 1:
1405 plusequals = True
1406
1407 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1408 notadded = []
1409 for layer in addlayers:
1410 layer_canon = canonicalise_path(layer)
1411 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1412 notadded.append(layer)
1413 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1414 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1415
1416 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1417 if addlayers:
1418 # Still need to add these
1419 for addlayer in addlayers:
1420 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1421 updated = True
1422
1423 if updated:
1424 with open(bblayers_conf, 'w') as f:
1425 f.writelines(newlines)
1426
1427 notremoved = list(set(removelayers) - set(removed))
1428
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001429 return (notadded, notremoved)
1430
1431
1432def get_file_layer(filename, d):
1433 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001434 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001435 collection_res = {}
1436 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001437 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001438
1439 def path_to_layer(path):
1440 # Use longest path so we handle nested layers
1441 matchlen = 0
1442 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001443 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001444 if len(regex) > matchlen and re.match(regex, path):
1445 matchlen = len(regex)
1446 match = collection
1447 return match
1448
1449 result = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001450 bbfiles = (d.getVar('BBFILES') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001451 bbfilesmatch = False
1452 for bbfilesentry in bbfiles:
1453 if fnmatch.fnmatch(filename, bbfilesentry):
1454 bbfilesmatch = True
1455 result = path_to_layer(bbfilesentry)
1456
1457 if not bbfilesmatch:
1458 # Probably a bbclass
1459 result = path_to_layer(filename)
1460
1461 return result
1462
1463
1464# Constant taken from http://linux.die.net/include/linux/prctl.h
1465PR_SET_PDEATHSIG = 1
1466
1467class PrCtlError(Exception):
1468 pass
1469
1470def signal_on_parent_exit(signame):
1471 """
1472 Trigger signame to be sent when the parent process dies
1473 """
1474 signum = getattr(signal, signame)
1475 # http://linux.die.net/man/2/prctl
1476 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1477 if result != 0:
1478 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001479
1480#
1481# Manually call the ioprio syscall. We could depend on other libs like psutil
1482# however this gets us enough of what we need to bitbake for now without the
1483# dependency
1484#
1485_unamearch = os.uname()[4]
1486IOPRIO_WHO_PROCESS = 1
1487IOPRIO_CLASS_SHIFT = 13
1488
1489def ioprio_set(who, cls, value):
1490 NR_ioprio_set = None
1491 if _unamearch == "x86_64":
1492 NR_ioprio_set = 251
1493 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1494 NR_ioprio_set = 289
Brad Bishop19323692019-04-05 15:28:33 -04001495 elif _unamearch == "aarch64":
1496 NR_ioprio_set = 30
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001497
1498 if NR_ioprio_set:
1499 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1500 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1501 if rc != 0:
1502 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1503 else:
1504 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001505
1506def set_process_name(name):
1507 from ctypes import cdll, byref, create_string_buffer
1508 # This is nice to have for debugging, not essential
1509 try:
1510 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001511 buf = create_string_buffer(bytes(name, 'utf-8'))
1512 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001513 except:
1514 pass
1515
1516# export common proxies variables from datastore to environment
1517def export_proxies(d):
1518 import os
1519
1520 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001521 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1522 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001523 exported = False
1524
1525 for v in variables:
1526 if v in os.environ.keys():
1527 exported = True
1528 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001529 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001530 if v_proxy is not None:
1531 os.environ[v] = v_proxy
1532 exported = True
1533
1534 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001535
1536
1537def load_plugins(logger, plugins, pluginpath):
1538 def load_plugin(name):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001539 logger.debug(1, 'Loading plugin %s' % name)
Brad Bishop19323692019-04-05 15:28:33 -04001540 spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] )
1541 if spec:
1542 return spec.loader.load_module()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001543
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001544 logger.debug(1, 'Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001545
1546 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1547 for ext in python_extensions)
1548 files = itertools.chain.from_iterable(expanded)
1549 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1550 for name in names:
1551 if name != '__init__':
1552 plugin = load_plugin(name)
1553 if hasattr(plugin, 'plugin_init'):
1554 obj = plugin.plugin_init(plugins)
1555 plugins.append(obj or plugin)
1556 else:
1557 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001558
1559
1560class LogCatcher(logging.Handler):
1561 """Logging handler for collecting logged messages so you can check them later"""
1562 def __init__(self):
1563 self.messages = []
1564 logging.Handler.__init__(self, logging.WARNING)
1565 def emit(self, record):
1566 self.messages.append(bb.build.logformatter.format(record))
1567 def contains(self, message):
1568 return (message in self.messages)