File: Work.py

package info (click to toggle)
aap 1.072-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 4,980 kB
  • ctags: 2,160
  • sloc: python: 15,113; makefile: 61; sh: 31
file content (477 lines) | stat: -rw-r--r-- 16,706 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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# Part of the A-A-P recipe executive: remember the work specified in the 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


# Currently a Work object contains these items:
#  recdict      -  Dictionary of global variables from the toplevel recipe.
#                  The "_work" variable points back to the Work object.
#  dependencies -  list of dependencies
#  rules        -  list of rules
#  nodes        -  dictionary of nodes from the recipes; index is node.name;
#                  (gone through fname_fold()). normally used to lookup virtual
#                  nodes 
#  absnodes     -  same contents as nodes, but key is the absolute name:
#                  node.absname (gone through fname_fold()).
#  aliasnodes   -  same as absnodes, but uses the alias name of nodes.
#  top_recipe   -  name of toplevel recipe used (None when not reading a recipe)

import os
import os.path
import string

from Node import Node
from RecPos import rpcopy
import Global
from Util import *
from Message import *
from Remote import is_url
from AapVersion import *
import Scope

def set_defaults(rd):
    """
    Set the default values for variables in dictionary "rd".
    This also adds dependencies and rules to the Work object associated with
    "rd".
    """
    from Dictlist import listitem2str, dictlist2str
    from DoRead import read_recipe, read_recipe_dir

    # $VERSIONSTR
    rd["VERSIONSTR"] = version_string

    # $HOME
    home = home_dir()
    if home:
        rd["HOME"] = listitem2str(os.path.basename(home))
    else:
        rd["HOME"] = ''
    
    # $CACHEPATH
    cache = []
    if os.path.exists("/var/aap/cache"):
        cache.append({"name" : "/var/aap/cache"})
    if home:
        cache.append({"name" : os.path.join(home, "cache")})
    cache.append({"name" : in_aap_dir("cache")})
    rd["CACHEPATH"] = dictlist2str(cache)

    # $BDIR
    if os.name == "posix":
        def fixname(n):
            """Change all non-letters, non-digits in "n" to underscores and
            return the result."""
            s = ''
            for c in n:
                if c in string.letters + string.digits:
                    s = s + c
                else:
                    s = s + '_'
            return s

        sysname, n, release, v, m = os.uname()
	rd["OSNAME"] = fixname(sysname) + fixname(release)
    else:
        rd["OSNAME"] = os.name
    rd["BDIR"] = "build-" + rd["OSNAME"]

    # $OSTYPE
    n = os.name
    if os.name == "dos":
        n = "msdos"
    elif os.name == "nt":
        n = "mswin"
    rd["OSTYPE"] = n

    # $MESSAGE
    msg_init(rd)

    # $SRCPATH (to be evaluated when used)
    rd["SRCPATH"] = ExpandVar(". $BDIR")

    # Standard variables.
    rd["bar"] = '|'
    rd["br"] = '\n'
    rd["BR"] = '\n'
    rd["empty"] = ''
    rd["gt"] = '>'
    rd["lt"] = '<'
    rd["pipe"] = '|'

    # Variables for a port recipe.
    rd["DISTDIR"] = "distfiles"
    rd["PATCHDISTDIR"] = "patches"
    rd["WRKDIR"] = "work"

    # $CACHEUPDATE
    rd["CACHEUPDATE"] = "12 hour"

    # $AAPVERSION
    rd["AAPVERSION"] = int(version_number)

    # Directory where our modules are.
    if Global.aap_rootdir:
        # Use rootdir if possible, when started as "../Exec/aap" __file__ will
        # be a relative file name, that doesn't work after chdir().
        dir = Global.aap_rootdir
    else:
        # Just in case rootdir wasn't set (never happens?).
        dir, tail = os.path.split(__file__)
        Global.set_aap_rootdir(dir)

    # $AAP: Execute aap, making sure the right version of Python is used.
    #       Use quotes where approriate, the path may have a space.
    rd["AAP"] = (listitem2str(sys.executable) + ' '
                                  + listitem2str(os.path.join(dir, "Main.py")))

    #
    # Read A-A-P default recipes.
    #
    read_recipe([], rd, os.path.join(dir, "default.aap"), 1, optional = 1)

    # Keep a copy of the result for the "_default" scope.
    rd["_default"] = Scope.RecipeDict(rd)

    # Read system and user default recipes.
    # Keep a copy of the result for the "_start" scope.
    for dir in default_dirs(rd):
        read_recipe_dir(rd, os.path.join(dir, "startup"))
    rd["_start"] = Scope.RecipeDict(rd)


class Work:
    def __init__(self, recdict = None):
        if recdict is None:
            self.topscope = Scope.create_topscope("toplevel")
            self.recdict = self.topscope.data
        else:
            # The ":execute" command may pass on a scope.
            self.topscope = recdict["_top"]
            self.recdict = recdict
        self.dependencies = []
        self.rules = []
        self.routes = {}
        self.nodes = {}
        self.absnodes = {}
        self.aliasnodes = {}
        self.top_recipe = None
        self.recipe_already_read = {}
        self.module_already_read = {}

        # This makes it possible to find "work" from the global variables.
        setwork(self.recdict, self)


    def add_dependency(self, rpstack, dep):
        """Add a new dependency.  This takes care of creating nodes for the
           sources and targets and setting the dependency for the target nodes
           if the dependency has commands."""
        self.dependencies.append(dep);

        # For each target let the Node know this Depend uses it.  If there are
        # commands also let it know this Depend builds it.
        for item in dep.targetlist:
            n = item["_node"]
            n.add_dependency(dep)
            if dep.commands:
                if (n.get_first_build_dependency()
                                    and not n.name in Global.virtual_targets):
                    from Process import recipe_error
                    recipe_error(rpstack,
                            _('Multiple build commands for target "%s"')
                                                                % item["name"])
                n.add_build_dependency(dep)


    def dictlist_nodes(self, dictlist):
        """Make sure there is a global node for each item in "dictlist" and
           add a reference to the node in the dictlist item.
           Carry over specific attributes to the node."""
        for item in dictlist:
            n = self.get_node(item["name"], 1)
            n.set_sticky_attributes(item)
            item["_node"] = n

    def add_dictlist_nodes(self, dictlist):
        """Add nodes for all items in "dictlist".  Also carry over attributes
        to the Node."""
        for item in dictlist:
            self.get_node(item["name"], 1, item)


    def add_node(self, node):
        """Add a Node to the global list of nodes.  The Node is the target
           and/or source in a dependency."""
        self.nodes[fname_fold(node.name)] = node
        self.absnodes[fname_fold(node.absname)] = node

    def del_node(self, node):
        """Remove a node from the global list of nodes."""
        del self.nodes[fname_fold(node.name)]
        del self.absnodes[fname_fold(node.absname)]

    def add_node_alias(self, node):
        """Add a Node to the global list of alias nodes."""
        self.aliasnodes[fname_fold(node.absalias)] = node


    def find_node(self, name, absname = None, use_alias = 1):
        """
        Find an existing Node by name or absname.
        For a virtual node "absname" should be an empty string (not None!).
        If "absname" is given it must have gone through expanduser() and
        abspath().
        """
        # First try the absolute name, it's more reliable.  Must not be used
        # for virtual nodes though.
        # Then check the short name, only for virtual nodes (may have been used
        # in another recipe).
        if absname is None:
            absname = os.path.abspath(os.path.expanduser(name))

        # Might try again with a folded name if fname_fold() may return a
        # different file name.  This is just to optimize the speed.
        if fname_fold_same():
            r = [0]
        else:
            r = [1, 0]

        if absname:
            findname = absname
            for i in r:
                if self.absnodes.has_key(findname):
                    return self.absnodes[findname]
                if i:
                    findname = fname_fold(absname)

        for i in r:
            findname = name
            if self.nodes.has_key(findname):
                n = self.nodes[findname]
                if n.attributes.get("virtual"):
                    return n
            if i:
                findname = fname_fold(name)

        # Now try again to find an alias.  Only use the absolute name, virtual
        # nodes don't have an alias.
        if absname and use_alias:
            findname = absname
            for i in r:
                if self.aliasnodes.has_key(findname):
                    return self.aliasnodes[findname]
                if i:
                    findname = fname_fold(absname)

        return None


    def get_node(self, name, add = 0, dict = {}, use_alias = 1):
        """Find a Node by name, create a new one if necessary.
           A new node is added to the global list if "add" is non-zero.
           When "dict" is given, check for attributes that apply to the
           Node."""
        absname = os.path.abspath(os.path.expanduser(name))
        n = self.find_node(name, absname, use_alias = use_alias)
        if n is None:
            n = Node(name, absname)
            if add:
                self.add_node(n)
        elif not n.name_relative and not (
                        name[0] == '~' or os.path.isabs(name) or is_url(name)):
            # Remember the relative name was used, this matters for where
            # signatures are stored.
            n.name_relative = 1

        if dict:
            n.set_attributes(dict)

        return n

    def node_set_alias(self, name, alias, dict = {}):
        """
        Define a node "name" with an alias "alias".
        """
        n = self.find_node(alias, use_alias = 0)
        if n:
            # If a node already exists by the alias name it may be that the
            # alias was used before it was defined, e.g.:
            #    all : foo
            #    :program foo : foo.c
            # But avoid adding an alias if it's a real node:
            #    :program foo : foo.c
            #    :lib foo : foo.c   # node = libfoo.a, don't use alias "foo"

            if n.alias:
                # Node already has an alias, thus it was not the situation that
                # the alias was used before it was defined.
                return

            if self.find_node(name, use_alias = 0):
                # New name also exists, don't set an alias.
                return

            # Node for alias name already exists, rename it.
            # Need to remove it from the global node list and add it back with
            # the new name.
            self.del_node(n)
            n.set_name(name)
            n.set_attributes(dict)
            self.add_node(n)
        else:
            # Create a new node.
            n = self.get_node(name, add = 1, dict = dict, use_alias = 0)
        n.set_alias(alias)
        self.add_node_alias(n)

    def add_node_attributes(self, dictlist):
        """Add attributes from existing nodes to the items in "dictlist".
           Used for sources and targets of executed dependencies and rules.
           Existing attributes are not overwritten."""
        for item in dictlist:
            node = self.find_node(item["name"])
            if node:
                for k in node.attributes.keys():
                    if not item.has_key(k):
                        item[k] = node.attributes[k]


    def add_rule(self, rule):
        self.rules.append(rule);

    def del_rule(self, rule):
        self.rules.remove(rule)

    def clearrules(self):
        self.rules = []

    def add_route(self, route):
        """
        Add a route to "routes".
        We actually add every possible route, since the input and output can be
        a list of filetype names.
        """
        for in_ftype in route.typelist[0]:
            for out_ftype in route.typelist[-1]:
                if not self.routes.has_key(in_ftype):
                    self.routes[in_ftype] = {}
                self.routes[in_ftype][out_ftype] = route

    def find_route(self, in_ftype, out_ftype, use_actions = 0):
        """
        Find a route for input filetype "in_ftype" to "out_ftype".
        Return the found route object or None.
        """
        l = self.routes.get(in_ftype)
        if l:
            l = l.get(out_ftype)
        if not l and use_actions:
            # No route specified, try using one or more actions.
            from Action import find_action_route
            l = find_action_route(in_ftype, out_ftype)
        return l


    def print_comments(self):
        """Print comments for all dependencies with a comment and for standard
        targets."""
        if not self.dependencies:
            msg_print(_("No dependencies in recipe"))
        else:
            # Collect the messages in toprint[].
            toprint = {}
            for d in self.dependencies:
                for t in d.targetlist:
                    comment = ''
                    if t.has_key("comment"):
                        comment = t["comment"]
                    else:
                        node = self.find_node(t["name"])
                        if node and node.attributes.has_key("comment"):
                            comment = node.attributes["comment"]
                    n = t["name"]
                    if comment:
                        toprint[n] = comment
                    elif n in Global.virtual_targets:
                        toprint[n] = _('standard target, no comment specified')

            # Sort the keys and print the messages.
            keys = toprint.keys()
            keys.sort()
            for n in keys:
                spaces = 12 - len(n)
                if spaces < 1:
                    spaces = 1
                msg_print(('target %s:' % n)
                                        + "            "[:spaces] + toprint[n])

def assert_attribute(recdict, dict, attrname):
    """Check if dictlist "dict" has an entry for attribute "attrname".
       If not, obtain it from any node that has this attribute."""
    if dict.has_key(attrname):
        return
    for node in getwork(recdict).nodes.values():
        if node.attributes.has_key(attrname):
            msg_extra(recdict, _('Using %s attribute from node "%s"')
                                                       % (attrname, node.name))
            dict[attrname] = node.attributes[attrname]
            return
    raise UserError, (_('Missing %s attribute for "%s"')
                                                    % (attrname, dict["name"]))


def setwork(recdict, work):
    """Set the Work object in 'recdict' to "work"."""
    recdict["_work"] = work

def getwork(recdict):
    """Return the Work object that contains 'recdict'.  Search scopes upto the
       toplevel."""
    return recdict["_no"].get("_work")

def setrpstack(recdict, rpstack):
    """Set the RecPos stack in 'recdict'.  This is separate for each scope."""
    recdict["_rpstack"] = rpstack

def getrpstack(recdict, line_nr = -1):
    """Return the RecPos stack in 'recdict'.
       When a line number is specified: Make a copy and set the line number for
       the item at the top of the stack."""
    rp = recdict["_rpstack"]
    if line_nr >= 0:
        rp = rpcopy(rp, line_nr)
    return rp


# A Route object contains:
#  recdict      - recdict of where the route was defined
#  rpstack      - rpstack of where the route was defined
#  default      - 1 when default route
#  typelist     - list of list of filetype names, input first
#  targetattr   - attributes for the target (e.g., "buildaction")
#  steplist     - list of steps to be taken, each step as a string.
#  lnumlist     - line number in recipe for each step
#
# Illustration:
#       :route intype1,intype2 xxtype outtype
#               oneaction $(source).xx
#               lastaction
#
# Too small to put in a separate file...
#

class Route:
    def __init__(self, recdict, rpstack, default, typelist,
                                               targetattr, steplist, lnumlist):
        self.recdict = recdict
        self.rpstack = rpstack
        self.default = default
        self.typelist = typelist
        self.targetattr = targetattr
        self.steplist = steplist
        self.lnumlist = lnumlist

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