As recommended by pb_ and RP, prepend the task name with 'task_' when mangling OVERRI...
[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
59 class TaskBase(event.Event):
60     """Base class for task events"""
61
62     def __init__(self, t, d = {}):
63         self.task = t
64         self.data = d
65
66     def getTask(self):
67         return self._task
68
69     def setTask(self, task):
70         self._task = task
71
72     task = property(getTask, setTask, None, "task property")
73
74     def getData(self):
75         return self._data
76
77     def setData(self, data):
78         self._data = data
79
80     data = property(getData, setData, None, "data property")
81
82 class TaskStarted(TaskBase):
83     """Task execution started"""
84
85 class TaskSucceeded(TaskBase):
86     """Task execution completed"""
87
88 class TaskFailed(TaskBase):
89     """Task execution failed"""
90
91 class InvalidTask(TaskBase):
92     """Invalid Task"""
93
94 # functions
95
96 def init(data):
97     global _task_data, _task_graph, _task_stack
98     _task_data = data.init()
99     _task_graph = bb.digraph()
100     _task_stack = []
101
102
103 def exec_func(func, d, dirs = None):
104     """Execute an BB 'function'"""
105
106     body = data.getVar(func, d)
107     if not body:
108         return
109
110     if not dirs:
111         dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
112     for adir in dirs:
113         adir = data.expand(adir, d)
114         mkdirhier(adir)
115
116     if len(dirs) > 0:
117         adir = dirs[-1]
118     else:
119         adir = data.getVar('B', d, 1)
120
121     adir = data.expand(adir, d)
122
123     try:
124         prevdir = os.getcwd()
125     except OSError:
126         prevdir = data.expand('${TOPDIR}', d)
127     if adir and os.access(adir, os.F_OK):
128         os.chdir(adir)
129
130     if data.getVarFlag(func, "python", d):
131         exec_func_python(func, d)
132     else:
133         exec_func_shell(func, d)
134     os.chdir(prevdir)
135
136 def exec_func_python(func, d):
137     """Execute a python BB 'function'"""
138     import re, os
139
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()
143     g = {} # globals
144     g['bb'] = bb
145     g['os'] = os
146     g['d'] = d
147     exec comp in g
148     if os.path.exists(prevdir):
149         os.chdir(prevdir)
150
151 def exec_func_shell(func, d):
152     """Execute a shell BB 'function' Returns true if execution was successful.
153
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.
156
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.
160     """
161     import sys
162
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):
167             return
168
169     global logfile
170     t = data.getVar('T', d, 1)
171     if not t:
172         return 0
173     mkdirhier(t)
174     logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
175     runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
176
177     f = open(runfile, "w")
178     f.write("#!/bin/sh -e\n")
179     if bb.debug_level > 0: f.write("set -x\n")
180     data.emit_env(f, d)
181
182     f.write("cd %s\n" % os.getcwd())
183     if func: f.write("%s\n" % func)
184     f.close()
185     os.chmod(runfile, 0775)
186     if not func:
187         error("Function not specified")
188         raise FuncFailed()
189
190     # open logs
191     si = file('/dev/null', 'r')
192     try:
193         if bb.debug_level > 0:
194             so = os.popen("tee \"%s\"" % logfile, "w")
195         else:
196             so = file(logfile, 'w')
197     except OSError, e:
198         bb.error("opening log file: %s" % e)
199         pass
200
201     se = so
202
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()]
207
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])
212
213     # execute function
214     prevdir = os.getcwd()
215     if data.getVarFlag(func, "fakeroot", d):
216         maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
217     else:
218         maybe_fakeroot = ''
219     ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
220     os.chdir(prevdir)
221
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])
226
227     # close our logs
228     si.close()
229     so.close()
230     se.close()
231
232     # close the backup fds
233     os.close(osi[0])
234     os.close(oso[0])
235     os.close(ose[0])
236
237     if ret==0:
238         if bb.debug_level > 0:
239             os.remove(runfile)
240 #            os.remove(logfile)
241         return
242     else:
243         error("function %s failed" % func)
244         if data.getVar("BBINCLUDELOGS", d):
245             error("log data follows (%s)" % logfile)
246             f = open(logfile, "r")
247             while True:
248                 l = f.readline()
249                 if l == '':
250                     break
251                 l = l.rstrip()
252                 print '| %s' % l
253             f.close()
254         else:
255             error("see log in %s" % logfile)
256         raise FuncFailed()
257
258
259 _task_cache = []
260
261 def exec_task(task, d):
262     """Execute an BB 'task'
263
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."""
267
268     # check if the task is in the graph..
269     task_graph = data.getVar('_task_graph', d)
270     if not task_graph:
271         task_graph = bb.digraph()
272         data.setVar('_task_graph', task_graph, d)
273     task_cache = data.getVar('_task_cache', d)
274     if not task_cache:
275         task_cache = []
276         data.setVar('_task_cache', task_cache, d)
277     if not task_graph.hasnode(task):
278         raise EventException("", InvalidTask(task, d))
279
280     # check whether this task needs executing..
281     if not data.getVarFlag(task, 'force', d):
282         if stamp_is_current(task, d):
283             return 1
284
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:
289                 return 1
290
291             if task != item:
292                 # deeper than toplevel, exec w/ deps
293                 exec_task(item, d)
294                 return 1
295
296             try:
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)
312
313     # execute
314     task_graph.walkdown(task, execute)
315
316     # make stamp, or cause event and raise exception
317     if not data.getVarFlag(task, 'nostamp', d):
318         mkstamp(task, d)
319
320
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)
324     if not task_graph:
325         task_graph = bb.digraph()
326         data.setVar('_task_graph', task_graph, d)
327     stamp = data.getVar('STAMP', d)
328     if not stamp:
329         return 0
330     stampfile = "%s.%s" % (data.expand(stamp, d), task)
331     if not os.access(stampfile, os.F_OK):
332         return 0
333
334     if checkdeps == 0:
335         return 1
336
337     import stat
338     tasktime = os.stat(stampfile)[stat.ST_MTIME]
339
340     _deps = []
341     def checkStamp(graph, task):
342         # check for existance
343         if data.getVarFlag(task, 'nostamp', d):
344             return 1
345
346         if not stamp_is_current(task, d, 0):
347             return 0
348
349         depfile = "%s.%s" % (data.expand(stamp, d), task)
350         deptime = os.stat(depfile)[stat.ST_MTIME]
351         if deptime > tasktime:
352             return 0
353         return 1
354
355     return task_graph.walkdown(task, checkStamp)
356
357
358 def md5_is_current(task):
359     """Check if a md5 file for a given task is current"""
360
361
362 def mkstamp(task, d):
363     """Creates/updates a stamp for a given task"""
364     stamp = data.getVar('STAMP', d)
365     if not stamp:
366         return
367     stamp = "%s.%s" % (data.expand(stamp, d), task)
368     mkdirhier(os.path.dirname(stamp))
369     open(stamp, "w+")
370
371
372 def add_task(task, deps, d):
373     task_graph = data.getVar('_task_graph', d)
374     if not task_graph:
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)
379     for dep in deps:
380         if not task_graph.hasnode(dep):
381             task_graph.addnode(dep, None)
382         task_graph.addnode(task, dep)
383
384
385 def remove_task(task, kill, d):
386     """Remove an BB 'task'.
387
388        If kill is 1, also remove tasks that depend on this task."""
389
390     task_graph = data.getVar('_task_graph', d)
391     if not task_graph:
392         task_graph = bb.digraph()
393         data.setVar('_task_graph', task_graph, d)
394     if not task_graph.hasnode(task):
395         return
396
397     data.delVarFlag(task, 'task', d)
398     ref = 1
399     if kill == 1:
400         ref = 2
401     task_graph.delnode(task, ref)
402
403 def task_exists(task, d):
404     task_graph = data.getVar('_task_graph', d)
405     if not task_graph:
406         task_graph = bb.digraph()
407         data.setVar('_task_graph', task_graph, d)
408     return task_graph.hasnode(task)
409
410 def get_task_data():
411     return _task_data