blob: 5a1727116a9a98a00d4a301fba0025beefe8cdcb [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
Patrick Williamsc124f4f2015-09-15 14:41:29 -050018import glob
Andrew Geissler635e0e42020-08-21 15:58:33 -050019import itertools
Patrick Williamsc124f4f2015-09-15 14:41:29 -050020import time
Andrew Geissler635e0e42020-08-21 15:58:33 -050021import re
Patrick Williamsc124f4f2015-09-15 14:41:29 -050022import stat
Patrick Williams92b42cb2022-09-03 06:53:57 -050023import datetime
Patrick Williamsc124f4f2015-09-15 14:41:29 -050024import bb
25import bb.msg
26import bb.process
Patrick Williamsc0f7c042017-02-23 20:41:17 -060027import bb.progress
28from bb import data, event, utils
Patrick Williamsc124f4f2015-09-15 14:41:29 -050029
30bblogger = logging.getLogger('BitBake')
31logger = logging.getLogger('BitBake.Build')
32
Andrew Geisslerc9f78652020-09-18 14:11:35 -050033verboseShellLogging = False
34verboseStdoutLogging = False
35
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036__mtime_cache = {}
37
38def cached_mtime_noerror(f):
39 if f not in __mtime_cache:
40 try:
41 __mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
42 except OSError:
43 return 0
44 return __mtime_cache[f]
45
46def reset_cache():
47 global __mtime_cache
48 __mtime_cache = {}
49
50# When we execute a Python function, we'd like certain things
51# in all namespaces, hence we add them to __builtins__.
52# If we do not do this and use the exec globals, they will
53# not be available to subfunctions.
Patrick Williamsc0f7c042017-02-23 20:41:17 -060054if hasattr(__builtins__, '__setitem__'):
55 builtins = __builtins__
56else:
57 builtins = __builtins__.__dict__
58
59builtins['bb'] = bb
60builtins['os'] = os
Patrick Williamsc124f4f2015-09-15 14:41:29 -050061
Patrick Williamsc124f4f2015-09-15 14:41:29 -050062class TaskBase(event.Event):
63 """Base class for task events"""
64
Andrew Geissler82c905d2020-04-13 13:39:40 -050065 def __init__(self, t, fn, logfile, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050066 self._task = t
Andrew Geissler82c905d2020-04-13 13:39:40 -050067 self._fn = fn
Brad Bishop6e60e8b2018-02-01 10:27:11 -050068 self._package = d.getVar("PF")
69 self._mc = d.getVar("BB_CURRENT_MC")
70 self.taskfile = d.getVar("FILE")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050071 self.taskname = self._task
72 self.logfile = logfile
73 self.time = time.time()
Andrew Geissler82c905d2020-04-13 13:39:40 -050074 self.pn = d.getVar("PN")
75 self.pv = d.getVar("PV")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050076 event.Event.__init__(self)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050077 self._message = "recipe %s: task %s: %s" % (d.getVar("PF"), t, self.getDisplayName())
Patrick Williamsc124f4f2015-09-15 14:41:29 -050078
79 def getTask(self):
80 return self._task
81
82 def setTask(self, task):
83 self._task = task
84
85 def getDisplayName(self):
86 return bb.event.getName(self)[4:]
87
88 task = property(getTask, setTask, None, "task property")
89
90class TaskStarted(TaskBase):
91 """Task execution started"""
Andrew Geissler82c905d2020-04-13 13:39:40 -050092 def __init__(self, t, fn, logfile, taskflags, d):
93 super(TaskStarted, self).__init__(t, fn, logfile, d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050094 self.taskflags = taskflags
95
96class TaskSucceeded(TaskBase):
97 """Task execution completed"""
98
99class TaskFailed(TaskBase):
100 """Task execution failed"""
101
Andrew Geissler82c905d2020-04-13 13:39:40 -0500102 def __init__(self, task, fn, logfile, metadata, errprinted = False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500103 self.errprinted = errprinted
Andrew Geissler82c905d2020-04-13 13:39:40 -0500104 super(TaskFailed, self).__init__(task, fn, logfile, metadata)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500105
106class TaskFailedSilent(TaskBase):
107 """Task execution failed (silently)"""
108 def getDisplayName(self):
109 # Don't need to tell the user it was silent
110 return "Failed"
111
112class TaskInvalid(TaskBase):
113
Andrew Geissler82c905d2020-04-13 13:39:40 -0500114 def __init__(self, task, fn, metadata):
115 super(TaskInvalid, self).__init__(task, fn, None, metadata)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500116 self._message = "No such task '%s'" % task
117
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600118class TaskProgress(event.Event):
119 """
120 Task made some progress that could be reported to the user, usually in
121 the form of a progress bar or similar.
122 NOTE: this class does not inherit from TaskBase since it doesn't need
123 to - it's fired within the task context itself, so we don't have any of
124 the context information that you do in the case of the other events.
125 The event PID can be used to determine which task it came from.
126 The progress value is normally 0-100, but can also be negative
127 indicating that progress has been made but we aren't able to determine
128 how much.
129 The rate is optional, this is simply an extra string to display to the
130 user if specified.
131 """
132 def __init__(self, progress, rate=None):
133 self.progress = progress
134 self.rate = rate
135 event.Event.__init__(self)
136
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500137
138class LogTee(object):
139 def __init__(self, logger, outfile):
140 self.outfile = outfile
141 self.logger = logger
142 self.name = self.outfile.name
143
144 def write(self, string):
145 self.logger.plain(string)
146 self.outfile.write(string)
147
148 def __enter__(self):
149 self.outfile.__enter__()
150 return self
151
152 def __exit__(self, *excinfo):
153 self.outfile.__exit__(*excinfo)
154
155 def __repr__(self):
156 return '<LogTee {0}>'.format(self.name)
Brad Bishop15ae2502019-06-18 21:44:24 -0400157
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500158 def flush(self):
159 self.outfile.flush()
160
Brad Bishop15ae2502019-06-18 21:44:24 -0400161
162class StdoutNoopContextManager:
163 """
164 This class acts like sys.stdout, but adds noop __enter__ and __exit__ methods.
165 """
166 def __enter__(self):
167 return sys.stdout
168
169 def __exit__(self, *exc_info):
170 pass
171
172 def write(self, string):
173 return sys.stdout.write(string)
174
175 def flush(self):
176 sys.stdout.flush()
177
178 @property
179 def name(self):
180 return sys.stdout.name
181
182
Brad Bishop08902b02019-08-20 09:16:51 -0400183def exec_func(func, d, dirs = None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500184 """Execute a BB 'function'"""
185
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600186 try:
187 oldcwd = os.getcwd()
188 except:
189 oldcwd = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500190
191 flags = d.getVarFlags(func)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500192 cleandirs = flags.get('cleandirs') if flags else None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500193 if cleandirs:
194 for cdir in d.expand(cleandirs).split():
195 bb.utils.remove(cdir, True)
196 bb.utils.mkdirhier(cdir)
197
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500198 if flags and dirs is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500199 dirs = flags.get('dirs')
200 if dirs:
201 dirs = d.expand(dirs).split()
202
203 if dirs:
204 for adir in dirs:
205 bb.utils.mkdirhier(adir)
206 adir = dirs[-1]
207 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600208 adir = None
209
210 body = d.getVar(func, False)
211 if not body:
212 if body is None:
213 logger.warning("Function %s doesn't exist", func)
214 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500215
216 ispython = flags.get('python')
217
218 lockflag = flags.get('lockfiles')
219 if lockflag:
220 lockfiles = [f for f in d.expand(lockflag).split()]
221 else:
222 lockfiles = None
223
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500224 tempdir = d.getVar('T')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500225
226 # or func allows items to be executed outside of the normal
227 # task set, such as buildhistory
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500228 task = d.getVar('BB_RUNTASK') or func
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500229 if task == func:
230 taskfunc = task
231 else:
232 taskfunc = "%s.%s" % (task, func)
233
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500234 runfmt = d.getVar('BB_RUNFMT') or "run.{func}.{pid}"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500235 runfn = runfmt.format(taskfunc=taskfunc, task=task, func=func, pid=os.getpid())
236 runfile = os.path.join(tempdir, runfn)
237 bb.utils.mkdirhier(os.path.dirname(runfile))
238
239 # Setup the courtesy link to the runfn, only for tasks
240 # we create the link 'just' before the run script is created
241 # if we create it after, and if the run script fails, then the
242 # link won't be created as an exception would be fired.
243 if task == func:
244 runlink = os.path.join(tempdir, 'run.{0}'.format(task))
245 if runlink:
246 bb.utils.remove(runlink)
247
248 try:
249 os.symlink(runfn, runlink)
250 except OSError:
251 pass
252
253 with bb.utils.fileslocked(lockfiles):
254 if ispython:
Brad Bishop08902b02019-08-20 09:16:51 -0400255 exec_func_python(func, d, runfile, cwd=adir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500256 else:
257 exec_func_shell(func, d, runfile, cwd=adir)
258
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600259 try:
260 curcwd = os.getcwd()
261 except:
262 curcwd = None
263
264 if oldcwd and curcwd != oldcwd:
265 try:
266 bb.warn("Task %s changed cwd to %s" % (func, curcwd))
267 os.chdir(oldcwd)
268 except:
269 pass
270
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500271_functionfmt = """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500272{function}(d)
273"""
274logformatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
Brad Bishop08902b02019-08-20 09:16:51 -0400275def exec_func_python(func, d, runfile, cwd=None):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500276 """Execute a python BB 'function'"""
277
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500278 code = _functionfmt.format(function=func)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500279 bb.utils.mkdirhier(os.path.dirname(runfile))
280 with open(runfile, 'w') as script:
281 bb.data.emit_func_python(func, script, d)
282
283 if cwd:
284 try:
285 olddir = os.getcwd()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600286 except OSError as e:
287 bb.warn("%s: Cannot get cwd: %s" % (func, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500288 olddir = None
289 os.chdir(cwd)
290
291 bb.debug(2, "Executing python function %s" % func)
292
293 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500294 text = "def %s(d):\n%s" % (func, d.getVar(func, False))
295 fn = d.getVarFlag(func, "filename", False)
296 lineno = int(d.getVarFlag(func, "lineno", False))
297 bb.methodpool.insert_method(func, text, fn, lineno - 1)
298
Andrew Geissler5199d832021-09-24 16:47:35 -0500299 comp = utils.better_compile(code, func, "exec_func_python() autogenerated")
300 utils.better_exec(comp, {"d": d}, code, "exec_func_python() autogenerated")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500301 finally:
Andrew Geissler5199d832021-09-24 16:47:35 -0500302 # We want any stdout/stderr to be printed before any other log messages to make debugging
303 # more accurate. In some cases we seem to lose stdout/stderr entirely in logging tests without this.
304 sys.stdout.flush()
305 sys.stderr.flush()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500306 bb.debug(2, "Python function %s finished" % func)
307
308 if cwd and olddir:
309 try:
310 os.chdir(olddir)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600311 except OSError as e:
312 bb.warn("%s: Cannot restore cwd %s: %s" % (func, olddir, e))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500313
314def shell_trap_code():
315 return '''#!/bin/sh\n
Andrew Geissler635e0e42020-08-21 15:58:33 -0500316__BITBAKE_LAST_LINE=0
317
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500318# Emit a useful diagnostic if something fails:
Andrew Geissler635e0e42020-08-21 15:58:33 -0500319bb_sh_exit_handler() {
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500320 ret=$?
Andrew Geissler635e0e42020-08-21 15:58:33 -0500321 if [ "$ret" != 0 ]; then
322 echo "WARNING: exit code $ret from a shell command."
323 fi
324 exit $ret
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500325}
Andrew Geissler635e0e42020-08-21 15:58:33 -0500326
327bb_bash_exit_handler() {
328 ret=$?
329 { set +x; } > /dev/null
330 trap "" DEBUG
331 if [ "$ret" != 0 ]; then
332 echo "WARNING: ${BASH_SOURCE[0]}:${__BITBAKE_LAST_LINE} exit $ret from '$1'"
333
334 echo "WARNING: Backtrace (BB generated script): "
335 for i in $(seq 1 $((${#FUNCNAME[@]} - 1))); do
336 if [ "$i" -eq 1 ]; then
337 echo -e "\t#$((i)): ${FUNCNAME[$i]}, ${BASH_SOURCE[$((i-1))]}, line ${__BITBAKE_LAST_LINE}"
338 else
339 echo -e "\t#$((i)): ${FUNCNAME[$i]}, ${BASH_SOURCE[$((i-1))]}, line ${BASH_LINENO[$((i-1))]}"
340 fi
341 done
342 fi
343 exit $ret
344}
345
346bb_bash_debug_handler() {
347 local line=${BASH_LINENO[0]}
348 # For some reason the DEBUG trap trips with lineno=1 when scripts exit; ignore it
349 if [ "$line" -eq 1 ]; then
350 return
351 fi
352
353 # Track the line number of commands as they execute. This is so we can have access to the failing line number
354 # 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
355 if [ "${FUNCNAME[1]}" != "bb_bash_exit_handler" ]; then
356 __BITBAKE_LAST_LINE=$line
357 fi
358}
359
360case $BASH_VERSION in
361"") trap 'bb_sh_exit_handler' 0
362 set -e
363 ;;
364*) trap 'bb_bash_exit_handler "$BASH_COMMAND"' 0
365 trap '{ bb_bash_debug_handler; } 2>/dev/null' DEBUG
366 set -e
367 shopt -s extdebug
368 ;;
369esac
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500370'''
371
Brad Bishop15ae2502019-06-18 21:44:24 -0400372def create_progress_handler(func, progress, logfile, d):
373 if progress == 'percent':
374 # Use default regex
375 return bb.progress.BasicProgressHandler(d, outfile=logfile)
376 elif progress.startswith('percent:'):
377 # Use specified regex
378 return bb.progress.BasicProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile)
379 elif progress.startswith('outof:'):
380 # Use specified regex
381 return bb.progress.OutOfProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile)
382 elif progress.startswith("custom:"):
383 # Use a custom progress handler that was injected via OE_EXTRA_IMPORTS or __builtins__
384 import functools
385 from types import ModuleType
386
387 parts = progress.split(":", 2)
388 _, cls, otherargs = parts[0], parts[1], (parts[2] or None) if parts[2:] else None
389 if cls:
390 def resolve(x, y):
391 if not x:
392 return None
393 if isinstance(x, ModuleType):
394 return getattr(x, y, None)
395 return x.get(y)
396 cls_obj = functools.reduce(resolve, cls.split("."), bb.utils._context)
397 if not cls_obj:
398 # Fall-back on __builtins__
Andrew Geissler635e0e42020-08-21 15:58:33 -0500399 cls_obj = functools.reduce(resolve, cls.split("."), __builtins__)
Brad Bishop15ae2502019-06-18 21:44:24 -0400400 if cls_obj:
401 return cls_obj(d, outfile=logfile, otherargs=otherargs)
402 bb.warn('%s: unknown custom progress handler in task progress varflag value "%s", ignoring' % (func, cls))
403 else:
404 bb.warn('%s: invalid task progress varflag value "%s", ignoring' % (func, progress))
405
406 return logfile
407
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500408def exec_func_shell(func, d, runfile, cwd=None):
409 """Execute a shell function from the metadata
410
411 Note on directory behavior. The 'dirs' varflag should contain a list
412 of the directories you need created prior to execution. The last
413 item in the list is where we will chdir/cd to.
414 """
415
416 # Don't let the emitted shell script override PWD
417 d.delVarFlag('PWD', 'export')
418
419 with open(runfile, 'w') as script:
420 script.write(shell_trap_code())
421
422 bb.data.emit_func(func, script, d)
423
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500424 if verboseShellLogging or bb.utils.to_boolean(d.getVar("BB_VERBOSE_LOGS", False)):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500425 script.write("set -x\n")
426 if cwd:
427 script.write("cd '%s'\n" % cwd)
428 script.write("%s\n" % func)
429 script.write('''
430# cleanup
431ret=$?
432trap '' 0
433exit $ret
434''')
435
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600436 os.chmod(runfile, 0o775)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500437
438 cmd = runfile
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500439 if d.getVarFlag(func, 'fakeroot', False):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500440 fakerootcmd = d.getVar('FAKEROOT')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500441 if fakerootcmd:
442 cmd = [fakerootcmd, runfile]
443
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500444 if verboseStdoutLogging:
Brad Bishop15ae2502019-06-18 21:44:24 -0400445 logfile = LogTee(logger, StdoutNoopContextManager())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500446 else:
Brad Bishop15ae2502019-06-18 21:44:24 -0400447 logfile = StdoutNoopContextManager()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500448
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500449 progress = d.getVarFlag(func, 'progress')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600450 if progress:
Andrew Geissler635e0e42020-08-21 15:58:33 -0500451 try:
452 logfile = create_progress_handler(func, progress, logfile, d)
453 except:
454 from traceback import format_exc
455 logger.error("Failed to create progress handler")
456 logger.error(format_exc())
457 raise
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600458
459 fifobuffer = bytearray()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500460 def readfifo(data):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600461 nonlocal fifobuffer
462 fifobuffer.extend(data)
463 while fifobuffer:
464 message, token, nextmsg = fifobuffer.partition(b"\00")
465 if token:
466 splitval = message.split(b' ', 1)
467 cmd = splitval[0].decode("utf-8")
468 if len(splitval) > 1:
469 value = splitval[1].decode("utf-8")
470 else:
471 value = ''
472 if cmd == 'bbplain':
473 bb.plain(value)
474 elif cmd == 'bbnote':
475 bb.note(value)
Brad Bishopc342db32019-05-15 21:57:59 -0400476 elif cmd == 'bbverbnote':
477 bb.verbnote(value)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600478 elif cmd == 'bbwarn':
479 bb.warn(value)
480 elif cmd == 'bberror':
481 bb.error(value)
482 elif cmd == 'bbfatal':
483 # The caller will call exit themselves, so bb.error() is
484 # what we want here rather than bb.fatal()
485 bb.error(value)
486 elif cmd == 'bbfatal_log':
487 bb.error(value, forcelog=True)
488 elif cmd == 'bbdebug':
489 splitval = value.split(' ', 1)
490 level = int(splitval[0])
491 value = splitval[1]
492 bb.debug(level, value)
493 else:
494 bb.warn("Unrecognised command '%s' on FIFO" % cmd)
495 fifobuffer = nextmsg
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500496 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600497 break
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500498
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500499 tempdir = d.getVar('T')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500500 fifopath = os.path.join(tempdir, 'fifo.%s' % os.getpid())
501 if os.path.exists(fifopath):
502 os.unlink(fifopath)
503 os.mkfifo(fifopath)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600504 with open(fifopath, 'r+b', buffering=0) as fifo:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500505 try:
506 bb.debug(2, "Executing shell function %s" % func)
Brad Bishop08902b02019-08-20 09:16:51 -0400507 with open(os.devnull, 'r+') as stdin, logfile:
508 bb.process.run(cmd, shell=False, stdin=stdin, log=logfile, extrafiles=[(fifo,readfifo)])
Andrew Geissler635e0e42020-08-21 15:58:33 -0500509 except bb.process.ExecutionError as exe:
510 # Find the backtrace that the shell trap generated
511 backtrace_marker_regex = re.compile(r"WARNING: Backtrace \(BB generated script\)")
512 stdout_lines = (exe.stdout or "").split("\n")
513 backtrace_start_line = None
514 for i, line in enumerate(reversed(stdout_lines)):
515 if backtrace_marker_regex.search(line):
516 backtrace_start_line = len(stdout_lines) - i
517 break
518
519 # Read the backtrace frames, starting at the location we just found
520 backtrace_entry_regex = re.compile(r"#(?P<frameno>\d+): (?P<funcname>[^\s]+), (?P<file>.+?), line ("
521 r"?P<lineno>\d+)")
522 backtrace_frames = []
523 if backtrace_start_line:
524 for line in itertools.islice(stdout_lines, backtrace_start_line, None):
525 match = backtrace_entry_regex.search(line)
526 if match:
527 backtrace_frames.append(match.groupdict())
528
529 with open(runfile, "r") as script:
530 script_lines = [line.rstrip() for line in script.readlines()]
531
532 # For each backtrace frame, search backwards in the script (from the line number called out by the frame),
533 # to find the comment that emit_vars injected when it wrote the script. This will give us the metadata
534 # filename (e.g. .bb or .bbclass) and line number where the shell function was originally defined.
535 script_metadata_comment_regex = re.compile(r"# line: (?P<lineno>\d+), file: (?P<file>.+)")
536 better_frames = []
537 # Skip the very last frame since it's just the call to the shell task in the body of the script
538 for frame in backtrace_frames[:-1]:
539 # Check whether the frame corresponds to a function defined in the script vs external script.
540 if os.path.samefile(frame["file"], runfile):
541 # Search backwards from the frame lineno to locate the comment that BB injected
542 i = int(frame["lineno"]) - 1
543 while i >= 0:
544 match = script_metadata_comment_regex.match(script_lines[i])
545 if match:
546 # Calculate the relative line in the function itself
547 relative_line_in_function = int(frame["lineno"]) - i - 2
548 # Calculate line in the function as declared in the metadata
549 metadata_function_line = relative_line_in_function + int(match["lineno"])
550 better_frames.append("#{frameno}: {funcname}, {file}, line {lineno}".format(
551 frameno=frame["frameno"],
552 funcname=frame["funcname"],
553 file=match["file"],
554 lineno=metadata_function_line
555 ))
556 break
557 i -= 1
558 else:
559 better_frames.append("#{frameno}: {funcname}, {file}, line {lineno}".format(**frame))
560
561 if better_frames:
562 better_frames = ("\t{0}".format(frame) for frame in better_frames)
563 exe.extra_message = "\nBacktrace (metadata-relative locations):\n{0}".format("\n".join(better_frames))
564 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500565 finally:
566 os.unlink(fifopath)
567
568 bb.debug(2, "Shell function %s finished" % func)
569
570def _task_data(fn, task, d):
571 localdata = bb.data.createCopy(d)
572 localdata.setVar('BB_FILENAME', fn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500573 localdata.setVar('OVERRIDES', 'task-%s:%s' %
574 (task[3:].replace('_', '-'), d.getVar('OVERRIDES', False)))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500575 bb.data.expandKeys(localdata)
576 return localdata
577
578def _exec_task(fn, task, d, quieterr):
579 """Execute a BB 'task'
580
581 Execution of a task involves a bit more setup than executing a function,
582 running it with its own local metadata, and with some useful variables set.
583 """
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500584 if not d.getVarFlag(task, 'task', False):
Andrew Geissler9aee5002022-03-30 16:27:02 +0000585 event.fire(TaskInvalid(task, fn, d), d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500586 logger.error("No such task: %s" % task)
587 return 1
588
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600589 logger.debug("Executing task %s", task)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500590
591 localdata = _task_data(fn, task, d)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500592 tempdir = localdata.getVar('T')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500593 if not tempdir:
594 bb.fatal("T variable not set, unable to build")
595
596 # Change nice level if we're asked to
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500597 nice = localdata.getVar("BB_TASK_NICE_LEVEL")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500598 if nice:
599 curnice = os.nice(0)
600 nice = int(nice) - curnice
601 newnice = os.nice(nice)
Andrew Geisslerd1e89492021-02-12 15:35:20 -0600602 logger.debug("Renice to %s " % newnice)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500603 ionice = localdata.getVar("BB_TASK_IONICE_LEVEL")
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500604 if ionice:
605 try:
606 cls, prio = ionice.split(".", 1)
607 bb.utils.ioprio_set(os.getpid(), int(cls), int(prio))
608 except:
609 bb.warn("Invalid ionice level %s" % ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500610
611 bb.utils.mkdirhier(tempdir)
612
613 # Determine the logfile to generate
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500614 logfmt = localdata.getVar('BB_LOGFMT') or 'log.{task}.{pid}'
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500615 logbase = logfmt.format(task=task, pid=os.getpid())
616
617 # Document the order of the tasks...
618 logorder = os.path.join(tempdir, 'log.task_order')
619 try:
620 with open(logorder, 'a') as logorderfile:
Patrick Williams92b42cb2022-09-03 06:53:57 -0500621 timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S.%f")
622 logorderfile.write('{0} {1} ({2}): {3}\n'.format(timestamp, task, os.getpid(), logbase))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500623 except OSError:
624 logger.exception("Opening log file '%s'", logorder)
625 pass
626
627 # Setup the courtesy link to the logfn
628 loglink = os.path.join(tempdir, 'log.{0}'.format(task))
629 logfn = os.path.join(tempdir, logbase)
630 if loglink:
631 bb.utils.remove(loglink)
632
633 try:
634 os.symlink(logbase, loglink)
635 except OSError:
636 pass
637
638 prefuncs = localdata.getVarFlag(task, 'prefuncs', expand=True)
639 postfuncs = localdata.getVarFlag(task, 'postfuncs', expand=True)
640
641 class ErrorCheckHandler(logging.Handler):
642 def __init__(self):
643 self.triggered = False
644 logging.Handler.__init__(self, logging.ERROR)
645 def emit(self, record):
646 if getattr(record, 'forcelog', False):
647 self.triggered = False
648 else:
649 self.triggered = True
650
651 # Handle logfiles
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500652 try:
653 bb.utils.mkdirhier(os.path.dirname(logfn))
654 logfile = open(logfn, 'w')
655 except OSError:
656 logger.exception("Opening log file '%s'", logfn)
657 pass
658
659 # Dup the existing fds so we dont lose them
660 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
661 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
662 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
663
664 # Replace those fds with our own
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800665 with open('/dev/null', 'r') as si:
666 os.dup2(si.fileno(), osi[1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667 os.dup2(logfile.fileno(), oso[1])
668 os.dup2(logfile.fileno(), ose[1])
669
670 # Ensure Python logging goes to the logfile
671 handler = logging.StreamHandler(logfile)
672 handler.setFormatter(logformatter)
673 # Always enable full debug output into task logfiles
674 handler.setLevel(logging.DEBUG - 2)
675 bblogger.addHandler(handler)
676
677 errchk = ErrorCheckHandler()
678 bblogger.addHandler(errchk)
679
680 localdata.setVar('BB_LOGFILE', logfn)
681 localdata.setVar('BB_RUNTASK', task)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500682 localdata.setVar('BB_TASK_LOGGER', bblogger)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500683
684 flags = localdata.getVarFlags(task)
685
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500686 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600687 try:
Andrew Geissler82c905d2020-04-13 13:39:40 -0500688 event.fire(TaskStarted(task, fn, logfn, flags, localdata), localdata)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600689
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600690 for func in (prefuncs or '').split():
691 exec_func(func, localdata)
692 exec_func(task, localdata)
693 for func in (postfuncs or '').split():
694 exec_func(func, localdata)
Andrew Geissler5199d832021-09-24 16:47:35 -0500695 finally:
696 # Need to flush and close the logs before sending events where the
697 # UI may try to look at the logs.
698 sys.stdout.flush()
699 sys.stderr.flush()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500700
Andrew Geissler5199d832021-09-24 16:47:35 -0500701 bblogger.removeHandler(handler)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500702
Andrew Geissler5199d832021-09-24 16:47:35 -0500703 # Restore the backup fds
704 os.dup2(osi[0], osi[1])
705 os.dup2(oso[0], oso[1])
706 os.dup2(ose[0], ose[1])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500707
Andrew Geissler5199d832021-09-24 16:47:35 -0500708 # Close the backup fds
709 os.close(osi[0])
710 os.close(oso[0])
711 os.close(ose[0])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500712
Andrew Geissler5199d832021-09-24 16:47:35 -0500713 logfile.close()
714 if os.path.exists(logfn) and os.path.getsize(logfn) == 0:
715 logger.debug2("Zero size logfn %s, removing", logfn)
716 bb.utils.remove(logfn)
717 bb.utils.remove(loglink)
Andrew Geissler5199d832021-09-24 16:47:35 -0500718 except (Exception, SystemExit) as exc:
Andrew Geissler595f6302022-01-24 19:11:47 +0000719 handled = False
720 if isinstance(exc, bb.BBHandledException):
721 handled = True
722
Andrew Geissler5199d832021-09-24 16:47:35 -0500723 if quieterr:
Andrew Geissler595f6302022-01-24 19:11:47 +0000724 if not handled:
725 logger.warning(repr(exc))
Andrew Geissler5199d832021-09-24 16:47:35 -0500726 event.fire(TaskFailedSilent(task, fn, logfn, localdata), localdata)
727 else:
728 errprinted = errchk.triggered
729 # If the output is already on stdout, we've printed the information in the
730 # logs once already so don't duplicate
Andrew Geissler595f6302022-01-24 19:11:47 +0000731 if verboseStdoutLogging or handled:
Andrew Geissler5199d832021-09-24 16:47:35 -0500732 errprinted = True
Andrew Geissler595f6302022-01-24 19:11:47 +0000733 if not handled:
734 logger.error(repr(exc))
Andrew Geissler5199d832021-09-24 16:47:35 -0500735 event.fire(TaskFailed(task, fn, logfn, localdata, errprinted), localdata)
736 return 1
737
Andrew Geissler82c905d2020-04-13 13:39:40 -0500738 event.fire(TaskSucceeded(task, fn, logfn, localdata), localdata)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500739
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500740 if not localdata.getVarFlag(task, 'nostamp', False) and not localdata.getVarFlag(task, 'selfstamp', False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500741 make_stamp(task, localdata)
742
743 return 0
744
745def exec_task(fn, task, d, profile = False):
746 try:
747 quieterr = False
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500748 if d.getVarFlag(task, "quieterrors", False) is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500749 quieterr = True
750
751 if profile:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500752 profname = "profile-%s.log" % (d.getVar("PN") + "-" + task)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500753 try:
754 import cProfile as profile
755 except:
756 import profile
757 prof = profile.Profile()
758 ret = profile.Profile.runcall(prof, _exec_task, fn, task, d, quieterr)
759 prof.dump_stats(profname)
760 bb.utils.process_profilelog(profname)
761
762 return ret
763 else:
764 return _exec_task(fn, task, d, quieterr)
765
766 except Exception:
767 from traceback import format_exc
768 if not quieterr:
769 logger.error("Build of %s failed" % (task))
770 logger.error(format_exc())
771 failedevent = TaskFailed(task, None, d, True)
772 event.fire(failedevent, d)
773 return 1
774
Andrew Geissler517393d2023-01-13 08:55:19 -0600775def _get_cleanmask(taskname, mcfn):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500776 """
777 Internal stamp helper function to generate stamp cleaning mask
778 Returns the stamp path+filename
779
780 In the bitbake core, d can be a CacheData and file_name will be set.
781 When called in task context, d will be a data store, file_name will not be set
782 """
Andrew Geissler517393d2023-01-13 08:55:19 -0600783 cleanmask = bb.parse.siggen.stampcleanmask_mcfn(taskname, mcfn)
784 taskflagname = taskname.replace("_setscene", "")
785 if cleanmask:
786 return [cleanmask, cleanmask.replace(taskflagname, taskflagname + "_setscene")]
787 return []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500788
Andrew Geissler517393d2023-01-13 08:55:19 -0600789def clean_stamp_mcfn(task, mcfn):
790 cleanmask = _get_cleanmask(task, mcfn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500791 for mask in cleanmask:
792 for name in glob.glob(mask):
793 # Preserve sigdata files in the stamps directory
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500794 if "sigdata" in name or "sigbasedata" in name:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500795 continue
796 # Preserve taint files in the stamps directory
797 if name.endswith('.taint'):
798 continue
799 os.unlink(name)
Andrew Geisslerd5838332022-05-27 11:33:10 -0500800
Andrew Geissler517393d2023-01-13 08:55:19 -0600801def clean_stamp(task, d):
802 mcfn = d.getVar('BB_FILENAME')
803 clean_stamp_mcfn(task, mcfn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500804
Andrew Geissler517393d2023-01-13 08:55:19 -0600805def make_stamp_mcfn(task, mcfn):
806
807 basestamp = bb.parse.siggen.stampfile_mcfn(task, mcfn)
808
809 stampdir = os.path.dirname(basestamp)
810 if cached_mtime_noerror(stampdir) == 0:
811 bb.utils.mkdirhier(stampdir)
812
813 clean_stamp_mcfn(task, mcfn)
814
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500815 # Remove the file and recreate to force timestamp
816 # change on broken NFS filesystems
Andrew Geissler517393d2023-01-13 08:55:19 -0600817 if basestamp:
818 bb.utils.remove(basestamp)
819 open(basestamp, "w").close()
820
821def make_stamp(task, d):
822 """
823 Creates/updates a stamp for a given task
824 """
825 mcfn = d.getVar('BB_FILENAME')
826
827 make_stamp_mcfn(task, mcfn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500828
829 # If we're in task context, write out a signature file for each task
830 # as it completes
Andrew Geissler517393d2023-01-13 08:55:19 -0600831 if not task.endswith("_setscene"):
832 stampbase = bb.parse.siggen.stampfile_base(mcfn)
833 bb.parse.siggen.dump_sigtask(mcfn, task, stampbase, True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500834
Andrew Geissler517393d2023-01-13 08:55:19 -0600835
836def find_stale_stamps(task, mcfn):
837 current = bb.parse.siggen.stampfile_mcfn(task, mcfn)
838 current2 = bb.parse.siggen.stampfile_mcfn(task + "_setscene", mcfn)
839 cleanmask = _get_cleanmask(task, mcfn)
Andrew Geissler95ac1b82021-03-31 14:34:31 -0500840 found = []
841 for mask in cleanmask:
842 for name in glob.glob(mask):
843 if "sigdata" in name or "sigbasedata" in name:
844 continue
845 if name.endswith('.taint'):
846 continue
847 if name == current or name == current2:
848 continue
849 logger.debug2("Stampfile %s does not match %s or %s" % (name, current, current2))
850 found.append(name)
851 return found
852
Andrew Geissler517393d2023-01-13 08:55:19 -0600853def write_taint(task, d):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500854 """
855 Creates a "taint" file which will force the specified task and its
856 dependents to be re-run the next time by influencing the value of its
857 taskhash.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500858 """
Andrew Geissler517393d2023-01-13 08:55:19 -0600859 mcfn = d.getVar('BB_FILENAME')
860 bb.parse.siggen.invalidate_task(task, mcfn)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500861
862def add_tasks(tasklist, d):
863 task_deps = d.getVar('_task_deps', False)
864 if not task_deps:
865 task_deps = {}
866 if not 'tasks' in task_deps:
867 task_deps['tasks'] = []
868 if not 'parents' in task_deps:
869 task_deps['parents'] = {}
870
871 for task in tasklist:
872 task = d.expand(task)
873
874 d.setVarFlag(task, 'task', 1)
875
876 if not task in task_deps['tasks']:
877 task_deps['tasks'].append(task)
878
879 flags = d.getVarFlags(task)
880 def getTask(name):
881 if not name in task_deps:
882 task_deps[name] = {}
883 if name in flags:
884 deptask = d.expand(flags[name])
Andrew Geissler09036742021-06-25 14:25:14 -0500885 if name in ['noexec', 'fakeroot', 'nostamp']:
886 if deptask != '1':
887 bb.warn("In a future version of BitBake, setting the '{}' flag to something other than '1' "
888 "will result in the flag not being set. See YP bug #13808.".format(name))
889
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500890 task_deps[name][task] = deptask
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800891 getTask('mcdepends')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500892 getTask('depends')
893 getTask('rdepends')
894 getTask('deptask')
895 getTask('rdeptask')
896 getTask('recrdeptask')
897 getTask('recideptask')
898 getTask('nostamp')
899 getTask('fakeroot')
900 getTask('noexec')
901 getTask('umask')
902 task_deps['parents'][task] = []
903 if 'deps' in flags:
904 for dep in flags['deps']:
Brad Bishopc342db32019-05-15 21:57:59 -0400905 # Check and warn for "addtask task after foo" while foo does not exist
906 #if not dep in tasklist:
907 # bb.warn('%s: dependent task %s for %s does not exist' % (d.getVar('PN'), dep, task))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500908 dep = d.expand(dep)
909 task_deps['parents'][task].append(dep)
910
911 # don't assume holding a reference
912 d.setVar('_task_deps', task_deps)
913
914def addtask(task, before, after, d):
915 if task[:3] != "do_":
916 task = "do_" + task
917
918 d.setVarFlag(task, "task", 1)
919 bbtasks = d.getVar('__BBTASKS', False) or []
920 if task not in bbtasks:
921 bbtasks.append(task)
922 d.setVar('__BBTASKS', bbtasks)
923
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500924 existing = d.getVarFlag(task, "deps", False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500925 if after is not None:
926 # set up deps for function
927 for entry in after.split():
928 if entry not in existing:
929 existing.append(entry)
930 d.setVarFlag(task, "deps", existing)
931 if before is not None:
932 # set up things that depend on this func
933 for entry in before.split():
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500934 existing = d.getVarFlag(entry, "deps", False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500935 if task not in existing:
936 d.setVarFlag(entry, "deps", [task] + existing)
937
938def deltask(task, d):
939 if task[:3] != "do_":
940 task = "do_" + task
941
942 bbtasks = d.getVar('__BBTASKS', False) or []
943 if task in bbtasks:
944 bbtasks.remove(task)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600945 d.delVarFlag(task, 'task')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500946 d.setVar('__BBTASKS', bbtasks)
947
948 d.delVarFlag(task, 'deps')
949 for bbtask in d.getVar('__BBTASKS', False) or []:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500950 deps = d.getVarFlag(bbtask, 'deps', False) or []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500951 if task in deps:
952 deps.remove(task)
953 d.setVarFlag(bbtask, 'deps', deps)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500954
955def preceedtask(task, with_recrdeptasks, d):
956 """
957 Returns a set of tasks in the current recipe which were specified as
958 precondition by the task itself ("after") or which listed themselves
959 as precondition ("before"). Preceeding tasks specified via the
960 "recrdeptask" are included in the result only if requested. Beware
961 that this may lead to the task itself being listed.
962 """
963 preceed = set()
Brad Bishop316dfdd2018-06-25 12:45:53 -0400964
965 # Ignore tasks which don't exist
966 tasks = d.getVar('__BBTASKS', False)
967 if task not in tasks:
968 return preceed
969
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500970 preceed.update(d.getVarFlag(task, 'deps') or [])
971 if with_recrdeptasks:
972 recrdeptask = d.getVarFlag(task, 'recrdeptask')
973 if recrdeptask:
974 preceed.update(recrdeptask.split())
975 return preceed
976
977def tasksbetween(task_start, task_end, d):
978 """
979 Return the list of tasks between two tasks in the current recipe,
980 where task_start is to start at and task_end is the task to end at
981 (and task_end has a dependency chain back to task_start).
982 """
983 outtasks = []
984 tasks = list(filter(lambda k: d.getVarFlag(k, "task"), d.keys()))
985 def follow_chain(task, endtask, chain=None):
986 if not chain:
987 chain = []
Andrew Geissler5199d832021-09-24 16:47:35 -0500988 if task in chain:
989 bb.fatal("Circular task dependencies as %s depends on itself via the chain %s" % (task, " -> ".join(chain)))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500990 chain.append(task)
991 for othertask in tasks:
992 if othertask == task:
993 continue
994 if task == endtask:
995 for ctask in chain:
996 if ctask not in outtasks:
997 outtasks.append(ctask)
998 else:
999 deps = d.getVarFlag(othertask, 'deps', False)
1000 if task in deps:
1001 follow_chain(othertask, endtask, chain)
1002 chain.pop()
1003 follow_chain(task_start, task_end)
1004 return outtasks