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