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, 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 interact = data.getVarFlag(func, 'interactive', d)
136 if check in globals():
137 if globals()[check](func, deps):
141 t = data.getVar('T', d, 1)
145 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
146 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
148 f = open(runfile, "w")
149 f.write("#!/bin/sh -e\n")
150 if bb.msg.debug_level['general'] > 0: f.write("set -x\n")
153 f.write("cd %s\n" % os.getcwd())
154 if func: f.write("%s\n" % func)
156 os.chmod(runfile, 0775)
158 bb.msg.error(bb.msg.domain.Build, "Function not specified")
162 si = file('/dev/null', 'r')
164 if bb.msg.debug_level['general'] > 0:
165 so = os.popen("tee \"%s\"" % logfile, "w")
167 so = file(logfile, 'w')
169 bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
175 # dup the existing fds so we dont lose them
176 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
177 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
178 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
180 # replace those fds with our own
181 os.dup2(si.fileno(), osi[1])
182 os.dup2(so.fileno(), oso[1])
183 os.dup2(se.fileno(), ose[1])
186 prevdir = os.getcwd()
187 if data.getVarFlag(func, "fakeroot", d):
188 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
191 ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
198 # restore the backups
199 os.dup2(osi[0], osi[1])
200 os.dup2(oso[0], oso[1])
201 os.dup2(ose[0], ose[1])
208 # close the backup fds
214 if bb.msg.debug_level['general'] > 0:
219 bb.msg.error(bb.msg.domain.Build, "function %s failed" % func)
220 if data.getVar("BBINCLUDELOGS", d):
221 bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
222 f = open(logfile, "r")
231 bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
232 raise FuncFailed( logfile )
235 def exec_task(task, d):
236 """Execute an BB 'task'
238 The primary difference between executing a task versus executing
239 a function is that a task exists in the task digraph, and therefore
240 has dependencies amongst other tasks."""
242 # check if the task is in the graph..
243 task_graph = data.getVar('_task_graph', d)
245 task_graph = bb.digraph()
246 data.setVar('_task_graph', task_graph, d)
247 task_cache = data.getVar('_task_cache', d)
250 data.setVar('_task_cache', task_cache, d)
251 if not task_graph.hasnode(task):
252 raise EventException("Missing node in task graph", InvalidTask(task, d))
254 # check whether this task needs executing..
255 if not data.getVarFlag(task, 'force', d):
256 if stamp_is_current(task, d):
259 # follow digraph path up, then execute our way back down
260 def execute(graph, item):
261 if data.getVarFlag(item, 'task', d):
262 if item in task_cache:
266 # deeper than toplevel, exec w/ deps
271 bb.msg.debug(1, bb.msg.domain.Build, "Executing task %s" % item)
272 old_overrides = data.getVar('OVERRIDES', d, 0)
273 localdata = data.createCopy(d)
274 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
275 data.update_data(localdata)
276 event.fire(TaskStarted(item, localdata))
277 exec_func(item, localdata)
278 event.fire(TaskSucceeded(item, localdata))
279 task_cache.append(item)
280 data.setVar('_task_cache', task_cache, d)
281 except FuncFailed, reason:
282 bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % reason )
283 failedevent = TaskFailed(item, d)
284 event.fire(failedevent)
285 raise EventException("Function failed in task: %s" % reason, failedevent)
287 if data.getVarFlag(task, 'dontrundeps', d):
290 task_graph.walkdown(task, execute)
292 # make stamp, or cause event and raise exception
293 if not data.getVarFlag(task, 'nostamp', d):
296 def stamp_is_current_cache(dataCache, file_name, task, checkdeps = 1):
298 Check status of a given task's stamp.
299 Returns 0 if it is not current and needs updating.
300 Same as stamp_is_current but works against the dataCache instead of d
302 task_graph = dataCache.task_queues[file_name]
304 if not dataCache.stamp[file_name]:
307 stampfile = "%s.%s" % (dataCache.stamp[file_name], task)
308 if not os.access(stampfile, os.F_OK):
315 tasktime = os.stat(stampfile)[stat.ST_MTIME]
318 def checkStamp(graph, task):
319 # check for existance
320 if 'nostamp' in dataCache.task_deps[file_name] and task in dataCache.task_deps[file_name]['nostamp']:
323 if not stamp_is_current_cache(dataCache, file_name, task, 0):
326 depfile = "%s.%s" % (dataCache.stamp[file_name], task)
327 deptime = os.stat(depfile)[stat.ST_MTIME]
328 if deptime > tasktime:
332 return task_graph.walkdown(task, checkStamp)
334 def stamp_is_current(task, d, checkdeps = 1):
336 Check status of a given task's stamp.
337 Returns 0 if it is not current and needs updating.
339 task_graph = data.getVar('_task_graph', d)
341 task_graph = bb.digraph()
342 data.setVar('_task_graph', task_graph, d)
343 stamp = data.getVar('STAMP', d)
346 stampfile = "%s.%s" % (data.expand(stamp, d), task)
347 if not os.access(stampfile, os.F_OK):
354 tasktime = os.stat(stampfile)[stat.ST_MTIME]
357 def checkStamp(graph, task):
358 # check for existance
359 if data.getVarFlag(task, 'nostamp', d):
362 if not stamp_is_current(task, d, 0):
365 depfile = "%s.%s" % (data.expand(stamp, d), task)
366 deptime = os.stat(depfile)[stat.ST_MTIME]
367 if deptime > tasktime:
371 return task_graph.walkdown(task, checkStamp)
374 def md5_is_current(task):
375 """Check if a md5 file for a given task is current"""
378 def mkstamp(task, d):
379 """Creates/updates a stamp for a given task"""
380 stamp = data.getVar('STAMP', d)
383 stamp = "%s.%s" % (data.expand(stamp, d), task)
384 mkdirhier(os.path.dirname(stamp))
385 # Remove the file and recreate to force timestamp
386 # change on broken NFS filesystems
387 if os.access(stamp, os.F_OK):
392 def add_task(task, deps, d):
393 task_graph = data.getVar('_task_graph', d)
395 task_graph = bb.digraph()
396 data.setVarFlag(task, 'task', 1, d)
397 task_graph.addnode(task, None)
399 if not task_graph.hasnode(dep):
400 task_graph.addnode(dep, None)
401 task_graph.addnode(task, dep)
402 # don't assume holding a reference
403 data.setVar('_task_graph', task_graph, d)
405 task_deps = data.getVar('_task_deps', d)
409 deptask = data.getVarFlag(task, name, d)
411 if not name in task_deps:
413 task_deps[name][task] = deptask
416 getTask('recrdeptask')
419 data.setVar('_task_deps', task_deps, d)
421 def remove_task(task, kill, d):
422 """Remove an BB 'task'.
424 If kill is 1, also remove tasks that depend on this task."""
426 task_graph = data.getVar('_task_graph', d)
428 task_graph = bb.digraph()
429 if not task_graph.hasnode(task):
432 data.delVarFlag(task, 'task', d)
436 task_graph.delnode(task, ref)
437 data.setVar('_task_graph', task_graph, d)
439 def task_exists(task, d):
440 task_graph = data.getVar('_task_graph', d)
442 task_graph = bb.digraph()
443 data.setVar('_task_graph', task_graph, d)
444 return task_graph.hasnode(task)