1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# BitBake 'Build' implementation 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# Core code for function execution and task handling in the 5*4882a593Smuzhiyun# BitBake build tools. 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun# Copyright (C) 2003, 2004 Chris Larson 8*4882a593Smuzhiyun# 9*4882a593Smuzhiyun# Based on Gentoo's portage.py. 10*4882a593Smuzhiyun# 11*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 12*4882a593Smuzhiyun# 13*4882a593Smuzhiyun# Based on functions from the base bb module, Copyright 2003 Holger Schurig 14*4882a593Smuzhiyun 15*4882a593Smuzhiyunimport os 16*4882a593Smuzhiyunimport sys 17*4882a593Smuzhiyunimport logging 18*4882a593Smuzhiyunimport glob 19*4882a593Smuzhiyunimport itertools 20*4882a593Smuzhiyunimport time 21*4882a593Smuzhiyunimport re 22*4882a593Smuzhiyunimport stat 23*4882a593Smuzhiyunimport bb 24*4882a593Smuzhiyunimport bb.msg 25*4882a593Smuzhiyunimport bb.process 26*4882a593Smuzhiyunimport bb.progress 27*4882a593Smuzhiyunfrom bb import data, event, utils 28*4882a593Smuzhiyun 29*4882a593Smuzhiyunbblogger = logging.getLogger('BitBake') 30*4882a593Smuzhiyunlogger = logging.getLogger('BitBake.Build') 31*4882a593Smuzhiyun 32*4882a593SmuzhiyunverboseShellLogging = False 33*4882a593SmuzhiyunverboseStdoutLogging = False 34*4882a593Smuzhiyun 35*4882a593Smuzhiyun__mtime_cache = {} 36*4882a593Smuzhiyun 37*4882a593Smuzhiyundef cached_mtime_noerror(f): 38*4882a593Smuzhiyun if f not in __mtime_cache: 39*4882a593Smuzhiyun try: 40*4882a593Smuzhiyun __mtime_cache[f] = os.stat(f)[stat.ST_MTIME] 41*4882a593Smuzhiyun except OSError: 42*4882a593Smuzhiyun return 0 43*4882a593Smuzhiyun return __mtime_cache[f] 44*4882a593Smuzhiyun 45*4882a593Smuzhiyundef reset_cache(): 46*4882a593Smuzhiyun global __mtime_cache 47*4882a593Smuzhiyun __mtime_cache = {} 48*4882a593Smuzhiyun 49*4882a593Smuzhiyun# When we execute a Python function, we'd like certain things 50*4882a593Smuzhiyun# in all namespaces, hence we add them to __builtins__. 51*4882a593Smuzhiyun# If we do not do this and use the exec globals, they will 52*4882a593Smuzhiyun# not be available to subfunctions. 53*4882a593Smuzhiyunif hasattr(__builtins__, '__setitem__'): 54*4882a593Smuzhiyun builtins = __builtins__ 55*4882a593Smuzhiyunelse: 56*4882a593Smuzhiyun builtins = __builtins__.__dict__ 57*4882a593Smuzhiyun 58*4882a593Smuzhiyunbuiltins['bb'] = bb 59*4882a593Smuzhiyunbuiltins['os'] = os 60*4882a593Smuzhiyun 61*4882a593Smuzhiyunclass TaskBase(event.Event): 62*4882a593Smuzhiyun """Base class for task events""" 63*4882a593Smuzhiyun 64*4882a593Smuzhiyun def __init__(self, t, fn, logfile, d): 65*4882a593Smuzhiyun self._task = t 66*4882a593Smuzhiyun self._fn = fn 67*4882a593Smuzhiyun self._package = d.getVar("PF") 68*4882a593Smuzhiyun self._mc = d.getVar("BB_CURRENT_MC") 69*4882a593Smuzhiyun self.taskfile = d.getVar("FILE") 70*4882a593Smuzhiyun self.taskname = self._task 71*4882a593Smuzhiyun self.logfile = logfile 72*4882a593Smuzhiyun self.time = time.time() 73*4882a593Smuzhiyun self.pn = d.getVar("PN") 74*4882a593Smuzhiyun self.pv = d.getVar("PV") 75*4882a593Smuzhiyun event.Event.__init__(self) 76*4882a593Smuzhiyun self._message = "recipe %s: task %s: %s" % (d.getVar("PF"), t, self.getDisplayName()) 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun def getTask(self): 79*4882a593Smuzhiyun return self._task 80*4882a593Smuzhiyun 81*4882a593Smuzhiyun def setTask(self, task): 82*4882a593Smuzhiyun self._task = task 83*4882a593Smuzhiyun 84*4882a593Smuzhiyun def getDisplayName(self): 85*4882a593Smuzhiyun return bb.event.getName(self)[4:] 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun task = property(getTask, setTask, None, "task property") 88*4882a593Smuzhiyun 89*4882a593Smuzhiyunclass TaskStarted(TaskBase): 90*4882a593Smuzhiyun """Task execution started""" 91*4882a593Smuzhiyun def __init__(self, t, fn, logfile, taskflags, d): 92*4882a593Smuzhiyun super(TaskStarted, self).__init__(t, fn, logfile, d) 93*4882a593Smuzhiyun self.taskflags = taskflags 94*4882a593Smuzhiyun 95*4882a593Smuzhiyunclass TaskSucceeded(TaskBase): 96*4882a593Smuzhiyun """Task execution completed""" 97*4882a593Smuzhiyun 98*4882a593Smuzhiyunclass TaskFailed(TaskBase): 99*4882a593Smuzhiyun """Task execution failed""" 100*4882a593Smuzhiyun 101*4882a593Smuzhiyun def __init__(self, task, fn, logfile, metadata, errprinted = False): 102*4882a593Smuzhiyun self.errprinted = errprinted 103*4882a593Smuzhiyun super(TaskFailed, self).__init__(task, fn, logfile, metadata) 104*4882a593Smuzhiyun 105*4882a593Smuzhiyunclass TaskFailedSilent(TaskBase): 106*4882a593Smuzhiyun """Task execution failed (silently)""" 107*4882a593Smuzhiyun def getDisplayName(self): 108*4882a593Smuzhiyun # Don't need to tell the user it was silent 109*4882a593Smuzhiyun return "Failed" 110*4882a593Smuzhiyun 111*4882a593Smuzhiyunclass TaskInvalid(TaskBase): 112*4882a593Smuzhiyun 113*4882a593Smuzhiyun def __init__(self, task, fn, metadata): 114*4882a593Smuzhiyun super(TaskInvalid, self).__init__(task, fn, None, metadata) 115*4882a593Smuzhiyun self._message = "No such task '%s'" % task 116*4882a593Smuzhiyun 117*4882a593Smuzhiyunclass TaskProgress(event.Event): 118*4882a593Smuzhiyun """ 119*4882a593Smuzhiyun Task made some progress that could be reported to the user, usually in 120*4882a593Smuzhiyun the form of a progress bar or similar. 121*4882a593Smuzhiyun NOTE: this class does not inherit from TaskBase since it doesn't need 122*4882a593Smuzhiyun to - it's fired within the task context itself, so we don't have any of 123*4882a593Smuzhiyun the context information that you do in the case of the other events. 124*4882a593Smuzhiyun The event PID can be used to determine which task it came from. 125*4882a593Smuzhiyun The progress value is normally 0-100, but can also be negative 126*4882a593Smuzhiyun indicating that progress has been made but we aren't able to determine 127*4882a593Smuzhiyun how much. 128*4882a593Smuzhiyun The rate is optional, this is simply an extra string to display to the 129*4882a593Smuzhiyun user if specified. 130*4882a593Smuzhiyun """ 131*4882a593Smuzhiyun def __init__(self, progress, rate=None): 132*4882a593Smuzhiyun self.progress = progress 133*4882a593Smuzhiyun self.rate = rate 134*4882a593Smuzhiyun event.Event.__init__(self) 135*4882a593Smuzhiyun 136*4882a593Smuzhiyun 137*4882a593Smuzhiyunclass LogTee(object): 138*4882a593Smuzhiyun def __init__(self, logger, outfile): 139*4882a593Smuzhiyun self.outfile = outfile 140*4882a593Smuzhiyun self.logger = logger 141*4882a593Smuzhiyun self.name = self.outfile.name 142*4882a593Smuzhiyun 143*4882a593Smuzhiyun def write(self, string): 144*4882a593Smuzhiyun self.logger.plain(string) 145*4882a593Smuzhiyun self.outfile.write(string) 146*4882a593Smuzhiyun 147*4882a593Smuzhiyun def __enter__(self): 148*4882a593Smuzhiyun self.outfile.__enter__() 149*4882a593Smuzhiyun return self 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun def __exit__(self, *excinfo): 152*4882a593Smuzhiyun self.outfile.__exit__(*excinfo) 153*4882a593Smuzhiyun 154*4882a593Smuzhiyun def __repr__(self): 155*4882a593Smuzhiyun return '<LogTee {0}>'.format(self.name) 156*4882a593Smuzhiyun 157*4882a593Smuzhiyun def flush(self): 158*4882a593Smuzhiyun self.outfile.flush() 159*4882a593Smuzhiyun 160*4882a593Smuzhiyun 161*4882a593Smuzhiyunclass StdoutNoopContextManager: 162*4882a593Smuzhiyun """ 163*4882a593Smuzhiyun This class acts like sys.stdout, but adds noop __enter__ and __exit__ methods. 164*4882a593Smuzhiyun """ 165*4882a593Smuzhiyun def __enter__(self): 166*4882a593Smuzhiyun return sys.stdout 167*4882a593Smuzhiyun 168*4882a593Smuzhiyun def __exit__(self, *exc_info): 169*4882a593Smuzhiyun pass 170*4882a593Smuzhiyun 171*4882a593Smuzhiyun def write(self, string): 172*4882a593Smuzhiyun return sys.stdout.write(string) 173*4882a593Smuzhiyun 174*4882a593Smuzhiyun def flush(self): 175*4882a593Smuzhiyun sys.stdout.flush() 176*4882a593Smuzhiyun 177*4882a593Smuzhiyun @property 178*4882a593Smuzhiyun def name(self): 179*4882a593Smuzhiyun return sys.stdout.name 180*4882a593Smuzhiyun 181*4882a593Smuzhiyun 182*4882a593Smuzhiyundef exec_func(func, d, dirs = None): 183*4882a593Smuzhiyun """Execute a BB 'function'""" 184*4882a593Smuzhiyun 185*4882a593Smuzhiyun try: 186*4882a593Smuzhiyun oldcwd = os.getcwd() 187*4882a593Smuzhiyun except: 188*4882a593Smuzhiyun oldcwd = None 189*4882a593Smuzhiyun 190*4882a593Smuzhiyun flags = d.getVarFlags(func) 191*4882a593Smuzhiyun cleandirs = flags.get('cleandirs') if flags else None 192*4882a593Smuzhiyun if cleandirs: 193*4882a593Smuzhiyun for cdir in d.expand(cleandirs).split(): 194*4882a593Smuzhiyun bb.utils.remove(cdir, True) 195*4882a593Smuzhiyun bb.utils.mkdirhier(cdir) 196*4882a593Smuzhiyun 197*4882a593Smuzhiyun if flags and dirs is None: 198*4882a593Smuzhiyun dirs = flags.get('dirs') 199*4882a593Smuzhiyun if dirs: 200*4882a593Smuzhiyun dirs = d.expand(dirs).split() 201*4882a593Smuzhiyun 202*4882a593Smuzhiyun if dirs: 203*4882a593Smuzhiyun for adir in dirs: 204*4882a593Smuzhiyun bb.utils.mkdirhier(adir) 205*4882a593Smuzhiyun adir = dirs[-1] 206*4882a593Smuzhiyun else: 207*4882a593Smuzhiyun adir = None 208*4882a593Smuzhiyun 209*4882a593Smuzhiyun body = d.getVar(func, False) 210*4882a593Smuzhiyun if not body: 211*4882a593Smuzhiyun if body is None: 212*4882a593Smuzhiyun logger.warning("Function %s doesn't exist", func) 213*4882a593Smuzhiyun return 214*4882a593Smuzhiyun 215*4882a593Smuzhiyun ispython = flags.get('python') 216*4882a593Smuzhiyun 217*4882a593Smuzhiyun lockflag = flags.get('lockfiles') 218*4882a593Smuzhiyun if lockflag: 219*4882a593Smuzhiyun lockfiles = [f for f in d.expand(lockflag).split()] 220*4882a593Smuzhiyun else: 221*4882a593Smuzhiyun lockfiles = None 222*4882a593Smuzhiyun 223*4882a593Smuzhiyun tempdir = d.getVar('T') 224*4882a593Smuzhiyun 225*4882a593Smuzhiyun # or func allows items to be executed outside of the normal 226*4882a593Smuzhiyun # task set, such as buildhistory 227*4882a593Smuzhiyun task = d.getVar('BB_RUNTASK') or func 228*4882a593Smuzhiyun if task == func: 229*4882a593Smuzhiyun taskfunc = task 230*4882a593Smuzhiyun else: 231*4882a593Smuzhiyun taskfunc = "%s.%s" % (task, func) 232*4882a593Smuzhiyun 233*4882a593Smuzhiyun runfmt = d.getVar('BB_RUNFMT') or "run.{func}.{pid}" 234*4882a593Smuzhiyun runfn = runfmt.format(taskfunc=taskfunc, task=task, func=func, pid=os.getpid()) 235*4882a593Smuzhiyun runfile = os.path.join(tempdir, runfn) 236*4882a593Smuzhiyun bb.utils.mkdirhier(os.path.dirname(runfile)) 237*4882a593Smuzhiyun 238*4882a593Smuzhiyun # Setup the courtesy link to the runfn, only for tasks 239*4882a593Smuzhiyun # we create the link 'just' before the run script is created 240*4882a593Smuzhiyun # if we create it after, and if the run script fails, then the 241*4882a593Smuzhiyun # link won't be created as an exception would be fired. 242*4882a593Smuzhiyun if task == func: 243*4882a593Smuzhiyun runlink = os.path.join(tempdir, 'run.{0}'.format(task)) 244*4882a593Smuzhiyun if runlink: 245*4882a593Smuzhiyun bb.utils.remove(runlink) 246*4882a593Smuzhiyun 247*4882a593Smuzhiyun try: 248*4882a593Smuzhiyun os.symlink(runfn, runlink) 249*4882a593Smuzhiyun except OSError: 250*4882a593Smuzhiyun pass 251*4882a593Smuzhiyun 252*4882a593Smuzhiyun with bb.utils.fileslocked(lockfiles): 253*4882a593Smuzhiyun if ispython: 254*4882a593Smuzhiyun exec_func_python(func, d, runfile, cwd=adir) 255*4882a593Smuzhiyun else: 256*4882a593Smuzhiyun exec_func_shell(func, d, runfile, cwd=adir) 257*4882a593Smuzhiyun 258*4882a593Smuzhiyun try: 259*4882a593Smuzhiyun curcwd = os.getcwd() 260*4882a593Smuzhiyun except: 261*4882a593Smuzhiyun curcwd = None 262*4882a593Smuzhiyun 263*4882a593Smuzhiyun if oldcwd and curcwd != oldcwd: 264*4882a593Smuzhiyun try: 265*4882a593Smuzhiyun bb.warn("Task %s changed cwd to %s" % (func, curcwd)) 266*4882a593Smuzhiyun os.chdir(oldcwd) 267*4882a593Smuzhiyun except: 268*4882a593Smuzhiyun pass 269*4882a593Smuzhiyun 270*4882a593Smuzhiyun_functionfmt = """ 271*4882a593Smuzhiyun{function}(d) 272*4882a593Smuzhiyun""" 273*4882a593Smuzhiyunlogformatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s") 274*4882a593Smuzhiyundef exec_func_python(func, d, runfile, cwd=None): 275*4882a593Smuzhiyun """Execute a python BB 'function'""" 276*4882a593Smuzhiyun 277*4882a593Smuzhiyun code = _functionfmt.format(function=func) 278*4882a593Smuzhiyun bb.utils.mkdirhier(os.path.dirname(runfile)) 279*4882a593Smuzhiyun with open(runfile, 'w') as script: 280*4882a593Smuzhiyun bb.data.emit_func_python(func, script, d) 281*4882a593Smuzhiyun 282*4882a593Smuzhiyun if cwd: 283*4882a593Smuzhiyun try: 284*4882a593Smuzhiyun olddir = os.getcwd() 285*4882a593Smuzhiyun except OSError as e: 286*4882a593Smuzhiyun bb.warn("%s: Cannot get cwd: %s" % (func, e)) 287*4882a593Smuzhiyun olddir = None 288*4882a593Smuzhiyun os.chdir(cwd) 289*4882a593Smuzhiyun 290*4882a593Smuzhiyun bb.debug(2, "Executing python function %s" % func) 291*4882a593Smuzhiyun 292*4882a593Smuzhiyun try: 293*4882a593Smuzhiyun text = "def %s(d):\n%s" % (func, d.getVar(func, False)) 294*4882a593Smuzhiyun fn = d.getVarFlag(func, "filename", False) 295*4882a593Smuzhiyun lineno = int(d.getVarFlag(func, "lineno", False)) 296*4882a593Smuzhiyun bb.methodpool.insert_method(func, text, fn, lineno - 1) 297*4882a593Smuzhiyun 298*4882a593Smuzhiyun comp = utils.better_compile(code, func, "exec_func_python() autogenerated") 299*4882a593Smuzhiyun utils.better_exec(comp, {"d": d}, code, "exec_func_python() autogenerated") 300*4882a593Smuzhiyun finally: 301*4882a593Smuzhiyun # We want any stdout/stderr to be printed before any other log messages to make debugging 302*4882a593Smuzhiyun # more accurate. In some cases we seem to lose stdout/stderr entirely in logging tests without this. 303*4882a593Smuzhiyun sys.stdout.flush() 304*4882a593Smuzhiyun sys.stderr.flush() 305*4882a593Smuzhiyun bb.debug(2, "Python function %s finished" % func) 306*4882a593Smuzhiyun 307*4882a593Smuzhiyun if cwd and olddir: 308*4882a593Smuzhiyun try: 309*4882a593Smuzhiyun os.chdir(olddir) 310*4882a593Smuzhiyun except OSError as e: 311*4882a593Smuzhiyun bb.warn("%s: Cannot restore cwd %s: %s" % (func, olddir, e)) 312*4882a593Smuzhiyun 313*4882a593Smuzhiyundef shell_trap_code(): 314*4882a593Smuzhiyun return '''#!/bin/sh\n 315*4882a593Smuzhiyun__BITBAKE_LAST_LINE=0 316*4882a593Smuzhiyun 317*4882a593Smuzhiyun# Emit a useful diagnostic if something fails: 318*4882a593Smuzhiyunbb_sh_exit_handler() { 319*4882a593Smuzhiyun ret=$? 320*4882a593Smuzhiyun if [ "$ret" != 0 ]; then 321*4882a593Smuzhiyun echo "WARNING: exit code $ret from a shell command." 322*4882a593Smuzhiyun fi 323*4882a593Smuzhiyun exit $ret 324*4882a593Smuzhiyun} 325*4882a593Smuzhiyun 326*4882a593Smuzhiyunbb_bash_exit_handler() { 327*4882a593Smuzhiyun ret=$? 328*4882a593Smuzhiyun { set +x; } > /dev/null 329*4882a593Smuzhiyun trap "" DEBUG 330*4882a593Smuzhiyun if [ "$ret" != 0 ]; then 331*4882a593Smuzhiyun echo "WARNING: ${BASH_SOURCE[0]}:${__BITBAKE_LAST_LINE} exit $ret from '$1'" 332*4882a593Smuzhiyun 333*4882a593Smuzhiyun echo "WARNING: Backtrace (BB generated script): " 334*4882a593Smuzhiyun for i in $(seq 1 $((${#FUNCNAME[@]} - 1))); do 335*4882a593Smuzhiyun if [ "$i" -eq 1 ]; then 336*4882a593Smuzhiyun echo -e "\t#$((i)): ${FUNCNAME[$i]}, ${BASH_SOURCE[$((i-1))]}, line ${__BITBAKE_LAST_LINE}" 337*4882a593Smuzhiyun else 338*4882a593Smuzhiyun echo -e "\t#$((i)): ${FUNCNAME[$i]}, ${BASH_SOURCE[$((i-1))]}, line ${BASH_LINENO[$((i-1))]}" 339*4882a593Smuzhiyun fi 340*4882a593Smuzhiyun done 341*4882a593Smuzhiyun fi 342*4882a593Smuzhiyun exit $ret 343*4882a593Smuzhiyun} 344*4882a593Smuzhiyun 345*4882a593Smuzhiyunbb_bash_debug_handler() { 346*4882a593Smuzhiyun local line=${BASH_LINENO[0]} 347*4882a593Smuzhiyun # For some reason the DEBUG trap trips with lineno=1 when scripts exit; ignore it 348*4882a593Smuzhiyun if [ "$line" -eq 1 ]; then 349*4882a593Smuzhiyun return 350*4882a593Smuzhiyun fi 351*4882a593Smuzhiyun 352*4882a593Smuzhiyun # Track the line number of commands as they execute. This is so we can have access to the failing line number 353*4882a593Smuzhiyun # in the EXIT trap. See http://gnu-bash.2382.n7.nabble.com/trap-echo-quot-trap-exit-on-LINENO-quot-EXIT-gt-wrong-linenumber-td3666.html 354*4882a593Smuzhiyun if [ "${FUNCNAME[1]}" != "bb_bash_exit_handler" ]; then 355*4882a593Smuzhiyun __BITBAKE_LAST_LINE=$line 356*4882a593Smuzhiyun fi 357*4882a593Smuzhiyun} 358*4882a593Smuzhiyun 359*4882a593Smuzhiyuncase $BASH_VERSION in 360*4882a593Smuzhiyun"") trap 'bb_sh_exit_handler' 0 361*4882a593Smuzhiyun set -e 362*4882a593Smuzhiyun ;; 363*4882a593Smuzhiyun*) trap 'bb_bash_exit_handler "$BASH_COMMAND"' 0 364*4882a593Smuzhiyun trap '{ bb_bash_debug_handler; } 2>/dev/null' DEBUG 365*4882a593Smuzhiyun set -e 366*4882a593Smuzhiyun shopt -s extdebug 367*4882a593Smuzhiyun ;; 368*4882a593Smuzhiyunesac 369*4882a593Smuzhiyun''' 370*4882a593Smuzhiyun 371*4882a593Smuzhiyundef create_progress_handler(func, progress, logfile, d): 372*4882a593Smuzhiyun if progress == 'percent': 373*4882a593Smuzhiyun # Use default regex 374*4882a593Smuzhiyun return bb.progress.BasicProgressHandler(d, outfile=logfile) 375*4882a593Smuzhiyun elif progress.startswith('percent:'): 376*4882a593Smuzhiyun # Use specified regex 377*4882a593Smuzhiyun return bb.progress.BasicProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile) 378*4882a593Smuzhiyun elif progress.startswith('outof:'): 379*4882a593Smuzhiyun # Use specified regex 380*4882a593Smuzhiyun return bb.progress.OutOfProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile) 381*4882a593Smuzhiyun elif progress.startswith("custom:"): 382*4882a593Smuzhiyun # Use a custom progress handler that was injected via OE_EXTRA_IMPORTS or __builtins__ 383*4882a593Smuzhiyun import functools 384*4882a593Smuzhiyun from types import ModuleType 385*4882a593Smuzhiyun 386*4882a593Smuzhiyun parts = progress.split(":", 2) 387*4882a593Smuzhiyun _, cls, otherargs = parts[0], parts[1], (parts[2] or None) if parts[2:] else None 388*4882a593Smuzhiyun if cls: 389*4882a593Smuzhiyun def resolve(x, y): 390*4882a593Smuzhiyun if not x: 391*4882a593Smuzhiyun return None 392*4882a593Smuzhiyun if isinstance(x, ModuleType): 393*4882a593Smuzhiyun return getattr(x, y, None) 394*4882a593Smuzhiyun return x.get(y) 395*4882a593Smuzhiyun cls_obj = functools.reduce(resolve, cls.split("."), bb.utils._context) 396*4882a593Smuzhiyun if not cls_obj: 397*4882a593Smuzhiyun # Fall-back on __builtins__ 398*4882a593Smuzhiyun cls_obj = functools.reduce(resolve, cls.split("."), __builtins__) 399*4882a593Smuzhiyun if cls_obj: 400*4882a593Smuzhiyun return cls_obj(d, outfile=logfile, otherargs=otherargs) 401*4882a593Smuzhiyun bb.warn('%s: unknown custom progress handler in task progress varflag value "%s", ignoring' % (func, cls)) 402*4882a593Smuzhiyun else: 403*4882a593Smuzhiyun bb.warn('%s: invalid task progress varflag value "%s", ignoring' % (func, progress)) 404*4882a593Smuzhiyun 405*4882a593Smuzhiyun return logfile 406*4882a593Smuzhiyun 407*4882a593Smuzhiyundef exec_func_shell(func, d, runfile, cwd=None): 408*4882a593Smuzhiyun """Execute a shell function from the metadata 409*4882a593Smuzhiyun 410*4882a593Smuzhiyun Note on directory behavior. The 'dirs' varflag should contain a list 411*4882a593Smuzhiyun of the directories you need created prior to execution. The last 412*4882a593Smuzhiyun item in the list is where we will chdir/cd to. 413*4882a593Smuzhiyun """ 414*4882a593Smuzhiyun 415*4882a593Smuzhiyun # Don't let the emitted shell script override PWD 416*4882a593Smuzhiyun d.delVarFlag('PWD', 'export') 417*4882a593Smuzhiyun 418*4882a593Smuzhiyun with open(runfile, 'w') as script: 419*4882a593Smuzhiyun script.write(shell_trap_code()) 420*4882a593Smuzhiyun 421*4882a593Smuzhiyun bb.data.emit_func(func, script, d) 422*4882a593Smuzhiyun 423*4882a593Smuzhiyun if verboseShellLogging or bb.utils.to_boolean(d.getVar("BB_VERBOSE_LOGS", False)): 424*4882a593Smuzhiyun script.write("set -x\n") 425*4882a593Smuzhiyun if cwd: 426*4882a593Smuzhiyun script.write("cd '%s'\n" % cwd) 427*4882a593Smuzhiyun script.write("%s\n" % func) 428*4882a593Smuzhiyun script.write(''' 429*4882a593Smuzhiyun# cleanup 430*4882a593Smuzhiyunret=$? 431*4882a593Smuzhiyuntrap '' 0 432*4882a593Smuzhiyunexit $ret 433*4882a593Smuzhiyun''') 434*4882a593Smuzhiyun 435*4882a593Smuzhiyun os.chmod(runfile, 0o775) 436*4882a593Smuzhiyun 437*4882a593Smuzhiyun cmd = runfile 438*4882a593Smuzhiyun if d.getVarFlag(func, 'fakeroot', False): 439*4882a593Smuzhiyun fakerootcmd = d.getVar('FAKEROOT') 440*4882a593Smuzhiyun if fakerootcmd: 441*4882a593Smuzhiyun cmd = [fakerootcmd, runfile] 442*4882a593Smuzhiyun 443*4882a593Smuzhiyun if verboseStdoutLogging: 444*4882a593Smuzhiyun logfile = LogTee(logger, StdoutNoopContextManager()) 445*4882a593Smuzhiyun else: 446*4882a593Smuzhiyun logfile = StdoutNoopContextManager() 447*4882a593Smuzhiyun 448*4882a593Smuzhiyun progress = d.getVarFlag(func, 'progress') 449*4882a593Smuzhiyun if progress: 450*4882a593Smuzhiyun try: 451*4882a593Smuzhiyun logfile = create_progress_handler(func, progress, logfile, d) 452*4882a593Smuzhiyun except: 453*4882a593Smuzhiyun from traceback import format_exc 454*4882a593Smuzhiyun logger.error("Failed to create progress handler") 455*4882a593Smuzhiyun logger.error(format_exc()) 456*4882a593Smuzhiyun raise 457*4882a593Smuzhiyun 458*4882a593Smuzhiyun fifobuffer = bytearray() 459*4882a593Smuzhiyun def readfifo(data): 460*4882a593Smuzhiyun nonlocal fifobuffer 461*4882a593Smuzhiyun fifobuffer.extend(data) 462*4882a593Smuzhiyun while fifobuffer: 463*4882a593Smuzhiyun message, token, nextmsg = fifobuffer.partition(b"\00") 464*4882a593Smuzhiyun if token: 465*4882a593Smuzhiyun splitval = message.split(b' ', 1) 466*4882a593Smuzhiyun cmd = splitval[0].decode("utf-8") 467*4882a593Smuzhiyun if len(splitval) > 1: 468*4882a593Smuzhiyun value = splitval[1].decode("utf-8") 469*4882a593Smuzhiyun else: 470*4882a593Smuzhiyun value = '' 471*4882a593Smuzhiyun if cmd == 'bbplain': 472*4882a593Smuzhiyun bb.plain(value) 473*4882a593Smuzhiyun elif cmd == 'bbnote': 474*4882a593Smuzhiyun bb.note(value) 475*4882a593Smuzhiyun elif cmd == 'bbverbnote': 476*4882a593Smuzhiyun bb.verbnote(value) 477*4882a593Smuzhiyun elif cmd == 'bbwarn': 478*4882a593Smuzhiyun bb.warn(value) 479*4882a593Smuzhiyun elif cmd == 'bberror': 480*4882a593Smuzhiyun bb.error(value) 481*4882a593Smuzhiyun elif cmd == 'bbfatal': 482*4882a593Smuzhiyun # The caller will call exit themselves, so bb.error() is 483*4882a593Smuzhiyun # what we want here rather than bb.fatal() 484*4882a593Smuzhiyun bb.error(value) 485*4882a593Smuzhiyun elif cmd == 'bbfatal_log': 486*4882a593Smuzhiyun bb.error(value, forcelog=True) 487*4882a593Smuzhiyun elif cmd == 'bbdebug': 488*4882a593Smuzhiyun splitval = value.split(' ', 1) 489*4882a593Smuzhiyun level = int(splitval[0]) 490*4882a593Smuzhiyun value = splitval[1] 491*4882a593Smuzhiyun bb.debug(level, value) 492*4882a593Smuzhiyun else: 493*4882a593Smuzhiyun bb.warn("Unrecognised command '%s' on FIFO" % cmd) 494*4882a593Smuzhiyun fifobuffer = nextmsg 495*4882a593Smuzhiyun else: 496*4882a593Smuzhiyun break 497*4882a593Smuzhiyun 498*4882a593Smuzhiyun tempdir = d.getVar('T') 499*4882a593Smuzhiyun fifopath = os.path.join(tempdir, 'fifo.%s' % os.getpid()) 500*4882a593Smuzhiyun if os.path.exists(fifopath): 501*4882a593Smuzhiyun os.unlink(fifopath) 502*4882a593Smuzhiyun os.mkfifo(fifopath) 503*4882a593Smuzhiyun with open(fifopath, 'r+b', buffering=0) as fifo: 504*4882a593Smuzhiyun try: 505*4882a593Smuzhiyun bb.debug(2, "Executing shell function %s" % func) 506*4882a593Smuzhiyun with open(os.devnull, 'r+') as stdin, logfile: 507*4882a593Smuzhiyun bb.process.run(cmd, shell=False, stdin=stdin, log=logfile, extrafiles=[(fifo,readfifo)]) 508*4882a593Smuzhiyun except bb.process.ExecutionError as exe: 509*4882a593Smuzhiyun # Find the backtrace that the shell trap generated 510*4882a593Smuzhiyun backtrace_marker_regex = re.compile(r"WARNING: Backtrace \(BB generated script\)") 511*4882a593Smuzhiyun stdout_lines = (exe.stdout or "").split("\n") 512*4882a593Smuzhiyun backtrace_start_line = None 513*4882a593Smuzhiyun for i, line in enumerate(reversed(stdout_lines)): 514*4882a593Smuzhiyun if backtrace_marker_regex.search(line): 515*4882a593Smuzhiyun backtrace_start_line = len(stdout_lines) - i 516*4882a593Smuzhiyun break 517*4882a593Smuzhiyun 518*4882a593Smuzhiyun # Read the backtrace frames, starting at the location we just found 519*4882a593Smuzhiyun backtrace_entry_regex = re.compile(r"#(?P<frameno>\d+): (?P<funcname>[^\s]+), (?P<file>.+?), line (" 520*4882a593Smuzhiyun r"?P<lineno>\d+)") 521*4882a593Smuzhiyun backtrace_frames = [] 522*4882a593Smuzhiyun if backtrace_start_line: 523*4882a593Smuzhiyun for line in itertools.islice(stdout_lines, backtrace_start_line, None): 524*4882a593Smuzhiyun match = backtrace_entry_regex.search(line) 525*4882a593Smuzhiyun if match: 526*4882a593Smuzhiyun backtrace_frames.append(match.groupdict()) 527*4882a593Smuzhiyun 528*4882a593Smuzhiyun with open(runfile, "r") as script: 529*4882a593Smuzhiyun script_lines = [line.rstrip() for line in script.readlines()] 530*4882a593Smuzhiyun 531*4882a593Smuzhiyun # For each backtrace frame, search backwards in the script (from the line number called out by the frame), 532*4882a593Smuzhiyun # to find the comment that emit_vars injected when it wrote the script. This will give us the metadata 533*4882a593Smuzhiyun # filename (e.g. .bb or .bbclass) and line number where the shell function was originally defined. 534*4882a593Smuzhiyun script_metadata_comment_regex = re.compile(r"# line: (?P<lineno>\d+), file: (?P<file>.+)") 535*4882a593Smuzhiyun better_frames = [] 536*4882a593Smuzhiyun # Skip the very last frame since it's just the call to the shell task in the body of the script 537*4882a593Smuzhiyun for frame in backtrace_frames[:-1]: 538*4882a593Smuzhiyun # Check whether the frame corresponds to a function defined in the script vs external script. 539*4882a593Smuzhiyun if os.path.samefile(frame["file"], runfile): 540*4882a593Smuzhiyun # Search backwards from the frame lineno to locate the comment that BB injected 541*4882a593Smuzhiyun i = int(frame["lineno"]) - 1 542*4882a593Smuzhiyun while i >= 0: 543*4882a593Smuzhiyun match = script_metadata_comment_regex.match(script_lines[i]) 544*4882a593Smuzhiyun if match: 545*4882a593Smuzhiyun # Calculate the relative line in the function itself 546*4882a593Smuzhiyun relative_line_in_function = int(frame["lineno"]) - i - 2 547*4882a593Smuzhiyun # Calculate line in the function as declared in the metadata 548*4882a593Smuzhiyun metadata_function_line = relative_line_in_function + int(match["lineno"]) 549*4882a593Smuzhiyun better_frames.append("#{frameno}: {funcname}, {file}, line {lineno}".format( 550*4882a593Smuzhiyun frameno=frame["frameno"], 551*4882a593Smuzhiyun funcname=frame["funcname"], 552*4882a593Smuzhiyun file=match["file"], 553*4882a593Smuzhiyun lineno=metadata_function_line 554*4882a593Smuzhiyun )) 555*4882a593Smuzhiyun break 556*4882a593Smuzhiyun i -= 1 557*4882a593Smuzhiyun else: 558*4882a593Smuzhiyun better_frames.append("#{frameno}: {funcname}, {file}, line {lineno}".format(**frame)) 559*4882a593Smuzhiyun 560*4882a593Smuzhiyun if better_frames: 561*4882a593Smuzhiyun better_frames = ("\t{0}".format(frame) for frame in better_frames) 562*4882a593Smuzhiyun exe.extra_message = "\nBacktrace (metadata-relative locations):\n{0}".format("\n".join(better_frames)) 563*4882a593Smuzhiyun raise 564*4882a593Smuzhiyun finally: 565*4882a593Smuzhiyun os.unlink(fifopath) 566*4882a593Smuzhiyun 567*4882a593Smuzhiyun bb.debug(2, "Shell function %s finished" % func) 568*4882a593Smuzhiyun 569*4882a593Smuzhiyundef _task_data(fn, task, d): 570*4882a593Smuzhiyun localdata = bb.data.createCopy(d) 571*4882a593Smuzhiyun localdata.setVar('BB_FILENAME', fn) 572*4882a593Smuzhiyun localdata.setVar('OVERRIDES', 'task-%s:%s' % 573*4882a593Smuzhiyun (task[3:].replace('_', '-'), d.getVar('OVERRIDES', False))) 574*4882a593Smuzhiyun localdata.finalize() 575*4882a593Smuzhiyun bb.data.expandKeys(localdata) 576*4882a593Smuzhiyun return localdata 577*4882a593Smuzhiyun 578*4882a593Smuzhiyundef _exec_task(fn, task, d, quieterr): 579*4882a593Smuzhiyun """Execute a BB 'task' 580*4882a593Smuzhiyun 581*4882a593Smuzhiyun Execution of a task involves a bit more setup than executing a function, 582*4882a593Smuzhiyun running it with its own local metadata, and with some useful variables set. 583*4882a593Smuzhiyun """ 584*4882a593Smuzhiyun if not d.getVarFlag(task, 'task', False): 585*4882a593Smuzhiyun event.fire(TaskInvalid(task, fn, d), d) 586*4882a593Smuzhiyun logger.error("No such task: %s" % task) 587*4882a593Smuzhiyun return 1 588*4882a593Smuzhiyun 589*4882a593Smuzhiyun logger.debug("Executing task %s", task) 590*4882a593Smuzhiyun 591*4882a593Smuzhiyun localdata = _task_data(fn, task, d) 592*4882a593Smuzhiyun tempdir = localdata.getVar('T') 593*4882a593Smuzhiyun if not tempdir: 594*4882a593Smuzhiyun bb.fatal("T variable not set, unable to build") 595*4882a593Smuzhiyun 596*4882a593Smuzhiyun # Change nice level if we're asked to 597*4882a593Smuzhiyun nice = localdata.getVar("BB_TASK_NICE_LEVEL") 598*4882a593Smuzhiyun if nice: 599*4882a593Smuzhiyun curnice = os.nice(0) 600*4882a593Smuzhiyun nice = int(nice) - curnice 601*4882a593Smuzhiyun newnice = os.nice(nice) 602*4882a593Smuzhiyun logger.debug("Renice to %s " % newnice) 603*4882a593Smuzhiyun ionice = localdata.getVar("BB_TASK_IONICE_LEVEL") 604*4882a593Smuzhiyun if ionice: 605*4882a593Smuzhiyun try: 606*4882a593Smuzhiyun cls, prio = ionice.split(".", 1) 607*4882a593Smuzhiyun bb.utils.ioprio_set(os.getpid(), int(cls), int(prio)) 608*4882a593Smuzhiyun except: 609*4882a593Smuzhiyun bb.warn("Invalid ionice level %s" % ionice) 610*4882a593Smuzhiyun 611*4882a593Smuzhiyun bb.utils.mkdirhier(tempdir) 612*4882a593Smuzhiyun 613*4882a593Smuzhiyun # Determine the logfile to generate 614*4882a593Smuzhiyun logfmt = localdata.getVar('BB_LOGFMT') or 'log.{task}.{pid}' 615*4882a593Smuzhiyun logbase = logfmt.format(task=task, pid=os.getpid()) 616*4882a593Smuzhiyun 617*4882a593Smuzhiyun # Document the order of the tasks... 618*4882a593Smuzhiyun logorder = os.path.join(tempdir, 'log.task_order') 619*4882a593Smuzhiyun try: 620*4882a593Smuzhiyun with open(logorder, 'a') as logorderfile: 621*4882a593Smuzhiyun logorderfile.write('{0} ({1}): {2}\n'.format(task, os.getpid(), logbase)) 622*4882a593Smuzhiyun except OSError: 623*4882a593Smuzhiyun logger.exception("Opening log file '%s'", logorder) 624*4882a593Smuzhiyun pass 625*4882a593Smuzhiyun 626*4882a593Smuzhiyun # Setup the courtesy link to the logfn 627*4882a593Smuzhiyun loglink = os.path.join(tempdir, 'log.{0}'.format(task)) 628*4882a593Smuzhiyun logfn = os.path.join(tempdir, logbase) 629*4882a593Smuzhiyun if loglink: 630*4882a593Smuzhiyun bb.utils.remove(loglink) 631*4882a593Smuzhiyun 632*4882a593Smuzhiyun try: 633*4882a593Smuzhiyun os.symlink(logbase, loglink) 634*4882a593Smuzhiyun except OSError: 635*4882a593Smuzhiyun pass 636*4882a593Smuzhiyun 637*4882a593Smuzhiyun prefuncs = localdata.getVarFlag(task, 'prefuncs', expand=True) 638*4882a593Smuzhiyun postfuncs = localdata.getVarFlag(task, 'postfuncs', expand=True) 639*4882a593Smuzhiyun 640*4882a593Smuzhiyun class ErrorCheckHandler(logging.Handler): 641*4882a593Smuzhiyun def __init__(self): 642*4882a593Smuzhiyun self.triggered = False 643*4882a593Smuzhiyun logging.Handler.__init__(self, logging.ERROR) 644*4882a593Smuzhiyun def emit(self, record): 645*4882a593Smuzhiyun if getattr(record, 'forcelog', False): 646*4882a593Smuzhiyun self.triggered = False 647*4882a593Smuzhiyun else: 648*4882a593Smuzhiyun self.triggered = True 649*4882a593Smuzhiyun 650*4882a593Smuzhiyun # Handle logfiles 651*4882a593Smuzhiyun try: 652*4882a593Smuzhiyun bb.utils.mkdirhier(os.path.dirname(logfn)) 653*4882a593Smuzhiyun logfile = open(logfn, 'w') 654*4882a593Smuzhiyun except OSError: 655*4882a593Smuzhiyun logger.exception("Opening log file '%s'", logfn) 656*4882a593Smuzhiyun pass 657*4882a593Smuzhiyun 658*4882a593Smuzhiyun # Dup the existing fds so we dont lose them 659*4882a593Smuzhiyun osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()] 660*4882a593Smuzhiyun oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()] 661*4882a593Smuzhiyun ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()] 662*4882a593Smuzhiyun 663*4882a593Smuzhiyun # Replace those fds with our own 664*4882a593Smuzhiyun with open('/dev/null', 'r') as si: 665*4882a593Smuzhiyun os.dup2(si.fileno(), osi[1]) 666*4882a593Smuzhiyun os.dup2(logfile.fileno(), oso[1]) 667*4882a593Smuzhiyun os.dup2(logfile.fileno(), ose[1]) 668*4882a593Smuzhiyun 669*4882a593Smuzhiyun # Ensure Python logging goes to the logfile 670*4882a593Smuzhiyun handler = logging.StreamHandler(logfile) 671*4882a593Smuzhiyun handler.setFormatter(logformatter) 672*4882a593Smuzhiyun # Always enable full debug output into task logfiles 673*4882a593Smuzhiyun handler.setLevel(logging.DEBUG - 2) 674*4882a593Smuzhiyun bblogger.addHandler(handler) 675*4882a593Smuzhiyun 676*4882a593Smuzhiyun errchk = ErrorCheckHandler() 677*4882a593Smuzhiyun bblogger.addHandler(errchk) 678*4882a593Smuzhiyun 679*4882a593Smuzhiyun localdata.setVar('BB_LOGFILE', logfn) 680*4882a593Smuzhiyun localdata.setVar('BB_RUNTASK', task) 681*4882a593Smuzhiyun localdata.setVar('BB_TASK_LOGGER', bblogger) 682*4882a593Smuzhiyun 683*4882a593Smuzhiyun flags = localdata.getVarFlags(task) 684*4882a593Smuzhiyun 685*4882a593Smuzhiyun try: 686*4882a593Smuzhiyun try: 687*4882a593Smuzhiyun event.fire(TaskStarted(task, fn, logfn, flags, localdata), localdata) 688*4882a593Smuzhiyun 689*4882a593Smuzhiyun for func in (prefuncs or '').split(): 690*4882a593Smuzhiyun exec_func(func, localdata) 691*4882a593Smuzhiyun exec_func(task, localdata) 692*4882a593Smuzhiyun for func in (postfuncs or '').split(): 693*4882a593Smuzhiyun exec_func(func, localdata) 694*4882a593Smuzhiyun finally: 695*4882a593Smuzhiyun # Need to flush and close the logs before sending events where the 696*4882a593Smuzhiyun # UI may try to look at the logs. 697*4882a593Smuzhiyun sys.stdout.flush() 698*4882a593Smuzhiyun sys.stderr.flush() 699*4882a593Smuzhiyun 700*4882a593Smuzhiyun bblogger.removeHandler(handler) 701*4882a593Smuzhiyun 702*4882a593Smuzhiyun # Restore the backup fds 703*4882a593Smuzhiyun os.dup2(osi[0], osi[1]) 704*4882a593Smuzhiyun os.dup2(oso[0], oso[1]) 705*4882a593Smuzhiyun os.dup2(ose[0], ose[1]) 706*4882a593Smuzhiyun 707*4882a593Smuzhiyun # Close the backup fds 708*4882a593Smuzhiyun os.close(osi[0]) 709*4882a593Smuzhiyun os.close(oso[0]) 710*4882a593Smuzhiyun os.close(ose[0]) 711*4882a593Smuzhiyun 712*4882a593Smuzhiyun logfile.close() 713*4882a593Smuzhiyun if os.path.exists(logfn) and os.path.getsize(logfn) == 0: 714*4882a593Smuzhiyun logger.debug2("Zero size logfn %s, removing", logfn) 715*4882a593Smuzhiyun bb.utils.remove(logfn) 716*4882a593Smuzhiyun bb.utils.remove(loglink) 717*4882a593Smuzhiyun except (Exception, SystemExit) as exc: 718*4882a593Smuzhiyun handled = False 719*4882a593Smuzhiyun if isinstance(exc, bb.BBHandledException): 720*4882a593Smuzhiyun handled = True 721*4882a593Smuzhiyun 722*4882a593Smuzhiyun if quieterr: 723*4882a593Smuzhiyun if not handled: 724*4882a593Smuzhiyun logger.warning(repr(exc)) 725*4882a593Smuzhiyun event.fire(TaskFailedSilent(task, fn, logfn, localdata), localdata) 726*4882a593Smuzhiyun else: 727*4882a593Smuzhiyun errprinted = errchk.triggered 728*4882a593Smuzhiyun # If the output is already on stdout, we've printed the information in the 729*4882a593Smuzhiyun # logs once already so don't duplicate 730*4882a593Smuzhiyun if verboseStdoutLogging or handled: 731*4882a593Smuzhiyun errprinted = True 732*4882a593Smuzhiyun if not handled: 733*4882a593Smuzhiyun logger.error(repr(exc)) 734*4882a593Smuzhiyun event.fire(TaskFailed(task, fn, logfn, localdata, errprinted), localdata) 735*4882a593Smuzhiyun return 1 736*4882a593Smuzhiyun 737*4882a593Smuzhiyun event.fire(TaskSucceeded(task, fn, logfn, localdata), localdata) 738*4882a593Smuzhiyun 739*4882a593Smuzhiyun if not localdata.getVarFlag(task, 'nostamp', False) and not localdata.getVarFlag(task, 'selfstamp', False): 740*4882a593Smuzhiyun make_stamp(task, localdata) 741*4882a593Smuzhiyun 742*4882a593Smuzhiyun return 0 743*4882a593Smuzhiyun 744*4882a593Smuzhiyundef exec_task(fn, task, d, profile = False): 745*4882a593Smuzhiyun try: 746*4882a593Smuzhiyun quieterr = False 747*4882a593Smuzhiyun if d.getVarFlag(task, "quieterrors", False) is not None: 748*4882a593Smuzhiyun quieterr = True 749*4882a593Smuzhiyun 750*4882a593Smuzhiyun if profile: 751*4882a593Smuzhiyun profname = "profile-%s.log" % (d.getVar("PN") + "-" + task) 752*4882a593Smuzhiyun try: 753*4882a593Smuzhiyun import cProfile as profile 754*4882a593Smuzhiyun except: 755*4882a593Smuzhiyun import profile 756*4882a593Smuzhiyun prof = profile.Profile() 757*4882a593Smuzhiyun ret = profile.Profile.runcall(prof, _exec_task, fn, task, d, quieterr) 758*4882a593Smuzhiyun prof.dump_stats(profname) 759*4882a593Smuzhiyun bb.utils.process_profilelog(profname) 760*4882a593Smuzhiyun 761*4882a593Smuzhiyun return ret 762*4882a593Smuzhiyun else: 763*4882a593Smuzhiyun return _exec_task(fn, task, d, quieterr) 764*4882a593Smuzhiyun 765*4882a593Smuzhiyun except Exception: 766*4882a593Smuzhiyun from traceback import format_exc 767*4882a593Smuzhiyun if not quieterr: 768*4882a593Smuzhiyun logger.error("Build of %s failed" % (task)) 769*4882a593Smuzhiyun logger.error(format_exc()) 770*4882a593Smuzhiyun failedevent = TaskFailed(task, None, d, True) 771*4882a593Smuzhiyun event.fire(failedevent, d) 772*4882a593Smuzhiyun return 1 773*4882a593Smuzhiyun 774*4882a593Smuzhiyundef stamp_internal(taskname, d, file_name, baseonly=False, noextra=False): 775*4882a593Smuzhiyun """ 776*4882a593Smuzhiyun Internal stamp helper function 777*4882a593Smuzhiyun Makes sure the stamp directory exists 778*4882a593Smuzhiyun Returns the stamp path+filename 779*4882a593Smuzhiyun 780*4882a593Smuzhiyun In the bitbake core, d can be a CacheData and file_name will be set. 781*4882a593Smuzhiyun When called in task context, d will be a data store, file_name will not be set 782*4882a593Smuzhiyun """ 783*4882a593Smuzhiyun taskflagname = taskname 784*4882a593Smuzhiyun if taskname.endswith("_setscene") and taskname != "do_setscene": 785*4882a593Smuzhiyun taskflagname = taskname.replace("_setscene", "") 786*4882a593Smuzhiyun 787*4882a593Smuzhiyun if file_name: 788*4882a593Smuzhiyun stamp = d.stamp[file_name] 789*4882a593Smuzhiyun extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or "" 790*4882a593Smuzhiyun else: 791*4882a593Smuzhiyun stamp = d.getVar('STAMP') 792*4882a593Smuzhiyun file_name = d.getVar('BB_FILENAME') 793*4882a593Smuzhiyun extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info') or "" 794*4882a593Smuzhiyun 795*4882a593Smuzhiyun if baseonly: 796*4882a593Smuzhiyun return stamp 797*4882a593Smuzhiyun if noextra: 798*4882a593Smuzhiyun extrainfo = "" 799*4882a593Smuzhiyun 800*4882a593Smuzhiyun if not stamp: 801*4882a593Smuzhiyun return 802*4882a593Smuzhiyun 803*4882a593Smuzhiyun stamp = bb.parse.siggen.stampfile(stamp, file_name, taskname, extrainfo) 804*4882a593Smuzhiyun 805*4882a593Smuzhiyun stampdir = os.path.dirname(stamp) 806*4882a593Smuzhiyun if cached_mtime_noerror(stampdir) == 0: 807*4882a593Smuzhiyun bb.utils.mkdirhier(stampdir) 808*4882a593Smuzhiyun 809*4882a593Smuzhiyun return stamp 810*4882a593Smuzhiyun 811*4882a593Smuzhiyundef stamp_cleanmask_internal(taskname, d, file_name): 812*4882a593Smuzhiyun """ 813*4882a593Smuzhiyun Internal stamp helper function to generate stamp cleaning mask 814*4882a593Smuzhiyun Returns the stamp path+filename 815*4882a593Smuzhiyun 816*4882a593Smuzhiyun In the bitbake core, d can be a CacheData and file_name will be set. 817*4882a593Smuzhiyun When called in task context, d will be a data store, file_name will not be set 818*4882a593Smuzhiyun """ 819*4882a593Smuzhiyun taskflagname = taskname 820*4882a593Smuzhiyun if taskname.endswith("_setscene") and taskname != "do_setscene": 821*4882a593Smuzhiyun taskflagname = taskname.replace("_setscene", "") 822*4882a593Smuzhiyun 823*4882a593Smuzhiyun if file_name: 824*4882a593Smuzhiyun stamp = d.stampclean[file_name] 825*4882a593Smuzhiyun extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or "" 826*4882a593Smuzhiyun else: 827*4882a593Smuzhiyun stamp = d.getVar('STAMPCLEAN') 828*4882a593Smuzhiyun file_name = d.getVar('BB_FILENAME') 829*4882a593Smuzhiyun extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info') or "" 830*4882a593Smuzhiyun 831*4882a593Smuzhiyun if not stamp: 832*4882a593Smuzhiyun return [] 833*4882a593Smuzhiyun 834*4882a593Smuzhiyun cleanmask = bb.parse.siggen.stampcleanmask(stamp, file_name, taskname, extrainfo) 835*4882a593Smuzhiyun 836*4882a593Smuzhiyun return [cleanmask, cleanmask.replace(taskflagname, taskflagname + "_setscene")] 837*4882a593Smuzhiyun 838*4882a593Smuzhiyundef clean_stamp(task, d, file_name = None): 839*4882a593Smuzhiyun cleanmask = stamp_cleanmask_internal(task, d, file_name) 840*4882a593Smuzhiyun for mask in cleanmask: 841*4882a593Smuzhiyun for name in glob.glob(mask): 842*4882a593Smuzhiyun # Preserve sigdata files in the stamps directory 843*4882a593Smuzhiyun if "sigdata" in name or "sigbasedata" in name: 844*4882a593Smuzhiyun continue 845*4882a593Smuzhiyun # Preserve taint files in the stamps directory 846*4882a593Smuzhiyun if name.endswith('.taint'): 847*4882a593Smuzhiyun continue 848*4882a593Smuzhiyun os.unlink(name) 849*4882a593Smuzhiyun return 850*4882a593Smuzhiyun 851*4882a593Smuzhiyundef make_stamp(task, d, file_name = None): 852*4882a593Smuzhiyun """ 853*4882a593Smuzhiyun Creates/updates a stamp for a given task 854*4882a593Smuzhiyun (d can be a data dict or dataCache) 855*4882a593Smuzhiyun """ 856*4882a593Smuzhiyun clean_stamp(task, d, file_name) 857*4882a593Smuzhiyun 858*4882a593Smuzhiyun stamp = stamp_internal(task, d, file_name) 859*4882a593Smuzhiyun # Remove the file and recreate to force timestamp 860*4882a593Smuzhiyun # change on broken NFS filesystems 861*4882a593Smuzhiyun if stamp: 862*4882a593Smuzhiyun bb.utils.remove(stamp) 863*4882a593Smuzhiyun open(stamp, "w").close() 864*4882a593Smuzhiyun 865*4882a593Smuzhiyun # If we're in task context, write out a signature file for each task 866*4882a593Smuzhiyun # as it completes 867*4882a593Smuzhiyun if not task.endswith("_setscene") and task != "do_setscene" and not file_name: 868*4882a593Smuzhiyun stampbase = stamp_internal(task, d, None, True) 869*4882a593Smuzhiyun file_name = d.getVar('BB_FILENAME') 870*4882a593Smuzhiyun bb.parse.siggen.dump_sigtask(file_name, task, stampbase, True) 871*4882a593Smuzhiyun 872*4882a593Smuzhiyundef find_stale_stamps(task, d, file_name=None): 873*4882a593Smuzhiyun current = stamp_internal(task, d, file_name) 874*4882a593Smuzhiyun current2 = stamp_internal(task + "_setscene", d, file_name) 875*4882a593Smuzhiyun cleanmask = stamp_cleanmask_internal(task, d, file_name) 876*4882a593Smuzhiyun found = [] 877*4882a593Smuzhiyun for mask in cleanmask: 878*4882a593Smuzhiyun for name in glob.glob(mask): 879*4882a593Smuzhiyun if "sigdata" in name or "sigbasedata" in name: 880*4882a593Smuzhiyun continue 881*4882a593Smuzhiyun if name.endswith('.taint'): 882*4882a593Smuzhiyun continue 883*4882a593Smuzhiyun if name == current or name == current2: 884*4882a593Smuzhiyun continue 885*4882a593Smuzhiyun logger.debug2("Stampfile %s does not match %s or %s" % (name, current, current2)) 886*4882a593Smuzhiyun found.append(name) 887*4882a593Smuzhiyun return found 888*4882a593Smuzhiyun 889*4882a593Smuzhiyundef del_stamp(task, d, file_name = None): 890*4882a593Smuzhiyun """ 891*4882a593Smuzhiyun Removes a stamp for a given task 892*4882a593Smuzhiyun (d can be a data dict or dataCache) 893*4882a593Smuzhiyun """ 894*4882a593Smuzhiyun stamp = stamp_internal(task, d, file_name) 895*4882a593Smuzhiyun bb.utils.remove(stamp) 896*4882a593Smuzhiyun 897*4882a593Smuzhiyundef write_taint(task, d, file_name = None): 898*4882a593Smuzhiyun """ 899*4882a593Smuzhiyun Creates a "taint" file which will force the specified task and its 900*4882a593Smuzhiyun dependents to be re-run the next time by influencing the value of its 901*4882a593Smuzhiyun taskhash. 902*4882a593Smuzhiyun (d can be a data dict or dataCache) 903*4882a593Smuzhiyun """ 904*4882a593Smuzhiyun import uuid 905*4882a593Smuzhiyun if file_name: 906*4882a593Smuzhiyun taintfn = d.stamp[file_name] + '.' + task + '.taint' 907*4882a593Smuzhiyun else: 908*4882a593Smuzhiyun taintfn = d.getVar('STAMP') + '.' + task + '.taint' 909*4882a593Smuzhiyun bb.utils.mkdirhier(os.path.dirname(taintfn)) 910*4882a593Smuzhiyun # The specific content of the taint file is not really important, 911*4882a593Smuzhiyun # we just need it to be random, so a random UUID is used 912*4882a593Smuzhiyun with open(taintfn, 'w') as taintf: 913*4882a593Smuzhiyun taintf.write(str(uuid.uuid4())) 914*4882a593Smuzhiyun 915*4882a593Smuzhiyundef stampfile(taskname, d, file_name = None, noextra=False): 916*4882a593Smuzhiyun """ 917*4882a593Smuzhiyun Return the stamp for a given task 918*4882a593Smuzhiyun (d can be a data dict or dataCache) 919*4882a593Smuzhiyun """ 920*4882a593Smuzhiyun return stamp_internal(taskname, d, file_name, noextra=noextra) 921*4882a593Smuzhiyun 922*4882a593Smuzhiyundef add_tasks(tasklist, d): 923*4882a593Smuzhiyun task_deps = d.getVar('_task_deps', False) 924*4882a593Smuzhiyun if not task_deps: 925*4882a593Smuzhiyun task_deps = {} 926*4882a593Smuzhiyun if not 'tasks' in task_deps: 927*4882a593Smuzhiyun task_deps['tasks'] = [] 928*4882a593Smuzhiyun if not 'parents' in task_deps: 929*4882a593Smuzhiyun task_deps['parents'] = {} 930*4882a593Smuzhiyun 931*4882a593Smuzhiyun for task in tasklist: 932*4882a593Smuzhiyun task = d.expand(task) 933*4882a593Smuzhiyun 934*4882a593Smuzhiyun d.setVarFlag(task, 'task', 1) 935*4882a593Smuzhiyun 936*4882a593Smuzhiyun if not task in task_deps['tasks']: 937*4882a593Smuzhiyun task_deps['tasks'].append(task) 938*4882a593Smuzhiyun 939*4882a593Smuzhiyun flags = d.getVarFlags(task) 940*4882a593Smuzhiyun def getTask(name): 941*4882a593Smuzhiyun if not name in task_deps: 942*4882a593Smuzhiyun task_deps[name] = {} 943*4882a593Smuzhiyun if name in flags: 944*4882a593Smuzhiyun deptask = d.expand(flags[name]) 945*4882a593Smuzhiyun if name in ['noexec', 'fakeroot', 'nostamp']: 946*4882a593Smuzhiyun if deptask != '1': 947*4882a593Smuzhiyun bb.warn("In a future version of BitBake, setting the '{}' flag to something other than '1' " 948*4882a593Smuzhiyun "will result in the flag not being set. See YP bug #13808.".format(name)) 949*4882a593Smuzhiyun 950*4882a593Smuzhiyun task_deps[name][task] = deptask 951*4882a593Smuzhiyun getTask('mcdepends') 952*4882a593Smuzhiyun getTask('depends') 953*4882a593Smuzhiyun getTask('rdepends') 954*4882a593Smuzhiyun getTask('deptask') 955*4882a593Smuzhiyun getTask('rdeptask') 956*4882a593Smuzhiyun getTask('recrdeptask') 957*4882a593Smuzhiyun getTask('recideptask') 958*4882a593Smuzhiyun getTask('nostamp') 959*4882a593Smuzhiyun getTask('fakeroot') 960*4882a593Smuzhiyun getTask('noexec') 961*4882a593Smuzhiyun getTask('umask') 962*4882a593Smuzhiyun task_deps['parents'][task] = [] 963*4882a593Smuzhiyun if 'deps' in flags: 964*4882a593Smuzhiyun for dep in flags['deps']: 965*4882a593Smuzhiyun # Check and warn for "addtask task after foo" while foo does not exist 966*4882a593Smuzhiyun #if not dep in tasklist: 967*4882a593Smuzhiyun # bb.warn('%s: dependent task %s for %s does not exist' % (d.getVar('PN'), dep, task)) 968*4882a593Smuzhiyun dep = d.expand(dep) 969*4882a593Smuzhiyun task_deps['parents'][task].append(dep) 970*4882a593Smuzhiyun 971*4882a593Smuzhiyun # don't assume holding a reference 972*4882a593Smuzhiyun d.setVar('_task_deps', task_deps) 973*4882a593Smuzhiyun 974*4882a593Smuzhiyundef addtask(task, before, after, d): 975*4882a593Smuzhiyun if task[:3] != "do_": 976*4882a593Smuzhiyun task = "do_" + task 977*4882a593Smuzhiyun 978*4882a593Smuzhiyun d.setVarFlag(task, "task", 1) 979*4882a593Smuzhiyun bbtasks = d.getVar('__BBTASKS', False) or [] 980*4882a593Smuzhiyun if task not in bbtasks: 981*4882a593Smuzhiyun bbtasks.append(task) 982*4882a593Smuzhiyun d.setVar('__BBTASKS', bbtasks) 983*4882a593Smuzhiyun 984*4882a593Smuzhiyun existing = d.getVarFlag(task, "deps", False) or [] 985*4882a593Smuzhiyun if after is not None: 986*4882a593Smuzhiyun # set up deps for function 987*4882a593Smuzhiyun for entry in after.split(): 988*4882a593Smuzhiyun if entry not in existing: 989*4882a593Smuzhiyun existing.append(entry) 990*4882a593Smuzhiyun d.setVarFlag(task, "deps", existing) 991*4882a593Smuzhiyun if before is not None: 992*4882a593Smuzhiyun # set up things that depend on this func 993*4882a593Smuzhiyun for entry in before.split(): 994*4882a593Smuzhiyun existing = d.getVarFlag(entry, "deps", False) or [] 995*4882a593Smuzhiyun if task not in existing: 996*4882a593Smuzhiyun d.setVarFlag(entry, "deps", [task] + existing) 997*4882a593Smuzhiyun 998*4882a593Smuzhiyundef deltask(task, d): 999*4882a593Smuzhiyun if task[:3] != "do_": 1000*4882a593Smuzhiyun task = "do_" + task 1001*4882a593Smuzhiyun 1002*4882a593Smuzhiyun bbtasks = d.getVar('__BBTASKS', False) or [] 1003*4882a593Smuzhiyun if task in bbtasks: 1004*4882a593Smuzhiyun bbtasks.remove(task) 1005*4882a593Smuzhiyun d.delVarFlag(task, 'task') 1006*4882a593Smuzhiyun d.setVar('__BBTASKS', bbtasks) 1007*4882a593Smuzhiyun 1008*4882a593Smuzhiyun d.delVarFlag(task, 'deps') 1009*4882a593Smuzhiyun for bbtask in d.getVar('__BBTASKS', False) or []: 1010*4882a593Smuzhiyun deps = d.getVarFlag(bbtask, 'deps', False) or [] 1011*4882a593Smuzhiyun if task in deps: 1012*4882a593Smuzhiyun deps.remove(task) 1013*4882a593Smuzhiyun d.setVarFlag(bbtask, 'deps', deps) 1014*4882a593Smuzhiyun 1015*4882a593Smuzhiyundef preceedtask(task, with_recrdeptasks, d): 1016*4882a593Smuzhiyun """ 1017*4882a593Smuzhiyun Returns a set of tasks in the current recipe which were specified as 1018*4882a593Smuzhiyun precondition by the task itself ("after") or which listed themselves 1019*4882a593Smuzhiyun as precondition ("before"). Preceeding tasks specified via the 1020*4882a593Smuzhiyun "recrdeptask" are included in the result only if requested. Beware 1021*4882a593Smuzhiyun that this may lead to the task itself being listed. 1022*4882a593Smuzhiyun """ 1023*4882a593Smuzhiyun preceed = set() 1024*4882a593Smuzhiyun 1025*4882a593Smuzhiyun # Ignore tasks which don't exist 1026*4882a593Smuzhiyun tasks = d.getVar('__BBTASKS', False) 1027*4882a593Smuzhiyun if task not in tasks: 1028*4882a593Smuzhiyun return preceed 1029*4882a593Smuzhiyun 1030*4882a593Smuzhiyun preceed.update(d.getVarFlag(task, 'deps') or []) 1031*4882a593Smuzhiyun if with_recrdeptasks: 1032*4882a593Smuzhiyun recrdeptask = d.getVarFlag(task, 'recrdeptask') 1033*4882a593Smuzhiyun if recrdeptask: 1034*4882a593Smuzhiyun preceed.update(recrdeptask.split()) 1035*4882a593Smuzhiyun return preceed 1036*4882a593Smuzhiyun 1037*4882a593Smuzhiyundef tasksbetween(task_start, task_end, d): 1038*4882a593Smuzhiyun """ 1039*4882a593Smuzhiyun Return the list of tasks between two tasks in the current recipe, 1040*4882a593Smuzhiyun where task_start is to start at and task_end is the task to end at 1041*4882a593Smuzhiyun (and task_end has a dependency chain back to task_start). 1042*4882a593Smuzhiyun """ 1043*4882a593Smuzhiyun outtasks = [] 1044*4882a593Smuzhiyun tasks = list(filter(lambda k: d.getVarFlag(k, "task"), d.keys())) 1045*4882a593Smuzhiyun def follow_chain(task, endtask, chain=None): 1046*4882a593Smuzhiyun if not chain: 1047*4882a593Smuzhiyun chain = [] 1048*4882a593Smuzhiyun if task in chain: 1049*4882a593Smuzhiyun bb.fatal("Circular task dependencies as %s depends on itself via the chain %s" % (task, " -> ".join(chain))) 1050*4882a593Smuzhiyun chain.append(task) 1051*4882a593Smuzhiyun for othertask in tasks: 1052*4882a593Smuzhiyun if othertask == task: 1053*4882a593Smuzhiyun continue 1054*4882a593Smuzhiyun if task == endtask: 1055*4882a593Smuzhiyun for ctask in chain: 1056*4882a593Smuzhiyun if ctask not in outtasks: 1057*4882a593Smuzhiyun outtasks.append(ctask) 1058*4882a593Smuzhiyun else: 1059*4882a593Smuzhiyun deps = d.getVarFlag(othertask, 'deps', False) 1060*4882a593Smuzhiyun if task in deps: 1061*4882a593Smuzhiyun follow_chain(othertask, endtask, chain) 1062*4882a593Smuzhiyun chain.pop() 1063*4882a593Smuzhiyun follow_chain(task_start, task_end) 1064*4882a593Smuzhiyun return outtasks 1065