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