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