Make -f (force) flag handling work properly
[vuplus_bitbake] / lib / bb / build.py
1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 #
4 # BitBake 'Build' implementation
5 #
6 # Core code for function execution and task handling in the
7 # BitBake build tools.
8 #
9 # Copyright (C) 2003, 2004  Chris Larson
10 #
11 # Based on Gentoo's portage.py.
12 #
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License version 2 as
15 # published by the Free Software Foundation.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License along
23 # with this program; if not, write to the Free Software Foundation, Inc.,
24 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #
26 #Based on functions from the base bb module, Copyright 2003 Holger Schurig
27
28 from bb import data, fetch, 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     interact = data.getVarFlag(func, 'interactive', d)
136     if check in globals():
137         if globals()[check](func, deps):
138             return
139
140     global logfile
141     t = data.getVar('T', d, 1)
142     if not t:
143         return 0
144     mkdirhier(t)
145     logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
146     runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
147
148     f = open(runfile, "w")
149     f.write("#!/bin/sh -e\n")
150     if bb.msg.debug_level['default'] > 0: f.write("set -x\n")
151     data.emit_env(f, d)
152
153     f.write("cd %s\n" % os.getcwd())
154     if func: f.write("%s\n" % func)
155     f.close()
156     os.chmod(runfile, 0775)
157     if not func:
158         bb.msg.error(bb.msg.domain.Build, "Function not specified")
159         raise FuncFailed()
160
161     # open logs
162     si = file('/dev/null', 'r')
163     try:
164         if bb.msg.debug_level['default'] > 0:
165             so = os.popen("tee \"%s\"" % logfile, "w")
166         else:
167             so = file(logfile, 'w')
168     except OSError, e:
169         bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
170         pass
171
172     se = so
173
174     if not interact:
175         # dup the existing fds so we dont lose them
176         osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
177         oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
178         ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
179
180         # replace those fds with our own
181         os.dup2(si.fileno(), osi[1])
182         os.dup2(so.fileno(), oso[1])
183         os.dup2(se.fileno(), ose[1])
184
185     # execute function
186     prevdir = os.getcwd()
187     if data.getVarFlag(func, "fakeroot", d):
188         maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
189     else:
190         maybe_fakeroot = ''
191     ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
192     try:
193         os.chdir(prevdir)
194     except:
195         pass
196
197     if not interact:
198         # restore the backups
199         os.dup2(osi[0], osi[1])
200         os.dup2(oso[0], oso[1])
201         os.dup2(ose[0], ose[1])
202
203         # close our logs
204         si.close()
205         so.close()
206         se.close()
207
208         # close the backup fds
209         os.close(osi[0])
210         os.close(oso[0])
211         os.close(ose[0])
212
213     if ret==0:
214         if bb.msg.debug_level['default'] > 0:
215             os.remove(runfile)
216 #            os.remove(logfile)
217         return
218     else:
219         bb.msg.error(bb.msg.domain.Build, "function %s failed" % func)
220         if data.getVar("BBINCLUDELOGS", d):
221             bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
222             number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
223             if number_of_lines:
224                 os.system('tail -n%s %s' % (number_of_lines, logfile))
225             else:
226                 f = open(logfile, "r")
227                 while True:
228                     l = f.readline()
229                     if l == '':
230                         break
231                     l = l.rstrip()
232                     print '| %s' % l
233                 f.close()
234         else:
235             bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
236         raise FuncFailed( logfile )
237
238
239 def exec_task(task, d):
240     """Execute an BB 'task'
241
242        The primary difference between executing a task versus executing
243        a function is that a task exists in the task digraph, and therefore
244        has dependencies amongst other tasks."""
245
246     # check if the task is in the graph..
247     task_graph = data.getVar('_task_graph', d)
248     if not task_graph:
249         task_graph = bb.digraph()
250         data.setVar('_task_graph', task_graph, d)
251     task_cache = data.getVar('_task_cache', d)
252     if not task_cache:
253         task_cache = []
254         data.setVar('_task_cache', task_cache, d)
255     if not task_graph.hasnode(task):
256         raise EventException("Missing node in task graph", InvalidTask(task, d))
257
258     # check whether this task needs executing..
259     if stamp_is_current(task, d):
260         return 1
261
262     # follow digraph path up, then execute our way back down
263     def execute(graph, item):
264         if data.getVarFlag(item, 'task', d):
265             if item in task_cache:
266                 return 1
267
268             if task != item:
269                 # deeper than toplevel, exec w/ deps
270                 exec_task(item, d)
271                 return 1
272
273             try:
274                 bb.msg.debug(1, bb.msg.domain.Build, "Executing task %s" % item)
275                 old_overrides = data.getVar('OVERRIDES', d, 0)
276                 localdata = data.createCopy(d)
277                 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
278                 data.update_data(localdata)
279                 event.fire(TaskStarted(item, localdata))
280                 exec_func(item, localdata)
281                 event.fire(TaskSucceeded(item, localdata))
282                 task_cache.append(item)
283                 data.setVar('_task_cache', task_cache, d)
284             except FuncFailed, reason:
285                 bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % reason )
286                 failedevent = TaskFailed(item, d)
287                 event.fire(failedevent)
288                 raise EventException("Function failed in task: %s" % reason, failedevent)
289
290     if data.getVarFlag(task, 'dontrundeps', d):
291         execute(None, task)
292     else:
293         task_graph.walkdown(task, execute)
294
295     # make stamp, or cause event and raise exception
296     if not data.getVarFlag(task, 'nostamp', d):
297         make_stamp(task, d)
298
299 def extract_stamp_data(d, fn):
300     """
301     Extracts stamp data from d which is either a data dictonary (fn unset) 
302     or a dataCache entry (fn set). 
303     """
304     if fn:
305         return (d.task_queues[fn], d.stamp[fn], d.task_deps[fn])
306     task_graph = data.getVar('_task_graph', d)
307     if not task_graph:
308         task_graph = bb.digraph()
309         data.setVar('_task_graph', task_graph, d)
310     return (task_graph, data.getVar('STAMP', d, 1), None)
311
312 def extract_stamp(d, fn):
313     """
314     Extracts stamp format which is either a data dictonary (fn unset) 
315     or a dataCache entry (fn set). 
316     """
317     if fn:
318         return d.stamp[fn]
319     return data.getVar('STAMP', d, 1)
320
321 def stamp_is_current(task, d, file_name = None, checkdeps = 1):
322     """
323     Check status of a given task's stamp. 
324     Returns 0 if it is not current and needs updating.
325     (d can be a data dict or dataCache)
326     """
327
328     (task_graph, stampfn, taskdep) = extract_stamp_data(d, file_name)
329
330     if not stampfn:
331         return 0
332
333     stampfile = "%s.%s" % (stampfn, task)
334     if not os.access(stampfile, os.F_OK):
335         return 0
336
337     if checkdeps == 0:
338         return 1
339
340     import stat
341     tasktime = os.stat(stampfile)[stat.ST_MTIME]
342
343     _deps = []
344     def checkStamp(graph, task):
345         # check for existance
346         if file_name:
347             if 'nostamp' in taskdep and task in taskdep['nostamp']:
348                 return 1
349         else:
350             if data.getVarFlag(task, 'nostamp', d):
351                 return 1
352
353         if not stamp_is_current(task, d, file_name, 0                                           ):
354             return 0
355
356         depfile = "%s.%s" % (stampfn, task)
357         deptime = os.stat(depfile)[stat.ST_MTIME]
358         if deptime > tasktime:
359             return 0
360         return 1
361
362     return task_graph.walkdown(task, checkStamp)
363
364 def stamp_internal(task, d, file_name):
365     """
366     Internal stamp helper function
367     Removes any stamp for the given task
368     Makes sure the stamp directory exists
369     Returns the stamp path+filename
370     """
371     stamp = extract_stamp(d, file_name)
372     if not stamp:
373         return
374     stamp = "%s.%s" % (stamp, task)
375     mkdirhier(os.path.dirname(stamp))
376     # Remove the file and recreate to force timestamp
377     # change on broken NFS filesystems
378     if os.access(stamp, os.F_OK):
379         os.remove(stamp)
380     return stamp
381
382 def make_stamp(task, d, file_name = None):
383     """
384     Creates/updates a stamp for a given task
385     (d can be a data dict or dataCache)
386     """
387     stamp = stamp_internal(task, d, file_name)
388     if stamp:
389         f = open(stamp, "w")
390         f.close()
391
392 def del_stamp(task, d, file_name = None):
393     """
394     Removes a stamp for a given task
395     (d can be a data dict or dataCache)
396     """
397     stamp_internal(task, d, file_name)
398
399 def add_task(task, deps, d):
400     task_graph = data.getVar('_task_graph', d)
401     if not task_graph:
402         task_graph = bb.digraph()
403     data.setVarFlag(task, 'task', 1, d)
404     task_graph.addnode(task, None)
405     for dep in deps:
406         if not task_graph.hasnode(dep):
407             task_graph.addnode(dep, None)
408         task_graph.addnode(task, dep)
409     # don't assume holding a reference
410     data.setVar('_task_graph', task_graph, d)
411
412     task_deps = data.getVar('_task_deps', d)
413     if not task_deps:
414         task_deps = {}
415     def getTask(name):
416         deptask = data.getVarFlag(task, name, d)
417         if deptask:
418             if not name in task_deps:
419                 task_deps[name] = {}
420             task_deps[name][task] = deptask
421     getTask('deptask')
422     getTask('rdeptask')
423     getTask('recrdeptask')
424     getTask('nostamp')
425
426     data.setVar('_task_deps', task_deps, d)
427
428 def remove_task(task, kill, d):
429     """Remove an BB 'task'.
430
431        If kill is 1, also remove tasks that depend on this task."""
432
433     task_graph = data.getVar('_task_graph', d)
434     if not task_graph:
435         task_graph = bb.digraph()
436     if not task_graph.hasnode(task):
437         return
438
439     data.delVarFlag(task, 'task', d)
440     ref = 1
441     if kill == 1:
442         ref = 2
443     task_graph.delnode(task, ref)
444     data.setVar('_task_graph', task_graph, d)
445
446 def task_exists(task, d):
447     task_graph = data.getVar('_task_graph', d)
448     if not task_graph:
449         task_graph = bb.digraph()
450         data.setVar('_task_graph', task_graph, d)
451     return task_graph.hasnode(task)