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 = {}):
69 def setTask(self, task):
72 task = property(getTask, setTask, None, "task property")
77 def setData(self, data):
80 data = property(getData, setData, None, "data property")
82 class TaskStarted(TaskBase):
83 """Task execution started"""
85 class TaskSucceeded(TaskBase):
86 """Task execution completed"""
88 class TaskFailed(TaskBase):
89 """Task execution failed"""
91 class InvalidTask(TaskBase):
97 global _task_data, _task_graph, _task_stack
98 _task_data = data.init()
99 _task_graph = bb.digraph()
103 def exec_func(func, d, dirs = None):
104 """Execute an BB 'function'"""
106 body = data.getVar(func, d)
111 dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
113 adir = data.expand(adir, d)
119 adir = data.getVar('B', d, 1)
121 adir = data.expand(adir, d)
124 prevdir = os.getcwd()
126 prevdir = data.expand('${TOPDIR}', d)
127 if adir and os.access(adir, os.F_OK):
130 if data.getVarFlag(func, "python", d):
131 exec_func_python(func, d)
133 exec_func_shell(func, d)
136 def exec_func_python(func, d):
137 """Execute a python BB 'function'"""
140 tmp = "def " + func + "():\n%s" % data.getVar(func, d)
141 comp = compile(tmp + '\n' + func + '()', bb.data.getVar('FILE', d, 1) + ':' + func, "exec")
142 prevdir = os.getcwd()
148 if os.path.exists(prevdir):
151 def exec_func_shell(func, d):
152 """Execute a shell BB 'function' Returns true if execution was successful.
154 For this, it creates a bash shell script in the tmp dectory, writes the local
155 data into it and finally executes. The output of the shell will end in a log file and stdout.
157 Note on directory behavior. The 'dirs' varflag should contain a list
158 of the directories you need created prior to execution. The last
159 item in the list is where we will chdir/cd to.
163 deps = data.getVarFlag(func, 'deps', d)
164 check = data.getVarFlag(func, 'check', d)
165 if check in globals():
166 if globals()[check](func, deps):
170 t = data.getVar('T', d, 1)
174 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
175 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
177 f = open(runfile, "w")
178 f.write("#!/bin/sh -e\n")
179 if bb.debug_level > 0: f.write("set -x\n")
182 f.write("cd %s\n" % os.getcwd())
183 if func: f.write("%s\n" % func)
185 os.chmod(runfile, 0775)
187 error("Function not specified")
191 si = file('/dev/null', 'r')
193 if bb.debug_level > 0:
194 so = os.popen("tee \"%s\"" % logfile, "w")
196 so = file(logfile, 'w')
198 bb.error("opening log file: %s" % e)
203 # dup the existing fds so we dont lose them
204 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
205 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
206 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
208 # replace those fds with our own
209 os.dup2(si.fileno(), osi[1])
210 os.dup2(so.fileno(), oso[1])
211 os.dup2(se.fileno(), ose[1])
214 prevdir = os.getcwd()
215 if data.getVarFlag(func, "fakeroot", d):
216 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
219 ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
222 # restore the backups
223 os.dup2(osi[0], osi[1])
224 os.dup2(oso[0], oso[1])
225 os.dup2(ose[0], ose[1])
232 # close the backup fds
238 if bb.debug_level > 0:
243 error("function %s failed" % func)
244 if data.getVar("BBINCLUDELOGS", d):
245 error("log data follows (%s)" % logfile)
246 f = open(logfile, "r")
255 error("see log in %s" % logfile)
261 def exec_task(task, d):
262 """Execute an BB 'task'
264 The primary difference between executing a task versus executing
265 a function is that a task exists in the task digraph, and therefore
266 has dependencies amongst other tasks."""
268 # check if the task is in the graph..
269 task_graph = data.getVar('_task_graph', d)
271 task_graph = bb.digraph()
272 data.setVar('_task_graph', task_graph, d)
273 task_cache = data.getVar('_task_cache', d)
276 data.setVar('_task_cache', task_cache, d)
277 if not task_graph.hasnode(task):
278 raise EventException("", InvalidTask(task, d))
280 # check whether this task needs executing..
281 if not data.getVarFlag(task, 'force', d):
282 if stamp_is_current(task, d):
285 # follow digraph path up, then execute our way back down
286 def execute(graph, item):
287 if data.getVarFlag(item, 'task', d):
288 if item in task_cache:
292 # deeper than toplevel, exec w/ deps
297 debug(1, "Executing task %s" % item)
298 old_overrides = data.getVar('OVERRIDES', d, 0)
299 from copy import deepcopy
300 localdata = deepcopy(d)
301 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
302 data.update_data(localdata)
303 event.fire(TaskStarted(item, localdata))
304 exec_func(item, localdata)
305 event.fire(TaskSucceeded(item, localdata))
306 task_cache.append(item)
307 except FuncFailed, reason:
308 note( "Task failed: %s" % reason )
309 failedevent = TaskFailed(item, d)
310 event.fire(failedevent)
311 raise EventException(None, failedevent)
314 task_graph.walkdown(task, execute)
316 # make stamp, or cause event and raise exception
317 if not data.getVarFlag(task, 'nostamp', d):
321 def stamp_is_current(task, d, checkdeps = 1):
322 """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
323 task_graph = data.getVar('_task_graph', d)
325 task_graph = bb.digraph()
326 data.setVar('_task_graph', task_graph, d)
327 stamp = data.getVar('STAMP', d)
330 stampfile = "%s.%s" % (data.expand(stamp, d), task)
331 if not os.access(stampfile, os.F_OK):
338 tasktime = os.stat(stampfile)[stat.ST_MTIME]
341 def checkStamp(graph, task):
342 # check for existance
343 if data.getVarFlag(task, 'nostamp', d):
346 if not stamp_is_current(task, d, 0):
349 depfile = "%s.%s" % (data.expand(stamp, d), task)
350 deptime = os.stat(depfile)[stat.ST_MTIME]
351 if deptime > tasktime:
355 return task_graph.walkdown(task, checkStamp)
358 def md5_is_current(task):
359 """Check if a md5 file for a given task is current"""
362 def mkstamp(task, d):
363 """Creates/updates a stamp for a given task"""
364 stamp = data.getVar('STAMP', d)
367 stamp = "%s.%s" % (data.expand(stamp, d), task)
368 mkdirhier(os.path.dirname(stamp))
372 def add_task(task, deps, d):
373 task_graph = data.getVar('_task_graph', d)
375 task_graph = bb.digraph()
376 data.setVar('_task_graph', task_graph, d)
377 data.setVarFlag(task, 'task', 1, d)
378 task_graph.addnode(task, None)
380 if not task_graph.hasnode(dep):
381 task_graph.addnode(dep, None)
382 task_graph.addnode(task, dep)
385 def remove_task(task, kill, d):
386 """Remove an BB 'task'.
388 If kill is 1, also remove tasks that depend on this task."""
390 task_graph = data.getVar('_task_graph', d)
392 task_graph = bb.digraph()
393 data.setVar('_task_graph', task_graph, d)
394 if not task_graph.hasnode(task):
397 data.delVarFlag(task, 'task', d)
401 task_graph.delnode(task, ref)
403 def task_exists(task, d):
404 task_graph = data.getVar('_task_graph', d)
406 task_graph = bb.digraph()
407 data.setVar('_task_graph', task_graph, d)
408 return task_graph.hasnode(task)