# 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:
#  globals      -  dictionary of global variables from the recipe
#                  The "_work" variable points back to the Work object.
#  dependencies -  list of dependencies
#  rules        -  list of rules
#  autodepends	-  list of rules used for automatic dependency checking
#  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

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):
	self.globals = {}
	self.dependencies = []
	self.rules = []
	self.autodepends = []
	self.nodes = {}
	self.absnodes = {}

	# This makes it possible to find "work" from the global variables.
	self.globals["_work"] = self

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

	# $HOME
	if os.environ.has_key("HOME"):
	    self.globals["HOME"] = listitem2str(os.environ["HOME"])
	else:
	    self.globals["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 = "aap/cache"
	    else:
		n = ".aap/cache"
	    cache.append({"name" :
			    listitem2str(os.path.join(os.environ["HOME"], n))})
	cache.append({"name" : "aap/cache"})
	self.globals["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()
	    self.globals["BDIR"] = ("build-" + fixname(sysname)
							    + fixname(release))
	else:
	    self.globals["BDIR"] = "build-" + os.name

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

	# $MESSAGE
	msg_init(self.globals)

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

	# $cache_update
	self.globals["cache_update"] = "12 hour"

	# $aapversion
	self.globals["aapversion"] = string.atoi(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:
		self.globals[k] = Global.cmd_args.values[k]


    def add_dependency(self, rpstack, dep, has_commands):
	"""Add a new dependency.  This takes care of creating nodes for the
	   sources and targets and setting the dependency for the target nodes
	   if "has_commands" is non-zero."""
	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 has_commands:
		if (n.get_first_build_dependency()
				    and not n.name in Globals.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_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."""
	# 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(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.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."""
	n = self.find_node(name)
	if n is None:
	    n = Node(name)
	    if add:
		self.add_node(n)
	elif not (os.path.isabs(name) or is_url(name)):
	    # Remember the relative name was used, this matters for where
	    # signatures are stored.
	    n.relative_name()

	n.set_attributes(dict)

	return 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_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 add_autodepend(self, rule):
	self.autodepends.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:
		if t.has_key("comment"):
		    msg_print('target "%s": %s' % (t["name"], t["comment"]))
		elif t["name"] in Global.virtual_targets:
		    msg_print((_('target "%s": standard target, no comment specified')
								  % t["name"]))


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

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

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

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