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")
59 class TaskBase(event.Event):
60 """Base class for task events"""
62 def __init__(self, t, d = {}):
64 event.Event.__init__(self, d)
69 def setTask(self, task):
72 task = property(getTask, setTask, None, "task property")
74 class TaskStarted(TaskBase):
75 """Task execution started"""
77 class TaskSucceeded(TaskBase):
78 """Task execution completed"""
80 class TaskFailed(TaskBase):
81 """Task execution failed"""
83 class InvalidTask(TaskBase):
89 global _task_data, _task_graph, _task_stack
90 _task_data = data.init()
91 _task_graph = bb.digraph()
95 def exec_func(func, d, dirs = None):
96 """Execute an BB 'function'"""
98 body = data.getVar(func, d)
103 dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
105 adir = data.expand(adir, d)
111 adir = data.getVar('B', d, 1)
113 adir = data.expand(adir, d)
116 prevdir = os.getcwd()
118 prevdir = data.expand('${TOPDIR}', d)
119 if adir and os.access(adir, os.F_OK):
122 if data.getVarFlag(func, "python", d):
123 exec_func_python(func, d)
125 exec_func_shell(func, d)
128 def exec_func_python(func, d):
129 """Execute a python BB 'function'"""
132 tmp = "def " + func + "():\n%s" % data.getVar(func, d)
133 comp = compile(tmp + '\n' + func + '()', bb.data.getVar('FILE', d, 1) + ':' + func, "exec")
134 prevdir = os.getcwd()
140 if os.path.exists(prevdir):
143 def exec_func_shell(func, d):
144 """Execute a shell BB 'function' Returns true if execution was successful.
146 For this, it creates a bash shell script in the tmp dectory, writes the local
147 data into it and finally executes. The output of the shell will end in a log file and stdout.
149 Note on directory behavior. The 'dirs' varflag should contain a list
150 of the directories you need created prior to execution. The last
151 item in the list is where we will chdir/cd to.
155 deps = data.getVarFlag(func, 'deps', d)
156 check = data.getVarFlag(func, 'check', d)
157 if check in globals():
158 if globals()[check](func, deps):
162 t = data.getVar('T', d, 1)
166 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
167 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
169 f = open(runfile, "w")
170 f.write("#!/bin/sh -e\n")
171 if bb.debug_level > 0: f.write("set -x\n")
174 f.write("cd %s\n" % os.getcwd())
175 if func: f.write("%s\n" % func)
177 os.chmod(runfile, 0775)
179 error("Function not specified")
183 si = file('/dev/null', 'r')
185 if bb.debug_level > 0:
186 so = os.popen("tee \"%s\"" % logfile, "w")
188 so = file(logfile, 'w')
190 bb.error("opening log file: %s" % e)
195 # dup the existing fds so we dont lose them
196 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
197 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
198 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
200 # replace those fds with our own
201 os.dup2(si.fileno(), osi[1])
202 os.dup2(so.fileno(), oso[1])
203 os.dup2(se.fileno(), ose[1])
206 prevdir = os.getcwd()
207 if data.getVarFlag(func, "fakeroot", d):
208 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
211 ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
214 # restore the backups
215 os.dup2(osi[0], osi[1])
216 os.dup2(oso[0], oso[1])
217 os.dup2(ose[0], ose[1])
224 # close the backup fds
230 if bb.debug_level > 0:
235 error("function %s failed" % func)
236 if data.getVar("BBINCLUDELOGS", d):
237 error("log data follows (%s)" % logfile)
238 f = open(logfile, "r")
247 error("see log in %s" % logfile)
253 def exec_task(task, d):
254 """Execute an BB 'task'
256 The primary difference between executing a task versus executing
257 a function is that a task exists in the task digraph, and therefore
258 has dependencies amongst other tasks."""
260 # check if the task is in the graph..
261 task_graph = data.getVar('_task_graph', d)
263 task_graph = bb.digraph()
264 data.setVar('_task_graph', task_graph, d)
265 task_cache = data.getVar('_task_cache', d)
268 data.setVar('_task_cache', task_cache, d)
269 if not task_graph.hasnode(task):
270 raise EventException("", InvalidTask(task, d))
272 # check whether this task needs executing..
273 if not data.getVarFlag(task, 'force', d):
274 if stamp_is_current(task, d):
277 # follow digraph path up, then execute our way back down
278 def execute(graph, item):
279 if data.getVarFlag(item, 'task', d):
280 if item in task_cache:
284 # deeper than toplevel, exec w/ deps
289 debug(1, "Executing task %s" % item)
290 old_overrides = data.getVar('OVERRIDES', d, 0)
291 localdata = data.createCopy(d)
292 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
293 data.update_data(localdata)
294 event.fire(TaskStarted(item, localdata))
295 exec_func(item, localdata)
296 event.fire(TaskSucceeded(item, localdata))
297 task_cache.append(item)
298 except FuncFailed, reason:
299 note( "Task failed: %s" % reason )
300 failedevent = TaskFailed(item, d)
301 event.fire(failedevent)
302 raise EventException(None, failedevent)
305 task_graph.walkdown(task, execute)
307 # make stamp, or cause event and raise exception
308 if not data.getVarFlag(task, 'nostamp', d):
312 def stamp_is_current(task, d, checkdeps = 1):
313 """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
314 task_graph = data.getVar('_task_graph', d)
316 task_graph = bb.digraph()
317 data.setVar('_task_graph', task_graph, d)
318 stamp = data.getVar('STAMP', d)
321 stampfile = "%s.%s" % (data.expand(stamp, d), task)
322 if not os.access(stampfile, os.F_OK):
329 tasktime = os.stat(stampfile)[stat.ST_MTIME]
332 def checkStamp(graph, task):
333 # check for existance
334 if data.getVarFlag(task, 'nostamp', d):
337 if not stamp_is_current(task, d, 0):
340 depfile = "%s.%s" % (data.expand(stamp, d), task)
341 deptime = os.stat(depfile)[stat.ST_MTIME]
342 if deptime > tasktime:
346 return task_graph.walkdown(task, checkStamp)
349 def md5_is_current(task):
350 """Check if a md5 file for a given task is current"""
353 def mkstamp(task, d):
354 """Creates/updates a stamp for a given task"""
355 stamp = data.getVar('STAMP', d)
358 stamp = "%s.%s" % (data.expand(stamp, d), task)
359 mkdirhier(os.path.dirname(stamp))
363 def add_task(task, deps, d):
364 task_graph = data.getVar('_task_graph', d)
366 task_graph = bb.digraph()
367 data.setVarFlag(task, 'task', 1, d)
368 task_graph.addnode(task, None)
370 if not task_graph.hasnode(dep):
371 task_graph.addnode(dep, None)
372 task_graph.addnode(task, dep)
373 # don't assume holding a reference
374 data.setVar('_task_graph', task_graph, d)
377 def remove_task(task, kill, d):
378 """Remove an BB 'task'.
380 If kill is 1, also remove tasks that depend on this task."""
382 task_graph = data.getVar('_task_graph', d)
384 task_graph = bb.digraph()
385 if not task_graph.hasnode(task):
388 data.delVarFlag(task, 'task', d)
392 task_graph.delnode(task, ref)
393 data.setVar('_task_graph', task_graph, d)
395 def task_exists(task, d):
396 task_graph = data.getVar('_task_graph', d)
398 task_graph = bb.digraph()
399 data.setVar('_task_graph', task_graph, d)
400 return task_graph.hasnode(task)