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):
48 self.args = msg, event
50 class TaskBase(event.Event):
51 """Base class for task events"""
53 def __init__(self, t, d ):
55 event.Event.__init__(self, d)
60 def setTask(self, task):
63 task = property(getTask, setTask, None, "task property")
65 class TaskStarted(TaskBase):
66 """Task execution started"""
68 class TaskSucceeded(TaskBase):
69 """Task execution completed"""
71 class TaskFailed(TaskBase):
72 """Task execution failed"""
74 class InvalidTask(TaskBase):
80 global _task_data, _task_graph, _task_stack
81 _task_data = data.init()
82 _task_graph = bb.digraph()
86 def exec_func(func, d, dirs = None):
87 """Execute an BB 'function'"""
89 body = data.getVar(func, d)
94 dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
96 adir = data.expand(adir, d)
102 adir = data.getVar('B', d, 1)
104 adir = data.expand(adir, d)
107 prevdir = os.getcwd()
109 prevdir = data.expand('${TOPDIR}', d)
110 if adir and os.access(adir, os.F_OK):
113 if data.getVarFlag(func, "python", d):
114 exec_func_python(func, d)
116 exec_func_shell(func, d)
118 if os.path.exists(prevdir):
121 def exec_func_python(func, d):
122 """Execute a python BB 'function'"""
125 tmp = "def " + func + "():\n%s" % data.getVar(func, d)
126 comp = compile(tmp + '\n' + func + '()', bb.data.getVar('FILE', d, 1) + ':' + func, "exec")
127 prevdir = os.getcwd()
133 if os.path.exists(prevdir):
136 def exec_func_shell(func, d):
137 """Execute a shell BB 'function' Returns true if execution was successful.
139 For this, it creates a bash shell script in the tmp dectory, writes the local
140 data into it and finally executes. The output of the shell will end in a log file and stdout.
142 Note on directory behavior. The 'dirs' varflag should contain a list
143 of the directories you need created prior to execution. The last
144 item in the list is where we will chdir/cd to.
148 deps = data.getVarFlag(func, 'deps', d)
149 check = data.getVarFlag(func, 'check', d)
150 if check in globals():
151 if globals()[check](func, deps):
155 t = data.getVar('T', d, 1)
159 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
160 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
162 f = open(runfile, "w")
163 f.write("#!/bin/sh -e\n")
164 if bb.debug_level > 0: f.write("set -x\n")
167 f.write("cd %s\n" % os.getcwd())
168 if func: f.write("%s\n" % func)
170 os.chmod(runfile, 0775)
172 error("Function not specified")
176 si = file('/dev/null', 'r')
178 if bb.debug_level > 0:
179 so = os.popen("tee \"%s\"" % logfile, "w")
181 so = file(logfile, 'w')
183 bb.error("opening log file: %s" % e)
188 # dup the existing fds so we dont lose them
189 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
190 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
191 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
193 # replace those fds with our own
194 os.dup2(si.fileno(), osi[1])
195 os.dup2(so.fileno(), oso[1])
196 os.dup2(se.fileno(), ose[1])
199 prevdir = os.getcwd()
200 if data.getVarFlag(func, "fakeroot", d):
201 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
204 ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
207 # restore the backups
208 os.dup2(osi[0], osi[1])
209 os.dup2(oso[0], oso[1])
210 os.dup2(ose[0], ose[1])
217 # close the backup fds
223 if bb.debug_level > 0:
228 error("function %s failed" % func)
229 if data.getVar("BBINCLUDELOGS", d):
230 error("log data follows (%s)" % logfile)
231 f = open(logfile, "r")
240 error("see log in %s" % logfile)
241 raise FuncFailed( logfile )
244 def exec_task(task, d):
245 """Execute an BB 'task'
247 The primary difference between executing a task versus executing
248 a function is that a task exists in the task digraph, and therefore
249 has dependencies amongst other tasks."""
251 # check if the task is in the graph..
252 task_graph = data.getVar('_task_graph', d)
254 task_graph = bb.digraph()
255 data.setVar('_task_graph', task_graph, d)
256 task_cache = data.getVar('_task_cache', d)
259 data.setVar('_task_cache', task_cache, d)
260 if not task_graph.hasnode(task):
261 raise EventException("Missing node in task graph", InvalidTask(task, d))
263 # check whether this task needs executing..
264 if not data.getVarFlag(task, 'force', d):
265 if stamp_is_current(task, d):
268 # follow digraph path up, then execute our way back down
269 def execute(graph, item):
270 if data.getVarFlag(item, 'task', d):
271 if item in task_cache:
275 # deeper than toplevel, exec w/ deps
280 debug(1, "Executing task %s" % item)
281 old_overrides = data.getVar('OVERRIDES', d, 0)
282 localdata = data.createCopy(d)
283 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
284 data.update_data(localdata)
285 event.fire(TaskStarted(item, localdata))
286 exec_func(item, localdata)
287 event.fire(TaskSucceeded(item, localdata))
288 task_cache.append(item)
289 data.setVar('_task_cache', task_cache, d)
290 except FuncFailed, reason:
291 note( "Task failed: %s" % reason )
292 failedevent = TaskFailed(item, d)
293 event.fire(failedevent)
294 raise EventException("Function failed in task: %s" % reason, failedevent)
297 task_graph.walkdown(task, execute)
299 # make stamp, or cause event and raise exception
300 if not data.getVarFlag(task, 'nostamp', d):
304 def stamp_is_current(task, d, checkdeps = 1):
305 """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
306 task_graph = data.getVar('_task_graph', d)
308 task_graph = bb.digraph()
309 data.setVar('_task_graph', task_graph, d)
310 stamp = data.getVar('STAMP', d)
313 stampfile = "%s.%s" % (data.expand(stamp, d), task)
314 if not os.access(stampfile, os.F_OK):
321 tasktime = os.stat(stampfile)[stat.ST_MTIME]
324 def checkStamp(graph, task):
325 # check for existance
326 if data.getVarFlag(task, 'nostamp', d):
329 if not stamp_is_current(task, d, 0):
332 depfile = "%s.%s" % (data.expand(stamp, d), task)
333 deptime = os.stat(depfile)[stat.ST_MTIME]
334 if deptime > tasktime:
338 return task_graph.walkdown(task, checkStamp)
341 def md5_is_current(task):
342 """Check if a md5 file for a given task is current"""
345 def mkstamp(task, d):
346 """Creates/updates a stamp for a given task"""
347 stamp = data.getVar('STAMP', d)
350 stamp = "%s.%s" % (data.expand(stamp, d), task)
351 mkdirhier(os.path.dirname(stamp))
355 def add_task(task, deps, d):
356 task_graph = data.getVar('_task_graph', d)
358 task_graph = bb.digraph()
359 data.setVarFlag(task, 'task', 1, d)
360 task_graph.addnode(task, None)
362 if not task_graph.hasnode(dep):
363 task_graph.addnode(dep, None)
364 task_graph.addnode(task, dep)
365 # don't assume holding a reference
366 data.setVar('_task_graph', task_graph, d)
369 def remove_task(task, kill, d):
370 """Remove an BB 'task'.
372 If kill is 1, also remove tasks that depend on this task."""
374 task_graph = data.getVar('_task_graph', d)
376 task_graph = bb.digraph()
377 if not task_graph.hasnode(task):
380 data.delVarFlag(task, 'task', d)
384 task_graph.delnode(task, ref)
385 data.setVar('_task_graph', task_graph, d)
387 def task_exists(task, d):
388 task_graph = data.getVar('_task_graph', d)
390 task_graph = bb.digraph()
391 data.setVar('_task_graph', task_graph, d)
392 return task_graph.hasnode(task)