Show filename of log when displaying it, on failure.
[bitbake.git] / bin / oe / 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 OpenEmbedded 'Build' implementation
6
7 Core code for function execution and task handling in the
8 OpenEmbedded (http://openembedded.org) build infrastructure.
9
10 Copyright: (c) 2003 Chris Larson
11
12 Based on functions from the base oe module, Copyright 2003 Holger Schurig
13 """
14
15 from oe import debug, data, fetch, fatal, error, note, event, mkdirhier
16 import oe, os
17
18 # data holds flags and function name for a given task
19 _task_data = data.init()
20
21 # graph represents task interdependencies
22 _task_graph = oe.digraph()
23
24 # stack represents execution order, excepting dependencies
25 _task_stack = []
26
27 # events
28 class FuncFailed(Exception):
29     """Executed function failed"""
30
31 class EventException(Exception):
32     """Exception which is associated with an Event."""
33
34     def __init__(self, msg, event):
35         self.event = event
36
37     def getEvent(self):
38         return self._event
39
40     def setEvent(self, event):
41         self._event = event
42
43     event = property(getEvent, setEvent, None, "event property")
44
45 class TaskBase(event.Event):
46     """Base class for task events"""
47
48     def __init__(self, t, d = {}):
49         self.task = t
50         self.data = d
51
52     def getTask(self):
53         return self._task
54
55     def setTask(self, task):
56         self._task = task
57
58     task = property(getTask, setTask, None, "task property")
59
60     def getData(self):
61         return self._data
62
63     def setData(self, data):
64         self._data = data
65
66     data = property(getData, setData, None, "data property")
67
68 class TaskStarted(TaskBase):
69     """Task execution started"""
70
71 class TaskSucceeded(TaskBase):
72     """Task execution completed"""
73
74 class TaskFailed(TaskBase):
75     """Task execution failed"""
76
77 class InvalidTask(TaskBase):
78     """Invalid Task"""
79
80 # functions
81
82 def init(data):
83     global _task_data, _task_graph, _task_stack
84     _task_data = data.init()
85     _task_graph = oe.digraph()
86     _task_stack = []
87
88
89 def exec_func(func, d, dirs = None):
90     """Execute an OE 'function'"""
91
92     body = data.getVar(func, d)
93     if not body:
94         return
95
96     if not dirs:
97         dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
98     for adir in dirs:
99         adir = data.expand(adir, d)
100         mkdirhier(adir)
101
102     if len(dirs) > 0:
103         adir = dirs[-1]
104     else:
105         adir = data.getVar('B', d, 1)
106
107     adir = data.expand(adir, d)
108
109     try:
110         prevdir = os.getcwd()
111     except OSError:
112         prevdir = data.expand('${TOPDIR}', d)
113     if adir and os.access(adir, os.F_OK):
114         os.chdir(adir)
115
116     if data.getVarFlag(func, "python", d):
117         exec_func_python(func, d)
118     else:
119         exec_func_shell(func, d)
120     os.chdir(prevdir)
121
122 def exec_func_python(func, d):
123     """Execute a python OE 'function'"""
124     import re, os
125
126     tmp = "def " + func + "():\n%s" % data.getVar(func, d)
127     comp = compile(tmp + '\n' + func + '()', oe.data.getVar('FILE', d, 1) + ':' + func, "exec")
128     prevdir = os.getcwd()
129     g = {} # globals
130     g['oe'] = oe
131     g['os'] = os
132     g['d'] = d
133     exec comp in g
134     if os.path.exists(prevdir):
135         os.chdir(prevdir)
136
137 def exec_func_shell(func, d):
138     """Execute a shell OE 'function' Returns true if execution was successful.
139
140     For this, it creates a bash shell script in the tmp dectory, writes the local
141     data into it and finally executes. The output of the shell will end in a log file and stdout.
142
143     Note on directory behavior.  The 'dirs' varflag should contain a list
144     of the directories you need created prior to execution.  The last
145     item in the list is where we will chdir/cd to.
146     """
147     import sys
148
149     deps = data.getVarFlag(func, 'deps', d)
150     check = data.getVarFlag(func, 'check', d)
151     if check in globals():
152         if globals()[check](func, deps):
153             return
154
155     global logfile
156     t = data.getVar('T', d, 1)
157     if not t:
158         return 0
159     mkdirhier(t)
160     logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
161     runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
162
163     f = open(runfile, "w")
164     f.write("#!/bin/sh -e\n")
165     if data.getVar("OEDEBUG", d): f.write("set -x\n")
166     data.emit_env(f, d)
167
168     f.write("cd %s\n" % os.getcwd())
169     if func: f.write("%s\n" % func)
170     f.close()
171     os.chmod(runfile, 0775)
172     if not func:
173         error("Function not specified")
174         raise FuncFailed()
175
176     # open logs
177     si = file('/dev/null', 'r')
178     try:
179         if data.getVar("OEDEBUG", d):
180             so = os.popen("tee \"%s\"" % logfile, "w")
181         else:
182             so = file(logfile, 'w')
183     except OSError, e:
184         oe.error("opening log file: %s" % e)
185         pass
186
187     se = so
188
189     # dup the existing fds so we dont lose them
190     osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
191     oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
192     ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
193
194     # replace those fds with our own
195     os.dup2(si.fileno(), osi[1])
196     os.dup2(so.fileno(), oso[1])
197     os.dup2(se.fileno(), ose[1])
198
199     # execute function
200     prevdir = os.getcwd()
201     if data.getVarFlag(func, "fakeroot", d):
202         maybe_fakeroot = oe.data.expand("${STAGING_BINDIR}/fakeroot ",d)
203     else:
204         maybe_fakeroot = ''
205     ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
206     os.chdir(prevdir)
207
208     # restore the backups
209     os.dup2(osi[0], osi[1])
210     os.dup2(oso[0], oso[1])
211     os.dup2(ose[0], ose[1])
212
213     # close our logs
214     si.close()
215     so.close()
216     se.close()
217
218     # close the backup fds
219     os.close(osi[0])
220     os.close(oso[0])
221     os.close(ose[0])
222
223     if ret==0:
224         if not data.getVar("OEDEBUG", d):
225             os.remove(runfile)
226 #            os.remove(logfile)
227         return
228     else:
229         error("function %s failed" % func)
230         if data.getVar("OEINCLUDELOGS", d):
231             error("log data follows (%s)" % logfile)
232             f = open(logfile, "r")
233             while True:
234                 l = f.readline()
235                 if l == '':
236                     break
237                 l = l.rstrip()
238                 print '| %s' % l
239             f.close()
240         else:
241             error("see log in %s" % logfile)
242         raise FuncFailed()
243
244
245 _task_cache = []
246
247 def exec_task(task, d):
248     """Execute an OE 'task'
249
250        The primary difference between executing a task versus executing
251        a function is that a task exists in the task digraph, and therefore
252        has dependencies amongst other tasks."""
253
254     # check if the task is in the graph..
255     task_graph = data.getVar('_task_graph', d)
256     if not task_graph:
257         task_graph = oe.digraph()
258         data.setVar('_task_graph', task_graph, d)
259     task_cache = data.getVar('_task_cache', d)
260     if not task_cache:
261         task_cache = []
262         data.setVar('_task_cache', task_cache, d)
263     if not task_graph.hasnode(task):
264         raise EventException("", InvalidTask(task, d))
265
266     # check whether this task needs executing..
267     if not data.getVarFlag(task, 'force', d):
268         if stamp_is_current(task, d):
269             return 1
270
271     # follow digraph path up, then execute our way back down
272     def execute(graph, item):
273         if data.getVarFlag(item, 'task', d):
274             if item in task_cache:
275                 return 1
276
277             if task != item:
278                 # deeper than toplevel, exec w/ deps
279                 exec_task(item, d)
280                 return 1
281
282             try:
283                 debug(1, "Executing task %s" % item)
284                 event.fire(TaskStarted(item, d))
285                 exec_func(item, d)
286                 event.fire(TaskSucceeded(item, d))
287                 task_cache.append(item)
288             except FuncFailed, reason:
289                 note( "Task failed: %s" % reason )
290                 failedevent = TaskFailed(item, d)
291                 event.fire(failedevent)
292                 raise EventException(None, failedevent)
293
294     # execute
295     task_graph.walkdown(task, execute)
296
297     # make stamp, or cause event and raise exception
298     if not data.getVarFlag(task, 'nostamp', d):
299         mkstamp(task, d)
300
301
302 def stamp_is_current(task, d, checkdeps = 1):
303     """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
304     task_graph = data.getVar('_task_graph', d)
305     if not task_graph:
306         task_graph = oe.digraph()
307         data.setVar('_task_graph', task_graph, d)
308     stamp = data.getVar('STAMP', d)
309     if not stamp:
310         return 0
311     stampfile = "%s.%s" % (data.expand(stamp, d), task)
312     if not os.access(stampfile, os.F_OK):
313         return 0
314
315     if checkdeps == 0:
316         return 1
317
318     import stat
319     tasktime = os.stat(stampfile)[stat.ST_MTIME]
320
321     _deps = []
322     def checkStamp(graph, task):
323         # check for existance
324         if data.getVarFlag(task, 'nostamp', d):
325             return 1
326
327         if not stamp_is_current(task, d, 0):
328             return 0
329
330         depfile = "%s.%s" % (data.expand(stamp, d), task)
331         deptime = os.stat(depfile)[stat.ST_MTIME]
332         if deptime > tasktime:
333             return 0
334         return 1
335
336     return task_graph.walkdown(task, checkStamp)
337
338
339 def md5_is_current(task):
340     """Check if a md5 file for a given task is current"""
341
342
343 def mkstamp(task, d):
344     """Creates/updates a stamp for a given task"""
345     mkdirhier(data.expand('${TMPDIR}/stamps', d));
346     stamp = data.getVar('STAMP', d)
347     if not stamp:
348         return
349     stamp = "%s.%s" % (data.expand(stamp, d), task)
350     open(stamp, "w+")
351
352
353 def add_task(task, deps, d):
354     task_graph = data.getVar('_task_graph', d)
355     if not task_graph:
356         task_graph = oe.digraph()
357         data.setVar('_task_graph', task_graph, d)
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
365
366 def remove_task(task, kill, d):
367     """Remove an OE 'task'.
368
369        If kill is 1, also remove tasks that depend on this task."""
370
371     task_graph = data.getVar('_task_graph', d)
372     if not task_graph:
373         task_graph = oe.digraph()
374         data.setVar('_task_graph', task_graph, d)
375     if not task_graph.hasnode(task):
376         return
377
378     data.delVarFlag(task, 'task', d)
379     ref = 1
380     if kill == 1:
381         ref = 2
382     task_graph.delnode(task, ref)
383
384 def task_exists(task, d):
385     task_graph = data.getVar('_task_graph', d)
386     if not task_graph:
387         task_graph = oe.digraph()
388         data.setVar('_task_graph', task_graph, d)
389     return task_graph.hasnode(task)
390
391 def get_task_data():
392     return _task_data