blob: 73b6cb423b5772237958b686f33a16ec66bffbfd [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3"""
4BitBake Utility Functions
5"""
6
7# Copyright (C) 2004 Michael Lauer
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
22import re, fcntl, os, string, stat, shutil, time
23import sys
24import errno
25import logging
26import bb
27import bb.msg
28import multiprocessing
29import fcntl
Patrick Williamsc0f7c042017-02-23 20:41:17 -060030import imp
31import itertools
Patrick Williamsc124f4f2015-09-15 14:41:29 -050032import subprocess
33import glob
34import fnmatch
35import traceback
36import errno
37import signal
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050038import ast
Patrick Williamsc0f7c042017-02-23 20:41:17 -060039import collections
40import copy
41from subprocess import getstatusoutput
Patrick Williamsc124f4f2015-09-15 14:41:29 -050042from contextlib import contextmanager
43from ctypes import cdll
44
Patrick Williamsc124f4f2015-09-15 14:41:29 -050045logger = logging.getLogger("BitBake.Util")
Patrick Williamsc0f7c042017-02-23 20:41:17 -060046python_extensions = [e for e, _, _ in imp.get_suffixes()]
47
Patrick Williamsc124f4f2015-09-15 14:41:29 -050048
49def clean_context():
50 return {
51 "os": os,
52 "bb": bb,
53 "time": time,
54 }
55
56def get_context():
57 return _context
58
59
60def set_context(ctx):
61 _context = ctx
62
63# Context used in better_exec, eval
64_context = clean_context()
65
66class VersionStringException(Exception):
67 """Exception raised when an invalid version specification is found"""
68
69def explode_version(s):
70 r = []
71 alpha_regexp = re.compile('^([a-zA-Z]+)(.*)$')
72 numeric_regexp = re.compile('^(\d+)(.*)$')
73 while (s != ''):
74 if s[0] in string.digits:
75 m = numeric_regexp.match(s)
76 r.append((0, int(m.group(1))))
77 s = m.group(2)
78 continue
Patrick Williamsc0f7c042017-02-23 20:41:17 -060079 if s[0] in string.ascii_letters:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050080 m = alpha_regexp.match(s)
81 r.append((1, m.group(1)))
82 s = m.group(2)
83 continue
84 if s[0] == '~':
85 r.append((-1, s[0]))
86 else:
87 r.append((2, s[0]))
88 s = s[1:]
89 return r
90
91def split_version(s):
92 """Split a version string into its constituent parts (PE, PV, PR)"""
93 s = s.strip(" <>=")
94 e = 0
95 if s.count(':'):
96 e = int(s.split(":")[0])
97 s = s.split(":")[1]
98 r = ""
99 if s.count('-'):
100 r = s.rsplit("-", 1)[1]
101 s = s.rsplit("-", 1)[0]
102 v = s
103 return (e, v, r)
104
105def vercmp_part(a, b):
106 va = explode_version(a)
107 vb = explode_version(b)
108 while True:
109 if va == []:
110 (oa, ca) = (0, None)
111 else:
112 (oa, ca) = va.pop(0)
113 if vb == []:
114 (ob, cb) = (0, None)
115 else:
116 (ob, cb) = vb.pop(0)
117 if (oa, ca) == (0, None) and (ob, cb) == (0, None):
118 return 0
119 if oa < ob:
120 return -1
121 elif oa > ob:
122 return 1
123 elif ca < cb:
124 return -1
125 elif ca > cb:
126 return 1
127
128def vercmp(ta, tb):
129 (ea, va, ra) = ta
130 (eb, vb, rb) = tb
131
132 r = int(ea or 0) - int(eb or 0)
133 if (r == 0):
134 r = vercmp_part(va, vb)
135 if (r == 0):
136 r = vercmp_part(ra, rb)
137 return r
138
139def vercmp_string(a, b):
140 ta = split_version(a)
141 tb = split_version(b)
142 return vercmp(ta, tb)
143
144def vercmp_string_op(a, b, op):
145 """
146 Compare two versions and check if the specified comparison operator matches the result of the comparison.
147 This function is fairly liberal about what operators it will accept since there are a variety of styles
148 depending on the context.
149 """
150 res = vercmp_string(a, b)
151 if op in ('=', '=='):
152 return res == 0
153 elif op == '<=':
154 return res <= 0
155 elif op == '>=':
156 return res >= 0
157 elif op in ('>', '>>'):
158 return res > 0
159 elif op in ('<', '<<'):
160 return res < 0
161 elif op == '!=':
162 return res != 0
163 else:
164 raise VersionStringException('Unsupported comparison operator "%s"' % op)
165
166def explode_deps(s):
167 """
168 Take an RDEPENDS style string of format:
169 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
170 and return a list of dependencies.
171 Version information is ignored.
172 """
173 r = []
174 l = s.split()
175 flag = False
176 for i in l:
177 if i[0] == '(':
178 flag = True
179 #j = []
180 if not flag:
181 r.append(i)
182 #else:
183 # j.append(i)
184 if flag and i.endswith(')'):
185 flag = False
186 # Ignore version
187 #r[-1] += ' ' + ' '.join(j)
188 return r
189
Brad Bishop316dfdd2018-06-25 12:45:53 -0400190def explode_dep_versions2(s, *, sort=True):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500191 """
192 Take an RDEPENDS style string of format:
193 "DEPEND1 (optional version) DEPEND2 (optional version) ..."
194 and return a dictionary of dependencies and versions.
195 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600196 r = collections.OrderedDict()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500197 l = s.replace(",", "").split()
198 lastdep = None
199 lastcmp = ""
200 lastver = ""
201 incmp = False
202 inversion = False
203 for i in l:
204 if i[0] == '(':
205 incmp = True
206 i = i[1:].strip()
207 if not i:
208 continue
209
210 if incmp:
211 incmp = False
212 inversion = True
213 # This list is based on behavior and supported comparisons from deb, opkg and rpm.
214 #
215 # Even though =<, <<, ==, !=, =>, and >> may not be supported,
216 # we list each possibly valid item.
217 # The build system is responsible for validation of what it supports.
218 if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')):
219 lastcmp = i[0:2]
220 i = i[2:]
221 elif i.startswith(('<', '>', '=')):
222 lastcmp = i[0:1]
223 i = i[1:]
224 else:
225 # This is an unsupported case!
226 raise VersionStringException('Invalid version specification in "(%s" - invalid or missing operator' % i)
227 lastcmp = (i or "")
228 i = ""
229 i.strip()
230 if not i:
231 continue
232
233 if inversion:
234 if i.endswith(')'):
235 i = i[:-1] or ""
236 inversion = False
237 if lastver and i:
238 lastver += " "
239 if i:
240 lastver += i
241 if lastdep not in r:
242 r[lastdep] = []
243 r[lastdep].append(lastcmp + " " + lastver)
244 continue
245
246 #if not inversion:
247 lastdep = i
248 lastver = ""
249 lastcmp = ""
250 if not (i in r and r[i]):
251 r[lastdep] = []
252
Brad Bishop316dfdd2018-06-25 12:45:53 -0400253 if sort:
254 r = collections.OrderedDict(sorted(r.items(), key=lambda x: x[0]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500255 return r
256
257def explode_dep_versions(s):
258 r = explode_dep_versions2(s)
259 for d in r:
260 if not r[d]:
261 r[d] = None
262 continue
263 if len(r[d]) > 1:
264 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))
265 r[d] = r[d][0]
266 return r
267
268def join_deps(deps, commasep=True):
269 """
270 Take the result from explode_dep_versions and generate a dependency string
271 """
272 result = []
273 for dep in deps:
274 if deps[dep]:
275 if isinstance(deps[dep], list):
276 for v in deps[dep]:
277 result.append(dep + " (" + v + ")")
278 else:
279 result.append(dep + " (" + deps[dep] + ")")
280 else:
281 result.append(dep)
282 if commasep:
283 return ", ".join(result)
284 else:
285 return " ".join(result)
286
287def _print_trace(body, line):
288 """
289 Print the Environment of a Text Body
290 """
291 error = []
292 # print the environment of the method
293 min_line = max(1, line-4)
294 max_line = min(line + 4, len(body))
295 for i in range(min_line, max_line + 1):
296 if line == i:
297 error.append(' *** %.4d:%s' % (i, body[i-1].rstrip()))
298 else:
299 error.append(' %.4d:%s' % (i, body[i-1].rstrip()))
300 return error
301
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500302def better_compile(text, file, realfile, mode = "exec", lineno = 0):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500303 """
304 A better compile method. This method
305 will print the offending lines.
306 """
307 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500308 cache = bb.methodpool.compile_cache(text)
309 if cache:
310 return cache
311 # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though
312 text2 = "\n" * int(lineno) + text
313 code = compile(text2, realfile, mode)
314 bb.methodpool.compile_cache_add(text, code)
315 return code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500316 except Exception as e:
317 error = []
318 # split the text into lines again
319 body = text.split('\n')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500320 error.append("Error in compiling python function in %s, line %s:\n" % (realfile, lineno))
321 if hasattr(e, "lineno"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322 error.append("The code lines resulting in this error were:")
323 error.extend(_print_trace(body, e.lineno))
324 else:
325 error.append("The function causing this error was:")
326 for line in body:
327 error.append(line)
328 error.append("%s: %s" % (e.__class__.__name__, str(e)))
329
330 logger.error("\n".join(error))
331
332 e = bb.BBHandledException(e)
333 raise e
334
335def _print_exception(t, value, tb, realfile, text, context):
336 error = []
337 try:
338 exception = traceback.format_exception_only(t, value)
339 error.append('Error executing a python function in %s:\n' % realfile)
340
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500341 # Strip 'us' from the stack (better_exec call) unless that was where the
342 # error came from
343 if tb.tb_next is not None:
344 tb = tb.tb_next
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500345
346 textarray = text.split('\n')
347
348 linefailed = tb.tb_lineno
349
350 tbextract = traceback.extract_tb(tb)
351 tbformat = traceback.format_list(tbextract)
352 error.append("The stack trace of python calls that resulted in this exception/failure was:")
353 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2]))
354 error.extend(_print_trace(textarray, linefailed))
355
356 # See if this is a function we constructed and has calls back into other functions in
357 # "text". If so, try and improve the context of the error by diving down the trace
358 level = 0
359 nexttb = tb.tb_next
360 while nexttb is not None and (level+1) < len(tbextract):
361 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]))
362 if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]:
363 # The code was possibly in the string we compiled ourselves
364 error.extend(_print_trace(textarray, tbextract[level+1][1]))
365 elif tbextract[level+1][0].startswith("/"):
366 # The code looks like it might be in a file, try and load it
367 try:
368 with open(tbextract[level+1][0], "r") as f:
369 text = f.readlines()
370 error.extend(_print_trace(text, tbextract[level+1][1]))
371 except:
372 error.append(tbformat[level+1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500373 else:
374 error.append(tbformat[level+1])
375 nexttb = tb.tb_next
376 level = level + 1
377
378 error.append("Exception: %s" % ''.join(exception))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600379
380 # If the exception is from spwaning a task, let's be helpful and display
381 # the output (which hopefully includes stderr).
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500382 if isinstance(value, subprocess.CalledProcessError) and value.output:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600383 error.append("Subprocess output:")
384 error.append(value.output.decode("utf-8", errors="ignore"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500385 finally:
386 logger.error("\n".join(error))
387
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500388def better_exec(code, context, text = None, realfile = "<code>", pythonexception=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500389 """
390 Similiar to better_compile, better_exec will
391 print the lines that are responsible for the
392 error.
393 """
394 import bb.parse
395 if not text:
396 text = code
397 if not hasattr(code, "co_filename"):
398 code = better_compile(code, realfile, realfile)
399 try:
400 exec(code, get_context(), context)
401 except (bb.BBHandledException, bb.parse.SkipRecipe, bb.build.FuncFailed, bb.data_smart.ExpansionError):
402 # Error already shown so passthrough, no need for traceback
403 raise
404 except Exception as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500405 if pythonexception:
406 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500407 (t, value, tb) = sys.exc_info()
408 try:
409 _print_exception(t, value, tb, realfile, text, context)
410 except Exception as e:
411 logger.error("Exception handler error: %s" % str(e))
412
413 e = bb.BBHandledException(e)
414 raise e
415
416def simple_exec(code, context):
417 exec(code, get_context(), context)
418
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600419def better_eval(source, locals, extraglobals = None):
420 ctx = get_context()
421 if extraglobals:
422 ctx = copy.copy(ctx)
423 for g in extraglobals:
424 ctx[g] = extraglobals[g]
425 return eval(source, ctx, locals)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500426
427@contextmanager
428def fileslocked(files):
429 """Context manager for locking and unlocking file locks."""
430 locks = []
431 if files:
432 for lockfile in files:
433 locks.append(bb.utils.lockfile(lockfile))
434
435 yield
436
437 for lock in locks:
438 bb.utils.unlockfile(lock)
439
440@contextmanager
441def timeout(seconds):
442 def timeout_handler(signum, frame):
443 pass
444
445 original_handler = signal.signal(signal.SIGALRM, timeout_handler)
446
447 try:
448 signal.alarm(seconds)
449 yield
450 finally:
451 signal.alarm(0)
452 signal.signal(signal.SIGALRM, original_handler)
453
454def lockfile(name, shared=False, retry=True, block=False):
455 """
456 Use the specified file as a lock file, return when the lock has
457 been acquired. Returns a variable to pass to unlockfile().
458 Parameters:
459 retry: True to re-try locking if it fails, False otherwise
460 block: True to block until the lock succeeds, False otherwise
461 The retry and block parameters are kind of equivalent unless you
462 consider the possibility of sending a signal to the process to break
463 out - at which point you want block=True rather than retry=True.
464 """
465 dirname = os.path.dirname(name)
466 mkdirhier(dirname)
467
468 if not os.access(dirname, os.W_OK):
469 logger.error("Unable to acquire lock '%s', directory is not writable",
470 name)
471 sys.exit(1)
472
473 op = fcntl.LOCK_EX
474 if shared:
475 op = fcntl.LOCK_SH
476 if not retry and not block:
477 op = op | fcntl.LOCK_NB
478
479 while True:
480 # If we leave the lockfiles lying around there is no problem
481 # but we should clean up after ourselves. This gives potential
482 # for races though. To work around this, when we acquire the lock
483 # we check the file we locked was still the lock file on disk.
484 # by comparing inode numbers. If they don't match or the lockfile
485 # no longer exists, we start again.
486
487 # This implementation is unfair since the last person to request the
488 # lock is the most likely to win it.
489
490 try:
491 lf = open(name, 'a+')
492 fileno = lf.fileno()
493 fcntl.flock(fileno, op)
494 statinfo = os.fstat(fileno)
495 if os.path.exists(lf.name):
496 statinfo2 = os.stat(lf.name)
497 if statinfo.st_ino == statinfo2.st_ino:
498 return lf
499 lf.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800500 except OSError as e:
501 if e.errno == errno.EACCES:
502 logger.error("Unable to acquire lock '%s', %s",
503 e.strerror, name)
504 sys.exit(1)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500505 try:
506 lf.close()
507 except Exception:
508 pass
509 pass
510 if not retry:
511 return None
512
513def unlockfile(lf):
514 """
515 Unlock a file locked using lockfile()
516 """
517 try:
518 # If we had a shared lock, we need to promote to exclusive before
519 # removing the lockfile. Attempt this, ignore failures.
520 fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
521 os.unlink(lf.name)
522 except (IOError, OSError):
523 pass
524 fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
525 lf.close()
526
527def md5_file(filename):
528 """
529 Return the hex string representation of the MD5 checksum of filename.
530 """
Brad Bishop220d5532018-08-14 00:59:39 +0100531 import hashlib, mmap
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500532
533 with open(filename, "rb") as f:
Brad Bishop220d5532018-08-14 00:59:39 +0100534 m = hashlib.md5()
535 try:
536 with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
537 for chunk in iter(lambda: mm.read(8192), b''):
538 m.update(chunk)
539 except ValueError:
540 # You can't mmap() an empty file so silence this exception
541 pass
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500542 return m.hexdigest()
543
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
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500550
551 s = hashlib.sha256()
552 with open(filename, "rb") as f:
553 for line in f:
554 s.update(line)
555 return s.hexdigest()
556
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500557def sha1_file(filename):
558 """
559 Return the hex string representation of the SHA1 checksum of the filename
560 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500561 import hashlib
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500562
563 s = hashlib.sha1()
564 with open(filename, "rb") as f:
565 for line in f:
566 s.update(line)
567 return s.hexdigest()
568
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500569def preserved_envvars_exported():
570 """Variables which are taken from the environment and placed in and exported
571 from the metadata"""
572 return [
573 'BB_TASKHASH',
574 'HOME',
575 'LOGNAME',
576 'PATH',
577 'PWD',
578 'SHELL',
579 'TERM',
580 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600581 'LC_ALL',
582 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500583 ]
584
585def preserved_envvars():
586 """Variables which are taken from the environment and placed in the metadata"""
587 v = [
588 'BBPATH',
589 'BB_PRESERVE_ENV',
590 'BB_ENV_WHITELIST',
591 'BB_ENV_EXTRAWHITE',
592 ]
593 return v + preserved_envvars_exported()
594
595def filter_environment(good_vars):
596 """
597 Create a pristine environment for bitbake. This will remove variables that
598 are not known and may influence the build in a negative way.
599 """
600
601 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600602 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500603 if key in good_vars:
604 continue
605
606 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500607 del os.environ[key]
608
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600609 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
610 # access methods will use ascii. You can't change that mode once the interpreter is
611 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
612 # distros support that and we need to set something.
613 os.environ["LC_ALL"] = "en_US.UTF-8"
614
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500615 if removed_vars:
616 logger.debug(1, "Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
617
618 return removed_vars
619
620def approved_variables():
621 """
622 Determine and return the list of whitelisted variables which are approved
623 to remain in the environment.
624 """
625 if 'BB_PRESERVE_ENV' in os.environ:
626 return os.environ.keys()
627 approved = []
628 if 'BB_ENV_WHITELIST' in os.environ:
629 approved = os.environ['BB_ENV_WHITELIST'].split()
630 approved.extend(['BB_ENV_WHITELIST'])
631 else:
632 approved = preserved_envvars()
633 if 'BB_ENV_EXTRAWHITE' in os.environ:
634 approved.extend(os.environ['BB_ENV_EXTRAWHITE'].split())
635 if 'BB_ENV_EXTRAWHITE' not in approved:
636 approved.extend(['BB_ENV_EXTRAWHITE'])
637 return approved
638
639def clean_environment():
640 """
641 Clean up any spurious environment variables. This will remove any
642 variables the user hasn't chosen to preserve.
643 """
644 if 'BB_PRESERVE_ENV' not in os.environ:
645 good_vars = approved_variables()
646 return filter_environment(good_vars)
647
648 return {}
649
650def empty_environment():
651 """
652 Remove all variables from the environment.
653 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600654 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500655 os.unsetenv(s)
656 del os.environ[s]
657
658def build_environment(d):
659 """
660 Build an environment from all exported variables.
661 """
662 import bb.data
663 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500664 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500665 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500666 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667
668def _check_unsafe_delete_path(path):
669 """
670 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
671 the caller should raise an exception with an appropriate message.
672 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
673 with potentially disastrous results.
674 """
675 extra = ''
676 # HOME might not be /home/something, so in case we can get it, check against it
677 homedir = os.environ.get('HOME', '')
678 if homedir:
679 extra = '|%s' % homedir
680 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
681 return True
682 return False
683
684def remove(path, recurse=False):
685 """Equivalent to rm -f or rm -rf"""
686 if not path:
687 return
688 if recurse:
689 for name in glob.glob(path):
690 if _check_unsafe_delete_path(path):
691 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % path)
692 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500693 subprocess.check_call(['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500694 return
695 for name in glob.glob(path):
696 try:
697 os.unlink(name)
698 except OSError as exc:
699 if exc.errno != errno.ENOENT:
700 raise
701
702def prunedir(topdir):
703 # Delete everything reachable from the directory named in 'topdir'.
704 # CAUTION: This is dangerous!
705 if _check_unsafe_delete_path(topdir):
706 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
707 for root, dirs, files in os.walk(topdir, topdown = False):
708 for name in files:
709 os.remove(os.path.join(root, name))
710 for name in dirs:
711 if os.path.islink(os.path.join(root, name)):
712 os.remove(os.path.join(root, name))
713 else:
714 os.rmdir(os.path.join(root, name))
715 os.rmdir(topdir)
716
717#
718# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
719# but thats possibly insane and suffixes is probably going to be small
720#
721def prune_suffix(var, suffixes, d):
722 # See if var ends with any of the suffixes listed and
723 # remove it if found
724 for suffix in suffixes:
725 if var.endswith(suffix):
726 return var.replace(suffix, "")
727 return var
728
729def mkdirhier(directory):
730 """Create a directory like 'mkdir -p', but does not complain if
731 directory already exists like os.makedirs
732 """
733
734 try:
735 os.makedirs(directory)
736 except OSError as e:
737 if e.errno != errno.EEXIST:
738 raise e
739
740def movefile(src, dest, newmtime = None, sstat = None):
741 """Moves a file from src to dest, preserving all permissions and
742 attributes; mtime will be preserved even when moving across
743 filesystems. Returns true on success and false on failure. Move is
744 atomic.
745 """
746
747 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
748 try:
749 if not sstat:
750 sstat = os.lstat(src)
751 except Exception as e:
752 print("movefile: Stating source file failed...", e)
753 return None
754
755 destexists = 1
756 try:
757 dstat = os.lstat(dest)
758 except:
759 dstat = os.lstat(os.path.dirname(dest))
760 destexists = 0
761
762 if destexists:
763 if stat.S_ISLNK(dstat[stat.ST_MODE]):
764 try:
765 os.unlink(dest)
766 destexists = 0
767 except Exception as e:
768 pass
769
770 if stat.S_ISLNK(sstat[stat.ST_MODE]):
771 try:
772 target = os.readlink(src)
773 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
774 os.unlink(dest)
775 os.symlink(target, dest)
776 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
777 os.unlink(src)
778 return os.lstat(dest)
779 except Exception as e:
780 print("movefile: failed to properly create symlink:", dest, "->", target, e)
781 return None
782
783 renamefailed = 1
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500784 # os.rename needs to know the dest path ending with file name
785 # so append the file name to a path only if it's a dir specified
786 srcfname = os.path.basename(src)
787 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
788 else dest
789
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500790 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
791 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500792 os.rename(src, destpath)
793 renamefailed = 0
794 except Exception as e:
795 if e[0] != errno.EXDEV:
796 # Some random error.
797 print("movefile: Failed to move", src, "to", dest, e)
798 return None
799 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
800
801 if renamefailed:
802 didcopy = 0
803 if stat.S_ISREG(sstat[stat.ST_MODE]):
804 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500805 shutil.copyfile(src, destpath + "#new")
806 os.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500807 didcopy = 1
808 except Exception as e:
809 print('movefile: copy', src, '->', dest, 'failed.', e)
810 return None
811 else:
812 #we don't yet handle special, so we need to fall back to /bin/mv
813 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
814 if a[0] != 0:
815 print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
816 return None # failure
817 try:
818 if didcopy:
Brad Bishop316dfdd2018-06-25 12:45:53 -0400819 os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID])
820 os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500821 os.unlink(src)
822 except Exception as e:
823 print("movefile: Failed to chown/chmod/unlink", dest, e)
824 return None
825
826 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500827 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500828 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500829 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500830 newmtime = sstat[stat.ST_MTIME]
831 return newmtime
832
833def copyfile(src, dest, newmtime = None, sstat = None):
834 """
835 Copies a file from src to dest, preserving all permissions and
836 attributes; mtime will be preserved even when moving across
837 filesystems. Returns true on success and false on failure.
838 """
839 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
840 try:
841 if not sstat:
842 sstat = os.lstat(src)
843 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600844 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500845 return False
846
847 destexists = 1
848 try:
849 dstat = os.lstat(dest)
850 except:
851 dstat = os.lstat(os.path.dirname(dest))
852 destexists = 0
853
854 if destexists:
855 if stat.S_ISLNK(dstat[stat.ST_MODE]):
856 try:
857 os.unlink(dest)
858 destexists = 0
859 except Exception as e:
860 pass
861
862 if stat.S_ISLNK(sstat[stat.ST_MODE]):
863 try:
864 target = os.readlink(src)
865 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
866 os.unlink(dest)
867 os.symlink(target, dest)
868 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
869 return os.lstat(dest)
870 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600871 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500872 return False
873
874 if stat.S_ISREG(sstat[stat.ST_MODE]):
875 try:
876 srcchown = False
877 if not os.access(src, os.R_OK):
878 # Make sure we can read it
879 srcchown = True
880 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
881
882 # For safety copy then move it over.
883 shutil.copyfile(src, dest + "#new")
884 os.rename(dest + "#new", dest)
885 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600886 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500887 return False
888 finally:
889 if srcchown:
890 os.chmod(src, sstat[stat.ST_MODE])
891 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
892
893 else:
894 #we don't yet handle special, so we need to fall back to /bin/mv
895 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
896 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600897 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500898 return False # failure
899 try:
900 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
901 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
902 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600903 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500904 return False
905
906 if newmtime:
907 os.utime(dest, (newmtime, newmtime))
908 else:
909 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
910 newmtime = sstat[stat.ST_MTIME]
911 return newmtime
912
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800913def break_hardlinks(src, sstat = None):
914 """
915 Ensures src is the only hardlink to this file. Other hardlinks,
916 if any, are not affected (other than in their st_nlink value, of
917 course). Returns true on success and false on failure.
918
919 """
920 try:
921 if not sstat:
922 sstat = os.lstat(src)
923 except Exception as e:
924 logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e))
925 return False
926 if sstat[stat.ST_NLINK] == 1:
927 return True
928 return copyfile(src, src, sstat=sstat)
929
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500930def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500931 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500932 Locate `item` in the list of paths `path` (colon separated string like $PATH).
933 If `direction` is non-zero then the list is reversed.
934 If `history` is True then the list of candidates also returned as result,history.
935 If `executable` is True then the candidate has to be an executable file,
936 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500937 """
938
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500939 if executable:
940 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
941 else:
942 is_candidate = lambda p: os.path.exists(p)
943
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500944 hist = []
945 paths = (path or "").split(':')
946 if direction != 0:
947 paths.reverse()
948
949 for p in paths:
950 next = os.path.join(p, item)
951 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500952 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500953 if not os.path.isabs(next):
954 next = os.path.abspath(next)
955 if history:
956 return next, hist
957 return next
958
959 if history:
960 return "", hist
961 return ""
962
963def to_boolean(string, default=None):
964 if not string:
965 return default
966
967 normalized = string.lower()
968 if normalized in ("y", "yes", "1", "true"):
969 return True
970 elif normalized in ("n", "no", "0", "false"):
971 return False
972 else:
973 raise ValueError("Invalid value for to_boolean: %s" % string)
974
975def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500976 """Check if a variable contains all the values specified.
977
978 Arguments:
979
980 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500981 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500982
983 checkvalues -- if this is a string it is split on whitespace into a set(),
984 otherwise coerced directly into a set().
985
986 truevalue -- the value to return if checkvalues is a subset of variable.
987
988 falsevalue -- the value to return if variable is empty or if checkvalues is
989 not a subset of variable.
990
991 d -- the data store.
992 """
993
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500994 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500995 if not val:
996 return falsevalue
997 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600998 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500999 checkvalues = set(checkvalues.split())
1000 else:
1001 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001002 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001003 return truevalue
1004 return falsevalue
1005
1006def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001007 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001008 if not val:
1009 return falsevalue
1010 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001011 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001012 checkvalues = set(checkvalues.split())
1013 else:
1014 checkvalues = set(checkvalues)
1015 if checkvalues & val:
1016 return truevalue
1017 return falsevalue
1018
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001019def filter(variable, checkvalues, d):
1020 """Return all words in the variable that are present in the checkvalues.
1021
1022 Arguments:
1023
1024 variable -- the variable name. This will be fetched and expanded (using
1025 d.getVar(variable)) and then split into a set().
1026
1027 checkvalues -- if this is a string it is split on whitespace into a set(),
1028 otherwise coerced directly into a set().
1029
1030 d -- the data store.
1031 """
1032
1033 val = d.getVar(variable)
1034 if not val:
1035 return ''
1036 val = set(val.split())
1037 if isinstance(checkvalues, str):
1038 checkvalues = set(checkvalues.split())
1039 else:
1040 checkvalues = set(checkvalues)
1041 return ' '.join(sorted(checkvalues & val))
1042
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001043def cpu_count():
1044 return multiprocessing.cpu_count()
1045
1046def nonblockingfd(fd):
1047 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1048
1049def process_profilelog(fn, pout = None):
1050 # Either call with a list of filenames and set pout or a filename and optionally pout.
1051 if not pout:
1052 pout = fn + '.processed'
1053 pout = open(pout, 'w')
1054
1055 import pstats
1056 if isinstance(fn, list):
1057 p = pstats.Stats(*fn, stream=pout)
1058 else:
1059 p = pstats.Stats(fn, stream=pout)
1060 p.sort_stats('time')
1061 p.print_stats()
1062 p.print_callers()
1063 p.sort_stats('cumulative')
1064 p.print_stats()
1065
1066 pout.flush()
1067 pout.close()
1068
1069#
1070# Was present to work around multiprocessing pool bugs in python < 2.7.3
1071#
1072def multiprocessingpool(*args, **kwargs):
1073
1074 import multiprocessing.pool
1075 #import multiprocessing.util
1076 #multiprocessing.util.log_to_stderr(10)
1077 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1078 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1079 def wrapper(func):
1080 def wrap(self, timeout=None):
1081 return func(self, timeout=timeout if timeout is not None else 1e100)
1082 return wrap
1083 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1084
1085 return multiprocessing.Pool(*args, **kwargs)
1086
1087def exec_flat_python_func(func, *args, **kwargs):
1088 """Execute a flat python function (defined with def funcname(args):...)"""
1089 # Prepare a small piece of python code which calls the requested function
1090 # To do this we need to prepare two things - a set of variables we can use to pass
1091 # the values of arguments into the calling function, and the list of arguments for
1092 # the function being called
1093 context = {}
1094 funcargs = []
1095 # Handle unnamed arguments
1096 aidx = 1
1097 for arg in args:
1098 argname = 'arg_%s' % aidx
1099 context[argname] = arg
1100 funcargs.append(argname)
1101 aidx += 1
1102 # Handle keyword arguments
1103 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001104 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001105 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1106 comp = bb.utils.better_compile(code, '<string>', '<string>')
1107 bb.utils.better_exec(comp, context, code, '<string>')
1108 return context['retval']
1109
1110def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1111 """Edit lines from a recipe or config file and modify one or more
1112 specified variable values set in the file using a specified callback
1113 function. Lines are expected to have trailing newlines.
1114 Parameters:
1115 meta_lines: lines from the file; can be a list or an iterable
1116 (e.g. file pointer)
1117 variables: a list of variable names to look for. Functions
1118 may also be specified, but must be specified with '()' at
1119 the end of the name. Note that the function doesn't have
1120 any intrinsic understanding of _append, _prepend, _remove,
1121 or overrides, so these are considered as part of the name.
1122 These values go into a regular expression, so regular
1123 expression syntax is allowed.
1124 varfunc: callback function called for every variable matching
1125 one of the entries in the variables parameter. The function
1126 should take four arguments:
1127 varname: name of variable matched
1128 origvalue: current value in file
1129 op: the operator (e.g. '+=')
1130 newlines: list of lines up to this point. You can use
1131 this to prepend lines before this variable setting
1132 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001133 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001134 newvalue: new value to substitute in, or None to drop
1135 the variable setting entirely. (If the removal
1136 results in two consecutive blank lines, one of the
1137 blank lines will also be dropped).
1138 newop: the operator to use - if you specify None here,
1139 the original operation will be used.
1140 indent: number of spaces to indent multi-line entries,
1141 or -1 to indent up to the level of the assignment
1142 and opening quote, or a string to use as the indent.
1143 minbreak: True to allow the first element of a
1144 multi-line value to continue on the same line as
1145 the assignment, False to indent before the first
1146 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001147 To clarify, if you wish not to change the value, then you
1148 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001149 match_overrides: True to match items with _overrides on the end,
1150 False otherwise
1151 Returns a tuple:
1152 updated:
1153 True if changes were made, False otherwise.
1154 newlines:
1155 Lines after processing
1156 """
1157
1158 var_res = {}
1159 if match_overrides:
1160 override_re = '(_[a-zA-Z0-9-_$(){}]+)?'
1161 else:
1162 override_re = ''
1163 for var in variables:
1164 if var.endswith('()'):
1165 var_res[var] = re.compile('^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
1166 else:
1167 var_res[var] = re.compile('^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
1168
1169 updated = False
1170 varset_start = ''
1171 varlines = []
1172 newlines = []
1173 in_var = None
1174 full_value = ''
1175 var_end = ''
1176
1177 def handle_var_end():
1178 prerun_newlines = newlines[:]
1179 op = varset_start[len(in_var):].strip()
1180 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1181 changed = (prerun_newlines != newlines)
1182
1183 if newvalue is None:
1184 # Drop the value
1185 return True
1186 elif newvalue != full_value or (newop not in [None, op]):
1187 if newop not in [None, op]:
1188 # Callback changed the operator
1189 varset_new = "%s %s" % (in_var, newop)
1190 else:
1191 varset_new = varset_start
1192
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001193 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001194 if indent == -1:
1195 indentspc = ' ' * (len(varset_new) + 2)
1196 else:
1197 indentspc = ' ' * indent
1198 else:
1199 indentspc = indent
1200 if in_var.endswith('()'):
1201 # A function definition
1202 if isinstance(newvalue, list):
1203 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1204 else:
1205 if not newvalue.startswith('\n'):
1206 newvalue = '\n' + newvalue
1207 if not newvalue.endswith('\n'):
1208 newvalue = newvalue + '\n'
1209 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1210 else:
1211 # Normal variable
1212 if isinstance(newvalue, list):
1213 if not newvalue:
1214 # Empty list -> empty string
1215 newlines.append('%s ""\n' % varset_new)
1216 elif minbreak:
1217 # First item on first line
1218 if len(newvalue) == 1:
1219 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1220 else:
1221 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1222 for item in newvalue[1:]:
1223 newlines.append('%s%s \\\n' % (indentspc, item))
1224 newlines.append('%s"\n' % indentspc)
1225 else:
1226 # No item on first line
1227 newlines.append('%s " \\\n' % varset_new)
1228 for item in newvalue:
1229 newlines.append('%s%s \\\n' % (indentspc, item))
1230 newlines.append('%s"\n' % indentspc)
1231 else:
1232 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1233 return True
1234 else:
1235 # Put the old lines back where they were
1236 newlines.extend(varlines)
1237 # If newlines was touched by the function, we'll need to return True
1238 return changed
1239
1240 checkspc = False
1241
1242 for line in meta_lines:
1243 if in_var:
1244 value = line.rstrip()
1245 varlines.append(line)
1246 if in_var.endswith('()'):
1247 full_value += '\n' + value
1248 else:
1249 full_value += value[:-1]
1250 if value.endswith(var_end):
1251 if in_var.endswith('()'):
1252 if full_value.count('{') - full_value.count('}') >= 0:
1253 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001254 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001255 if handle_var_end():
1256 updated = True
1257 checkspc = True
1258 in_var = None
1259 else:
1260 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001261 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001262 res = var_re.match(line)
1263 if res:
1264 isfunc = varname.endswith('()')
1265 if isfunc:
1266 splitvalue = line.split('{', 1)
1267 var_end = '}'
1268 else:
1269 var_end = res.groups()[-1]
1270 splitvalue = line.split(var_end, 1)
1271 varset_start = splitvalue[0].rstrip()
1272 value = splitvalue[1].rstrip()
1273 if not isfunc and value.endswith('\\'):
1274 value = value[:-1]
1275 full_value = value
1276 varlines = [line]
1277 in_var = res.group(1)
1278 if isfunc:
1279 in_var += '()'
1280 if value.endswith(var_end):
1281 full_value = full_value[:-1]
1282 if handle_var_end():
1283 updated = True
1284 checkspc = True
1285 in_var = None
1286 skip = True
1287 break
1288 if not skip:
1289 if checkspc:
1290 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001291 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001292 # Squash blank line if there are two consecutive blanks after a removal
1293 continue
1294 newlines.append(line)
1295 return (updated, newlines)
1296
1297
1298def edit_metadata_file(meta_file, variables, varfunc):
1299 """Edit a recipe or config file and modify one or more specified
1300 variable values set in the file using a specified callback function.
1301 The file is only written to if the value(s) actually change.
1302 This is basically the file version of edit_metadata(), see that
1303 function's description for parameter/usage information.
1304 Returns True if the file was written to, False otherwise.
1305 """
1306 with open(meta_file, 'r') as f:
1307 (updated, newlines) = edit_metadata(f, variables, varfunc)
1308 if updated:
1309 with open(meta_file, 'w') as f:
1310 f.writelines(newlines)
1311 return updated
1312
1313
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001314def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001315 """Edit bblayers.conf, adding and/or removing layers
1316 Parameters:
1317 bblayers_conf: path to bblayers.conf file to edit
1318 add: layer path (or list of layer paths) to add; None or empty
1319 list to add nothing
1320 remove: layer path (or list of layer paths) to remove; None or
1321 empty list to remove nothing
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001322 edit_cb: optional callback function that will be called after
1323 processing adds/removes once per existing entry.
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001324 Returns a tuple:
1325 notadded: list of layers specified to be added but weren't
1326 (because they were already in the list)
1327 notremoved: list of layers that were specified to be removed
1328 but weren't (because they weren't in the list)
1329 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001330
1331 import fnmatch
1332
1333 def remove_trailing_sep(pth):
1334 if pth and pth[-1] == os.sep:
1335 pth = pth[:-1]
1336 return pth
1337
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001338 approved = bb.utils.approved_variables()
1339 def canonicalise_path(pth):
1340 pth = remove_trailing_sep(pth)
1341 if 'HOME' in approved and '~' in pth:
1342 pth = os.path.expanduser(pth)
1343 return pth
1344
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001345 def layerlist_param(value):
1346 if not value:
1347 return []
1348 elif isinstance(value, list):
1349 return [remove_trailing_sep(x) for x in value]
1350 else:
1351 return [remove_trailing_sep(value)]
1352
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001353 addlayers = layerlist_param(add)
1354 removelayers = layerlist_param(remove)
1355
1356 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1357 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001358 removed = []
1359 plusequals = False
1360 orig_bblayers = []
1361
1362 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1363 bblayercalls.append(op)
1364 if op == '=':
1365 del orig_bblayers[:]
1366 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1367 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001368
1369 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001370 updated = False
1371 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1372 if removelayers:
1373 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001374 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001375 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001376 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001377 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001378 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001379 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001380 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001381 for addlayer in addlayers:
1382 if addlayer not in bblayers:
1383 updated = True
1384 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001385 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001386
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001387 if edit_cb:
1388 newlist = []
1389 for layer in bblayers:
1390 res = edit_cb(layer, canonicalise_path(layer))
1391 if res != layer:
1392 newlist.append(res)
1393 updated = True
1394 else:
1395 newlist.append(layer)
1396 bblayers = newlist
1397
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001398 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001399 if op == '+=' and not bblayers:
1400 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001401 return (bblayers, None, 2, False)
1402 else:
1403 return (origvalue, None, 2, False)
1404
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001405 with open(bblayers_conf, 'r') as f:
1406 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001407
1408 if not bblayercalls:
1409 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1410
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001411 # Try to do the "smart" thing depending on how the user has laid out
1412 # their bblayers.conf file
1413 if bblayercalls.count('+=') > 1:
1414 plusequals = True
1415
1416 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1417 notadded = []
1418 for layer in addlayers:
1419 layer_canon = canonicalise_path(layer)
1420 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1421 notadded.append(layer)
1422 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1423 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1424
1425 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1426 if addlayers:
1427 # Still need to add these
1428 for addlayer in addlayers:
1429 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1430 updated = True
1431
1432 if updated:
1433 with open(bblayers_conf, 'w') as f:
1434 f.writelines(newlines)
1435
1436 notremoved = list(set(removelayers) - set(removed))
1437
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001438 return (notadded, notremoved)
1439
1440
1441def get_file_layer(filename, d):
1442 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001443 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001444 collection_res = {}
1445 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001446 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001447
1448 def path_to_layer(path):
1449 # Use longest path so we handle nested layers
1450 matchlen = 0
1451 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001452 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001453 if len(regex) > matchlen and re.match(regex, path):
1454 matchlen = len(regex)
1455 match = collection
1456 return match
1457
1458 result = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001459 bbfiles = (d.getVar('BBFILES') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001460 bbfilesmatch = False
1461 for bbfilesentry in bbfiles:
1462 if fnmatch.fnmatch(filename, bbfilesentry):
1463 bbfilesmatch = True
1464 result = path_to_layer(bbfilesentry)
1465
1466 if not bbfilesmatch:
1467 # Probably a bbclass
1468 result = path_to_layer(filename)
1469
1470 return result
1471
1472
1473# Constant taken from http://linux.die.net/include/linux/prctl.h
1474PR_SET_PDEATHSIG = 1
1475
1476class PrCtlError(Exception):
1477 pass
1478
1479def signal_on_parent_exit(signame):
1480 """
1481 Trigger signame to be sent when the parent process dies
1482 """
1483 signum = getattr(signal, signame)
1484 # http://linux.die.net/man/2/prctl
1485 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1486 if result != 0:
1487 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001488
1489#
1490# Manually call the ioprio syscall. We could depend on other libs like psutil
1491# however this gets us enough of what we need to bitbake for now without the
1492# dependency
1493#
1494_unamearch = os.uname()[4]
1495IOPRIO_WHO_PROCESS = 1
1496IOPRIO_CLASS_SHIFT = 13
1497
1498def ioprio_set(who, cls, value):
1499 NR_ioprio_set = None
1500 if _unamearch == "x86_64":
1501 NR_ioprio_set = 251
1502 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1503 NR_ioprio_set = 289
1504
1505 if NR_ioprio_set:
1506 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1507 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1508 if rc != 0:
1509 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1510 else:
1511 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001512
1513def set_process_name(name):
1514 from ctypes import cdll, byref, create_string_buffer
1515 # This is nice to have for debugging, not essential
1516 try:
1517 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001518 buf = create_string_buffer(bytes(name, 'utf-8'))
1519 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001520 except:
1521 pass
1522
1523# export common proxies variables from datastore to environment
1524def export_proxies(d):
1525 import os
1526
1527 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001528 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1529 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001530 exported = False
1531
1532 for v in variables:
1533 if v in os.environ.keys():
1534 exported = True
1535 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001536 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001537 if v_proxy is not None:
1538 os.environ[v] = v_proxy
1539 exported = True
1540
1541 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001542
1543
1544def load_plugins(logger, plugins, pluginpath):
1545 def load_plugin(name):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001546 logger.debug(1, 'Loading plugin %s' % name)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001547 fp, pathname, description = imp.find_module(name, [pluginpath])
1548 try:
1549 return imp.load_module(name, fp, pathname, description)
1550 finally:
1551 if fp:
1552 fp.close()
1553
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001554 logger.debug(1, 'Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001555
1556 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1557 for ext in python_extensions)
1558 files = itertools.chain.from_iterable(expanded)
1559 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1560 for name in names:
1561 if name != '__init__':
1562 plugin = load_plugin(name)
1563 if hasattr(plugin, 'plugin_init'):
1564 obj = plugin.plugin_init(plugins)
1565 plugins.append(obj or plugin)
1566 else:
1567 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001568
1569
1570class LogCatcher(logging.Handler):
1571 """Logging handler for collecting logged messages so you can check them later"""
1572 def __init__(self):
1573 self.messages = []
1574 logging.Handler.__init__(self, logging.WARNING)
1575 def emit(self, record):
1576 self.messages.append(bb.build.logformatter.format(record))
1577 def contains(self, message):
1578 return (message in self.messages)