# 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: