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