build.py: Make sure expandKeys has been called on the data dictonary before running...
[vuplus_bitbake] / lib / bb / build.py
index d6f2e97..7644bf0 100644 (file)
@@ -1,42 +1,33 @@
-#!/usr/bin/env python
 # ex:ts=4:sw=4:sts=4:et
 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
-"""
-BitBake 'Build' implementation
-
-Core code for function execution and task handling in the
-BitBake build tools.
-
-Copyright (C) 2003, 2004  Chris Larson
-
-Based on Gentoo's portage.py.
-
-This program is free software; you can redistribute it and/or modify it under
-the terms of the GNU General Public License as published by the Free Software
-Foundation; either version 2 of the License, or (at your option) any later
-version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License along with
-
-Based on functions from the base bb module, Copyright 2003 Holger Schurig
-"""
-
-from bb import debug, data, fetch, fatal, error, note, event, mkdirhier
+#
+# BitBake 'Build' implementation
+#
+# Core code for function execution and task handling in the
+# BitBake build tools.
+#
+# Copyright (C) 2003, 2004  Chris Larson
+#
+# Based on Gentoo's portage.py.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+#Based on functions from the base bb module, Copyright 2003 Holger Schurig
+
+from bb import data, fetch, event, mkdirhier, utils
 import bb, os
 
-# data holds flags and function name for a given task
-_task_data = data.init()
-
-# graph represents task interdependencies
-_task_graph = bb.digraph()
-
-# stack represents execution order, excepting dependencies
-_task_stack = []
-
 # events
 class FuncFailed(Exception):
     """Executed function failed"""
@@ -45,23 +36,14 @@ class EventException(Exception):
     """Exception which is associated with an Event."""
 
     def __init__(self, msg, event):
-        self.event = event
-
-    def getEvent(self):
-        return self._event
-
-    def setEvent(self, event):
-        self._event = event
-
-    event = property(getEvent, setEvent, None, "event property")
-
+        self.args = msg, event
 
 class TaskBase(event.Event):
     """Base class for task events"""
 
-    def __init__(self, t, d = {}):
-        self.task = t
-        self.data = d
+    def __init__(self, t, d ):
+        self._task = t
+        event.Event.__init__(self, d)
 
     def getTask(self):
         return self._task
@@ -71,14 +53,6 @@ class TaskBase(event.Event):
 
     task = property(getTask, setTask, None, "task property")
 
-    def getData(self):
-        return self._data
-
-    def setData(self, data):
-        self._data = data
-
-    data = property(getData, setData, None, "data property")
-
 class TaskStarted(TaskBase):
     """Task execution started"""
 
@@ -93,13 +67,6 @@ class InvalidTask(TaskBase):
 
 # functions
 
-def init(data):
-    global _task_data, _task_graph, _task_stack
-    _task_data = data.init()
-    _task_graph = bb.digraph()
-    _task_stack = []
-
-
 def exec_func(func, d, dirs = None):
     """Execute an BB 'function'"""
 
@@ -107,10 +74,22 @@ def exec_func(func, d, dirs = None):
     if not body:
         return
 
-    if not dirs:
-        dirs = (data.getVarFlag(func, 'dirs', d) or "").split()
+    flags = data.getVarFlags(func, d)
+    for item in ['deps', 'check', 'interactive', 'python', 'cleandirs', 'dirs', 'lockfiles', 'fakeroot']:
+        if not item in flags:
+            flags[item] = None
+
+    ispython = flags['python']
+
+    cleandirs = (data.expand(flags['cleandirs'], d) or "").split()
+    for cdir in cleandirs:
+        os.system("rm -rf %s" % cdir)
+
+    if dirs:
+        dirs = data.expand(dirs, d)
+    else:
+        dirs = (data.expand(flags['dirs'], d) or "").split()
     for adir in dirs:
-        adir = data.expand(adir, d)
         mkdirhier(adir)
 
     if len(dirs) > 0:
@@ -118,37 +97,47 @@ def exec_func(func, d, dirs = None):
     else:
         adir = data.getVar('B', d, 1)
 
-    adir = data.expand(adir, d)
-
     try:
         prevdir = os.getcwd()
     except OSError:
-        prevdir = data.expand('${TOPDIR}', d)
+        prevdir = data.getVar('TOPDIR', d, True)
     if adir and os.access(adir, os.F_OK):
         os.chdir(adir)
 
-    if data.getVarFlag(func, "python", d):
+    locks = []
+    lockfiles = (data.expand(flags['lockfiles'], d) or "").split()
+    for lock in lockfiles:
+        locks.append(bb.utils.lockfile(lock))
+
+    if flags['python']:
         exec_func_python(func, d)
     else:
-        exec_func_shell(func, d)
-    os.chdir(prevdir)
+        exec_func_shell(func, d, flags)
+
+    for lock in locks:
+        bb.utils.unlockfile(lock)
+
+    if os.path.exists(prevdir):
+        os.chdir(prevdir)
 
 def exec_func_python(func, d):
     """Execute a python BB 'function'"""
     import re, os
 
-    tmp = "def " + func + "():\n%s" % data.getVar(func, d)
-    comp = compile(tmp + '\n' + func + '()', bb.data.getVar('FILE', d, 1) + ':' + func, "exec")
+    bbfile = bb.data.getVar('FILE', d, 1)
+    tmp  = "def " + func + "():\n%s" % data.getVar(func, d)
+    tmp += '\n' + func + '()'
+    comp = utils.better_compile(tmp, func, bbfile)
     prevdir = os.getcwd()
     g = {} # globals
     g['bb'] = bb
     g['os'] = os
     g['d'] = d
-    exec comp in g
+    utils.better_exec(comp, g, tmp, bbfile)
     if os.path.exists(prevdir):
         os.chdir(prevdir)
 
-def exec_func_shell(func, d):
+def exec_func_shell(func, d, flags):
     """Execute a shell BB 'function' Returns true if execution was successful.
 
     For this, it creates a bash shell script in the tmp dectory, writes the local
@@ -160,8 +149,9 @@ def exec_func_shell(func, d):
     """
     import sys
 
-    deps = data.getVarFlag(func, 'deps', d)
-    check = data.getVarFlag(func, 'check', d)
+    deps = flags['deps']
+    check = flags['check']
+    interact = flags['interactive']
     if check in globals():
         if globals()[check](func, deps):
             return
@@ -176,7 +166,7 @@ def exec_func_shell(func, d):
 
     f = open(runfile, "w")
     f.write("#!/bin/sh -e\n")
-    if bb.debug_level > 0: f.write("set -x\n")
+    if bb.msg.debug_level['default'] > 0: f.write("set -x\n")
     data.emit_env(f, d)
 
     f.write("cd %s\n" % os.getcwd())
@@ -184,79 +174,87 @@ def exec_func_shell(func, d):
     f.close()
     os.chmod(runfile, 0775)
     if not func:
-        error("Function not specified")
+        bb.msg.error(bb.msg.domain.Build, "Function not specified")
         raise FuncFailed()
 
     # open logs
     si = file('/dev/null', 'r')
     try:
-        if bb.debug_level > 0:
+        if bb.msg.debug_level['default'] > 0:
             so = os.popen("tee \"%s\"" % logfile, "w")
         else:
             so = file(logfile, 'w')
     except OSError, e:
-        bb.error("opening log file: %s" % e)
+        bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
         pass
 
     se = so
 
-    # dup the existing fds so we dont lose them
-    osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
-    oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
-    ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
+    if not interact:
+        # dup the existing fds so we dont lose them
+        osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
+        oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
+        ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
 
-    # replace those fds with our own
-    os.dup2(si.fileno(), osi[1])
-    os.dup2(so.fileno(), oso[1])
-    os.dup2(se.fileno(), ose[1])
+        # replace those fds with our own
+        os.dup2(si.fileno(), osi[1])
+        os.dup2(so.fileno(), oso[1])
+        os.dup2(se.fileno(), ose[1])
 
     # execute function
     prevdir = os.getcwd()
-    if data.getVarFlag(func, "fakeroot", d):
+    if flags['fakeroot']:
         maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
     else:
         maybe_fakeroot = ''
-    ret = os.system('%ssh -e %s' % (maybe_fakeroot, runfile))
-    os.chdir(prevdir)
+    lang_environment = "LC_ALL=C "
+    ret = os.system('%s%ssh -e %s' % (lang_environment, maybe_fakeroot, runfile))
+    try:
+        os.chdir(prevdir)
+    except:
+        pass
 
-    # restore the backups
-    os.dup2(osi[0], osi[1])
-    os.dup2(oso[0], oso[1])
-    os.dup2(ose[0], ose[1])
+    if not interact:
+        # restore the backups
+        os.dup2(osi[0], osi[1])
+        os.dup2(oso[0], oso[1])
+        os.dup2(ose[0], ose[1])
 
-    # close our logs
-    si.close()
-    so.close()
-    se.close()
+        # close our logs
+        si.close()
+        so.close()
+        se.close()
 
-    # close the backup fds
-    os.close(osi[0])
-    os.close(oso[0])
-    os.close(ose[0])
+        # close the backup fds
+        os.close(osi[0])
+        os.close(oso[0])
+        os.close(ose[0])
 
     if ret==0:
-        if bb.debug_level > 0:
+        if bb.msg.debug_level['default'] > 0:
             os.remove(runfile)
 #            os.remove(logfile)
         return
     else:
-        error("function %s failed" % func)
+        bb.msg.error(bb.msg.domain.Build, "function %s failed" % func)
         if data.getVar("BBINCLUDELOGS", d):
-            error("log data follows (%s)" % logfile)
-            f = open(logfile, "r")
-            while True:
-                l = f.readline()
-                if l == '':
-                    break
-                l = l.rstrip()
-                print '| %s' % l
-            f.close()
+            bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
+            number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
+            if number_of_lines:
+                os.system('tail -n%s %s' % (number_of_lines, logfile))
+            else:
+                f = open(logfile, "r")
+                while True:
+                    l = f.readline()
+                    if l == '':
+                        break
+                    l = l.rstrip()
+                    print '| %s' % l
+                f.close()
         else:
-            error("see log in %s" % logfile)
-        raise FuncFailed()
-
+            bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
+        raise FuncFailed( logfile )
 
-_task_cache = []
 
 def exec_task(task, d):
     """Execute an BB 'task'
@@ -265,147 +263,114 @@ def exec_task(task, d):
        a function is that a task exists in the task digraph, and therefore
        has dependencies amongst other tasks."""
 
-    # check if the task is in the graph..
-    task_graph = data.getVar('_task_graph', d)
-    if not task_graph:
-        task_graph = bb.digraph()
-        data.setVar('_task_graph', task_graph, d)
-    task_cache = data.getVar('_task_cache', d)
-    if not task_cache:
-        task_cache = []
-        data.setVar('_task_cache', task_cache, d)
-    if not task_graph.hasnode(task):
-        raise EventException("", InvalidTask(task, d))
-
-    # check whether this task needs executing..
-    if not data.getVarFlag(task, 'force', d):
-        if stamp_is_current(task, d):
-            return 1
-
-    # follow digraph path up, then execute our way back down
-    def execute(graph, item):
-        if data.getVarFlag(item, 'task', d):
-            if item in task_cache:
-                return 1
-
-            if task != item:
-                # deeper than toplevel, exec w/ deps
-                exec_task(item, d)
-                return 1
-
-            try:
-                debug(1, "Executing task %s" % item)
-                old_overrides = data.getVar('OVERRIDES', d, 0)
-                from copy import deepcopy
-                localdata = deepcopy(d)
-                data.setVar('OVERRIDES', 'task_%s:%s' % (item, old_overrides), localdata)
-                data.update_data(localdata)
-                event.fire(TaskStarted(item, localdata))
-                exec_func(item, localdata)
-                event.fire(TaskSucceeded(item, localdata))
-                task_cache.append(item)
-            except FuncFailed, reason:
-                note( "Task failed: %s" % reason )
-                failedevent = TaskFailed(item, d)
-                event.fire(failedevent)
-                raise EventException(None, failedevent)
-
-    # execute
-    task_graph.walkdown(task, execute)
-
-    # make stamp, or cause event and raise exception
-    if not data.getVarFlag(task, 'nostamp', d):
-        mkstamp(task, d)
-
-
-def stamp_is_current(task, d, checkdeps = 1):
-    """Check status of a given task's stamp. returns 0 if it is not current and needs updating."""
-    task_graph = data.getVar('_task_graph', d)
-    if not task_graph:
-        task_graph = bb.digraph()
-        data.setVar('_task_graph', task_graph, d)
-    stamp = data.getVar('STAMP', d)
-    if not stamp:
-        return 0
-    stampfile = "%s.%s" % (data.expand(stamp, d), task)
-    if not os.access(stampfile, os.F_OK):
-        return 0
-
-    if checkdeps == 0:
-        return 1
-
-    import stat
-    tasktime = os.stat(stampfile)[stat.ST_MTIME]
-
-    _deps = []
-    def checkStamp(graph, task):
-        # check for existance
-        if data.getVarFlag(task, 'nostamp', d):
-            return 1
-
-        if not stamp_is_current(task, d, 0):
-            return 0
-
-        depfile = "%s.%s" % (data.expand(stamp, d), task)
-        deptime = os.stat(depfile)[stat.ST_MTIME]
-        if deptime > tasktime:
-            return 0
-        return 1
-
-    return task_graph.walkdown(task, checkStamp)
+    # Check whther this is a valid task
+    if not data.getVarFlag(task, 'task', d):
+        raise EventException("No such task", InvalidTask(task, d))
 
+    try:
+        bb.msg.debug(1, bb.msg.domain.Build, "Executing task %s" % task)
+        old_overrides = data.getVar('OVERRIDES', d, 0)
+        localdata = data.createCopy(d)
+        data.setVar('OVERRIDES', 'task-%s:%s' % (task[3:], old_overrides), localdata)
+        data.update_data(localdata)
+        data.expandKeys(localdata)
+        event.fire(TaskStarted(task, localdata))
+        exec_func(task, localdata)
+        event.fire(TaskSucceeded(task, localdata))
+    except FuncFailed, reason:
+        bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % reason )
+        failedevent = TaskFailed(task, d)
+        event.fire(failedevent)
+        raise EventException("Function failed in task: %s" % reason, failedevent)
 
-def md5_is_current(task):
-    """Check if a md5 file for a given task is current"""
+    # make stamp, or cause event and raise exception
+    if not data.getVarFlag(task, 'nostamp', d) and not data.getVarFlag(task, 'selfstamp', d):
+        make_stamp(task, d)
 
+def extract_stamp(d, fn):
+    """
+    Extracts stamp format which is either a data dictonary (fn unset) 
+    or a dataCache entry (fn set). 
+    """
+    if fn:
+        return d.stamp[fn]
+    return data.getVar('STAMP', d, 1)
 
-def mkstamp(task, d):
-    """Creates/updates a stamp for a given task"""
-    stamp = data.getVar('STAMP', d)
+def stamp_internal(task, d, file_name):
+    """
+    Internal stamp helper function
+    Removes any stamp for the given task
+    Makes sure the stamp directory exists
+    Returns the stamp path+filename
+    """
+    stamp = extract_stamp(d, file_name)
     if not stamp:
         return
-    stamp = "%s.%s" % (data.expand(stamp, d), task)
+    stamp = "%s.%s" % (stamp, task)
     mkdirhier(os.path.dirname(stamp))
-    open(stamp, "w+")
-
+    # Remove the file and recreate to force timestamp
+    # change on broken NFS filesystems
+    if os.access(stamp, os.F_OK):
+        os.remove(stamp)
+    return stamp
 
-def add_task(task, deps, d):
-    task_graph = data.getVar('_task_graph', d)
-    if not task_graph:
-        task_graph = bb.digraph()
-        data.setVar('_task_graph', task_graph, d)
-    data.setVarFlag(task, 'task', 1, d)
-    task_graph.addnode(task, None)
-    for dep in deps:
-        if not task_graph.hasnode(dep):
-            task_graph.addnode(dep, None)
-        task_graph.addnode(task, dep)
+def make_stamp(task, d, file_name = None):
+    """
+    Creates/updates a stamp for a given task
+    (d can be a data dict or dataCache)
+    """
+    stamp = stamp_internal(task, d, file_name)
+    if stamp:
+        f = open(stamp, "w")
+        f.close()
 
+def del_stamp(task, d, file_name = None):
+    """
+    Removes a stamp for a given task
+    (d can be a data dict or dataCache)
+    """
+    stamp_internal(task, d, file_name)
+
+def add_tasks(tasklist, d):
+    task_deps = data.getVar('_task_deps', d)
+    if not task_deps:
+        task_deps = {}
+    if not 'tasks' in task_deps:
+        task_deps['tasks'] = []
+    if not 'parents' in task_deps:
+        task_deps['parents'] = {}
+
+    for task in tasklist:
+        task = data.expand(task, d)
+        data.setVarFlag(task, 'task', 1, d)
+
+        if not task in task_deps['tasks']:
+            task_deps['tasks'].append(task)
+
+        flags = data.getVarFlags(task, d)    
+        def getTask(name):
+            if not name in task_deps:
+                task_deps[name] = {}
+            if name in flags:
+                deptask = data.expand(flags[name], d)
+                task_deps[name][task] = deptask
+        getTask('depends')
+        getTask('deptask')
+        getTask('rdeptask')
+        getTask('recrdeptask')
+        getTask('nostamp')
+        task_deps['parents'][task] = []
+        for dep in flags['deps']:
+            dep = data.expand(dep, d)
+            task_deps['parents'][task].append(dep)
+
+    # don't assume holding a reference
+    data.setVar('_task_deps', task_deps, d)
 
 def remove_task(task, kill, d):
     """Remove an BB 'task'.
 
        If kill is 1, also remove tasks that depend on this task."""
 
-    task_graph = data.getVar('_task_graph', d)
-    if not task_graph:
-        task_graph = bb.digraph()
-        data.setVar('_task_graph', task_graph, d)
-    if not task_graph.hasnode(task):
-        return
-
     data.delVarFlag(task, 'task', d)
-    ref = 1
-    if kill == 1:
-        ref = 2
-    task_graph.delnode(task, ref)
-
-def task_exists(task, d):
-    task_graph = data.getVar('_task_graph', d)
-    if not task_graph:
-        task_graph = bb.digraph()
-        data.setVar('_task_graph', task_graph, d)
-    return task_graph.hasnode(task)
-
-def get_task_data():
-    return _task_data
+