1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 # BitBake 'Build' implementation
6 # Core code for function execution and task handling in the
9 # Copyright (C) 2003, 2004 Chris Larson
11 # Based on Gentoo's portage.py.
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License version 2 as
15 # published by the Free Software Foundation.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License along
23 # with this program; if not, write to the Free Software Foundation, Inc.,
24 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #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['default'] > 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['default'] > 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 lang_environment = "LC_ALL=C "
192 ret = os.system('%s%ssh -e %s' % (lang_environment, maybe_fakeroot, runfile))
199 # restore the backups
200 os.dup2(osi[0], osi[1])
201 os.dup2(oso[0], oso[1])
202 os.dup2(ose[0], ose[1])
209 # close the backup fds
215 if bb.msg.debug_level['default'] > 0:
220 bb.msg.error(bb.msg.domain.Build, "function %s failed" % func)
221 if data.getVar("BBINCLUDELOGS", d):
222 bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
223 number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
225 os.system('tail -n%s %s' % (number_of_lines, logfile))
227 f = open(logfile, "r")
236 bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
237 raise FuncFailed( logfile )
240 def exec_task(task, d):
241 """Execute an BB 'task'
243 The primary difference between executing a task versus executing
244 a function is that a task exists in the task digraph, and therefore
245 has dependencies amongst other tasks."""
247 # check if the task is in the graph..
248 task_graph = data.getVar('_task_graph', d)
250 task_graph = bb.digraph()
251 data.setVar('_task_graph', task_graph, d)
252 task_cache = data.getVar('_task_cache', d)
255 data.setVar('_task_cache', task_cache, d)
256 if not task_graph.hasnode(task):
257 raise EventException("Missing node in task graph", InvalidTask(task, d))
259 # check whether this task needs executing..
260 if stamp_is_current(task, d):
263 # follow digraph path up, then execute our way back down
264 def execute(graph, item):
265 if data.getVarFlag(item, 'task', d):
266 if item in task_cache:
270 # deeper than toplevel, exec w/ deps
275 bb.msg.debug(1, bb.msg.domain.Build, "Executing task %s" % item)
276 old_overrides = data.getVar('OVERRIDES', d, 0)
277 localdata = data.createCopy(d)
278 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
279 data.update_data(localdata)
280 event.fire(TaskStarted(item, localdata))
281 exec_func(item, localdata)
282 event.fire(TaskSucceeded(item, localdata))
283 task_cache.append(item)
284 data.setVar('_task_cache', task_cache, d)
285 except FuncFailed, reason:
286 bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % reason )
287 failedevent = TaskFailed(item, d)
288 event.fire(failedevent)
289 raise EventException("Function failed in task: %s" % reason, failedevent)
291 if data.getVarFlag(task, 'dontrundeps', d):
294 task_graph.walkdown(task, execute)
296 # make stamp, or cause event and raise exception
297 if not data.getVarFlag(task, 'nostamp', d):
300 def extract_stamp_data(d, fn):
302 Extracts stamp data from d which is either a data dictonary (fn unset)
303 or a dataCache entry (fn set).
306 return (d.task_queues[fn], d.stamp[fn], d.task_deps[fn])
307 task_graph = data.getVar('_task_graph', d)
309 task_graph = bb.digraph()
310 data.setVar('_task_graph', task_graph, d)
311 return (task_graph, data.getVar('STAMP', d, 1), None)
313 def extract_stamp(d, fn):
315 Extracts stamp format which is either a data dictonary (fn unset)
316 or a dataCache entry (fn set).
320 return data.getVar('STAMP', d, 1)
322 def stamp_is_current(task, d, file_name = None, checkdeps = 1):
324 Check status of a given task's stamp.
325 Returns 0 if it is not current and needs updating.
326 (d can be a data dict or dataCache)
329 (task_graph, stampfn, taskdep) = extract_stamp_data(d, file_name)
334 stampfile = "%s.%s" % (stampfn, task)
335 if not os.access(stampfile, os.F_OK):
342 tasktime = os.stat(stampfile)[stat.ST_MTIME]
345 def checkStamp(graph, task):
346 # check for existance
348 if 'nostamp' in taskdep and task in taskdep['nostamp']:
351 if data.getVarFlag(task, 'nostamp', d):
354 if not stamp_is_current(task, d, file_name, 0 ):
357 depfile = "%s.%s" % (stampfn, task)
358 deptime = os.stat(depfile)[stat.ST_MTIME]
359 if deptime > tasktime:
363 return task_graph.walkdown(task, checkStamp)
365 def stamp_internal(task, d, file_name):
367 Internal stamp helper function
368 Removes any stamp for the given task
369 Makes sure the stamp directory exists
370 Returns the stamp path+filename
372 stamp = extract_stamp(d, file_name)
375 stamp = "%s.%s" % (stamp, task)
376 mkdirhier(os.path.dirname(stamp))
377 # Remove the file and recreate to force timestamp
378 # change on broken NFS filesystems
379 if os.access(stamp, os.F_OK):
383 def make_stamp(task, d, file_name = None):
385 Creates/updates a stamp for a given task
386 (d can be a data dict or dataCache)
388 stamp = stamp_internal(task, d, file_name)
393 def del_stamp(task, d, file_name = None):
395 Removes a stamp for a given task
396 (d can be a data dict or dataCache)
398 stamp_internal(task, d, file_name)
400 def add_task(task, deps, d):
401 task_graph = data.getVar('_task_graph', d)
403 task_graph = bb.digraph()
404 data.setVarFlag(task, 'task', 1, d)
405 task_graph.addnode(task, None)
407 if not task_graph.hasnode(dep):
408 task_graph.addnode(dep, None)
409 task_graph.addnode(task, dep)
410 # don't assume holding a reference
411 data.setVar('_task_graph', task_graph, d)
413 task_deps = data.getVar('_task_deps', d)
417 deptask = data.getVarFlag(task, name, d)
419 deptask = data.expand(deptask, d)
420 if not name in task_deps:
422 task_deps[name][task] = deptask
426 getTask('recrdeptask')
429 data.setVar('_task_deps', task_deps, d)
431 def remove_task(task, kill, d):
432 """Remove an BB 'task'.
434 If kill is 1, also remove tasks that depend on this task."""
436 task_graph = data.getVar('_task_graph', d)
438 task_graph = bb.digraph()
439 if not task_graph.hasnode(task):
442 data.delVarFlag(task, 'task', d)
446 task_graph.delnode(task, ref)
447 data.setVar('_task_graph', task_graph, d)
449 def task_exists(task, d):
450 task_graph = data.getVar('_task_graph', d)
452 task_graph = bb.digraph()
453 data.setVar('_task_graph', task_graph, d)
454 return task_graph.hasnode(task)