]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/tools/build/src/build/virtual_target.py
add subtree-ish sources for 12.0.3
[ceph.git] / ceph / src / boost / tools / build / src / build / virtual_target.py
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
63 import bjam
64
65 import re
66 import os.path
67 import string
68 import types
69
70 from b2.util import path, utility, set, is_iterable_typed
71 from b2.util.utility import add_grist, get_grist, ungrist, replace_grist, get_value
72 from b2.util.sequence import unique
73 from b2.tools import common
74 from b2.exceptions import *
75 import b2.build.type
76 import b2.build.property_set as property_set
77
78 import b2.build.property as property
79
80 from b2.manager import get_manager
81 from b2.util import bjam_signature
82
83 __re_starts_with_at = re.compile ('^@(.*)')
84
85 class 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 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
263 class 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
377 class 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 existance, 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 convertion 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"]))
610 def 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
642 class 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
747 class 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
764 class 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
929 class 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
946 class 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
963 def 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
998 def 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
1036 class 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