blob: 22428a649c962a15901eccc0a5cb91f59d809e3d [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# BitBake 'Build' implementation
5#
6# Core code for function execution and task handling in the
7# BitBake build tools.
8#
9# Copyright (C) 2003, 2004 Chris Larson
10#
11# Based on Gentoo's portage.py.
12#
13# This program is free software; you can redistribute it and/or modify
14# it under the terms of the GNU General Public License version 2 as
15# published by the Free Software Foundation.
16#
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License along
23# with this program; if not, write to the Free Software Foundation, Inc.,
24# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25#
26# Based on functions from the base bb module, Copyright 2003 Holger Schurig
27
28import os
29import sys
30import logging
31import shlex
32import glob
33import time
34import stat
35import bb
36import bb.msg
37import bb.process
38from contextlib import nested
39from bb import event, utils
40
41bblogger = logging.getLogger('BitBake')
42logger = logging.getLogger('BitBake.Build')
43
44NULL = open(os.devnull, 'r+')
45
46__mtime_cache = {}
47
48def cached_mtime_noerror(f):
49 if f not in __mtime_cache:
50 try:
51 __mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
52 except OSError:
53 return 0
54 return __mtime_cache[f]
55
56def reset_cache():
57 global __mtime_cache
58 __mtime_cache = {}
59
60# When we execute a Python function, we'd like certain things
61# in all namespaces, hence we add them to __builtins__.
62# If we do not do this and use the exec globals, they will
63# not be available to subfunctions.
64__builtins__['bb'] = bb
65__builtins__['os'] = os
66
67class FuncFailed(Exception):
68 def __init__(self, name = None, logfile = None):
69 self.logfile = logfile
70 self.name = name
71 if name:
72 self.msg = 'Function failed: %s' % name
73 else:
74 self.msg = "Function failed"
75
76 def __str__(self):
77 if self.logfile and os.path.exists(self.logfile):
78 msg = ("%s (log file is located at %s)" %
79 (self.msg, self.logfile))
80 else:
81 msg = self.msg
82 return msg
83
84class TaskBase(event.Event):
85 """Base class for task events"""
86
87 def __init__(self, t, logfile, d):
88 self._task = t
89 self._package = d.getVar("PF", True)
90 self.taskfile = d.getVar("FILE", True)
91 self.taskname = self._task
92 self.logfile = logfile
93 self.time = time.time()
94 event.Event.__init__(self)
95 self._message = "recipe %s: task %s: %s" % (d.getVar("PF", True), t, self.getDisplayName())
96
97 def getTask(self):
98 return self._task
99
100 def setTask(self, task):
101 self._task = task
102
103 def getDisplayName(self):
104 return bb.event.getName(self)[4:]
105
106 task = property(getTask, setTask, None, "task property")
107
108class TaskStarted(TaskBase):
109 """Task execution started"""
110 def __init__(self, t, logfile, taskflags, d):
111 super(TaskStarted, self).__init__(t, logfile, d)
112 self.taskflags = taskflags
113
114class TaskSucceeded(TaskBase):
115 """Task execution completed"""
116
117class TaskFailed(TaskBase):
118 """Task execution failed"""
119
120 def __init__(self, task, logfile, metadata, errprinted = False):
121 self.errprinted = errprinted
122 super(TaskFailed, self).__init__(task, logfile, metadata)
123
124class TaskFailedSilent(TaskBase):
125 """Task execution failed (silently)"""
126 def getDisplayName(self):
127 # Don't need to tell the user it was silent
128 return "Failed"
129
130class TaskInvalid(TaskBase):
131
132 def __init__(self, task, metadata):
133 super(TaskInvalid, self).__init__(task, None, metadata)
134 self._message = "No such task '%s'" % task
135
136
137class LogTee(object):
138 def __init__(self, logger, outfile):
139 self.outfile = outfile
140 self.logger = logger
141 self.name = self.outfile.name
142
143 def write(self, string):
144 self.logger.plain(string)
145 self.outfile.write(string)
146
147 def __enter__(self):
148 self.outfile.__enter__()
149 return self
150
151 def __exit__(self, *excinfo):
152 self.outfile.__exit__(*excinfo)
153
154 def __repr__(self):
155 return '<LogTee {0}>'.format(self.name)
156 def flush(self):
157 self.outfile.flush()
158
159def exec_func(func, d, dirs = None):
160 """Execute a BB 'function'"""
161
162 body = d.getVar(func, False)
163 if not body:
164 if body is None:
165 logger.warn("Function %s doesn't exist", func)
166 return
167
168 flags = d.getVarFlags(func)
169 cleandirs = flags.get('cleandirs')
170 if cleandirs:
171 for cdir in d.expand(cleandirs).split():
172 bb.utils.remove(cdir, True)
173 bb.utils.mkdirhier(cdir)
174
175 if dirs is None:
176 dirs = flags.get('dirs')
177 if dirs:
178 dirs = d.expand(dirs).split()
179
180 if dirs:
181 for adir in dirs:
182 bb.utils.mkdirhier(adir)
183 adir = dirs[-1]
184 else:
185 adir = d.getVar('B', True)
186 bb.utils.mkdirhier(adir)
187
188 ispython = flags.get('python')
189
190 lockflag = flags.get('lockfiles')
191 if lockflag:
192 lockfiles = [f for f in d.expand(lockflag).split()]
193 else:
194 lockfiles = None
195
196 tempdir = d.getVar('T', True)
197
198 # or func allows items to be executed outside of the normal
199 # task set, such as buildhistory
200 task = d.getVar('BB_RUNTASK', True) or func
201 if task == func:
202 taskfunc = task
203 else:
204 taskfunc = "%s.%s" % (task, func)
205
206 runfmt = d.getVar('BB_RUNFMT', True) or "run.{func}.{pid}"
207 runfn = runfmt.format(taskfunc=taskfunc, task=task, func=func, pid=os.getpid())
208 runfile = os.path.join(tempdir, runfn)
209 bb.utils.mkdirhier(os.path.dirname(runfile))
210
211 # Setup the courtesy link to the runfn, only for tasks
212 # we create the link 'just' before the run script is created
213 # if we create it after, and if the run script fails, then the
214 # link won't be created as an exception would be fired.
215 if task == func:
216 runlink = os.path.join(tempdir, 'run.{0}'.format(task))
217 if runlink:
218 bb.utils.remove(runlink)
219
220 try:
221 os.symlink(runfn, runlink)
222 except OSError:
223 pass
224
225 with bb.utils.fileslocked(lockfiles):
226 if ispython:
227 exec_func_python(func, d, runfile, cwd=adir)
228 else:
229 exec_func_shell(func, d, runfile, cwd=adir)
230
231_functionfmt = """
232def {function}(d):
233{body}
234
235{function}(d)
236"""
237logformatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
238def exec_func_python(func, d, runfile, cwd=None):
239 """Execute a python BB 'function'"""
240
241 bbfile = d.getVar('FILE', True)
242 code = _functionfmt.format(function=func, body=d.getVar(func, True))
243 bb.utils.mkdirhier(os.path.dirname(runfile))
244 with open(runfile, 'w') as script:
245 bb.data.emit_func_python(func, script, d)
246
247 if cwd:
248 try:
249 olddir = os.getcwd()
250 except OSError:
251 olddir = None
252 os.chdir(cwd)
253
254 bb.debug(2, "Executing python function %s" % func)
255
256 try:
257 comp = utils.better_compile(code, func, bbfile)
258 utils.better_exec(comp, {"d": d}, code, bbfile)
259 except (bb.parse.SkipRecipe, bb.build.FuncFailed):
260 raise
261 except:
262 raise FuncFailed(func, None)
263 finally:
264 bb.debug(2, "Python function %s finished" % func)
265
266 if cwd and olddir:
267 try:
268 os.chdir(olddir)
269 except OSError:
270 pass
271
272def shell_trap_code():
273 return '''#!/bin/sh\n
274# Emit a useful diagnostic if something fails:
275bb_exit_handler() {
276 ret=$?
277 case $ret in
278 0) ;;
279 *) case $BASH_VERSION in
280 "") echo "WARNING: exit code $ret from a shell command.";;
281 *) echo "WARNING: ${BASH_SOURCE[0]}:${BASH_LINENO[0]} exit $ret from
282 \"$BASH_COMMAND\"";;
283 esac
284 exit $ret
285 esac
286}
287trap 'bb_exit_handler' 0
288set -e
289'''
290
291def exec_func_shell(func, d, runfile, cwd=None):
292 """Execute a shell function from the metadata
293
294 Note on directory behavior. The 'dirs' varflag should contain a list
295 of the directories you need created prior to execution. The last
296 item in the list is where we will chdir/cd to.
297 """
298
299 # Don't let the emitted shell script override PWD
300 d.delVarFlag('PWD', 'export')
301
302 with open(runfile, 'w') as script:
303 script.write(shell_trap_code())
304
305 bb.data.emit_func(func, script, d)
306
307 if bb.msg.loggerVerboseLogs:
308 script.write("set -x\n")
309 if cwd:
310 script.write("cd '%s'\n" % cwd)
311 script.write("%s\n" % func)
312 script.write('''
313# cleanup
314ret=$?
315trap '' 0
316exit $ret
317''')
318
319 os.chmod(runfile, 0775)
320
321 cmd = runfile
322 if d.getVarFlag(func, 'fakeroot'):
323 fakerootcmd = d.getVar('FAKEROOT', True)
324 if fakerootcmd:
325 cmd = [fakerootcmd, runfile]
326
327 if bb.msg.loggerDefaultVerbose:
328 logfile = LogTee(logger, sys.stdout)
329 else:
330 logfile = sys.stdout
331
332 def readfifo(data):
333 lines = data.split('\0')
334 for line in lines:
335 splitval = line.split(' ', 1)
336 cmd = splitval[0]
337 if len(splitval) > 1:
338 value = splitval[1]
339 else:
340 value = ''
341 if cmd == 'bbplain':
342 bb.plain(value)
343 elif cmd == 'bbnote':
344 bb.note(value)
345 elif cmd == 'bbwarn':
346 bb.warn(value)
347 elif cmd == 'bberror':
348 bb.error(value)
349 elif cmd == 'bbfatal':
350 # The caller will call exit themselves, so bb.error() is
351 # what we want here rather than bb.fatal()
352 bb.error(value)
353 elif cmd == 'bbfatal_log':
354 bb.error(value, forcelog=True)
355 elif cmd == 'bbdebug':
356 splitval = value.split(' ', 1)
357 level = int(splitval[0])
358 value = splitval[1]
359 bb.debug(level, value)
360
361 tempdir = d.getVar('T', True)
362 fifopath = os.path.join(tempdir, 'fifo.%s' % os.getpid())
363 if os.path.exists(fifopath):
364 os.unlink(fifopath)
365 os.mkfifo(fifopath)
366 with open(fifopath, 'r+') as fifo:
367 try:
368 bb.debug(2, "Executing shell function %s" % func)
369
370 try:
371 with open(os.devnull, 'r+') as stdin:
372 bb.process.run(cmd, shell=False, stdin=stdin, log=logfile, extrafiles=[(fifo,readfifo)])
373 except bb.process.CmdError:
374 logfn = d.getVar('BB_LOGFILE', True)
375 raise FuncFailed(func, logfn)
376 finally:
377 os.unlink(fifopath)
378
379 bb.debug(2, "Shell function %s finished" % func)
380
381def _task_data(fn, task, d):
382 localdata = bb.data.createCopy(d)
383 localdata.setVar('BB_FILENAME', fn)
384 localdata.setVar('BB_CURRENTTASK', task[3:])
385 localdata.setVar('OVERRIDES', 'task-%s:%s' %
386 (task[3:].replace('_', '-'), d.getVar('OVERRIDES', False)))
387 localdata.finalize()
388 bb.data.expandKeys(localdata)
389 return localdata
390
391def _exec_task(fn, task, d, quieterr):
392 """Execute a BB 'task'
393
394 Execution of a task involves a bit more setup than executing a function,
395 running it with its own local metadata, and with some useful variables set.
396 """
397 if not d.getVarFlag(task, 'task'):
398 event.fire(TaskInvalid(task, d), d)
399 logger.error("No such task: %s" % task)
400 return 1
401
402 logger.debug(1, "Executing task %s", task)
403
404 localdata = _task_data(fn, task, d)
405 tempdir = localdata.getVar('T', True)
406 if not tempdir:
407 bb.fatal("T variable not set, unable to build")
408
409 # Change nice level if we're asked to
410 nice = localdata.getVar("BB_TASK_NICE_LEVEL", True)
411 if nice:
412 curnice = os.nice(0)
413 nice = int(nice) - curnice
414 newnice = os.nice(nice)
415 logger.debug(1, "Renice to %s " % newnice)
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500416 ionice = localdata.getVar("BB_TASK_IONICE_LEVEL", True)
417 if ionice:
418 try:
419 cls, prio = ionice.split(".", 1)
420 bb.utils.ioprio_set(os.getpid(), int(cls), int(prio))
421 except:
422 bb.warn("Invalid ionice level %s" % ionice)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500423
424 bb.utils.mkdirhier(tempdir)
425
426 # Determine the logfile to generate
427 logfmt = localdata.getVar('BB_LOGFMT', True) or 'log.{task}.{pid}'
428 logbase = logfmt.format(task=task, pid=os.getpid())
429
430 # Document the order of the tasks...
431 logorder = os.path.join(tempdir, 'log.task_order')
432 try:
433 with open(logorder, 'a') as logorderfile:
434 logorderfile.write('{0} ({1}): {2}\n'.format(task, os.getpid(), logbase))
435 except OSError:
436 logger.exception("Opening log file '%s'", logorder)
437 pass
438
439 # Setup the courtesy link to the logfn
440 loglink = os.path.join(tempdir, 'log.{0}'.format(task))
441 logfn = os.path.join(tempdir, logbase)
442 if loglink:
443 bb.utils.remove(loglink)
444
445 try:
446 os.symlink(logbase, loglink)
447 except OSError:
448 pass
449
450 prefuncs = localdata.getVarFlag(task, 'prefuncs', expand=True)
451 postfuncs = localdata.getVarFlag(task, 'postfuncs', expand=True)
452
453 class ErrorCheckHandler(logging.Handler):
454 def __init__(self):
455 self.triggered = False
456 logging.Handler.__init__(self, logging.ERROR)
457 def emit(self, record):
458 if getattr(record, 'forcelog', False):
459 self.triggered = False
460 else:
461 self.triggered = True
462
463 # Handle logfiles
464 si = open('/dev/null', 'r')
465 try:
466 bb.utils.mkdirhier(os.path.dirname(logfn))
467 logfile = open(logfn, 'w')
468 except OSError:
469 logger.exception("Opening log file '%s'", logfn)
470 pass
471
472 # Dup the existing fds so we dont lose them
473 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
474 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
475 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
476
477 # Replace those fds with our own
478 os.dup2(si.fileno(), osi[1])
479 os.dup2(logfile.fileno(), oso[1])
480 os.dup2(logfile.fileno(), ose[1])
481
482 # Ensure Python logging goes to the logfile
483 handler = logging.StreamHandler(logfile)
484 handler.setFormatter(logformatter)
485 # Always enable full debug output into task logfiles
486 handler.setLevel(logging.DEBUG - 2)
487 bblogger.addHandler(handler)
488
489 errchk = ErrorCheckHandler()
490 bblogger.addHandler(errchk)
491
492 localdata.setVar('BB_LOGFILE', logfn)
493 localdata.setVar('BB_RUNTASK', task)
494
495 flags = localdata.getVarFlags(task)
496
497 event.fire(TaskStarted(task, logfn, flags, localdata), localdata)
498 try:
499 for func in (prefuncs or '').split():
500 exec_func(func, localdata)
501 exec_func(task, localdata)
502 for func in (postfuncs or '').split():
503 exec_func(func, localdata)
504 except FuncFailed as exc:
505 if quieterr:
506 event.fire(TaskFailedSilent(task, logfn, localdata), localdata)
507 else:
508 errprinted = errchk.triggered
509 logger.error(str(exc))
510 event.fire(TaskFailed(task, logfn, localdata, errprinted), localdata)
511 return 1
512 finally:
513 sys.stdout.flush()
514 sys.stderr.flush()
515
516 bblogger.removeHandler(handler)
517
518 # Restore the backup fds
519 os.dup2(osi[0], osi[1])
520 os.dup2(oso[0], oso[1])
521 os.dup2(ose[0], ose[1])
522
523 # Close the backup fds
524 os.close(osi[0])
525 os.close(oso[0])
526 os.close(ose[0])
527 si.close()
528
529 logfile.close()
530 if os.path.exists(logfn) and os.path.getsize(logfn) == 0:
531 logger.debug(2, "Zero size logfn %s, removing", logfn)
532 bb.utils.remove(logfn)
533 bb.utils.remove(loglink)
534 event.fire(TaskSucceeded(task, logfn, localdata), localdata)
535
536 if not localdata.getVarFlag(task, 'nostamp') and not localdata.getVarFlag(task, 'selfstamp'):
537 make_stamp(task, localdata)
538
539 return 0
540
541def exec_task(fn, task, d, profile = False):
542 try:
543 quieterr = False
544 if d.getVarFlag(task, "quieterrors") is not None:
545 quieterr = True
546
547 if profile:
548 profname = "profile-%s.log" % (d.getVar("PN", True) + "-" + task)
549 try:
550 import cProfile as profile
551 except:
552 import profile
553 prof = profile.Profile()
554 ret = profile.Profile.runcall(prof, _exec_task, fn, task, d, quieterr)
555 prof.dump_stats(profname)
556 bb.utils.process_profilelog(profname)
557
558 return ret
559 else:
560 return _exec_task(fn, task, d, quieterr)
561
562 except Exception:
563 from traceback import format_exc
564 if not quieterr:
565 logger.error("Build of %s failed" % (task))
566 logger.error(format_exc())
567 failedevent = TaskFailed(task, None, d, True)
568 event.fire(failedevent, d)
569 return 1
570
571def stamp_internal(taskname, d, file_name, baseonly=False):
572 """
573 Internal stamp helper function
574 Makes sure the stamp directory exists
575 Returns the stamp path+filename
576
577 In the bitbake core, d can be a CacheData and file_name will be set.
578 When called in task context, d will be a data store, file_name will not be set
579 """
580 taskflagname = taskname
581 if taskname.endswith("_setscene") and taskname != "do_setscene":
582 taskflagname = taskname.replace("_setscene", "")
583
584 if file_name:
585 stamp = d.stamp_base[file_name].get(taskflagname) or d.stamp[file_name]
586 extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
587 else:
588 stamp = d.getVarFlag(taskflagname, 'stamp-base', True) or d.getVar('STAMP', True)
589 file_name = d.getVar('BB_FILENAME', True)
590 extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info', True) or ""
591
592 if baseonly:
593 return stamp
594
595 if not stamp:
596 return
597
598 stamp = bb.parse.siggen.stampfile(stamp, file_name, taskname, extrainfo)
599
600 stampdir = os.path.dirname(stamp)
601 if cached_mtime_noerror(stampdir) == 0:
602 bb.utils.mkdirhier(stampdir)
603
604 return stamp
605
606def stamp_cleanmask_internal(taskname, d, file_name):
607 """
608 Internal stamp helper function to generate stamp cleaning mask
609 Returns the stamp path+filename
610
611 In the bitbake core, d can be a CacheData and file_name will be set.
612 When called in task context, d will be a data store, file_name will not be set
613 """
614 taskflagname = taskname
615 if taskname.endswith("_setscene") and taskname != "do_setscene":
616 taskflagname = taskname.replace("_setscene", "")
617
618 if file_name:
619 stamp = d.stamp_base_clean[file_name].get(taskflagname) or d.stampclean[file_name]
620 extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
621 else:
622 stamp = d.getVarFlag(taskflagname, 'stamp-base-clean', True) or d.getVar('STAMPCLEAN', True)
623 file_name = d.getVar('BB_FILENAME', True)
624 extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info', True) or ""
625
626 if not stamp:
627 return []
628
629 cleanmask = bb.parse.siggen.stampcleanmask(stamp, file_name, taskname, extrainfo)
630
631 return [cleanmask, cleanmask.replace(taskflagname, taskflagname + "_setscene")]
632
633def make_stamp(task, d, file_name = None):
634 """
635 Creates/updates a stamp for a given task
636 (d can be a data dict or dataCache)
637 """
638 cleanmask = stamp_cleanmask_internal(task, d, file_name)
639 for mask in cleanmask:
640 for name in glob.glob(mask):
641 # Preserve sigdata files in the stamps directory
642 if "sigdata" in name:
643 continue
644 # Preserve taint files in the stamps directory
645 if name.endswith('.taint'):
646 continue
647 os.unlink(name)
648
649 stamp = stamp_internal(task, d, file_name)
650 # Remove the file and recreate to force timestamp
651 # change on broken NFS filesystems
652 if stamp:
653 bb.utils.remove(stamp)
654 open(stamp, "w").close()
655
656 # If we're in task context, write out a signature file for each task
657 # as it completes
658 if not task.endswith("_setscene") and task != "do_setscene" and not file_name:
659 stampbase = stamp_internal(task, d, None, True)
660 file_name = d.getVar('BB_FILENAME', True)
661 bb.parse.siggen.dump_sigtask(file_name, task, stampbase, True)
662
663def del_stamp(task, d, file_name = None):
664 """
665 Removes a stamp for a given task
666 (d can be a data dict or dataCache)
667 """
668 stamp = stamp_internal(task, d, file_name)
669 bb.utils.remove(stamp)
670
671def write_taint(task, d, file_name = None):
672 """
673 Creates a "taint" file which will force the specified task and its
674 dependents to be re-run the next time by influencing the value of its
675 taskhash.
676 (d can be a data dict or dataCache)
677 """
678 import uuid
679 if file_name:
680 taintfn = d.stamp[file_name] + '.' + task + '.taint'
681 else:
682 taintfn = d.getVar('STAMP', True) + '.' + task + '.taint'
683 bb.utils.mkdirhier(os.path.dirname(taintfn))
684 # The specific content of the taint file is not really important,
685 # we just need it to be random, so a random UUID is used
686 with open(taintfn, 'w') as taintf:
687 taintf.write(str(uuid.uuid4()))
688
689def stampfile(taskname, d, file_name = None):
690 """
691 Return the stamp for a given task
692 (d can be a data dict or dataCache)
693 """
694 return stamp_internal(taskname, d, file_name)
695
696def add_tasks(tasklist, d):
697 task_deps = d.getVar('_task_deps', False)
698 if not task_deps:
699 task_deps = {}
700 if not 'tasks' in task_deps:
701 task_deps['tasks'] = []
702 if not 'parents' in task_deps:
703 task_deps['parents'] = {}
704
705 for task in tasklist:
706 task = d.expand(task)
707
708 d.setVarFlag(task, 'task', 1)
709
710 if not task in task_deps['tasks']:
711 task_deps['tasks'].append(task)
712
713 flags = d.getVarFlags(task)
714 def getTask(name):
715 if not name in task_deps:
716 task_deps[name] = {}
717 if name in flags:
718 deptask = d.expand(flags[name])
719 task_deps[name][task] = deptask
720 getTask('depends')
721 getTask('rdepends')
722 getTask('deptask')
723 getTask('rdeptask')
724 getTask('recrdeptask')
725 getTask('recideptask')
726 getTask('nostamp')
727 getTask('fakeroot')
728 getTask('noexec')
729 getTask('umask')
730 task_deps['parents'][task] = []
731 if 'deps' in flags:
732 for dep in flags['deps']:
733 dep = d.expand(dep)
734 task_deps['parents'][task].append(dep)
735
736 # don't assume holding a reference
737 d.setVar('_task_deps', task_deps)
738
739def addtask(task, before, after, d):
740 if task[:3] != "do_":
741 task = "do_" + task
742
743 d.setVarFlag(task, "task", 1)
744 bbtasks = d.getVar('__BBTASKS', False) or []
745 if task not in bbtasks:
746 bbtasks.append(task)
747 d.setVar('__BBTASKS', bbtasks)
748
749 existing = d.getVarFlag(task, "deps") or []
750 if after is not None:
751 # set up deps for function
752 for entry in after.split():
753 if entry not in existing:
754 existing.append(entry)
755 d.setVarFlag(task, "deps", existing)
756 if before is not None:
757 # set up things that depend on this func
758 for entry in before.split():
759 existing = d.getVarFlag(entry, "deps") or []
760 if task not in existing:
761 d.setVarFlag(entry, "deps", [task] + existing)
762
763def deltask(task, d):
764 if task[:3] != "do_":
765 task = "do_" + task
766
767 bbtasks = d.getVar('__BBTASKS', False) or []
768 if task in bbtasks:
769 bbtasks.remove(task)
770 d.setVar('__BBTASKS', bbtasks)
771
772 d.delVarFlag(task, 'deps')
773 for bbtask in d.getVar('__BBTASKS', False) or []:
774 deps = d.getVarFlag(bbtask, 'deps') or []
775 if task in deps:
776 deps.remove(task)
777 d.setVarFlag(bbtask, 'deps', deps)