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)
77 flags = data.getVarFlags(func, d)
78 for item in ['deps', 'check', 'interactive', 'python', 'cleandirs', 'dirs', 'lockfiles', 'fakeroot']:
82 ispython = flags['python']
84 cleandirs = (data.expand(flags['cleandirs'], d) or "").split()
85 for cdir in cleandirs:
86 os.system("rm -rf %s" % cdir)
89 dirs = data.expand(dirs, d)
91 dirs = (data.expand(flags['dirs'], d) or "").split()
98 adir = data.getVar('B', d, 1)
101 prevdir = os.getcwd()
103 prevdir = data.getVar('TOPDIR', d, True)
104 if adir and os.access(adir, os.F_OK):
108 lockfiles = (data.expand(flags['lockfiles'], d) or "").split()
109 for lock in lockfiles:
110 locks.append(bb.utils.lockfile(lock))
113 exec_func_python(func, d)
115 exec_func_shell(func, d, flags)
118 bb.utils.unlockfile(lock)
120 if os.path.exists(prevdir):
123 def exec_func_python(func, d):
124 """Execute a python BB 'function'"""
127 bbfile = bb.data.getVar('FILE', d, 1)
128 tmp = "def " + func + "():\n%s" % data.getVar(func, d)
129 tmp += '\n' + func + '()'
130 comp = utils.better_compile(tmp, func, bbfile)
131 prevdir = os.getcwd()
136 utils.better_exec(comp, g, tmp, bbfile)
137 if os.path.exists(prevdir):
140 def exec_func_shell(func, d, flags):
141 """Execute a shell BB 'function' Returns true if execution was successful.
143 For this, it creates a bash shell script in the tmp dectory, writes the local
144 data into it and finally executes. The output of the shell will end in a log file and stdout.
146 Note on directory behavior. The 'dirs' varflag should contain a list
147 of the directories you need created prior to execution. The last
148 item in the list is where we will chdir/cd to.
153 check = flags['check']
154 interact = flags['interactive']
155 if check in globals():
156 if globals()[check](func, deps):
160 t = data.getVar('T', d, 1)
164 logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
165 runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
167 f = open(runfile, "w")
168 f.write("#!/bin/sh -e\n")
169 if bb.msg.debug_level['default'] > 0: f.write("set -x\n")
172 f.write("cd %s\n" % os.getcwd())
173 if func: f.write("%s\n" % func)
175 os.chmod(runfile, 0775)
177 bb.msg.error(bb.msg.domain.Build, "Function not specified")
181 si = file('/dev/null', 'r')
183 if bb.msg.debug_level['default'] > 0:
184 so = os.popen("tee \"%s\"" % logfile, "w")
186 so = file(logfile, 'w')
188 bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
194 # dup the existing fds so we dont lose them
195 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
196 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
197 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
199 # replace those fds with our own
200 os.dup2(si.fileno(), osi[1])
201 os.dup2(so.fileno(), oso[1])
202 os.dup2(se.fileno(), ose[1])
205 prevdir = os.getcwd()
206 if flags['fakeroot']:
207 maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
210 lang_environment = "LC_ALL=C "
211 ret = os.system('%s%ssh -e %s' % (lang_environment, maybe_fakeroot, runfile))
218 # restore the backups
219 os.dup2(osi[0], osi[1])
220 os.dup2(oso[0], oso[1])
221 os.dup2(ose[0], ose[1])
228 # close the backup fds
234 if bb.msg.debug_level['default'] > 0:
239 bb.msg.error(bb.msg.domain.Build, "function %s failed" % func)
240 if data.getVar("BBINCLUDELOGS", d):
241 bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
242 number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
244 os.system('tail -n%s %s' % (number_of_lines, logfile))
246 f = open(logfile, "r")
255 bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
256 raise FuncFailed( logfile )
259 def exec_task(task, d):
260 """Execute an BB 'task'
262 The primary difference between executing a task versus executing
263 a function is that a task exists in the task digraph, and therefore
264 has dependencies amongst other tasks."""
266 # Check whther this is a valid task
267 if not data.getVarFlag(task, 'task', d):
268 raise EventException("No such task", InvalidTask(task, d))
271 bb.msg.debug(1, bb.msg.domain.Build, "Executing task %s" % task)
272 old_overrides = data.getVar('OVERRIDES', d, 0)
273 localdata = data.createCopy(d)
274 data.setVar('OVERRIDES', 'task-%s:%s' % (task[3:], old_overrides), localdata)
275 data.update_data(localdata)
276 data.expandKeys(localdata)
277 event.fire(TaskStarted(task, localdata))
278 exec_func(task, localdata)
279 event.fire(TaskSucceeded(task, localdata))
280 except FuncFailed, reason:
281 bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % reason )
282 failedevent = TaskFailed(task, d)
283 event.fire(failedevent)
284 raise EventException("Function failed in task: %s" % reason, failedevent)
286 # make stamp, or cause event and raise exception
287 if not data.getVarFlag(task, 'nostamp', d) and not data.getVarFlag(task, 'selfstamp', d):
290 def extract_stamp(d, fn):
292 Extracts stamp format which is either a data dictonary (fn unset)
293 or a dataCache entry (fn set).
297 return data.getVar('STAMP', d, 1)
299 def stamp_internal(task, d, file_name):
301 Internal stamp helper function
302 Removes any stamp for the given task
303 Makes sure the stamp directory exists
304 Returns the stamp path+filename
306 stamp = extract_stamp(d, file_name)
309 stamp = "%s.%s" % (stamp, task)
310 mkdirhier(os.path.dirname(stamp))
311 # Remove the file and recreate to force timestamp
312 # change on broken NFS filesystems
313 if os.access(stamp, os.F_OK):
317 def make_stamp(task, d, file_name = None):
319 Creates/updates a stamp for a given task
320 (d can be a data dict or dataCache)
322 stamp = stamp_internal(task, d, file_name)
327 def del_stamp(task, d, file_name = None):
329 Removes a stamp for a given task
330 (d can be a data dict or dataCache)
332 stamp_internal(task, d, file_name)
334 def add_tasks(tasklist, d):
335 task_deps = data.getVar('_task_deps', d)
338 if not 'tasks' in task_deps:
339 task_deps['tasks'] = []
340 if not 'parents' in task_deps:
341 task_deps['parents'] = {}
343 for task in tasklist:
344 task = data.expand(task, d)
345 data.setVarFlag(task, 'task', 1, d)
347 if not task in task_deps['tasks']:
348 task_deps['tasks'].append(task)
350 flags = data.getVarFlags(task, d)
352 if not name in task_deps:
355 deptask = data.expand(flags[name], d)
356 task_deps[name][task] = deptask
360 getTask('recrdeptask')
362 task_deps['parents'][task] = []
363 for dep in flags['deps']:
364 dep = data.expand(dep, d)
365 task_deps['parents'][task].append(dep)
367 # don't assume holding a reference
368 data.setVar('_task_deps', task_deps, 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 data.delVarFlag(task, 'task', d)