# Part of the A-A-P recipe executive: Dependency rules

# Copyright (C) 2002-2003 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING


# A Depend object contains:
#  targetlist   - dictlist of targets
#  build_attr   - dictlist of build attributes (right after the ":")
#  sourcelist   - dictlist of sources
#  rpstack      - RecPos stack for where the commands were defined
#  commands     - string of command lines
#  builddir     - directory where "commands" are to be executed
#  buildrecdict - the recdict of the recipe where it was defined or None
#  use_recdict  - recdict to be used when executing build commands
#  matchstr     - for a rule: the string that matched %
#  startup      - non-zero for a dependency defined in a startup recipe
#
# Illustration:
#       targetlist : {build_attr} sourcelist
#               commands

import os
import os.path
import string

from Error import *
from Util import *
from Message import *

class Depend:
    def __init__(self, targetlist, build_attr, sourcelist,
                     work, rpstack, commands, builddir = None, recdict = None):
        self.targetlist = targetlist
        self.build_attr = build_attr
        self.sourcelist = sourcelist
        self.rpstack = rpstack

        self.commands = commands
        if builddir is None:
            self.builddir = os.getcwd()
        else:
            self.builddir = builddir
        self.buildrecdict = recdict
        self.keep_current_scope = 0     # Current scope overrules scope of
                                        # build commands; used for rules and
                                        # actions.
        self.use_recdict = None
        self.matchstr = ''
        self.startup = 0
        self.in_use = 0

        # Add nodes for all sources and targets, with a pointer back to the
        # node.  Also carries over the attributes to the node.
        work.dictlist_nodes(self.targetlist)
        work.dictlist_nodes(self.sourcelist)

    def __str__(self):
        from Dictlist import dictlist2str, dictlistattr2str

        return (dictlist2str(self.targetlist)
                    + " : "
                    + dictlistattr2str(self.build_attr)
                    + dictlist2str(self.sourcelist)
                    + "\n" + self.commands)

    def get_scope_names(self, work):
        """
        Get the list of scope names to be used for build commands.  First the
        one specified as build attribute.  A scope specified for a source
        is appended.
        """
        # Attribute on dependency itself.
        xscope = self.build_attr.get("scope")
        if xscope:
            ret = [ xscope ]
        else:
            ret = []

        for s in self.sourcelist:
            if s.get("scope"):
                # Attribute on source mentioned for dependency.
                ret.append(s["scope"])
            else:
                node = work.find_node(s["name"])
                if node and node.attributes.get("scope"):
                    # Attribute on source node.
                    ret.append(node.attributes["scope"])
        return ret


def depend_auto(work, recdict, node, node_dict, level):
    """
    Find the implied dependencies for "node".
    "node_dict" contains attributes specified for the node specifically for the
    current dependency.  They overrule attributes from the node itself.
    The dependencies are returned in node.autodep_dictlist.
    If "node" changed since last time, regenerate the dependencies.
    """
    # Previously we returned quickly when the automatic dependencies were
    # already generated, because we cannot be sure they are still up-to-date.
    # Changed attributes might matter.

    # Don't generate automatic dependencies when "AUTODEPEND" is "off".
    if (recdict["_no"].get("AUTODEPEND") == "off"
            or (node.attributes.get("autodepend") == "off"
                or node_dict.get("autodepend") == "off")):
        node.autodep_dictlist = []
        return

    # Get the file type.
    ftype = node_dict.get("filetype")
    if not ftype:
        ftype = node.get_ftype(recdict)
    if not ftype:
        msg_depend(recdict,
                        _('Unknown type of file, no dependency check for "%s"')
                                                    % node.short_name(), level)
        node.autodep_dictlist = []
        return

    # A compressed or ".in" file is not checked.
    if ftype == "ignore":
        node.autodep_dictlist = []
        return

    # Trigger the rule to produce a dependency recipe for this node.
    # This will also update node.autodep_dictlist when node.autodep_recursive
    # is set.
    from DoBuild import build_autodepend
    recipe = build_autodepend(work, recdict, ftype, node, node_dict, level)

    # return silently when no dependencies could be generated
    if not recipe:
        node.autodep_dictlist = []
        return

    if not os.path.exists(recipe.name):
        msg_warning(recdict,
                      _('Dependency file was not created: "%s"') % recipe.name)
        node.autodep_dictlist = []
        return

    # Read the generated recipe file when not done already.
    if not node.autodep_recursive:
        node.autodep_dictlist = read_auto_depend(recdict, recipe,
                                                               node.get_name())


def read_auto_depend(recdict, recipe, skipname):
    """
    Read a generated recipe file from node "recipe".
    We only want the part after the ":", the item before it may be wrong
    (gcc generates foo.o: foo.c foo.h).
    Ignore "skipname".
    Don't read the recipe as a normal recipe, that would cause trouble with
    things we don't want to find in there. 
    """
    try:
        fd = open(recipe.name)
    except:
        msg_error(recdict, _('Cannot open "%s" for reading.') % recipe.name)
        return []

    from ParsePos import ParsePos
    from RecPos import RecPos
    rpstack = [ RecPos(recipe.name) ]

    # create an object to contain the file position
    fp = ParsePos(rpstack, file = fd)
    fp.nextline()           # read the first (and only) line
    if fp.line is None:
        msg_depend(recdict, _('Nothing to read from "%s".') % recipe.name)
        return []
    i = string.find(fp.line, ":")
    if os.name != "posix" and i == 1:       # DOS filename: "C:\dir\foo.c"
        i = string.find(fp.line, ":", 2)
    if i < 0:
        msg_error(recdict, _('No colon found in "%s".') % recipe.name)
        return []

    autodep_dictlist = []
    if i + 1 < fp.line_len:
        # Need to convert names with backslash-space to quoted name.
        nl = ''
        i = i + 1
        item = ''
        had_bsl = 0
        had_q = 0
        line_len = len(fp.line)
        while 1:
            if i >= line_len or is_white(fp.line[i]):
                if item:
                    # End of an item, append it to "nl" with or without quotes.
                    if nl:
                        nl = nl + ' '
                    if had_bsl == 1:
                        nl = nl + enquote(item)
                    else:
                        nl = nl + item
                if i >= line_len:
                    break
                had_bsl = 0
                had_q = 0
                item = ''
            elif (fp.line[i] == '\\' and i + 1 < line_len
                                   and is_white(fp.line[i + 1]) and not had_q):
                i = i + 1
                had_bsl = 1     # found a backslashed space or tab
                item = item + fp.line[i]
            elif fp.line[i] == '"' or fp.line[i] == "'":
                had_q = 1       # don't recognize backslash after a quote
                item = item + fp.line[i]
            else:
                item = item + fp.line[i]
            i = i + 1

        from Dictlist import str2dictlist
        autodep_dictlist = str2dictlist(rpstack, nl)

        # Make the path absolute (that's faster, SRCPATH won't be used).
        # Remove the node itself.
        for k in autodep_dictlist[:]:
            k["name"] = os.path.abspath(k["name"])
            if k["name"] == skipname:
                autodep_dictlist.remove(k)

    # Check for trailing text.
    fp.nextline()
    if not fp.line is None:
        msg_note(recdict, _('Found trailing text in "%s"') % recipe.name)

    fd.close()
    return autodep_dictlist



# vim: set sw=4 et sts=4 tw=79 fo+=l:
