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