blob: e2f91fa3b70d4d37f3571077234196d6fc9c34be [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
2# BitBake 'Build' implementation
3#
4# Core code for function execution and task handling in the
5# BitBake build tools.
6#
7# Copyright (C) 2003, 2004 Chris Larson
8#
9# Based on Gentoo's portage.py.
10#
Brad Bishopc342db32019-05-15 21:57:59 -040011# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050012#
13# Based on functions from the base bb module, Copyright 2003 Holger Schurig
14
15import os
16import sys
17import logging
18import shlex
19import glob
20import time
21import stat
22import bb
23import bb.msg
24import bb.process
Patrick Williamsc0f7c042017-02-23 20:41:17 -060025import bb.progress
26from bb import data, event, utils
Patrick Williamsc124f4f2015-09-15 14:41:29 -050027
28bblogger = logging.getLogger('BitBake')
29logger = logging.getLogger('BitBake.Build')
30
Patrick Williamsc124f4f2015-09-15 14:41:29 -050031__mtime_cache = {}
32
33def cached_mtime_noerror(f):
34 if f not in __mtime_cache:
35 try:
36 __mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
37 except OSError:
38 return 0
39 return __mtime_cache[f]
40
41def reset_cache():
42 global __mtime_cache
43 __mtime_cache = {}
44
45# When we execute a Python function, we'd like certain things
46# in all namespaces, hence we add them to __builtins__.
47# If we do not do this and use the exec globals, they will
48# not be available to subfunctions.
Patrick Williamsc0f7c042017-02-23 20:41:17 -060049if hasattr(__builtins__, '__setitem__'):
50 builtins = __builtins__
51else:
52 builtins = __builtins__.__dict__
53
54builtins['bb'] = bb
55builtins['os'] = os
Patrick Williamsc124f4f2015-09-15 14:41:29 -050056
57class FuncFailed(Exception):
58 def __init__(self, name = None, logfile = None):
59 self.logfile = logfile
60 self.name = name
61 if name:
62 self.msg = 'Function failed: %s' % name
63 else:
64 self.msg = "Function failed"
65
66 def __str__(self):
67 if self.logfile and os.path.exists(self.logfile):
68 msg = ("%s (log file is located at %s)" %
69 (self.msg, self.logfile))
70 else:
71 msg = self.msg
72 return msg
73
74class TaskBase(event.Event):
75 """Base class for task events"""
76
77 def __init__(self, t, logfile, d):
78 self._task = t
Brad Bishop6e60e8b2018-02-01 10:27:11 -050079 self._package = d.getVar("PF")
80 self._mc = d.getVar("BB_CURRENT_MC")
81 self.taskfile = d.getVar("FILE")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050082 self.taskname = self._task
83 self.logfile = logfile
84 self.time = time.time()
85 event.Event.__init__(self)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050086 self._message = "recipe %s: task %s: %s" % (d.getVar("PF"), t, self.getDisplayName())
Patrick Williamsc124f4f2015-09-15 14:41:29 -050087
88 def getTask(self):
89 return self._task
90
91 def setTask(self, task):
92 self._task = task
93
94 def getDisplayName(self):
95 return bb.event.getName(self)[4:]
96
97 task = property(getTask, setTask, None, "task property")
98
99class TaskStarted(TaskBase):
100 """Task execution started"""
101 def __init__(self, t, logfile, taskflags, d):
102 super(TaskStarted, self).__init__(t, logfile, d)
103 self.taskflags = taskflags
104
105class TaskSucceeded(TaskBase):
106 """Task execution completed"""
107
108class TaskFailed(TaskBase):
109 """Task execution failed"""
110
111 def __init__(self, task, logfile, metadata, errprinted = False):
112 self.errprinted = errprinted
113 super(TaskFailed, self).__init__(task, logfile, metadata)
114
115class TaskFailedSilent(TaskBase):
116 """Task execution failed (silently)"""
117 def getDisplayName(self):
118 # Don't need to tell the user it was silent
119 return "Failed"
120
121class TaskInvalid(TaskBase):
122
123 def __init__(self, task, metadata):
124 super(TaskInvalid, self).__init__(task, None, metadata)
125 self._message = "No such task '%s'" % task
126
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600127class TaskProgress(event.Event):
128 """
129 Task made some progress that could be reported to the user, usually in
130 the form of a progress bar or similar.
131 NOTE: this class does not inherit from TaskBase since it doesn't need
132 to - it's fired within the task context itself, so we don't have any of
133 the context information that you do in the case of the other events.
134 The event PID can be used to determine which task it came from.
135 The progress value is normally 0-100, but can also be negative
136 indicating that progress has been made but we aren't able to determine
137 how much.
138 The rate is optional, this is simply an extra string to display to the
139 user if specified.
140 """
141 def __init__(self, progress, rate=None):
142 self.progress = progress
143 self.rate = rate
144 event.Event.__init__(self)
145
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500146
147class LogTee(object):
148 def __init__(self, logger, outfile):
149 self.outfile = outfile
150 self.logger = logger
151 self.name = self.outfile.name
152
153 def write(self, string):
154 self.logger.plain(string)
155 self.outfile.write(string)
156
157 def __enter__(self):
158 self.outfile.__enter__()
159 return self
160
161 def __exit__(self, *excinfo):
162 self.outfile.__exit__(*excinfo)
163
164 def __repr__(self):
165 return '<LogTee {0}>'.format(self.name)
Brad Bishop15ae2502019-06-18 21:44:24 -0400166
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500167 def flush(self):
168 self.outfile.flush()
169
Brad Bishop15ae2502019-06-18 21:44:24 -0400170
171class StdoutNoopContextManager:
172 """
173 This class acts like sys.stdout, but adds noop __enter__ and __exit__ methods.
174 """
175 def __enter__(self):
176 return sys.stdout
177
178 def __exit__(self, *exc_info):
179 pass
180
181 def write(self, string):
182 return sys.stdout.write(string)
183
184 def flush(self):
185 sys.stdout.flush()
186
187 @property
188 def name(self):
189 return sys.stdout.name
190
191
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500192#
193# pythonexception allows the python exceptions generated to be raised
Brad Bishop15ae2502019-06-18 21:44:24 -0400194# as the real exceptions (not FuncFailed) and without a backtrace at the
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500195# origin of the failure.
196#
197def exec_func(func, d, dirs = None, pythonexception=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500198 """Execute a BB 'function'"""
199
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600200 try:
201 oldcwd = os.getcwd()
202 except:
203 oldcwd = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500204
205 flags = d.getVarFlags(func)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500206 cleandirs = flags.get('cleandirs') if flags else None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500207 if cleandirs:
208 for cdir in d.expand(cleandirs).split():
209 bb.utils.remove(cdir, True)
210 bb.utils.mkdirhier(cdir)
211
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500212 if flags and dirs is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500213 dirs = flags.get('dirs')
214 if dirs:
215 dirs = d.expand(dirs).split()
216
217 if dirs:
218 for adir in dirs:
219 bb.utils.mkdirhier(adir)
220 adir = dirs[-1]
221 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600222 adir = None
223
224 body = d.getVar(func, False)
225 if not body:
226 if body is None:
227 logger.warning("Function %s doesn't exist", func)
228 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500229
230 ispython = flags.get('python')
231
232 lockflag = flags.get('lockfiles')
233 if lockflag:
234 lockfiles = [f for f in d.expand(lockflag).split()]
235 else:
236 lockfiles = None
237
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500238 tempdir = d.getVar('T')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500239
240 # or func allows items to be executed outside of the normal
241 # task set, such as buildhistory
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500242 task = d.getVar('BB_RUNTASK') or func
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500243 if task == func:
244 taskfunc = task
245 else:
246 taskfunc = "%s.%s" % (task, func)
247
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500248 runfmt = d.getVar('BB_RUNFMT') or "run.{func}.{pid}"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500249 runfn = runfmt.format(taskfunc=taskfunc, task=task, func=func, pid=os.getpid())
250 runfile = os.path.join(tempdir, runfn)
251 bb.utils.mkdirhier(os.path.dirname(runfile))
252
253 # Setup the courtesy link to the runfn, only for tasks
254 # we create the link 'just' before the run script is created
255 # if we create it after, and if the run script fails, then the
256 # link won't be created as an exception would be fired.
257 if task == func:
258 runlink = os.path.join(tempdir, 'run.{0}'.format(task))
259 if runlink:
260 bb.utils.remove(runlink)
261
262 try:
263 os.symlink(runfn, runlink)
264 except OSError:
265 pass
266
267 with bb.utils.fileslocked(lockfiles):
268 if ispython:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500269 exec_func_python(func, d, runfile, cwd=adir, pythonexception=pythonexception)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500270 else:
271 exec_func_shell(func, d, runfile, cwd=adir)
272
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600273 try:
274 curcwd = os.getcwd()
275 except:
276 curcwd = None
277
278 if oldcwd and curcwd != oldcwd:
279 try:
280 bb.warn("Task %s changed cwd to %s" % (func, curcwd))
281 os.chdir(oldcwd)
282 except:
283 pass
284
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500285_functionfmt = """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500286{function}(d)
287"""
288logformatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500289def exec_func_python(func, d, runfile, cwd=None, pythonexception=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500290 """Execute a python BB 'function'"""
291
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500292 code = _functionfmt.format(function=func)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500293 bb.utils.mkdirhier(os.path.dirname(runfile))
294 with open(runfile, 'w') as script:
295 bb.data.emit_func_python(func, script, d)
296
297 if cwd:
298 try:
299 olddir = os.getcwd()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600300 except OSError as e:
301 bb.warn("%s: Cannot get cwd: %s" % (func, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500302 olddir = None
303 os.chdir(cwd)
304
305 bb.debug(2, "Executing python function %s" % func)
306
307 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500308 text = "def %s(d):\n%s" % (func, d.getVar(func, False))
309 fn = d.getVarFlag(func, "filename", False)
310 lineno = int(d.getVarFlag(func, "lineno", False))
311 bb.methodpool.insert_method(func, text, fn, lineno - 1)
312
313 comp = utils.better_compile(code, func, "exec_python_func() autogenerated")
314 utils.better_exec(comp, {"d": d}, code, "exec_python_func() autogenerated", pythonexception=pythonexception)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500315 except (bb.parse.SkipRecipe, bb.build.FuncFailed):
316 raise
Brad Bishop19323692019-04-05 15:28:33 -0400317 except Exception as e:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500318 if pythonexception:
319 raise
Brad Bishop19323692019-04-05 15:28:33 -0400320 logger.error(str(e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500321 raise FuncFailed(func, None)
322 finally:
323 bb.debug(2, "Python function %s finished" % func)
324
325 if cwd and olddir:
326 try:
327 os.chdir(olddir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600328 except OSError as e:
329 bb.warn("%s: Cannot restore cwd %s: %s" % (func, olddir, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500330
331def shell_trap_code():
332 return '''#!/bin/sh\n
333# Emit a useful diagnostic if something fails:
334bb_exit_handler() {
335 ret=$?
336 case $ret in
337 0) ;;
338 *) case $BASH_VERSION in
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500339 "") echo "WARNING: exit code $ret from a shell command.";;
340 *) echo "WARNING: ${BASH_SOURCE[0]}:${BASH_LINENO[0]} exit $ret from '$BASH_COMMAND'";;
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500341 esac
342 exit $ret
343 esac
344}
345trap 'bb_exit_handler' 0
346set -e
347'''
348
Brad Bishop15ae2502019-06-18 21:44:24 -0400349def create_progress_handler(func, progress, logfile, d):
350 if progress == 'percent':
351 # Use default regex
352 return bb.progress.BasicProgressHandler(d, outfile=logfile)
353 elif progress.startswith('percent:'):
354 # Use specified regex
355 return bb.progress.BasicProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile)
356 elif progress.startswith('outof:'):
357 # Use specified regex
358 return bb.progress.OutOfProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile)
359 elif progress.startswith("custom:"):
360 # Use a custom progress handler that was injected via OE_EXTRA_IMPORTS or __builtins__
361 import functools
362 from types import ModuleType
363
364 parts = progress.split(":", 2)
365 _, cls, otherargs = parts[0], parts[1], (parts[2] or None) if parts[2:] else None
366 if cls:
367 def resolve(x, y):
368 if not x:
369 return None
370 if isinstance(x, ModuleType):
371 return getattr(x, y, None)
372 return x.get(y)
373 cls_obj = functools.reduce(resolve, cls.split("."), bb.utils._context)
374 if not cls_obj:
375 # Fall-back on __builtins__
376 cls_obj = functools.reduce(lambda x, y: x.get(y), cls.split("."), __builtins__)
377 if cls_obj:
378 return cls_obj(d, outfile=logfile, otherargs=otherargs)
379 bb.warn('%s: unknown custom progress handler in task progress varflag value "%s", ignoring' % (func, cls))
380 else:
381 bb.warn('%s: invalid task progress varflag value "%s", ignoring' % (func, progress))
382
383 return logfile
384
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500385def exec_func_shell(func, d, runfile, cwd=None):
386 """Execute a shell function from the metadata
387
388 Note on directory behavior. The 'dirs' varflag should contain a list
389 of the directories you need created prior to execution. The last
390 item in the list is where we will chdir/cd to.
391 """
392
393 # Don't let the emitted shell script override PWD
394 d.delVarFlag('PWD', 'export')
395
396 with open(runfile, 'w') as script:
397 script.write(shell_trap_code())
398
399 bb.data.emit_func(func, script, d)
400
401 if bb.msg.loggerVerboseLogs:
402 script.write("set -x\n")
403 if cwd:
404 script.write("cd '%s'\n" % cwd)
405 script.write("%s\n" % func)
406 script.write('''
407# cleanup
408ret=$?
409trap '' 0
410exit $ret
411''')
412
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600413 os.chmod(runfile, 0o775)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500414
415 cmd = runfile
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500416 if d.getVarFlag(func, 'fakeroot', False):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500417 fakerootcmd = d.getVar('FAKEROOT')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500418 if fakerootcmd:
419 cmd = [fakerootcmd, runfile]
420
421 if bb.msg.loggerDefaultVerbose:
Brad Bishop15ae2502019-06-18 21:44:24 -0400422 logfile = LogTee(logger, StdoutNoopContextManager())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500423 else:
Brad Bishop15ae2502019-06-18 21:44:24 -0400424 logfile = StdoutNoopContextManager()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500425
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500426 progress = d.getVarFlag(func, 'progress')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600427 if progress:
Brad Bishop15ae2502019-06-18 21:44:24 -0400428 logfile = create_progress_handler(func, progress, logfile, d)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600429
430 fifobuffer = bytearray()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500431 def readfifo(data):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600432 nonlocal fifobuffer
433 fifobuffer.extend(data)
434 while fifobuffer:
435 message, token, nextmsg = fifobuffer.partition(b"\00")
436 if token:
437 splitval = message.split(b' ', 1)
438 cmd = splitval[0].decode("utf-8")
439 if len(splitval) > 1:
440 value = splitval[1].decode("utf-8")
441 else:
442 value = ''
443 if cmd == 'bbplain':
444 bb.plain(value)
445 elif cmd == 'bbnote':
446 bb.note(value)
Brad Bishopc342db32019-05-15 21:57:59 -0400447 elif cmd == 'bbverbnote':
448 bb.verbnote(value)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600449 elif cmd == 'bbwarn':
450 bb.warn(value)
451 elif cmd == 'bberror':
452 bb.error(value)
453 elif cmd == 'bbfatal':
454 # The caller will call exit themselves, so bb.error() is
455 # what we want here rather than bb.fatal()
456 bb.error(value)
457 elif cmd == 'bbfatal_log':
458 bb.error(value, forcelog=True)
459 elif cmd == 'bbdebug':
460 splitval = value.split(' ', 1)
461 level = int(splitval[0])
462 value = splitval[1]
463 bb.debug(level, value)
464 else:
465 bb.warn("Unrecognised command '%s' on FIFO" % cmd)
466 fifobuffer = nextmsg
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500467 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600468 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500469
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500470 tempdir = d.getVar('T')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500471 fifopath = os.path.join(tempdir, 'fifo.%s' % os.getpid())
472 if os.path.exists(fifopath):
473 os.unlink(fifopath)
474 os.mkfifo(fifopath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600475 with open(fifopath, 'r+b', buffering=0) as fifo:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500476 try:
477 bb.debug(2, "Executing shell function %s" % func)
478
479 try:
Brad Bishop15ae2502019-06-18 21:44:24 -0400480 with open(os.devnull, 'r+') as stdin, logfile:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500481 bb.process.run(cmd, shell=False, stdin=stdin, log=logfile, extrafiles=[(fifo,readfifo)])
482 except bb.process.CmdError:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500483 logfn = d.getVar('BB_LOGFILE')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500484 raise FuncFailed(func, logfn)
485 finally:
486 os.unlink(fifopath)
487
488 bb.debug(2, "Shell function %s finished" % func)
489
490def _task_data(fn, task, d):
491 localdata = bb.data.createCopy(d)
492 localdata.setVar('BB_FILENAME', fn)
493 localdata.setVar('BB_CURRENTTASK', task[3:])
494 localdata.setVar('OVERRIDES', 'task-%s:%s' %
495 (task[3:].replace('_', '-'), d.getVar('OVERRIDES', False)))
496 localdata.finalize()
497 bb.data.expandKeys(localdata)
498 return localdata
499
500def _exec_task(fn, task, d, quieterr):
501 """Execute a BB 'task'
502
503 Execution of a task involves a bit more setup than executing a function,
504 running it with its own local metadata, and with some useful variables set.
505 """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500506 if not d.getVarFlag(task, 'task', False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500507 event.fire(TaskInvalid(task, d), d)
508 logger.error("No such task: %s" % task)
509 return 1
510
511 logger.debug(1, "Executing task %s", task)
512
513 localdata = _task_data(fn, task, d)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500514 tempdir = localdata.getVar('T')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500515 if not tempdir:
516 bb.fatal("T variable not set, unable to build")
517
518 # Change nice level if we're asked to
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500519 nice = localdata.getVar("BB_TASK_NICE_LEVEL")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520 if nice:
521 curnice = os.nice(0)
522 nice = int(nice) - curnice
523 newnice = os.nice(nice)
524 logger.debug(1, "Renice to %s " % newnice)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500525 ionice = localdata.getVar("BB_TASK_IONICE_LEVEL")
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500526 if ionice:
527 try:
528 cls, prio = ionice.split(".", 1)
529 bb.utils.ioprio_set(os.getpid(), int(cls), int(prio))
530 except:
531 bb.warn("Invalid ionice level %s" % ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500532
533 bb.utils.mkdirhier(tempdir)
534
535 # Determine the logfile to generate
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500536 logfmt = localdata.getVar('BB_LOGFMT') or 'log.{task}.{pid}'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500537 logbase = logfmt.format(task=task, pid=os.getpid())
538
539 # Document the order of the tasks...
540 logorder = os.path.join(tempdir, 'log.task_order')
541 try:
542 with open(logorder, 'a') as logorderfile:
543 logorderfile.write('{0} ({1}): {2}\n'.format(task, os.getpid(), logbase))
544 except OSError:
545 logger.exception("Opening log file '%s'", logorder)
546 pass
547
548 # Setup the courtesy link to the logfn
549 loglink = os.path.join(tempdir, 'log.{0}'.format(task))
550 logfn = os.path.join(tempdir, logbase)
551 if loglink:
552 bb.utils.remove(loglink)
553
554 try:
555 os.symlink(logbase, loglink)
556 except OSError:
557 pass
558
559 prefuncs = localdata.getVarFlag(task, 'prefuncs', expand=True)
560 postfuncs = localdata.getVarFlag(task, 'postfuncs', expand=True)
561
562 class ErrorCheckHandler(logging.Handler):
563 def __init__(self):
564 self.triggered = False
565 logging.Handler.__init__(self, logging.ERROR)
566 def emit(self, record):
567 if getattr(record, 'forcelog', False):
568 self.triggered = False
569 else:
570 self.triggered = True
571
572 # Handle logfiles
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500573 try:
574 bb.utils.mkdirhier(os.path.dirname(logfn))
575 logfile = open(logfn, 'w')
576 except OSError:
577 logger.exception("Opening log file '%s'", logfn)
578 pass
579
580 # Dup the existing fds so we dont lose them
581 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
582 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
583 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
584
585 # Replace those fds with our own
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800586 with open('/dev/null', 'r') as si:
587 os.dup2(si.fileno(), osi[1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500588 os.dup2(logfile.fileno(), oso[1])
589 os.dup2(logfile.fileno(), ose[1])
590
591 # Ensure Python logging goes to the logfile
592 handler = logging.StreamHandler(logfile)
593 handler.setFormatter(logformatter)
594 # Always enable full debug output into task logfiles
595 handler.setLevel(logging.DEBUG - 2)
596 bblogger.addHandler(handler)
597
598 errchk = ErrorCheckHandler()
599 bblogger.addHandler(errchk)
600
601 localdata.setVar('BB_LOGFILE', logfn)
602 localdata.setVar('BB_RUNTASK', task)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500603 localdata.setVar('BB_TASK_LOGGER', bblogger)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500604
605 flags = localdata.getVarFlags(task)
606
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500607 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600608 try:
609 event.fire(TaskStarted(task, logfn, flags, localdata), localdata)
610 except (bb.BBHandledException, SystemExit):
611 return 1
612 except FuncFailed as exc:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500613 logger.error(str(exc))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600614 return 1
615
616 try:
617 for func in (prefuncs or '').split():
618 exec_func(func, localdata)
619 exec_func(task, localdata)
620 for func in (postfuncs or '').split():
621 exec_func(func, localdata)
622 except FuncFailed as exc:
623 if quieterr:
624 event.fire(TaskFailedSilent(task, logfn, localdata), localdata)
625 else:
626 errprinted = errchk.triggered
627 logger.error(str(exc))
628 event.fire(TaskFailed(task, logfn, localdata, errprinted), localdata)
629 return 1
630 except bb.BBHandledException:
631 event.fire(TaskFailed(task, logfn, localdata, True), localdata)
632 return 1
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500633 finally:
634 sys.stdout.flush()
635 sys.stderr.flush()
636
637 bblogger.removeHandler(handler)
638
639 # Restore the backup fds
640 os.dup2(osi[0], osi[1])
641 os.dup2(oso[0], oso[1])
642 os.dup2(ose[0], ose[1])
643
644 # Close the backup fds
645 os.close(osi[0])
646 os.close(oso[0])
647 os.close(ose[0])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500648
649 logfile.close()
650 if os.path.exists(logfn) and os.path.getsize(logfn) == 0:
651 logger.debug(2, "Zero size logfn %s, removing", logfn)
652 bb.utils.remove(logfn)
653 bb.utils.remove(loglink)
654 event.fire(TaskSucceeded(task, logfn, localdata), localdata)
655
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500656 if not localdata.getVarFlag(task, 'nostamp', False) and not localdata.getVarFlag(task, 'selfstamp', False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500657 make_stamp(task, localdata)
658
659 return 0
660
661def exec_task(fn, task, d, profile = False):
662 try:
663 quieterr = False
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500664 if d.getVarFlag(task, "quieterrors", False) is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500665 quieterr = True
666
667 if profile:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500668 profname = "profile-%s.log" % (d.getVar("PN") + "-" + task)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500669 try:
670 import cProfile as profile
671 except:
672 import profile
673 prof = profile.Profile()
674 ret = profile.Profile.runcall(prof, _exec_task, fn, task, d, quieterr)
675 prof.dump_stats(profname)
676 bb.utils.process_profilelog(profname)
677
678 return ret
679 else:
680 return _exec_task(fn, task, d, quieterr)
681
682 except Exception:
683 from traceback import format_exc
684 if not quieterr:
685 logger.error("Build of %s failed" % (task))
686 logger.error(format_exc())
687 failedevent = TaskFailed(task, None, d, True)
688 event.fire(failedevent, d)
689 return 1
690
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600691def stamp_internal(taskname, d, file_name, baseonly=False, noextra=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500692 """
693 Internal stamp helper function
694 Makes sure the stamp directory exists
695 Returns the stamp path+filename
696
697 In the bitbake core, d can be a CacheData and file_name will be set.
698 When called in task context, d will be a data store, file_name will not be set
699 """
700 taskflagname = taskname
701 if taskname.endswith("_setscene") and taskname != "do_setscene":
702 taskflagname = taskname.replace("_setscene", "")
703
704 if file_name:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500705 stamp = d.stamp[file_name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500706 extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
707 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500708 stamp = d.getVar('STAMP')
709 file_name = d.getVar('BB_FILENAME')
710 extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info') or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500711
712 if baseonly:
713 return stamp
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600714 if noextra:
715 extrainfo = ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500716
717 if not stamp:
718 return
719
720 stamp = bb.parse.siggen.stampfile(stamp, file_name, taskname, extrainfo)
721
722 stampdir = os.path.dirname(stamp)
723 if cached_mtime_noerror(stampdir) == 0:
724 bb.utils.mkdirhier(stampdir)
725
726 return stamp
727
728def stamp_cleanmask_internal(taskname, d, file_name):
729 """
730 Internal stamp helper function to generate stamp cleaning mask
731 Returns the stamp path+filename
732
733 In the bitbake core, d can be a CacheData and file_name will be set.
734 When called in task context, d will be a data store, file_name will not be set
735 """
736 taskflagname = taskname
737 if taskname.endswith("_setscene") and taskname != "do_setscene":
738 taskflagname = taskname.replace("_setscene", "")
739
740 if file_name:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500741 stamp = d.stampclean[file_name]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500742 extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
743 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500744 stamp = d.getVar('STAMPCLEAN')
745 file_name = d.getVar('BB_FILENAME')
746 extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info') or ""
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500747
748 if not stamp:
749 return []
750
751 cleanmask = bb.parse.siggen.stampcleanmask(stamp, file_name, taskname, extrainfo)
752
753 return [cleanmask, cleanmask.replace(taskflagname, taskflagname + "_setscene")]
754
755def make_stamp(task, d, file_name = None):
756 """
757 Creates/updates a stamp for a given task
758 (d can be a data dict or dataCache)
759 """
760 cleanmask = stamp_cleanmask_internal(task, d, file_name)
761 for mask in cleanmask:
762 for name in glob.glob(mask):
763 # Preserve sigdata files in the stamps directory
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500764 if "sigdata" in name or "sigbasedata" in name:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500765 continue
766 # Preserve taint files in the stamps directory
767 if name.endswith('.taint'):
768 continue
769 os.unlink(name)
770
771 stamp = stamp_internal(task, d, file_name)
772 # Remove the file and recreate to force timestamp
773 # change on broken NFS filesystems
774 if stamp:
775 bb.utils.remove(stamp)
776 open(stamp, "w").close()
777
778 # If we're in task context, write out a signature file for each task
779 # as it completes
780 if not task.endswith("_setscene") and task != "do_setscene" and not file_name:
781 stampbase = stamp_internal(task, d, None, True)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500782 file_name = d.getVar('BB_FILENAME')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500783 bb.parse.siggen.dump_sigtask(file_name, task, stampbase, True)
784
785def del_stamp(task, d, file_name = None):
786 """
787 Removes a stamp for a given task
788 (d can be a data dict or dataCache)
789 """
790 stamp = stamp_internal(task, d, file_name)
791 bb.utils.remove(stamp)
792
793def write_taint(task, d, file_name = None):
794 """
795 Creates a "taint" file which will force the specified task and its
796 dependents to be re-run the next time by influencing the value of its
797 taskhash.
798 (d can be a data dict or dataCache)
799 """
800 import uuid
801 if file_name:
802 taintfn = d.stamp[file_name] + '.' + task + '.taint'
803 else:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500804 taintfn = d.getVar('STAMP') + '.' + task + '.taint'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500805 bb.utils.mkdirhier(os.path.dirname(taintfn))
806 # The specific content of the taint file is not really important,
807 # we just need it to be random, so a random UUID is used
808 with open(taintfn, 'w') as taintf:
809 taintf.write(str(uuid.uuid4()))
810
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600811def stampfile(taskname, d, file_name = None, noextra=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500812 """
813 Return the stamp for a given task
814 (d can be a data dict or dataCache)
815 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600816 return stamp_internal(taskname, d, file_name, noextra=noextra)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500817
818def add_tasks(tasklist, d):
819 task_deps = d.getVar('_task_deps', False)
820 if not task_deps:
821 task_deps = {}
822 if not 'tasks' in task_deps:
823 task_deps['tasks'] = []
824 if not 'parents' in task_deps:
825 task_deps['parents'] = {}
826
827 for task in tasklist:
828 task = d.expand(task)
829
830 d.setVarFlag(task, 'task', 1)
831
832 if not task in task_deps['tasks']:
833 task_deps['tasks'].append(task)
834
835 flags = d.getVarFlags(task)
836 def getTask(name):
837 if not name in task_deps:
838 task_deps[name] = {}
839 if name in flags:
840 deptask = d.expand(flags[name])
841 task_deps[name][task] = deptask
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800842 getTask('mcdepends')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500843 getTask('depends')
844 getTask('rdepends')
845 getTask('deptask')
846 getTask('rdeptask')
847 getTask('recrdeptask')
848 getTask('recideptask')
849 getTask('nostamp')
850 getTask('fakeroot')
851 getTask('noexec')
852 getTask('umask')
853 task_deps['parents'][task] = []
854 if 'deps' in flags:
855 for dep in flags['deps']:
Brad Bishopc342db32019-05-15 21:57:59 -0400856 # Check and warn for "addtask task after foo" while foo does not exist
857 #if not dep in tasklist:
858 # bb.warn('%s: dependent task %s for %s does not exist' % (d.getVar('PN'), dep, task))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500859 dep = d.expand(dep)
860 task_deps['parents'][task].append(dep)
861
862 # don't assume holding a reference
863 d.setVar('_task_deps', task_deps)
864
865def addtask(task, before, after, d):
866 if task[:3] != "do_":
867 task = "do_" + task
868
869 d.setVarFlag(task, "task", 1)
870 bbtasks = d.getVar('__BBTASKS', False) or []
871 if task not in bbtasks:
872 bbtasks.append(task)
873 d.setVar('__BBTASKS', bbtasks)
874
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500875 existing = d.getVarFlag(task, "deps", False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500876 if after is not None:
877 # set up deps for function
878 for entry in after.split():
879 if entry not in existing:
880 existing.append(entry)
881 d.setVarFlag(task, "deps", existing)
882 if before is not None:
883 # set up things that depend on this func
884 for entry in before.split():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500885 existing = d.getVarFlag(entry, "deps", False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500886 if task not in existing:
887 d.setVarFlag(entry, "deps", [task] + existing)
888
889def deltask(task, d):
890 if task[:3] != "do_":
891 task = "do_" + task
892
893 bbtasks = d.getVar('__BBTASKS', False) or []
894 if task in bbtasks:
895 bbtasks.remove(task)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600896 d.delVarFlag(task, 'task')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500897 d.setVar('__BBTASKS', bbtasks)
898
899 d.delVarFlag(task, 'deps')
900 for bbtask in d.getVar('__BBTASKS', False) or []:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500901 deps = d.getVarFlag(bbtask, 'deps', False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500902 if task in deps:
903 deps.remove(task)
904 d.setVarFlag(bbtask, 'deps', deps)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500905
906def preceedtask(task, with_recrdeptasks, d):
907 """
908 Returns a set of tasks in the current recipe which were specified as
909 precondition by the task itself ("after") or which listed themselves
910 as precondition ("before"). Preceeding tasks specified via the
911 "recrdeptask" are included in the result only if requested. Beware
912 that this may lead to the task itself being listed.
913 """
914 preceed = set()
Brad Bishop316dfdd2018-06-25 12:45:53 -0400915
916 # Ignore tasks which don't exist
917 tasks = d.getVar('__BBTASKS', False)
918 if task not in tasks:
919 return preceed
920
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500921 preceed.update(d.getVarFlag(task, 'deps') or [])
922 if with_recrdeptasks:
923 recrdeptask = d.getVarFlag(task, 'recrdeptask')
924 if recrdeptask:
925 preceed.update(recrdeptask.split())
926 return preceed
927
928def tasksbetween(task_start, task_end, d):
929 """
930 Return the list of tasks between two tasks in the current recipe,
931 where task_start is to start at and task_end is the task to end at
932 (and task_end has a dependency chain back to task_start).
933 """
934 outtasks = []
935 tasks = list(filter(lambda k: d.getVarFlag(k, "task"), d.keys()))
936 def follow_chain(task, endtask, chain=None):
937 if not chain:
938 chain = []
939 chain.append(task)
940 for othertask in tasks:
941 if othertask == task:
942 continue
943 if task == endtask:
944 for ctask in chain:
945 if ctask not in outtasks:
946 outtasks.append(ctask)
947 else:
948 deps = d.getVarFlag(othertask, 'deps', False)
949 if task in deps:
950 follow_chain(othertask, endtask, chain)
951 chain.pop()
952 follow_chain(task_start, task_end)
953 return outtasks