4 # Copyright Vladimir Prus 2002-2007.
5 # Copyright Rene Rivera 2006.
7 # Distributed under the Boost Software License, Version 1.0.
8 # (See accompanying file LICENSE.txt or copy at
9 # https://www.bfgroup.xyz/b2/LICENSE.txt)
11 # Supports 'abstract' targets, which are targets explicitly defined in Jamfile.
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.
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.
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.
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'.
35 # +------------------------+
37 # +========================+
41 # |generate(properties) = 0|
42 # +-----------+------------+
49 # +------------------------+------+------------------------------+
52 # +----------+-----------+ +------+------+ +------+-------+
53 # | project_target | | MainTarget | | BasicTarget |
54 # +======================+ 1 * +=============+ alternatives +==============+
55 # | generate(properties) |o-----------+ generate |<>------------->| generate |
56 # | main-target | +-------------+ | construct = 0|
57 # +----------------------+ +--------------+
64 # ...--+----------------+------------------+----------------+---+
67 # ... ---+-----+ +------+-------+ +------+------+ +--------+-----+
68 # | | TypedTarget | | make-target | | stage-target |
69 # . +==============+ +=============+ +==============+
70 # . | construct | | construct | | construct |
71 # +--------------+ +-------------+ +--------------+
77 from b2
.manager
import get_manager
79 from b2
.util
.utility
import *
80 import property, project
, virtual_target
, property_set
, feature
, generators
, toolset
81 from virtual_target
import Subvariant
82 from b2
.exceptions
import *
83 from b2
.util
.sequence
import unique
84 from b2
.util
import path
, bjam_signature
, safe_isinstance
, is_iterable_typed
85 from b2
.build
import errors
86 from b2
.build
.errors
import user_error_checkpoint
88 import b2
.build
.build_request
as build_request
91 _re_separate_target_from_properties
= re
.compile (r
'^([^<]*)(/(<.*))?$')
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_
= {}
100 # Current indent for debugging messages
103 self
.debug_building_
= "--debug-building" in bjam
.variable("ARGV")
107 def main_target_alternative (self
, target
):
108 """ Registers the specified target as a main target alternatives.
111 assert isinstance(target
, AbstractTarget
)
112 target
.project ().add_alternative (target
)
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))
128 t
= b2
.util
.jam_to_value_maybe(t
)
130 if isinstance (t
, AbstractTarget
):
134 name
= main_target_name
+ '__' + name
137 # Inline targets are not built by default.
139 p
.mark_targets_as_explicit([name
])
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
154 'specification' are the properties xplicitly specified for a
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())
163 requirements
= property_set
.refine_from_user_input(
164 project
.get("requirements"), specification
,
165 project
.project_module(), project
.get("location"))
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
177 assert is_iterable_typed(specification
, basestring
)
178 assert isinstance(project
, ProjectTarget
)
179 project_usage_requirements
= project
.get ('usage-requirements')
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
185 usage_requirements
= property_set
.create_from_user_input(
186 specification
, project
.project_module(), project
.get("location"))
188 return project_usage_requirements
.add (usage_requirements
)
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
197 assert is_iterable_typed(specification
, basestring
)
198 assert isinstance(project
, ProjectTarget
)
200 return property_set
.create_with_validation(specification
)
202 return project
.get ('default-build')
204 def start_building (self
, main_target_instance
):
205 """ Helper rules to detect cycles in main target references.
207 assert isinstance(main_target_instance
, MainTarget
)
208 if id(main_target_instance
) in self
.targets_being_built_
:
210 for t
in self
.targets_being_built_
.values() + [main_target_instance
]:
211 names
.append (t
.full_name())
213 get_manager().errors()("Recursion in main target references\n")
215 self
.targets_being_built_
[id(main_target_instance
)] = main_target_instance
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
)]
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'.
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
)))
239 def increase_indent(self
):
242 def decrease_indent(self
):
243 self
.indent_
= self
.indent_
[0:-4]
246 return self
.debug_building_
248 def log(self
, message
):
249 if self
.debug_building_
:
250 print self
.indent_
+ message
252 def push_target(self
, target
):
253 assert isinstance(target
, AbstractTarget
)
254 self
.targets_
.append(target
)
256 def pop_target(self
):
257 self
.targets_
= self
.targets_
[:-1]
260 return self
.targets_
[0]
263 class GenerateResult
:
265 def __init__ (self
, ur
=None, targets
=None):
268 assert isinstance(ur
, property_set
.PropertySet
) or ur
is None
269 assert is_iterable_typed(targets
, virtual_target
.VirtualTarget
)
271 self
.__usage
_requirements
= ur
272 self
.__targets
= targets
274 if not self
.__usage
_requirements
:
275 self
.__usage
_requirements
= property_set
.empty ()
277 def usage_requirements (self
):
278 return self
.__usage
_requirements
281 return self
.__targets
283 def extend (self
, other
):
284 assert (isinstance (other
, GenerateResult
))
286 self
.__usage
_requirements
= self
.__usage
_requirements
.add (other
.usage_requirements ())
287 self
.__targets
.extend (other
.targets ())
289 class AbstractTarget
:
290 """ Base class for all abstract targets.
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 ()
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.
305 # Why allow manager to be specified? Because otherwise project target could not derive
308 self
.manager_
= manager
310 self
.manager_
= project
.manager ()
313 self
.project_
= project
314 self
.location_
= errors
.nearest_user_location()
320 """ Returns the name of this target.
325 """ Returns the project for this target.
330 """ Return the location where the target was declared.
332 return self
.location_
334 def full_name (self
):
335 """ Returns a user-readable name for this target.
337 location
= self
.project ().get ('location')
338 return location
+ '/' + self
.name_
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
349 If 'property_set' is empty, performs default build of this
350 target, in a way specific to derived class.
352 raise BaseException ("method should be defined in derived classes")
354 def rename (self
, new_name
):
355 assert isinstance(new_name
, basestring
)
356 self
.name_
= new_name
358 class ProjectTarget (AbstractTarget
):
359 """ Project target class (derived from 'AbstractTarget')
361 This class these responsibilities:
362 - maintaining a list of main target in this project and
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
369 - The first time 'main-target' or 'has-main-target' rule is called,
370 all alternatives are enumerated an main targets are created.
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
)
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
384 self
.build_dir_
= None
389 # True is main targets have already been built.
390 self
.built_main_targets_
= False
392 # A list of the registered alternatives for this project.
393 self
.alternatives_
= []
395 # A map from main target name to the target corresponding
397 self
.main_target_
= {}
399 # Targets marked as explicit.
400 self
.explicit_targets_
= set()
402 # Targets marked as always
403 self
.always_targets_
= set()
405 # The constants defined for this project.
408 # Whether targets for all main target are already created.
409 self
.built_main_targets_
= 0
412 self
.inherit (parent_project
)
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_
420 def get (self
, attribute
):
421 assert isinstance(attribute
, basestring
)
422 return self
.manager().projects().attribute(
423 self
.project_module_
, attribute
)
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')
431 return self
.build_dir_
433 def generate (self
, ps
):
434 """ Generates all possible targets contained in this project.
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 ()
441 result
= GenerateResult ()
443 for t
in self
.targets_to_build ():
447 self
.manager_
.targets().decrease_indent ()
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.
456 if not self
.built_main_targets_
:
457 self
.build_main_targets ()
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_
:
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
+ "/"))
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."""
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
)
480 def mark_targets_as_always(self
, target_names
):
481 assert is_iterable_typed(target_names
, basestring
)
482 self
.always_targets_
.update(target_names
)
484 def add_alternative (self
, target_instance
):
485 """ Add new target alternative.
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 ())
491 self
.alternatives_
.append (target_instance
)
493 def main_target (self
, name
):
494 assert isinstance(name
, basestring
)
495 if not self
.built_main_targets_
:
496 self
.build_main_targets()
498 return self
.main_target_
[name
]
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()
506 return name
in self
.main_target_
508 def create_main_target (self
, name
):
509 """ Returns a 'MainTarget' class instance corresponding to the 'name'.
511 assert isinstance(name
, basestring
)
512 if not self
.built_main_targets_
:
513 self
.build_main_targets ()
515 return self
.main_targets_
.get (name
, None)
518 def find_really(self
, id):
519 """ Find and return the target with the specified id, treated
522 assert isinstance(id, basestring
)
525 current_location
= self
.get ('location')
527 __re_split_project_target
= re
.compile (r
'(.*)//(.*)')
528 split
= __re_split_project_target
.match (id)
534 project_part
= split
.group(1)
535 target_part
= split
.group(2)
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.'
545 project_registry
= self
.project_
.manager ().projects ()
547 extra_error_message
= ''
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
)
554 project_target
= project_registry
.target (pm
)
555 result
= project_target
.find (target_part
, no_error
=1)
558 extra_error_message
= "error: could not find project '$(project_part)'"
561 # Interpret target-name as name of main target
562 # Need to do this before checking for file. Consider this:
564 # exe test : test.cpp ;
565 # install s : test : <location>. ;
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.
571 if self
.has_main_target(id):
572 result
= self
.main_target(id)
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.
583 # Interpret id as project-id
584 project_module
= project_registry
.find (id, current_location
)
586 result
= project_registry
.target (project_module
)
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)
596 v
= self
.find_really (id)
597 self
.ids_cache_
[id] = v
602 raise BaseException ("Unable to find file or target named '%s'\nreferred from project at '%s'" % (id, self
.get ('location')))
605 def build_main_targets (self
):
606 self
.built_main_targets_
= True
608 for a
in self
.alternatives_
:
610 if name
not in self
.main_target_
:
611 t
= MainTarget (name
, self
.project_
)
612 self
.main_target_
[name
] = t
614 if name
in self
.always_targets_
:
617 self
.main_target_
[name
].add_alternative (a
)
619 def add_constant(self
, name
, value
, path
=0):
620 """Adds a new constant for this project.
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.
627 assert isinstance(name
, basestring
)
628 assert is_iterable_typed(value
, basestring
)
629 assert isinstance(path
, int) # will also match bools
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')
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
))]
645 self
.constants_
[name
] = value
646 bjam
.call("set-variable", self
.project_module(), name
, value
)
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
])
655 # Import rules from parent
656 this_module
= self
.project_module()
657 parent_module
= parent_project
.project_module()
659 rules
= bjam
.call("RULENAMES", parent_module
)
662 user_rules
= [x
for x
in rules
663 if x
not in self
.manager().projects().project_rules().all_names()]
665 bjam
.call("import-rules-from-parent", parent_module
, this_module
, user_rules
)
667 class MainTarget (AbstractTarget
):
668 """ A named top-level target in Jamfile.
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 ()
676 def add_alternative (self
, target
):
677 """ Add a new alternative for this target.
679 assert isinstance(target
, BasicTarget
)
680 d
= target
.default_build ()
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"
686 "differing from previous default build: '%s'" % (self
.full_name (), d
.raw (), self
.default_build_
.raw ()))
689 self
.default_build_
= d
691 self
.alternatives_
.append (target
)
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)?
698 # When selecting alternatives we have to consider defaults,
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
706 property_set_
= property_set_
.add_defaults ()
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.
712 best_properties
= None
714 if len (self
.alternatives_
) == 0:
717 if len (self
.alternatives_
) == 1:
718 return self
.alternatives_
[0]
721 print "Property set for selection:", property_set_
723 for v
in self
.alternatives_
:
724 properties
= v
.match (property_set_
, debug
)
726 if properties
is not None:
729 best_properties
= properties
732 if b2
.util
.set.equal (properties
, best_properties
):
735 elif b2
.util
.set.contains (properties
, best_properties
):
736 # Do nothing, this alternative is worse
739 elif b2
.util
.set.contains (best_properties
, properties
):
741 best_properties
= properties
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_
)
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.
758 assert isinstance(ps
, property_set
.PropertySet
)
759 self
.manager_
.targets ().start_building (self
)
761 # We want composite properties in build request act as if
762 # all the properties it expands too are explicitly specified.
765 all_property_sets
= self
.apply_default_build (ps
)
767 result
= GenerateResult ()
769 for p
in all_property_sets
:
770 result
.extend (self
.__generate
_really
(p
))
772 self
.manager_
.targets ().end_building (self
)
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.
783 assert isinstance(prop_set
, property_set
.PropertySet
)
784 best_alternative
= self
.__select
_alternatives
(prop_set
, debug
=0)
785 self
.best_alternative
= best_alternative
787 if not best_alternative
:
789 # self.__select_alternatives(prop_set, debug=1)
790 self
.manager_
.errors()(
791 "No best alternative for '%s'.\n"
792 % (self
.full_name(),))
794 result
= best_alternative
.generate (prop_set
)
796 # Now return virtual targets for the only alternative
799 def rename(self
, new_name
):
800 assert isinstance(new_name
, basestring
)
801 AbstractTarget
.rename(self
, new_name
)
802 for a
in self
.alternatives_
:
805 class 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.
810 def __init__ (self
, manager
, file, project
):
811 AbstractTarget
.__init
__ (self
, file, project
)
812 self
.file_location_
= None
814 def generate (self
, properties
):
815 return GenerateResult (None, [
816 self
.manager_
.virtual_targets ().from_file (
817 self
.name_
, self
.location(), self
.project_
) ])
820 """ Returns true if the referred file really exists.
828 # Returns the location of target. Needed by 'testing.jam'
829 if not self
.file_location_
:
830 source_location
= self
.project_
.get('source-location')
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
839 return self
.file_location_
841 def 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.
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
)
851 raise BaseException ("Invalid reference: '%s'" % target_reference
)
858 sproperties
= property.create_from_strings(feature
.split(split
.group(3)))
859 sproperties
= feature
.expand_composites(sproperties
)
862 target
= project
.find (id)
864 return (target
, property_set
.create(sproperties
))
866 def 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
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
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
)
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
)
886 return target
.generate(rproperties
)
890 class 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
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
)
905 raise InvalidSource ("property '%s' found in the 'sources' parameter for '%s'" % (s
, name
))
907 self
.sources_
= sources
909 if not requirements
: requirements
= property_set
.empty ()
910 self
.requirements_
= requirements
912 if not default_build
: default_build
= property_set
.empty ()
913 self
.default_build_
= default_build
915 if not usage_requirements
: usage_requirements
= property_set
.empty ()
916 self
.usage_requirements_
= usage_requirements
918 # A cache for resolved references
919 self
.source_targets_
= None
921 # A cache for generated targets
924 # A cache for build requests
925 self
.request_cache
= {}
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
932 self
.user_context_
= self
.manager_
.errors().capture_user_context()[-1:]
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.
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])
950 return self
.source_targets_
952 def requirements (self
):
953 return self
.requirements_
955 def default_build (self
):
956 return self
.default_build_
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
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
= []
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
)
976 other
= property_set
.create(other
)
978 key
= (build_request
, other
)
979 if key
not in self
.request_cache
:
980 self
.request_cache
[key
] = self
.__common
_properties
2 (build_request
, other
)
982 return self
.request_cache
[key
].add_raw(free_unconditional
)
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.
990 # Handles situation where evaluating one conditional requirements affects
991 # condition of another conditional requirements, for example:
993 # <toolset>gcc:<variant>release <variant>release:<define>RELEASE
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:
1002 # exe a : a.cpp : <threading>single <toolset>foo:<threading>multi ;
1004 # I'm not sure if this should be an error, or not, especially given that
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())
1014 context
= context
.refine(property_set
.create(unconditional
))
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.
1020 conditionals
= property_set
.create(requirements
.conditional())
1022 # It's supposed that #conditionals iterations
1023 # should be enough for properties to propagate along conditions in any
1025 max_iterations
= len(conditionals
.all()) +\
1026 len(requirements
.get("<conditional>")) + 1
1028 added_requirements
= []
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
1036 indirect
= requirements
.get("<conditional>")
1039 for i
in range(0, max_iterations
):
1041 e
= conditionals
.evaluate_conditionals(current
).all()[:]
1043 # Evaluate indirect conditionals.
1046 i
= b2
.util
.jam_to_value_maybe(i
)
1048 # This is Python callable, yeah.
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()])
1055 new
= property.create_from_strings(br
)
1057 new
= property.translate_paths(new
, self
.project().location())
1060 if e
== added_requirements
:
1061 # If we got the same result, we've found final properties.
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
1069 added_requirements
= e
1070 current
= context
.refine(property_set
.create(feature
.expand(e
)))
1073 self
.manager().errors()("Can't evaluate conditional properties "
1074 + str(conditionals
))
1078 return property_set
.create(unconditional
+ added_requirements
)
1079 elif what
== "refined":
1082 self
.manager().errors("Invalid value of the 'what' parameter")
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.
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()
1100 return self
.evaluate_requirements(requirements
, build_request
,
1103 def match (self
, property_set_
, debug
):
1104 """ Returns the alternative condition for this alternative, if
1105 the condition is satisfied by 'property_set'.
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
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
)
1121 print " next alternative: required properties:", [str(p
) for p
in condition
]
1123 if b2
.util
.set.contains (condition
, property_set_
.all()):
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
)
1138 usage_requirements
= []
1139 for id in target_ids
:
1141 result
= generate_from_reference(id, self
.project_
, property_set_
)
1142 targets
+= result
.targets()
1143 usage_requirements
+= result
.usage_requirements().all()
1145 return (targets
, usage_requirements
)
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.
1152 Returns a tuple (result, usage_requirements).
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
:
1160 result
= generate_from_reference(p
.value
, self
.project_
, ps
)
1162 for t
in result
.targets():
1163 result_properties
.append(property.Property(p
.feature
, t
))
1165 usage_requirements
+= result
.usage_requirements().all()
1167 return (result_properties
, usage_requirements
)
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
1178 assert isinstance(ps
, property_set
.PropertySet
)
1179 self
.manager_
.errors().push_user_context(
1180 "Generating target " + self
.full_name(), self
.user_context_
)
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 ()))
1194 self
.manager().targets().push_target(self
)
1196 if ps
not in self
.generated_
:
1198 # Apply free features form the command line. If user
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_
)
1205 self
.manager().targets().log(
1206 "Common properties are '%s'" % str (rproperties
))
1208 if rproperties
.get("<build>") != ["no"]:
1210 result
= GenerateResult ()
1212 properties
= rproperties
.non_dependency ()
1214 (p
, u
) = self
.generate_dependency_properties (rproperties
.dependency (), rproperties
)
1216 assert all(isinstance(p
, property.Property
) for p
in properties
)
1217 usage_requirements
= u
1219 (source_targets
, u
) = self
.generate_dependency_targets (self
.sources_
, rproperties
)
1220 usage_requirements
+= u
1222 self
.manager_
.targets().log(
1223 "Usage requirements for '%s' are '%s'" % (self
.name_
, usage_requirements
))
1227 rproperties
= property_set
.create(properties
+ usage_requirements
)
1228 usage_requirements
= property_set
.create (usage_requirements
)
1230 self
.manager_
.targets().log(
1231 "Build properties: '%s'" % str(rproperties
))
1233 source_targets
+= rproperties
.get('<source>')
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
1241 source_targets
= unique(source_targets
, stable
=True)
1243 # FIXME: figure why this call messes up source_targets in-place
1244 result
= self
.construct (self
.name_
, source_targets
[:], rproperties
)
1247 assert len(result
) == 2
1255 s
= self
.create_subvariant (
1257 self
.manager().virtual_targets().recent_targets(), ps
,
1258 source_targets
, rproperties
, usage_requirements
)
1259 self
.manager().virtual_targets().clear_recent_targets()
1261 ur
= self
.compute_usage_requirements (s
)
1263 s
.set_usage_requirements (ur
)
1265 self
.manager_
.targets().log (
1266 "Usage requirements from '%s' are '%s'" %
1267 (self
.name(), str(rproperties
)))
1269 self
.generated_
[ps
] = GenerateResult (ur
, result
)
1271 self
.generated_
[ps
] = GenerateResult (property_set
.empty(), [])
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.
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
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
1286 self
.generated_
[ps
] = GenerateResult(
1287 property_set
.create(["<build>no"]), [])
1289 self
.manager().targets().log ("Already built")
1291 self
.manager().targets().pop_target()
1292 self
.manager().targets().decrease_indent()
1294 return self
.generated_
[ps
]
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
1301 assert isinstance(subvariant
, virtual_target
.Subvariant
)
1302 rproperties
= subvariant
.build_properties ()
1303 xusage_requirements
=self
.evaluate_requirements(
1304 self
.usage_requirements_
, rproperties
, "added")
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
)
1311 result
= property_set
.create (xusage_requirements
.non_dependency () + extra
)
1313 # Propagate usage requirements we've got from sources, except
1314 # for the <pch-header> and <pch-file> features.
1316 # That feature specifies which pch file to use, and should apply
1317 # only to direct dependents. Consider:
1320 # lib lib1 : ..... pch1 ;
1322 # lib lib2 : pch2 lib1 ;
1324 # Here, lib2 should not get <pch-header> property from pch1.
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.
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'):
1336 for p
in properties
:
1337 if p
.feature
.name
!= 'library':
1338 new_properties
.append(p
)
1339 properties
= new_properties
1341 result
= result
.add_raw(properties
)
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
)
1360 for e
in root_targets
:
1363 s
= Subvariant (self
, build_request
, sources
,
1364 rproperties
, usage_requirements
, all_targets
)
1366 for v
in all_targets
:
1367 if not v
.creating_subvariant():
1368 v
.creating_subvariant(s
)
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.
1377 raise BaseException ("method should be defined in derived classes")
1380 class TypedTarget (BasicTarget
):
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
)
1388 def __jam_repr__(self
):
1389 return b2
.util
.value_to_jam(self
)
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],
1400 prop_set
.add_raw(['<main-target-type>' + self
.type_
]),
1401 source_targets
, True)
1404 print "warning: Unable to construct '%s'" % self
.full_name ()
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 https://www.bfgroup.xyz/b2/manual/release/index.html#bbv2.overview.configuration"
1413 print "To debug this problem, try the --debug-generators option."
1418 def 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
)
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
)
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.
1434 if defaults_to_apply
:
1436 # We have to compress subproperties here to prevent
1437 # property lists like:
1439 # <toolset>msvc <toolset-msvc:version>7.1 <threading>multi
1441 # from being expanded into:
1443 # <toolset-msvc:version>7.1/<threading>multi
1444 # <toolset>msvc/<toolset-msvc:version>7.1/<threading>multi
1446 # due to cross-product property combination. That may
1447 # be an indication that
1448 # build_request.expand-no-defaults is the wrong rule
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
]
1456 for p
in properties
:
1457 result
.append(property_set
.create(feature
.expand(p
.all())))
1459 result
= [property_set
.empty()]
1462 result
.append (property_set_
)
1467 def 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
)
1474 from b2
.manager
import get_manager
1475 t
= get_manager().targets()
1477 project
= get_manager().projects().current()
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
)))
1487 def 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()
1496 project
= get_manager().projects().current()
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
)))
1505 def metatarget_function_for_class(class_
):
1507 @bjam_signature((["name"], ["sources", "*"], ["requirements", "*"],
1508 ["default_build", "*"], ["usage_requirements", "*"]))
1509 def create_metatarget(name
, sources
, requirements
= [], default_build
= None, usage_requirements
= []):
1511 from b2
.manager
import get_manager
1512 t
= get_manager().targets()
1514 project
= get_manager().projects().current()
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
)))
1523 return create_metatarget