build: fix dir removal traceback
[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 import os
29 import sys
30 import logging
31 import bb
32 import bb.msg
33 import bb.process
34 from contextlib import nested
35 from bb import data, event, utils
36
37 bblogger = logging.getLogger('BitBake')
38 logger = logging.getLogger('BitBake.Build')
39
40 NULL = open(os.devnull, 'r+')
41
42
43 # When we execute a python function we'd like certain things
44 # in all namespaces, hence we add them to __builtins__
45 # If we do not do this and use the exec globals, they will
46 # not be available to subfunctions.
47 __builtins__['bb'] = bb
48 __builtins__['os'] = os
49
50 class FuncFailed(Exception):
51     def __init__(self, name = None, logfile = None):
52         self.logfile = logfile
53         self.name = name
54         if name:
55             self.msg = "Function '%s' failed" % name
56         else:
57             self.msg = "Function failed"
58
59     def __str__(self):
60         if self.logfile and os.path.exists(self.logfile):
61             msg = ("%s (see %s for further information)" %
62                    (self.msg, self.logfile))
63         else:
64             msg = self.msg
65         return msg
66
67 class TaskBase(event.Event):
68     """Base class for task events"""
69
70     def __init__(self, t, d ):
71         self._task = t
72         self._package = bb.data.getVar("PF", d, 1)
73         event.Event.__init__(self)
74         self._message = "package %s: task %s: %s" % (bb.data.getVar("PF", d, 1), t, bb.event.getName(self)[4:])
75
76     def getTask(self):
77         return self._task
78
79     def setTask(self, task):
80         self._task = task
81
82     task = property(getTask, setTask, None, "task property")
83
84 class TaskStarted(TaskBase):
85     """Task execution started"""
86
87 class TaskSucceeded(TaskBase):
88     """Task execution completed"""
89
90 class TaskFailed(TaskBase):
91     """Task execution failed"""
92
93     def __init__(self, task, logfile, metadata):
94         self.logfile = logfile
95         super(TaskFailed, self).__init__(task, metadata)
96
97 class TaskInvalid(TaskBase):
98
99     def __init__(self, task, metadata):
100         super(TaskInvalid, self).__init__(task, metadata)
101         self._message = "No such task '%s'" % task
102
103
104 class LogTee(object):
105     def __init__(self, logger, outfile):
106         self.outfile = outfile
107         self.logger = logger
108         self.name = self.outfile.name
109
110     def write(self, string):
111         self.logger.plain(string)
112         self.outfile.write(string)
113
114     def __enter__(self):
115         self.outfile.__enter__()
116         return self
117
118     def __exit__(self, *excinfo):
119         self.outfile.__exit__(*excinfo)
120
121     def __repr__(self):
122         return '<LogTee {0}>'.format(self.name)
123
124
125 def exec_func(func, d, dirs = None):
126     """Execute an BB 'function'"""
127
128     body = data.getVar(func, d)
129     if not body:
130         if body is None:
131             logger.warn("Function %s doesn't exist", func)
132         return
133
134     flags = data.getVarFlags(func, d)
135     cleandirs = flags.get('cleandirs')
136     if cleandirs:
137         for cdir in data.expand(cleandirs, d).split():
138             bb.utils.remove(cdir, True)
139
140     if dirs is None:
141         dirs = flags.get('dirs')
142         if dirs:
143             dirs = data.expand(dirs, d).split()
144
145     if dirs:
146         for adir in dirs:
147             bb.utils.mkdirhier(adir)
148         adir = dirs[-1]
149     else:
150         adir = data.getVar('B', d, 1)
151         if not os.path.exists(adir):
152             adir = None
153
154     ispython = flags.get('python')
155     fakeroot = flags.get('fakeroot')
156
157     lockflag = flags.get('lockfiles')
158     if lockflag:
159         lockfiles = [data.expand(f, d) for f in lockflag.split()]
160     else:
161         lockfiles = None
162
163     tempdir = data.getVar('T', d, 1)
164     bb.utils.mkdirhier(tempdir)
165     runfile = os.path.join(tempdir, 'run.{0}.{1}'.format(func, os.getpid()))
166
167     with bb.utils.fileslocked(lockfiles):
168         if ispython:
169             exec_func_python(func, d, runfile, cwd=adir)
170         else:
171             exec_func_shell(func, d, runfile, cwd=adir, fakeroot=fakeroot)
172
173 _functionfmt = """
174 def {function}(d):
175 {body}
176
177 {function}(d)
178 """
179 logformatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
180 def exec_func_python(func, d, runfile, cwd=None):
181     """Execute a python BB 'function'"""
182
183     bbfile = d.getVar('FILE', True)
184     code = _functionfmt.format(function=func, body=d.getVar(func, True))
185     bb.utils.mkdirhier(os.path.dirname(runfile))
186     with open(runfile, 'w') as script:
187         script.write(code)
188
189     if cwd:
190         os.chdir(cwd)
191         try:
192             olddir = os.getcwd()
193         except OSError:
194             olddir = None
195
196     try:
197         comp = utils.better_compile(code, func, bbfile)
198         utils.better_exec(comp, {"d": d}, code, bbfile)
199     except:
200         if sys.exc_info()[0] in (bb.parse.SkipPackage, bb.build.FuncFailed):
201             raise
202
203         raise FuncFailed(func, None)
204     finally:
205         if cwd and olddir:
206             os.chdir(olddir)
207
208 def exec_func_shell(function, d, runfile, cwd=None, fakeroot=False):
209     """Execute a shell function from the metadata
210
211     Note on directory behavior.  The 'dirs' varflag should contain a list
212     of the directories you need created prior to execution.  The last
213     item in the list is where we will chdir/cd to.
214     """
215
216     # Don't let the emitted shell script override PWD
217     d.delVarFlag('PWD', 'export')
218
219     with open(runfile, 'w') as script:
220         script.write('#!/bin/sh -e\n')
221         data.emit_func(function, script, d)
222
223         script.write("set -x\n")
224         if cwd:
225             script.write("cd %s\n" % cwd)
226         script.write("%s\n" % function)
227
228     os.chmod(runfile, 0775)
229
230     if fakeroot:
231         cmd = ['fakeroot', runfile]
232     else:
233         cmd = runfile
234
235     if logger.isEnabledFor(logging.DEBUG):
236         logfile = LogTee(logger, sys.stdout)
237     else:
238         logfile = sys.stdout
239
240     try:
241         bb.process.run(cmd, shell=False, stdin=NULL, log=logfile)
242     except bb.process.CmdError:
243         logfn = d.getVar('BB_LOGFILE', True)
244         raise FuncFailed(function, logfn)
245
246 def _task_data(fn, task, d):
247     localdata = data.createCopy(d)
248     localdata.setVar('BB_FILENAME', fn)
249     localdata.setVar('BB_CURRENTTASK', task[3:])
250     localdata.setVar('OVERRIDES', 'task-%s:%s' %
251                      (task[3:], d.getVar('OVERRIDES', False)))
252     localdata.finalize()
253     data.expandKeys(localdata)
254     return localdata
255
256 def _exec_task(fn, task, d):
257     """Execute a BB 'task'
258
259     Execution of a task involves a bit more setup than executing a function,
260     running it with its own local metadata, and with some useful variables set.
261     """
262     if not data.getVarFlag(task, 'task', d):
263         event.fire(TaskInvalid(task, d), d)
264         logger.error("No such task: %s" % task)
265         return 1
266
267     logger.debug(1, "Executing task %s", task)
268
269     localdata = _task_data(fn, task, d)
270     tempdir = localdata.getVar('T', True)
271     if not tempdir:
272         bb.fatal("T variable not set, unable to build")
273
274     bb.utils.mkdirhier(tempdir)
275     loglink = os.path.join(tempdir, 'log.{0}'.format(task))
276     logfn = os.path.join(tempdir, 'log.{0}.{1}'.format(task, os.getpid()))
277     if loglink:
278         bb.utils.remove(loglink)
279
280         try:
281            os.symlink(logfn, loglink)
282         except OSError:
283            pass
284
285     prefuncs = localdata.getVarFlag(task, 'prefuncs', expand=True)
286     postfuncs = localdata.getVarFlag(task, 'postfuncs', expand=True)
287
288     # Handle logfiles
289     si = file('/dev/null', 'r')
290     try:
291         logfile = file(logfn, 'w')
292     except OSError:
293         logger.exception("Opening log file '%s'", logfn)
294         pass
295
296     # Dup the existing fds so we dont lose them
297     osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
298     oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
299     ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
300
301     # Replace those fds with our own
302     os.dup2(si.fileno(), osi[1])
303     os.dup2(logfile.fileno(), oso[1])
304     os.dup2(logfile.fileno(), ose[1])
305
306     # Ensure python logging goes to the logfile
307     handler = logging.StreamHandler(logfile)
308     handler.setFormatter(logformatter)
309     bblogger.addHandler(handler)
310
311     localdata.setVar('BB_LOGFILE', logfn)
312
313     event.fire(TaskStarted(task, localdata), localdata)
314     try:
315         for func in (prefuncs or '').split():
316             exec_func(func, localdata)
317         exec_func(task, localdata)
318         for func in (postfuncs or '').split():
319             exec_func(func, localdata)
320     except FuncFailed as exc:
321         logger.error(str(exc))
322         event.fire(TaskFailed(task, logfn, localdata), localdata)
323         return 1
324     finally:
325         sys.stdout.flush()
326         sys.stderr.flush()
327
328         bblogger.removeHandler(handler)
329
330         # Restore the backup fds
331         os.dup2(osi[0], osi[1])
332         os.dup2(oso[0], oso[1])
333         os.dup2(ose[0], ose[1])
334
335         # Close the backup fds
336         os.close(osi[0])
337         os.close(oso[0])
338         os.close(ose[0])
339         si.close()
340
341         logfile.close()
342         if os.path.exists(logfn) and os.path.getsize(logfn) == 0:
343             logger.debug(2, "Zero size logfn %s, removing", logfn)
344             bb.utils.remove(logfn)
345             bb.utils.remove(loglink)
346     event.fire(TaskSucceeded(task, localdata), localdata)
347
348     if not localdata.getVarFlag(task, 'nostamp') and not localdata.getVarFlag(task, 'selfstamp'):
349         make_stamp(task, localdata)
350
351     return 0
352
353 def exec_task(fn, task, d):
354     try: 
355         return _exec_task(fn, task, d)
356     except Exception:
357         from traceback import format_exc
358         logger.error("Build of %s failed" % (task))
359         logger.error(format_exc())
360         failedevent = TaskFailed(task, None, d)
361         event.fire(failedevent, d)
362         return 1
363
364 def stamp_internal(taskname, d, file_name):
365     """
366     Internal stamp helper function
367     Makes sure the stamp directory exists
368     Returns the stamp path+filename
369
370     In the bitbake core, d can be a CacheData and file_name will be set.
371     When called in task context, d will be a data store, file_name will not be set
372     """
373     taskflagname = taskname
374     if taskname.endswith("_setscene") and taskname != "do_setscene":
375         taskflagname = taskname.replace("_setscene", "")
376
377     if file_name:
378         stamp = d.stamp[file_name]
379         extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
380     else:
381         stamp = d.getVar('STAMP', True)
382         file_name = d.getVar('BB_FILENAME', True)
383         extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info', True) or ""
384
385     if not stamp:
386         return
387
388     stamp = bb.parse.siggen.stampfile(stamp, file_name, taskname, extrainfo)
389
390     bb.utils.mkdirhier(os.path.dirname(stamp))
391
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     # Remove the file and recreate to force timestamp
401     # change on broken NFS filesystems
402     if stamp:
403         bb.utils.remove(stamp)
404         f = open(stamp, "w")
405         f.close()
406
407 def del_stamp(task, d, file_name = None):
408     """
409     Removes a stamp for a given task
410     (d can be a data dict or dataCache)
411     """
412     stamp = stamp_internal(task, d, file_name)
413     bb.utils.remove(stamp)
414
415 def stampfile(taskname, d, file_name = None):
416     """
417     Return the stamp for a given task
418     (d can be a data dict or dataCache)
419     """
420     return stamp_internal(taskname, d, file_name)
421
422 def add_tasks(tasklist, d):
423     task_deps = data.getVar('_task_deps', d)
424     if not task_deps:
425         task_deps = {}
426     if not 'tasks' in task_deps:
427         task_deps['tasks'] = []
428     if not 'parents' in task_deps:
429         task_deps['parents'] = {}
430
431     for task in tasklist:
432         task = data.expand(task, d)
433         data.setVarFlag(task, 'task', 1, d)
434
435         if not task in task_deps['tasks']:
436             task_deps['tasks'].append(task)
437
438         flags = data.getVarFlags(task, d)
439         def getTask(name):
440             if not name in task_deps:
441                 task_deps[name] = {}
442             if name in flags:
443                 deptask = data.expand(flags[name], d)
444                 task_deps[name][task] = deptask
445         getTask('depends')
446         getTask('deptask')
447         getTask('rdeptask')
448         getTask('recrdeptask')
449         getTask('nostamp')
450         getTask('fakeroot')
451         getTask('noexec')
452         task_deps['parents'][task] = []
453         for dep in flags['deps']:
454             dep = data.expand(dep, d)
455             task_deps['parents'][task].append(dep)
456
457     # don't assume holding a reference
458     data.setVar('_task_deps', task_deps, d)
459
460 def remove_task(task, kill, d):
461     """Remove an BB 'task'.
462
463        If kill is 1, also remove tasks that depend on this task."""
464
465     data.delVarFlag(task, 'task', d)