- set magic args attribute for event exception
[vuplus_bitbake] / lib / bb / build.py
1 #!/usr/bin/env python
2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 """
5 BitBake 'Build' implementation
6
7 Core code for function execution and task handling in the
8 BitBake build tools.
9
10 Copyright (C) 2003, 2004  Chris Larson
11
12 Based on Gentoo's portage.py.
13
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
17 version.
18
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.
22
23 You should have received a copy of the GNU General Public License along with
24
25 Based on functions from the base bb module, Copyright 2003 Holger Schurig
26 """
27
28 from bb import debug, data, fetch, fatal, error, note, event, mkdirhier
29 import bb, os
30
31 # data holds flags and function name for a given task
32 _task_data = data.init()
33
34 # graph represents task interdependencies
35 _task_graph = bb.digraph()
36
37 # stack represents execution order, excepting dependencies
38 _task_stack = []
39
40 # events
41 class FuncFailed(Exception):
42     """Executed function failed"""
43
44 class EventException(Exception):
45     """Exception which is associated with an Event."""
46
47     def __init__(self, msg, event):
48         self.args = msg, event
49
50 class TaskBase(event.Event):
51     """Base class for task events"""
52
53     def __init__(self, t, d ):
54         self._task = t
55         event.Event.__init__(self, d)
56
57     def getTask(self):
58         return self._task
59
60     def setTask(self, task):
61         self._task = task
62
63     task = property(getTask, setTask, None, "task property")
64
65 class TaskStarted(TaskBase):
66     """Task execution started"""
67
68 class TaskSucceeded(TaskBase):
69     """Task execution completed"""
70
71 class TaskFailed(TaskBase):
72     """Task execution failed"""
73
74 class InvalidTask(TaskBase):
75     """Invalid Task"""
76
77 # functions
78
79 def init(data):
80     global _task_data, _task_graph, _task_stack
81     _task_data = data.init()
82     _task_graph = bb.digraph()
83     _task_stack = []
84
85
86 def exec_func(func, d, dirs = None):
87     """Execute an BB 'function'"""
88
89     body = data.getVar(func, d)
90     if not body:
91         return
92
93     if not dirs:
94         dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
95     for adir in dirs:
96         adir = data.expand(adir, d)
97         mkdirhier(adir)
98
99     if len(dirs) > 0:
100         adir = dirs[-1]
101     else:
102         adir = data.getVar('B', d, 1)
103
104     adir = data.expand(adir, d)
105
106     try:
107         prevdir = os.getcwd()
108     except OSError:
109         prevdir = data.expand('${TOPDIR}', d)
110     if adir and os.access(adir, os.F_OK):
111         os.chdir(adir)
112
113     if data.getVarFlag(func, "python", d):
114         exec_func_python(func, d)
115     else:
116         exec_func_shell(func, d)
117     os.chdir(prevdir)
118
119 def exec_func_python(func, d):
120     """Execute a python BB 'function'"""
121     import re, os
122
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()
126     g = {} # globals
127     g['bb'] = bb
128     g['os'] = os
129     g['d'] = d
130     exec comp in g
131     if os.path.exists(prevdir):
132         os.chdir(prevdir)
133
134 def exec_func_shell(func, d):
135     """Execute a shell BB 'function' Returns true if execution was successful.
136
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.
139
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.
143     """
144     import sys
145
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):
150             return
151
152     global logfile
153     t = data.getVar('T', d, 1)
154     if not t:
155         return 0
156     mkdirhier(t)
157     logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
158     runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
159
160     f = open(runfile, "w")
161     f.write("#!/bin/sh -e\n")
162     if bb.debug_level > 0: f.write("set -x\n")
163     data.emit_env(f, d)
164
165     f.write("cd %s\n" % os.getcwd())
166     if func: f.write("%s\n" % func)
167     f.close()
168     os.chmod(runfile, 0775)
169     if not func:
170         error("Function not specified")
171         raise FuncFailed()
172
173     # open logs
174     si = file('/dev/null', 'r')
175     try:
176         if bb.debug_level > 0:
177             so = os.popen("tee \"%s\"" % logfile, "w")
178         else:
179             so = file(logfile, 'w')
180     except OSError, e:
181         bb.error("opening log file: %s" % e)
182         pass
183
184     se = so
185
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()]
190
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])
195
196     # execute function
197     prevdir = os.getcwd()
198     if data.getVarFlag(func, "fakeroot", d):
199         maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
200     else:
201         maybe_fakeroot = ''
202     ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
203     os.chdir(prevdir)
204
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])
209
210     # close our logs
211     si.close()
212     so.close()
213     se.close()
214
215     # close the backup fds
216     os.close(osi[0])
217     os.close(oso[0])
218     os.close(ose[0])
219
220     if ret==0:
221         if bb.debug_level > 0:
222             os.remove(runfile)
223 #            os.remove(logfile)
224         return
225     else:
226         error("function %s failed" % func)
227         if data.getVar("BBINCLUDELOGS", d):
228             error("log data follows (%s)" % logfile)
229             f = open(logfile, "r")
230             while True:
231                 l = f.readline()
232                 if l == '':
233                     break
234                 l = l.rstrip()
235                 print '| %s' % l
236             f.close()
237         else:
238             error("see log in %s" % logfile)
239         raise FuncFailed( logfile )
240
241
242 _task_cache = []
243
244 def exec_task(task, d):
245     """Execute an BB 'task'
246
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."""
250
251     # check if the task is in the graph..
252     task_graph = data.getVar('_task_graph', d)
253     if not task_graph:
254         task_graph = bb.digraph()
255         data.setVar('_task_graph', task_graph, d)
256     task_cache = data.getVar('_task_cache', d)
257     if not task_cache:
258         task_cache = []
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))
262
263     # check whether this task needs executing..
264     if not data.getVarFlag(task, 'force', d):
265         if stamp_is_current(task, d):
266             return 1
267
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:
272                 return 1
273
274             if task != item:
275                 # deeper than toplevel, exec w/ deps
276                 exec_task(item, d)
277                 return 1
278
279             try:
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)
294
295     # execute
296     task_graph.walkdown(task, execute)
297
298     # make stamp, or cause event and raise exception
299     if not data.getVarFlag(task, 'nostamp', d):
300         mkstamp(task, d)
301
302
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)
306     if not task_graph:
307         task_graph = bb.digraph()
308         data.setVar('_task_graph', task_graph, d)
309     stamp = data.getVar('STAMP', d)
310     if not stamp:
311         return 0
312     stampfile = "%s.%s" % (data.expand(stamp, d), task)
313     if not os.access(stampfile, os.F_OK):
314         return 0
315
316     if checkdeps == 0:
317         return 1
318
319     import stat
320     tasktime = os.stat(stampfile)[stat.ST_MTIME]
321
322     _deps = []
323     def checkStamp(graph, task):
324         # check for existance
325         if data.getVarFlag(task, 'nostamp', d):
326             return 1
327
328         if not stamp_is_current(task, d, 0):
329             return 0
330
331         depfile = "%s.%s" % (data.expand(stamp, d), task)
332         deptime = os.stat(depfile)[stat.ST_MTIME]
333         if deptime > tasktime:
334             return 0
335         return 1
336
337     return task_graph.walkdown(task, checkStamp)
338
339
340 def md5_is_current(task):
341     """Check if a md5 file for a given task is current"""
342
343
344 def mkstamp(task, d):
345     """Creates/updates a stamp for a given task"""
346     stamp = data.getVar('STAMP', d)
347     if not stamp:
348         return
349     stamp = "%s.%s" % (data.expand(stamp, d), task)
350     mkdirhier(os.path.dirname(stamp))
351     open(stamp, "w+")
352
353
354 def add_task(task, deps, d):
355     task_graph = data.getVar('_task_graph', d)
356     if not task_graph:
357         task_graph = bb.digraph()
358     data.setVarFlag(task, 'task', 1, d)
359     task_graph.addnode(task, None)
360     for dep in deps:
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)
366
367
368 def remove_task(task, kill, d):
369     """Remove an BB 'task'.
370
371        If kill is 1, also remove tasks that depend on this task."""
372
373     task_graph = data.getVar('_task_graph', d)
374     if not task_graph:
375         task_graph = bb.digraph()
376     if not task_graph.hasnode(task):
377         return
378
379     data.delVarFlag(task, 'task', d)
380     ref = 1
381     if kill == 1:
382         ref = 2
383     task_graph.delnode(task, ref)
384     data.setVar('_task_graph', task_graph, d)
385
386 def task_exists(task, d):
387     task_graph = data.getVar('_task_graph', d)
388     if not task_graph:
389         task_graph = bb.digraph()
390         data.setVar('_task_graph', task_graph, d)
391     return task_graph.hasnode(task)
392
393 def get_task_data():
394     return _task_data