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 data, fetch, fatal, error, note, event, mkdirhier, utils
32 class FuncFailed(Exception):
33 """Executed function failed"""
35 class EventException(Exception):
36 """Exception which is associated with an Event."""
38 def __init__(self, msg, event):
39 self.args = msg, event
41 class TaskBase(event.Event):
42 """Base class for task events"""
44 def __init__(self, t, d ):
46 event.Event.__init__(self, d)
51 def setTask(self, task):
54 task = property(getTask, setTask, None, "task property")
56 class TaskStarted(TaskBase):
57 """Task execution started"""
59 class TaskSucceeded(TaskBase):
60 """Task execution completed"""
62 class TaskFailed(TaskBase):
63 """Task execution failed"""
65 class InvalidTask(TaskBase):
70 def exec_func(func, d, dirs = None):
71 """Execute an BB 'function'"""
73 body = data.getVar(func, d)
78 dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
80 adir = data.expand(adir, d)
86 adir = data.getVar('B', d, 1)
88 adir = data.expand(adir, d)
93 prevdir = data.expand('${TOPDIR}', d)
94 if adir and os.access(adir, os.F_OK):
97 if data.getVarFlag(func, "python", d):
98 exec_func_python(func, d)
100 exec_func_shell(func, d)
102 if os.path.exists(prevdir):
105 def exec_func_python(func, d):
106 """Execute a python BB 'function'"""
109 tmp = "def " + func + "():\n%s" % data.getVar(func, d)
110 tmp += '\n' + func + '()'
111 comp = utils.better_compile(tmp, func, bb.data.getVar('FILE', d, 1) )
112 prevdir = os.getcwd()
117 utils.better_exec(comp,g,tmp, bb.data.getVar('FILE',d,1))
118 if os.path.exists(prevdir):
121 def exec_func_shell(func, d):
122 """Execute a shell BB 'function' Returns true if execution was successful.
124 For this, it creates a bash shell script in the tmp dectory, writes the local
125 data into it and finally executes. The output of the shell will end in a log file and stdout.
127 Note on directory behavior. The 'dirs' varflag should contain a list
128 of the directories you need created prior to execution. The last
129 item in the list is where we will chdir/cd to.
133 deps = data.getVarFlag(func, 'deps', d)
134 check = data.getVarFlag(func, 'check', d)
135 if check in globals():
136 if globals()[check](func, deps):
140 t = data.getVar('T', d, 1)
144 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
145 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
147 f = open(runfile, "w")
148 f.write("#!/bin/sh -e\n")
149 if bb.msg.debug_level > 0: f.write("set -x\n")
152 f.write("cd %s\n" % os.getcwd())
153 if func: f.write("%s\n" % func)
155 os.chmod(runfile, 0775)
157 error("Function not specified")
161 si = file('/dev/null', 'r')
163 if bb.msg.debug_level > 0:
164 so = os.popen("tee \"%s\"" % logfile, "w")
166 so = file(logfile, 'w')
168 bb.error("opening log file: %s" % e)
173 # dup the existing fds so we dont lose them
174 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
175 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
176 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
178 # replace those fds with our own
179 os.dup2(si.fileno(), osi[1])
180 os.dup2(so.fileno(), oso[1])
181 os.dup2(se.fileno(), ose[1])
184 prevdir = os.getcwd()
185 if data.getVarFlag(func, "fakeroot", d):
186 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
189 ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
195 # restore the backups
196 os.dup2(osi[0], osi[1])
197 os.dup2(oso[0], oso[1])
198 os.dup2(ose[0], ose[1])
205 # close the backup fds
211 if bb.msg.debug_level > 0:
216 error("function %s failed" % func)
217 if data.getVar("BBINCLUDELOGS", d):
218 error("log data follows (%s)" % logfile)
219 f = open(logfile, "r")
228 error("see log in %s" % logfile)
229 raise FuncFailed( logfile )
232 def exec_task(task, d):
233 """Execute an BB 'task'
235 The primary difference between executing a task versus executing
236 a function is that a task exists in the task digraph, and therefore
237 has dependencies amongst other tasks."""
239 # check if the task is in the graph..
240 task_graph = data.getVar('_task_graph', d)
242 task_graph = bb.digraph()
243 data.setVar('_task_graph', task_graph, d)
244 task_cache = data.getVar('_task_cache', d)
247 data.setVar('_task_cache', task_cache, d)
248 if not task_graph.hasnode(task):
249 raise EventException("Missing node in task graph", InvalidTask(task, d))
251 # check whether this task needs executing..
252 if not data.getVarFlag(task, 'force', d):
253 if stamp_is_current(task, d):
256 # follow digraph path up, then execute our way back down
257 def execute(graph, item):
258 if data.getVarFlag(item, 'task', d):
259 if item in task_cache:
263 # deeper than toplevel, exec w/ deps
268 bb.msg.debug(1, bb.msg.domain.Build, "Executing task %s" % item)
269 old_overrides = data.getVar('OVERRIDES', d, 0)
270 localdata = data.createCopy(d)
271 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
272 data.update_data(localdata)
273 event.fire(TaskStarted(item, localdata))
274 exec_func(item, localdata)
275 event.fire(TaskSucceeded(item, localdata))
276 task_cache.append(item)
277 data.setVar('_task_cache', task_cache, d)
278 except FuncFailed, reason:
279 note( "Task failed: %s" % reason )
280 failedevent = TaskFailed(item, d)
281 event.fire(failedevent)
282 raise EventException("Function failed in task: %s" % reason, failedevent)
285 task_graph.walkdown(task, execute)
287 # make stamp, or cause event and raise exception
288 if not data.getVarFlag(task, 'nostamp', d):
291 def stamp_is_current_cache(dataCache, file_name, task, checkdeps = 1):
293 Check status of a given task's stamp.
294 Returns 0 if it is not current and needs updating.
295 Same as stamp_is_current but works against the dataCache instead of d
297 task_graph = dataCache.task_queues[file_name]
299 if not dataCache.stamp[file_name]:
302 stampfile = "%s.%s" % (dataCache.stamp[file_name], task)
303 if not os.access(stampfile, os.F_OK):
310 tasktime = os.stat(stampfile)[stat.ST_MTIME]
313 def checkStamp(graph, task):
314 # check for existance
315 if 'nostamp' in dataCache.task_deps[file_name] and task in dataCache.task_deps[file_name]['nostamp']:
318 if not stamp_is_current_cache(dataCache, file_name, task, 0):
321 depfile = "%s.%s" % (dataCache.stamp[file_name], task)
322 deptime = os.stat(depfile)[stat.ST_MTIME]
323 if deptime > tasktime:
327 return task_graph.walkdown(task, checkStamp)
329 def stamp_is_current(task, d, checkdeps = 1):
331 Check status of a given task's stamp.
332 Returns 0 if it is not current and needs updating.
334 task_graph = data.getVar('_task_graph', d)
336 task_graph = bb.digraph()
337 data.setVar('_task_graph', task_graph, d)
338 stamp = data.getVar('STAMP', d)
341 stampfile = "%s.%s" % (data.expand(stamp, d), task)
342 if not os.access(stampfile, os.F_OK):
349 tasktime = os.stat(stampfile)[stat.ST_MTIME]
352 def checkStamp(graph, task):
353 # check for existance
354 if data.getVarFlag(task, 'nostamp', d):
357 if not stamp_is_current(task, d, 0):
360 depfile = "%s.%s" % (data.expand(stamp, d), task)
361 deptime = os.stat(depfile)[stat.ST_MTIME]
362 if deptime > tasktime:
366 return task_graph.walkdown(task, checkStamp)
369 def md5_is_current(task):
370 """Check if a md5 file for a given task is current"""
373 def mkstamp(task, d):
374 """Creates/updates a stamp for a given task"""
375 stamp = data.getVar('STAMP', d)
378 stamp = "%s.%s" % (data.expand(stamp, d), task)
379 print "Updating %s" % stamp
380 mkdirhier(os.path.dirname(stamp))
381 # Remove the file and recreate to force timestamp
382 # change on broken NFS filesystems
383 if os.access(stamp, os.F_OK):
388 def add_task(task, deps, d):
389 task_graph = data.getVar('_task_graph', d)
391 task_graph = bb.digraph()
392 data.setVarFlag(task, 'task', 1, d)
393 task_graph.addnode(task, None)
395 if not task_graph.hasnode(dep):
396 task_graph.addnode(dep, None)
397 task_graph.addnode(task, dep)
398 # don't assume holding a reference
399 data.setVar('_task_graph', task_graph, d)
401 task_deps = data.getVar('_task_deps', d)
405 deptask = data.getVarFlag(task, name, d)
407 if not name in task_deps:
409 task_deps[name][task] = deptask
412 getTask('recrdeptask')
415 data.setVar('_task_deps', task_deps, d)
417 def remove_task(task, kill, d):
418 """Remove an BB 'task'.
420 If kill is 1, also remove tasks that depend on this task."""
422 task_graph = data.getVar('_task_graph', d)
424 task_graph = bb.digraph()
425 if not task_graph.hasnode(task):
428 data.delVarFlag(task, 'task', d)
432 task_graph.delnode(task, ref)
433 data.setVar('_task_graph', task_graph, d)
435 def task_exists(task, d):
436 task_graph = data.getVar('_task_graph', d)
438 task_graph = bb.digraph()
439 data.setVar('_task_graph', task_graph, d)
440 return task_graph.hasnode(task)