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):
53 def setEvent(self, event):
56 event = property(getEvent, setEvent, None, "event property")
59 class TaskBase(event.Event):
60 """Base class for task events"""
62 def __init__(self, t, d = {}):
64 event.Event.__init__(self, d)
69 def setTask(self, task):
72 task = property(getTask, setTask, None, "task property")
74 class TaskStarted(TaskBase):
75 """Task execution started"""
77 class TaskSucceeded(TaskBase):
78 """Task execution completed"""
80 class TaskFailed(TaskBase):
81 """Task execution failed"""
83 class InvalidTask(TaskBase):
89 global _task_data, _task_graph, _task_stack
90 _task_data = data.init()
91 _task_graph = bb.digraph()
95 def exec_func(func, d, dirs = None):
96 """Execute an BB 'function'"""
98 body = data.getVar(func, d)
103 dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
105 adir = data.expand(adir, d)
111 adir = data.getVar('B', d, 1)
113 adir = data.expand(adir, d)
116 prevdir = os.getcwd()
118 prevdir = data.expand('${TOPDIR}', d)
119 if adir and os.access(adir, os.F_OK):
122 if data.getVarFlag(func, "python", d):
123 exec_func_python(func, d)
125 exec_func_shell(func, d)
128 def exec_func_python(func, d):
129 """Execute a python BB 'function'"""
132 tmp = "def " + func + "():\n%s" % data.getVar(func, d)
133 comp = compile(tmp + '\n' + func + '()', bb.data.getVar('FILE', d, 1) + ':' + func, "exec")
134 prevdir = os.getcwd()
140 if os.path.exists(prevdir):
143 def exec_func_shell(func, d):
144 """Execute a shell BB 'function' Returns true if execution was successful.
146 For this, it creates a bash shell script in the tmp dectory, writes the local
147 data into it and finally executes. The output of the shell will end in a log file and stdout.
149 Note on directory behavior. The 'dirs' varflag should contain a list
150 of the directories you need created prior to execution. The last
151 item in the list is where we will chdir/cd to.
155 deps = data.getVarFlag(func, 'deps', d)
156 check = data.getVarFlag(func, 'check', d)
157 if check in globals():
158 if globals()[check](func, deps):
162 t = data.getVar('T', d, 1)
166 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
167 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
169 f = open(runfile, "w")
170 f.write("#!/bin/sh -e\n")
171 if bb.debug_level > 0: f.write("set -x\n")
174 f.write("cd %s\n" % os.getcwd())
175 if func: f.write("%s\n" % func)
177 os.chmod(runfile, 0775)
179 error("Function not specified")
183 si = file('/dev/null', 'r')
185 if bb.debug_level > 0:
186 so = os.popen("tee \"%s\"" % logfile, "w")
188 so = file(logfile, 'w')
190 bb.error("opening log file: %s" % e)
195 # dup the existing fds so we dont lose them
196 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
197 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
198 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
200 # replace those fds with our own
201 os.dup2(si.fileno(), osi[1])
202 os.dup2(so.fileno(), oso[1])
203 os.dup2(se.fileno(), ose[1])
206 prevdir = os.getcwd()
207 if data.getVarFlag(func, "fakeroot", d):
208 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
211 ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
214 # restore the backups
215 os.dup2(osi[0], osi[1])
216 os.dup2(oso[0], oso[1])
217 os.dup2(ose[0], ose[1])
224 # close the backup fds
230 if bb.debug_level > 0:
235 error("function %s failed" % func)
236 if data.getVar("BBINCLUDELOGS", d):
237 error("log data follows (%s)" % logfile)
238 f = open(logfile, "r")
247 error("see log in %s" % logfile)
253 def exec_task(task, d):
254 """Execute an BB 'task'
256 The primary difference between executing a task versus executing
257 a function is that a task exists in the task digraph, and therefore
258 has dependencies amongst other tasks."""
260 # check if the task is in the graph..
261 task_graph = data.getVar('_task_graph', d)
263 task_graph = bb.digraph()
264 data.setVar('_task_graph', task_graph, d)
265 task_cache = data.getVar('_task_cache', d)
268 data.setVar('_task_cache', task_cache, d)
269 if not task_graph.hasnode(task):
270 raise EventException("", InvalidTask(task, d))
272 # check whether this task needs executing..
273 if not data.getVarFlag(task, 'force', d):
274 if stamp_is_current(task, d):
277 # follow digraph path up, then execute our way back down
278 def execute(graph, item):
279 if data.getVarFlag(item, 'task', d):
280 if item in task_cache:
284 # deeper than toplevel, exec w/ deps
289 debug(1, "Executing task %s" % item)
290 old_overrides = data.getVar('OVERRIDES', d, 0)
291 from copy import deepcopy
292 localdata = deepcopy(d)
293 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
294 data.update_data(localdata)
295 event.fire(TaskStarted(item, localdata))
296 exec_func(item, localdata)
297 event.fire(TaskSucceeded(item, localdata))
298 task_cache.append(item)
299 except FuncFailed, reason:
300 note( "Task failed: %s" % reason )
301 failedevent = TaskFailed(item, d)
302 event.fire(failedevent)
303 raise EventException(None, failedevent)
306 task_graph.walkdown(task, execute)
308 # make stamp, or cause event and raise exception
309 if not data.getVarFlag(task, 'nostamp', d):
313 def stamp_is_current(task, d, checkdeps = 1):
314 """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
315 task_graph = data.getVar('_task_graph', d)
317 task_graph = bb.digraph()
318 data.setVar('_task_graph', task_graph, d)
319 stamp = data.getVar('STAMP', d)
322 stampfile = "%s.%s" % (data.expand(stamp, d), task)
323 if not os.access(stampfile, os.F_OK):
330 tasktime = os.stat(stampfile)[stat.ST_MTIME]
333 def checkStamp(graph, task):
334 # check for existance
335 if data.getVarFlag(task, 'nostamp', d):
338 if not stamp_is_current(task, d, 0):
341 depfile = "%s.%s" % (data.expand(stamp, d), task)
342 deptime = os.stat(depfile)[stat.ST_MTIME]
343 if deptime > tasktime:
347 return task_graph.walkdown(task, checkStamp)
350 def md5_is_current(task):
351 """Check if a md5 file for a given task is current"""
354 def mkstamp(task, d):
355 """Creates/updates a stamp for a given task"""
356 stamp = data.getVar('STAMP', d)
359 stamp = "%s.%s" % (data.expand(stamp, d), task)
360 mkdirhier(os.path.dirname(stamp))
364 def add_task(task, deps, d):
365 task_graph = data.getVar('_task_graph', d)
367 task_graph = bb.digraph()
368 data.setVar('_task_graph', task_graph, d)
369 data.setVarFlag(task, 'task', 1, d)
370 task_graph.addnode(task, None)
372 if not task_graph.hasnode(dep):
373 task_graph.addnode(dep, None)
374 task_graph.addnode(task, dep)
377 def remove_task(task, kill, d):
378 """Remove an BB 'task'.
380 If kill is 1, also remove tasks that depend on this task."""
382 task_graph = data.getVar('_task_graph', d)
384 task_graph = bb.digraph()
385 data.setVar('_task_graph', task_graph, d)
386 if not task_graph.hasnode(task):
389 data.delVarFlag(task, 'task', d)
393 task_graph.delnode(task, ref)
395 def task_exists(task, d):
396 task_graph = data.getVar('_task_graph', d)
398 task_graph = bb.digraph()
399 data.setVar('_task_graph', task_graph, d)
400 return task_graph.hasnode(task)