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 debug, 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.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.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     os.chdir(prevdir)
191
192     # restore the backups
193     os.dup2(osi[0], osi[1])
194     os.dup2(oso[0], oso[1])
195     os.dup2(ose[0], ose[1])
196
197     # close our logs
198     si.close()
199     so.close()
200     se.close()
201
202     # close the backup fds
203     os.close(osi[0])
204     os.close(oso[0])
205     os.close(ose[0])
206
207     if ret==0:
208         if bb.debug_level > 0:
209             os.remove(runfile)
210 #            os.remove(logfile)
211         return
212     else:
213         error("function %s failed" % func)
214         if data.getVar("BBINCLUDELOGS", d):
215             error("log data follows (%s)" % logfile)
216             f = open(logfile, "r")
217             while True:
218                 l = f.readline()
219                 if l == '':
220                     break
221                 l = l.rstrip()
222                 print '| %s' % l
223             f.close()
224         else:
225             error("see log in %s" % logfile)
226         raise FuncFailed( logfile )
227
228
229 def exec_task(task, d):
230     """Execute an BB 'task'
231
232        The primary difference between executing a task versus executing
233        a function is that a task exists in the task digraph, and therefore
234        has dependencies amongst other tasks."""
235
236     # check if the task is in the graph..
237     task_graph = data.getVar('_task_graph', d)
238     if not task_graph:
239         task_graph = bb.digraph()
240         data.setVar('_task_graph', task_graph, d)
241     task_cache = data.getVar('_task_cache', d)
242     if not task_cache:
243         task_cache = []
244         data.setVar('_task_cache', task_cache, d)
245     if not task_graph.hasnode(task):
246         raise EventException("Missing node in task graph", InvalidTask(task, d))
247
248     # check whether this task needs executing..
249     if not data.getVarFlag(task, 'force', d):
250         if stamp_is_current(task, d):
251             return 1
252
253     # follow digraph path up, then execute our way back down
254     def execute(graph, item):
255         if data.getVarFlag(item, 'task', d):
256             if item in task_cache:
257                 return 1
258
259             if task != item:
260                 # deeper than toplevel, exec w/ deps
261                 exec_task(item, d)
262                 return 1
263
264             try:
265                 debug(1, "Executing task %s" % item)
266                 old_overrides = data.getVar('OVERRIDES', d, 0)
267                 localdata = data.createCopy(d)
268                 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
269                 data.update_data(localdata)
270                 event.fire(TaskStarted(item, localdata))
271                 exec_func(item, localdata)
272                 event.fire(TaskSucceeded(item, localdata))
273                 task_cache.append(item)
274                 data.setVar('_task_cache', task_cache, d)
275             except FuncFailed, reason:
276                 note( "Task failed: %s" % reason )
277                 failedevent = TaskFailed(item, d)
278                 event.fire(failedevent)
279                 raise EventException("Function failed in task: %s" % reason, failedevent)
280
281     # execute
282     task_graph.walkdown(task, execute)
283
284     # make stamp, or cause event and raise exception
285     if not data.getVarFlag(task, 'nostamp', d):
286         mkstamp(task, d)
287
288
289 def stamp_is_current(task, d, checkdeps = 1):
290     """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
291     task_graph = data.getVar('_task_graph', d)
292     if not task_graph:
293         task_graph = bb.digraph()
294         data.setVar('_task_graph', task_graph, d)
295     stamp = data.getVar('STAMP', d)
296     if not stamp:
297         return 0
298     stampfile = "%s.%s" % (data.expand(stamp, d), task)
299     if not os.access(stampfile, os.F_OK):
300         return 0
301
302     if checkdeps == 0:
303         return 1
304
305     import stat
306     tasktime = os.stat(stampfile)[stat.ST_MTIME]
307
308     _deps = []
309     def checkStamp(graph, task):
310         # check for existance
311         if data.getVarFlag(task, 'nostamp', d):
312             return 1
313
314         if not stamp_is_current(task, d, 0):
315             return 0
316
317         depfile = "%s.%s" % (data.expand(stamp, d), task)
318         deptime = os.stat(depfile)[stat.ST_MTIME]
319         if deptime > tasktime:
320             return 0
321         return 1
322
323     return task_graph.walkdown(task, checkStamp)
324
325
326 def md5_is_current(task):
327     """Check if a md5 file for a given task is current"""
328
329
330 def mkstamp(task, d):
331     """Creates/updates a stamp for a given task"""
332     stamp = data.getVar('STAMP', d)
333     if not stamp:
334         return
335     stamp = "%s.%s" % (data.expand(stamp, d), task)
336     mkdirhier(os.path.dirname(stamp))
337     open(stamp, "w+")
338
339
340 def add_task(task, deps, d):
341     task_graph = data.getVar('_task_graph', d)
342     if not task_graph:
343         task_graph = bb.digraph()
344     data.setVarFlag(task, 'task', 1, d)
345     task_graph.addnode(task, None)
346     for dep in deps:
347         if not task_graph.hasnode(dep):
348             task_graph.addnode(dep, None)
349         task_graph.addnode(task, dep)
350     # don't assume holding a reference
351     data.setVar('_task_graph', task_graph, d)
352
353
354 def remove_task(task, kill, d):
355     """Remove an BB 'task'.
356
357        If kill is 1, also remove tasks that depend on this task."""
358
359     task_graph = data.getVar('_task_graph', d)
360     if not task_graph:
361         task_graph = bb.digraph()
362     if not task_graph.hasnode(task):
363         return
364
365     data.delVarFlag(task, 'task', d)
366     ref = 1
367     if kill == 1:
368         ref = 2
369     task_graph.delnode(task, ref)
370     data.setVar('_task_graph', task_graph, d)
371
372 def task_exists(task, d):
373     task_graph = data.getVar('_task_graph', d)
374     if not task_graph:
375         task_graph = bb.digraph()
376         data.setVar('_task_graph', task_graph, d)
377     return task_graph.hasnode(task)