1# Status: ported.
2# Base revision: 64488.
3#
4#  Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
5#  distribute this software is granted provided this copyright notice appears in
6#  all copies. This software is provided "as is" without express or implied
7#  warranty, and with no claim as to its suitability for any purpose.
8
9#  Implements virtual targets, which correspond to actual files created during
10#  build, but are not yet targets in Jam sense. They are needed, for example,
11#  when searching for possible transormation sequences, when it's not known
12#  if particular target should be created at all.
13#
14#
15#                       +--------------------------+
16#                       | VirtualTarget            |
17#                       +==========================+
18#                       | actualize                |
19#                       +--------------------------+
20#                       | actualize_action() = 0   |
21#                       | actualize_location() = 0 |
22#                       +----------------+---------+
23#                                        |
24#                                        ^
25#                                       / \
26#                                      +-+-+
27#                                        |
28#    +---------------------+     +-------+--------------+
29#    | Action              |     | AbstractFileTarget   |
30#    +=====================|   * +======================+
31#    | action_name         |  +--+ action               |
32#    | properties          |  |  +----------------------+
33#    +---------------------+--+  | actualize_action()   |
34#    | actualize()         |0..1 +-----------+----------+
35#    | path()              |                 |
36#    | adjust_properties() | sources         |
37#    | actualize_sources() | targets         |
38#    +------+--------------+                 ^
39#           |                               / \
40#           ^                              +-+-+
41#          / \                               |
42#         +-+-+                +-------------+-------------+
43#           |                  |                           |
44#           |           +------+---------------+  +--------+-------------+
45#           |           | FileTarget           |  | SearchedLibTarget    |
46#           |           +======================+  +======================+
47#           |           | actualize-location() |  | actualize-location() |
48#           |           +----------------------+  +----------------------+
49#           |
50#         +-+------------------------------+
51#         |                                |
52#    +----+----------------+     +---------+-----------+
53#    | CompileAction       |     | LinkAction          |
54#    +=====================+     +=====================+
55#    | adjust_properties() |     | adjust_properties() |
56#    +---------------------+     | actualize_sources() |
57#                                +---------------------+
58#
59# The 'CompileAction' and 'LinkAction' classes are defined not here,
60# but in builtin.jam modules. They are shown in the diagram to give
61# the big picture.
62
63import bjam
64
65import re
66import os.path
67import string
68import types
69
70from b2.util import path, utility, set, is_iterable_typed
71from b2.util.utility import add_grist, get_grist, ungrist, replace_grist, get_value
72from b2.util.sequence import unique
73from b2.tools import common
74from b2.exceptions import *
75import b2.build.type
76import b2.build.property_set as property_set
77
78import b2.build.property as property
79
80from b2.manager import get_manager
81from b2.util import bjam_signature
82
83__re_starts_with_at = re.compile ('^@(.*)')
84
85class VirtualTargetRegistry:
86    def __init__ (self, manager):
87        self.manager_ = manager
88
89        # A cache for FileTargets
90        self.files_ = {}
91
92        # A cache for targets.
93        self.cache_ = {}
94
95        # A map of actual names to virtual targets.
96        # Used to make sure we don't associate same
97        # actual target to two virtual targets.
98        self.actual_ = {}
99
100        self.recent_targets_ = []
101
102        # All targets ever registered
103        self.all_targets_ = []
104
105        self.next_id_ = 0
106
107    def register (self, target):
108        """ Registers a new virtual target. Checks if there's already registered target, with the same
109            name, type, project and subvariant properties, and also with the same sources
110            and equal action. If such target is found it is returned and 'target' is not registered.
111            Otherwise, 'target' is registered and returned.
112        """
113        assert isinstance(target, VirtualTarget)
114        if target.path():
115            signature = target.path() + "-" + target.name()
116        else:
117            signature = "-" + target.name()
118
119        result = None
120        if signature not in self.cache_:
121            self.cache_ [signature] = []
122
123        for t in self.cache_ [signature]:
124            a1 = t.action ()
125            a2 = target.action ()
126
127            # TODO: why are we checking for not result?
128            if not result:
129                if not a1 and not a2:
130                    result = t
131                else:
132                    if a1 and a2 and a1.action_name () == a2.action_name () and a1.sources () == a2.sources ():
133                        ps1 = a1.properties ()
134                        ps2 = a2.properties ()
135                        p1 = ps1.base () + ps1.free () +\
136                            b2.util.set.difference(ps1.dependency(), ps1.incidental())
137                        p2 = ps2.base () + ps2.free () +\
138                            b2.util.set.difference(ps2.dependency(), ps2.incidental())
139                        if p1 == p2:
140                            result = t
141
142        if not result:
143            self.cache_ [signature].append (target)
144            result = target
145
146        # TODO: Don't append if we found pre-existing target?
147        self.recent_targets_.append(result)
148        self.all_targets_.append(result)
149
150        return result
151
152    def from_file (self, file, file_location, project):
153        """ Creates a virtual target with appropriate name and type from 'file'.
154            If a target with that name in that project was already created, returns that already
155            created target.
156            TODO: more correct way would be to compute path to the file, based on name and source location
157            for the project, and use that path to determine if the target was already created.
158            TODO: passing project with all virtual targets starts to be annoying.
159        """
160        if __debug__:
161            from .targets import ProjectTarget
162            assert isinstance(file, basestring)
163            assert isinstance(file_location, basestring)
164            assert isinstance(project, ProjectTarget)
165        # Check if we've created a target corresponding to this file.
166        path = os.path.join(os.getcwd(), file_location, file)
167        path = os.path.normpath(path)
168
169        if path in self.files_:
170            return self.files_ [path]
171
172        file_type = b2.build.type.type (file)
173
174        result = FileTarget (file, file_type, project,
175                             None, file_location)
176        self.files_ [path] = result
177
178        return result
179
180    def recent_targets(self):
181        """Each target returned by 'register' is added to a list of
182        'recent-target', returned by this function. So, this allows
183        us to find all targets created when building a given main
184        target, even if the target."""
185
186        return self.recent_targets_
187
188    def clear_recent_targets(self):
189        self.recent_targets_ = []
190
191    def all_targets(self):
192        # Returns all virtual targets ever created
193        return self.all_targets_
194
195    # Returns all targets from 'targets' with types
196    # equal to 'type' or derived from it.
197    def select_by_type(self, type, targets):
198        return [t for t in targets if b2.build.type.is_sybtype(t.type(), type)]
199
200    def register_actual_name (self, actual_name, virtual_target):
201        assert isinstance(actual_name, basestring)
202        assert isinstance(virtual_target, VirtualTarget)
203        if actual_name in self.actual_:
204            cs1 = self.actual_ [actual_name].creating_subvariant ()
205            cs2 = virtual_target.creating_subvariant ()
206            cmt1 = cs1.main_target ()
207            cmt2 = cs2.main_target ()
208
209            action1 = self.actual_ [actual_name].action ()
210            action2 = virtual_target.action ()
211
212            properties_added = []
213            properties_removed = []
214            if action1 and action2:
215                p1 = action1.properties ()
216                p1 = p1.raw ()
217                p2 = action2.properties ()
218                p2 = p2.raw ()
219
220                properties_removed = set.difference (p1, p2)
221                if not properties_removed:
222                    properties_removed = ["none"]
223
224                properties_added = set.difference (p2, p1)
225                if not properties_added:
226                    properties_added = ["none"]
227
228            # FIXME: Revive printing of real location.
229            get_manager().errors()(
230                "Duplicate name of actual target: '%s'\n"
231                "previous virtual target '%s'\n"
232                "created from '%s'\n"
233                "another virtual target '%s'\n"
234                "created from '%s'\n"
235                "added properties:\n%s\n"
236                "removed properties:\n%s\n"
237                % (actual_name,
238                   self.actual_ [actual_name], cmt1.project().location(),
239                   virtual_target,
240                   cmt2.project().location(),
241                   '\n'.join('\t' + p for p in properties_added),
242                   '\n'.join('\t' + p for p in properties_removed)))
243
244        else:
245            self.actual_ [actual_name] = virtual_target
246
247
248    def add_suffix (self, specified_name, file_type, prop_set):
249        """ Appends the suffix appropriate to 'type/property_set' combination
250            to the specified name and returns the result.
251        """
252        assert isinstance(specified_name, basestring)
253        assert isinstance(file_type, basestring)
254        assert isinstance(prop_set, property_set.PropertySet)
255        suffix = b2.build.type.generated_target_suffix (file_type, prop_set)
256
257        if suffix:
258            return specified_name + '.' + suffix
259
260        else:
261            return specified_name
262
263class VirtualTarget:
264    """ Potential target. It can be converted into jam target and used in
265        building, if needed. However, it can be also dropped, which allows
266        to search for different transformation and select only one.
267        name:    name of this target.
268        project: project to which this target belongs.
269    """
270    def __init__ (self, name, project):
271        if __debug__:
272            from .targets import ProjectTarget
273            assert isinstance(name, basestring)
274            assert isinstance(project, ProjectTarget)
275        self.name_ = name
276        self.project_ = project
277        self.dependencies_ = []
278        self.always_ = False
279
280        # Caches if dapendencies for scanners have already been set.
281        self.made_ = {}
282
283    def manager(self):
284        return self.project_.manager()
285
286    def virtual_targets(self):
287        return self.manager().virtual_targets()
288
289    def name (self):
290        """ Name of this target.
291        """
292        return self.name_
293
294    def project (self):
295        """ Project of this target.
296        """
297        return self.project_
298
299    def depends (self, d):
300        """ Adds additional instances of 'VirtualTarget' that this
301            one depends on.
302        """
303        self.dependencies_ = unique (self.dependencies_ + d).sort ()
304
305    def dependencies (self):
306        return self.dependencies_
307
308    def always(self):
309        self.always_ = True
310
311    def actualize (self, scanner = None):
312        """ Generates all the actual targets and sets up build actions for
313            this target.
314
315            If 'scanner' is specified, creates an additional target
316            with the same location as actual target, which will depend on the
317            actual target and be associated with 'scanner'. That additional
318            target is returned. See the docs (#dependency_scanning) for rationale.
319            Target must correspond to a file if 'scanner' is specified.
320
321            If scanner is not specified, then actual target is returned.
322        """
323        if __debug__:
324            from .scanner import Scanner
325            assert scanner is None or isinstance(scanner, Scanner)
326        actual_name = self.actualize_no_scanner ()
327
328        if self.always_:
329            bjam.call("ALWAYS", actual_name)
330
331        if not scanner:
332            return actual_name
333
334        else:
335            # Add the scanner instance to the grist for name.
336            g = '-'.join ([ungrist(get_grist(actual_name)), str(id(scanner))])
337
338            name = replace_grist (actual_name, '<' + g + '>')
339
340            if name not in self.made_:
341                self.made_ [name] = True
342
343                self.project_.manager ().engine ().add_dependency (name, actual_name)
344
345                self.actualize_location (name)
346
347                self.project_.manager ().scanners ().install (scanner, name, str (self))
348
349            return name
350
351# private: (overridables)
352
353    def actualize_action (self, target):
354        """ Sets up build actions for 'target'. Should call appropriate rules
355            and set target variables.
356        """
357        raise BaseException ("method should be defined in derived classes")
358
359    def actualize_location (self, target):
360        """ Sets up variables on 'target' which specify its location.
361        """
362        raise BaseException ("method should be defined in derived classes")
363
364    def path (self):
365        """ If the target is generated one, returns the path where it will be
366            generated. Otherwise, returns empty list.
367        """
368        raise BaseException ("method should be defined in derived classes")
369
370    def actual_name (self):
371        """ Return that actual target name that should be used
372            (for the case where no scanner is involved)
373        """
374        raise BaseException ("method should be defined in derived classes")
375
376
377class AbstractFileTarget (VirtualTarget):
378    """ Target which correspond to a file. The exact mapping for file
379        is not yet specified in this class. (TODO: Actually, the class name
380        could be better...)
381
382        May be a source file (when no action is specified), or
383        derived file (otherwise).
384
385        The target's grist is concatenation of project's location,
386        properties of action (for derived files), and, optionally,
387        value identifying the main target.
388
389        exact:  If non-empty, the name is exactly the name
390                created file should have. Otherwise, the '__init__'
391                method will add suffix obtained from 'type' by
392                calling 'type.generated-target-suffix'.
393
394        type:   optional type of this target.
395    """
396    def __init__ (self, name, type, project, action = None, exact=False):
397        assert isinstance(type, basestring) or type is None
398        assert action is None or isinstance(action, Action)
399        assert isinstance(exact, (int, bool))
400        VirtualTarget.__init__ (self, name, project)
401
402        self.type_ = type
403
404        self.action_ = action
405        self.exact_ = exact
406
407        if action:
408            action.add_targets ([self])
409
410            if self.type and not exact:
411                self.__adjust_name (name)
412
413
414        self.actual_name_ = None
415        self.path_ = None
416        self.intermediate_ = False
417        self.creating_subvariant_ = None
418
419        # True if this is a root target.
420        self.root_ = False
421
422    def type (self):
423        return self.type_
424
425    def set_path (self, path):
426        """ Sets the path. When generating target name, it will override any path
427            computation from properties.
428        """
429        assert isinstance(path, basestring)
430        self.path_ = os.path.normpath(path)
431
432    def action (self):
433        """ Returns the action.
434        """
435        return self.action_
436
437    def root (self, set = None):
438        """ Sets/gets the 'root' flag. Target is root is it directly correspods to some
439            variant of a main target.
440        """
441        assert isinstance(set, (int, bool, type(None)))
442        if set:
443            self.root_ = True
444        return self.root_
445
446    def creating_subvariant (self, s = None):
447        """ Gets or sets the subvariant which created this target. Subvariant
448        is set when target is brought into existence, and is never changed
449        after that. In particual, if target is shared by subvariant, only
450        the first is stored.
451        s:  If specified, specified the value to set,
452                which should be instance of 'subvariant' class.
453        """
454        assert s is None or isinstance(s, Subvariant)
455        if s and not self.creating_subvariant ():
456            if self.creating_subvariant ():
457                raise BaseException ("Attempt to change 'dg'")
458
459            else:
460                self.creating_subvariant_ = s
461
462        return self.creating_subvariant_
463
464    def actualize_action (self, target):
465        assert isinstance(target, basestring)
466        if self.action_:
467            self.action_.actualize ()
468
469    # Return a human-readable representation of this target
470    #
471    # If this target has an action, that's:
472    #
473    #    { <action-name>-<self.name>.<self.type> <action-sources>... }
474    #
475    # otherwise, it's:
476    #
477    #    { <self.name>.<self.type> }
478    #
479    def str(self):
480        a = self.action()
481
482        name_dot_type = self.name_ + "." + self.type_
483
484        if a:
485            action_name = a.action_name()
486            ss = [ s.str() for s in a.sources()]
487
488            return "{ %s-%s %s}" % (action_name, name_dot_type, str(ss))
489        else:
490            return "{ " + name_dot_type + " }"
491
492# private:
493
494    def actual_name (self):
495        if not self.actual_name_:
496            self.actual_name_ = '<' + self.grist() + '>' + os.path.normpath(self.name_)
497
498        return self.actual_name_
499
500    def grist (self):
501        """Helper to 'actual_name', above. Compute unique prefix used to distinguish
502            this target from other targets with the same name which create different
503            file.
504        """
505        # Depending on target, there may be different approaches to generating
506        # unique prefixes. We'll generate prefixes in the form
507        # <one letter approach code> <the actual prefix>
508        path = self.path ()
509
510        if path:
511            # The target will be generated to a known path. Just use the path
512            # for identification, since path is as unique as it can get.
513            return 'p' + path
514
515        else:
516            # File is either source, which will be searched for, or is not a file at
517            # all. Use the location of project for distinguishing.
518            project_location = self.project_.get ('location')
519            path_components = b2.util.path.split(project_location)
520            location_grist = '!'.join (path_components)
521
522            if self.action_:
523                ps = self.action_.properties ()
524                property_grist = ps.as_path ()
525                # 'property_grist' can be empty when 'ps' is an empty
526                # property set.
527                if property_grist:
528                    location_grist = location_grist + '/' + property_grist
529
530            return 'l' + location_grist
531
532    def __adjust_name(self, specified_name):
533        """Given the target name specified in constructor, returns the
534        name which should be really used, by looking at the <tag> properties.
535        The tag properties come in two flavour:
536          - <tag>value,
537          - <tag>@rule-name
538        In the first case, value is just added to name
539        In the second case, the specified rule is called with specified name,
540        target type and properties and should return the new name.
541        If not <tag> property is specified, or the rule specified by
542        <tag> returns nothing, returns the result of calling
543        virtual-target.add-suffix"""
544        assert isinstance(specified_name, basestring)
545        if self.action_:
546            ps = self.action_.properties()
547        else:
548            ps = property_set.empty()
549
550        # FIXME: I'm not sure how this is used, need to check with
551        # Rene to figure out how to implement
552        #~ We add ourselves to the properties so that any tag rule can get
553        #~ more direct information about the target than just that available
554        #~ through the properties. This is useful in implementing
555        #~ name changes based on the sources of the target. For example to
556        #~ make unique names of object files based on the source file.
557        #~ --grafik
558        #ps = property_set.create(ps.raw() + ["<target>%s" % "XXXX"])
559        #ps = [ property-set.create [ $(ps).raw ] <target>$(__name__) ] ;
560
561        tag = ps.get("<tag>")
562
563        if tag:
564
565            if len(tag) > 1:
566                get_manager().errors()(
567                    """<tag>@rulename is present but is not the only <tag> feature""")
568
569            tag = tag[0]
570            if callable(tag):
571                self.name_ = tag(specified_name, self.type_, ps)
572            else:
573                if not tag[0] == '@':
574                    self.manager_.errors()("""The value of the <tag> feature must be '@rule-nane'""")
575
576                exported_ps = b2.util.value_to_jam(ps, methods=True)
577                self.name_ = b2.util.call_jam_function(
578                    tag[1:], specified_name, self.type_, exported_ps)
579                if self.name_:
580                    self.name_ = self.name_[0]
581
582        # If there's no tag or the tag rule returned nothing.
583        if not tag or not self.name_:
584            self.name_ = add_prefix_and_suffix(specified_name, self.type_, ps)
585
586    def actualize_no_scanner(self):
587        name = self.actual_name()
588
589        # Do anything only on the first invocation
590        if not self.made_:
591            self.made_[name] = True
592
593            if self.action_:
594                # For non-derived target, we don't care if there
595                # are several virtual targets that refer to the same name.
596                # One case when this is unavoidable is when file name is
597                # main.cpp and two targets have types CPP (for compiling)
598                # and MOCCABLE_CPP (for conversion to H via Qt tools).
599                self.virtual_targets().register_actual_name(name, self)
600
601            for i in self.dependencies_:
602                self.manager_.engine().add_dependency(name, i.actualize())
603
604            self.actualize_location(name)
605            self.actualize_action(name)
606
607        return name
608
609@bjam_signature((["specified_name"], ["type"], ["property_set"]))
610def add_prefix_and_suffix(specified_name, type, property_set):
611    """Appends the suffix appropriate to 'type/property-set' combination
612    to the specified name and returns the result."""
613
614    property_set = b2.util.jam_to_value_maybe(property_set)
615
616    suffix = ""
617    if type:
618        suffix = b2.build.type.generated_target_suffix(type, property_set)
619
620    # Handle suffixes for which no leading dot is desired.  Those are
621    # specified by enclosing them in <...>.  Needed by python so it
622    # can create "_d.so" extensions, for example.
623    if get_grist(suffix):
624        suffix = ungrist(suffix)
625    elif suffix:
626        suffix = "." + suffix
627
628    prefix = ""
629    if type:
630        prefix = b2.build.type.generated_target_prefix(type, property_set)
631
632    if specified_name.startswith(prefix):
633        prefix = ""
634
635    if not prefix:
636        prefix = ""
637    if not suffix:
638        suffix = ""
639    return prefix + specified_name + suffix
640
641
642class FileTarget (AbstractFileTarget):
643    """ File target with explicitly known location.
644
645        The file path is determined as
646           - value passed to the 'set_path' method, if any
647           - for derived files, project's build dir, joined with components
648             that describe action's properties. If the free properties
649             are not equal to the project's reference properties
650             an element with name of main target is added.
651           - for source files, project's source dir
652
653        The file suffix is
654            - the value passed to the 'suffix' method, if any, or
655            - the suffix which correspond to the target's type.
656    """
657    def __init__ (self, name, type, project, action = None, path=None, exact=False):
658        assert isinstance(type, basestring) or type is None
659        assert action is None or isinstance(action, Action)
660        assert isinstance(exact, (int, bool))
661        AbstractFileTarget.__init__ (self, name, type, project, action, exact)
662
663        self.path_ = path
664
665    def __str__(self):
666        if self.type_:
667            return self.name_ + "." + self.type_
668        else:
669            return self.name_
670
671    def clone_with_different_type(self, new_type):
672        assert isinstance(new_type, basestring)
673        return FileTarget(self.name_, new_type, self.project_,
674                          self.action_, self.path_, exact=True)
675
676    def actualize_location (self, target):
677        assert isinstance(target, basestring)
678        engine = self.project_.manager_.engine ()
679
680        if self.action_:
681            # This is a derived file.
682            path = self.path ()
683            engine.set_target_variable (target, 'LOCATE', path)
684
685            # Make sure the path exists.
686            engine.add_dependency (target, path)
687            common.mkdir(engine, path)
688
689            # It's possible that the target name includes a directory
690            # too, for example when installing headers. Create that
691            # directory.
692            d = os.path.dirname(get_value(target))
693            if d:
694                d = os.path.join(path, d)
695                engine.add_dependency(target, d)
696                common.mkdir(engine, d)
697
698            # For real file target, we create a fake target that
699            # depends on the real target. This allows to run
700            #
701            #    bjam hello.o
702            #
703            # without trying to guess the name of the real target.
704            # Note the that target has no directory name, and a special
705            # grist <e>.
706            #
707            # First, that means that "bjam hello.o" will build all
708            # known hello.o targets.
709            # Second, the <e> grist makes sure this target won't be confused
710            # with other targets, for example, if we have subdir 'test'
711            # with target 'test' in it that includes 'test.o' file,
712            # then the target for directory will be just 'test' the target
713            # for test.o will be <ptest/bin/gcc/debug>test.o and the target
714            # we create below will be <e>test.o
715            engine.add_dependency("<e>%s" % get_value(target), target)
716
717            # Allow bjam <path-to-file>/<file> to work.  This won't catch all
718            # possible ways to refer to the path (relative/absolute, extra ".",
719            # various "..", but should help in obvious cases.
720            engine.add_dependency("<e>%s" % (os.path.join(path, get_value(target))), target)
721
722        else:
723            # This is a source file.
724            engine.set_target_variable (target, 'SEARCH', self.project_.get ('source-location'))
725
726
727    def path (self):
728        """ Returns the directory for this target.
729        """
730        if not self.path_:
731            if self.action_:
732                p = self.action_.properties ()
733                (target_path, relative_to_build_dir) = p.target_path ()
734
735                if relative_to_build_dir:
736                    # Indicates that the path is relative to
737                    # build dir.
738                    target_path = os.path.join (self.project_.build_dir (), target_path)
739
740                # Store the computed path, so that it's not recomputed
741                # any more
742                self.path_ = target_path
743
744        return os.path.normpath(self.path_)
745
746
747class NotFileTarget(AbstractFileTarget):
748
749    def __init__(self, name, project, action):
750        assert isinstance(action, Action)
751        AbstractFileTarget.__init__(self, name, None, project, action)
752
753    def path(self):
754        """Returns nothing, to indicate that target path is not known."""
755        return None
756
757    def actualize_location(self, target):
758        assert isinstance(target, basestring)
759        bjam.call("NOTFILE", target)
760        bjam.call("ALWAYS", target)
761        bjam.call("NOUPDATE", target)
762
763
764class Action:
765    """ Class which represents an action.
766        Both 'targets' and 'sources' should list instances of 'VirtualTarget'.
767        Action name should name a rule with this prototype
768            rule action_name ( targets + : sources * : properties * )
769        Targets and sources are passed as actual jam targets. The rule may
770        not establish dependency relationship, but should do everything else.
771    """
772    def __init__ (self, manager, sources, action_name, prop_set):
773        assert is_iterable_typed(sources, VirtualTarget)
774        assert isinstance(action_name, basestring) or action_name is None
775        assert(isinstance(prop_set, property_set.PropertySet))
776        self.sources_ = sources
777        self.action_name_ = action_name
778        if not prop_set:
779            prop_set = property_set.empty()
780        self.properties_ = prop_set
781        if not all(isinstance(v, VirtualTarget) for v in prop_set.get('implicit-dependency')):
782            import pdb
783            pdb.set_trace()
784
785        self.manager_ = manager
786        self.engine_ = self.manager_.engine ()
787        self.targets_ = []
788
789        # Indicates whether this has been actualized or not.
790        self.actualized_ = False
791
792        self.dependency_only_sources_ = []
793        self.actual_sources_ = []
794
795
796    def add_targets (self, targets):
797        assert is_iterable_typed(targets, VirtualTarget)
798        self.targets_ += targets
799
800
801    def replace_targets(self, old_targets, new_targets):
802        assert is_iterable_typed(old_targets, VirtualTarget)
803        assert is_iterable_typed(new_targets, VirtualTarget)
804        self.targets_ = [t for t in self.targets_ if not t in old_targets] + new_targets
805
806    def targets (self):
807        return self.targets_
808
809    def sources (self):
810        return self.sources_
811
812    def action_name (self):
813        return self.action_name_
814
815    def properties (self):
816        return self.properties_
817
818    def actualize (self):
819        """ Generates actual build instructions.
820        """
821        if self.actualized_:
822            return
823
824        self.actualized_ = True
825
826        ps = self.properties ()
827        properties = self.adjust_properties (ps)
828
829
830        actual_targets = []
831
832        for i in self.targets ():
833            actual_targets.append (i.actualize ())
834
835        self.actualize_sources (self.sources (), properties)
836
837        self.engine_.add_dependency (actual_targets, self.actual_sources_ + self.dependency_only_sources_)
838
839        # FIXME: check the comment below. Was self.action_name_ [1]
840        # Action name can include additional rule arguments, which should not
841        # be passed to 'set-target-variables'.
842        # FIXME: breaking circular dependency
843        import toolset
844        toolset.set_target_variables (self.manager_, self.action_name_, actual_targets, properties)
845
846        engine = self.manager_.engine ()
847
848        # FIXME: this is supposed to help --out-xml option, but we don't
849        # implement that now, and anyway, we should handle it in Python,
850        # not but putting variables on bjam-level targets.
851        bjam.call("set-target-variable", actual_targets, ".action", repr(self))
852
853        self.manager_.engine ().set_update_action (self.action_name_, actual_targets, self.actual_sources_,
854                                                   properties)
855
856        # Since we set up creating action here, we also set up
857        # action for cleaning up
858        self.manager_.engine ().set_update_action ('common.Clean', 'clean-all',
859                                                   actual_targets)
860
861        return actual_targets
862
863    def actualize_source_type (self, sources, prop_set):
864        """ Helper for 'actualize_sources'.
865            For each passed source, actualizes it with the appropriate scanner.
866            Returns the actualized virtual targets.
867        """
868        assert is_iterable_typed(sources, VirtualTarget)
869        assert isinstance(prop_set, property_set.PropertySet)
870        result = []
871        for i in sources:
872            scanner = None
873
874# FIXME: what's this?
875#            if isinstance (i, str):
876#                i = self.manager_.get_object (i)
877
878            if i.type ():
879                scanner = b2.build.type.get_scanner (i.type (), prop_set)
880
881            r = i.actualize (scanner)
882            result.append (r)
883
884        return result
885
886    def actualize_sources (self, sources, prop_set):
887        """ Creates actual jam targets for sources. Initializes two member
888            variables:
889            'self.actual_sources_' -- sources which are passed to updating action
890            'self.dependency_only_sources_' -- sources which are made dependencies, but
891            are not used otherwise.
892
893            New values will be *appended* to the variables. They may be non-empty,
894            if caller wants it.
895        """
896        assert is_iterable_typed(sources, VirtualTarget)
897        assert isinstance(prop_set, property_set.PropertySet)
898        dependencies = self.properties_.get ('<dependency>')
899
900        self.dependency_only_sources_ += self.actualize_source_type (dependencies, prop_set)
901        self.actual_sources_ += self.actualize_source_type (sources, prop_set)
902
903        # This is used to help bjam find dependencies in generated headers
904        # in other main targets.
905        # Say:
906        #
907        #   make a.h : ....... ;
908        #   exe hello : hello.cpp : <implicit-dependency>a.h ;
909        #
910        # However, for bjam to find the dependency the generated target must
911        # be actualized (i.e. have the jam target). In the above case,
912        # if we're building just hello ("bjam hello"), 'a.h' won't be
913        # actualized unless we do it here.
914        implicit = self.properties_.get("<implicit-dependency>")
915
916        for i in implicit:
917            i.actualize()
918
919    def adjust_properties (self, prop_set):
920        """ Determines real properties when trying building with 'properties'.
921            This is last chance to fix properties, for example to adjust includes
922            to get generated headers correctly. Default implementation returns
923            its argument.
924        """
925        assert isinstance(prop_set, property_set.PropertySet)
926        return prop_set
927
928
929class NullAction (Action):
930    """ Action class which does nothing --- it produces the targets with
931        specific properties out of nowhere. It's needed to distinguish virtual
932        targets with different properties that are known to exist, and have no
933        actions which create them.
934    """
935    def __init__ (self, manager, prop_set):
936        assert isinstance(prop_set, property_set.PropertySet)
937        Action.__init__ (self, manager, [], None, prop_set)
938
939    def actualize (self):
940        if not self.actualized_:
941            self.actualized_ = True
942
943            for i in self.targets ():
944                i.actualize ()
945
946class NonScanningAction(Action):
947    """Class which acts exactly like 'action', except that the sources
948    are not scanned for dependencies."""
949
950    def __init__(self, sources, action_name, property_set):
951        #FIXME: should the manager parameter of Action.__init__
952        #be removed? -- Steven Watanabe
953        Action.__init__(self, b2.manager.get_manager(), sources, action_name, property_set)
954
955    def actualize_source_type(self, sources, ps=None):
956        assert is_iterable_typed(sources, VirtualTarget)
957        assert isinstance(ps, property_set.PropertySet) or ps is None
958        result = []
959        for s in sources:
960            result.append(s.actualize())
961        return result
962
963def traverse (target, include_roots = False, include_sources = False):
964    """ Traverses the dependency graph of 'target' and return all targets that will
965        be created before this one is created. If root of some dependency graph is
966        found during traversal, it's either included or not, dependencing of the
967        value of 'include_roots'. In either case, sources of root are not traversed.
968    """
969    assert isinstance(target, VirtualTarget)
970    assert isinstance(include_roots, (int, bool))
971    assert isinstance(include_sources, (int, bool))
972    result = []
973
974    if target.action ():
975        action = target.action ()
976
977        # This includes 'target' as well
978        result += action.targets ()
979
980        for t in action.sources ():
981
982            # FIXME:
983            # TODO: see comment in Manager.register_object ()
984            #if not isinstance (t, VirtualTarget):
985            #    t = target.project_.manager_.get_object (t)
986
987            if not t.root ():
988                result += traverse (t, include_roots, include_sources)
989
990            elif include_roots:
991                result.append (t)
992
993    elif include_sources:
994        result.append (target)
995
996    return result
997
998def clone_action (action, new_project, new_action_name, new_properties):
999    """Takes an 'action' instances and creates new instance of it
1000    and all produced target. The rule-name and properties are set
1001    to 'new-rule-name' and 'new-properties', if those are specified.
1002    Returns the cloned action."""
1003    if __debug__:
1004        from .targets import ProjectTarget
1005        assert isinstance(action, Action)
1006        assert isinstance(new_project, ProjectTarget)
1007        assert isinstance(new_action_name, basestring)
1008        assert isinstance(new_properties, property_set.PropertySet)
1009    if not new_action_name:
1010        new_action_name = action.action_name()
1011
1012    if not new_properties:
1013        new_properties = action.properties()
1014
1015    cloned_action = action.__class__(action.manager_, action.sources(), new_action_name,
1016                                     new_properties)
1017
1018    cloned_targets = []
1019    for target in action.targets():
1020
1021        n = target.name()
1022        # Don't modify the name of the produced targets. Strip the directory f
1023        cloned_target = FileTarget(n, target.type(), new_project,
1024                                   cloned_action, exact=True)
1025
1026        d = target.dependencies()
1027        if d:
1028            cloned_target.depends(d)
1029        cloned_target.root(target.root())
1030        cloned_target.creating_subvariant(target.creating_subvariant())
1031
1032        cloned_targets.append(cloned_target)
1033
1034    return cloned_action
1035
1036class Subvariant:
1037
1038    def __init__ (self, main_target, prop_set, sources, build_properties, sources_usage_requirements, created_targets):
1039        """
1040        main_target:                 The instance of MainTarget class
1041        prop_set:                    Properties requested for this target
1042        sources:
1043        build_properties:            Actually used properties
1044        sources_usage_requirements:  Properties propagated from sources
1045        created_targets:             Top-level created targets
1046        """
1047        if __debug__:
1048            from .targets import AbstractTarget
1049            assert isinstance(main_target, AbstractTarget)
1050            assert isinstance(prop_set, property_set.PropertySet)
1051            assert is_iterable_typed(sources, VirtualTarget)
1052            assert isinstance(build_properties, property_set.PropertySet)
1053            assert isinstance(sources_usage_requirements, property_set.PropertySet)
1054            assert is_iterable_typed(created_targets, VirtualTarget)
1055        self.main_target_ = main_target
1056        self.properties_ = prop_set
1057        self.sources_ = sources
1058        self.build_properties_ = build_properties
1059        self.sources_usage_requirements_ = sources_usage_requirements
1060        self.created_targets_ = created_targets
1061
1062        self.usage_requirements_ = None
1063
1064        # Pre-compose the list of other dependency graphs, on which this one
1065        # depends
1066        deps = build_properties.get('<implicit-dependency>')
1067
1068        self.other_dg_ = []
1069        for d in deps:
1070            self.other_dg_.append(d.creating_subvariant ())
1071
1072        self.other_dg_ = unique (self.other_dg_)
1073
1074        self.implicit_includes_cache_ = {}
1075        self.target_directories_ = None
1076
1077    def main_target (self):
1078        return self.main_target_
1079
1080    def created_targets (self):
1081        return self.created_targets_
1082
1083    def requested_properties (self):
1084        return self.properties_
1085
1086    def build_properties (self):
1087        return self.build_properties_
1088
1089    def sources_usage_requirements (self):
1090        return self.sources_usage_requirements_
1091
1092    def set_usage_requirements (self, usage_requirements):
1093        assert isinstance(usage_requirements, property_set.PropertySet)
1094        self.usage_requirements_ = usage_requirements
1095
1096    def usage_requirements (self):
1097        return self.usage_requirements_
1098
1099    def all_referenced_targets(self, result):
1100        """Returns all targets referenced by this subvariant,
1101        either directly or indirectly, and either as sources,
1102        or as dependency properties. Targets referred with
1103        dependency property are returned a properties, not targets."""
1104        if __debug__:
1105            from .property import Property
1106            assert is_iterable_typed(result, (VirtualTarget, Property))
1107        # Find directly referenced targets.
1108        deps = self.build_properties().dependency()
1109        all_targets = self.sources_ + deps
1110
1111        # Find other subvariants.
1112        r = []
1113        for e in all_targets:
1114            if not e in result:
1115                result.add(e)
1116                if isinstance(e, property.Property):
1117                    t = e.value
1118                else:
1119                    t = e
1120
1121                # FIXME: how can this be?
1122                cs = t.creating_subvariant()
1123                if cs:
1124                    r.append(cs)
1125        r = unique(r)
1126        for s in r:
1127            if s != self:
1128                s.all_referenced_targets(result)
1129
1130
1131    def implicit_includes (self, feature, target_type):
1132        """ Returns the properties which specify implicit include paths to
1133            generated headers. This traverses all targets in this subvariant,
1134            and subvariants referred by <implcit-dependecy>properties.
1135            For all targets which are of type 'target-type' (or for all targets,
1136            if 'target_type' is not specified), the result will contain
1137            <$(feature)>path-to-that-target.
1138        """
1139        assert isinstance(feature, basestring)
1140        assert isinstance(target_type, basestring)
1141        if not target_type:
1142            key = feature
1143        else:
1144            key = feature + "-" + target_type
1145
1146
1147        result = self.implicit_includes_cache_.get(key)
1148        if not result:
1149            target_paths = self.all_target_directories(target_type)
1150            target_paths = unique(target_paths)
1151            result = ["<%s>%s" % (feature, p) for p in target_paths]
1152            self.implicit_includes_cache_[key] = result
1153
1154        return result
1155
1156    def all_target_directories(self, target_type = None):
1157        assert isinstance(target_type, (basestring, type(None)))
1158        # TODO: does not appear to use target_type in deciding
1159        # if we've computed this already.
1160        if not self.target_directories_:
1161            self.target_directories_ = self.compute_target_directories(target_type)
1162        return self.target_directories_
1163
1164    def compute_target_directories(self, target_type=None):
1165        assert isinstance(target_type, (basestring, type(None)))
1166        result = []
1167        for t in self.created_targets():
1168            if not target_type or b2.build.type.is_derived(t.type(), target_type):
1169                result.append(t.path())
1170
1171        for d in self.other_dg_:
1172            result.extend(d.all_target_directories(target_type))
1173
1174        result = unique(result)
1175        return result
1176