blob: 948c3951f4e3957f6a2bb5cac79773dbf50b09e5 [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)
416
417 bb.utils.mkdirhier(tempdir)
418
419 # Determine the logfile to generate
420 logfmt = localdata.getVar('BB_LOGFMT', True) or 'log.{task}.{pid}'
421 logbase = logfmt.format(task=task, pid=os.getpid())
422
423 # Document the order of the tasks...
424 logorder = os.path.join(tempdir, 'log.task_order')
425 try:
426 with open(logorder, 'a') as logorderfile:
427 logorderfile.write('{0} ({1}): {2}\n'.format(task, os.getpid(), logbase))
428 except OSError:
429 logger.exception("Opening log file '%s'", logorder)
430 pass
431
432 # Setup the courtesy link to the logfn
433 loglink = os.path.join(tempdir, 'log.{0}'.format(task))
434 logfn = os.path.join(tempdir, logbase)
435 if loglink:
436 bb.utils.remove(loglink)
437
438 try:
439 os.symlink(logbase, loglink)
440 except OSError:
441 pass
442
443 prefuncs = localdata.getVarFlag(task, 'prefuncs', expand=True)
444 postfuncs = localdata.getVarFlag(task, 'postfuncs', expand=True)
445
446 class ErrorCheckHandler(logging.Handler):
447 def __init__(self):
448 self.triggered = False
449 logging.Handler.__init__(self, logging.ERROR)
450 def emit(self, record):
451 if getattr(record, 'forcelog', False):
452 self.triggered = False
453 else:
454 self.triggered = True
455
456 # Handle logfiles
457 si = open('/dev/null', 'r')
458 try:
459 bb.utils.mkdirhier(os.path.dirname(logfn))
460 logfile = open(logfn, 'w')
461 except OSError:
462 logger.exception("Opening log file '%s'", logfn)
463 pass
464
465 # Dup the existing fds so we dont lose them
466 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
467 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
468 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
469
470 # Replace those fds with our own
471 os.dup2(si.fileno(), osi[1])
472 os.dup2(logfile.fileno(), oso[1])
473 os.dup2(logfile.fileno(), ose[1])
474
475 # Ensure Python logging goes to the logfile
476 handler = logging.StreamHandler(logfile)
477 handler.setFormatter(logformatter)
478 # Always enable full debug output into task logfiles
479 handler.setLevel(logging.DEBUG - 2)
480 bblogger.addHandler(handler)
481
482 errchk = ErrorCheckHandler()
483 bblogger.addHandler(errchk)
484
485 localdata.setVar('BB_LOGFILE', logfn)
486 localdata.setVar('BB_RUNTASK', task)
487
488 flags = localdata.getVarFlags(task)
489
490 event.fire(TaskStarted(task, logfn, flags, localdata), localdata)
491 try:
492 for func in (prefuncs or '').split():
493 exec_func(func, localdata)
494 exec_func(task, localdata)
495 for func in (postfuncs or '').split():
496 exec_func(func, localdata)
497 except FuncFailed as exc:
498 if quieterr:
499 event.fire(TaskFailedSilent(task, logfn, localdata), localdata)
500 else:
501 errprinted = errchk.triggered
502 logger.error(str(exc))
503 event.fire(TaskFailed(task, logfn, localdata, errprinted), localdata)
504 return 1
505 finally:
506 sys.stdout.flush()
507 sys.stderr.flush()
508
509 bblogger.removeHandler(handler)
510
511 # Restore the backup fds
512 os.dup2(osi[0], osi[1])
513 os.dup2(oso[0], oso[1])
514 os.dup2(ose[0], ose[1])
515
516 # Close the backup fds
517 os.close(osi[0])
518 os.close(oso[0])
519 os.close(ose[0])
520 si.close()
521
522 logfile.close()
523 if os.path.exists(logfn) and os.path.getsize(logfn) == 0:
524 logger.debug(2, "Zero size logfn %s, removing", logfn)
525 bb.utils.remove(logfn)
526 bb.utils.remove(loglink)
527 event.fire(TaskSucceeded(task, logfn, localdata), localdata)
528
529 if not localdata.getVarFlag(task, 'nostamp') and not localdata.getVarFlag(task, 'selfstamp'):
530 make_stamp(task, localdata)
531
532 return 0
533
534def exec_task(fn, task, d, profile = False):
535 try:
536 quieterr = False
537 if d.getVarFlag(task, "quieterrors") is not None:
538 quieterr = True
539
540 if profile:
541 profname = "profile-%s.log" % (d.getVar("PN", True) + "-" + task)
542 try:
543 import cProfile as profile
544 except:
545 import profile
546 prof = profile.Profile()
547 ret = profile.Profile.runcall(prof, _exec_task, fn, task, d, quieterr)
548 prof.dump_stats(profname)
549 bb.utils.process_profilelog(profname)
550
551 return ret
552 else:
553 return _exec_task(fn, task, d, quieterr)
554
555 except Exception:
556 from traceback import format_exc
557 if not quieterr:
558 logger.error("Build of %s failed" % (task))
559 logger.error(format_exc())
560 failedevent = TaskFailed(task, None, d, True)
561 event.fire(failedevent, d)
562 return 1
563
564def stamp_internal(taskname, d, file_name, baseonly=False):
565 """
566 Internal stamp helper function
567 Makes sure the stamp directory exists
568 Returns the stamp path+filename
569
570 In the bitbake core, d can be a CacheData and file_name will be set.
571 When called in task context, d will be a data store, file_name will not be set
572 """
573 taskflagname = taskname
574 if taskname.endswith("_setscene") and taskname != "do_setscene":
575 taskflagname = taskname.replace("_setscene", "")
576
577 if file_name:
578 stamp = d.stamp_base[file_name].get(taskflagname) or d.stamp[file_name]
579 extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
580 else:
581 stamp = d.getVarFlag(taskflagname, 'stamp-base', True) or d.getVar('STAMP', True)
582 file_name = d.getVar('BB_FILENAME', True)
583 extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info', True) or ""
584
585 if baseonly:
586 return stamp
587
588 if not stamp:
589 return
590
591 stamp = bb.parse.siggen.stampfile(stamp, file_name, taskname, extrainfo)
592
593 stampdir = os.path.dirname(stamp)
594 if cached_mtime_noerror(stampdir) == 0:
595 bb.utils.mkdirhier(stampdir)
596
597 return stamp
598
599def stamp_cleanmask_internal(taskname, d, file_name):
600 """
601 Internal stamp helper function to generate stamp cleaning mask
602 Returns the stamp path+filename
603
604 In the bitbake core, d can be a CacheData and file_name will be set.
605 When called in task context, d will be a data store, file_name will not be set
606 """
607 taskflagname = taskname
608 if taskname.endswith("_setscene") and taskname != "do_setscene":
609 taskflagname = taskname.replace("_setscene", "")
610
611 if file_name:
612 stamp = d.stamp_base_clean[file_name].get(taskflagname) or d.stampclean[file_name]
613 extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
614 else:
615 stamp = d.getVarFlag(taskflagname, 'stamp-base-clean', True) or d.getVar('STAMPCLEAN', True)
616 file_name = d.getVar('BB_FILENAME', True)
617 extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info', True) or ""
618
619 if not stamp:
620 return []
621
622 cleanmask = bb.parse.siggen.stampcleanmask(stamp, file_name, taskname, extrainfo)
623
624 return [cleanmask, cleanmask.replace(taskflagname, taskflagname + "_setscene")]
625
626def make_stamp(task, d, file_name = None):
627 """
628 Creates/updates a stamp for a given task
629 (d can be a data dict or dataCache)
630 """
631 cleanmask = stamp_cleanmask_internal(task, d, file_name)
632 for mask in cleanmask:
633 for name in glob.glob(mask):
634 # Preserve sigdata files in the stamps directory
635 if "sigdata" in name:
636 continue
637 # Preserve taint files in the stamps directory
638 if name.endswith('.taint'):
639 continue
640 os.unlink(name)
641
642 stamp = stamp_internal(task, d, file_name)
643 # Remove the file and recreate to force timestamp
644 # change on broken NFS filesystems
645 if stamp:
646 bb.utils.remove(stamp)
647 open(stamp, "w").close()
648
649 # If we're in task context, write out a signature file for each task
650 # as it completes
651 if not task.endswith("_setscene") and task != "do_setscene" and not file_name:
652 stampbase = stamp_internal(task, d, None, True)
653 file_name = d.getVar('BB_FILENAME', True)
654 bb.parse.siggen.dump_sigtask(file_name, task, stampbase, True)
655
656def del_stamp(task, d, file_name = None):
657 """
658 Removes a stamp for a given task
659 (d can be a data dict or dataCache)
660 """
661 stamp = stamp_internal(task, d, file_name)
662 bb.utils.remove(stamp)
663
664def write_taint(task, d, file_name = None):
665 """
666 Creates a "taint" file which will force the specified task and its
667 dependents to be re-run the next time by influencing the value of its
668 taskhash.
669 (d can be a data dict or dataCache)
670 """
671 import uuid
672 if file_name:
673 taintfn = d.stamp[file_name] + '.' + task + '.taint'
674 else:
675 taintfn = d.getVar('STAMP', True) + '.' + task + '.taint'
676 bb.utils.mkdirhier(os.path.dirname(taintfn))
677 # The specific content of the taint file is not really important,
678 # we just need it to be random, so a random UUID is used
679 with open(taintfn, 'w') as taintf:
680 taintf.write(str(uuid.uuid4()))
681
682def stampfile(taskname, d, file_name = None):
683 """
684 Return the stamp for a given task
685 (d can be a data dict or dataCache)
686 """
687 return stamp_internal(taskname, d, file_name)
688
689def add_tasks(tasklist, d):
690 task_deps = d.getVar('_task_deps', False)
691 if not task_deps:
692 task_deps = {}
693 if not 'tasks' in task_deps:
694 task_deps['tasks'] = []
695 if not 'parents' in task_deps:
696 task_deps['parents'] = {}
697
698 for task in tasklist:
699 task = d.expand(task)
700
701 d.setVarFlag(task, 'task', 1)
702
703 if not task in task_deps['tasks']:
704 task_deps['tasks'].append(task)
705
706 flags = d.getVarFlags(task)
707 def getTask(name):
708 if not name in task_deps:
709 task_deps[name] = {}
710 if name in flags:
711 deptask = d.expand(flags[name])
712 task_deps[name][task] = deptask
713 getTask('depends')
714 getTask('rdepends')
715 getTask('deptask')
716 getTask('rdeptask')
717 getTask('recrdeptask')
718 getTask('recideptask')
719 getTask('nostamp')
720 getTask('fakeroot')
721 getTask('noexec')
722 getTask('umask')
723 task_deps['parents'][task] = []
724 if 'deps' in flags:
725 for dep in flags['deps']:
726 dep = d.expand(dep)
727 task_deps['parents'][task].append(dep)
728
729 # don't assume holding a reference
730 d.setVar('_task_deps', task_deps)
731
732def addtask(task, before, after, d):
733 if task[:3] != "do_":
734 task = "do_" + task
735
736 d.setVarFlag(task, "task", 1)
737 bbtasks = d.getVar('__BBTASKS', False) or []
738 if task not in bbtasks:
739 bbtasks.append(task)
740 d.setVar('__BBTASKS', bbtasks)
741
742 existing = d.getVarFlag(task, "deps") or []
743 if after is not None:
744 # set up deps for function
745 for entry in after.split():
746 if entry not in existing:
747 existing.append(entry)
748 d.setVarFlag(task, "deps", existing)
749 if before is not None:
750 # set up things that depend on this func
751 for entry in before.split():
752 existing = d.getVarFlag(entry, "deps") or []
753 if task not in existing:
754 d.setVarFlag(entry, "deps", [task] + existing)
755
756def deltask(task, d):
757 if task[:3] != "do_":
758 task = "do_" + task
759
760 bbtasks = d.getVar('__BBTASKS', False) or []
761 if task in bbtasks:
762 bbtasks.remove(task)
763 d.setVar('__BBTASKS', bbtasks)
764
765 d.delVarFlag(task, 'deps')
766 for bbtask in d.getVar('__BBTASKS', False) or []:
767 deps = d.getVarFlag(bbtask, 'deps') or []
768 if task in deps:
769 deps.remove(task)
770 d.setVarFlag(bbtask, 'deps', deps)