summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/build.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/build.py')
-rw-r--r--bitbake/lib/bb/build.py667
1 files changed, 667 insertions, 0 deletions
diff --git a/bitbake/lib/bb/build.py b/bitbake/lib/bb/build.py
new file mode 100644
index 0000000000..2e49a09365
--- /dev/null
+++ b/bitbake/lib/bb/build.py
@@ -0,0 +1,667 @@
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
28import os
29import sys
30import logging
31import shlex
32import glob
33import bb
34import bb.msg
35import bb.process
36from contextlib import nested
37from bb import event, utils
38
39bblogger = logging.getLogger('BitBake')
40logger = logging.getLogger('BitBake.Build')
41
42NULL = open(os.devnull, 'r+')
43
44
45# When we execute a python function we'd like certain things
46# in all namespaces, hence we add them to __builtins__
47# If we do not do this and use the exec globals, they will
48# not be available to subfunctions.
49__builtins__['bb'] = bb
50__builtins__['os'] = os
51
52class FuncFailed(Exception):
53 def __init__(self, name = None, logfile = None):
54 self.logfile = logfile
55 self.name = name
56 if name:
57 self.msg = 'Function failed: %s' % name
58 else:
59 self.msg = "Function failed"
60
61 def __str__(self):
62 if self.logfile and os.path.exists(self.logfile):
63 msg = ("%s (log file is located at %s)" %
64 (self.msg, self.logfile))
65 else:
66 msg = self.msg
67 return msg
68
69class TaskBase(event.Event):
70 """Base class for task events"""
71
72 def __init__(self, t, logfile, d):
73 self._task = t
74 self._package = d.getVar("PF", True)
75 self.taskfile = d.getVar("FILE", True)
76 self.taskname = self._task
77 self.logfile = logfile
78 event.Event.__init__(self)
79 self._message = "recipe %s: task %s: %s" % (d.getVar("PF", True), t, self.getDisplayName())
80
81 def getTask(self):
82 return self._task
83
84 def setTask(self, task):
85 self._task = task
86
87 def getDisplayName(self):
88 return bb.event.getName(self)[4:]
89
90 task = property(getTask, setTask, None, "task property")
91
92class TaskStarted(TaskBase):
93 """Task execution started"""
94
95class TaskSucceeded(TaskBase):
96 """Task execution completed"""
97
98class TaskFailed(TaskBase):
99 """Task execution failed"""
100
101 def __init__(self, task, logfile, metadata, errprinted = False):
102 self.errprinted = errprinted
103 super(TaskFailed, self).__init__(task, logfile, metadata)
104
105class TaskFailedSilent(TaskBase):
106 """Task execution failed (silently)"""
107 def getDisplayName(self):
108 # Don't need to tell the user it was silent
109 return "Failed"
110
111class TaskInvalid(TaskBase):
112
113 def __init__(self, task, metadata):
114 super(TaskInvalid, self).__init__(task, None, metadata)
115 self._message = "No such task '%s'" % task
116
117
118class LogTee(object):
119 def __init__(self, logger, outfile):
120 self.outfile = outfile
121 self.logger = logger
122 self.name = self.outfile.name
123
124 def write(self, string):
125 self.logger.plain(string)
126 self.outfile.write(string)
127
128 def __enter__(self):
129 self.outfile.__enter__()
130 return self
131
132 def __exit__(self, *excinfo):
133 self.outfile.__exit__(*excinfo)
134
135 def __repr__(self):
136 return '<LogTee {0}>'.format(self.name)
137 def flush(self):
138 self.outfile.flush()
139
140def exec_func(func, d, dirs = None):
141 """Execute an BB 'function'"""
142
143 body = d.getVar(func)
144 if not body:
145 if body is None:
146 logger.warn("Function %s doesn't exist", func)
147 return
148
149 flags = d.getVarFlags(func)
150 cleandirs = flags.get('cleandirs')
151 if cleandirs:
152 for cdir in d.expand(cleandirs).split():
153 bb.utils.remove(cdir, True)
154
155 if dirs is None:
156 dirs = flags.get('dirs')
157 if dirs:
158 dirs = d.expand(dirs).split()
159
160 if dirs:
161 for adir in dirs:
162 bb.utils.mkdirhier(adir)
163 adir = dirs[-1]
164 else:
165 adir = d.getVar('B', True)
166 bb.utils.mkdirhier(adir)
167
168 ispython = flags.get('python')
169
170 lockflag = flags.get('lockfiles')
171 if lockflag:
172 lockfiles = [d.expand(f) for f in lockflag.split()]
173 else:
174 lockfiles = None
175
176 tempdir = d.getVar('T', True)
177
178 # or func allows items to be executed outside of the normal
179 # task set, such as buildhistory
180 task = d.getVar('BB_RUNTASK', True) or func
181 if task == func:
182 taskfunc = task
183 else:
184 taskfunc = "%s.%s" % (task, func)
185
186 runfmt = d.getVar('BB_RUNFMT', True) or "run.{func}.{pid}"
187 runfn = runfmt.format(taskfunc=taskfunc, task=task, func=func, pid=os.getpid())
188 runfile = os.path.join(tempdir, runfn)
189 bb.utils.mkdirhier(os.path.dirname(runfile))
190
191 # Setup the courtesy link to the runfn, only for tasks
192 # we create the link 'just' before the run script is created
193 # if we create it after, and if the run script fails, then the
194 # link won't be created as an exception would be fired.
195 if task == func:
196 runlink = os.path.join(tempdir, 'run.{0}'.format(task))
197 if runlink:
198 bb.utils.remove(runlink)
199
200 try:
201 os.symlink(runfn, runlink)
202 except OSError:
203 pass
204
205 with bb.utils.fileslocked(lockfiles):
206 if ispython:
207 exec_func_python(func, d, runfile, cwd=adir)
208 else:
209 exec_func_shell(func, d, runfile, cwd=adir)
210
211_functionfmt = """
212def {function}(d):
213{body}
214
215{function}(d)
216"""
217logformatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
218def exec_func_python(func, d, runfile, cwd=None):
219 """Execute a python BB 'function'"""
220
221 bbfile = d.getVar('FILE', True)
222 code = _functionfmt.format(function=func, body=d.getVar(func, True))
223 bb.utils.mkdirhier(os.path.dirname(runfile))
224 with open(runfile, 'w') as script:
225 script.write(code)
226
227 if cwd:
228 try:
229 olddir = os.getcwd()
230 except OSError:
231 olddir = None
232 os.chdir(cwd)
233
234 bb.debug(2, "Executing python function %s" % func)
235
236 try:
237 comp = utils.better_compile(code, func, bbfile)
238 utils.better_exec(comp, {"d": d}, code, bbfile)
239 except:
240 if sys.exc_info()[0] in (bb.parse.SkipPackage, bb.build.FuncFailed):
241 raise
242
243 raise FuncFailed(func, None)
244 finally:
245 bb.debug(2, "Python function %s finished" % func)
246
247 if cwd and olddir:
248 try:
249 os.chdir(olddir)
250 except OSError:
251 pass
252
253def exec_func_shell(func, d, runfile, cwd=None):
254 """Execute a shell function from the metadata
255
256 Note on directory behavior. The 'dirs' varflag should contain a list
257 of the directories you need created prior to execution. The last
258 item in the list is where we will chdir/cd to.
259 """
260
261 # Don't let the emitted shell script override PWD
262 d.delVarFlag('PWD', 'export')
263
264 with open(runfile, 'w') as script:
265 script.write('''#!/bin/sh\n
266# Emit a useful diagnostic if something fails:
267bb_exit_handler() {
268 ret=$?
269 case $ret in
270 0) ;;
271 *) case $BASH_VERSION in
272 "") echo "WARNING: exit code $ret from a shell command.";;
273 *) echo "WARNING: ${BASH_SOURCE[0]}:${BASH_LINENO[0]} exit $ret from
274 \"$BASH_COMMAND\"";;
275 esac
276 exit $ret
277 esac
278}
279trap 'bb_exit_handler' 0
280set -e
281''')
282
283 bb.data.emit_func(func, script, d)
284
285 if bb.msg.loggerVerboseLogs:
286 script.write("set -x\n")
287 if cwd:
288 script.write("cd %s\n" % cwd)
289 script.write("%s\n" % func)
290 script.write('''
291# cleanup
292ret=$?
293trap '' 0
294exit $?
295''')
296
297 os.chmod(runfile, 0775)
298
299 cmd = runfile
300 if d.getVarFlag(func, 'fakeroot'):
301 fakerootcmd = d.getVar('FAKEROOT', True)
302 if fakerootcmd:
303 cmd = [fakerootcmd, runfile]
304
305 if bb.msg.loggerDefaultVerbose:
306 logfile = LogTee(logger, sys.stdout)
307 else:
308 logfile = sys.stdout
309
310 bb.debug(2, "Executing shell function %s" % func)
311
312 try:
313 with open(os.devnull, 'r+') as stdin:
314 bb.process.run(cmd, shell=False, stdin=stdin, log=logfile)
315 except bb.process.CmdError:
316 logfn = d.getVar('BB_LOGFILE', True)
317 raise FuncFailed(func, logfn)
318
319 bb.debug(2, "Shell function %s finished" % func)
320
321def _task_data(fn, task, d):
322 localdata = bb.data.createCopy(d)
323 localdata.setVar('BB_FILENAME', fn)
324 localdata.setVar('BB_CURRENTTASK', task[3:])
325 localdata.setVar('OVERRIDES', 'task-%s:%s' %
326 (task[3:], d.getVar('OVERRIDES', False)))
327 localdata.finalize()
328 bb.data.expandKeys(localdata)
329 return localdata
330
331def _exec_task(fn, task, d, quieterr):
332 """Execute a BB 'task'
333
334 Execution of a task involves a bit more setup than executing a function,
335 running it with its own local metadata, and with some useful variables set.
336 """
337 if not d.getVarFlag(task, 'task'):
338 event.fire(TaskInvalid(task, d), d)
339 logger.error("No such task: %s" % task)
340 return 1
341
342 logger.debug(1, "Executing task %s", task)
343
344 localdata = _task_data(fn, task, d)
345 tempdir = localdata.getVar('T', True)
346 if not tempdir:
347 bb.fatal("T variable not set, unable to build")
348
349 # Change nice level if we're asked to
350 nice = localdata.getVar("BB_TASK_NICE_LEVEL", True)
351 if nice:
352 curnice = os.nice(0)
353 nice = int(nice) - curnice
354 newnice = os.nice(nice)
355 logger.debug(1, "Renice to %s " % newnice)
356
357 bb.utils.mkdirhier(tempdir)
358
359 # Determine the logfile to generate
360 logfmt = localdata.getVar('BB_LOGFMT', True) or 'log.{task}.{pid}'
361 logbase = logfmt.format(task=task, pid=os.getpid())
362
363 # Document the order of the tasks...
364 logorder = os.path.join(tempdir, 'log.task_order')
365 try:
366 with open(logorder, 'a') as logorderfile:
367 logorderfile.write('{0} ({1}): {2}\n'.format(task, os.getpid(), logbase))
368 except OSError:
369 logger.exception("Opening log file '%s'", logorder)
370 pass
371
372 # Setup the courtesy link to the logfn
373 loglink = os.path.join(tempdir, 'log.{0}'.format(task))
374 logfn = os.path.join(tempdir, logbase)
375 if loglink:
376 bb.utils.remove(loglink)
377
378 try:
379 os.symlink(logbase, loglink)
380 except OSError:
381 pass
382
383 prefuncs = localdata.getVarFlag(task, 'prefuncs', expand=True)
384 postfuncs = localdata.getVarFlag(task, 'postfuncs', expand=True)
385
386 class ErrorCheckHandler(logging.Handler):
387 def __init__(self):
388 self.triggered = False
389 logging.Handler.__init__(self, logging.ERROR)
390 def emit(self, record):
391 self.triggered = True
392
393 # Handle logfiles
394 si = open('/dev/null', 'r')
395 try:
396 bb.utils.mkdirhier(os.path.dirname(logfn))
397 logfile = open(logfn, 'w')
398 except OSError:
399 logger.exception("Opening log file '%s'", logfn)
400 pass
401
402 # Dup the existing fds so we dont lose them
403 osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
404 oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
405 ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
406
407 # Replace those fds with our own
408 os.dup2(si.fileno(), osi[1])
409 os.dup2(logfile.fileno(), oso[1])
410 os.dup2(logfile.fileno(), ose[1])
411
412 # Ensure python logging goes to the logfile
413 handler = logging.StreamHandler(logfile)
414 handler.setFormatter(logformatter)
415 # Always enable full debug output into task logfiles
416 handler.setLevel(logging.DEBUG - 2)
417 bblogger.addHandler(handler)
418
419 errchk = ErrorCheckHandler()
420 bblogger.addHandler(errchk)
421
422 localdata.setVar('BB_LOGFILE', logfn)
423 localdata.setVar('BB_RUNTASK', task)
424
425 event.fire(TaskStarted(task, logfn, localdata), localdata)
426 try:
427 for func in (prefuncs or '').split():
428 exec_func(func, localdata)
429 exec_func(task, localdata)
430 for func in (postfuncs or '').split():
431 exec_func(func, localdata)
432 except FuncFailed as exc:
433 if quieterr:
434 event.fire(TaskFailedSilent(task, logfn, localdata), localdata)
435 else:
436 errprinted = errchk.triggered
437 logger.error(str(exc))
438 event.fire(TaskFailed(task, logfn, localdata, errprinted), localdata)
439 return 1
440 finally:
441 sys.stdout.flush()
442 sys.stderr.flush()
443
444 bblogger.removeHandler(handler)
445
446 # Restore the backup fds
447 os.dup2(osi[0], osi[1])
448 os.dup2(oso[0], oso[1])
449 os.dup2(ose[0], ose[1])
450
451 # Close the backup fds
452 os.close(osi[0])
453 os.close(oso[0])
454 os.close(ose[0])
455 si.close()
456
457 logfile.close()
458 if os.path.exists(logfn) and os.path.getsize(logfn) == 0:
459 logger.debug(2, "Zero size logfn %s, removing", logfn)
460 bb.utils.remove(logfn)
461 bb.utils.remove(loglink)
462 event.fire(TaskSucceeded(task, logfn, localdata), localdata)
463
464 if not localdata.getVarFlag(task, 'nostamp') and not localdata.getVarFlag(task, 'selfstamp'):
465 make_stamp(task, localdata)
466
467 return 0
468
469def exec_task(fn, task, d, profile = False):
470 try:
471 quieterr = False
472 if d.getVarFlag(task, "quieterrors") is not None:
473 quieterr = True
474
475 if profile:
476 profname = "profile-%s.log" % (d.getVar("PN", True) + "-" + task)
477 try:
478 import cProfile as profile
479 except:
480 import profile
481 prof = profile.Profile()
482 ret = profile.Profile.runcall(prof, _exec_task, fn, task, d, quieterr)
483 prof.dump_stats(profname)
484 bb.utils.process_profilelog(profname)
485
486 return ret
487 else:
488 return _exec_task(fn, task, d, quieterr)
489
490 except Exception:
491 from traceback import format_exc
492 if not quieterr:
493 logger.error("Build of %s failed" % (task))
494 logger.error(format_exc())
495 failedevent = TaskFailed(task, None, d, True)
496 event.fire(failedevent, d)
497 return 1
498
499def stamp_internal(taskname, d, file_name):
500 """
501 Internal stamp helper function
502 Makes sure the stamp directory exists
503 Returns the stamp path+filename
504
505 In the bitbake core, d can be a CacheData and file_name will be set.
506 When called in task context, d will be a data store, file_name will not be set
507 """
508 taskflagname = taskname
509 if taskname.endswith("_setscene") and taskname != "do_setscene":
510 taskflagname = taskname.replace("_setscene", "")
511
512 if file_name:
513 stamp = d.stamp_base[file_name].get(taskflagname) or d.stamp[file_name]
514 extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
515 else:
516 stamp = d.getVarFlag(taskflagname, 'stamp-base', True) or d.getVar('STAMP', True)
517 file_name = d.getVar('BB_FILENAME', True)
518 extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info', True) or ""
519
520 if not stamp:
521 return
522
523 stamp = bb.parse.siggen.stampfile(stamp, file_name, taskname, extrainfo)
524
525 stampdir = os.path.dirname(stamp)
526 if bb.parse.cached_mtime_noerror(stampdir) == 0:
527 bb.utils.mkdirhier(stampdir)
528
529 return stamp
530
531def stamp_cleanmask_internal(taskname, d, file_name):
532 """
533 Internal stamp helper function to generate stamp cleaning mask
534 Returns the stamp path+filename
535
536 In the bitbake core, d can be a CacheData and file_name will be set.
537 When called in task context, d will be a data store, file_name will not be set
538 """
539 taskflagname = taskname
540 if taskname.endswith("_setscene") and taskname != "do_setscene":
541 taskflagname = taskname.replace("_setscene", "")
542
543 if file_name:
544 stamp = d.stamp_base_clean[file_name].get(taskflagname) or d.stampclean[file_name]
545 extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
546 else:
547 stamp = d.getVarFlag(taskflagname, 'stamp-base-clean', True) or d.getVar('STAMPCLEAN', True)
548 file_name = d.getVar('BB_FILENAME', True)
549 extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info', True) or ""
550
551 if not stamp:
552 return []
553
554 cleanmask = bb.parse.siggen.stampcleanmask(stamp, file_name, taskname, extrainfo)
555
556 return [cleanmask, cleanmask.replace(taskflagname, taskflagname + "_setscene")]
557
558def make_stamp(task, d, file_name = None):
559 """
560 Creates/updates a stamp for a given task
561 (d can be a data dict or dataCache)
562 """
563 cleanmask = stamp_cleanmask_internal(task, d, file_name)
564 for mask in cleanmask:
565 for name in glob.glob(mask):
566 # Preserve sigdata files in the stamps directory
567 if "sigdata" in name:
568 continue
569 # Preserve taint files in the stamps directory
570 if name.endswith('.taint'):
571 continue
572 os.unlink(name)
573
574 stamp = stamp_internal(task, d, file_name)
575 # Remove the file and recreate to force timestamp
576 # change on broken NFS filesystems
577 if stamp:
578 bb.utils.remove(stamp)
579 open(stamp, "w").close()
580
581 # If we're in task context, write out a signature file for each task
582 # as it completes
583 if not task.endswith("_setscene") and task != "do_setscene" and not file_name:
584 file_name = d.getVar('BB_FILENAME', True)
585 bb.parse.siggen.dump_sigtask(file_name, task, d.getVar('STAMP', True), True)
586
587def del_stamp(task, d, file_name = None):
588 """
589 Removes a stamp for a given task
590 (d can be a data dict or dataCache)
591 """
592 stamp = stamp_internal(task, d, file_name)
593 bb.utils.remove(stamp)
594
595def write_taint(task, d, file_name = None):
596 """
597 Creates a "taint" file which will force the specified task and its
598 dependents to be re-run the next time by influencing the value of its
599 taskhash.
600 (d can be a data dict or dataCache)
601 """
602 import uuid
603 if file_name:
604 taintfn = d.stamp[file_name] + '.' + task + '.taint'
605 else:
606 taintfn = d.getVar('STAMP', True) + '.' + task + '.taint'
607 bb.utils.mkdirhier(os.path.dirname(taintfn))
608 # The specific content of the taint file is not really important,
609 # we just need it to be random, so a random UUID is used
610 with open(taintfn, 'w') as taintf:
611 taintf.write(str(uuid.uuid4()))
612
613def stampfile(taskname, d, file_name = None):
614 """
615 Return the stamp for a given task
616 (d can be a data dict or dataCache)
617 """
618 return stamp_internal(taskname, d, file_name)
619
620def add_tasks(tasklist, d):
621 task_deps = d.getVar('_task_deps')
622 if not task_deps:
623 task_deps = {}
624 if not 'tasks' in task_deps:
625 task_deps['tasks'] = []
626 if not 'parents' in task_deps:
627 task_deps['parents'] = {}
628
629 for task in tasklist:
630 task = d.expand(task)
631 d.setVarFlag(task, 'task', 1)
632
633 if not task in task_deps['tasks']:
634 task_deps['tasks'].append(task)
635
636 flags = d.getVarFlags(task)
637 def getTask(name):
638 if not name in task_deps:
639 task_deps[name] = {}
640 if name in flags:
641 deptask = d.expand(flags[name])
642 task_deps[name][task] = deptask
643 getTask('depends')
644 getTask('rdepends')
645 getTask('deptask')
646 getTask('rdeptask')
647 getTask('recrdeptask')
648 getTask('recideptask')
649 getTask('nostamp')
650 getTask('fakeroot')
651 getTask('noexec')
652 getTask('umask')
653 task_deps['parents'][task] = []
654 if 'deps' in flags:
655 for dep in flags['deps']:
656 dep = d.expand(dep)
657 task_deps['parents'][task].append(dep)
658
659 # don't assume holding a reference
660 d.setVar('_task_deps', task_deps)
661
662def remove_task(task, kill, d):
663 """Remove an BB 'task'.
664
665 If kill is 1, also remove tasks that depend on this task."""
666
667 d.delVarFlag(task, 'task')