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