2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
5 BitBake 'Build' implementation
7 Core code for function execution and task handling in the
10 Copyright (C) 2003, 2004 Chris Larson
12 Based on Gentoo's portage.py.
14 This program is free software; you can redistribute it and/or modify it under
15 the terms of the GNU General Public License as published by the Free Software
16 Foundation; either version 2 of the License, or (at your option) any later
19 This program is distributed in the hope that it will be useful, but WITHOUT
20 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License along with
25 Based on functions from the base bb module, Copyright 2003 Holger Schurig
28 from bb import debug, data, fetch, fatal, error, note, event, mkdirhier
31 # data holds flags and function name for a given task
32 _task_data = data.init()
34 # graph represents task interdependencies
35 _task_graph = bb.digraph()
37 # stack represents execution order, excepting dependencies
41 class FuncFailed(Exception):
42 """Executed function failed"""
44 class EventException(Exception):
45 """Exception which is associated with an Event."""
47 def __init__(self, msg, event):
53 def setEvent(self, event):
56 event = property(getEvent, setEvent, None, "event property")
58 class TaskBase(event.Event):
59 """Base class for task events"""
61 def __init__(self, t, d = {}):
68 def setTask(self, task):
71 task = property(getTask, setTask, None, "task property")
76 def setData(self, data):
79 data = property(getData, setData, None, "data property")
81 class TaskStarted(TaskBase):
82 """Task execution started"""
84 class TaskSucceeded(TaskBase):
85 """Task execution completed"""
87 class TaskFailed(TaskBase):
88 """Task execution failed"""
90 class InvalidTask(TaskBase):
96 global _task_data, _task_graph, _task_stack
97 _task_data = data.init()
98 _task_graph = bb.digraph()
102 def exec_func(func, d, dirs = None):
103 """Execute an BB 'function'"""
105 body = data.getVar(func, d)
110 dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
112 adir = data.expand(adir, d)
118 adir = data.getVar('B', d, 1)
120 adir = data.expand(adir, d)
123 prevdir = os.getcwd()
125 prevdir = data.expand('${TOPDIR}', d)
126 if adir and os.access(adir, os.F_OK):
129 if data.getVarFlag(func, "python", d):
130 exec_func_python(func, d)
132 exec_func_shell(func, d)
135 def exec_func_python(func, d):
136 """Execute a python BB 'function'"""
139 tmp = "def " + func + "():\n%s" % data.getVar(func, d)
140 comp = compile(tmp + '\n' + func + '()', bb.data.getVar('FILE', d, 1) + ':' + func, "exec")
141 prevdir = os.getcwd()
147 if os.path.exists(prevdir):
150 def exec_func_shell(func, d):
151 """Execute a shell BB 'function' Returns true if execution was successful.
153 For this, it creates a bash shell script in the tmp dectory, writes the local
154 data into it and finally executes. The output of the shell will end in a log file and stdout.
156 Note on directory behavior. The 'dirs' varflag should contain a list
157 of the directories you need created prior to execution. The last
158 item in the list is where we will chdir/cd to.
162 deps = data.getVarFlag(func, 'deps', d)
163 check = data.getVarFlag(func, 'check', d)
164 if check in globals():
165 if globals()[check](func, deps):
169 t = data.getVar('T', d, 1)
173 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
174 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
176 f = open(runfile, "w")
177 f.write("#!/bin/sh -e\n")
178 if bb.debug_level > 0: f.write("set -x\n")
181 f.write("cd %s\n" % os.getcwd())
182 if func: f.write("%s\n" % func)
184 os.chmod(runfile, 0775)
186 error("Function not specified")
190 si = file('/dev/null', 'r')
192 if bb.debug_level > 0:
193 so = os.popen("tee \"%s\"" % logfile, "w")
195 so = file(logfile, 'w')
197 bb.error("opening log file: %s" % e)
202 # dup the existing fds so we dont lose them
203 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
204 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
205 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
207 # replace those fds with our own
208 os.dup2(si.fileno(), osi[1])
209 os.dup2(so.fileno(), oso[1])
210 os.dup2(se.fileno(), ose[1])
213 prevdir = os.getcwd()
214 if data.getVarFlag(func, "fakeroot", d):
215 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
218 ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
221 # restore the backups
222 os.dup2(osi[0], osi[1])
223 os.dup2(oso[0], oso[1])
224 os.dup2(ose[0], ose[1])
231 # close the backup fds
237 if bb.debug_level > 0:
242 error("function %s failed" % func)
243 if data.getVar("BBINCLUDELOGS", d):
244 error("log data follows (%s)" % logfile)
245 f = open(logfile, "r")
254 error("see log in %s" % logfile)
260 def exec_task(task, d):
261 """Execute an BB 'task'
263 The primary difference between executing a task versus executing
264 a function is that a task exists in the task digraph, and therefore
265 has dependencies amongst other tasks."""
267 # check if the task is in the graph..
268 task_graph = data.getVar('_task_graph', d)
270 task_graph = bb.digraph()
271 data.setVar('_task_graph', task_graph, d)
272 task_cache = data.getVar('_task_cache', d)
275 data.setVar('_task_cache', task_cache, d)
276 if not task_graph.hasnode(task):
277 raise EventException("", InvalidTask(task, d))
279 # check whether this task needs executing..
280 if not data.getVarFlag(task, 'force', d):
281 if stamp_is_current(task, d):
284 # follow digraph path up, then execute our way back down
285 def execute(graph, item):
286 if data.getVarFlag(item, 'task', d):
287 if item in task_cache:
291 # deeper than toplevel, exec w/ deps
296 debug(1, "Executing task %s" % item)
297 old_overrides = data.getVar('OVERRIDES', d, 0)
298 from copy import deepcopy
299 localdata = deepcopy(d)
300 data.setVar('OVERRIDES', '%s:%s' % (item, old_overrides), localdata)
301 data.update_data(localdata)
302 event.fire(TaskStarted(item, localdata))
303 exec_func(item, localdata)
304 event.fire(TaskSucceeded(item, localdata))
305 task_cache.append(item)
306 except FuncFailed, reason:
307 note( "Task failed: %s" % reason )
308 failedevent = TaskFailed(item, d)
309 event.fire(failedevent)
310 raise EventException(None, failedevent)
313 task_graph.walkdown(task, execute)
315 # make stamp, or cause event and raise exception
316 if not data.getVarFlag(task, 'nostamp', d):
320 def stamp_is_current(task, d, checkdeps = 1):
321 """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
322 task_graph = data.getVar('_task_graph', d)
324 task_graph = bb.digraph()
325 data.setVar('_task_graph', task_graph, d)
326 stamp = data.getVar('STAMP', d)
329 stampfile = "%s.%s" % (data.expand(stamp, d), task)
330 if not os.access(stampfile, os.F_OK):
337 tasktime = os.stat(stampfile)[stat.ST_MTIME]
340 def checkStamp(graph, task):
341 # check for existance
342 if data.getVarFlag(task, 'nostamp', d):
345 if not stamp_is_current(task, d, 0):
348 depfile = "%s.%s" % (data.expand(stamp, d), task)
349 deptime = os.stat(depfile)[stat.ST_MTIME]
350 if deptime > tasktime:
354 return task_graph.walkdown(task, checkStamp)
357 def md5_is_current(task):
358 """Check if a md5 file for a given task is current"""
361 def mkstamp(task, d):
362 """Creates/updates a stamp for a given task"""
363 stamp = data.getVar('STAMP', d)
366 stamp = "%s.%s" % (data.expand(stamp, d), task)
367 mkdirhier(os.path.dirname(stamp))
371 def add_task(task, deps, d):
372 task_graph = data.getVar('_task_graph', d)
374 task_graph = bb.digraph()
375 data.setVar('_task_graph', task_graph, d)
376 data.setVarFlag(task, 'task', 1, d)
377 task_graph.addnode(task, None)
379 if not task_graph.hasnode(dep):
380 task_graph.addnode(dep, None)
381 task_graph.addnode(task, dep)
384 def remove_task(task, kill, d):
385 """Remove an BB 'task'.
387 If kill is 1, also remove tasks that depend on this task."""
389 task_graph = data.getVar('_task_graph', d)
391 task_graph = bb.digraph()
392 data.setVar('_task_graph', task_graph, d)
393 if not task_graph.hasnode(task):
396 data.delVarFlag(task, 'task', d)
400 task_graph.delnode(task, ref)
402 def task_exists(task, d):
403 task_graph = data.getVar('_task_graph', d)
405 task_graph = bb.digraph()
406 data.setVar('_task_graph', task_graph, d)
407 return task_graph.hasnode(task)