# Part of the A-A-P recipe executive: remember the work specified in the recipe

# 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


# Currently a Work object contains these items:
#  recdict      -  Dictionary of global variables from the toplevel recipe.
#                  The "_work" variable points back to the Work object.
#  dependencies -  list of dependencies
#  rules        -  list of rules
#  nodes        -  dictionary of nodes from the recipes; index is node.name;
#		   normally used to lookup virtual nodes
#  absnodes	-  same contents as nodes, but key is the absolute name:
#		   node.absname
#  top_recipe	-  toplevel recipe used (None when not reading a recipe)

import os
import os.path
import string

from Node import Node
from RecPos import rpcopy
import Global
from Util import *
from Message import *
from Remote import is_url
from Version import *

class Work:
    def __init__(self, recdict = None):
	if recdict is None:
	    self.recdict = {"_locals": {}, "_assigned": {}, "_globals": {}}
	else:
	    self.recdict = recdict
	self.dependencies = []
	self.rules = []
	self.nodes = {}
	self.absnodes = {}
	self.top_recipe = None

	# This makes it possible to find "work" from the global variables.
	setwork(self.recdict, self)
	rd = self.recdict

	#
	# Add the default variables.
	#
	from Dictlist import listitem2str, dictlist2str

	# $VERSIONSTR
	rd["VERSIONSTR"] = version_string

	# $HOME
	if os.environ.has_key("HOME"):
	    rd["HOME"] = listitem2str(os.environ["HOME"])
	else:
	    rd["HOME"] = ''
	
	# $CACHE
	cache = []
	if os.path.exists("/var/aap/cache"):
	    cache.append({"name" : "/var/aap/cache"})
	if os.environ.has_key("HOME"):
	    if os.name == 'nt' or os.name == 'dos' or os.name == 'os2':
		n = in_aap_dir("cache")
	    else:
		n = ".aap/cache"
	    cache.append({"name" :
			    listitem2str(os.path.join(os.environ["HOME"], n))})
	cache.append({"name" : in_aap_dir("cache")})
	rd["CACHE"] = dictlist2str(cache)

	# $BDIR
	if os.name == "posix":
	    def fixname(n):
		"""Change all non-letters, non-digits in "n" to underscores and
		return the result."""
		s = ''
		for c in n:
		    if c in string.letters + string.digits:
			s = s + c
		    else:
			s = s + '_'
		return s

	    sysname, n, release, v, m = os.uname()
	    rd["BDIR"] = ("build-" + fixname(sysname) + fixname(release))
	else:
	    rd["BDIR"] = "build-" + os.name

	# $OSTYPE
	n = os.name
	if os.name == "dos":
	    n = "msdos"
	elif os.name == "nt":
	    n = "mswin"
	rd["OSTYPE"] = n

	# $MESSAGE
	msg_init(rd)

	# $SRCPATH (to be evaluated when used)
	rd["SRCPATH"] = ". $BDIR"
	rd["$SRCPATH"] = 1

	# Standard variables.
	rd["bar"] = '|'
	rd["br"] = '\n'
	rd["empty"] = ''
	rd["gt"] = '>'
	rd["lt"] = '<'
	rd["pipe"] = '|'

	# Variables for a port recipe.
	rd["DISTDIR"] = "distfiles"
	rd["PATCHDISTDIR"] = "patches"
	rd["WRKDIR"] = "work"

	# $cache_update
	rd["cache_update"] = "12 hour"

	# $aapversion
	rd["aapversion"] = int(version_number)

	# Add values set in the command line arguments
	for k in Global.cmd_args.values.keys():
	    add = 1
	    for c in k:
		if not varchar(c):
		    add = 0
		    break
	    if add:
		rd[k] = Global.cmd_args.values[k]


    def add_dependency(self, rpstack, dep):
	"""Add a new dependency.  This takes care of creating nodes for the
	   sources and targets and setting the dependency for the target nodes
	   if the dependency has commands."""
	self.dependencies.append(dep);

	# For each target let the Node know this Depend uses it.  If there are
	# commands also let it know this Depend builds it.
	for item in dep.targetlist:
	    n = item["_node"]
	    n.add_dependency(dep)
	    if dep.commands:
		if (n.get_first_build_dependency()
				    and not n.name in Global.virtual_targets):
		    from Process import recipe_error
		    recipe_error(rpstack,
			    _('Multiple build commands for target "%s"')
								% item["name"])
		n.add_build_dependency(dep)


    def dictlist_nodes(self, dictlist):
	"""Make sure there is a global node for each item in "dictlist" and
	   add a reference to the node in the dictlist item.
	   Carry over specific attributes to the node."""
	for item in dictlist:
	    n = self.get_node(item["name"], 1)
	    n.set_sticky_attributes(item)
	    item["_node"] = n

    def add_dictlist_nodes(self, dictlist):
	"""Add nodes for all items in "dictlist".  Also carry over attributes
	to the Node."""
	for item in dictlist:
	    self.get_node(item["name"], 1, item)


    def add_node(self, node):
	"""Add a Node to the global list of nodes.  The Node is the target
	   and/or source in a dependency."""
	self.nodes[node.name] = node
	self.absnodes[node.absname] = node

    def find_node(self, name, absname = None):
	"""Find an existing Node by name or absname.  For a virtual node
	"absname" should be an empty string.  If "absname" is given it must
	have gone through expanduser() and abspath()."""
	# First try the absolute name, it's more reliable.  Must not be used
	# for virtual nodes though.
	# Then check the short name, only for virtual nodes (may have been used
	# in another recipe).
	if absname is None:
	    absname = os.path.abspath(os.path.expanduser(name))
	if absname and self.absnodes.has_key(absname):
	    return self.absnodes[absname]
	if self.nodes.has_key(name):
	    n = self.nodes[name]
	    if n.attributes.get("virtual"):
		return n
	return None

    def get_node(self, name, add = 0, dict = {}):
	"""Find a Node by name, create a new one if necessary.
	   A new node is added to the global list if "add" is non-zero.
	   When "dict" is given, check for attributes that apply to the
	   Node."""
	absname = os.path.abspath(os.path.expanduser(name))
	n = self.find_node(name, absname)
	if n is None:
	    n = Node(name, absname)
	    if add:
		self.add_node(n)
	elif not n.name_relative and not (
			name[0] == '~' or os.path.isabs(name) or is_url(name)):
	    # Remember the relative name was used, this matters for where
	    # signatures are stored.
	    n.name_relative = 1

	if dict:
	    n.set_attributes(dict)

	return n

    def add_node_attributes(self, dictlist):
	"""Add attributes from existing nodes to the items in "dictlist".
	   Used for sources and targets of executed dependencies and rules.
	   Existing attributes are not overwritten."""
	for item in dictlist:
	    node = self.find_node(item["name"])
	    if node:
		for k in node.attributes.keys():
		    if not item.has_key(k):
			item[k] = node.attributes[k]


    def add_rule(self, rule):
	self.rules.append(rule);

    def print_comments(self):
	"""Print comments for all dependencies with a comment and for standard
	targets."""
	if not self.dependencies:
	    msg_print(_("No dependencies in recipe"))
	for d in self.dependencies:
	    for t in d.targetlist:
		comment = ''
		if t.has_key("comment"):
		    comment = t["comment"]
		else:
		    node = self.find_node(t["name"])
		    if node and node.attributes.has_key("comment"):
			comment = node.attributes["comment"]
		if comment:
		    msg_print('target "%s": %s' % (t["name"], comment))
		elif t["name"] in Global.virtual_targets:
		    msg_print(_('target "%s": standard target, no comment specified')
								  % t["name"])

def assert_attribute(recdict, dict, attrname):
    """Check if dictlist "dict" has an entry for attribute "attrname".
       If not, obtain it from any node that has this attribute."""
    if dict.has_key(attrname):
	return
    for node in getwork(recdict).nodes.values():
	if node.attributes.has_key(attrname):
	    msg_extra(recdict, _('Using %s attribute from node "%s"')
						       % (attrname, node.name))
	    dict[attrname] = node.attributes[attrname]
	    return
    raise UserError, (_('Missing %s attribute for "%s"')
						    % (attrname, dict["name"]))


def setwork(recdict, work):
    """Set the Work object in 'recdict' to "work"."""
    recdict["_work"] = work

def getwork(recdict):
    """Return the Work object that contains 'recdict'."""
    return recdict["_work"]

def setrpstack(recdict, rpstack):
    """Set the RecPos stack in 'recdict'."""
    recdict["_rpstack"] = rpstack

def getrpstack(recdict, line_nr = -1):
    """Return the RecPos stack in 'recdict'.
       When a line number is specified: Make a copy and set the line number for
       the item at the top of the stack."""
    rp = recdict["_rpstack"]
    if line_nr >= 0:
	rp = rpcopy(rp, line_nr)
    return rp

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