1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
|
# Part of the A-A-P recipe executive: Node used in a recipe
# Copyright (C) 2002-2003 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 *
import Filetype
#
# 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, normalized 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 nodes like "finally", "clean" and "fetch"
# 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
builderror = 4 # attempted to update and failed.
def __init__(self, name, absname = None):
"""Create a node for "name". Caller must have already made sure "name"
is normalized (but not made absolute)."""
self.attributes = {} # dictionary of attributes; relevant
# ones are "directory", "virtual" and
# "cache_update"
self.set_name(name, absname)
self.alias = None # optional alias name
self.absalias = None # optional alias absolute 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
self.scope_recdict = None # Scope in which the node was used as a
# target. Used for finding rules that
# may apply.
# 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.autodep_dictlist = None # dictlist for items this node depends
# on, from automatic dependencies
self.autodep_recursive = 0 # autodep_dictlist generated recursively
self.autodep_busy = 0 # used in dictlist_update()
self.did_add_clean = 0 # used by add_clean()
self.recursive_level = 0 # depth of building recursively
def copy(self):
"""Make a copy of a Node object. This is a shallow copy of most
things, but the attributes and dependencies are done an extra
level."""
import copy
r = copy.copy(self)
r.attributes = copy.copy(self.attributes)
r.dependencies = copy.copy(self.dependencies)
r.build_dependencies = copy.copy(self.build_dependencies)
return r
def rpstack(self):
"""
Get a useful rpstack for when this node is used as a target (for error
messages).
"""
if self.current_rule:
return self.current_rule.rpstack
if self.current_dep:
return self.current_dep.rpstack
return []
def set_name(self, name, absname = None):
"""Set the name of the node. Used when creating a new node and when an
alias is going to be used."""
import Global
from Remote import is_url
self.name = name
# Set "virtual" when it's a know virtual target.
if name in Global.virtual_targets:
self.attributes["virtual"] = 1
# Remember the directory of the recipe where the node name was set.
# "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.
# A URL, "~/" and "~user/" are also absolute.
if os.path.isabs(name) or is_url(name):
self.name_relative = 0
if absname is None:
absname = os.path.normpath(name)
elif name[0] == '~':
self.name_relative = 0
if absname is None:
absname = os.path.abspath(os.path.expanduser(name))
else:
self.name_relative = 1
if absname is None:
absname = os.path.abspath(name)
self.absname = absname
def set_alias(self, name, absname = None):
"""Set the alias name for this node."""
self.alias = name
if absname:
self.absalias = absname
else:
self.absalias = os.path.abspath(os.path.expanduser(name))
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.attributes.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.attributes.get("virtual"):
return self.name
return shorten_name(self.absname)
def get_ftype(self, recdict):
"""Return the detected file type for this node."""
ft = self.attributes.get("filetype")
if ft:
return ft
# A ":program" command adds a lower priority filetype attribute.
ft = self.attributes.get("filetypehint")
if ft:
return ft
return Filetype.ft_detect(self.get_name(), recdict = recdict)
# 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_fname(self):
"""Get the file name to use for the signatures of this node.
When using a directory, append "sign_fname"."""
if self.attributes.has_key("signfile"):
return os.path.abspath(os.path.expanduser(
self.attributes["signfile"]))
if self.attributes.has_key("signdirectory"):
dir = os.path.abspath(os.path.expanduser(
self.attributes["signdirectory"]))
elif self.name_relative and not self.attributes.get("virtual"):
dir = os.path.dirname(self.absname)
else:
dir = self.sign_dir
from Sign import sign_normal_fname
return os.path.join(dir, sign_normal_fname)
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):
"""Return the first build dependency, None if there isn't one."""
if self.build_dependencies:
return self.build_dependencies[0]
return None
def get_recipe_build_dependency(self, recdict):
"""Return the first build dependency for recipe with "recdict", None if
there isn't one."""
for dep in self.build_dependencies:
if dep.buildrecdict is recdict:
return dep
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", "remember", "directory", "filetype", "force",
"constant", "fetch", "commit", "publish", "signfile",
"depdir"]:
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 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.attributes.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_fetch(self):
"""Return non-zero if this node should be fetched when using the
"fetch" target or ":fetch"."""
# Never fetch a virtual node.
# Fetching is skipped when the node has a "constant" attribute with
# a non-empty non-zero value and the file exists.
return (not self.attributes.get("virtual")
and (not self.attributes.get("constant")
or not os.path.exists(self.get_name())))
def __str__(self):
return "Node " + self.get_name()
# vim: set sw=4 et sts=4 tw=79 fo+=l:
|