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

# Copyright (C) 2002 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	- recdict of a child recipe 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 *
import Filetype
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.use_recdict = None
	self.matchstr = ''
	self.startup = 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 depend_auto(work, recdict, node, level):
    """Find the implied dependencies for "node".  They are returned in
       node.auto_depend.
       If "node" changed since last time, regenerate the dependencies."""
    # Return quickly when the automatic dependencies were already generated.
    if not node.auto_depend is None:
	return

    # Don't generate automatic dependencies when "autodepend" is "off".
    if ((recdict.has_key("autodepend") and recdict["autodepend"] == "off")
	    or (node.attributes.has_key("autodepend")
		and node.attributes["autodepend"] == "off")):
	node.auto_depend = []
	return

    # Get the file type.
    node_name = node.get_name()
    if node.attributes.has_key("filetype"):
	ftype = node.attributes["filetype"]
    else:
	ftype = Filetype.ft_detect(node_name, recdict = recdict)
	if not ftype:
	    msg_depend(recdict,
		    _('Unknown type of file, no dependency check for "%s"')
						    % node.short_name(), level)
	    node.auto_depend = []
	    return

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

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

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

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

    # Read the generated recipe file when not done already.
    if not node.auto_depend_rec:
	node.auto_depend = read_auto_depend(recdict, recipe, node_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:
	file = 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 = file)
    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 []

    auto_depend = []
    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
	auto_depend = str2dictlist(rpstack, nl)

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

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

    file.close()
    return auto_depend



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