bitbake/lib/bb/build.py:
[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 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     if data.getVarFlag(task, 'dontrundeps', d):
285         execute(None, task)
286     else:
287         task_graph.walkdown(task, execute)
288
289     # make stamp, or cause event and raise exception
290     if not data.getVarFlag(task, 'nostamp', d):
291         mkstamp(task, d)
292
293 def stamp_is_current_cache(dataCache, file_name, task, checkdeps = 1):
294     """
295     Check status of a given task's stamp. 
296     Returns 0 if it is not current and needs updating.
297     Same as stamp_is_current but works against the dataCache instead of d
298     """
299     task_graph = dataCache.task_queues[file_name]
300
301     if not dataCache.stamp[file_name]:
302         return 0
303
304     stampfile = "%s.%s" % (dataCache.stamp[file_name], task)
305     if not os.access(stampfile, os.F_OK):
306         return 0
307
308     if checkdeps == 0:
309         return 1
310
311     import stat
312     tasktime = os.stat(stampfile)[stat.ST_MTIME]
313
314     _deps = []
315     def checkStamp(graph, task):
316         # check for existance
317         if 'nostamp' in dataCache.task_deps[file_name] and task in dataCache.task_deps[file_name]['nostamp']:
318             return 1
319
320         if not stamp_is_current_cache(dataCache, file_name, task, 0):
321             return 0
322
323         depfile = "%s.%s" % (dataCache.stamp[file_name], task)
324         deptime = os.stat(depfile)[stat.ST_MTIME]
325         if deptime > tasktime:
326             return 0
327         return 1
328
329     return task_graph.walkdown(task, checkStamp)
330
331 def stamp_is_current(task, d, checkdeps = 1):
332     """
333     Check status of a given task's stamp. 
334     Returns 0 if it is not current and needs updating.
335     """
336     task_graph = data.getVar('_task_graph', d)
337     if not task_graph:
338         task_graph = bb.digraph()
339         data.setVar('_task_graph', task_graph, d)
340     stamp = data.getVar('STAMP', d)
341     if not stamp:
342         return 0
343     stampfile = "%s.%s" % (data.expand(stamp, d), task)
344     if not os.access(stampfile, os.F_OK):
345         return 0
346
347     if checkdeps == 0:
348         return 1
349
350     import stat
351     tasktime = os.stat(stampfile)[stat.ST_MTIME]
352
353     _deps = []
354     def checkStamp(graph, task):
355         # check for existance
356         if data.getVarFlag(task, 'nostamp', d):
357             return 1
358
359         if not stamp_is_current(task, d, 0):
360             return 0
361
362         depfile = "%s.%s" % (data.expand(stamp, d), task)
363         deptime = os.stat(depfile)[stat.ST_MTIME]
364         if deptime > tasktime:
365             return 0
366         return 1
367
368     return task_graph.walkdown(task, checkStamp)
369
370
371 def md5_is_current(task):
372     """Check if a md5 file for a given task is current"""
373
374
375 def mkstamp(task, d):
376     """Creates/updates a stamp for a given task"""
377     stamp = data.getVar('STAMP', d)
378     if not stamp:
379         return
380     stamp = "%s.%s" % (data.expand(stamp, d), task)
381     mkdirhier(os.path.dirname(stamp))
382     # Remove the file and recreate to force timestamp
383     # change on broken NFS filesystems
384     if os.access(stamp, os.F_OK):
385         os.remove(stamp)
386     f = open(stamp, "w")
387     f.close()
388
389 def add_task(task, deps, d):
390     task_graph = data.getVar('_task_graph', d)
391     if not task_graph:
392         task_graph = bb.digraph()
393     data.setVarFlag(task, 'task', 1, d)
394     task_graph.addnode(task, None)
395     for dep in deps:
396         if not task_graph.hasnode(dep):
397             task_graph.addnode(dep, None)
398         task_graph.addnode(task, dep)
399     # don't assume holding a reference
400     data.setVar('_task_graph', task_graph, d)
401
402     task_deps = data.getVar('_task_deps', d)
403     if not task_deps:
404         task_deps = {}
405     def getTask(name):
406         deptask = data.getVarFlag(task, name, d)
407         if deptask:
408             if not name in task_deps:
409                 task_deps[name] = {}
410             task_deps[name][task] = deptask
411     getTask('deptask')
412     getTask('rdeptask')
413     getTask('recrdeptask')
414     getTask('nostamp')
415
416     data.setVar('_task_deps', task_deps, d)
417
418 def remove_task(task, kill, d):
419     """Remove an BB 'task'.
420
421        If kill is 1, also remove tasks that depend on this task."""
422
423     task_graph = data.getVar('_task_graph', d)
424     if not task_graph:
425         task_graph = bb.digraph()
426     if not task_graph.hasnode(task):
427         return
428
429     data.delVarFlag(task, 'task', d)
430     ref = 1
431     if kill == 1:
432         ref = 2
433     task_graph.delnode(task, ref)
434     data.setVar('_task_graph', task_graph, d)
435
436 def task_exists(task, d):
437     task_graph = data.getVar('_task_graph', d)
438     if not task_graph:
439         task_graph = bb.digraph()
440         data.setVar('_task_graph', task_graph, d)
441     return task_graph.hasnode(task)