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