blob: 6a44db57d74db2680a74ba6934e3fd5ff36659b9 [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
190def explode_dep_versions2(s):
191 """
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
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600253 r = collections.OrderedDict(sorted(r.items(), key=lambda x: x[0]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254 return r
255
256def explode_dep_versions(s):
257 r = explode_dep_versions2(s)
258 for d in r:
259 if not r[d]:
260 r[d] = None
261 continue
262 if len(r[d]) > 1:
263 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))
264 r[d] = r[d][0]
265 return r
266
267def join_deps(deps, commasep=True):
268 """
269 Take the result from explode_dep_versions and generate a dependency string
270 """
271 result = []
272 for dep in deps:
273 if deps[dep]:
274 if isinstance(deps[dep], list):
275 for v in deps[dep]:
276 result.append(dep + " (" + v + ")")
277 else:
278 result.append(dep + " (" + deps[dep] + ")")
279 else:
280 result.append(dep)
281 if commasep:
282 return ", ".join(result)
283 else:
284 return " ".join(result)
285
286def _print_trace(body, line):
287 """
288 Print the Environment of a Text Body
289 """
290 error = []
291 # print the environment of the method
292 min_line = max(1, line-4)
293 max_line = min(line + 4, len(body))
294 for i in range(min_line, max_line + 1):
295 if line == i:
296 error.append(' *** %.4d:%s' % (i, body[i-1].rstrip()))
297 else:
298 error.append(' %.4d:%s' % (i, body[i-1].rstrip()))
299 return error
300
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500301def better_compile(text, file, realfile, mode = "exec", lineno = 0):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500302 """
303 A better compile method. This method
304 will print the offending lines.
305 """
306 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500307 cache = bb.methodpool.compile_cache(text)
308 if cache:
309 return cache
310 # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though
311 text2 = "\n" * int(lineno) + text
312 code = compile(text2, realfile, mode)
313 bb.methodpool.compile_cache_add(text, code)
314 return code
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500315 except Exception as e:
316 error = []
317 # split the text into lines again
318 body = text.split('\n')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500319 error.append("Error in compiling python function in %s, line %s:\n" % (realfile, lineno))
320 if hasattr(e, "lineno"):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500321 error.append("The code lines resulting in this error were:")
322 error.extend(_print_trace(body, e.lineno))
323 else:
324 error.append("The function causing this error was:")
325 for line in body:
326 error.append(line)
327 error.append("%s: %s" % (e.__class__.__name__, str(e)))
328
329 logger.error("\n".join(error))
330
331 e = bb.BBHandledException(e)
332 raise e
333
334def _print_exception(t, value, tb, realfile, text, context):
335 error = []
336 try:
337 exception = traceback.format_exception_only(t, value)
338 error.append('Error executing a python function in %s:\n' % realfile)
339
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500340 # Strip 'us' from the stack (better_exec call) unless that was where the
341 # error came from
342 if tb.tb_next is not None:
343 tb = tb.tb_next
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500344
345 textarray = text.split('\n')
346
347 linefailed = tb.tb_lineno
348
349 tbextract = traceback.extract_tb(tb)
350 tbformat = traceback.format_list(tbextract)
351 error.append("The stack trace of python calls that resulted in this exception/failure was:")
352 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2]))
353 error.extend(_print_trace(textarray, linefailed))
354
355 # See if this is a function we constructed and has calls back into other functions in
356 # "text". If so, try and improve the context of the error by diving down the trace
357 level = 0
358 nexttb = tb.tb_next
359 while nexttb is not None and (level+1) < len(tbextract):
360 error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2]))
361 if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]:
362 # The code was possibly in the string we compiled ourselves
363 error.extend(_print_trace(textarray, tbextract[level+1][1]))
364 elif tbextract[level+1][0].startswith("/"):
365 # The code looks like it might be in a file, try and load it
366 try:
367 with open(tbextract[level+1][0], "r") as f:
368 text = f.readlines()
369 error.extend(_print_trace(text, tbextract[level+1][1]))
370 except:
371 error.append(tbformat[level+1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500372 else:
373 error.append(tbformat[level+1])
374 nexttb = tb.tb_next
375 level = level + 1
376
377 error.append("Exception: %s" % ''.join(exception))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600378
379 # If the exception is from spwaning a task, let's be helpful and display
380 # the output (which hopefully includes stderr).
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500381 if isinstance(value, subprocess.CalledProcessError) and value.output:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600382 error.append("Subprocess output:")
383 error.append(value.output.decode("utf-8", errors="ignore"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500384 finally:
385 logger.error("\n".join(error))
386
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500387def better_exec(code, context, text = None, realfile = "<code>", pythonexception=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500388 """
389 Similiar to better_compile, better_exec will
390 print the lines that are responsible for the
391 error.
392 """
393 import bb.parse
394 if not text:
395 text = code
396 if not hasattr(code, "co_filename"):
397 code = better_compile(code, realfile, realfile)
398 try:
399 exec(code, get_context(), context)
400 except (bb.BBHandledException, bb.parse.SkipRecipe, bb.build.FuncFailed, bb.data_smart.ExpansionError):
401 # Error already shown so passthrough, no need for traceback
402 raise
403 except Exception as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500404 if pythonexception:
405 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500406 (t, value, tb) = sys.exc_info()
407 try:
408 _print_exception(t, value, tb, realfile, text, context)
409 except Exception as e:
410 logger.error("Exception handler error: %s" % str(e))
411
412 e = bb.BBHandledException(e)
413 raise e
414
415def simple_exec(code, context):
416 exec(code, get_context(), context)
417
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600418def better_eval(source, locals, extraglobals = None):
419 ctx = get_context()
420 if extraglobals:
421 ctx = copy.copy(ctx)
422 for g in extraglobals:
423 ctx[g] = extraglobals[g]
424 return eval(source, ctx, locals)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500425
426@contextmanager
427def fileslocked(files):
428 """Context manager for locking and unlocking file locks."""
429 locks = []
430 if files:
431 for lockfile in files:
432 locks.append(bb.utils.lockfile(lockfile))
433
434 yield
435
436 for lock in locks:
437 bb.utils.unlockfile(lock)
438
439@contextmanager
440def timeout(seconds):
441 def timeout_handler(signum, frame):
442 pass
443
444 original_handler = signal.signal(signal.SIGALRM, timeout_handler)
445
446 try:
447 signal.alarm(seconds)
448 yield
449 finally:
450 signal.alarm(0)
451 signal.signal(signal.SIGALRM, original_handler)
452
453def lockfile(name, shared=False, retry=True, block=False):
454 """
455 Use the specified file as a lock file, return when the lock has
456 been acquired. Returns a variable to pass to unlockfile().
457 Parameters:
458 retry: True to re-try locking if it fails, False otherwise
459 block: True to block until the lock succeeds, False otherwise
460 The retry and block parameters are kind of equivalent unless you
461 consider the possibility of sending a signal to the process to break
462 out - at which point you want block=True rather than retry=True.
463 """
464 dirname = os.path.dirname(name)
465 mkdirhier(dirname)
466
467 if not os.access(dirname, os.W_OK):
468 logger.error("Unable to acquire lock '%s', directory is not writable",
469 name)
470 sys.exit(1)
471
472 op = fcntl.LOCK_EX
473 if shared:
474 op = fcntl.LOCK_SH
475 if not retry and not block:
476 op = op | fcntl.LOCK_NB
477
478 while True:
479 # If we leave the lockfiles lying around there is no problem
480 # but we should clean up after ourselves. This gives potential
481 # for races though. To work around this, when we acquire the lock
482 # we check the file we locked was still the lock file on disk.
483 # by comparing inode numbers. If they don't match or the lockfile
484 # no longer exists, we start again.
485
486 # This implementation is unfair since the last person to request the
487 # lock is the most likely to win it.
488
489 try:
490 lf = open(name, 'a+')
491 fileno = lf.fileno()
492 fcntl.flock(fileno, op)
493 statinfo = os.fstat(fileno)
494 if os.path.exists(lf.name):
495 statinfo2 = os.stat(lf.name)
496 if statinfo.st_ino == statinfo2.st_ino:
497 return lf
498 lf.close()
499 except Exception:
500 try:
501 lf.close()
502 except Exception:
503 pass
504 pass
505 if not retry:
506 return None
507
508def unlockfile(lf):
509 """
510 Unlock a file locked using lockfile()
511 """
512 try:
513 # If we had a shared lock, we need to promote to exclusive before
514 # removing the lockfile. Attempt this, ignore failures.
515 fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
516 os.unlink(lf.name)
517 except (IOError, OSError):
518 pass
519 fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
520 lf.close()
521
522def md5_file(filename):
523 """
524 Return the hex string representation of the MD5 checksum of filename.
525 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500526 import hashlib
527 m = hashlib.md5()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500528
529 with open(filename, "rb") as f:
530 for line in f:
531 m.update(line)
532 return m.hexdigest()
533
534def sha256_file(filename):
535 """
536 Return the hex string representation of the 256-bit SHA checksum of
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500537 filename.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500538 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500539 import hashlib
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500540
541 s = hashlib.sha256()
542 with open(filename, "rb") as f:
543 for line in f:
544 s.update(line)
545 return s.hexdigest()
546
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500547def sha1_file(filename):
548 """
549 Return the hex string representation of the SHA1 checksum of the filename
550 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500551 import hashlib
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500552
553 s = hashlib.sha1()
554 with open(filename, "rb") as f:
555 for line in f:
556 s.update(line)
557 return s.hexdigest()
558
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500559def preserved_envvars_exported():
560 """Variables which are taken from the environment and placed in and exported
561 from the metadata"""
562 return [
563 'BB_TASKHASH',
564 'HOME',
565 'LOGNAME',
566 'PATH',
567 'PWD',
568 'SHELL',
569 'TERM',
570 'USER',
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600571 'LC_ALL',
572 'BBSERVER',
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500573 ]
574
575def preserved_envvars():
576 """Variables which are taken from the environment and placed in the metadata"""
577 v = [
578 'BBPATH',
579 'BB_PRESERVE_ENV',
580 'BB_ENV_WHITELIST',
581 'BB_ENV_EXTRAWHITE',
582 ]
583 return v + preserved_envvars_exported()
584
585def filter_environment(good_vars):
586 """
587 Create a pristine environment for bitbake. This will remove variables that
588 are not known and may influence the build in a negative way.
589 """
590
591 removed_vars = {}
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600592 for key in list(os.environ):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500593 if key in good_vars:
594 continue
595
596 removed_vars[key] = os.environ[key]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500597 del os.environ[key]
598
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600599 # If we spawn a python process, we need to have a UTF-8 locale, else python's file
600 # access methods will use ascii. You can't change that mode once the interpreter is
601 # started so we have to ensure a locale is set. Ideally we'd use C.UTF-8 but not all
602 # distros support that and we need to set something.
603 os.environ["LC_ALL"] = "en_US.UTF-8"
604
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500605 if removed_vars:
606 logger.debug(1, "Removed the following variables from the environment: %s", ", ".join(removed_vars.keys()))
607
608 return removed_vars
609
610def approved_variables():
611 """
612 Determine and return the list of whitelisted variables which are approved
613 to remain in the environment.
614 """
615 if 'BB_PRESERVE_ENV' in os.environ:
616 return os.environ.keys()
617 approved = []
618 if 'BB_ENV_WHITELIST' in os.environ:
619 approved = os.environ['BB_ENV_WHITELIST'].split()
620 approved.extend(['BB_ENV_WHITELIST'])
621 else:
622 approved = preserved_envvars()
623 if 'BB_ENV_EXTRAWHITE' in os.environ:
624 approved.extend(os.environ['BB_ENV_EXTRAWHITE'].split())
625 if 'BB_ENV_EXTRAWHITE' not in approved:
626 approved.extend(['BB_ENV_EXTRAWHITE'])
627 return approved
628
629def clean_environment():
630 """
631 Clean up any spurious environment variables. This will remove any
632 variables the user hasn't chosen to preserve.
633 """
634 if 'BB_PRESERVE_ENV' not in os.environ:
635 good_vars = approved_variables()
636 return filter_environment(good_vars)
637
638 return {}
639
640def empty_environment():
641 """
642 Remove all variables from the environment.
643 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600644 for s in list(os.environ.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500645 os.unsetenv(s)
646 del os.environ[s]
647
648def build_environment(d):
649 """
650 Build an environment from all exported variables.
651 """
652 import bb.data
653 for var in bb.data.keys(d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500654 export = d.getVarFlag(var, "export", False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500655 if export:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500656 os.environ[var] = d.getVar(var) or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500657
658def _check_unsafe_delete_path(path):
659 """
660 Basic safeguard against recursively deleting something we shouldn't. If it returns True,
661 the caller should raise an exception with an appropriate message.
662 NOTE: This is NOT meant to be a security mechanism - just a guard against silly mistakes
663 with potentially disastrous results.
664 """
665 extra = ''
666 # HOME might not be /home/something, so in case we can get it, check against it
667 homedir = os.environ.get('HOME', '')
668 if homedir:
669 extra = '|%s' % homedir
670 if re.match('(/|//|/home|/home/[^/]*%s)$' % extra, os.path.abspath(path)):
671 return True
672 return False
673
674def remove(path, recurse=False):
675 """Equivalent to rm -f or rm -rf"""
676 if not path:
677 return
678 if recurse:
679 for name in glob.glob(path):
680 if _check_unsafe_delete_path(path):
681 raise Exception('bb.utils.remove: called with dangerous path "%s" and recurse=True, refusing to delete!' % path)
682 # shutil.rmtree(name) would be ideal but its too slow
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500683 subprocess.check_call(['rm', '-rf'] + glob.glob(path))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500684 return
685 for name in glob.glob(path):
686 try:
687 os.unlink(name)
688 except OSError as exc:
689 if exc.errno != errno.ENOENT:
690 raise
691
692def prunedir(topdir):
693 # Delete everything reachable from the directory named in 'topdir'.
694 # CAUTION: This is dangerous!
695 if _check_unsafe_delete_path(topdir):
696 raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir)
697 for root, dirs, files in os.walk(topdir, topdown = False):
698 for name in files:
699 os.remove(os.path.join(root, name))
700 for name in dirs:
701 if os.path.islink(os.path.join(root, name)):
702 os.remove(os.path.join(root, name))
703 else:
704 os.rmdir(os.path.join(root, name))
705 os.rmdir(topdir)
706
707#
708# Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var)
709# but thats possibly insane and suffixes is probably going to be small
710#
711def prune_suffix(var, suffixes, d):
712 # See if var ends with any of the suffixes listed and
713 # remove it if found
714 for suffix in suffixes:
715 if var.endswith(suffix):
716 return var.replace(suffix, "")
717 return var
718
719def mkdirhier(directory):
720 """Create a directory like 'mkdir -p', but does not complain if
721 directory already exists like os.makedirs
722 """
723
724 try:
725 os.makedirs(directory)
726 except OSError as e:
727 if e.errno != errno.EEXIST:
728 raise e
729
730def movefile(src, dest, newmtime = None, sstat = None):
731 """Moves a file from src to dest, preserving all permissions and
732 attributes; mtime will be preserved even when moving across
733 filesystems. Returns true on success and false on failure. Move is
734 atomic.
735 """
736
737 #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
738 try:
739 if not sstat:
740 sstat = os.lstat(src)
741 except Exception as e:
742 print("movefile: Stating source file failed...", e)
743 return None
744
745 destexists = 1
746 try:
747 dstat = os.lstat(dest)
748 except:
749 dstat = os.lstat(os.path.dirname(dest))
750 destexists = 0
751
752 if destexists:
753 if stat.S_ISLNK(dstat[stat.ST_MODE]):
754 try:
755 os.unlink(dest)
756 destexists = 0
757 except Exception as e:
758 pass
759
760 if stat.S_ISLNK(sstat[stat.ST_MODE]):
761 try:
762 target = os.readlink(src)
763 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
764 os.unlink(dest)
765 os.symlink(target, dest)
766 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
767 os.unlink(src)
768 return os.lstat(dest)
769 except Exception as e:
770 print("movefile: failed to properly create symlink:", dest, "->", target, e)
771 return None
772
773 renamefailed = 1
774 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
775 try:
776 # os.rename needs to know the dest path ending with file name
777 # so append the file name to a path only if it's a dir specified
778 srcfname = os.path.basename(src)
779 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
780 else dest
781 os.rename(src, destpath)
782 renamefailed = 0
783 except Exception as e:
784 if e[0] != errno.EXDEV:
785 # Some random error.
786 print("movefile: Failed to move", src, "to", dest, e)
787 return None
788 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
789
790 if renamefailed:
791 didcopy = 0
792 if stat.S_ISREG(sstat[stat.ST_MODE]):
793 try: # For safety copy then move it over.
794 shutil.copyfile(src, dest + "#new")
795 os.rename(dest + "#new", dest)
796 didcopy = 1
797 except Exception as e:
798 print('movefile: copy', src, '->', dest, 'failed.', e)
799 return None
800 else:
801 #we don't yet handle special, so we need to fall back to /bin/mv
802 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
803 if a[0] != 0:
804 print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
805 return None # failure
806 try:
807 if didcopy:
808 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
809 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
810 os.unlink(src)
811 except Exception as e:
812 print("movefile: Failed to chown/chmod/unlink", dest, e)
813 return None
814
815 if newmtime:
816 os.utime(dest, (newmtime, newmtime))
817 else:
818 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
819 newmtime = sstat[stat.ST_MTIME]
820 return newmtime
821
822def copyfile(src, dest, newmtime = None, sstat = None):
823 """
824 Copies a file from src to dest, preserving all permissions and
825 attributes; mtime will be preserved even when moving across
826 filesystems. Returns true on success and false on failure.
827 """
828 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
829 try:
830 if not sstat:
831 sstat = os.lstat(src)
832 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600833 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500834 return False
835
836 destexists = 1
837 try:
838 dstat = os.lstat(dest)
839 except:
840 dstat = os.lstat(os.path.dirname(dest))
841 destexists = 0
842
843 if destexists:
844 if stat.S_ISLNK(dstat[stat.ST_MODE]):
845 try:
846 os.unlink(dest)
847 destexists = 0
848 except Exception as e:
849 pass
850
851 if stat.S_ISLNK(sstat[stat.ST_MODE]):
852 try:
853 target = os.readlink(src)
854 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
855 os.unlink(dest)
856 os.symlink(target, dest)
857 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
858 return os.lstat(dest)
859 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600860 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500861 return False
862
863 if stat.S_ISREG(sstat[stat.ST_MODE]):
864 try:
865 srcchown = False
866 if not os.access(src, os.R_OK):
867 # Make sure we can read it
868 srcchown = True
869 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
870
871 # For safety copy then move it over.
872 shutil.copyfile(src, dest + "#new")
873 os.rename(dest + "#new", dest)
874 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600875 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500876 return False
877 finally:
878 if srcchown:
879 os.chmod(src, sstat[stat.ST_MODE])
880 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
881
882 else:
883 #we don't yet handle special, so we need to fall back to /bin/mv
884 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
885 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600886 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500887 return False # failure
888 try:
889 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
890 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
891 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600892 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500893 return False
894
895 if newmtime:
896 os.utime(dest, (newmtime, newmtime))
897 else:
898 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
899 newmtime = sstat[stat.ST_MTIME]
900 return newmtime
901
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500902def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500903 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500904 Locate `item` in the list of paths `path` (colon separated string like $PATH).
905 If `direction` is non-zero then the list is reversed.
906 If `history` is True then the list of candidates also returned as result,history.
907 If `executable` is True then the candidate has to be an executable file,
908 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500909 """
910
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500911 if executable:
912 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
913 else:
914 is_candidate = lambda p: os.path.exists(p)
915
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500916 hist = []
917 paths = (path or "").split(':')
918 if direction != 0:
919 paths.reverse()
920
921 for p in paths:
922 next = os.path.join(p, item)
923 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500924 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500925 if not os.path.isabs(next):
926 next = os.path.abspath(next)
927 if history:
928 return next, hist
929 return next
930
931 if history:
932 return "", hist
933 return ""
934
935def to_boolean(string, default=None):
936 if not string:
937 return default
938
939 normalized = string.lower()
940 if normalized in ("y", "yes", "1", "true"):
941 return True
942 elif normalized in ("n", "no", "0", "false"):
943 return False
944 else:
945 raise ValueError("Invalid value for to_boolean: %s" % string)
946
947def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500948 """Check if a variable contains all the values specified.
949
950 Arguments:
951
952 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500953 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500954
955 checkvalues -- if this is a string it is split on whitespace into a set(),
956 otherwise coerced directly into a set().
957
958 truevalue -- the value to return if checkvalues is a subset of variable.
959
960 falsevalue -- the value to return if variable is empty or if checkvalues is
961 not a subset of variable.
962
963 d -- the data store.
964 """
965
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500966 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500967 if not val:
968 return falsevalue
969 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600970 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500971 checkvalues = set(checkvalues.split())
972 else:
973 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500974 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500975 return truevalue
976 return falsevalue
977
978def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500979 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500980 if not val:
981 return falsevalue
982 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600983 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500984 checkvalues = set(checkvalues.split())
985 else:
986 checkvalues = set(checkvalues)
987 if checkvalues & val:
988 return truevalue
989 return falsevalue
990
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500991def filter(variable, checkvalues, d):
992 """Return all words in the variable that are present in the checkvalues.
993
994 Arguments:
995
996 variable -- the variable name. This will be fetched and expanded (using
997 d.getVar(variable)) and then split into a set().
998
999 checkvalues -- if this is a string it is split on whitespace into a set(),
1000 otherwise coerced directly into a set().
1001
1002 d -- the data store.
1003 """
1004
1005 val = d.getVar(variable)
1006 if not val:
1007 return ''
1008 val = set(val.split())
1009 if isinstance(checkvalues, str):
1010 checkvalues = set(checkvalues.split())
1011 else:
1012 checkvalues = set(checkvalues)
1013 return ' '.join(sorted(checkvalues & val))
1014
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001015def cpu_count():
1016 return multiprocessing.cpu_count()
1017
1018def nonblockingfd(fd):
1019 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1020
1021def process_profilelog(fn, pout = None):
1022 # Either call with a list of filenames and set pout or a filename and optionally pout.
1023 if not pout:
1024 pout = fn + '.processed'
1025 pout = open(pout, 'w')
1026
1027 import pstats
1028 if isinstance(fn, list):
1029 p = pstats.Stats(*fn, stream=pout)
1030 else:
1031 p = pstats.Stats(fn, stream=pout)
1032 p.sort_stats('time')
1033 p.print_stats()
1034 p.print_callers()
1035 p.sort_stats('cumulative')
1036 p.print_stats()
1037
1038 pout.flush()
1039 pout.close()
1040
1041#
1042# Was present to work around multiprocessing pool bugs in python < 2.7.3
1043#
1044def multiprocessingpool(*args, **kwargs):
1045
1046 import multiprocessing.pool
1047 #import multiprocessing.util
1048 #multiprocessing.util.log_to_stderr(10)
1049 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1050 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1051 def wrapper(func):
1052 def wrap(self, timeout=None):
1053 return func(self, timeout=timeout if timeout is not None else 1e100)
1054 return wrap
1055 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1056
1057 return multiprocessing.Pool(*args, **kwargs)
1058
1059def exec_flat_python_func(func, *args, **kwargs):
1060 """Execute a flat python function (defined with def funcname(args):...)"""
1061 # Prepare a small piece of python code which calls the requested function
1062 # To do this we need to prepare two things - a set of variables we can use to pass
1063 # the values of arguments into the calling function, and the list of arguments for
1064 # the function being called
1065 context = {}
1066 funcargs = []
1067 # Handle unnamed arguments
1068 aidx = 1
1069 for arg in args:
1070 argname = 'arg_%s' % aidx
1071 context[argname] = arg
1072 funcargs.append(argname)
1073 aidx += 1
1074 # Handle keyword arguments
1075 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001076 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001077 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1078 comp = bb.utils.better_compile(code, '<string>', '<string>')
1079 bb.utils.better_exec(comp, context, code, '<string>')
1080 return context['retval']
1081
1082def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1083 """Edit lines from a recipe or config file and modify one or more
1084 specified variable values set in the file using a specified callback
1085 function. Lines are expected to have trailing newlines.
1086 Parameters:
1087 meta_lines: lines from the file; can be a list or an iterable
1088 (e.g. file pointer)
1089 variables: a list of variable names to look for. Functions
1090 may also be specified, but must be specified with '()' at
1091 the end of the name. Note that the function doesn't have
1092 any intrinsic understanding of _append, _prepend, _remove,
1093 or overrides, so these are considered as part of the name.
1094 These values go into a regular expression, so regular
1095 expression syntax is allowed.
1096 varfunc: callback function called for every variable matching
1097 one of the entries in the variables parameter. The function
1098 should take four arguments:
1099 varname: name of variable matched
1100 origvalue: current value in file
1101 op: the operator (e.g. '+=')
1102 newlines: list of lines up to this point. You can use
1103 this to prepend lines before this variable setting
1104 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001105 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001106 newvalue: new value to substitute in, or None to drop
1107 the variable setting entirely. (If the removal
1108 results in two consecutive blank lines, one of the
1109 blank lines will also be dropped).
1110 newop: the operator to use - if you specify None here,
1111 the original operation will be used.
1112 indent: number of spaces to indent multi-line entries,
1113 or -1 to indent up to the level of the assignment
1114 and opening quote, or a string to use as the indent.
1115 minbreak: True to allow the first element of a
1116 multi-line value to continue on the same line as
1117 the assignment, False to indent before the first
1118 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001119 To clarify, if you wish not to change the value, then you
1120 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001121 match_overrides: True to match items with _overrides on the end,
1122 False otherwise
1123 Returns a tuple:
1124 updated:
1125 True if changes were made, False otherwise.
1126 newlines:
1127 Lines after processing
1128 """
1129
1130 var_res = {}
1131 if match_overrides:
1132 override_re = '(_[a-zA-Z0-9-_$(){}]+)?'
1133 else:
1134 override_re = ''
1135 for var in variables:
1136 if var.endswith('()'):
1137 var_res[var] = re.compile('^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
1138 else:
1139 var_res[var] = re.compile('^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
1140
1141 updated = False
1142 varset_start = ''
1143 varlines = []
1144 newlines = []
1145 in_var = None
1146 full_value = ''
1147 var_end = ''
1148
1149 def handle_var_end():
1150 prerun_newlines = newlines[:]
1151 op = varset_start[len(in_var):].strip()
1152 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1153 changed = (prerun_newlines != newlines)
1154
1155 if newvalue is None:
1156 # Drop the value
1157 return True
1158 elif newvalue != full_value or (newop not in [None, op]):
1159 if newop not in [None, op]:
1160 # Callback changed the operator
1161 varset_new = "%s %s" % (in_var, newop)
1162 else:
1163 varset_new = varset_start
1164
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001165 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001166 if indent == -1:
1167 indentspc = ' ' * (len(varset_new) + 2)
1168 else:
1169 indentspc = ' ' * indent
1170 else:
1171 indentspc = indent
1172 if in_var.endswith('()'):
1173 # A function definition
1174 if isinstance(newvalue, list):
1175 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1176 else:
1177 if not newvalue.startswith('\n'):
1178 newvalue = '\n' + newvalue
1179 if not newvalue.endswith('\n'):
1180 newvalue = newvalue + '\n'
1181 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1182 else:
1183 # Normal variable
1184 if isinstance(newvalue, list):
1185 if not newvalue:
1186 # Empty list -> empty string
1187 newlines.append('%s ""\n' % varset_new)
1188 elif minbreak:
1189 # First item on first line
1190 if len(newvalue) == 1:
1191 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1192 else:
1193 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1194 for item in newvalue[1:]:
1195 newlines.append('%s%s \\\n' % (indentspc, item))
1196 newlines.append('%s"\n' % indentspc)
1197 else:
1198 # No item on first line
1199 newlines.append('%s " \\\n' % varset_new)
1200 for item in newvalue:
1201 newlines.append('%s%s \\\n' % (indentspc, item))
1202 newlines.append('%s"\n' % indentspc)
1203 else:
1204 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1205 return True
1206 else:
1207 # Put the old lines back where they were
1208 newlines.extend(varlines)
1209 # If newlines was touched by the function, we'll need to return True
1210 return changed
1211
1212 checkspc = False
1213
1214 for line in meta_lines:
1215 if in_var:
1216 value = line.rstrip()
1217 varlines.append(line)
1218 if in_var.endswith('()'):
1219 full_value += '\n' + value
1220 else:
1221 full_value += value[:-1]
1222 if value.endswith(var_end):
1223 if in_var.endswith('()'):
1224 if full_value.count('{') - full_value.count('}') >= 0:
1225 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001226 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001227 if handle_var_end():
1228 updated = True
1229 checkspc = True
1230 in_var = None
1231 else:
1232 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001233 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001234 res = var_re.match(line)
1235 if res:
1236 isfunc = varname.endswith('()')
1237 if isfunc:
1238 splitvalue = line.split('{', 1)
1239 var_end = '}'
1240 else:
1241 var_end = res.groups()[-1]
1242 splitvalue = line.split(var_end, 1)
1243 varset_start = splitvalue[0].rstrip()
1244 value = splitvalue[1].rstrip()
1245 if not isfunc and value.endswith('\\'):
1246 value = value[:-1]
1247 full_value = value
1248 varlines = [line]
1249 in_var = res.group(1)
1250 if isfunc:
1251 in_var += '()'
1252 if value.endswith(var_end):
1253 full_value = full_value[:-1]
1254 if handle_var_end():
1255 updated = True
1256 checkspc = True
1257 in_var = None
1258 skip = True
1259 break
1260 if not skip:
1261 if checkspc:
1262 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001263 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001264 # Squash blank line if there are two consecutive blanks after a removal
1265 continue
1266 newlines.append(line)
1267 return (updated, newlines)
1268
1269
1270def edit_metadata_file(meta_file, variables, varfunc):
1271 """Edit a recipe or config file and modify one or more specified
1272 variable values set in the file using a specified callback function.
1273 The file is only written to if the value(s) actually change.
1274 This is basically the file version of edit_metadata(), see that
1275 function's description for parameter/usage information.
1276 Returns True if the file was written to, False otherwise.
1277 """
1278 with open(meta_file, 'r') as f:
1279 (updated, newlines) = edit_metadata(f, variables, varfunc)
1280 if updated:
1281 with open(meta_file, 'w') as f:
1282 f.writelines(newlines)
1283 return updated
1284
1285
1286def edit_bblayers_conf(bblayers_conf, add, remove):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001287 """Edit bblayers.conf, adding and/or removing layers
1288 Parameters:
1289 bblayers_conf: path to bblayers.conf file to edit
1290 add: layer path (or list of layer paths) to add; None or empty
1291 list to add nothing
1292 remove: layer path (or list of layer paths) to remove; None or
1293 empty list to remove nothing
1294 Returns a tuple:
1295 notadded: list of layers specified to be added but weren't
1296 (because they were already in the list)
1297 notremoved: list of layers that were specified to be removed
1298 but weren't (because they weren't in the list)
1299 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001300
1301 import fnmatch
1302
1303 def remove_trailing_sep(pth):
1304 if pth and pth[-1] == os.sep:
1305 pth = pth[:-1]
1306 return pth
1307
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001308 approved = bb.utils.approved_variables()
1309 def canonicalise_path(pth):
1310 pth = remove_trailing_sep(pth)
1311 if 'HOME' in approved and '~' in pth:
1312 pth = os.path.expanduser(pth)
1313 return pth
1314
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001315 def layerlist_param(value):
1316 if not value:
1317 return []
1318 elif isinstance(value, list):
1319 return [remove_trailing_sep(x) for x in value]
1320 else:
1321 return [remove_trailing_sep(value)]
1322
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001323 addlayers = layerlist_param(add)
1324 removelayers = layerlist_param(remove)
1325
1326 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1327 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001328 removed = []
1329 plusequals = False
1330 orig_bblayers = []
1331
1332 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1333 bblayercalls.append(op)
1334 if op == '=':
1335 del orig_bblayers[:]
1336 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1337 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001338
1339 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001340 updated = False
1341 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1342 if removelayers:
1343 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001344 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001345 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001346 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001347 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001348 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001349 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001350 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001351 for addlayer in addlayers:
1352 if addlayer not in bblayers:
1353 updated = True
1354 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001355 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001356
1357 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001358 if op == '+=' and not bblayers:
1359 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001360 return (bblayers, None, 2, False)
1361 else:
1362 return (origvalue, None, 2, False)
1363
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001364 with open(bblayers_conf, 'r') as f:
1365 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001366
1367 if not bblayercalls:
1368 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1369
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001370 # Try to do the "smart" thing depending on how the user has laid out
1371 # their bblayers.conf file
1372 if bblayercalls.count('+=') > 1:
1373 plusequals = True
1374
1375 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1376 notadded = []
1377 for layer in addlayers:
1378 layer_canon = canonicalise_path(layer)
1379 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1380 notadded.append(layer)
1381 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1382 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1383
1384 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1385 if addlayers:
1386 # Still need to add these
1387 for addlayer in addlayers:
1388 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1389 updated = True
1390
1391 if updated:
1392 with open(bblayers_conf, 'w') as f:
1393 f.writelines(newlines)
1394
1395 notremoved = list(set(removelayers) - set(removed))
1396
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001397 return (notadded, notremoved)
1398
1399
1400def get_file_layer(filename, d):
1401 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001402 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001403 collection_res = {}
1404 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001405 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001406
1407 def path_to_layer(path):
1408 # Use longest path so we handle nested layers
1409 matchlen = 0
1410 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001411 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001412 if len(regex) > matchlen and re.match(regex, path):
1413 matchlen = len(regex)
1414 match = collection
1415 return match
1416
1417 result = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001418 bbfiles = (d.getVar('BBFILES') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001419 bbfilesmatch = False
1420 for bbfilesentry in bbfiles:
1421 if fnmatch.fnmatch(filename, bbfilesentry):
1422 bbfilesmatch = True
1423 result = path_to_layer(bbfilesentry)
1424
1425 if not bbfilesmatch:
1426 # Probably a bbclass
1427 result = path_to_layer(filename)
1428
1429 return result
1430
1431
1432# Constant taken from http://linux.die.net/include/linux/prctl.h
1433PR_SET_PDEATHSIG = 1
1434
1435class PrCtlError(Exception):
1436 pass
1437
1438def signal_on_parent_exit(signame):
1439 """
1440 Trigger signame to be sent when the parent process dies
1441 """
1442 signum = getattr(signal, signame)
1443 # http://linux.die.net/man/2/prctl
1444 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1445 if result != 0:
1446 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001447
1448#
1449# Manually call the ioprio syscall. We could depend on other libs like psutil
1450# however this gets us enough of what we need to bitbake for now without the
1451# dependency
1452#
1453_unamearch = os.uname()[4]
1454IOPRIO_WHO_PROCESS = 1
1455IOPRIO_CLASS_SHIFT = 13
1456
1457def ioprio_set(who, cls, value):
1458 NR_ioprio_set = None
1459 if _unamearch == "x86_64":
1460 NR_ioprio_set = 251
1461 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1462 NR_ioprio_set = 289
1463
1464 if NR_ioprio_set:
1465 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1466 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1467 if rc != 0:
1468 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1469 else:
1470 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001471
1472def set_process_name(name):
1473 from ctypes import cdll, byref, create_string_buffer
1474 # This is nice to have for debugging, not essential
1475 try:
1476 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001477 buf = create_string_buffer(bytes(name, 'utf-8'))
1478 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001479 except:
1480 pass
1481
1482# export common proxies variables from datastore to environment
1483def export_proxies(d):
1484 import os
1485
1486 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001487 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1488 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001489 exported = False
1490
1491 for v in variables:
1492 if v in os.environ.keys():
1493 exported = True
1494 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001495 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001496 if v_proxy is not None:
1497 os.environ[v] = v_proxy
1498 exported = True
1499
1500 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001501
1502
1503def load_plugins(logger, plugins, pluginpath):
1504 def load_plugin(name):
1505 logger.debug('Loading plugin %s' % name)
1506 fp, pathname, description = imp.find_module(name, [pluginpath])
1507 try:
1508 return imp.load_module(name, fp, pathname, description)
1509 finally:
1510 if fp:
1511 fp.close()
1512
1513 logger.debug('Loading plugins from %s...' % pluginpath)
1514
1515 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1516 for ext in python_extensions)
1517 files = itertools.chain.from_iterable(expanded)
1518 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1519 for name in names:
1520 if name != '__init__':
1521 plugin = load_plugin(name)
1522 if hasattr(plugin, 'plugin_init'):
1523 obj = plugin.plugin_init(plugins)
1524 plugins.append(obj or plugin)
1525 else:
1526 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001527
1528
1529class LogCatcher(logging.Handler):
1530 """Logging handler for collecting logged messages so you can check them later"""
1531 def __init__(self):
1532 self.messages = []
1533 logging.Handler.__init__(self, logging.WARNING)
1534 def emit(self, record):
1535 self.messages.append(bb.build.logformatter.format(record))
1536 def contains(self, message):
1537 return (message in self.messages)