1# Status: ported. 2# Base revision: 64488 3 4# Copyright Vladimir Prus 2002-2007. 5# Copyright Rene Rivera 2006. 6# 7# Distributed under the Boost Software License, Version 1.0. 8# (See accompanying file LICENSE_1_0.txt or copy at 9# http://www.boost.org/LICENSE_1_0.txt) 10 11# Supports 'abstract' targets, which are targets explicitly defined in Jamfile. 12# 13# Abstract targets are represented by classes derived from 'AbstractTarget' class. 14# The first abstract target is 'project_target', which is created for each 15# Jamfile, and can be obtained by the 'target' rule in the Jamfile's module. 16# (see project.jam). 17# 18# Project targets keep a list of 'MainTarget' instances. 19# A main target is what the user explicitly defines in a Jamfile. It is 20# possible to have several definitions for a main target, for example to have 21# different lists of sources for different platforms. So, main targets 22# keep a list of alternatives. 23# 24# Each alternative is an instance of 'AbstractTarget'. When a main target 25# subvariant is defined by some rule, that rule will decide what class to 26# use, create an instance of that class and add it to the list of alternatives 27# for the main target. 28# 29# Rules supplied by the build system will use only targets derived 30# from 'BasicTarget' class, which will provide some default behaviour. 31# There will be two classes derived from it, 'make-target', created by the 32# 'make' rule, and 'TypedTarget', created by rules such as 'exe' and 'dll'. 33 34# 35# +------------------------+ 36# |AbstractTarget | 37# +========================+ 38# |name | 39# |project | 40# | | 41# |generate(properties) = 0| 42# +-----------+------------+ 43# | 44# ^ 45# / \ 46# +-+-+ 47# | 48# | 49# +------------------------+------+------------------------------+ 50# | | | 51# | | | 52# +----------+-----------+ +------+------+ +------+-------+ 53# | project_target | | MainTarget | | BasicTarget | 54# +======================+ 1 * +=============+ alternatives +==============+ 55# | generate(properties) |o-----------+ generate |<>------------->| generate | 56# | main-target | +-------------+ | construct = 0| 57# +----------------------+ +--------------+ 58# | 59# ^ 60# / \ 61# +-+-+ 62# | 63# | 64# ...--+----------------+------------------+----------------+---+ 65# | | | | 66# | | | | 67# ... ---+-----+ +------+-------+ +------+------+ +--------+-----+ 68# | | TypedTarget | | make-target | | stage-target | 69# . +==============+ +=============+ +==============+ 70# . | construct | | construct | | construct | 71# +--------------+ +-------------+ +--------------+ 72 73import re 74import os.path 75import sys 76 77from b2.manager import get_manager 78 79from b2.util.utility import * 80import property, project, virtual_target, property_set, feature, generators, toolset 81from virtual_target import Subvariant 82from b2.exceptions import * 83from b2.util.sequence import unique 84from b2.util import path, bjam_signature 85from b2.build.errors import user_error_checkpoint 86 87import b2.build.build_request as build_request 88 89import b2.util.set 90_re_separate_target_from_properties = re.compile (r'^([^<]*)(/(<.*))?$') 91 92class TargetRegistry: 93 94 def __init__ (self): 95 # All targets that are currently being built. 96 # Only the key is id (target), the value is the actual object. 97 self.targets_being_built_ = {} 98 99 # Current indent for debugging messages 100 self.indent_ = "" 101 102 self.debug_building_ = "--debug-building" in bjam.variable("ARGV") 103 104 self.targets_ = [] 105 106 def main_target_alternative (self, target): 107 """ Registers the specified target as a main target alternatives. 108 Returns 'target'. 109 """ 110 target.project ().add_alternative (target) 111 return target 112 113 def main_target_sources (self, sources, main_target_name, no_renaming=0): 114 """Return the list of sources to use, if main target rule is invoked 115 with 'sources'. If there are any objects in 'sources', they are treated 116 as main target instances, and the name of such targets are adjusted to 117 be '<name_of_this_target>__<name_of_source_target>'. Such renaming 118 is disabled is non-empty value is passed for 'no-renaming' parameter.""" 119 result = [] 120 121 for t in sources: 122 123 t = b2.util.jam_to_value_maybe(t) 124 125 if isinstance (t, AbstractTarget): 126 name = t.name () 127 128 if not no_renaming: 129 name = main_target_name + '__' + name 130 t.rename (name) 131 132 # Inline targets are not built by default. 133 p = t.project() 134 p.mark_targets_as_explicit([name]) 135 result.append(name) 136 137 else: 138 result.append (t) 139 140 return result 141 142 143 def main_target_requirements(self, specification, project): 144 """Returns the requirement to use when declaring a main target, 145 which are obtained by 146 - translating all specified property paths, and 147 - refining project requirements with the one specified for the target 148 149 'specification' are the properties xplicitly specified for a 150 main target 151 'project' is the project where the main taret is to be declared.""" 152 153 specification.extend(toolset.requirements()) 154 155 requirements = property_set.refine_from_user_input( 156 project.get("requirements"), specification, 157 project.project_module(), project.get("location")) 158 159 return requirements 160 161 def main_target_usage_requirements (self, specification, project): 162 """ Returns the use requirement to use when declaraing a main target, 163 which are obtained by 164 - translating all specified property paths, and 165 - adding project's usage requirements 166 specification: Use-properties explicitly specified for a main target 167 project: Project where the main target is to be declared 168 """ 169 project_usage_requirements = project.get ('usage-requirements') 170 171 # We don't use 'refine-from-user-input' because I'm not sure if: 172 # - removing of parent's usage requirements makes sense 173 # - refining of usage requirements is not needed, since usage requirements 174 # are always free. 175 usage_requirements = property_set.create_from_user_input( 176 specification, project.project_module(), project.get("location")) 177 178 return project_usage_requirements.add (usage_requirements) 179 180 def main_target_default_build (self, specification, project): 181 """ Return the default build value to use when declaring a main target, 182 which is obtained by using specified value if not empty and parent's 183 default build attribute otherwise. 184 specification: Default build explicitly specified for a main target 185 project: Project where the main target is to be declared 186 """ 187 if specification: 188 return property_set.create_with_validation(specification) 189 else: 190 return project.get ('default-build') 191 192 def start_building (self, main_target_instance): 193 """ Helper rules to detect cycles in main target references. 194 """ 195 if self.targets_being_built_.has_key(id(main_target_instance)): 196 names = [] 197 for t in self.targets_being_built_.values() + [main_target_instance]: 198 names.append (t.full_name()) 199 200 get_manager().errors()("Recursion in main target references\n") 201 202 self.targets_being_built_[id(main_target_instance)] = main_target_instance 203 204 def end_building (self, main_target_instance): 205 assert (self.targets_being_built_.has_key (id (main_target_instance))) 206 del self.targets_being_built_ [id (main_target_instance)] 207 208 def create_typed_target (self, type, project, name, sources, requirements, default_build, usage_requirements): 209 """ Creates a TypedTarget with the specified properties. 210 The 'name', 'sources', 'requirements', 'default_build' and 211 'usage_requirements' are assumed to be in the form specified 212 by the user in Jamfile corresponding to 'project'. 213 """ 214 return self.main_target_alternative (TypedTarget (name, project, type, 215 self.main_target_sources (sources, name), 216 self.main_target_requirements (requirements, project), 217 self.main_target_default_build (default_build, project), 218 self.main_target_usage_requirements (usage_requirements, project))) 219 220 def increase_indent(self): 221 self.indent_ += " " 222 223 def decrease_indent(self): 224 self.indent_ = self.indent_[0:-4] 225 226 def logging(self): 227 return self.debug_building_ 228 229 def log(self, message): 230 if self.debug_building_: 231 print self.indent_ + message 232 233 def push_target(self, target): 234 self.targets_.append(target) 235 236 def pop_target(self): 237 self.targets_ = self.targets_[:-1] 238 239 def current(self): 240 return self.targets_[0] 241 242 243class GenerateResult: 244 245 def __init__ (self, ur=None, targets=None): 246 if not targets: 247 targets = [] 248 249 self.__usage_requirements = ur 250 self.__targets = targets 251 assert all(isinstance(t, virtual_target.VirtualTarget) for t in targets) 252 253 if not self.__usage_requirements: 254 self.__usage_requirements = property_set.empty () 255 256 def usage_requirements (self): 257 return self.__usage_requirements 258 259 def targets (self): 260 return self.__targets 261 262 def extend (self, other): 263 assert (isinstance (other, GenerateResult)) 264 265 self.__usage_requirements = self.__usage_requirements.add (other.usage_requirements ()) 266 self.__targets.extend (other.targets ()) 267 268class AbstractTarget: 269 """ Base class for all abstract targets. 270 """ 271 def __init__ (self, name, project, manager = None): 272 """ manager: the Manager object 273 name: name of the target 274 project: the project target to which this one belongs 275 manager:the manager object. If none, uses project.manager () 276 """ 277 assert (isinstance (project, ProjectTarget)) 278 # Note: it might seem that we don't need either name or project at all. 279 # However, there are places where we really need it. One example is error 280 # messages which should name problematic targets. Another is setting correct 281 # paths for sources and generated files. 282 283 # Why allow manager to be specified? Because otherwise project target could not derive 284 # from this class. 285 if manager: 286 self.manager_ = manager 287 else: 288 self.manager_ = project.manager () 289 290 self.name_ = name 291 self.project_ = project 292 293 def manager (self): 294 return self.manager_ 295 296 def name (self): 297 """ Returns the name of this target. 298 """ 299 return self.name_ 300 301 def project (self): 302 """ Returns the project for this target. 303 """ 304 return self.project_ 305 306 def location (self): 307 """ Return the location where the target was declared. 308 """ 309 return self.location_ 310 311 def full_name (self): 312 """ Returns a user-readable name for this target. 313 """ 314 location = self.project ().get ('location') 315 return location + '/' + self.name_ 316 317 def generate (self, property_set): 318 """ Takes a property set. Generates virtual targets for this abstract 319 target, using the specified properties, unless a different value of some 320 feature is required by the target. 321 On success, returns a GenerateResult instance with: 322 - a property_set with the usage requirements to be 323 applied to dependents 324 - a list of produced virtual targets, which may be 325 empty. 326 If 'property_set' is empty, performs default build of this 327 target, in a way specific to derived class. 328 """ 329 raise BaseException ("method should be defined in derived classes") 330 331 def rename (self, new_name): 332 self.name_ = new_name 333 334class ProjectTarget (AbstractTarget): 335 """ Project target class (derived from 'AbstractTarget') 336 337 This class these responsibilities: 338 - maintaining a list of main target in this project and 339 building it 340 341 Main targets are constructed in two stages: 342 - When Jamfile is read, a number of calls to 'add_alternative' is made. 343 At that time, alternatives can also be renamed to account for inline 344 targets. 345 - The first time 'main-target' or 'has-main-target' rule is called, 346 all alternatives are enumerated an main targets are created. 347 """ 348 def __init__ (self, manager, name, project_module, parent_project, requirements, default_build): 349 AbstractTarget.__init__ (self, name, self, manager) 350 351 self.project_module_ = project_module 352 self.location_ = manager.projects().attribute (project_module, 'location') 353 self.requirements_ = requirements 354 self.default_build_ = default_build 355 356 self.build_dir_ = None 357 358 # A cache of IDs 359 self.ids_cache_ = {} 360 361 # True is main targets have already been built. 362 self.built_main_targets_ = False 363 364 # A list of the registered alternatives for this project. 365 self.alternatives_ = [] 366 367 # A map from main target name to the target corresponding 368 # to it. 369 self.main_target_ = {} 370 371 # Targets marked as explicit. 372 self.explicit_targets_ = set() 373 374 # Targets marked as always 375 self.always_targets_ = set() 376 377 # The constants defined for this project. 378 self.constants_ = {} 379 380 # Whether targets for all main target are already created. 381 self.built_main_targets_ = 0 382 383 if parent_project: 384 self.inherit (parent_project) 385 386 387 # TODO: This is needed only by the 'make' rule. Need to find the 388 # way to make 'make' work without this method. 389 def project_module (self): 390 return self.project_module_ 391 392 def get (self, attribute): 393 return self.manager().projects().attribute( 394 self.project_module_, attribute) 395 396 def build_dir (self): 397 if not self.build_dir_: 398 self.build_dir_ = self.get ('build-dir') 399 if not self.build_dir_: 400 self.build_dir_ = os.path.join(self.project_.get ('location'), 'bin') 401 402 return self.build_dir_ 403 404 def generate (self, ps): 405 """ Generates all possible targets contained in this project. 406 """ 407 self.manager_.targets().log( 408 "Building project '%s' with '%s'" % (self.name (), str(ps))) 409 self.manager_.targets().increase_indent () 410 411 result = GenerateResult () 412 413 for t in self.targets_to_build (): 414 g = t.generate (ps) 415 result.extend (g) 416 417 self.manager_.targets().decrease_indent () 418 return result 419 420 def targets_to_build (self): 421 """ Computes and returns a list of AbstractTarget instances which 422 must be built when this project is built. 423 """ 424 result = [] 425 426 if not self.built_main_targets_: 427 self.build_main_targets () 428 429 # Collect all main targets here, except for "explicit" ones. 430 for n, t in self.main_target_.iteritems (): 431 if not t.name () in self.explicit_targets_: 432 result.append (t) 433 434 # Collect all projects referenced via "projects-to-build" attribute. 435 self_location = self.get ('location') 436 for pn in self.get ('projects-to-build'): 437 result.append (self.find(pn + "/")) 438 439 return result 440 441 def mark_targets_as_explicit (self, target_names): 442 """Add 'target' to the list of targets in this project 443 that should be build only by explicit request.""" 444 445 # Record the name of the target, not instance, since this 446 # rule is called before main target instaces are created. 447 self.explicit_targets_.update(target_names) 448 449 def mark_targets_as_always(self, target_names): 450 self.always_targets_.update(target_names) 451 452 def add_alternative (self, target_instance): 453 """ Add new target alternative. 454 """ 455 if self.built_main_targets_: 456 raise IllegalOperation ("add-alternative called when main targets are already created for project '%s'" % self.full_name ()) 457 458 self.alternatives_.append (target_instance) 459 460 def main_target (self, name): 461 if not self.built_main_targets_: 462 self.build_main_targets() 463 464 return self.main_target_[name] 465 466 def has_main_target (self, name): 467 """Tells if a main target with the specified name exists.""" 468 if not self.built_main_targets_: 469 self.build_main_targets() 470 471 return self.main_target_.has_key(name) 472 473 def create_main_target (self, name): 474 """ Returns a 'MainTarget' class instance corresponding to the 'name'. 475 """ 476 if not self.built_main_targets_: 477 self.build_main_targets () 478 479 return self.main_targets_.get (name, None) 480 481 482 def find_really(self, id): 483 """ Find and return the target with the specified id, treated 484 relative to self. 485 """ 486 result = None 487 current_location = self.get ('location') 488 489 __re_split_project_target = re.compile (r'(.*)//(.*)') 490 split = __re_split_project_target.match (id) 491 492 project_part = None 493 target_part = None 494 495 if split: 496 project_part = split.group (1) 497 target_part = split.group (2) 498 499 project_registry = self.project_.manager ().projects () 500 501 extra_error_message = '' 502 if project_part: 503 # There's explicit project part in id. Looks up the 504 # project and pass the request to it. 505 pm = project_registry.find (project_part, current_location) 506 507 if pm: 508 project_target = project_registry.target (pm) 509 result = project_target.find (target_part, no_error=1) 510 511 else: 512 extra_error_message = "error: could not find project '$(project_part)'" 513 514 else: 515 # Interpret target-name as name of main target 516 # Need to do this before checking for file. Consider this: 517 # 518 # exe test : test.cpp ; 519 # install s : test : <location>. ; 520 # 521 # After first build we'll have target 'test' in Jamfile and file 522 # 'test' on the disk. We need target to override the file. 523 524 result = None 525 if self.has_main_target(id): 526 result = self.main_target(id) 527 528 if not result: 529 result = FileReference (self.manager_, id, self.project_) 530 if not result.exists (): 531 # File actually does not exist. 532 # Reset 'target' so that an error is issued. 533 result = None 534 535 536 if not result: 537 # Interpret id as project-id 538 project_module = project_registry.find (id, current_location) 539 if project_module: 540 result = project_registry.target (project_module) 541 542 return result 543 544 def find (self, id, no_error = False): 545 v = self.ids_cache_.get (id, None) 546 547 if not v: 548 v = self.find_really (id) 549 self.ids_cache_ [id] = v 550 551 if v or no_error: 552 return v 553 554 raise BaseException ("Unable to find file or target named '%s'\nreferred from project at '%s'" % (id, self.get ('location'))) 555 556 557 def build_main_targets (self): 558 self.built_main_targets_ = True 559 560 for a in self.alternatives_: 561 name = a.name () 562 if not self.main_target_.has_key (name): 563 t = MainTarget (name, self.project_) 564 self.main_target_ [name] = t 565 566 if name in self.always_targets_: 567 a.always() 568 569 self.main_target_ [name].add_alternative (a) 570 571 def add_constant(self, name, value, path=0): 572 """Adds a new constant for this project. 573 574 The constant will be available for use in Jamfile 575 module for this project. If 'path' is true, 576 the constant will be interpreted relatively 577 to the location of project. 578 """ 579 580 if path: 581 l = self.location_ 582 if not l: 583 # Project corresponding to config files do not have 584 # 'location' attribute, but do have source location. 585 # It might be more reasonable to make every project have 586 # a location and use some other approach to prevent buildable 587 # targets in config files, but that's for later. 588 l = get('source-location') 589 590 value = os.path.join(l, value) 591 # Now make the value absolute path. Constants should be in 592 # platform-native form. 593 value = os.path.normpath(os.path.join(os.getcwd(), value)) 594 595 self.constants_[name] = value 596 bjam.call("set-variable", self.project_module(), name, value) 597 598 def inherit(self, parent_project): 599 for c in parent_project.constants_: 600 # No need to pass the type. Path constants were converted to 601 # absolute paths already by parent. 602 self.add_constant(c, parent_project.constants_[c]) 603 604 # Import rules from parent 605 this_module = self.project_module() 606 parent_module = parent_project.project_module() 607 608 rules = bjam.call("RULENAMES", parent_module) 609 if not rules: 610 rules = [] 611 user_rules = [x for x in rules 612 if x not in self.manager().projects().project_rules().all_names()] 613 if user_rules: 614 bjam.call("import-rules-from-parent", parent_module, this_module, user_rules) 615 616class MainTarget (AbstractTarget): 617 """ A named top-level target in Jamfile. 618 """ 619 def __init__ (self, name, project): 620 AbstractTarget.__init__ (self, name, project) 621 self.alternatives_ = [] 622 self.default_build_ = property_set.empty () 623 624 def add_alternative (self, target): 625 """ Add a new alternative for this target. 626 """ 627 d = target.default_build () 628 629 if self.alternatives_ and self.default_build_ != d: 630 get_manager().errors()("default build must be identical in all alternatives\n" 631 "main target is '%s'\n" 632 "with '%s'\n" 633 "differing from previous default build: '%s'" % (self.full_name (), d.raw (), self.default_build_.raw ())) 634 635 else: 636 self.default_build_ = d 637 638 self.alternatives_.append (target) 639 640 def __select_alternatives (self, property_set, debug): 641 """ Returns the best viable alternative for this property_set 642 See the documentation for selection rules. 643 # TODO: shouldn't this be 'alternative' (singular)? 644 """ 645 # When selecting alternatives we have to consider defaults, 646 # for example: 647 # lib l : l.cpp : <variant>debug ; 648 # lib l : l_opt.cpp : <variant>release ; 649 # won't work unless we add default value <variant>debug. 650 property_set = property_set.add_defaults () 651 652 # The algorithm: we keep the current best viable alternative. 653 # When we've got new best viable alternative, we compare it 654 # with the current one. 655 best = None 656 best_properties = None 657 658 if len (self.alternatives_) == 0: 659 return None 660 661 if len (self.alternatives_) == 1: 662 return self.alternatives_ [0] 663 664 if debug: 665 print "Property set for selection:", property_set 666 667 for v in self.alternatives_: 668 properties = v.match (property_set, debug) 669 670 if properties is not None: 671 if not best: 672 best = v 673 best_properties = properties 674 675 else: 676 if b2.util.set.equal (properties, best_properties): 677 return None 678 679 elif b2.util.set.contains (properties, best_properties): 680 # Do nothing, this alternative is worse 681 pass 682 683 elif b2.util.set.contains (best_properties, properties): 684 best = v 685 best_properties = properties 686 687 else: 688 return None 689 690 return best 691 692 def apply_default_build (self, property_set): 693 return apply_default_build(property_set, self.default_build_) 694 695 def generate (self, ps): 696 """ Select an alternative for this main target, by finding all alternatives 697 which requirements are satisfied by 'properties' and picking the one with 698 longest requirements set. 699 Returns the result of calling 'generate' on that alternative. 700 """ 701 self.manager_.targets ().start_building (self) 702 703 # We want composite properties in build request act as if 704 # all the properties it expands too are explicitly specified. 705 ps = ps.expand () 706 707 all_property_sets = self.apply_default_build (ps) 708 709 result = GenerateResult () 710 711 for p in all_property_sets: 712 result.extend (self.__generate_really (p)) 713 714 self.manager_.targets ().end_building (self) 715 716 return result 717 718 def __generate_really (self, prop_set): 719 """ Generates the main target with the given property set 720 and returns a list which first element is property_set object 721 containing usage_requirements of generated target and with 722 generated virtual target in other elements. It's possible 723 that no targets are generated. 724 """ 725 best_alternative = self.__select_alternatives (prop_set, debug=0) 726 727 if not best_alternative: 728 # FIXME: revive. 729 # self.__select_alternatives(prop_set, debug=1) 730 self.manager_.errors()( 731 "No best alternative for '%s'.\n" 732 % (self.full_name(),)) 733 734 result = best_alternative.generate (prop_set) 735 736 # Now return virtual targets for the only alternative 737 return result 738 739 def rename(self, new_name): 740 AbstractTarget.rename(self, new_name) 741 for a in self.alternatives_: 742 a.rename(new_name) 743 744class FileReference (AbstractTarget): 745 """ Abstract target which refers to a source file. 746 This is artificial creature; it's usefull so that sources to 747 a target can be represented as list of abstract target instances. 748 """ 749 def __init__ (self, manager, file, project): 750 AbstractTarget.__init__ (self, file, project) 751 self.file_location_ = None 752 753 def generate (self, properties): 754 return GenerateResult (None, [ 755 self.manager_.virtual_targets ().from_file ( 756 self.name_, self.location(), self.project_) ]) 757 758 def exists (self): 759 """ Returns true if the referred file really exists. 760 """ 761 if self.location (): 762 return True 763 else: 764 return False 765 766 def location (self): 767 # Returns the location of target. Needed by 'testing.jam' 768 if not self.file_location_: 769 source_location = self.project_.get('source-location') 770 771 for src_dir in source_location: 772 location = os.path.join(src_dir, self.name()) 773 if os.path.isfile(location): 774 self.file_location_ = src_dir 775 self.file_path = location 776 break 777 778 return self.file_location_ 779 780def resolve_reference(target_reference, project): 781 """ Given a target_reference, made in context of 'project', 782 returns the AbstractTarget instance that is referred to, as well 783 as properties explicitly specified for this reference. 784 """ 785 # Separate target name from properties override 786 split = _re_separate_target_from_properties.match (target_reference) 787 if not split: 788 raise BaseException ("Invalid reference: '%s'" % target_reference) 789 790 id = split.group (1) 791 792 sproperties = [] 793 794 if split.group (3): 795 sproperties = property.create_from_strings(feature.split(split.group(3))) 796 sproperties = feature.expand_composites(sproperties) 797 798 # Find the target 799 target = project.find (id) 800 801 return (target, property_set.create(sproperties)) 802 803def generate_from_reference(target_reference, project, property_set): 804 """ Attempts to generate the target given by target reference, which 805 can refer both to a main target or to a file. 806 Returns a list consisting of 807 - usage requirements 808 - generated virtual targets, if any 809 target_reference: Target reference 810 project: Project where the reference is made 811 property_set: Properties of the main target that makes the reference 812 """ 813 target, sproperties = resolve_reference(target_reference, project) 814 815 # Take properties which should be propagated and refine them 816 # with source-specific requirements. 817 propagated = property_set.propagated() 818 rproperties = propagated.refine(sproperties) 819 820 return target.generate(rproperties) 821 822 823 824class BasicTarget (AbstractTarget): 825 """ Implements the most standard way of constructing main target 826 alternative from sources. Allows sources to be either file or 827 other main target and handles generation of those dependency 828 targets. 829 """ 830 def __init__ (self, name, project, sources, requirements = None, default_build = None, usage_requirements = None): 831 AbstractTarget.__init__ (self, name, project) 832 833 for s in sources: 834 if get_grist (s): 835 raise InvalidSource ("property '%s' found in the 'sources' parameter for '%s'" % (s, name)) 836 837 self.sources_ = sources 838 839 if not requirements: requirements = property_set.empty () 840 self.requirements_ = requirements 841 842 if not default_build: default_build = property_set.empty () 843 self.default_build_ = default_build 844 845 if not usage_requirements: usage_requirements = property_set.empty () 846 self.usage_requirements_ = usage_requirements 847 848 # A cache for resolved references 849 self.source_targets_ = None 850 851 # A cache for generated targets 852 self.generated_ = {} 853 854 # A cache for build requests 855 self.request_cache = {} 856 857 # Result of 'capture_user_context' has everything. For example, if this 858 # target is declare as result of loading Jamfile which was loaded when 859 # building target B which was requested from A, then we'll have A, B and 860 # Jamroot location in context. We only care about Jamroot location, most 861 # of the times. 862 self.user_context_ = self.manager_.errors().capture_user_context()[-1:] 863 864 self.always_ = False 865 866 def always(self): 867 self.always_ = True 868 869 def sources (self): 870 """ Returns the list of AbstractTargets which are used as sources. 871 The extra properties specified for sources are not represented. 872 The only used of this rule at the moment is the '--dump-tests' 873 feature of the test system. 874 """ 875 if self.source_targets_ == None: 876 self.source_targets_ = [] 877 for s in self.sources_: 878 self.source_targets_.append(resolve_reference(s, self.project_)[0]) 879 880 return self.source_targets_ 881 882 def requirements (self): 883 return self.requirements_ 884 885 def default_build (self): 886 return self.default_build_ 887 888 def common_properties (self, build_request, requirements): 889 """ Given build request and requirements, return properties 890 common to dependency build request and target build 891 properties. 892 """ 893 # For optimization, we add free unconditional requirements directly, 894 # without using complex algorithsm. 895 # This gives the complex algorithm better chance of caching results. 896 # The exact effect of this "optimization" is no longer clear 897 free_unconditional = [] 898 other = [] 899 for p in requirements.all(): 900 if p.feature().free() and not p.condition() and p.feature().name() != 'conditional': 901 free_unconditional.append(p) 902 else: 903 other.append(p) 904 other = property_set.create(other) 905 906 key = (build_request, other) 907 if not self.request_cache.has_key(key): 908 self.request_cache[key] = self.__common_properties2 (build_request, other) 909 910 return self.request_cache[key].add_raw(free_unconditional) 911 912 # Given 'context' -- a set of already present properties, and 'requirements', 913 # decide which extra properties should be applied to 'context'. 914 # For conditional requirements, this means evaluating condition. For 915 # indirect conditional requirements, this means calling a rule. Ordinary 916 # requirements are always applied. 917 # 918 # Handles situation where evaluating one conditional requirements affects 919 # condition of another conditional requirements, for example: 920 # 921 # <toolset>gcc:<variant>release <variant>release:<define>RELEASE 922 # 923 # If 'what' is 'refined' returns context refined with new requirements. 924 # If 'what' is 'added' returns just the requirements that must be applied. 925 def evaluate_requirements(self, requirements, context, what): 926 # Apply non-conditional requirements. 927 # It's possible that that further conditional requirement change 928 # a value set by non-conditional requirements. For example: 929 # 930 # exe a : a.cpp : <threading>single <toolset>foo:<threading>multi ; 931 # 932 # I'm not sure if this should be an error, or not, especially given that 933 # 934 # <threading>single 935 # 936 # might come from project's requirements. 937 unconditional = feature.expand(requirements.non_conditional()) 938 939 context = context.refine(property_set.create(unconditional)) 940 941 # We've collected properties that surely must be present in common 942 # properties. We now try to figure out what other properties 943 # should be added in order to satisfy rules (4)-(6) from the docs. 944 945 conditionals = property_set.create(requirements.conditional()) 946 947 # It's supposed that #conditionals iterations 948 # should be enough for properties to propagate along conditions in any 949 # direction. 950 max_iterations = len(conditionals.all()) +\ 951 len(requirements.get("<conditional>")) + 1 952 953 added_requirements = [] 954 current = context 955 956 # It's assumed that ordinary conditional requirements can't add 957 # <indirect-conditional> properties, and that rules referred 958 # by <indirect-conditional> properties can't add new 959 # <indirect-conditional> properties. So the list of indirect conditionals 960 # does not change. 961 indirect = requirements.get("<conditional>") 962 963 ok = 0 964 for i in range(0, max_iterations): 965 966 e = conditionals.evaluate_conditionals(current).all()[:] 967 968 # Evaluate indirect conditionals. 969 for i in indirect: 970 i = b2.util.jam_to_value_maybe(i) 971 if callable(i): 972 # This is Python callable, yeah. 973 e.extend(i(current)) 974 else: 975 # Name of bjam function. Because bjam is unable to handle 976 # list of Property, pass list of strings. 977 br = b2.util.call_jam_function(i[1:], [str(p) for p in current.all()]) 978 if br: 979 e.extend(property.create_from_strings(br)) 980 981 if e == added_requirements: 982 # If we got the same result, we've found final properties. 983 ok = 1 984 break 985 else: 986 # Oops, results of evaluation of conditionals has changed. 987 # Also 'current' contains leftover from previous evaluation. 988 # Recompute 'current' using initial properties and conditional 989 # requirements. 990 added_requirements = e 991 current = context.refine(property_set.create(feature.expand(e))) 992 993 if not ok: 994 self.manager().errors()("Can't evaluate conditional properties " 995 + str(conditionals)) 996 997 998 if what == "added": 999 return property_set.create(unconditional + added_requirements) 1000 elif what == "refined": 1001 return current 1002 else: 1003 self.manager().errors("Invalid value of the 'what' parameter") 1004 1005 def __common_properties2(self, build_request, requirements): 1006 # This guarantees that default properties are present 1007 # in result, unless they are overrided by some requirement. 1008 # TODO: There is possibility that we've added <foo>bar, which is composite 1009 # and expands to <foo2>bar2, but default value of <foo2> is not bar2, 1010 # in which case it's not clear what to do. 1011 # 1012 build_request = build_request.add_defaults() 1013 # Featured added by 'add-default' can be composite and expand 1014 # to features without default values -- so they are not added yet. 1015 # It could be clearer/faster to expand only newly added properties 1016 # but that's not critical. 1017 build_request = build_request.expand() 1018 1019 return self.evaluate_requirements(requirements, build_request, 1020 "refined") 1021 1022 def match (self, property_set, debug): 1023 """ Returns the alternative condition for this alternative, if 1024 the condition is satisfied by 'property_set'. 1025 """ 1026 # The condition is composed of all base non-conditional properties. 1027 # It's not clear if we should expand 'self.requirements_' or not. 1028 # For one thing, it would be nice to be able to put 1029 # <toolset>msvc-6.0 1030 # in requirements. 1031 # On the other hand, if we have <variant>release in condition it 1032 # does not make sense to require <optimization>full to be in 1033 # build request just to select this variant. 1034 bcondition = self.requirements_.base () 1035 ccondition = self.requirements_.conditional () 1036 condition = b2.util.set.difference (bcondition, ccondition) 1037 1038 if debug: 1039 print " next alternative: required properties:", [str(p) for p in condition] 1040 1041 if b2.util.set.contains (condition, property_set.all()): 1042 1043 if debug: 1044 print " matched" 1045 1046 return condition 1047 1048 else: 1049 return None 1050 1051 1052 def generate_dependency_targets (self, target_ids, property_set): 1053 targets = [] 1054 usage_requirements = [] 1055 for id in target_ids: 1056 1057 result = generate_from_reference(id, self.project_, property_set) 1058 targets += result.targets() 1059 usage_requirements += result.usage_requirements().all() 1060 1061 return (targets, usage_requirements) 1062 1063 def generate_dependency_properties(self, properties, ps): 1064 """ Takes a target reference, which might be either target id 1065 or a dependency property, and generates that target using 1066 'property_set' as build request. 1067 1068 Returns a tuple (result, usage_requirements). 1069 """ 1070 result_properties = [] 1071 usage_requirements = [] 1072 for p in properties: 1073 1074 result = generate_from_reference(p.value(), self.project_, ps) 1075 1076 for t in result.targets(): 1077 result_properties.append(property.Property(p.feature(), t)) 1078 1079 usage_requirements += result.usage_requirements().all() 1080 1081 return (result_properties, usage_requirements) 1082 1083 1084 1085 1086 @user_error_checkpoint 1087 def generate (self, ps): 1088 """ Determines final build properties, generates sources, 1089 and calls 'construct'. This method should not be 1090 overridden. 1091 """ 1092 self.manager_.errors().push_user_context( 1093 "Generating target " + self.full_name(), self.user_context_) 1094 1095 if self.manager().targets().logging(): 1096 self.manager().targets().log( 1097 "Building target '%s'" % self.name_) 1098 self.manager().targets().increase_indent () 1099 self.manager().targets().log( 1100 "Build request: '%s'" % str (ps.raw ())) 1101 cf = self.manager().command_line_free_features() 1102 self.manager().targets().log( 1103 "Command line free features: '%s'" % str (cf.raw ())) 1104 self.manager().targets().log( 1105 "Target requirements: %s'" % str (self.requirements().raw ())) 1106 1107 self.manager().targets().push_target(self) 1108 1109 if not self.generated_.has_key(ps): 1110 1111 # Apply free features form the command line. If user 1112 # said 1113 # define=FOO 1114 # he most likely want this define to be set for all compiles. 1115 ps = ps.refine(self.manager().command_line_free_features()) 1116 rproperties = self.common_properties (ps, self.requirements_) 1117 1118 self.manager().targets().log( 1119 "Common properties are '%s'" % str (rproperties)) 1120 1121 if rproperties.get("<build>") != ["no"]: 1122 1123 result = GenerateResult () 1124 1125 properties = rproperties.non_dependency () 1126 1127 (p, u) = self.generate_dependency_properties (rproperties.dependency (), rproperties) 1128 properties += p 1129 assert all(isinstance(p, property.Property) for p in properties) 1130 usage_requirements = u 1131 1132 (source_targets, u) = self.generate_dependency_targets (self.sources_, rproperties) 1133 usage_requirements += u 1134 1135 self.manager_.targets().log( 1136 "Usage requirements for '%s' are '%s'" % (self.name_, usage_requirements)) 1137 1138 # FIXME: 1139 1140 rproperties = property_set.create(properties + usage_requirements) 1141 usage_requirements = property_set.create (usage_requirements) 1142 1143 self.manager_.targets().log( 1144 "Build properties: '%s'" % str(rproperties)) 1145 1146 source_targets += rproperties.get('<source>') 1147 1148 # We might get duplicate sources, for example if 1149 # we link to two library which have the same <library> in 1150 # usage requirements. 1151 # Use stable sort, since for some targets the order is 1152 # important. E.g. RUN_PY target need python source to come 1153 # first. 1154 source_targets = unique(source_targets, stable=True) 1155 1156 # FIXME: figure why this call messes up source_targets in-place 1157 result = self.construct (self.name_, source_targets[:], rproperties) 1158 1159 if result: 1160 assert len(result) == 2 1161 gur = result [0] 1162 result = result [1] 1163 1164 if self.always_: 1165 for t in result: 1166 t.always() 1167 1168 s = self.create_subvariant ( 1169 result, 1170 self.manager().virtual_targets().recent_targets(), ps, 1171 source_targets, rproperties, usage_requirements) 1172 self.manager().virtual_targets().clear_recent_targets() 1173 1174 ur = self.compute_usage_requirements (s) 1175 ur = ur.add (gur) 1176 s.set_usage_requirements (ur) 1177 1178 self.manager_.targets().log ( 1179 "Usage requirements from '%s' are '%s'" % 1180 (self.name(), str(rproperties))) 1181 1182 self.generated_[ps] = GenerateResult (ur, result) 1183 else: 1184 self.generated_[ps] = GenerateResult (property_set.empty(), []) 1185 else: 1186 # If we just see <build>no, we cannot produce any reasonable 1187 # diagnostics. The code that adds this property is expected 1188 # to explain why a target is not built, for example using 1189 # the configure.log-component-configuration function. 1190 1191 # If this target fails to build, add <build>no to properties 1192 # to cause any parent target to fail to build. Except that it 1193 # - does not work now, since we check for <build>no only in 1194 # common properties, but not in properties that came from 1195 # dependencies 1196 # - it's not clear if that's a good idea anyway. The alias 1197 # target, for example, should not fail to build if a dependency 1198 # fails. 1199 self.generated_[ps] = GenerateResult( 1200 property_set.create(["<build>no"]), []) 1201 else: 1202 self.manager().targets().log ("Already built") 1203 1204 self.manager().targets().pop_target() 1205 self.manager().targets().decrease_indent() 1206 1207 return self.generated_[ps] 1208 1209 def compute_usage_requirements (self, subvariant): 1210 """ Given the set of generated targets, and refined build 1211 properties, determines and sets appripriate usage requirements 1212 on those targets. 1213 """ 1214 rproperties = subvariant.build_properties () 1215 xusage_requirements =self.evaluate_requirements( 1216 self.usage_requirements_, rproperties, "added") 1217 1218 # We generate all dependency properties and add them, 1219 # as well as their usage requirements, to result. 1220 (r1, r2) = self.generate_dependency_properties(xusage_requirements.dependency (), rproperties) 1221 extra = r1 + r2 1222 1223 result = property_set.create (xusage_requirements.non_dependency () + extra) 1224 1225 # Propagate usage requirements we've got from sources, except 1226 # for the <pch-header> and <pch-file> features. 1227 # 1228 # That feature specifies which pch file to use, and should apply 1229 # only to direct dependents. Consider: 1230 # 1231 # pch pch1 : ... 1232 # lib lib1 : ..... pch1 ; 1233 # pch pch2 : 1234 # lib lib2 : pch2 lib1 ; 1235 # 1236 # Here, lib2 should not get <pch-header> property from pch1. 1237 # 1238 # Essentially, when those two features are in usage requirements, 1239 # they are propagated only to direct dependents. We might need 1240 # a more general mechanism, but for now, only those two 1241 # features are special. 1242 removed_pch = filter(lambda prop: prop.feature().name() not in ['<pch-header>', '<pch-file>'], subvariant.sources_usage_requirements().all()) 1243 result = result.add(property_set.PropertySet(removed_pch)) 1244 1245 return result 1246 1247 def create_subvariant (self, root_targets, all_targets, 1248 build_request, sources, 1249 rproperties, usage_requirements): 1250 """Creates a new subvariant-dg instances for 'targets' 1251 - 'root-targets' the virtual targets will be returned to dependents 1252 - 'all-targets' all virtual 1253 targets created while building this main target 1254 - 'build-request' is property-set instance with 1255 requested build properties""" 1256 1257 for e in root_targets: 1258 e.root (True) 1259 1260 s = Subvariant (self, build_request, sources, 1261 rproperties, usage_requirements, all_targets) 1262 1263 for v in all_targets: 1264 if not v.creating_subvariant(): 1265 v.creating_subvariant(s) 1266 1267 return s 1268 1269 def construct (self, name, source_targets, properties): 1270 """ Constructs the virtual targets for this abstract targets and 1271 the dependecy graph. Returns a tuple consisting of the properties and the list of virtual targets. 1272 Should be overrided in derived classes. 1273 """ 1274 raise BaseException ("method should be defined in derived classes") 1275 1276 1277class TypedTarget (BasicTarget): 1278 import generators 1279 1280 def __init__ (self, name, project, type, sources, requirements, default_build, usage_requirements): 1281 BasicTarget.__init__ (self, name, project, sources, requirements, default_build, usage_requirements) 1282 self.type_ = type 1283 1284 def __jam_repr__(self): 1285 return b2.util.value_to_jam(self) 1286 1287 def type (self): 1288 return self.type_ 1289 1290 def construct (self, name, source_targets, prop_set): 1291 1292 r = generators.construct (self.project_, os.path.splitext(name)[0], 1293 self.type_, 1294 prop_set.add_raw(['<main-target-type>' + self.type_]), 1295 source_targets, True) 1296 1297 if not r: 1298 print "warning: Unable to construct '%s'" % self.full_name () 1299 1300 # Are there any top-level generators for this type/property set. 1301 if not generators.find_viable_generators (self.type_, prop_set): 1302 print "error: no generators were found for type '" + self.type_ + "'" 1303 print "error: and the requested properties" 1304 print "error: make sure you've configured the needed tools" 1305 print "See http://boost.org/boost-build2/doc/html/bbv2/advanced/configuration.html" 1306 1307 print "To debug this problem, try the --debug-generators option." 1308 sys.exit(1) 1309 1310 return r 1311 1312def apply_default_build(property_set, default_build): 1313 # 1. First, see what properties from default_build 1314 # are already present in property_set. 1315 1316 specified_features = set(p.feature() for p in property_set.all()) 1317 1318 defaults_to_apply = [] 1319 for d in default_build.all(): 1320 if not d.feature() in specified_features: 1321 defaults_to_apply.append(d) 1322 1323 # 2. If there's any defaults to be applied, form the new 1324 # build request. Pass it throw 'expand-no-defaults', since 1325 # default_build might contain "release debug", which will 1326 # result in two property_sets. 1327 result = [] 1328 if defaults_to_apply: 1329 1330 # We have to compress subproperties here to prevent 1331 # property lists like: 1332 # 1333 # <toolset>msvc <toolset-msvc:version>7.1 <threading>multi 1334 # 1335 # from being expanded into: 1336 # 1337 # <toolset-msvc:version>7.1/<threading>multi 1338 # <toolset>msvc/<toolset-msvc:version>7.1/<threading>multi 1339 # 1340 # due to cross-product property combination. That may 1341 # be an indication that 1342 # build_request.expand-no-defaults is the wrong rule 1343 # to use here. 1344 compressed = feature.compress_subproperties(property_set.all()) 1345 1346 result = build_request.expand_no_defaults( 1347 b2.build.property_set.create(feature.expand([p])) for p in (compressed + defaults_to_apply)) 1348 1349 else: 1350 result.append (property_set) 1351 1352 return result 1353 1354 1355def create_typed_metatarget(name, type, sources, requirements, default_build, usage_requirements): 1356 1357 from b2.manager import get_manager 1358 t = get_manager().targets() 1359 1360 project = get_manager().projects().current() 1361 1362 return t.main_target_alternative( 1363 TypedTarget(name, project, type, 1364 t.main_target_sources(sources, name), 1365 t.main_target_requirements(requirements, project), 1366 t.main_target_default_build(default_build, project), 1367 t.main_target_usage_requirements(usage_requirements, project))) 1368 1369 1370def create_metatarget(klass, name, sources, requirements=[], default_build=[], usage_requirements=[]): 1371 from b2.manager import get_manager 1372 t = get_manager().targets() 1373 1374 project = get_manager().projects().current() 1375 1376 return t.main_target_alternative( 1377 klass(name, project, 1378 t.main_target_sources(sources, name), 1379 t.main_target_requirements(requirements, project), 1380 t.main_target_default_build(default_build, project), 1381 t.main_target_usage_requirements(usage_requirements, project))) 1382 1383def metatarget_function_for_class(class_): 1384 1385 @bjam_signature((["name"], ["sources", "*"], ["requirements", "*"], 1386 ["default_build", "*"], ["usage_requirements", "*"])) 1387 def create_metatarget(name, sources, requirements = [], default_build = None, usage_requirements = []): 1388 1389 from b2.manager import get_manager 1390 t = get_manager().targets() 1391 1392 project = get_manager().projects().current() 1393 1394 return t.main_target_alternative( 1395 class_(name, project, 1396 t.main_target_sources(sources, name), 1397 t.main_target_requirements(requirements, project), 1398 t.main_target_default_build(default_build, project), 1399 t.main_target_usage_requirements(usage_requirements, project))) 1400 1401 return create_metatarget 1402