| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1 | """ | 
|  | 2 | BitBake Utility Functions | 
|  | 3 | """ | 
|  | 4 |  | 
|  | 5 | # Copyright (C) 2004 Michael Lauer | 
|  | 6 | # | 
| Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 7 | # SPDX-License-Identifier: GPL-2.0-only | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 8 | # | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 9 |  | 
|  | 10 | import re, fcntl, os, string, stat, shutil, time | 
|  | 11 | import sys | 
|  | 12 | import errno | 
|  | 13 | import logging | 
|  | 14 | import bb | 
|  | 15 | import bb.msg | 
|  | 16 | import multiprocessing | 
|  | 17 | import fcntl | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 18 | import importlib | 
|  | 19 | from importlib import machinery | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 20 | import itertools | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 21 | import subprocess | 
|  | 22 | import glob | 
|  | 23 | import fnmatch | 
|  | 24 | import traceback | 
|  | 25 | import errno | 
|  | 26 | import signal | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 27 | import ast | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 28 | import collections | 
|  | 29 | import copy | 
|  | 30 | from subprocess import getstatusoutput | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 31 | from contextlib import contextmanager | 
|  | 32 | from ctypes import cdll | 
|  | 33 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 34 | logger = logging.getLogger("BitBake.Util") | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 35 | python_extensions = importlib.machinery.all_suffixes() | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 36 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 37 |  | 
|  | 38 | def clean_context(): | 
|  | 39 | return { | 
|  | 40 | "os": os, | 
|  | 41 | "bb": bb, | 
|  | 42 | "time": time, | 
|  | 43 | } | 
|  | 44 |  | 
|  | 45 | def get_context(): | 
|  | 46 | return _context | 
|  | 47 |  | 
|  | 48 |  | 
|  | 49 | def set_context(ctx): | 
|  | 50 | _context = ctx | 
|  | 51 |  | 
|  | 52 | # Context used in better_exec, eval | 
|  | 53 | _context = clean_context() | 
|  | 54 |  | 
|  | 55 | class VersionStringException(Exception): | 
|  | 56 | """Exception raised when an invalid version specification is found""" | 
|  | 57 |  | 
|  | 58 | def explode_version(s): | 
|  | 59 | r = [] | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 60 | alpha_regexp = re.compile(r'^([a-zA-Z]+)(.*)$') | 
|  | 61 | numeric_regexp = re.compile(r'^(\d+)(.*)$') | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 62 | while (s != ''): | 
|  | 63 | if s[0] in string.digits: | 
|  | 64 | m = numeric_regexp.match(s) | 
|  | 65 | r.append((0, int(m.group(1)))) | 
|  | 66 | s = m.group(2) | 
|  | 67 | continue | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 68 | if s[0] in string.ascii_letters: | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 69 | m = alpha_regexp.match(s) | 
|  | 70 | r.append((1, m.group(1))) | 
|  | 71 | s = m.group(2) | 
|  | 72 | continue | 
|  | 73 | if s[0] == '~': | 
|  | 74 | r.append((-1, s[0])) | 
|  | 75 | else: | 
|  | 76 | r.append((2, s[0])) | 
|  | 77 | s = s[1:] | 
|  | 78 | return r | 
|  | 79 |  | 
|  | 80 | def split_version(s): | 
|  | 81 | """Split a version string into its constituent parts (PE, PV, PR)""" | 
|  | 82 | s = s.strip(" <>=") | 
|  | 83 | e = 0 | 
|  | 84 | if s.count(':'): | 
|  | 85 | e = int(s.split(":")[0]) | 
|  | 86 | s = s.split(":")[1] | 
|  | 87 | r = "" | 
|  | 88 | if s.count('-'): | 
|  | 89 | r = s.rsplit("-", 1)[1] | 
|  | 90 | s = s.rsplit("-", 1)[0] | 
|  | 91 | v = s | 
|  | 92 | return (e, v, r) | 
|  | 93 |  | 
|  | 94 | def vercmp_part(a, b): | 
|  | 95 | va = explode_version(a) | 
|  | 96 | vb = explode_version(b) | 
|  | 97 | while True: | 
|  | 98 | if va == []: | 
|  | 99 | (oa, ca) = (0, None) | 
|  | 100 | else: | 
|  | 101 | (oa, ca) = va.pop(0) | 
|  | 102 | if vb == []: | 
|  | 103 | (ob, cb) = (0, None) | 
|  | 104 | else: | 
|  | 105 | (ob, cb) = vb.pop(0) | 
|  | 106 | if (oa, ca) == (0, None) and (ob, cb) == (0, None): | 
|  | 107 | return 0 | 
|  | 108 | if oa < ob: | 
|  | 109 | return -1 | 
|  | 110 | elif oa > ob: | 
|  | 111 | return 1 | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 112 | elif ca is None: | 
|  | 113 | return -1 | 
|  | 114 | elif cb is None: | 
|  | 115 | return 1 | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 116 | elif ca < cb: | 
|  | 117 | return -1 | 
|  | 118 | elif ca > cb: | 
|  | 119 | return 1 | 
|  | 120 |  | 
|  | 121 | def vercmp(ta, tb): | 
|  | 122 | (ea, va, ra) = ta | 
|  | 123 | (eb, vb, rb) = tb | 
|  | 124 |  | 
|  | 125 | r = int(ea or 0) - int(eb or 0) | 
|  | 126 | if (r == 0): | 
|  | 127 | r = vercmp_part(va, vb) | 
|  | 128 | if (r == 0): | 
|  | 129 | r = vercmp_part(ra, rb) | 
|  | 130 | return r | 
|  | 131 |  | 
|  | 132 | def vercmp_string(a, b): | 
|  | 133 | ta = split_version(a) | 
|  | 134 | tb = split_version(b) | 
|  | 135 | return vercmp(ta, tb) | 
|  | 136 |  | 
|  | 137 | def vercmp_string_op(a, b, op): | 
|  | 138 | """ | 
|  | 139 | Compare two versions and check if the specified comparison operator matches the result of the comparison. | 
|  | 140 | This function is fairly liberal about what operators it will accept since there are a variety of styles | 
|  | 141 | depending on the context. | 
|  | 142 | """ | 
|  | 143 | res = vercmp_string(a, b) | 
|  | 144 | if op in ('=', '=='): | 
|  | 145 | return res == 0 | 
|  | 146 | elif op == '<=': | 
|  | 147 | return res <= 0 | 
|  | 148 | elif op == '>=': | 
|  | 149 | return res >= 0 | 
|  | 150 | elif op in ('>', '>>'): | 
|  | 151 | return res > 0 | 
|  | 152 | elif op in ('<', '<<'): | 
|  | 153 | return res < 0 | 
|  | 154 | elif op == '!=': | 
|  | 155 | return res != 0 | 
|  | 156 | else: | 
|  | 157 | raise VersionStringException('Unsupported comparison operator "%s"' % op) | 
|  | 158 |  | 
|  | 159 | def explode_deps(s): | 
|  | 160 | """ | 
|  | 161 | Take an RDEPENDS style string of format: | 
|  | 162 | "DEPEND1 (optional version) DEPEND2 (optional version) ..." | 
|  | 163 | and return a list of dependencies. | 
|  | 164 | Version information is ignored. | 
|  | 165 | """ | 
|  | 166 | r = [] | 
|  | 167 | l = s.split() | 
|  | 168 | flag = False | 
|  | 169 | for i in l: | 
|  | 170 | if i[0] == '(': | 
|  | 171 | flag = True | 
|  | 172 | #j = [] | 
|  | 173 | if not flag: | 
|  | 174 | r.append(i) | 
|  | 175 | #else: | 
|  | 176 | #    j.append(i) | 
|  | 177 | if flag and i.endswith(')'): | 
|  | 178 | flag = False | 
|  | 179 | # Ignore version | 
|  | 180 | #r[-1] += ' ' + ' '.join(j) | 
|  | 181 | return r | 
|  | 182 |  | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 183 | def explode_dep_versions2(s, *, sort=True): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 184 | """ | 
|  | 185 | Take an RDEPENDS style string of format: | 
|  | 186 | "DEPEND1 (optional version) DEPEND2 (optional version) ..." | 
|  | 187 | and return a dictionary of dependencies and versions. | 
|  | 188 | """ | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 189 | r = collections.OrderedDict() | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 190 | l = s.replace(",", "").split() | 
|  | 191 | lastdep = None | 
|  | 192 | lastcmp = "" | 
|  | 193 | lastver = "" | 
|  | 194 | incmp = False | 
|  | 195 | inversion = False | 
|  | 196 | for i in l: | 
|  | 197 | if i[0] == '(': | 
|  | 198 | incmp = True | 
|  | 199 | i = i[1:].strip() | 
|  | 200 | if not i: | 
|  | 201 | continue | 
|  | 202 |  | 
|  | 203 | if incmp: | 
|  | 204 | incmp = False | 
|  | 205 | inversion = True | 
|  | 206 | # This list is based on behavior and supported comparisons from deb, opkg and rpm. | 
|  | 207 | # | 
|  | 208 | # Even though =<, <<, ==, !=, =>, and >> may not be supported, | 
|  | 209 | # we list each possibly valid item. | 
|  | 210 | # The build system is responsible for validation of what it supports. | 
|  | 211 | if i.startswith(('<=', '=<', '<<', '==', '!=', '>=', '=>', '>>')): | 
|  | 212 | lastcmp = i[0:2] | 
|  | 213 | i = i[2:] | 
|  | 214 | elif i.startswith(('<', '>', '=')): | 
|  | 215 | lastcmp = i[0:1] | 
|  | 216 | i = i[1:] | 
|  | 217 | else: | 
|  | 218 | # This is an unsupported case! | 
|  | 219 | raise VersionStringException('Invalid version specification in "(%s" - invalid or missing operator' % i) | 
|  | 220 | lastcmp = (i or "") | 
|  | 221 | i = "" | 
|  | 222 | i.strip() | 
|  | 223 | if not i: | 
|  | 224 | continue | 
|  | 225 |  | 
|  | 226 | if inversion: | 
|  | 227 | if i.endswith(')'): | 
|  | 228 | i = i[:-1] or "" | 
|  | 229 | inversion = False | 
|  | 230 | if lastver and i: | 
|  | 231 | lastver += " " | 
|  | 232 | if i: | 
|  | 233 | lastver += i | 
|  | 234 | if lastdep not in r: | 
|  | 235 | r[lastdep] = [] | 
|  | 236 | r[lastdep].append(lastcmp + " " + lastver) | 
|  | 237 | continue | 
|  | 238 |  | 
|  | 239 | #if not inversion: | 
|  | 240 | lastdep = i | 
|  | 241 | lastver = "" | 
|  | 242 | lastcmp = "" | 
|  | 243 | if not (i in r and r[i]): | 
|  | 244 | r[lastdep] = [] | 
|  | 245 |  | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 246 | if sort: | 
|  | 247 | r = collections.OrderedDict(sorted(r.items(), key=lambda x: x[0])) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 248 | return r | 
|  | 249 |  | 
|  | 250 | def explode_dep_versions(s): | 
|  | 251 | r = explode_dep_versions2(s) | 
|  | 252 | for d in r: | 
|  | 253 | if not r[d]: | 
|  | 254 | r[d] = None | 
|  | 255 | continue | 
|  | 256 | if len(r[d]) > 1: | 
|  | 257 | bb.warn("explode_dep_versions(): Item %s appeared in dependency string '%s' multiple times with different values.  explode_dep_versions cannot cope with this." % (d, s)) | 
|  | 258 | r[d] = r[d][0] | 
|  | 259 | return r | 
|  | 260 |  | 
|  | 261 | def join_deps(deps, commasep=True): | 
|  | 262 | """ | 
|  | 263 | Take the result from explode_dep_versions and generate a dependency string | 
|  | 264 | """ | 
|  | 265 | result = [] | 
|  | 266 | for dep in deps: | 
|  | 267 | if deps[dep]: | 
|  | 268 | if isinstance(deps[dep], list): | 
|  | 269 | for v in deps[dep]: | 
|  | 270 | result.append(dep + " (" + v + ")") | 
|  | 271 | else: | 
|  | 272 | result.append(dep + " (" + deps[dep] + ")") | 
|  | 273 | else: | 
|  | 274 | result.append(dep) | 
|  | 275 | if commasep: | 
|  | 276 | return ", ".join(result) | 
|  | 277 | else: | 
|  | 278 | return " ".join(result) | 
|  | 279 |  | 
|  | 280 | def _print_trace(body, line): | 
|  | 281 | """ | 
|  | 282 | Print the Environment of a Text Body | 
|  | 283 | """ | 
|  | 284 | error = [] | 
|  | 285 | # print the environment of the method | 
|  | 286 | min_line = max(1, line-4) | 
|  | 287 | max_line = min(line + 4, len(body)) | 
|  | 288 | for i in range(min_line, max_line + 1): | 
|  | 289 | if line == i: | 
|  | 290 | error.append(' *** %.4d:%s' % (i, body[i-1].rstrip())) | 
|  | 291 | else: | 
|  | 292 | error.append('     %.4d:%s' % (i, body[i-1].rstrip())) | 
|  | 293 | return error | 
|  | 294 |  | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 295 | def better_compile(text, file, realfile, mode = "exec", lineno = 0): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 296 | """ | 
|  | 297 | A better compile method. This method | 
|  | 298 | will print the offending lines. | 
|  | 299 | """ | 
|  | 300 | try: | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 301 | cache = bb.methodpool.compile_cache(text) | 
|  | 302 | if cache: | 
|  | 303 | return cache | 
|  | 304 | # We can't add to the linenumbers for compile, we can pad to the correct number of blank lines though | 
|  | 305 | text2 = "\n" * int(lineno) + text | 
|  | 306 | code = compile(text2, realfile, mode) | 
|  | 307 | bb.methodpool.compile_cache_add(text, code) | 
|  | 308 | return code | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 309 | except Exception as e: | 
|  | 310 | error = [] | 
|  | 311 | # split the text into lines again | 
|  | 312 | body = text.split('\n') | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 313 | error.append("Error in compiling python function in %s, line %s:\n" % (realfile, e.lineno)) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 314 | if hasattr(e, "lineno"): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 315 | error.append("The code lines resulting in this error were:") | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 316 | # e.lineno: line's position in reaflile | 
|  | 317 | # lineno: function name's "position -1" in realfile | 
|  | 318 | # e.lineno - lineno: line's relative position in function | 
|  | 319 | error.extend(_print_trace(body, e.lineno - lineno)) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 320 | else: | 
|  | 321 | error.append("The function causing this error was:") | 
|  | 322 | for line in body: | 
|  | 323 | error.append(line) | 
|  | 324 | error.append("%s: %s" % (e.__class__.__name__, str(e))) | 
|  | 325 |  | 
|  | 326 | logger.error("\n".join(error)) | 
|  | 327 |  | 
|  | 328 | e = bb.BBHandledException(e) | 
|  | 329 | raise e | 
|  | 330 |  | 
|  | 331 | def _print_exception(t, value, tb, realfile, text, context): | 
|  | 332 | error = [] | 
|  | 333 | try: | 
|  | 334 | exception = traceback.format_exception_only(t, value) | 
|  | 335 | error.append('Error executing a python function in %s:\n' % realfile) | 
|  | 336 |  | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 337 | # Strip 'us' from the stack (better_exec call) unless that was where the | 
|  | 338 | # error came from | 
|  | 339 | if tb.tb_next is not None: | 
|  | 340 | tb = tb.tb_next | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 341 |  | 
|  | 342 | textarray = text.split('\n') | 
|  | 343 |  | 
|  | 344 | linefailed = tb.tb_lineno | 
|  | 345 |  | 
|  | 346 | tbextract = traceback.extract_tb(tb) | 
|  | 347 | tbformat = traceback.format_list(tbextract) | 
|  | 348 | error.append("The stack trace of python calls that resulted in this exception/failure was:") | 
|  | 349 | error.append("File: '%s', lineno: %s, function: %s" % (tbextract[0][0], tbextract[0][1], tbextract[0][2])) | 
|  | 350 | error.extend(_print_trace(textarray, linefailed)) | 
|  | 351 |  | 
|  | 352 | # See if this is a function we constructed and has calls back into other functions in | 
|  | 353 | # "text". If so, try and improve the context of the error by diving down the trace | 
|  | 354 | level = 0 | 
|  | 355 | nexttb = tb.tb_next | 
|  | 356 | while nexttb is not None and (level+1) < len(tbextract): | 
|  | 357 | error.append("File: '%s', lineno: %s, function: %s" % (tbextract[level+1][0], tbextract[level+1][1], tbextract[level+1][2])) | 
|  | 358 | if tbextract[level][0] == tbextract[level+1][0] and tbextract[level+1][2] == tbextract[level][0]: | 
|  | 359 | # The code was possibly in the string we compiled ourselves | 
|  | 360 | error.extend(_print_trace(textarray, tbextract[level+1][1])) | 
|  | 361 | elif tbextract[level+1][0].startswith("/"): | 
|  | 362 | # The code looks like it might be in a file, try and load it | 
|  | 363 | try: | 
|  | 364 | with open(tbextract[level+1][0], "r") as f: | 
|  | 365 | text = f.readlines() | 
|  | 366 | error.extend(_print_trace(text, tbextract[level+1][1])) | 
|  | 367 | except: | 
|  | 368 | error.append(tbformat[level+1]) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 369 | else: | 
|  | 370 | error.append(tbformat[level+1]) | 
|  | 371 | nexttb = tb.tb_next | 
|  | 372 | level = level + 1 | 
|  | 373 |  | 
|  | 374 | error.append("Exception: %s" % ''.join(exception)) | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 375 |  | 
|  | 376 | # If the exception is from spwaning a task, let's be helpful and display | 
|  | 377 | # the output (which hopefully includes stderr). | 
| Brad Bishop | 37a0e4d | 2017-12-04 01:01:44 -0500 | [diff] [blame] | 378 | if isinstance(value, subprocess.CalledProcessError) and value.output: | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 379 | error.append("Subprocess output:") | 
|  | 380 | error.append(value.output.decode("utf-8", errors="ignore")) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 381 | finally: | 
|  | 382 | logger.error("\n".join(error)) | 
|  | 383 |  | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 384 | def better_exec(code, context, text = None, realfile = "<code>", pythonexception=False): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 385 | """ | 
|  | 386 | Similiar to better_compile, better_exec will | 
|  | 387 | print the lines that are responsible for the | 
|  | 388 | error. | 
|  | 389 | """ | 
|  | 390 | import bb.parse | 
|  | 391 | if not text: | 
|  | 392 | text = code | 
|  | 393 | if not hasattr(code, "co_filename"): | 
|  | 394 | code = better_compile(code, realfile, realfile) | 
|  | 395 | try: | 
|  | 396 | exec(code, get_context(), context) | 
| Brad Bishop | 08902b0 | 2019-08-20 09:16:51 -0400 | [diff] [blame] | 397 | except (bb.BBHandledException, bb.parse.SkipRecipe, bb.data_smart.ExpansionError): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 398 | # Error already shown so passthrough, no need for traceback | 
|  | 399 | raise | 
|  | 400 | except Exception as e: | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 401 | if pythonexception: | 
|  | 402 | raise | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 403 | (t, value, tb) = sys.exc_info() | 
|  | 404 | try: | 
|  | 405 | _print_exception(t, value, tb, realfile, text, context) | 
|  | 406 | except Exception as e: | 
|  | 407 | logger.error("Exception handler error: %s" % str(e)) | 
|  | 408 |  | 
|  | 409 | e = bb.BBHandledException(e) | 
|  | 410 | raise e | 
|  | 411 |  | 
|  | 412 | def simple_exec(code, context): | 
|  | 413 | exec(code, get_context(), context) | 
|  | 414 |  | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 415 | def better_eval(source, locals, extraglobals = None): | 
|  | 416 | ctx = get_context() | 
|  | 417 | if extraglobals: | 
|  | 418 | ctx = copy.copy(ctx) | 
|  | 419 | for g in extraglobals: | 
|  | 420 | ctx[g] = extraglobals[g] | 
|  | 421 | return eval(source, ctx, locals) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 422 |  | 
|  | 423 | @contextmanager | 
|  | 424 | def fileslocked(files): | 
|  | 425 | """Context manager for locking and unlocking file locks.""" | 
|  | 426 | locks = [] | 
|  | 427 | if files: | 
|  | 428 | for lockfile in files: | 
|  | 429 | locks.append(bb.utils.lockfile(lockfile)) | 
|  | 430 |  | 
|  | 431 | yield | 
|  | 432 |  | 
|  | 433 | for lock in locks: | 
|  | 434 | bb.utils.unlockfile(lock) | 
|  | 435 |  | 
|  | 436 | @contextmanager | 
|  | 437 | def timeout(seconds): | 
|  | 438 | def timeout_handler(signum, frame): | 
|  | 439 | pass | 
|  | 440 |  | 
|  | 441 | original_handler = signal.signal(signal.SIGALRM, timeout_handler) | 
|  | 442 |  | 
|  | 443 | try: | 
|  | 444 | signal.alarm(seconds) | 
|  | 445 | yield | 
|  | 446 | finally: | 
|  | 447 | signal.alarm(0) | 
|  | 448 | signal.signal(signal.SIGALRM, original_handler) | 
|  | 449 |  | 
|  | 450 | def lockfile(name, shared=False, retry=True, block=False): | 
|  | 451 | """ | 
|  | 452 | Use the specified file as a lock file, return when the lock has | 
|  | 453 | been acquired. Returns a variable to pass to unlockfile(). | 
|  | 454 | Parameters: | 
|  | 455 | retry: True to re-try locking if it fails, False otherwise | 
|  | 456 | block: True to block until the lock succeeds, False otherwise | 
|  | 457 | The retry and block parameters are kind of equivalent unless you | 
|  | 458 | consider the possibility of sending a signal to the process to break | 
|  | 459 | out - at which point you want block=True rather than retry=True. | 
|  | 460 | """ | 
|  | 461 | dirname = os.path.dirname(name) | 
|  | 462 | mkdirhier(dirname) | 
|  | 463 |  | 
|  | 464 | if not os.access(dirname, os.W_OK): | 
|  | 465 | logger.error("Unable to acquire lock '%s', directory is not writable", | 
|  | 466 | name) | 
|  | 467 | sys.exit(1) | 
|  | 468 |  | 
|  | 469 | op = fcntl.LOCK_EX | 
|  | 470 | if shared: | 
|  | 471 | op = fcntl.LOCK_SH | 
|  | 472 | if not retry and not block: | 
|  | 473 | op = op | fcntl.LOCK_NB | 
|  | 474 |  | 
|  | 475 | while True: | 
|  | 476 | # If we leave the lockfiles lying around there is no problem | 
|  | 477 | # but we should clean up after ourselves. This gives potential | 
|  | 478 | # for races though. To work around this, when we acquire the lock | 
|  | 479 | # we check the file we locked was still the lock file on disk. | 
|  | 480 | # by comparing inode numbers. If they don't match or the lockfile | 
|  | 481 | # no longer exists, we start again. | 
|  | 482 |  | 
|  | 483 | # This implementation is unfair since the last person to request the | 
|  | 484 | # lock is the most likely to win it. | 
|  | 485 |  | 
|  | 486 | try: | 
|  | 487 | lf = open(name, 'a+') | 
|  | 488 | fileno = lf.fileno() | 
|  | 489 | fcntl.flock(fileno, op) | 
|  | 490 | statinfo = os.fstat(fileno) | 
|  | 491 | if os.path.exists(lf.name): | 
|  | 492 | statinfo2 = os.stat(lf.name) | 
|  | 493 | if statinfo.st_ino == statinfo2.st_ino: | 
|  | 494 | return lf | 
|  | 495 | lf.close() | 
| Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 496 | except OSError as e: | 
|  | 497 | if e.errno == errno.EACCES: | 
|  | 498 | logger.error("Unable to acquire lock '%s', %s", | 
|  | 499 | e.strerror, name) | 
|  | 500 | sys.exit(1) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 501 | try: | 
|  | 502 | lf.close() | 
|  | 503 | except Exception: | 
|  | 504 | pass | 
|  | 505 | pass | 
|  | 506 | if not retry: | 
|  | 507 | return None | 
|  | 508 |  | 
|  | 509 | def unlockfile(lf): | 
|  | 510 | """ | 
|  | 511 | Unlock a file locked using lockfile() | 
|  | 512 | """ | 
|  | 513 | try: | 
|  | 514 | # If we had a shared lock, we need to promote to exclusive before | 
|  | 515 | # removing the lockfile. Attempt this, ignore failures. | 
|  | 516 | fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB) | 
|  | 517 | os.unlink(lf.name) | 
|  | 518 | except (IOError, OSError): | 
|  | 519 | pass | 
|  | 520 | fcntl.flock(lf.fileno(), fcntl.LOCK_UN) | 
|  | 521 | lf.close() | 
|  | 522 |  | 
| Brad Bishop | 6dbb316 | 2019-11-25 09:41:34 -0500 | [diff] [blame] | 523 | def _hasher(method, filename): | 
|  | 524 | import mmap | 
|  | 525 |  | 
|  | 526 | with open(filename, "rb") as f: | 
|  | 527 | try: | 
|  | 528 | with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm: | 
|  | 529 | for chunk in iter(lambda: mm.read(8192), b''): | 
|  | 530 | method.update(chunk) | 
|  | 531 | except ValueError: | 
|  | 532 | # You can't mmap() an empty file so silence this exception | 
|  | 533 | pass | 
|  | 534 | return method.hexdigest() | 
|  | 535 |  | 
|  | 536 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 537 | def md5_file(filename): | 
|  | 538 | """ | 
|  | 539 | Return the hex string representation of the MD5 checksum of filename. | 
|  | 540 | """ | 
| Brad Bishop | 6dbb316 | 2019-11-25 09:41:34 -0500 | [diff] [blame] | 541 | import hashlib | 
|  | 542 | return _hasher(hashlib.md5(), filename) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 543 |  | 
|  | 544 | def sha256_file(filename): | 
|  | 545 | """ | 
|  | 546 | Return the hex string representation of the 256-bit SHA checksum of | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 547 | filename. | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 548 | """ | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 549 | import hashlib | 
| Brad Bishop | 6dbb316 | 2019-11-25 09:41:34 -0500 | [diff] [blame] | 550 | return _hasher(hashlib.sha256(), filename) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 551 |  | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 552 | def sha1_file(filename): | 
|  | 553 | """ | 
|  | 554 | Return the hex string representation of the SHA1 checksum of the filename | 
|  | 555 | """ | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 556 | import hashlib | 
| Brad Bishop | 6dbb316 | 2019-11-25 09:41:34 -0500 | [diff] [blame] | 557 | return _hasher(hashlib.sha1(), filename) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 558 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 559 | def 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 Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 571 | 'LC_ALL', | 
|  | 572 | 'BBSERVER', | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 573 | ] | 
|  | 574 |  | 
|  | 575 | def 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 |  | 
|  | 585 | def 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 Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 592 | for key in list(os.environ): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 593 | if key in good_vars: | 
|  | 594 | continue | 
|  | 595 |  | 
|  | 596 | removed_vars[key] = os.environ[key] | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 597 | del os.environ[key] | 
|  | 598 |  | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 599 | # 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 Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 605 | 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 |  | 
|  | 610 | def 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 |  | 
|  | 629 | def 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 |  | 
|  | 640 | def empty_environment(): | 
|  | 641 | """ | 
|  | 642 | Remove all variables from the environment. | 
|  | 643 | """ | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 644 | for s in list(os.environ.keys()): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 645 | os.unsetenv(s) | 
|  | 646 | del os.environ[s] | 
|  | 647 |  | 
|  | 648 | def 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 Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 654 | export = d.getVarFlag(var, "export", False) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 655 | if export: | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 656 | os.environ[var] = d.getVar(var) or "" | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 657 |  | 
|  | 658 | def _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 |  | 
| Brad Bishop | a34c030 | 2019-09-23 22:34:48 -0400 | [diff] [blame] | 674 | def remove(path, recurse=False, ionice=False): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 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 Bishop | a34c030 | 2019-09-23 22:34:48 -0400 | [diff] [blame] | 683 | cmd = [] | 
|  | 684 | if ionice: | 
|  | 685 | cmd = ['ionice', '-c', '3'] | 
|  | 686 | subprocess.check_call(cmd + ['rm', '-rf'] + glob.glob(path)) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 687 | return | 
|  | 688 | for name in glob.glob(path): | 
|  | 689 | try: | 
|  | 690 | os.unlink(name) | 
|  | 691 | except OSError as exc: | 
|  | 692 | if exc.errno != errno.ENOENT: | 
|  | 693 | raise | 
|  | 694 |  | 
| Brad Bishop | a34c030 | 2019-09-23 22:34:48 -0400 | [diff] [blame] | 695 | def prunedir(topdir, ionice=False): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 696 | # Delete everything reachable from the directory named in 'topdir'. | 
|  | 697 | # CAUTION:  This is dangerous! | 
|  | 698 | if _check_unsafe_delete_path(topdir): | 
|  | 699 | raise Exception('bb.utils.prunedir: called with dangerous path "%s", refusing to delete!' % topdir) | 
| Brad Bishop | a34c030 | 2019-09-23 22:34:48 -0400 | [diff] [blame] | 700 | remove(topdir, recurse=True, ionice=ionice) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 701 |  | 
|  | 702 | # | 
|  | 703 | # Could also use return re.compile("(%s)" % "|".join(map(re.escape, suffixes))).sub(lambda mo: "", var) | 
|  | 704 | # but thats possibly insane and suffixes is probably going to be small | 
|  | 705 | # | 
|  | 706 | def prune_suffix(var, suffixes, d): | 
|  | 707 | # See if var ends with any of the suffixes listed and | 
|  | 708 | # remove it if found | 
|  | 709 | for suffix in suffixes: | 
| Brad Bishop | d89cb5f | 2019-04-10 09:02:41 -0400 | [diff] [blame] | 710 | if suffix and var.endswith(suffix): | 
|  | 711 | return var[:-len(suffix)] | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 712 | return var | 
|  | 713 |  | 
|  | 714 | def mkdirhier(directory): | 
|  | 715 | """Create a directory like 'mkdir -p', but does not complain if | 
|  | 716 | directory already exists like os.makedirs | 
|  | 717 | """ | 
|  | 718 |  | 
|  | 719 | try: | 
|  | 720 | os.makedirs(directory) | 
|  | 721 | except OSError as e: | 
| Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 722 | if e.errno != errno.EEXIST or not os.path.isdir(directory): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 723 | raise e | 
|  | 724 |  | 
|  | 725 | def movefile(src, dest, newmtime = None, sstat = None): | 
|  | 726 | """Moves a file from src to dest, preserving all permissions and | 
|  | 727 | attributes; mtime will be preserved even when moving across | 
|  | 728 | filesystems.  Returns true on success and false on failure. Move is | 
|  | 729 | atomic. | 
|  | 730 | """ | 
|  | 731 |  | 
|  | 732 | #print "movefile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")" | 
|  | 733 | try: | 
|  | 734 | if not sstat: | 
|  | 735 | sstat = os.lstat(src) | 
|  | 736 | except Exception as e: | 
|  | 737 | print("movefile: Stating source file failed...", e) | 
|  | 738 | return None | 
|  | 739 |  | 
|  | 740 | destexists = 1 | 
|  | 741 | try: | 
|  | 742 | dstat = os.lstat(dest) | 
|  | 743 | except: | 
|  | 744 | dstat = os.lstat(os.path.dirname(dest)) | 
|  | 745 | destexists = 0 | 
|  | 746 |  | 
|  | 747 | if destexists: | 
|  | 748 | if stat.S_ISLNK(dstat[stat.ST_MODE]): | 
|  | 749 | try: | 
|  | 750 | os.unlink(dest) | 
|  | 751 | destexists = 0 | 
|  | 752 | except Exception as e: | 
|  | 753 | pass | 
|  | 754 |  | 
|  | 755 | if stat.S_ISLNK(sstat[stat.ST_MODE]): | 
|  | 756 | try: | 
|  | 757 | target = os.readlink(src) | 
|  | 758 | if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]): | 
|  | 759 | os.unlink(dest) | 
|  | 760 | os.symlink(target, dest) | 
|  | 761 | #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID]) | 
|  | 762 | os.unlink(src) | 
|  | 763 | return os.lstat(dest) | 
|  | 764 | except Exception as e: | 
|  | 765 | print("movefile: failed to properly create symlink:", dest, "->", target, e) | 
|  | 766 | return None | 
|  | 767 |  | 
|  | 768 | renamefailed = 1 | 
| Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 769 | # os.rename needs to know the dest path ending with file name | 
|  | 770 | # so append the file name to a path only if it's a dir specified | 
|  | 771 | srcfname = os.path.basename(src) | 
|  | 772 | destpath = os.path.join(dest, srcfname) if os.path.isdir(dest) \ | 
|  | 773 | else dest | 
|  | 774 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 775 | if sstat[stat.ST_DEV] == dstat[stat.ST_DEV]: | 
|  | 776 | try: | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 777 | os.rename(src, destpath) | 
|  | 778 | renamefailed = 0 | 
|  | 779 | except Exception as e: | 
| Brad Bishop | 79641f2 | 2019-09-10 07:20:22 -0400 | [diff] [blame] | 780 | if e.errno != errno.EXDEV: | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 781 | # Some random error. | 
|  | 782 | print("movefile: Failed to move", src, "to", dest, e) | 
|  | 783 | return None | 
|  | 784 | # Invalid cross-device-link 'bind' mounted or actually Cross-Device | 
|  | 785 |  | 
|  | 786 | if renamefailed: | 
|  | 787 | didcopy = 0 | 
|  | 788 | if stat.S_ISREG(sstat[stat.ST_MODE]): | 
|  | 789 | try: # For safety copy then move it over. | 
| Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 790 | shutil.copyfile(src, destpath + "#new") | 
|  | 791 | os.rename(destpath + "#new", destpath) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 792 | didcopy = 1 | 
|  | 793 | except Exception as e: | 
|  | 794 | print('movefile: copy', src, '->', dest, 'failed.', e) | 
|  | 795 | return None | 
|  | 796 | else: | 
|  | 797 | #we don't yet handle special, so we need to fall back to /bin/mv | 
|  | 798 | a = getstatusoutput("/bin/mv -f " + "'" + src + "' '" + dest + "'") | 
|  | 799 | if a[0] != 0: | 
|  | 800 | print("movefile: Failed to move special file:" + src + "' to '" + dest + "'", a) | 
|  | 801 | return None # failure | 
|  | 802 | try: | 
|  | 803 | if didcopy: | 
| Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 804 | os.lchown(destpath, sstat[stat.ST_UID], sstat[stat.ST_GID]) | 
|  | 805 | os.chmod(destpath, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 806 | os.unlink(src) | 
|  | 807 | except Exception as e: | 
|  | 808 | print("movefile: Failed to chown/chmod/unlink", dest, e) | 
|  | 809 | return None | 
|  | 810 |  | 
|  | 811 | if newmtime: | 
| Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 812 | os.utime(destpath, (newmtime, newmtime)) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 813 | else: | 
| Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 814 | os.utime(destpath, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME])) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 815 | newmtime = sstat[stat.ST_MTIME] | 
|  | 816 | return newmtime | 
|  | 817 |  | 
|  | 818 | def copyfile(src, dest, newmtime = None, sstat = None): | 
|  | 819 | """ | 
|  | 820 | Copies a file from src to dest, preserving all permissions and | 
|  | 821 | attributes; mtime will be preserved even when moving across | 
|  | 822 | filesystems.  Returns true on success and false on failure. | 
|  | 823 | """ | 
|  | 824 | #print "copyfile(" + src + "," + dest + "," + str(newmtime) + "," + str(sstat) + ")" | 
|  | 825 | try: | 
|  | 826 | if not sstat: | 
|  | 827 | sstat = os.lstat(src) | 
|  | 828 | except Exception as e: | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 829 | logger.warning("copyfile: stat of %s failed (%s)" % (src, e)) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 830 | return False | 
|  | 831 |  | 
|  | 832 | destexists = 1 | 
|  | 833 | try: | 
|  | 834 | dstat = os.lstat(dest) | 
|  | 835 | except: | 
|  | 836 | dstat = os.lstat(os.path.dirname(dest)) | 
|  | 837 | destexists = 0 | 
|  | 838 |  | 
|  | 839 | if destexists: | 
|  | 840 | if stat.S_ISLNK(dstat[stat.ST_MODE]): | 
|  | 841 | try: | 
|  | 842 | os.unlink(dest) | 
|  | 843 | destexists = 0 | 
|  | 844 | except Exception as e: | 
|  | 845 | pass | 
|  | 846 |  | 
|  | 847 | if stat.S_ISLNK(sstat[stat.ST_MODE]): | 
|  | 848 | try: | 
|  | 849 | target = os.readlink(src) | 
|  | 850 | if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]): | 
|  | 851 | os.unlink(dest) | 
|  | 852 | os.symlink(target, dest) | 
|  | 853 | #os.lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID]) | 
|  | 854 | return os.lstat(dest) | 
|  | 855 | except Exception as e: | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 856 | logger.warning("copyfile: failed to create symlink %s to %s (%s)" % (dest, target, e)) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 857 | return False | 
|  | 858 |  | 
|  | 859 | if stat.S_ISREG(sstat[stat.ST_MODE]): | 
|  | 860 | try: | 
|  | 861 | srcchown = False | 
|  | 862 | if not os.access(src, os.R_OK): | 
|  | 863 | # Make sure we can read it | 
|  | 864 | srcchown = True | 
|  | 865 | os.chmod(src, sstat[stat.ST_MODE] | stat.S_IRUSR) | 
|  | 866 |  | 
|  | 867 | # For safety copy then move it over. | 
|  | 868 | shutil.copyfile(src, dest + "#new") | 
|  | 869 | os.rename(dest + "#new", dest) | 
|  | 870 | except Exception as e: | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 871 | logger.warning("copyfile: copy %s to %s failed (%s)" % (src, dest, e)) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 872 | return False | 
|  | 873 | finally: | 
|  | 874 | if srcchown: | 
|  | 875 | os.chmod(src, sstat[stat.ST_MODE]) | 
|  | 876 | os.utime(src, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME])) | 
|  | 877 |  | 
|  | 878 | else: | 
|  | 879 | #we don't yet handle special, so we need to fall back to /bin/mv | 
|  | 880 | a = getstatusoutput("/bin/cp -f " + "'" + src + "' '" + dest + "'") | 
|  | 881 | if a[0] != 0: | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 882 | logger.warning("copyfile: failed to copy special file %s to %s (%s)" % (src, dest, a)) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 883 | return False # failure | 
|  | 884 | try: | 
|  | 885 | os.lchown(dest, sstat[stat.ST_UID], sstat[stat.ST_GID]) | 
|  | 886 | os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown | 
|  | 887 | except Exception as e: | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 888 | logger.warning("copyfile: failed to chown/chmod %s (%s)" % (dest, e)) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 889 | return False | 
|  | 890 |  | 
|  | 891 | if newmtime: | 
|  | 892 | os.utime(dest, (newmtime, newmtime)) | 
|  | 893 | else: | 
|  | 894 | os.utime(dest, (sstat[stat.ST_ATIME], sstat[stat.ST_MTIME])) | 
|  | 895 | newmtime = sstat[stat.ST_MTIME] | 
|  | 896 | return newmtime | 
|  | 897 |  | 
| Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 898 | def break_hardlinks(src, sstat = None): | 
|  | 899 | """ | 
|  | 900 | Ensures src is the only hardlink to this file.  Other hardlinks, | 
|  | 901 | if any, are not affected (other than in their st_nlink value, of | 
|  | 902 | course).  Returns true on success and false on failure. | 
|  | 903 |  | 
|  | 904 | """ | 
|  | 905 | try: | 
|  | 906 | if not sstat: | 
|  | 907 | sstat = os.lstat(src) | 
|  | 908 | except Exception as e: | 
|  | 909 | logger.warning("break_hardlinks: stat of %s failed (%s)" % (src, e)) | 
|  | 910 | return False | 
|  | 911 | if sstat[stat.ST_NLINK] == 1: | 
|  | 912 | return True | 
|  | 913 | return copyfile(src, src, sstat=sstat) | 
|  | 914 |  | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 915 | def which(path, item, direction = 0, history = False, executable=False): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 916 | """ | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 917 | Locate `item` in the list of paths `path` (colon separated string like $PATH). | 
|  | 918 | If `direction` is non-zero then the list is reversed. | 
|  | 919 | If `history` is True then the list of candidates also returned as result,history. | 
|  | 920 | If `executable` is True then the candidate has to be an executable file, | 
|  | 921 | otherwise the candidate simply has to exist. | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 922 | """ | 
|  | 923 |  | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 924 | if executable: | 
|  | 925 | is_candidate = lambda p: os.path.isfile(p) and os.access(p, os.X_OK) | 
|  | 926 | else: | 
|  | 927 | is_candidate = lambda p: os.path.exists(p) | 
|  | 928 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 929 | hist = [] | 
|  | 930 | paths = (path or "").split(':') | 
|  | 931 | if direction != 0: | 
|  | 932 | paths.reverse() | 
|  | 933 |  | 
|  | 934 | for p in paths: | 
|  | 935 | next = os.path.join(p, item) | 
|  | 936 | hist.append(next) | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 937 | if is_candidate(next): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 938 | if not os.path.isabs(next): | 
|  | 939 | next = os.path.abspath(next) | 
|  | 940 | if history: | 
|  | 941 | return next, hist | 
|  | 942 | return next | 
|  | 943 |  | 
|  | 944 | if history: | 
|  | 945 | return "", hist | 
|  | 946 | return "" | 
|  | 947 |  | 
|  | 948 | def to_boolean(string, default=None): | 
|  | 949 | if not string: | 
|  | 950 | return default | 
|  | 951 |  | 
|  | 952 | normalized = string.lower() | 
|  | 953 | if normalized in ("y", "yes", "1", "true"): | 
|  | 954 | return True | 
|  | 955 | elif normalized in ("n", "no", "0", "false"): | 
|  | 956 | return False | 
|  | 957 | else: | 
|  | 958 | raise ValueError("Invalid value for to_boolean: %s" % string) | 
|  | 959 |  | 
|  | 960 | def contains(variable, checkvalues, truevalue, falsevalue, d): | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 961 | """Check if a variable contains all the values specified. | 
|  | 962 |  | 
|  | 963 | Arguments: | 
|  | 964 |  | 
|  | 965 | variable -- the variable name. This will be fetched and expanded (using | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 966 | d.getVar(variable)) and then split into a set(). | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 967 |  | 
|  | 968 | checkvalues -- if this is a string it is split on whitespace into a set(), | 
|  | 969 | otherwise coerced directly into a set(). | 
|  | 970 |  | 
|  | 971 | truevalue -- the value to return if checkvalues is a subset of variable. | 
|  | 972 |  | 
|  | 973 | falsevalue -- the value to return if variable is empty or if checkvalues is | 
|  | 974 | not a subset of variable. | 
|  | 975 |  | 
|  | 976 | d -- the data store. | 
|  | 977 | """ | 
|  | 978 |  | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 979 | val = d.getVar(variable) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 980 | if not val: | 
|  | 981 | return falsevalue | 
|  | 982 | val = set(val.split()) | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 983 | if isinstance(checkvalues, str): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 984 | checkvalues = set(checkvalues.split()) | 
|  | 985 | else: | 
|  | 986 | checkvalues = set(checkvalues) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 987 | if checkvalues.issubset(val): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 988 | return truevalue | 
|  | 989 | return falsevalue | 
|  | 990 |  | 
|  | 991 | def contains_any(variable, checkvalues, truevalue, falsevalue, d): | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 992 | val = d.getVar(variable) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 993 | if not val: | 
|  | 994 | return falsevalue | 
|  | 995 | val = set(val.split()) | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 996 | if isinstance(checkvalues, str): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 997 | checkvalues = set(checkvalues.split()) | 
|  | 998 | else: | 
|  | 999 | checkvalues = set(checkvalues) | 
|  | 1000 | if checkvalues & val: | 
|  | 1001 | return truevalue | 
|  | 1002 | return falsevalue | 
|  | 1003 |  | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 1004 | def filter(variable, checkvalues, d): | 
|  | 1005 | """Return all words in the variable that are present in the checkvalues. | 
|  | 1006 |  | 
|  | 1007 | Arguments: | 
|  | 1008 |  | 
|  | 1009 | variable -- the variable name. This will be fetched and expanded (using | 
|  | 1010 | d.getVar(variable)) and then split into a set(). | 
|  | 1011 |  | 
|  | 1012 | checkvalues -- if this is a string it is split on whitespace into a set(), | 
|  | 1013 | otherwise coerced directly into a set(). | 
|  | 1014 |  | 
|  | 1015 | d -- the data store. | 
|  | 1016 | """ | 
|  | 1017 |  | 
|  | 1018 | val = d.getVar(variable) | 
|  | 1019 | if not val: | 
|  | 1020 | return '' | 
|  | 1021 | val = set(val.split()) | 
|  | 1022 | if isinstance(checkvalues, str): | 
|  | 1023 | checkvalues = set(checkvalues.split()) | 
|  | 1024 | else: | 
|  | 1025 | checkvalues = set(checkvalues) | 
|  | 1026 | return ' '.join(sorted(checkvalues & val)) | 
|  | 1027 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1028 | def cpu_count(): | 
|  | 1029 | return multiprocessing.cpu_count() | 
|  | 1030 |  | 
|  | 1031 | def nonblockingfd(fd): | 
|  | 1032 | fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK) | 
|  | 1033 |  | 
|  | 1034 | def process_profilelog(fn, pout = None): | 
|  | 1035 | # Either call with a list of filenames and set pout or a filename and optionally pout. | 
|  | 1036 | if not pout: | 
|  | 1037 | pout = fn + '.processed' | 
|  | 1038 | pout = open(pout, 'w') | 
|  | 1039 |  | 
|  | 1040 | import pstats | 
|  | 1041 | if isinstance(fn, list): | 
|  | 1042 | p = pstats.Stats(*fn, stream=pout) | 
|  | 1043 | else: | 
|  | 1044 | p = pstats.Stats(fn, stream=pout) | 
|  | 1045 | p.sort_stats('time') | 
|  | 1046 | p.print_stats() | 
|  | 1047 | p.print_callers() | 
|  | 1048 | p.sort_stats('cumulative') | 
|  | 1049 | p.print_stats() | 
|  | 1050 |  | 
|  | 1051 | pout.flush() | 
|  | 1052 | pout.close() | 
|  | 1053 |  | 
|  | 1054 | # | 
|  | 1055 | # Was present to work around multiprocessing pool bugs in python < 2.7.3 | 
|  | 1056 | # | 
|  | 1057 | def multiprocessingpool(*args, **kwargs): | 
|  | 1058 |  | 
|  | 1059 | import multiprocessing.pool | 
|  | 1060 | #import multiprocessing.util | 
|  | 1061 | #multiprocessing.util.log_to_stderr(10) | 
|  | 1062 | # Deal with a multiprocessing bug where signals to the processes would be delayed until the work | 
|  | 1063 | # completes. Putting in a timeout means the signals (like SIGINT/SIGTERM) get processed. | 
|  | 1064 | def wrapper(func): | 
|  | 1065 | def wrap(self, timeout=None): | 
|  | 1066 | return func(self, timeout=timeout if timeout is not None else 1e100) | 
|  | 1067 | return wrap | 
|  | 1068 | multiprocessing.pool.IMapIterator.next = wrapper(multiprocessing.pool.IMapIterator.next) | 
|  | 1069 |  | 
|  | 1070 | return multiprocessing.Pool(*args, **kwargs) | 
|  | 1071 |  | 
|  | 1072 | def exec_flat_python_func(func, *args, **kwargs): | 
|  | 1073 | """Execute a flat python function (defined with def funcname(args):...)""" | 
|  | 1074 | # Prepare a small piece of python code which calls the requested function | 
|  | 1075 | # To do this we need to prepare two things - a set of variables we can use to pass | 
|  | 1076 | # the values of arguments into the calling function, and the list of arguments for | 
|  | 1077 | # the function being called | 
|  | 1078 | context = {} | 
|  | 1079 | funcargs = [] | 
|  | 1080 | # Handle unnamed arguments | 
|  | 1081 | aidx = 1 | 
|  | 1082 | for arg in args: | 
|  | 1083 | argname = 'arg_%s' % aidx | 
|  | 1084 | context[argname] = arg | 
|  | 1085 | funcargs.append(argname) | 
|  | 1086 | aidx += 1 | 
|  | 1087 | # Handle keyword arguments | 
|  | 1088 | context.update(kwargs) | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1089 | funcargs.extend(['%s=%s' % (arg, arg) for arg in kwargs.keys()]) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1090 | code = 'retval = %s(%s)' % (func, ', '.join(funcargs)) | 
|  | 1091 | comp = bb.utils.better_compile(code, '<string>', '<string>') | 
|  | 1092 | bb.utils.better_exec(comp, context, code, '<string>') | 
|  | 1093 | return context['retval'] | 
|  | 1094 |  | 
|  | 1095 | def edit_metadata(meta_lines, variables, varfunc, match_overrides=False): | 
|  | 1096 | """Edit lines from a recipe or config file and modify one or more | 
|  | 1097 | specified variable values set in the file using a specified callback | 
|  | 1098 | function. Lines are expected to have trailing newlines. | 
|  | 1099 | Parameters: | 
|  | 1100 | meta_lines: lines from the file; can be a list or an iterable | 
|  | 1101 | (e.g. file pointer) | 
|  | 1102 | variables: a list of variable names to look for. Functions | 
|  | 1103 | may also be specified, but must be specified with '()' at | 
|  | 1104 | the end of the name. Note that the function doesn't have | 
|  | 1105 | any intrinsic understanding of _append, _prepend, _remove, | 
|  | 1106 | or overrides, so these are considered as part of the name. | 
|  | 1107 | These values go into a regular expression, so regular | 
|  | 1108 | expression syntax is allowed. | 
|  | 1109 | varfunc: callback function called for every variable matching | 
|  | 1110 | one of the entries in the variables parameter. The function | 
|  | 1111 | should take four arguments: | 
|  | 1112 | varname: name of variable matched | 
|  | 1113 | origvalue: current value in file | 
|  | 1114 | op: the operator (e.g. '+=') | 
|  | 1115 | newlines: list of lines up to this point. You can use | 
|  | 1116 | this to prepend lines before this variable setting | 
|  | 1117 | if you wish. | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1118 | and should return a four-element tuple: | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1119 | newvalue: new value to substitute in, or None to drop | 
|  | 1120 | the variable setting entirely. (If the removal | 
|  | 1121 | results in two consecutive blank lines, one of the | 
|  | 1122 | blank lines will also be dropped). | 
|  | 1123 | newop: the operator to use - if you specify None here, | 
|  | 1124 | the original operation will be used. | 
|  | 1125 | indent: number of spaces to indent multi-line entries, | 
|  | 1126 | or -1 to indent up to the level of the assignment | 
|  | 1127 | and opening quote, or a string to use as the indent. | 
|  | 1128 | minbreak: True to allow the first element of a | 
|  | 1129 | multi-line value to continue on the same line as | 
|  | 1130 | the assignment, False to indent before the first | 
|  | 1131 | element. | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1132 | To clarify, if you wish not to change the value, then you | 
|  | 1133 | would return like this: return origvalue, None, 0, True | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1134 | match_overrides: True to match items with _overrides on the end, | 
|  | 1135 | False otherwise | 
|  | 1136 | Returns a tuple: | 
|  | 1137 | updated: | 
|  | 1138 | True if changes were made, False otherwise. | 
|  | 1139 | newlines: | 
|  | 1140 | Lines after processing | 
|  | 1141 | """ | 
|  | 1142 |  | 
|  | 1143 | var_res = {} | 
|  | 1144 | if match_overrides: | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 1145 | override_re = r'(_[a-zA-Z0-9-_$(){}]+)?' | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1146 | else: | 
|  | 1147 | override_re = '' | 
|  | 1148 | for var in variables: | 
|  | 1149 | if var.endswith('()'): | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 1150 | var_res[var] = re.compile(r'^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re)) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1151 | else: | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 1152 | var_res[var] = re.compile(r'^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re)) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1153 |  | 
|  | 1154 | updated = False | 
|  | 1155 | varset_start = '' | 
|  | 1156 | varlines = [] | 
|  | 1157 | newlines = [] | 
|  | 1158 | in_var = None | 
|  | 1159 | full_value = '' | 
|  | 1160 | var_end = '' | 
|  | 1161 |  | 
|  | 1162 | def handle_var_end(): | 
|  | 1163 | prerun_newlines = newlines[:] | 
|  | 1164 | op = varset_start[len(in_var):].strip() | 
|  | 1165 | (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines) | 
|  | 1166 | changed = (prerun_newlines != newlines) | 
|  | 1167 |  | 
|  | 1168 | if newvalue is None: | 
|  | 1169 | # Drop the value | 
|  | 1170 | return True | 
|  | 1171 | elif newvalue != full_value or (newop not in [None, op]): | 
|  | 1172 | if newop not in [None, op]: | 
|  | 1173 | # Callback changed the operator | 
|  | 1174 | varset_new = "%s %s" % (in_var, newop) | 
|  | 1175 | else: | 
|  | 1176 | varset_new = varset_start | 
|  | 1177 |  | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1178 | if isinstance(indent, int): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1179 | if indent == -1: | 
|  | 1180 | indentspc = ' ' * (len(varset_new) + 2) | 
|  | 1181 | else: | 
|  | 1182 | indentspc = ' ' * indent | 
|  | 1183 | else: | 
|  | 1184 | indentspc = indent | 
|  | 1185 | if in_var.endswith('()'): | 
|  | 1186 | # A function definition | 
|  | 1187 | if isinstance(newvalue, list): | 
|  | 1188 | newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue))) | 
|  | 1189 | else: | 
|  | 1190 | if not newvalue.startswith('\n'): | 
|  | 1191 | newvalue = '\n' + newvalue | 
|  | 1192 | if not newvalue.endswith('\n'): | 
|  | 1193 | newvalue = newvalue + '\n' | 
|  | 1194 | newlines.append('%s {%s}\n' % (varset_new, newvalue)) | 
|  | 1195 | else: | 
|  | 1196 | # Normal variable | 
|  | 1197 | if isinstance(newvalue, list): | 
|  | 1198 | if not newvalue: | 
|  | 1199 | # Empty list -> empty string | 
|  | 1200 | newlines.append('%s ""\n' % varset_new) | 
|  | 1201 | elif minbreak: | 
|  | 1202 | # First item on first line | 
|  | 1203 | if len(newvalue) == 1: | 
|  | 1204 | newlines.append('%s "%s"\n' % (varset_new, newvalue[0])) | 
|  | 1205 | else: | 
|  | 1206 | newlines.append('%s "%s \\\n' % (varset_new, newvalue[0])) | 
|  | 1207 | for item in newvalue[1:]: | 
|  | 1208 | newlines.append('%s%s \\\n' % (indentspc, item)) | 
|  | 1209 | newlines.append('%s"\n' % indentspc) | 
|  | 1210 | else: | 
|  | 1211 | # No item on first line | 
|  | 1212 | newlines.append('%s " \\\n' % varset_new) | 
|  | 1213 | for item in newvalue: | 
|  | 1214 | newlines.append('%s%s \\\n' % (indentspc, item)) | 
|  | 1215 | newlines.append('%s"\n' % indentspc) | 
|  | 1216 | else: | 
|  | 1217 | newlines.append('%s "%s"\n' % (varset_new, newvalue)) | 
|  | 1218 | return True | 
|  | 1219 | else: | 
|  | 1220 | # Put the old lines back where they were | 
|  | 1221 | newlines.extend(varlines) | 
|  | 1222 | # If newlines was touched by the function, we'll need to return True | 
|  | 1223 | return changed | 
|  | 1224 |  | 
|  | 1225 | checkspc = False | 
|  | 1226 |  | 
|  | 1227 | for line in meta_lines: | 
|  | 1228 | if in_var: | 
|  | 1229 | value = line.rstrip() | 
|  | 1230 | varlines.append(line) | 
|  | 1231 | if in_var.endswith('()'): | 
|  | 1232 | full_value += '\n' + value | 
|  | 1233 | else: | 
|  | 1234 | full_value += value[:-1] | 
|  | 1235 | if value.endswith(var_end): | 
|  | 1236 | if in_var.endswith('()'): | 
|  | 1237 | if full_value.count('{') - full_value.count('}') >= 0: | 
|  | 1238 | continue | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 1239 | full_value = full_value[:-1] | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1240 | if handle_var_end(): | 
|  | 1241 | updated = True | 
|  | 1242 | checkspc = True | 
|  | 1243 | in_var = None | 
|  | 1244 | else: | 
|  | 1245 | skip = False | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1246 | for (varname, var_re) in var_res.items(): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1247 | res = var_re.match(line) | 
|  | 1248 | if res: | 
|  | 1249 | isfunc = varname.endswith('()') | 
|  | 1250 | if isfunc: | 
|  | 1251 | splitvalue = line.split('{', 1) | 
|  | 1252 | var_end = '}' | 
|  | 1253 | else: | 
|  | 1254 | var_end = res.groups()[-1] | 
|  | 1255 | splitvalue = line.split(var_end, 1) | 
|  | 1256 | varset_start = splitvalue[0].rstrip() | 
|  | 1257 | value = splitvalue[1].rstrip() | 
|  | 1258 | if not isfunc and value.endswith('\\'): | 
|  | 1259 | value = value[:-1] | 
|  | 1260 | full_value = value | 
|  | 1261 | varlines = [line] | 
|  | 1262 | in_var = res.group(1) | 
|  | 1263 | if isfunc: | 
|  | 1264 | in_var += '()' | 
|  | 1265 | if value.endswith(var_end): | 
|  | 1266 | full_value = full_value[:-1] | 
|  | 1267 | if handle_var_end(): | 
|  | 1268 | updated = True | 
|  | 1269 | checkspc = True | 
|  | 1270 | in_var = None | 
|  | 1271 | skip = True | 
|  | 1272 | break | 
|  | 1273 | if not skip: | 
|  | 1274 | if checkspc: | 
|  | 1275 | checkspc = False | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1276 | if newlines and newlines[-1] == '\n' and line == '\n': | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1277 | # Squash blank line if there are two consecutive blanks after a removal | 
|  | 1278 | continue | 
|  | 1279 | newlines.append(line) | 
|  | 1280 | return (updated, newlines) | 
|  | 1281 |  | 
|  | 1282 |  | 
|  | 1283 | def edit_metadata_file(meta_file, variables, varfunc): | 
|  | 1284 | """Edit a recipe or config file and modify one or more specified | 
|  | 1285 | variable values set in the file using a specified callback function. | 
|  | 1286 | The file is only written to if the value(s) actually change. | 
|  | 1287 | This is basically the file version of edit_metadata(), see that | 
|  | 1288 | function's description for parameter/usage information. | 
|  | 1289 | Returns True if the file was written to, False otherwise. | 
|  | 1290 | """ | 
|  | 1291 | with open(meta_file, 'r') as f: | 
|  | 1292 | (updated, newlines) = edit_metadata(f, variables, varfunc) | 
|  | 1293 | if updated: | 
|  | 1294 | with open(meta_file, 'w') as f: | 
|  | 1295 | f.writelines(newlines) | 
|  | 1296 | return updated | 
|  | 1297 |  | 
|  | 1298 |  | 
| Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 1299 | def edit_bblayers_conf(bblayers_conf, add, remove, edit_cb=None): | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1300 | """Edit bblayers.conf, adding and/or removing layers | 
|  | 1301 | Parameters: | 
|  | 1302 | bblayers_conf: path to bblayers.conf file to edit | 
|  | 1303 | add: layer path (or list of layer paths) to add; None or empty | 
|  | 1304 | list to add nothing | 
|  | 1305 | remove: layer path (or list of layer paths) to remove; None or | 
|  | 1306 | empty list to remove nothing | 
| Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 1307 | edit_cb: optional callback function that will be called after | 
|  | 1308 | processing adds/removes once per existing entry. | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1309 | Returns a tuple: | 
|  | 1310 | notadded: list of layers specified to be added but weren't | 
|  | 1311 | (because they were already in the list) | 
|  | 1312 | notremoved: list of layers that were specified to be removed | 
|  | 1313 | but weren't (because they weren't in the list) | 
|  | 1314 | """ | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1315 |  | 
|  | 1316 | import fnmatch | 
|  | 1317 |  | 
|  | 1318 | def remove_trailing_sep(pth): | 
|  | 1319 | if pth and pth[-1] == os.sep: | 
|  | 1320 | pth = pth[:-1] | 
|  | 1321 | return pth | 
|  | 1322 |  | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1323 | approved = bb.utils.approved_variables() | 
|  | 1324 | def canonicalise_path(pth): | 
|  | 1325 | pth = remove_trailing_sep(pth) | 
|  | 1326 | if 'HOME' in approved and '~' in pth: | 
|  | 1327 | pth = os.path.expanduser(pth) | 
|  | 1328 | return pth | 
|  | 1329 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1330 | def layerlist_param(value): | 
|  | 1331 | if not value: | 
|  | 1332 | return [] | 
|  | 1333 | elif isinstance(value, list): | 
|  | 1334 | return [remove_trailing_sep(x) for x in value] | 
|  | 1335 | else: | 
|  | 1336 | return [remove_trailing_sep(value)] | 
|  | 1337 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1338 | addlayers = layerlist_param(add) | 
|  | 1339 | removelayers = layerlist_param(remove) | 
|  | 1340 |  | 
|  | 1341 | # Need to use a list here because we can't set non-local variables from a callback in python 2.x | 
|  | 1342 | bblayercalls = [] | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1343 | removed = [] | 
|  | 1344 | plusequals = False | 
|  | 1345 | orig_bblayers = [] | 
|  | 1346 |  | 
|  | 1347 | def handle_bblayers_firstpass(varname, origvalue, op, newlines): | 
|  | 1348 | bblayercalls.append(op) | 
|  | 1349 | if op == '=': | 
|  | 1350 | del orig_bblayers[:] | 
|  | 1351 | orig_bblayers.extend([canonicalise_path(x) for x in origvalue.split()]) | 
|  | 1352 | return (origvalue, None, 2, False) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1353 |  | 
|  | 1354 | def handle_bblayers(varname, origvalue, op, newlines): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1355 | updated = False | 
|  | 1356 | bblayers = [remove_trailing_sep(x) for x in origvalue.split()] | 
|  | 1357 | if removelayers: | 
|  | 1358 | for removelayer in removelayers: | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1359 | for layer in bblayers: | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1360 | if fnmatch.fnmatch(canonicalise_path(layer), canonicalise_path(removelayer)): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1361 | updated = True | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1362 | bblayers.remove(layer) | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1363 | removed.append(removelayer) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1364 | break | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1365 | if addlayers and not plusequals: | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1366 | for addlayer in addlayers: | 
|  | 1367 | if addlayer not in bblayers: | 
|  | 1368 | updated = True | 
|  | 1369 | bblayers.append(addlayer) | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1370 | del addlayers[:] | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1371 |  | 
| Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 1372 | if edit_cb: | 
|  | 1373 | newlist = [] | 
|  | 1374 | for layer in bblayers: | 
|  | 1375 | res = edit_cb(layer, canonicalise_path(layer)) | 
|  | 1376 | if res != layer: | 
|  | 1377 | newlist.append(res) | 
|  | 1378 | updated = True | 
|  | 1379 | else: | 
|  | 1380 | newlist.append(layer) | 
|  | 1381 | bblayers = newlist | 
|  | 1382 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1383 | if updated: | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1384 | if op == '+=' and not bblayers: | 
|  | 1385 | bblayers = None | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1386 | return (bblayers, None, 2, False) | 
|  | 1387 | else: | 
|  | 1388 | return (origvalue, None, 2, False) | 
|  | 1389 |  | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1390 | with open(bblayers_conf, 'r') as f: | 
|  | 1391 | (_, newlines) = edit_metadata(f, ['BBLAYERS'], handle_bblayers_firstpass) | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1392 |  | 
|  | 1393 | if not bblayercalls: | 
|  | 1394 | raise Exception('Unable to find BBLAYERS in %s' % bblayers_conf) | 
|  | 1395 |  | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1396 | # Try to do the "smart" thing depending on how the user has laid out | 
|  | 1397 | # their bblayers.conf file | 
|  | 1398 | if bblayercalls.count('+=') > 1: | 
|  | 1399 | plusequals = True | 
|  | 1400 |  | 
|  | 1401 | removelayers_canon = [canonicalise_path(layer) for layer in removelayers] | 
|  | 1402 | notadded = [] | 
|  | 1403 | for layer in addlayers: | 
|  | 1404 | layer_canon = canonicalise_path(layer) | 
|  | 1405 | if layer_canon in orig_bblayers and not layer_canon in removelayers_canon: | 
|  | 1406 | notadded.append(layer) | 
|  | 1407 | notadded_canon = [canonicalise_path(layer) for layer in notadded] | 
|  | 1408 | addlayers[:] = [layer for layer in addlayers if canonicalise_path(layer) not in notadded_canon] | 
|  | 1409 |  | 
|  | 1410 | (updated, newlines) = edit_metadata(newlines, ['BBLAYERS'], handle_bblayers) | 
|  | 1411 | if addlayers: | 
|  | 1412 | # Still need to add these | 
|  | 1413 | for addlayer in addlayers: | 
|  | 1414 | newlines.append('BBLAYERS += "%s"\n' % addlayer) | 
|  | 1415 | updated = True | 
|  | 1416 |  | 
|  | 1417 | if updated: | 
|  | 1418 | with open(bblayers_conf, 'w') as f: | 
|  | 1419 | f.writelines(newlines) | 
|  | 1420 |  | 
|  | 1421 | notremoved = list(set(removelayers) - set(removed)) | 
|  | 1422 |  | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1423 | return (notadded, notremoved) | 
|  | 1424 |  | 
|  | 1425 |  | 
|  | 1426 | def get_file_layer(filename, d): | 
|  | 1427 | """Determine the collection (as defined by a layer's layer.conf file) containing the specified file""" | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 1428 | collections = (d.getVar('BBFILE_COLLECTIONS') or '').split() | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1429 | collection_res = {} | 
|  | 1430 | for collection in collections: | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 1431 | collection_res[collection] = d.getVar('BBFILE_PATTERN_%s' % collection) or '' | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1432 |  | 
|  | 1433 | def path_to_layer(path): | 
|  | 1434 | # Use longest path so we handle nested layers | 
|  | 1435 | matchlen = 0 | 
|  | 1436 | match = None | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1437 | for collection, regex in collection_res.items(): | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1438 | if len(regex) > matchlen and re.match(regex, path): | 
|  | 1439 | matchlen = len(regex) | 
|  | 1440 | match = collection | 
|  | 1441 | return match | 
|  | 1442 |  | 
|  | 1443 | result = None | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 1444 | bbfiles = (d.getVar('BBFILES') or '').split() | 
| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1445 | bbfilesmatch = False | 
|  | 1446 | for bbfilesentry in bbfiles: | 
|  | 1447 | if fnmatch.fnmatch(filename, bbfilesentry): | 
|  | 1448 | bbfilesmatch = True | 
|  | 1449 | result = path_to_layer(bbfilesentry) | 
|  | 1450 |  | 
|  | 1451 | if not bbfilesmatch: | 
|  | 1452 | # Probably a bbclass | 
|  | 1453 | result = path_to_layer(filename) | 
|  | 1454 |  | 
|  | 1455 | return result | 
|  | 1456 |  | 
|  | 1457 |  | 
|  | 1458 | # Constant taken from http://linux.die.net/include/linux/prctl.h | 
|  | 1459 | PR_SET_PDEATHSIG = 1 | 
|  | 1460 |  | 
|  | 1461 | class PrCtlError(Exception): | 
|  | 1462 | pass | 
|  | 1463 |  | 
|  | 1464 | def signal_on_parent_exit(signame): | 
|  | 1465 | """ | 
|  | 1466 | Trigger signame to be sent when the parent process dies | 
|  | 1467 | """ | 
|  | 1468 | signum = getattr(signal, signame) | 
|  | 1469 | # http://linux.die.net/man/2/prctl | 
|  | 1470 | result = cdll['libc.so.6'].prctl(PR_SET_PDEATHSIG, signum) | 
|  | 1471 | if result != 0: | 
|  | 1472 | raise PrCtlError('prctl failed with error code %s' % result) | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1473 |  | 
|  | 1474 | # | 
|  | 1475 | # Manually call the ioprio syscall. We could depend on other libs like psutil | 
|  | 1476 | # however this gets us enough of what we need to bitbake for now without the | 
|  | 1477 | # dependency | 
|  | 1478 | # | 
|  | 1479 | _unamearch = os.uname()[4] | 
|  | 1480 | IOPRIO_WHO_PROCESS = 1 | 
|  | 1481 | IOPRIO_CLASS_SHIFT = 13 | 
|  | 1482 |  | 
|  | 1483 | def ioprio_set(who, cls, value): | 
|  | 1484 | NR_ioprio_set = None | 
|  | 1485 | if _unamearch == "x86_64": | 
|  | 1486 | NR_ioprio_set = 251 | 
|  | 1487 | elif _unamearch[0] == "i" and _unamearch[2:3] == "86": | 
|  | 1488 | NR_ioprio_set = 289 | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 1489 | elif _unamearch == "aarch64": | 
|  | 1490 | NR_ioprio_set = 30 | 
| Patrick Williams | f1e5d69 | 2016-03-30 15:21:19 -0500 | [diff] [blame] | 1491 |  | 
|  | 1492 | if NR_ioprio_set: | 
|  | 1493 | ioprio = value | (cls << IOPRIO_CLASS_SHIFT) | 
|  | 1494 | rc = cdll['libc.so.6'].syscall(NR_ioprio_set, IOPRIO_WHO_PROCESS, who, ioprio) | 
|  | 1495 | if rc != 0: | 
|  | 1496 | raise ValueError("Unable to set ioprio, syscall returned %s" % rc) | 
|  | 1497 | else: | 
|  | 1498 | bb.warn("Unable to set IO Prio for arch %s" % _unamearch) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 1499 |  | 
|  | 1500 | def set_process_name(name): | 
|  | 1501 | from ctypes import cdll, byref, create_string_buffer | 
|  | 1502 | # This is nice to have for debugging, not essential | 
|  | 1503 | try: | 
|  | 1504 | libc = cdll.LoadLibrary('libc.so.6') | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1505 | buf = create_string_buffer(bytes(name, 'utf-8')) | 
|  | 1506 | libc.prctl(15, byref(buf), 0, 0, 0) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 1507 | except: | 
|  | 1508 | pass | 
|  | 1509 |  | 
|  | 1510 | # export common proxies variables from datastore to environment | 
|  | 1511 | def export_proxies(d): | 
|  | 1512 | import os | 
|  | 1513 |  | 
|  | 1514 | variables = ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY', | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1515 | 'ftp_proxy', 'FTP_PROXY', 'no_proxy', 'NO_PROXY', | 
|  | 1516 | 'GIT_PROXY_COMMAND'] | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 1517 | exported = False | 
|  | 1518 |  | 
|  | 1519 | for v in variables: | 
|  | 1520 | if v in os.environ.keys(): | 
|  | 1521 | exported = True | 
|  | 1522 | else: | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 1523 | v_proxy = d.getVar(v) | 
| Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 1524 | if v_proxy is not None: | 
|  | 1525 | os.environ[v] = v_proxy | 
|  | 1526 | exported = True | 
|  | 1527 |  | 
|  | 1528 | return exported | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1529 |  | 
|  | 1530 |  | 
|  | 1531 | def load_plugins(logger, plugins, pluginpath): | 
|  | 1532 | def load_plugin(name): | 
| Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 1533 | logger.debug(1, 'Loading plugin %s' % name) | 
| Brad Bishop | 1932369 | 2019-04-05 15:28:33 -0400 | [diff] [blame] | 1534 | spec = importlib.machinery.PathFinder.find_spec(name, path=[pluginpath] ) | 
|  | 1535 | if spec: | 
|  | 1536 | return spec.loader.load_module() | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1537 |  | 
| Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 1538 | logger.debug(1, 'Loading plugins from %s...' % pluginpath) | 
| Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 1539 |  | 
|  | 1540 | expanded = (glob.glob(os.path.join(pluginpath, '*' + ext)) | 
|  | 1541 | for ext in python_extensions) | 
|  | 1542 | files = itertools.chain.from_iterable(expanded) | 
|  | 1543 | names = set(os.path.splitext(os.path.basename(fn))[0] for fn in files) | 
|  | 1544 | for name in names: | 
|  | 1545 | if name != '__init__': | 
|  | 1546 | plugin = load_plugin(name) | 
|  | 1547 | if hasattr(plugin, 'plugin_init'): | 
|  | 1548 | obj = plugin.plugin_init(plugins) | 
|  | 1549 | plugins.append(obj or plugin) | 
|  | 1550 | else: | 
|  | 1551 | plugins.append(plugin) | 
| Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 1552 |  | 
|  | 1553 |  | 
|  | 1554 | class LogCatcher(logging.Handler): | 
|  | 1555 | """Logging handler for collecting logged messages so you can check them later""" | 
|  | 1556 | def __init__(self): | 
|  | 1557 | self.messages = [] | 
|  | 1558 | logging.Handler.__init__(self, logging.WARNING) | 
|  | 1559 | def emit(self, record): | 
|  | 1560 | self.messages.append(bb.build.logformatter.format(record)) | 
|  | 1561 | def contains(self, message): | 
|  | 1562 | return (message in self.messages) |