Add selfstamp and lockfiles flags
[bitbake.git] / lib / bb / build.py
1 # ex:ts=4:sw=4:sts=4:et
2 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3 #
4 # BitBake 'Build' implementation
5 #
6 # Core code for function execution and task handling in the
7 # BitBake build tools.
8 #
9 # Copyright (C) 2003, 2004  Chris Larson
10 #
11 # Based on Gentoo's portage.py.
12 #
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License version 2 as
15 # published by the Free Software Foundation.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License along
23 # with this program; if not, write to the Free Software Foundation, Inc.,
24 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #
26 #Based on functions from the base bb module, Copyright 2003 Holger Schurig
27
28 from bb import data, fetch, 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     cleandirs = (data.expand(data.getVarFlag(func, 'cleandirs', d), d) or "").split()
78     for cdir in cleandirs:
79         os.system("rm -rf %s" % cdir)
80
81     if not dirs:
82         dirs = (data.expand(data.getVarFlag(func, 'dirs', d), d) or "").split()
83     for adir in dirs:
84         mkdirhier(adir)
85
86     if len(dirs) > 0:
87         adir = dirs[-1]
88     else:
89         adir = data.getVar('B', d, 1)
90
91     adir = data.expand(adir, d)
92
93     try:
94         prevdir = os.getcwd()
95     except OSError:
96         prevdir = data.expand('${TOPDIR}', d)
97     if adir and os.access(adir, os.F_OK):
98         os.chdir(adir)
99
100     locks = []
101     lockfiles = (data.expand(data.getVarFlag(func, 'lockfiles', d), d) or "").split()
102     for lock in lockfiles:
103         locks.append(bb.utils.lockfile(lock))
104
105     if data.getVarFlag(func, "python", d):
106         exec_func_python(func, d)
107     else:
108         exec_func_shell(func, d)
109
110     for lock in locks:
111         bb.utils.unlockfile(lock)
112
113     if os.path.exists(prevdir):
114         os.chdir(prevdir)
115
116 def exec_func_python(func, d):
117     """Execute a python BB 'function'"""
118     import re, os
119
120     tmp  = "def " + func + "():\n%s" % data.getVar(func, d)
121     tmp += '\n' + func + '()'
122     comp = utils.better_compile(tmp, func, bb.data.getVar('FILE', d, 1) )
123     prevdir = os.getcwd()
124     g = {} # globals
125     g['bb'] = bb
126     g['os'] = os
127     g['d'] = d
128     utils.better_exec(comp,g,tmp, bb.data.getVar('FILE',d,1))
129     if os.path.exists(prevdir):
130         os.chdir(prevdir)
131
132 def exec_func_shell(func, d):
133     """Execute a shell BB 'function' Returns true if execution was successful.
134
135     For this, it creates a bash shell script in the tmp dectory, writes the local
136     data into it and finally executes. The output of the shell will end in a log file and stdout.
137
138     Note on directory behavior.  The 'dirs' varflag should contain a list
139     of the directories you need created prior to execution.  The last
140     item in the list is where we will chdir/cd to.
141     """
142     import sys
143
144     deps = data.getVarFlag(func, 'deps', d)
145     check = data.getVarFlag(func, 'check', d)
146     interact = data.getVarFlag(func, 'interactive', d)
147     if check in globals():
148         if globals()[check](func, deps):
149             return
150
151     global logfile
152     t = data.getVar('T', d, 1)
153     if not t:
154         return 0
155     mkdirhier(t)
156     logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
157     runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
158
159     f = open(runfile, "w")
160     f.write("#!/bin/sh -e\n")
161     if bb.msg.debug_level['default'] > 0: f.write("set -x\n")
162     data.emit_env(f, d)
163
164     f.write("cd %s\n" % os.getcwd())
165     if func: f.write("%s\n" % func)
166     f.close()
167     os.chmod(runfile, 0775)
168     if not func:
169         bb.msg.error(bb.msg.domain.Build, "Function not specified")
170         raise FuncFailed()
171
172     # open logs
173     si = file('/dev/null', 'r')
174     try:
175         if bb.msg.debug_level['default'] > 0:
176             so = os.popen("tee \"%s\"" % logfile, "w")
177         else:
178             so = file(logfile, 'w')
179     except OSError, e:
180         bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
181         pass
182
183     se = so
184
185     if not interact:
186         # dup the existing fds so we dont lose them
187         osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
188         oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
189         ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
190
191         # replace those fds with our own
192         os.dup2(si.fileno(), osi[1])
193         os.dup2(so.fileno(), oso[1])
194         os.dup2(se.fileno(), ose[1])
195
196     # execute function
197     prevdir = os.getcwd()
198     if data.getVarFlag(func, "fakeroot", d):
199         maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
200     else:
201         maybe_fakeroot = ''
202     lang_environment = "LC_ALL=C "
203     ret = os.system('%s%ssh -e %s' % (lang_environment, maybe_fakeroot, runfile))
204     try:
205         os.chdir(prevdir)
206     except:
207         pass
208
209     if not interact:
210         # restore the backups
211         os.dup2(osi[0], osi[1])
212         os.dup2(oso[0], oso[1])
213         os.dup2(ose[0], ose[1])
214
215         # close our logs
216         si.close()
217         so.close()
218         se.close()
219
220         # close the backup fds
221         os.close(osi[0])
222         os.close(oso[0])
223         os.close(ose[0])
224
225     if ret==0:
226         if bb.msg.debug_level['default'] > 0:
227             os.remove(runfile)
228 #            os.remove(logfile)
229         return
230     else:
231         bb.msg.error(bb.msg.domain.Build, "function %s failed" % func)
232         if data.getVar("BBINCLUDELOGS", d):
233             bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
234             number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
235             if number_of_lines:
236                 os.system('tail -n%s %s' % (number_of_lines, logfile))
237             else:
238                 f = open(logfile, "r")
239                 while True:
240                     l = f.readline()
241                     if l == '':
242                         break
243                     l = l.rstrip()
244                     print '| %s' % l
245                 f.close()
246         else:
247             bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
248         raise FuncFailed( logfile )
249
250
251 def exec_task(task, d):
252     """Execute an BB 'task'
253
254        The primary difference between executing a task versus executing
255        a function is that a task exists in the task digraph, and therefore
256        has dependencies amongst other tasks."""
257
258     # check if the task is in the graph..
259     task_graph = data.getVar('_task_graph', d)
260     if not task_graph:
261         task_graph = bb.digraph()
262         data.setVar('_task_graph', task_graph, d)
263     task_cache = data.getVar('_task_cache', d)
264     if not task_cache:
265         task_cache = []
266         data.setVar('_task_cache', task_cache, d)
267     if not task_graph.hasnode(task):
268         raise EventException("Missing node in task graph", InvalidTask(task, d))
269
270     # check whether this task needs executing..
271     if stamp_is_current(task, d):
272         return 1
273
274     # follow digraph path up, then execute our way back down
275     def execute(graph, item):
276         if data.getVarFlag(item, 'task', d):
277             if item in task_cache:
278                 return 1
279
280             if task != item:
281                 # deeper than toplevel, exec w/ deps
282                 exec_task(item, d)
283                 return 1
284
285             try:
286                 bb.msg.debug(1, bb.msg.domain.Build, "Executing task %s" % item)
287                 old_overrides = data.getVar('OVERRIDES', d, 0)
288                 localdata = data.createCopy(d)
289                 data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
290                 data.update_data(localdata)
291                 event.fire(TaskStarted(item, localdata))
292                 exec_func(item, localdata)
293                 event.fire(TaskSucceeded(item, localdata))
294                 task_cache.append(item)
295                 data.setVar('_task_cache', task_cache, d)
296             except FuncFailed, reason:
297                 bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % reason )
298                 failedevent = TaskFailed(item, d)
299                 event.fire(failedevent)
300                 raise EventException("Function failed in task: %s" % reason, failedevent)
301
302     if data.getVarFlag(task, 'dontrundeps', d):
303         execute(None, task)
304     else:
305         task_graph.walkdown(task, execute)
306
307     # make stamp, or cause event and raise exception
308     if not data.getVarFlag(task, 'nostamp', d) and not data.getVarFlag(task, 'selfstamp', d):
309         make_stamp(task, d)
310
311 def extract_stamp_data(d, fn):
312     """
313     Extracts stamp data from d which is either a data dictonary (fn unset) 
314     or a dataCache entry (fn set). 
315     """
316     if fn:
317         return (d.task_queues[fn], d.stamp[fn], d.task_deps[fn])
318     task_graph = data.getVar('_task_graph', d)
319     if not task_graph:
320         task_graph = bb.digraph()
321         data.setVar('_task_graph', task_graph, d)
322     return (task_graph, data.getVar('STAMP', d, 1), None)
323
324 def extract_stamp(d, fn):
325     """
326     Extracts stamp format which is either a data dictonary (fn unset) 
327     or a dataCache entry (fn set). 
328     """
329     if fn:
330         return d.stamp[fn]
331     return data.getVar('STAMP', d, 1)
332
333 def stamp_is_current(task, d, file_name = None, checkdeps = 1):
334     """
335     Check status of a given task's stamp. 
336     Returns 0 if it is not current and needs updating.
337     (d can be a data dict or dataCache)
338     """
339
340     (task_graph, stampfn, taskdep) = extract_stamp_data(d, file_name)
341
342     if not stampfn:
343         return 0
344
345     stampfile = "%s.%s" % (stampfn, task)
346     if not os.access(stampfile, os.F_OK):
347         return 0
348
349     if checkdeps == 0:
350         return 1
351
352     import stat
353     tasktime = os.stat(stampfile)[stat.ST_MTIME]
354
355     _deps = []
356     def checkStamp(graph, task):
357         # check for existance
358         if file_name:
359             if 'nostamp' in taskdep and task in taskdep['nostamp']:
360                 return 1
361         else:
362             if data.getVarFlag(task, 'nostamp', d):
363                 return 1
364
365         if not stamp_is_current(task, d, file_name, 0                                           ):
366             return 0
367
368         depfile = "%s.%s" % (stampfn, task)
369         deptime = os.stat(depfile)[stat.ST_MTIME]
370         if deptime > tasktime:
371             return 0
372         return 1
373
374     return task_graph.walkdown(task, checkStamp)
375
376 def stamp_internal(task, d, file_name):
377     """
378     Internal stamp helper function
379     Removes any stamp for the given task
380     Makes sure the stamp directory exists
381     Returns the stamp path+filename
382     """
383     stamp = extract_stamp(d, file_name)
384     if not stamp:
385         return
386     stamp = "%s.%s" % (stamp, task)
387     mkdirhier(os.path.dirname(stamp))
388     # Remove the file and recreate to force timestamp
389     # change on broken NFS filesystems
390     if os.access(stamp, os.F_OK):
391         os.remove(stamp)
392     return stamp
393
394 def make_stamp(task, d, file_name = None):
395     """
396     Creates/updates a stamp for a given task
397     (d can be a data dict or dataCache)
398     """
399     stamp = stamp_internal(task, d, file_name)
400     if stamp:
401         f = open(stamp, "w")
402         f.close()
403
404 def del_stamp(task, d, file_name = None):
405     """
406     Removes a stamp for a given task
407     (d can be a data dict or dataCache)
408     """
409     stamp_internal(task, d, file_name)
410
411 def add_tasks(tasklist, d):
412     task_graph = data.getVar('_task_graph', d)
413     task_deps = data.getVar('_task_deps', d)
414     if not task_graph:
415         task_graph = bb.digraph()
416     if not task_deps:
417         task_deps = {}
418
419     for task in tasklist:
420         deps = tasklist[task]
421         task = data.expand(task, d)
422
423         data.setVarFlag(task, 'task', 1, d)
424         task_graph.addnode(task, None)
425         for dep in deps:
426             dep = data.expand(dep, d)
427             if not task_graph.hasnode(dep):
428                 task_graph.addnode(dep, None)
429             task_graph.addnode(task, dep)
430
431         flags = data.getVarFlags(task, d)    
432         def getTask(name):
433             if name in flags:
434                 deptask = data.expand(flags[name], d)
435                 if not name in task_deps:
436                     task_deps[name] = {}
437                 task_deps[name][task] = deptask
438         getTask('depends')
439         getTask('deptask')
440         getTask('rdeptask')
441         getTask('recrdeptask')
442         getTask('nostamp')
443
444     # don't assume holding a reference
445     data.setVar('_task_graph', task_graph, d)
446     data.setVar('_task_deps', task_deps, d)
447
448 def remove_task(task, kill, d):
449     """Remove an BB 'task'.
450
451        If kill is 1, also remove tasks that depend on this task."""
452
453     task_graph = data.getVar('_task_graph', d)
454     if not task_graph:
455         task_graph = bb.digraph()
456     if not task_graph.hasnode(task):
457         return
458
459     data.delVarFlag(task, 'task', d)
460     ref = 1
461     if kill == 1:
462         ref = 2
463     task_graph.delnode(task, ref)
464     data.setVar('_task_graph', task_graph, d)
465
466 def task_exists(task, d):
467     task_graph = data.getVar('_task_graph', d)
468     if not task_graph:
469         task_graph = bb.digraph()
470         data.setVar('_task_graph', task_graph, d)
471     return task_graph.hasnode(task)