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, utils
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 tmp += '\n' + func + '()'
127 comp = utils.better_compile(tmp, func, bb.data.getVar('FILE', d, 1) )
128 prevdir = os.getcwd()
133 utils.better_exec(comp,g,tmp, bb.data.getVar('FILE',d,1))
134 if os.path.exists(prevdir):
137 def exec_func_shell(func, d):
138 """Execute a shell BB 'function' Returns true if execution was successful.
140 For this, it creates a bash shell script in the tmp dectory, writes the local
141 data into it and finally executes. The output of the shell will end in a log file and stdout.
143 Note on directory behavior. The 'dirs' varflag should contain a list
144 of the directories you need created prior to execution. The last
145 item in the list is where we will chdir/cd to.
149 deps = data.getVarFlag(func, 'deps', d)
150 check = data.getVarFlag(func, 'check', d)
151 if check in globals():
152 if globals()[check](func, deps):
156 t = data.getVar('T', d, 1)
160 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
161 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
163 f = open(runfile, "w")
164 f.write("#!/bin/sh -e\n")
165 if bb.debug_level > 0: f.write("set -x\n")
168 f.write("cd %s\n" % os.getcwd())
169 if func: f.write("%s\n" % func)
171 os.chmod(runfile, 0775)
173 error("Function not specified")
177 si = file('/dev/null', 'r')
179 if bb.debug_level > 0:
180 so = os.popen("tee \"%s\"" % logfile, "w")
182 so = file(logfile, 'w')
184 bb.error("opening log file: %s" % e)
189 # dup the existing fds so we dont lose them
190 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
191 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
192 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
194 # replace those fds with our own
195 os.dup2(si.fileno(), osi[1])
196 os.dup2(so.fileno(), oso[1])
197 os.dup2(se.fileno(), ose[1])
200 prevdir = os.getcwd()
201 if data.getVarFlag(func, "fakeroot", d):
202 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
205 ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
208 # restore the backups
209 os.dup2(osi[0], osi[1])
210 os.dup2(oso[0], oso[1])
211 os.dup2(ose[0], ose[1])
218 # close the backup fds
224 if bb.debug_level > 0:
229 error("function %s failed" % func)
230 if data.getVar("BBINCLUDELOGS", d):
231 error("log data follows (%s)" % logfile)
232 f = open(logfile, "r")
241 error("see log in %s" % logfile)
242 raise FuncFailed( logfile )
245 def exec_task(task, d):
246 """Execute an BB 'task'
248 The primary difference between executing a task versus executing
249 a function is that a task exists in the task digraph, and therefore
250 has dependencies amongst other tasks."""
252 # check if the task is in the graph..
253 task_graph = data.getVar('_task_graph', d)
255 task_graph = bb.digraph()
256 data.setVar('_task_graph', task_graph, d)
257 task_cache = data.getVar('_task_cache', d)
260 data.setVar('_task_cache', task_cache, d)
261 if not task_graph.hasnode(task):
262 raise EventException("Missing node in task graph", InvalidTask(task, d))
264 # check whether this task needs executing..
265 if not data.getVarFlag(task, 'force', d):
266 if stamp_is_current(task, d):
269 # follow digraph path up, then execute our way back down
270 def execute(graph, item):
271 if data.getVarFlag(item, 'task', d):
272 if item in task_cache:
276 # deeper than toplevel, exec w/ deps
281 debug(1, "Executing task %s" % item)
282 old_overrides = data.getVar('OVERRIDES', d, 0)
283 localdata = data.createCopy(d)
284 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
285 data.update_data(localdata)
286 event.fire(TaskStarted(item, localdata))
287 exec_func(item, localdata)
288 event.fire(TaskSucceeded(item, localdata))
289 task_cache.append(item)
290 data.setVar('_task_cache', task_cache, d)
291 except FuncFailed, reason:
292 note( "Task failed: %s" % reason )
293 failedevent = TaskFailed(item, d)
294 event.fire(failedevent)
295 raise EventException("Function failed in task: %s" % reason, failedevent)
298 task_graph.walkdown(task, execute)
300 # make stamp, or cause event and raise exception
301 if not data.getVarFlag(task, 'nostamp', d):
305 def stamp_is_current(task, d, checkdeps = 1):
306 """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
307 task_graph = data.getVar('_task_graph', d)
309 task_graph = bb.digraph()
310 data.setVar('_task_graph', task_graph, d)
311 stamp = data.getVar('STAMP', d)
314 stampfile = "%s.%s" % (data.expand(stamp, d), task)
315 if not os.access(stampfile, os.F_OK):
322 tasktime = os.stat(stampfile)[stat.ST_MTIME]
325 def checkStamp(graph, task):
326 # check for existance
327 if data.getVarFlag(task, 'nostamp', d):
330 if not stamp_is_current(task, d, 0):
333 depfile = "%s.%s" % (data.expand(stamp, d), task)
334 deptime = os.stat(depfile)[stat.ST_MTIME]
335 if deptime > tasktime:
339 return task_graph.walkdown(task, checkStamp)
342 def md5_is_current(task):
343 """Check if a md5 file for a given task is current"""
346 def mkstamp(task, d):
347 """Creates/updates a stamp for a given task"""
348 stamp = data.getVar('STAMP', d)
351 stamp = "%s.%s" % (data.expand(stamp, d), task)
352 mkdirhier(os.path.dirname(stamp))
356 def add_task(task, deps, d):
357 task_graph = data.getVar('_task_graph', d)
359 task_graph = bb.digraph()
360 data.setVarFlag(task, 'task', 1, d)
361 task_graph.addnode(task, None)
363 if not task_graph.hasnode(dep):
364 task_graph.addnode(dep, None)
365 task_graph.addnode(task, dep)
366 # don't assume holding a reference
367 data.setVar('_task_graph', task_graph, d)
370 def remove_task(task, kill, d):
371 """Remove an BB 'task'.
373 If kill is 1, also remove tasks that depend on this task."""
375 task_graph = data.getVar('_task_graph', d)
377 task_graph = bb.digraph()
378 if not task_graph.hasnode(task):
381 data.delVarFlag(task, 'task', d)
385 task_graph.delnode(task, ref)
386 data.setVar('_task_graph', task_graph, d)
388 def task_exists(task, d):
389 task_graph = data.getVar('_task_graph', d)
391 task_graph = bb.digraph()
392 data.setVar('_task_graph', task_graph, d)
393 return task_graph.hasnode(task)