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