blob: c540b49cf6d08c3f4de7f1bf6e46805478f12060 [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
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500774 # os.rename needs to know the dest path ending with file name
775 # so append the file name to a path only if it's a dir specified
776 srcfname = os.path.basename(src)
777 destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \
778 else dest
779
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500780 if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]:
781 try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500782 os.rename(src, destpath)
783 renamefailed = 0
784 except Exception as e:
785 if e[0] != errno.EXDEV:
786 # Some random error.
787 print("movefile: Failed to move", src, "to", dest, e)
788 return None
789 # Invalid cross-device-link 'bind' mounted or actually Cross-Device
790
791 if renamefailed:
792 didcopy = 0
793 if stat.S_ISREG(sstat[stat.ST_MODE]):
794 try: # For safety copy then move it over.
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500795 shutil.copyfile(src, destpath + "#new")
796 os.rename(destpath + "#new", destpath)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500797 didcopy = 1
798 except Exception as e:
799 print('movefile: copy', src, '->', dest, 'failed.', e)
800 return None
801 else:
802 #we don't yet handle special, so we need to fall back to /bin/mv
803 a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'")
804 if a[0] != 0:
805 print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a)
806 return None # failure
807 try:
808 if didcopy:
809 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
810 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
811 os.unlink(src)
812 except Exception as e:
813 print("movefile: Failed to chown/chmod/unlink", dest, e)
814 return None
815
816 if newmtime:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500817 os.utime(destpath, (newmtime, newmtime))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500818 else:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500819 os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500820 newmtime = sstat[stat.ST_MTIME]
821 return newmtime
822
823def copyfile(src, dest, newmtime = None, sstat = None):
824 """
825 Copies a file from src to dest, preserving all permissions and
826 attributes; mtime will be preserved even when moving across
827 filesystems. Returns true on success and false on failure.
828 """
829 #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")"
830 try:
831 if not sstat:
832 sstat = os.lstat(src)
833 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600834 logger.warning("copyfile: stat of %s failed (%s)" % (src, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500835 return False
836
837 destexists = 1
838 try:
839 dstat = os.lstat(dest)
840 except:
841 dstat = os.lstat(os.path.dirname(dest))
842 destexists = 0
843
844 if destexists:
845 if stat.S_ISLNK(dstat[stat.ST_MODE]):
846 try:
847 os.unlink(dest)
848 destexists = 0
849 except Exception as e:
850 pass
851
852 if stat.S_ISLNK(sstat[stat.ST_MODE]):
853 try:
854 target = os.readlink(src)
855 if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
856 os.unlink(dest)
857 os.symlink(target, dest)
858 #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
859 return os.lstat(dest)
860 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600861 logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500862 return False
863
864 if stat.S_ISREG(sstat[stat.ST_MODE]):
865 try:
866 srcchown = False
867 if not os.access(src, os.R_OK):
868 # Make sure we can read it
869 srcchown = True
870 os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR)
871
872 # For safety copy then move it over.
873 shutil.copyfile(src, dest + "#new")
874 os.rename(dest + "#new", dest)
875 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600876 logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500877 return False
878 finally:
879 if srcchown:
880 os.chmod(src, sstat[stat.ST_MODE])
881 os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
882
883 else:
884 #we don't yet handle special, so we need to fall back to /bin/mv
885 a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'")
886 if a[0] != 0:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600887 logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500888 return False # failure
889 try:
890 os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID])
891 os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
892 except Exception as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600893 logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500894 return False
895
896 if newmtime:
897 os.utime(dest, (newmtime, newmtime))
898 else:
899 os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME]))
900 newmtime = sstat[stat.ST_MTIME]
901 return newmtime
902
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500903def which(path, item, direction = 0, history = False, executable=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500904 """
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500905 Locate `item` in the list of paths `path` (colon separated string like $PATH).
906 If `direction` is non-zero then the list is reversed.
907 If `history` is True then the list of candidates also returned as result,history.
908 If `executable` is True then the candidate has to be an executable file,
909 otherwise the candidate simply has to exist.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500910 """
911
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500912 if executable:
913 is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK)
914 else:
915 is_candidate = lambda p: os.path.exists(p)
916
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500917 hist = []
918 paths = (path or "").split(':')
919 if direction != 0:
920 paths.reverse()
921
922 for p in paths:
923 next = os.path.join(p, item)
924 hist.append(next)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500925 if is_candidate(next):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500926 if not os.path.isabs(next):
927 next = os.path.abspath(next)
928 if history:
929 return next, hist
930 return next
931
932 if history:
933 return "", hist
934 return ""
935
936def to_boolean(string, default=None):
937 if not string:
938 return default
939
940 normalized = string.lower()
941 if normalized in ("y", "yes", "1", "true"):
942 return True
943 elif normalized in ("n", "no", "0", "false"):
944 return False
945 else:
946 raise ValueError("Invalid value for to_boolean: %s" % string)
947
948def contains(variable, checkvalues, truevalue, falsevalue, d):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500949 """Check if a variable contains all the values specified.
950
951 Arguments:
952
953 variable -- the variable name. This will be fetched and expanded (using
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500954 d.getVar(variable)) and then split into a set().
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500955
956 checkvalues -- if this is a string it is split on whitespace into a set(),
957 otherwise coerced directly into a set().
958
959 truevalue -- the value to return if checkvalues is a subset of variable.
960
961 falsevalue -- the value to return if variable is empty or if checkvalues is
962 not a subset of variable.
963
964 d -- the data store.
965 """
966
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500967 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500968 if not val:
969 return falsevalue
970 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600971 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500972 checkvalues = set(checkvalues.split())
973 else:
974 checkvalues = set(checkvalues)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500975 if checkvalues.issubset(val):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500976 return truevalue
977 return falsevalue
978
979def contains_any(variable, checkvalues, truevalue, falsevalue, d):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500980 val = d.getVar(variable)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500981 if not val:
982 return falsevalue
983 val = set(val.split())
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600984 if isinstance(checkvalues, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500985 checkvalues = set(checkvalues.split())
986 else:
987 checkvalues = set(checkvalues)
988 if checkvalues & val:
989 return truevalue
990 return falsevalue
991
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500992def filter(variable, checkvalues, d):
993 """Return all words in the variable that are present in the checkvalues.
994
995 Arguments:
996
997 variable -- the variable name. This will be fetched and expanded (using
998 d.getVar(variable)) and then split into a set().
999
1000 checkvalues -- if this is a string it is split on whitespace into a set(),
1001 otherwise coerced directly into a set().
1002
1003 d -- the data store.
1004 """
1005
1006 val = d.getVar(variable)
1007 if not val:
1008 return ''
1009 val = set(val.split())
1010 if isinstance(checkvalues, str):
1011 checkvalues = set(checkvalues.split())
1012 else:
1013 checkvalues = set(checkvalues)
1014 return ' '.join(sorted(checkvalues & val))
1015
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001016def cpu_count():
1017 return multiprocessing.cpu_count()
1018
1019def nonblockingfd(fd):
1020 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
1021
1022def process_profilelog(fn, pout = None):
1023 # Either call with a list of filenames and set pout or a filename and optionally pout.
1024 if not pout:
1025 pout = fn + '.processed'
1026 pout = open(pout, 'w')
1027
1028 import pstats
1029 if isinstance(fn, list):
1030 p = pstats.Stats(*fn, stream=pout)
1031 else:
1032 p = pstats.Stats(fn, stream=pout)
1033 p.sort_stats('time')
1034 p.print_stats()
1035 p.print_callers()
1036 p.sort_stats('cumulative')
1037 p.print_stats()
1038
1039 pout.flush()
1040 pout.close()
1041
1042#
1043# Was present to work around multiprocessing pool bugs in python < 2.7.3
1044#
1045def multiprocessingpool(*args, **kwargs):
1046
1047 import multiprocessing.pool
1048 #import multiprocessing.util
1049 #multiprocessing.util.log_to_stderr(10)
1050 # Deal with a multiprocessing bug where signals to the processes would be delayed until the work
1051 # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed.
1052 def wrapper(func):
1053 def wrap(self, timeout=None):
1054 return func(self, timeout=timeout if timeout is not None else 1e100)
1055 return wrap
1056 multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next)
1057
1058 return multiprocessing.Pool(*args, **kwargs)
1059
1060def exec_flat_python_func(func, *args, **kwargs):
1061 """Execute a flat python function (defined with def funcname(args):...)"""
1062 # Prepare a small piece of python code which calls the requested function
1063 # To do this we need to prepare two things - a set of variables we can use to pass
1064 # the values of arguments into the calling function, and the list of arguments for
1065 # the function being called
1066 context = {}
1067 funcargs = []
1068 # Handle unnamed arguments
1069 aidx = 1
1070 for arg in args:
1071 argname = 'arg_%s' % aidx
1072 context[argname] = arg
1073 funcargs.append(argname)
1074 aidx += 1
1075 # Handle keyword arguments
1076 context.update(kwargs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001077 funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()])
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001078 code = 'retval = %s(%s)' % (func, ', '.join(funcargs))
1079 comp = bb.utils.better_compile(code, '<string>', '<string>')
1080 bb.utils.better_exec(comp, context, code, '<string>')
1081 return context['retval']
1082
1083def edit_metadata(meta_lines, variables, varfunc, match_overrides=False):
1084 """Edit lines from a recipe or config file and modify one or more
1085 specified variable values set in the file using a specified callback
1086 function. Lines are expected to have trailing newlines.
1087 Parameters:
1088 meta_lines: lines from the file; can be a list or an iterable
1089 (e.g. file pointer)
1090 variables: a list of variable names to look for. Functions
1091 may also be specified, but must be specified with '()' at
1092 the end of the name. Note that the function doesn't have
1093 any intrinsic understanding of _append, _prepend, _remove,
1094 or overrides, so these are considered as part of the name.
1095 These values go into a regular expression, so regular
1096 expression syntax is allowed.
1097 varfunc: callback function called for every variable matching
1098 one of the entries in the variables parameter. The function
1099 should take four arguments:
1100 varname: name of variable matched
1101 origvalue: current value in file
1102 op: the operator (e.g. '+=')
1103 newlines: list of lines up to this point. You can use
1104 this to prepend lines before this variable setting
1105 if you wish.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001106 and should return a four-element tuple:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001107 newvalue: new value to substitute in, or None to drop
1108 the variable setting entirely. (If the removal
1109 results in two consecutive blank lines, one of the
1110 blank lines will also be dropped).
1111 newop: the operator to use - if you specify None here,
1112 the original operation will be used.
1113 indent: number of spaces to indent multi-line entries,
1114 or -1 to indent up to the level of the assignment
1115 and opening quote, or a string to use as the indent.
1116 minbreak: True to allow the first element of a
1117 multi-line value to continue on the same line as
1118 the assignment, False to indent before the first
1119 element.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001120 To clarify, if you wish not to change the value, then you
1121 would return like this: return origvalue, None, 0, True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001122 match_overrides: True to match items with _overrides on the end,
1123 False otherwise
1124 Returns a tuple:
1125 updated:
1126 True if changes were made, False otherwise.
1127 newlines:
1128 Lines after processing
1129 """
1130
1131 var_res = {}
1132 if match_overrides:
1133 override_re = '(_[a-zA-Z0-9-_$(){}]+)?'
1134 else:
1135 override_re = ''
1136 for var in variables:
1137 if var.endswith('()'):
1138 var_res[var] = re.compile('^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re))
1139 else:
1140 var_res[var] = re.compile('^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re))
1141
1142 updated = False
1143 varset_start = ''
1144 varlines = []
1145 newlines = []
1146 in_var = None
1147 full_value = ''
1148 var_end = ''
1149
1150 def handle_var_end():
1151 prerun_newlines = newlines[:]
1152 op = varset_start[len(in_var):].strip()
1153 (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines)
1154 changed = (prerun_newlines != newlines)
1155
1156 if newvalue is None:
1157 # Drop the value
1158 return True
1159 elif newvalue != full_value or (newop not in [None, op]):
1160 if newop not in [None, op]:
1161 # Callback changed the operator
1162 varset_new = "%s %s" % (in_var, newop)
1163 else:
1164 varset_new = varset_start
1165
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001166 if isinstance(indent, int):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001167 if indent == -1:
1168 indentspc = ' ' * (len(varset_new) + 2)
1169 else:
1170 indentspc = ' ' * indent
1171 else:
1172 indentspc = indent
1173 if in_var.endswith('()'):
1174 # A function definition
1175 if isinstance(newvalue, list):
1176 newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue)))
1177 else:
1178 if not newvalue.startswith('\n'):
1179 newvalue = '\n' + newvalue
1180 if not newvalue.endswith('\n'):
1181 newvalue = newvalue + '\n'
1182 newlines.append('%s {%s}\n' % (varset_new, newvalue))
1183 else:
1184 # Normal variable
1185 if isinstance(newvalue, list):
1186 if not newvalue:
1187 # Empty list -> empty string
1188 newlines.append('%s ""\n' % varset_new)
1189 elif minbreak:
1190 # First item on first line
1191 if len(newvalue) == 1:
1192 newlines.append('%s "%s"\n' % (varset_new, newvalue[0]))
1193 else:
1194 newlines.append('%s "%s \\\n' % (varset_new, newvalue[0]))
1195 for item in newvalue[1:]:
1196 newlines.append('%s%s \\\n' % (indentspc, item))
1197 newlines.append('%s"\n' % indentspc)
1198 else:
1199 # No item on first line
1200 newlines.append('%s " \\\n' % varset_new)
1201 for item in newvalue:
1202 newlines.append('%s%s \\\n' % (indentspc, item))
1203 newlines.append('%s"\n' % indentspc)
1204 else:
1205 newlines.append('%s "%s"\n' % (varset_new, newvalue))
1206 return True
1207 else:
1208 # Put the old lines back where they were
1209 newlines.extend(varlines)
1210 # If newlines was touched by the function, we'll need to return True
1211 return changed
1212
1213 checkspc = False
1214
1215 for line in meta_lines:
1216 if in_var:
1217 value = line.rstrip()
1218 varlines.append(line)
1219 if in_var.endswith('()'):
1220 full_value += '\n' + value
1221 else:
1222 full_value += value[:-1]
1223 if value.endswith(var_end):
1224 if in_var.endswith('()'):
1225 if full_value.count('{') - full_value.count('}') >= 0:
1226 continue
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001227 full_value = full_value[:-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001228 if handle_var_end():
1229 updated = True
1230 checkspc = True
1231 in_var = None
1232 else:
1233 skip = False
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001234 for (varname, var_re) in var_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001235 res = var_re.match(line)
1236 if res:
1237 isfunc = varname.endswith('()')
1238 if isfunc:
1239 splitvalue = line.split('{', 1)
1240 var_end = '}'
1241 else:
1242 var_end = res.groups()[-1]
1243 splitvalue = line.split(var_end, 1)
1244 varset_start = splitvalue[0].rstrip()
1245 value = splitvalue[1].rstrip()
1246 if not isfunc and value.endswith('\\'):
1247 value = value[:-1]
1248 full_value = value
1249 varlines = [line]
1250 in_var = res.group(1)
1251 if isfunc:
1252 in_var += '()'
1253 if value.endswith(var_end):
1254 full_value = full_value[:-1]
1255 if handle_var_end():
1256 updated = True
1257 checkspc = True
1258 in_var = None
1259 skip = True
1260 break
1261 if not skip:
1262 if checkspc:
1263 checkspc = False
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001264 if newlines and newlines[-1] == '\n' and line == '\n':
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001265 # Squash blank line if there are two consecutive blanks after a removal
1266 continue
1267 newlines.append(line)
1268 return (updated, newlines)
1269
1270
1271def edit_metadata_file(meta_file, variables, varfunc):
1272 """Edit a recipe or config file and modify one or more specified
1273 variable values set in the file using a specified callback function.
1274 The file is only written to if the value(s) actually change.
1275 This is basically the file version of edit_metadata(), see that
1276 function's description for parameter/usage information.
1277 Returns True if the file was written to, False otherwise.
1278 """
1279 with open(meta_file, 'r') as f:
1280 (updated, newlines) = edit_metadata(f, variables, varfunc)
1281 if updated:
1282 with open(meta_file, 'w') as f:
1283 f.writelines(newlines)
1284 return updated
1285
1286
1287def edit_bblayers_conf(bblayers_conf, add, remove):
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001288 """Edit bblayers.conf, adding and/or removing layers
1289 Parameters:
1290 bblayers_conf: path to bblayers.conf file to edit
1291 add: layer path (or list of layer paths) to add; None or empty
1292 list to add nothing
1293 remove: layer path (or list of layer paths) to remove; None or
1294 empty list to remove nothing
1295 Returns a tuple:
1296 notadded: list of layers specified to be added but weren't
1297 (because they were already in the list)
1298 notremoved: list of layers that were specified to be removed
1299 but weren't (because they weren't in the list)
1300 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001301
1302 import fnmatch
1303
1304 def remove_trailing_sep(pth):
1305 if pth and pth[-1] == os.sep:
1306 pth = pth[:-1]
1307 return pth
1308
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001309 approved = bb.utils.approved_variables()
1310 def canonicalise_path(pth):
1311 pth = remove_trailing_sep(pth)
1312 if 'HOME' in approved and '~' in pth:
1313 pth = os.path.expanduser(pth)
1314 return pth
1315
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001316 def layerlist_param(value):
1317 if not value:
1318 return []
1319 elif isinstance(value, list):
1320 return [remove_trailing_sep(x) for x in value]
1321 else:
1322 return [remove_trailing_sep(value)]
1323
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001324 addlayers = layerlist_param(add)
1325 removelayers = layerlist_param(remove)
1326
1327 # Need to use a list here because we can't set non-local variables from a callback in python 2.x
1328 bblayercalls = []
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001329 removed = []
1330 plusequals = False
1331 orig_bblayers = []
1332
1333 def handle_bblayers_firstpass(varname, origvalue, op, newlines):
1334 bblayercalls.append(op)
1335 if op == '=':
1336 del orig_bblayers[:]
1337 orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()])
1338 return (origvalue, None, 2, False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001339
1340 def handle_bblayers(varname, origvalue, op, newlines):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001341 updated = False
1342 bblayers = [remove_trailing_sep(x) for x in origvalue.split()]
1343 if removelayers:
1344 for removelayer in removelayers:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001345 for layer in bblayers:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001346 if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001347 updated = True
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001348 bblayers.remove(layer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001349 removed.append(removelayer)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001350 break
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001351 if addlayers and not plusequals:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001352 for addlayer in addlayers:
1353 if addlayer not in bblayers:
1354 updated = True
1355 bblayers.append(addlayer)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001356 del addlayers[:]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001357
1358 if updated:
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001359 if op == '+=' and not bblayers:
1360 bblayers = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001361 return (bblayers, None, 2, False)
1362 else:
1363 return (origvalue, None, 2, False)
1364
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001365 with open(bblayers_conf, 'r') as f:
1366 (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001367
1368 if not bblayercalls:
1369 raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf)
1370
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001371 # Try to do the "smart" thing depending on how the user has laid out
1372 # their bblayers.conf file
1373 if bblayercalls.count('+=') > 1:
1374 plusequals = True
1375
1376 removelayers_canon = [canonicalise_path(layer) for layer in removelayers]
1377 notadded = []
1378 for layer in addlayers:
1379 layer_canon = canonicalise_path(layer)
1380 if layer_canon in orig_bblayers and not layer_canon in removelayers_canon:
1381 notadded.append(layer)
1382 notadded_canon = [canonicalise_path(layer) for layer in notadded]
1383 addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon]
1384
1385 (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers)
1386 if addlayers:
1387 # Still need to add these
1388 for addlayer in addlayers:
1389 newlines.append('BBLAYERS += "%s"\n' % addlayer)
1390 updated = True
1391
1392 if updated:
1393 with open(bblayers_conf, 'w') as f:
1394 f.writelines(newlines)
1395
1396 notremoved = list(set(removelayers) - set(removed))
1397
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001398 return (notadded, notremoved)
1399
1400
1401def get_file_layer(filename, d):
1402 """Determine the collection (as defined by a layer's layer.conf file) containing the specified file"""
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001403 collections = (d.getVar('BBFILE_COLLECTIONS') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001404 collection_res = {}
1405 for collection in collections:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001406 collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or ''
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001407
1408 def path_to_layer(path):
1409 # Use longest path so we handle nested layers
1410 matchlen = 0
1411 match = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001412 for collection, regex in collection_res.items():
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001413 if len(regex) > matchlen and re.match(regex, path):
1414 matchlen = len(regex)
1415 match = collection
1416 return match
1417
1418 result = None
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001419 bbfiles = (d.getVar('BBFILES') or '').split()
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001420 bbfilesmatch = False
1421 for bbfilesentry in bbfiles:
1422 if fnmatch.fnmatch(filename, bbfilesentry):
1423 bbfilesmatch = True
1424 result = path_to_layer(bbfilesentry)
1425
1426 if not bbfilesmatch:
1427 # Probably a bbclass
1428 result = path_to_layer(filename)
1429
1430 return result
1431
1432
1433# Constant taken from http://linux.die.net/include/linux/prctl.h
1434PR_SET_PDEATHSIG = 1
1435
1436class PrCtlError(Exception):
1437 pass
1438
1439def signal_on_parent_exit(signame):
1440 """
1441 Trigger signame to be sent when the parent process dies
1442 """
1443 signum = getattr(signal, signame)
1444 # http://linux.die.net/man/2/prctl
1445 result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum)
1446 if result != 0:
1447 raise PrCtlError('prctl failed with error code %s' % result)
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001448
1449#
1450# Manually call the ioprio syscall. We could depend on other libs like psutil
1451# however this gets us enough of what we need to bitbake for now without the
1452# dependency
1453#
1454_unamearch = os.uname()[4]
1455IOPRIO_WHO_PROCESS = 1
1456IOPRIO_CLASS_SHIFT = 13
1457
1458def ioprio_set(who, cls, value):
1459 NR_ioprio_set = None
1460 if _unamearch == "x86_64":
1461 NR_ioprio_set = 251
1462 elif _unamearch[0] == "i" and _unamearch[2:3] == "86":
1463 NR_ioprio_set = 289
1464
1465 if NR_ioprio_set:
1466 ioprio = value | (cls << IOPRIO_CLASS_SHIFT)
1467 rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio)
1468 if rc != 0:
1469 raise ValueError("Unable to set ioprio, syscall returned %s" % rc)
1470 else:
1471 bb.warn("Unable to set IO Prio for arch %s" % _unamearch)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001472
1473def set_process_name(name):
1474 from ctypes import cdll, byref, create_string_buffer
1475 # This is nice to have for debugging, not essential
1476 try:
1477 libc = cdll.LoadLibrary('libc.so.6')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001478 buf = create_string_buffer(bytes(name, 'utf-8'))
1479 libc.prctl(15, byref(buf), 0, 0, 0)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001480 except:
1481 pass
1482
1483# export common proxies variables from datastore to environment
1484def export_proxies(d):
1485 import os
1486
1487 variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001488 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY',
1489 'GIT_PROXY_COMMAND']
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001490 exported = False
1491
1492 for v in variables:
1493 if v in os.environ.keys():
1494 exported = True
1495 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001496 v_proxy = d.getVar(v)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001497 if v_proxy is not None:
1498 os.environ[v] = v_proxy
1499 exported = True
1500
1501 return exported
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001502
1503
1504def load_plugins(logger, plugins, pluginpath):
1505 def load_plugin(name):
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001506 logger.debug(1, 'Loading plugin %s' % name)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001507 fp, pathname, description = imp.find_module(name, [pluginpath])
1508 try:
1509 return imp.load_module(name, fp, pathname, description)
1510 finally:
1511 if fp:
1512 fp.close()
1513
Brad Bishopd7bf8c12018-02-25 22:55:05 -05001514 logger.debug(1, 'Loading plugins from %s...' % pluginpath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001515
1516 expanded = (glob.glob(os.path.join(pluginpath, '*' + ext))
1517 for ext in python_extensions)
1518 files = itertools.chain.from_iterable(expanded)
1519 names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files)
1520 for name in names:
1521 if name != '__init__':
1522 plugin = load_plugin(name)
1523 if hasattr(plugin, 'plugin_init'):
1524 obj = plugin.plugin_init(plugins)
1525 plugins.append(obj or plugin)
1526 else:
1527 plugins.append(plugin)
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001528
1529
1530class LogCatcher(logging.Handler):
1531 """Logging handler for collecting logged messages so you can check them later"""
1532 def __init__(self):
1533 self.messages = []
1534 logging.Handler.__init__(self, logging.WARNING)
1535 def emit(self, record):
1536 self.messages.append(bb.build.logformatter.format(record))
1537 def contains(self, message):
1538 return (message in self.messages)