bitbake/lib/bb/__init__.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     # 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
292 def stamp_is_current(task, d, checkdeps = 1):
293     """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
294     task_graph = data.getVar('_task_graph', d)
295     if not task_graph:
296         task_graph = bb.digraph()
297         data.setVar('_task_graph', task_graph, d)
298     stamp = data.getVar('STAMP', d)
299     if not stamp:
300         return 0
301     stampfile = "%s.%s" % (data.expand(stamp, d), task)
302     if not os.access(stampfile, os.F_OK):
303         return 0
304
305     if checkdeps == 0:
306         return 1
307
308     import stat
309     tasktime = os.stat(stampfile)[stat.ST_MTIME]
310
311     _deps = []
312     def checkStamp(graph, task):
313         # check for existance
314         if data.getVarFlag(task, 'nostamp', d):
315             return 1
316
317         if not stamp_is_current(task, d, 0):
318             return 0
319
320         depfile = "%s.%s" % (data.expand(stamp, d), task)
321         deptime = os.stat(depfile)[stat.ST_MTIME]
322         if deptime > tasktime:
323             return 0
324         return 1
325
326     return task_graph.walkdown(task, checkStamp)
327
328
329 def md5_is_current(task):
330     """Check if a md5 file for a given task is current"""
331
332
333 def mkstamp(task, d):
334     """Creates/updates a stamp for a given task"""
335     stamp = data.getVar('STAMP', d)
336     if not stamp:
337         return
338     stamp = "%s.%s" % (data.expand(stamp, d), task)
339     mkdirhier(os.path.dirname(stamp))
340     open(stamp, "w+")
341
342
343 def add_task(task, deps, d):
344     task_graph = data.getVar('_task_graph', d)
345     if not task_graph:
346         task_graph = bb.digraph()
347     data.setVarFlag(task, 'task', 1, d)
348     task_graph.addnode(task, None)
349     for dep in deps:
350         if not task_graph.hasnode(dep):
351             task_graph.addnode(dep, None)
352         task_graph.addnode(task, dep)
353     # don't assume holding a reference
354     data.setVar('_task_graph', task_graph, d)
355
356
357 def remove_task(task, kill, d):
358     """Remove an BB 'task'.
359
360        If kill is 1, also remove tasks that depend on this task."""
361
362     task_graph = data.getVar('_task_graph', d)
363     if not task_graph:
364         task_graph = bb.digraph()
365     if not task_graph.hasnode(task):
366         return
367
368     data.delVarFlag(task, 'task', d)
369     ref = 1
370     if kill == 1:
371         ref = 2
372     task_graph.delnode(task, ref)
373     data.setVar('_task_graph', task_graph, d)
374
375 def task_exists(task, d):
376     task_graph = data.getVar('_task_graph', d)
377     if not task_graph:
378         task_graph = bb.digraph()
379         data.setVar('_task_graph', task_graph, d)
380     return task_graph.hasnode(task)