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