blob: 378e699e0c81e8851670c99180c747347ca2caea [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()
500 except Exception:
501 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 Bishop6e60e8b2018-02-01 10:27:11 -0500527 import hashlib
528 m = hashlib.md5()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500529
530 with open(filename, "rb") as f:
531 for line in f:
532 m.update(line)
533 return m.hexdigest()
534
535def sha256_file(filename):
536 """
537 Return the hex string representation of the 256-bit SHA checksum of
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500538 filename.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500539 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500540 import hashlib
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500541
542 s = hashlib.sha256()
543 with open(filename, "rb") as f:
544 for line in f:
545 s.update(line)
546 return s.hexdigest()
547
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500548def sha1_file(filename):
549 """
550 Return the hex string representation of the SHA1 checksum of the filename
551 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500552 import hashlib
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500553
554 s = hashlib.sha1()
555 with open(filename, "rb") as f:
556 for line in f:
557 s.update(line)
558 return s.hexdigest()
559
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500560def preserved_envvars_exported():
561 """Variables which are taken from the environment and placed in and exported
562 from the metadata"""
563 return [
564 'BB_TASKHASH',
565 'HOME',
566 'LOGNAME',
567 'PATH',
568 'PWD',
569 'SHELL',
570 'TERM',
571 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600572 'LC_ALL',
573 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500574 ]
575
576def preserved_envvars():
577 """Variables which are taken from the environment and placed in the metadata"""
578 v = [
579 'BBPATH',
580 'BB_PRESERVE_ENV',
581 'BB_ENV_WHITELIST',
582 'BB_ENV_EXTRAWHITE',
583 ]
584 return v + preserved_envvars_exported()
585
586def filter_environment(good_vars):
587 """
588 Create a pristine environment for bitbake. This will remove variables that
589 are not known and may influence the build in a negative way.
590 """
591
592 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600593 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500594 if key in good_vars:
595 continue
596
597 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500598 del os.environ[key]
599
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600600 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
601 # access methods will use ascii. You can't change that mode once the interpreter is
602 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
603 # distros support that and we need to set something.
604 os.environ["LC_ALL"] = "en_US.UTF-8"
605
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500606 if removed_vars:
607 logger.debug(1, "Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
608
609 return removed_vars
610
611def approved_variables():
612 """
613 Determine and return the list of whitelisted variables which are approved
614 to remain in the environment.
615 """
616 if 'BB_PRESERVE_ENV' in os.environ:
617 return os.environ.keys()
618 approved = []
619 if 'BB_ENV_WHITELIST' in os.environ:
620 approved = os.environ['BB_ENV_WHITELIST'].split()
621 approved.extend(['BB_ENV_WHITELIST'])
622 else:
623 approved = preserved_envvars()
624 if 'BB_ENV_EXTRAWHITE' in os.environ:
625 approved.extend(os.environ['BB_ENV_EXTRAWHITE'].split())
626 if 'BB_ENV_EXTRAWHITE' not in approved:
627 approved.extend(['BB_ENV_EXTRAWHITE'])
628 return approved
629
630def clean_environment():
631 """
632 Clean up any spurious environment variables. This will remove any
633 variables the user hasn't chosen to preserve.
634 """
635 if 'BB_PRESERVE_ENV' not in os.environ:
636 good_vars = approved_variables()
637 return filter_environment(good_vars)
638
639 return {}
640
641def empty_environment():
642 """
643 Remove all variables from the environment.
644 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600645 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500646 os.unsetenv(s)
647 del os.environ[s]
648
649def build_environment(d):
650 """
651 Build an environment from all exported variables.
652 """
653 import bb.data
654 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500655 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500656 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500657 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500658
659def _check_unsafe_delete_path(path):
660 """
661 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
662 the caller should raise an exception with an appropriate message.
663 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
664 with potentially disastrous results.
665 """
666 extra = ''
667 # HOME might not be /home/something, so in case we can get it, check against it
668 homedir = os.environ.get('HOME', '')
669 if homedir:
670 extra = '|%s' % homedir
671 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
672 return True
673 return False
674
675def remove(path, recurse=False):
676 """Equivalent to rm -f or rm -rf"""
677 if not path:
678 return
679 if recurse:
680 for name in glob.glob(path):
681 if _check_unsafe_delete_path(path):
682 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % path)
683 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500684 subprocess.check_call(['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500685 return
686 for name in glob.glob(path):
687 try:
688 os.unlink(name)
689 except OSError as exc:
690 if exc.errno != errno.ENOENT:
691 raise
692
693def prunedir(topdir):
694 # Delete everything reachable from the directory named in 'topdir'.
695 # CAUTION: This is dangerous!
696 if _check_unsafe_delete_path(topdir):
697 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
698 for root, dirs, files in os.walk(topdir, topdown = False):
699 for name in files:
700 os.remove(os.path.join(root, name))
701 for name in dirs:
702 if os.path.islink(os.path.join(root, name)):
703 os.remove(os.path.join(root, name))
704 else:
705 os.rmdir(os.path.join(root, name))
706 os.rmdir(topdir)
707
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:
716 if var.endswith(suffix):
717 return var.replace(suffix, "")
718 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:
728 if e.errno != errno.EEXIST:
729 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:
786 if e[0] != errno.EXDEV:
787 # 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 Bishop6e60e8b2018-02-01 10:27:11 -0500904def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500905 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500906 Locate `item` in the list of paths `path` (colon separated string like $PATH).
907 If `direction` is non-zero then the list is reversed.
908 If `history` is True then the list of candidates also returned as result,history.
909 If `executable` is True then the candidate has to be an executable file,
910 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500911 """
912
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500913 if executable:
914 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
915 else:
916 is_candidate = lambda p: os.path.exists(p)
917
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500918 hist = []
919 paths = (path or "").split(':')
920 if direction != 0:
921 paths.reverse()
922
923 for p in paths:
924 next = os.path.join(p, item)
925 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500926 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500927 if not os.path.isabs(next):
928 next = os.path.abspath(next)
929 if history:
930 return next, hist
931 return next
932
933 if history:
934 return "", hist
935 return ""
936
937def to_boolean(string, default=None):
938 if not string:
939 return default
940
941 normalized = string.lower()
942 if normalized in ("y", "yes", "1", "true"):
943 return True
944 elif normalized in ("n", "no", "0", "false"):
945 return False
946 else:
947 raise ValueError("Invalid value for to_boolean: %s" % string)
948
949def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500950 """Check if a variable contains all the values specified.
951
952 Arguments:
953
954 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500955 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500956
957 checkvalues -- if this is a string it is split on whitespace into a set(),
958 otherwise coerced directly into a set().
959
960 truevalue -- the value to return if checkvalues is a subset of variable.
961
962 falsevalue -- the value to return if variable is empty or if checkvalues is
963 not a subset of variable.
964
965 d -- the data store.
966 """
967
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500968 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500969 if not val:
970 return falsevalue
971 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600972 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500973 checkvalues = set(checkvalues.split())
974 else:
975 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500976 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500977 return truevalue
978 return falsevalue
979
980def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500981 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500982 if not val:
983 return falsevalue
984 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600985 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500986 checkvalues = set(checkvalues.split())
987 else:
988 checkvalues = set(checkvalues)
989 if checkvalues & val:
990 return truevalue
991 return falsevalue
992
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500993def filter(variable, checkvalues, d):
994 """Return all words in the variable that are present in the checkvalues.
995
996 Arguments:
997
998 variable -- the variable name. This will be fetched and expanded (using
999 d.getVar(variable)) and then split into a set().
1000
1001 checkvalues -- if this is a string it is split on whitespace into a set(),
1002 otherwise coerced directly into a set().
1003
1004 d -- the data store.
1005 """
1006
1007 val = d.getVar(variable)
1008 if not val:
1009 return ''
1010 val = set(val.split())
1011 if isinstance(checkvalues, str):
1012 checkvalues = set(checkvalues.split())
1013 else:
1014 checkvalues = set(checkvalues)
1015 return ' '.join(sorted(checkvalues & val))
1016
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001017def cpu_count():
1018 return multiprocessing.cpu_count()
1019
1020def nonblockingfd(fd):
1021 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1022
1023def process_profilelog(fn, pout = None):
1024 # Either call with a list of filenames and set pout or a filename and optionally pout.
1025 if not pout:
1026 pout = fn + '.processed'
1027 pout = open(pout, 'w')
1028
1029 import pstats
1030 if isinstance(fn, list):
1031 p = pstats.Stats(*fn, stream=pout)
1032 else:
1033 p = pstats.Stats(fn, stream=pout)
1034 p.sort_stats('time')
1035 p.print_stats()
1036 p.print_callers()
1037 p.sort_stats('cumulative')
1038 p.print_stats()
1039
1040 pout.flush()
1041 pout.close()
1042
1043#
1044# Was present to work around multiprocessing pool bugs in python < 2.7.3
1045#
1046def multiprocessingpool(*args, **kwargs):
1047
1048 import multiprocessing.pool
1049 #import multiprocessing.util
1050 #multiprocessing.util.log_to_stderr(10)
1051 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1052 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1053 def wrapper(func):
1054 def wrap(self, timeout=None):
1055 return func(self, timeout=timeout if timeout is not None else 1e100)
1056 return wrap
1057 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1058
1059 return multiprocessing.Pool(*args, **kwargs)
1060
1061def exec_flat_python_func(func, *args, **kwargs):
1062 """Execute a flat python function (defined with def funcname(args):...)"""
1063 # Prepare a small piece of python code which calls the requested function
1064 # To do this we need to prepare two things - a set of variables we can use to pass
1065 # the values of arguments into the calling function, and the list of arguments for
1066 # the function being called
1067 context = {}
1068 funcargs = []
1069 # Handle unnamed arguments
1070 aidx = 1
1071 for arg in args:
1072 argname = 'arg_%s' % aidx
1073 context[argname] = arg
1074 funcargs.append(argname)
1075 aidx += 1
1076 # Handle keyword arguments
1077 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001078 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001079 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1080 comp = bb.utils.better_compile(code, '<string>', '<string>')
1081 bb.utils.better_exec(comp, context, code, '<string>')
1082 return context['retval']
1083
1084def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1085 """Edit lines from a recipe or config file and modify one or more
1086 specified variable values set in the file using a specified callback
1087 function. Lines are expected to have trailing newlines.
1088 Parameters:
1089 meta_lines: lines from the file; can be a list or an iterable
1090 (e.g. file pointer)
1091 variables: a list of variable names to look for. Functions
1092 may also be specified, but must be specified with '()' at
1093 the end of the name. Note that the function doesn't have
1094 any intrinsic understanding of _append, _prepend, _remove,
1095 or overrides, so these are considered as part of the name.
1096 These values go into a regular expression, so regular
1097 expression syntax is allowed.
1098 varfunc: callback function called for every variable matching
1099 one of the entries in the variables parameter. The function
1100 should take four arguments:
1101 varname: name of variable matched
1102 origvalue: current value in file
1103 op: the operator (e.g. '+=')
1104 newlines: list of lines up to this point. You can use
1105 this to prepend lines before this variable setting
1106 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001107 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001108 newvalue: new value to substitute in, or None to drop
1109 the variable setting entirely. (If the removal
1110 results in two consecutive blank lines, one of the
1111 blank lines will also be dropped).
1112 newop: the operator to use - if you specify None here,
1113 the original operation will be used.
1114 indent: number of spaces to indent multi-line entries,
1115 or -1 to indent up to the level of the assignment
1116 and opening quote, or a string to use as the indent.
1117 minbreak: True to allow the first element of a
1118 multi-line value to continue on the same line as
1119 the assignment, False to indent before the first
1120 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001121 To clarify, if you wish not to change the value, then you
1122 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001123 match_overrides: True to match items with _overrides on the end,
1124 False otherwise
1125 Returns a tuple:
1126 updated:
1127 True if changes were made, False otherwise.
1128 newlines:
1129 Lines after processing
1130 """
1131
1132 var_res = {}
1133 if match_overrides:
1134 override_re = '(_[a-zA-Z0-9-_$(){}]+)?'
1135 else:
1136 override_re = ''
1137 for var in variables:
1138 if var.endswith('()'):
1139 var_res[var] = re.compile('^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
1140 else:
1141 var_res[var] = re.compile('^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
1142
1143 updated = False
1144 varset_start = ''
1145 varlines = []
1146 newlines = []
1147 in_var = None
1148 full_value = ''
1149 var_end = ''
1150
1151 def handle_var_end():
1152 prerun_newlines = newlines[:]
1153 op = varset_start[len(in_var):].strip()
1154 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1155 changed = (prerun_newlines != newlines)
1156
1157 if newvalue is None:
1158 # Drop the value
1159 return True
1160 elif newvalue != full_value or (newop not in [None, op]):
1161 if newop not in [None, op]:
1162 # Callback changed the operator
1163 varset_new = "%s %s" % (in_var, newop)
1164 else:
1165 varset_new = varset_start
1166
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001167 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001168 if indent == -1:
1169 indentspc = ' ' * (len(varset_new) + 2)
1170 else:
1171 indentspc = ' ' * indent
1172 else:
1173 indentspc = indent
1174 if in_var.endswith('()'):
1175 # A function definition
1176 if isinstance(newvalue, list):
1177 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1178 else:
1179 if not newvalue.startswith('\n'):
1180 newvalue = '\n' + newvalue
1181 if not newvalue.endswith('\n'):
1182 newvalue = newvalue + '\n'
1183 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1184 else:
1185 # Normal variable
1186 if isinstance(newvalue, list):
1187 if not newvalue:
1188 # Empty list -> empty string
1189 newlines.append('%s ""\n' % varset_new)
1190 elif minbreak:
1191 # First item on first line
1192 if len(newvalue) == 1:
1193 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1194 else:
1195 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1196 for item in newvalue[1:]:
1197 newlines.append('%s%s \\\n' % (indentspc, item))
1198 newlines.append('%s"\n' % indentspc)
1199 else:
1200 # No item on first line
1201 newlines.append('%s " \\\n' % varset_new)
1202 for item in newvalue:
1203 newlines.append('%s%s \\\n' % (indentspc, item))
1204 newlines.append('%s"\n' % indentspc)
1205 else:
1206 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1207 return True
1208 else:
1209 # Put the old lines back where they were
1210 newlines.extend(varlines)
1211 # If newlines was touched by the function, we'll need to return True
1212 return changed
1213
1214 checkspc = False
1215
1216 for line in meta_lines:
1217 if in_var:
1218 value = line.rstrip()
1219 varlines.append(line)
1220 if in_var.endswith('()'):
1221 full_value += '\n' + value
1222 else:
1223 full_value += value[:-1]
1224 if value.endswith(var_end):
1225 if in_var.endswith('()'):
1226 if full_value.count('{') - full_value.count('}') >= 0:
1227 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001228 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001229 if handle_var_end():
1230 updated = True
1231 checkspc = True
1232 in_var = None
1233 else:
1234 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001235 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001236 res = var_re.match(line)
1237 if res:
1238 isfunc = varname.endswith('()')
1239 if isfunc:
1240 splitvalue = line.split('{', 1)
1241 var_end = '}'
1242 else:
1243 var_end = res.groups()[-1]
1244 splitvalue = line.split(var_end, 1)
1245 varset_start = splitvalue[0].rstrip()
1246 value = splitvalue[1].rstrip()
1247 if not isfunc and value.endswith('\\'):
1248 value = value[:-1]
1249 full_value = value
1250 varlines = [line]
1251 in_var = res.group(1)
1252 if isfunc:
1253 in_var += '()'
1254 if value.endswith(var_end):
1255 full_value = full_value[:-1]
1256 if handle_var_end():
1257 updated = True
1258 checkspc = True
1259 in_var = None
1260 skip = True
1261 break
1262 if not skip:
1263 if checkspc:
1264 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001265 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001266 # Squash blank line if there are two consecutive blanks after a removal
1267 continue
1268 newlines.append(line)
1269 return (updated, newlines)
1270
1271
1272def edit_metadata_file(meta_file, variables, varfunc):
1273 """Edit a recipe or config file and modify one or more specified
1274 variable values set in the file using a specified callback function.
1275 The file is only written to if the value(s) actually change.
1276 This is basically the file version of edit_metadata(), see that
1277 function's description for parameter/usage information.
1278 Returns True if the file was written to, False otherwise.
1279 """
1280 with open(meta_file, 'r') as f:
1281 (updated, newlines) = edit_metadata(f, variables, varfunc)
1282 if updated:
1283 with open(meta_file, 'w') as f:
1284 f.writelines(newlines)
1285 return updated
1286
1287
1288def edit_bblayers_conf(bblayers_conf, add, remove):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001289 """Edit bblayers.conf, adding and/or removing layers
1290 Parameters:
1291 bblayers_conf: path to bblayers.conf file to edit
1292 add: layer path (or list of layer paths) to add; None or empty
1293 list to add nothing
1294 remove: layer path (or list of layer paths) to remove; None or
1295 empty list to remove nothing
1296 Returns a tuple:
1297 notadded: list of layers specified to be added but weren't
1298 (because they were already in the list)
1299 notremoved: list of layers that were specified to be removed
1300 but weren't (because they weren't in the list)
1301 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001302
1303 import fnmatch
1304
1305 def remove_trailing_sep(pth):
1306 if pth and pth[-1] == os.sep:
1307 pth = pth[:-1]
1308 return pth
1309
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001310 approved = bb.utils.approved_variables()
1311 def canonicalise_path(pth):
1312 pth = remove_trailing_sep(pth)
1313 if 'HOME' in approved and '~' in pth:
1314 pth = os.path.expanduser(pth)
1315 return pth
1316
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001317 def layerlist_param(value):
1318 if not value:
1319 return []
1320 elif isinstance(value, list):
1321 return [remove_trailing_sep(x) for x in value]
1322 else:
1323 return [remove_trailing_sep(value)]
1324
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001325 addlayers = layerlist_param(add)
1326 removelayers = layerlist_param(remove)
1327
1328 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1329 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001330 removed = []
1331 plusequals = False
1332 orig_bblayers = []
1333
1334 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1335 bblayercalls.append(op)
1336 if op == '=':
1337 del orig_bblayers[:]
1338 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1339 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001340
1341 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001342 updated = False
1343 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1344 if removelayers:
1345 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001346 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001347 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001348 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001349 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001350 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001351 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001352 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001353 for addlayer in addlayers:
1354 if addlayer not in bblayers:
1355 updated = True
1356 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001357 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001358
1359 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001360 if op == '+=' and not bblayers:
1361 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001362 return (bblayers, None, 2, False)
1363 else:
1364 return (origvalue, None, 2, False)
1365
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001366 with open(bblayers_conf, 'r') as f:
1367 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001368
1369 if not bblayercalls:
1370 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1371
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001372 # Try to do the "smart" thing depending on how the user has laid out
1373 # their bblayers.conf file
1374 if bblayercalls.count('+=') > 1:
1375 plusequals = True
1376
1377 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1378 notadded = []
1379 for layer in addlayers:
1380 layer_canon = canonicalise_path(layer)
1381 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1382 notadded.append(layer)
1383 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1384 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1385
1386 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1387 if addlayers:
1388 # Still need to add these
1389 for addlayer in addlayers:
1390 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1391 updated = True
1392
1393 if updated:
1394 with open(bblayers_conf, 'w') as f:
1395 f.writelines(newlines)
1396
1397 notremoved = list(set(removelayers) - set(removed))
1398
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001399 return (notadded, notremoved)
1400
1401
1402def get_file_layer(filename, d):
1403 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001404 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001405 collection_res = {}
1406 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001407 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001408
1409 def path_to_layer(path):
1410 # Use longest path so we handle nested layers
1411 matchlen = 0
1412 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001413 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001414 if len(regex) > matchlen and re.match(regex, path):
1415 matchlen = len(regex)
1416 match = collection
1417 return match
1418
1419 result = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001420 bbfiles = (d.getVar('BBFILES') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001421 bbfilesmatch = False
1422 for bbfilesentry in bbfiles:
1423 if fnmatch.fnmatch(filename, bbfilesentry):
1424 bbfilesmatch = True
1425 result = path_to_layer(bbfilesentry)
1426
1427 if not bbfilesmatch:
1428 # Probably a bbclass
1429 result = path_to_layer(filename)
1430
1431 return result
1432
1433
1434# Constant taken from http://linux.die.net/include/linux/prctl.h
1435PR_SET_PDEATHSIG = 1
1436
1437class PrCtlError(Exception):
1438 pass
1439
1440def signal_on_parent_exit(signame):
1441 """
1442 Trigger signame to be sent when the parent process dies
1443 """
1444 signum = getattr(signal, signame)
1445 # http://linux.die.net/man/2/prctl
1446 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1447 if result != 0:
1448 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001449
1450#
1451# Manually call the ioprio syscall. We could depend on other libs like psutil
1452# however this gets us enough of what we need to bitbake for now without the
1453# dependency
1454#
1455_unamearch = os.uname()[4]
1456IOPRIO_WHO_PROCESS = 1
1457IOPRIO_CLASS_SHIFT = 13
1458
1459def ioprio_set(who, cls, value):
1460 NR_ioprio_set = None
1461 if _unamearch == "x86_64":
1462 NR_ioprio_set = 251
1463 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1464 NR_ioprio_set = 289
1465
1466 if NR_ioprio_set:
1467 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1468 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1469 if rc != 0:
1470 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1471 else:
1472 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001473
1474def set_process_name(name):
1475 from ctypes import cdll, byref, create_string_buffer
1476 # This is nice to have for debugging, not essential
1477 try:
1478 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001479 buf = create_string_buffer(bytes(name, 'utf-8'))
1480 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001481 except:
1482 pass
1483
1484# export common proxies variables from datastore to environment
1485def export_proxies(d):
1486 import os
1487
1488 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001489 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1490 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001491 exported = False
1492
1493 for v in variables:
1494 if v in os.environ.keys():
1495 exported = True
1496 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001497 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001498 if v_proxy is not None:
1499 os.environ[v] = v_proxy
1500 exported = True
1501
1502 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001503
1504
1505def load_plugins(logger, plugins, pluginpath):
1506 def load_plugin(name):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001507 logger.debug(1, 'Loading plugin %s' % name)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001508 fp, pathname, description = imp.find_module(name, [pluginpath])
1509 try:
1510 return imp.load_module(name, fp, pathname, description)
1511 finally:
1512 if fp:
1513 fp.close()
1514
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001515 logger.debug(1, 'Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001516
1517 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1518 for ext in python_extensions)
1519 files = itertools.chain.from_iterable(expanded)
1520 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1521 for name in names:
1522 if name != '__init__':
1523 plugin = load_plugin(name)
1524 if hasattr(plugin, 'plugin_init'):
1525 obj = plugin.plugin_init(plugins)
1526 plugins.append(obj or plugin)
1527 else:
1528 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001529
1530
1531class LogCatcher(logging.Handler):
1532 """Logging handler for collecting logged messages so you can check them later"""
1533 def __init__(self):
1534 self.messages = []
1535 logging.Handler.__init__(self, logging.WARNING)
1536 def emit(self, record):
1537 self.messages.append(bb.build.logformatter.format(record))
1538 def contains(self, message):
1539 return (message in self.messages)