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)
119 def exec_func_python(func, d):
120 """Execute a python BB 'function'"""
123 tmp = "def " + func + "():\n%s" % data.getVar(func, d)
124 comp = compile(tmp + '\n' + func + '()', bb.data.getVar('FILE', d, 1) + ':' + func, "exec")
125 prevdir = os.getcwd()
131 if os.path.exists(prevdir):
134 def exec_func_shell(func, d):
135 """Execute a shell BB 'function' Returns true if execution was successful.
137 For this, it creates a bash shell script in the tmp dectory, writes the local
138 data into it and finally executes. The output of the shell will end in a log file and stdout.
140 Note on directory behavior. The 'dirs' varflag should contain a list
141 of the directories you need created prior to execution. The last
142 item in the list is where we will chdir/cd to.
146 deps = data.getVarFlag(func, 'deps', d)
147 check = data.getVarFlag(func, 'check', d)
148 if check in globals():
149 if globals()[check](func, deps):
153 t = data.getVar('T', d, 1)
157 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
158 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
160 f = open(runfile, "w")
161 f.write("#!/bin/sh -e\n")
162 if bb.debug_level > 0: f.write("set -x\n")
165 f.write("cd %s\n" % os.getcwd())
166 if func: f.write("%s\n" % func)
168 os.chmod(runfile, 0775)
170 error("Function not specified")
174 si = file('/dev/null', 'r')
176 if bb.debug_level > 0:
177 so = os.popen("tee \"%s\"" % logfile, "w")
179 so = file(logfile, 'w')
181 bb.error("opening log file: %s" % e)
186 # dup the existing fds so we dont lose them
187 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
188 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
189 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
191 # replace those fds with our own
192 os.dup2(si.fileno(), osi[1])
193 os.dup2(so.fileno(), oso[1])
194 os.dup2(se.fileno(), ose[1])
197 prevdir = os.getcwd()
198 if data.getVarFlag(func, "fakeroot", d):
199 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
202 ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
205 # restore the backups
206 os.dup2(osi[0], osi[1])
207 os.dup2(oso[0], oso[1])
208 os.dup2(ose[0], ose[1])
215 # close the backup fds
221 if bb.debug_level > 0:
226 error("function %s failed" % func)
227 if data.getVar("BBINCLUDELOGS", d):
228 error("log data follows (%s)" % logfile)
229 f = open(logfile, "r")
238 error("see log in %s" % logfile)
239 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 except FuncFailed, reason:
290 note( "Task failed: %s" % reason )
291 failedevent = TaskFailed(item, d)
292 event.fire(failedevent)
293 raise EventException("Function failed in task: %s" % reason, failedevent)
296 task_graph.walkdown(task, execute)
298 # make stamp, or cause event and raise exception
299 if not data.getVarFlag(task, 'nostamp', d):
303 def stamp_is_current(task, d, checkdeps = 1):
304 """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
305 task_graph = data.getVar('_task_graph', d)
307 task_graph = bb.digraph()
308 data.setVar('_task_graph', task_graph, d)
309 stamp = data.getVar('STAMP', d)
312 stampfile = "%s.%s" % (data.expand(stamp, d), task)
313 if not os.access(stampfile, os.F_OK):
320 tasktime = os.stat(stampfile)[stat.ST_MTIME]
323 def checkStamp(graph, task):
324 # check for existance
325 if data.getVarFlag(task, 'nostamp', d):
328 if not stamp_is_current(task, d, 0):
331 depfile = "%s.%s" % (data.expand(stamp, d), task)
332 deptime = os.stat(depfile)[stat.ST_MTIME]
333 if deptime > tasktime:
337 return task_graph.walkdown(task, checkStamp)
340 def md5_is_current(task):
341 """Check if a md5 file for a given task is current"""
344 def mkstamp(task, d):
345 """Creates/updates a stamp for a given task"""
346 stamp = data.getVar('STAMP', d)
349 stamp = "%s.%s" % (data.expand(stamp, d), task)
350 mkdirhier(os.path.dirname(stamp))
354 def add_task(task, deps, d):
355 task_graph = data.getVar('_task_graph', d)
357 task_graph = bb.digraph()
358 data.setVarFlag(task, 'task', 1, d)
359 task_graph.addnode(task, None)
361 if not task_graph.hasnode(dep):
362 task_graph.addnode(dep, None)
363 task_graph.addnode(task, dep)
364 # don't assume holding a reference
365 data.setVar('_task_graph', task_graph, d)
368 def remove_task(task, kill, d):
369 """Remove an BB 'task'.
371 If kill is 1, also remove tasks that depend on this task."""
373 task_graph = data.getVar('_task_graph', d)
375 task_graph = bb.digraph()
376 if not task_graph.hasnode(task):
379 data.delVarFlag(task, 'task', d)
383 task_graph.delnode(task, ref)
384 data.setVar('_task_graph', task_graph, d)
386 def task_exists(task, d):
387 task_graph = data.getVar('_task_graph', d)
389 task_graph = bb.digraph()
390 data.setVar('_task_graph', task_graph, d)
391 return task_graph.hasnode(task)