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