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