# Part of the A-A-P recipe executive: Node used in a 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

import os

from Util import *

#
# A Node object is the source and/or target of a dependency.
# main items:
# name              name as used in the recipe (at first use, it's not changed
#                   when also used in a recipe in another directory)
# recipe_dir        directory in which "name" is valid
# absname           name with absolute path (meaningless for virtual targets,
#                   use get_name())
# attributes        dictionary for attributes, such as "virtual"
# dependencies      a list of references to the dependencies in which the node
#                   is a target.  It can be empty.
# build_dependencies  subset of "dependencies" for the ones that have build
#                   commands.  Only "finally" and "refresh" may have multiple
#                   entries.

class Node:

   # values used for "status"
   new = 1         # newly created
   busy = 2        # busy updating as a target
   updated = 3     # successfully updated

   def __init__(self, name):
       """Create a node for "name".  Caller must have already made sure "name"
       is normalized (but not made absolute)."""
       self.name = name

       self.attributes = {}            # dictionary of attributes; relevant
                                       # ones are "directory", "virtual" and
                                       # "cache_update"
       import Global
       from Remote import is_url

       if name in Global.virtual_targets:
           self.attributes["virtual"] = 1

       # Remember the directory of the recipe where the node was first used.
       # "name" is relative to this directory unless it's virtual.
       self.recipe_dir = os.getcwd()

       # Remember the absolute path for the Node.  When it's virtual absname
       # should not be used!  Use get_name() instead.
       if os.path.isabs(name) or is_url(name):
           self.name_relative = 0
           self.absname = self.name
       else:
           self.name_relative = 1
           self.absname = os.path.abspath(name)
       self._set_sign_dir(4)

       self.dependencies = []          # dependencies for which this node is a
                                       # target
       self.build_dependencies = []    # idem, with build commands

       self.status = Node.new          # current status

       # When status is "busy", either current_rule or current_dep indicates
       # which rule or dependency is being used, so that a clear error message
       # can be given for cyclic dependencies.
       self.current_rule = None
       self.current_dep = None

       self.auto_depend = None         # dictlist for automatic dependencies
       self.auto_depend_rec = 0        # auto_depend generated recursively
       self.did_auto_depend = 0        # used in dictlist_update()

   def get_name(self):
       """Get the name to be used for this Node.  When the "virtual" attribute
          is given it's the unexpanded name, otherwise the absolute name."""
       if self.get_virtual():
           return self.name
       return self.absname

   def short_name(self):
       """Get the shortest name that still makes clear what the name of the
          node is.  Used for messages."""
       if self.get_virtual():
           return self.name
       return shorten_name(self.absname)


   # When the Node is used as a target, we must decide where the
   # signatures are stored.  The priority order is:
   # 1. When used with a relative path name, but no "virtual" attribute, use
   #    the directory of the target.
   # 2. When a dependency with build commands is defined with this Node as
   #    a target, use the directory of that recipe.
   # 3. When any dependency is defined with this node as a target, use the
   #    directory of that recipe.
   # 4. Use the directory of the recipe where this Node was first used.
   # This can be overruled with the "signdirectory" attribute.
   # CAREFUL: the "virtual" and "signdirectory" attributes may be changed!
   # When adding the "virtual" attribute level 1 is skipped, thus the choice
   # between level 2, 3 or 4 must be remembered separately.
   def _set_sign_dir(self, level):
       """Set the directory for the signatures to the directory of the target
       (for level 1) or the current directory (where the recipe is)."""
       self.sign_dir = os.getcwd()
       self.sign_level = level

   def get_sign_dir(self):
       """Get the directory for where the signatures for this node are to be
          stored."""
       if self.attributes.has_key("signdirectory"):
           return self.attributes["signdirectory"]
       if self.name_relative and not self.get_virtual():
           return os.path.dirname(self.absname)
       return self.sign_dir

   def relative_name(self):
       """This node has been used with a relative file name, which means the
          target directory is to be used for signatures, unless the "virtual"
          attribute is used (may be added later)."""
       self.name_relative = 1

   def add_dependency(self, dependency):
       self.dependencies.append(dependency)
       if self.sign_level > 3:
           self._set_sign_dir(3)

   def get_dependencies(self):
       return self.dependencies

   def add_build_dependency(self, dependency):
       self.build_dependencies.append(dependency)
       if self.sign_level > 2:
           self._set_sign_dir(2)

   def get_first_build_dependency(self):
       if self.build_dependencies:
           return self.build_dependencies[0]
       return None

   def get_build_dependencies(self):
       return self.build_dependencies

   def set_attributes(self, dictlist):
       """Set attributes for a node from "dictlist".  Skip "name" and items
       that start with an underscore."""
       for k in dictlist.keys():
           if k == "virtual" and self.attributes.has_key(k):
               # The "virtual" attribute is never reset
               self.attributes[k] = (self.attributes[k] or dictlist[k])
           elif k != "name" and k[0] != '_':
               self.attributes[k] = dictlist[k]

   def set_sticky_attributes(self, dictlist):
       """Set only those attributes for the node from "dictlist" that can be
          carried over from a dependency to everywhere else the node is
          used."""
       for attr in ["virtual", "directory", "filetype", "constant", "refresh",
                                                         "commit", "publish"]:
           if dictlist.has_key(attr) and dictlist[attr]:
               self.attributes[attr] = dictlist[attr]

   def get_cache_update(self):
       """Get the cache_update attribute.  Return None if it's not set."""
       if self.attributes.has_key("cache_update"):
           return self.attributes["cache_update"]
       return None

   def get_virtual(self):
       """Get the virtual attribute.  Return zero if it's not set."""
       if self.attributes.has_key("virtual"):
           return self.attributes["virtual"]
       return 0

   def isdir(self):
       """Return non-zero when we know this Node is a directory.  When
          specified with set_attributes() return the value used (mode value
          for creation)."""
       # A specified attribute overrules everything
       if self.attributes.has_key("directory"):
           return self.attributes["directory"]
       # A virtual target can't be a directory
       if self.get_virtual():
           return 0
       # Check if the node exists and is a directory
       import os.path
       if os.path.isdir(self.get_name()):
           return 1
       # Must be a file then
       return 0

   def may_refresh(self):
       """Return non-zero if this node should be refreshed when using the
          "refresh" target or ":refresh"."""
       # Never refresh a virtual node.
       # Refreshing is skipped when the node has a "constant" attribute with
       # a non-empty non-zero value and the file exists.
       return (not self.get_virtual()
               and (not self.attributes.has_key("constant")
                   or self.attributes["constant"]
                   or not os.path.exists(self.get_name())))

   def __str__(self):
       return "Node " + self.get_name()


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