File: Node.py

package info (click to toggle)
aap 1.072-1.1
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k, lenny
  • size: 4,976 kB
  • ctags: 2,160
  • sloc: python: 15,113; makefile: 62; sh: 13
file content (291 lines) | stat: -rw-r--r-- 11,905 bytes parent folder | download | duplicates (2)
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: