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