1 # Status: mostly ported. Missing is --out-xml support, 'configure' integration
5 # Copyright 2003, 2005 Dave Abrahams
6 # Copyright 2006 Rene Rivera
7 # Copyright 2003, 2004, 2005, 2006, 2007 Vladimir Prus
8 # Distributed under the Boost Software License, Version 1.0.
9 # (See accompanying file LICENSE_1_0.txt or copy at
10 # http://www.boost.org/LICENSE_1_0.txt)
17 # set this early on since some of the following modules
18 # require looking at the sys.argv
19 sys
.argv
= bjam
.variable("ARGV")
22 from b2
.build
.engine
import Engine
23 from b2
.manager
import Manager
24 from b2
.util
.path
import glob
25 from b2
.build
import feature
, property_set
26 import b2
.build
.virtual_target
27 from b2
.build
.targets
import ProjectTarget
28 import b2
.build
.build_request
29 from b2
.build
.errors
import ExceptionWithUserContext
30 import b2
.tools
.common
31 from b2
.build
.toolset
import using
33 import b2
.build
.virtual_target
as virtual_target
34 import b2
.build
.build_request
as build_request
38 from b2
.manager
import get_manager
39 from b2
.util
import cached
40 from b2
.util
import option
42 ################################################################################
46 ################################################################################
48 # Flag indicating we should display additional debugging information related to
49 # locating and loading Boost Build configuration files.
52 # The cleaning is tricky. Say, if user says 'bjam --clean foo' where 'foo' is a
53 # directory, then we want to clean targets which are in 'foo' as well as those
54 # in any children Jamfiles under foo but not in any unrelated Jamfiles. To
55 # achieve this we collect a list of projects under which cleaning is allowed.
58 # Virtual targets obtained when building main targets references on the command
59 # line. When running 'bjam --clean main_target' we want to clean only files
60 # belonging to that main target so we need to record which targets are produced
62 results_of_main_targets
= []
64 # Was an XML dump requested?
67 # Default toolset & version to be used in case no other toolset has been used
68 # explicitly by either the loaded configuration files, the loaded project build
69 # scripts or an explicit toolset request on the command line. If not specified,
70 # an arbitrary default will be used based on the current host OS. This value,
71 # while not strictly necessary, has been added to allow testing Boost-Build's
72 # default toolset usage functionality.
73 default_toolset
= None
74 default_toolset_version
= None
76 ################################################################################
80 ################################################################################
82 # Returns the property set with the free features from the currently processed
85 def command_line_free_features():
86 return command_line_free_features
88 # Sets the default toolset & version to be used in case no other toolset has
89 # been used explicitly by either the loaded configuration files, the loaded
90 # project build scripts or an explicit toolset request on the command line. For
91 # more detailed information see the comment related to used global variables.
93 def set_default_toolset(toolset
, version
=None):
94 default_toolset
= toolset
95 default_toolset_version
= version
100 def add_pre_build_hook(callable):
101 pre_build_hook
.append(callable)
103 post_build_hook
= None
105 def set_post_build_hook(callable):
106 post_build_hook
= callable
108 ################################################################################
112 ################################################################################
114 # Returns actual Jam targets to be used for executing a clean request.
116 def actual_clean_targets(targets
):
118 # Construct a list of projects explicitly detected as targets on this build
119 # system run. These are the projects under which cleaning is allowed.
121 if isinstance(t
, b2
.build
.targets
.ProjectTarget
):
122 project_targets
.append(t
.project_module())
124 # Construct a list of targets explicitly detected on this build system run
125 # as a result of building main targets.
126 targets_to_clean
= set()
127 for t
in results_of_main_targets
:
128 # Do not include roots or sources.
129 targets_to_clean
.update(virtual_target
.traverse(t
))
132 for t
in get_manager().virtual_targets().all_targets():
134 # Remove only derived targets.
137 if t
in targets_to_clean
or should_clean_project(p
.project_module()):
140 return [t
.actualize() for t
in to_clean
]
142 _target_id_split
= re
.compile("(.*)//(.*)")
144 # Given a target id, try to find and return the corresponding target. This is
145 # only invoked when there is no Jamfile in ".". This code somewhat duplicates
146 # code in project-target.find but we can not reuse that code without a
147 # project-targets instance.
149 def find_target(target_id
):
151 projects
= get_manager().projects()
152 m
= _target_id_split
.match(target_id
)
154 pm
= projects
.find(m
.group(1), ".")
156 pm
= projects
.find(target_id
, ".")
159 result
= projects
.target(pm
)
162 result
= result
.find(m
.group(2))
166 def initialize_config_module(module_name
, location
=None):
168 get_manager().projects().initialize(module_name
, location
)
170 # Helper rule used to load configuration files. Loads the first configuration
171 # file with the given 'filename' at 'path' into module with name 'module-name'.
172 # Not finding the requested file may or may not be treated as an error depending
173 # on the must-find parameter. Returns a normalized path to the loaded
174 # configuration file or nothing if no file was loaded.
176 def load_config(module_name
, filename
, paths
, must_find
=False):
179 print "notice: Searching '%s' for '%s' configuration file '%s." \
180 % (paths
, module_name
, filename
)
184 t
= os
.path
.join(path
, filename
)
185 if os
.path
.exists(t
):
190 where
= os
.path
.realpath(where
)
193 print "notice: Loading '%s' configuration file '%s' from '%s'." \
194 % (module_name
, filename
, where
)
196 # Set source location so that path-constant in config files
197 # with relative paths work. This is of most importance
198 # for project-config.jam, but may be used in other
199 # config files as well.
200 attributes
= get_manager().projects().attributes(module_name
) ;
201 attributes
.set('source-location', os
.path
.dirname(where
), True)
202 get_manager().projects().load_standalone(module_name
, where
)
205 msg
= "Configuration file '%s' not found in '%s'." % (filename
, path
)
207 get_manager().errors()(msg
)
214 # Loads all the configuration files used by Boost Build in the following order:
217 # Loaded only if specified on the command-line using the --test-config
218 # command-line parameter. It is ok for this file not to exist even if
219 # specified. If this configuration file is loaded, regular site and user
220 # configuration files will not be. If a relative path is specified, file is
221 # searched for in the current folder.
224 # Always named site-config.jam. Will only be found if located on the system
225 # root path (Windows), /etc (non-Windows), user's home folder or the Boost
226 # Build path, in that order. Not loaded in case the test-config configuration
227 # file is loaded or the --ignore-site-config command-line option is specified.
230 # Named user-config.jam by default or may be named explicitly using the
231 # --user-config command-line option or the BOOST_BUILD_USER_CONFIG environment
232 # variable. If named explicitly the file is looked for from the current working
233 # directory and if the default one is used then it is searched for in the
234 # user's home directory and the Boost Build path, in that order. Not loaded in
235 # case either the test-config configuration file is loaded or an empty file
236 # name is explicitly specified. If the file name has been given explicitly then
237 # the file must exist.
239 # Test configurations have been added primarily for use by Boost Build's
240 # internal unit testing system but may be used freely in other places as well.
242 def load_configuration_files():
244 # Flag indicating that site configuration should not be loaded.
245 ignore_site_config
= "--ignore-site-config" in sys
.argv
247 initialize_config_module("test-config")
250 m
= re
.match("--test-config=(.*)$", a
)
252 test_config
= b2
.util
.unquote(m
.group(1))
256 where
= load_config("test-config", os
.path
.basename(test_config
), [os
.path
.dirname(test_config
)])
259 print "notice: Regular site and user configuration files will"
260 print "notice: be ignored due to the test configuration being loaded."
262 user_path
= [os
.path
.expanduser("~")] + bjam
.variable("BOOST_BUILD_PATH")
263 site_path
= ["/etc"] + user_path
264 if os
.name
in ["nt"]:
265 site_path
= [os
.getenv("SystemRoot")] + user_path
267 if debug_config
and not test_config
and ignore_site_config
:
268 print "notice: Site configuration files will be ignored due to the"
269 print "notice: --ignore-site-config command-line option."
271 initialize_config_module("site-config")
272 if not test_config
and not ignore_site_config
:
273 load_config('site-config', 'site-config.jam', site_path
)
275 initialize_config_module('user-config')
278 # Here, user_config has value of None if nothing is explicitly
279 # specified, and value of '' if user explicitly does not want
280 # to load any user config.
283 m
= re
.match("--user-config=(.*)$", a
)
285 user_config
= m
.group(1)
288 if user_config
is None:
289 user_config
= os
.getenv("BOOST_BUILD_USER_CONFIG")
291 # Special handling for the case when the OS does not strip the quotes
292 # around the file name, as is the case when using Cygwin bash.
293 user_config
= b2
.util
.unquote(user_config
)
294 explicitly_requested
= user_config
296 if user_config
is None:
297 user_config
= "user-config.jam"
300 if explicitly_requested
:
302 user_config
= os
.path
.abspath(user_config
)
305 print "notice: Loading explicitly specified user configuration file:"
306 print " " + user_config
308 load_config('user-config', os
.path
.basename(user_config
), [os
.path
.dirname(user_config
)], True)
310 load_config('user-config', os
.path
.basename(user_config
), user_path
)
313 print "notice: User configuration file loading explicitly disabled."
315 # We look for project-config.jam from "." upward. I am not sure this is
316 # 100% right decision, we might as well check for it only alongside the
317 # Jamroot file. However:
318 # - We need to load project-config.jam before Jamroot
319 # - We probably need to load project-config.jam even if there is no Jamroot
320 # - e.g. to implement automake-style out-of-tree builds.
321 if os
.path
.exists("project-config.jam"):
322 file = ["project-config.jam"]
324 file = b2
.util
.path
.glob_in_parents(".", ["project-config.jam"])
327 initialize_config_module('project-config', os
.path
.dirname(file[0]))
328 load_config('project-config', "project-config.jam", [os
.path
.dirname(file[0])], True)
330 get_manager().projects().end_load()
333 # Autoconfigure toolsets based on any instances of --toolset=xx,yy,...zz or
334 # toolset=xx,yy,...zz in the command line. May return additional properties to
335 # be processed as if they had been specified by the user.
337 def process_explicit_toolset_requests():
339 extra_properties
= []
341 option_toolsets
= [e
for option
in b2
.util
.regex
.transform(sys
.argv
, "^--toolset=(.*)$")
342 for e
in option
.split(',')]
343 feature_toolsets
= [e
for option
in b2
.util
.regex
.transform(sys
.argv
, "^toolset=(.*)$")
344 for e
in option
.split(',')]
346 for t
in option_toolsets
+ feature_toolsets
:
348 # Parse toolset-version/properties.
349 (toolset_version
, toolset
, version
) = re
.match("(([^-/]+)-?([^/]+)?)/?.*", t
).groups()
352 print "notice: [cmdline-cfg] Detected command-line request for '%s': toolset= %s version=%s" \
353 % (toolset_version
, toolset
, version
)
355 # If the toolset is not known, configure it now.
357 if toolset
in feature
.values("toolset"):
360 if known
and version
and not feature
.is_subvalue("toolset", toolset
, "version", version
):
362 # TODO: we should do 'using $(toolset)' in case no version has been
363 # specified and there are no versions defined for the given toolset to
364 # allow the toolset to configure its default version. For this we need
365 # to know how to detect whether a given toolset has any versions
366 # defined. An alternative would be to do this whenever version is not
367 # specified but that would require that toolsets correctly handle the
368 # case when their default version is configured multiple times which
369 # should be checked for all existing toolsets first.
374 print "notice: [cmdline-cfg] toolset '%s' not previously configured; attempting to auto-configure now" % toolset_version
375 if version
is not None:
376 using(toolset
, version
)
384 print "notice: [cmdline-cfg] toolset '%s' already configured" % toolset_version
386 # Make sure we get an appropriate property into the build request in
387 # case toolset has been specified using the "--toolset=..." command-line
389 if not t
in sys
.argv
and not t
in feature_toolsets
:
392 print "notice: [cmdline-cfg] adding toolset=%s) to the build request." % t
;
393 extra_properties
+= "toolset=%s" % t
395 return extra_properties
399 # Returns 'true' if the given 'project' is equal to or is a (possibly indirect)
400 # child to any of the projects requested to be cleaned in this build system run.
401 # Returns 'false' otherwise. Expects the .project-targets list to have already
405 def should_clean_project(project
):
407 if project
in project_targets
:
411 parent
= get_manager().projects().attribute(project
, "parent-module")
412 if parent
and parent
!= "user-config":
413 return should_clean_project(parent
)
417 ################################################################################
422 ################################################################################
426 # FIXME: document this option.
427 if "--profiling" in sys
.argv
:
429 r
= cProfile
.runctx('main_real()', globals(), locals(), "stones.prof")
432 stats
= pstats
.Stats("stones.prof")
434 stats
.sort_stats('time', 'calls')
435 stats
.print_callers(20)
440 except ExceptionWithUserContext
, e
:
445 global debug_config
, out_xml
447 debug_config
= "--debug-configuration" in sys
.argv
448 out_xml
= any(re
.match("^--out-xml=(.*)$", a
) for a
in sys
.argv
)
452 global_build_dir
= option
.get("build-dir")
453 manager
= Manager(engine
, global_build_dir
)
455 import b2
.build
.configure
as configure
457 if "--version" in sys
.argv
:
458 from b2
.build
import version
462 # This module defines types and generator and what not,
463 # and depends on manager's existence
464 import b2
.tools
.builtin
466 b2
.tools
.common
.init(manager
)
468 load_configuration_files()
470 # Load explicitly specified toolset modules.
471 extra_properties
= process_explicit_toolset_requests()
473 # Load the actual project build script modules. We always load the project
474 # in the current folder so 'use-project' directives have any chance of
475 # being seen. Otherwise, we would not be able to refer to subprojects using
477 current_project
= None
478 projects
= get_manager().projects()
479 if projects
.find(".", "."):
480 current_project
= projects
.target(projects
.load("."))
482 # Load the default toolset module if no other has already been specified.
483 if not feature
.values("toolset"):
488 dtv
= default_toolset_version
494 #else if [ os.name ] = MACOSX
496 # default-toolset = darwin ;
499 print "warning: No toolsets are configured."
500 print "warning: Configuring default toolset '%s'." % dt
501 print "warning: If the default is wrong, your build may not work correctly."
502 print "warning: Use the \"toolset=xxxxx\" option to override our guess."
503 print "warning: For more configuration options, please consult"
504 print "warning: http://boost.org/boost-build2/doc/html/bbv2/advanced/configuration.html"
508 # Parse command line for targets and properties. Note that this requires
509 # that all project files already be loaded.
510 (target_ids
, properties
) = build_request
.from_command_line(sys
.argv
[1:] + extra_properties
)
512 # Check that we actually found something to build.
513 if not current_project
and not target_ids
:
514 get_manager().errors()("no Jamfile in current directory found, and no target references specified.")
518 # Flags indicating that this build system run has been started in order to
519 # clean existing instead of create new targets. Note that these are not the
520 # final flag values as they may get changed later on due to some special
521 # targets being specified on the command line.
522 clean
= "--clean" in sys
.argv
523 cleanall
= "--clean-all" in sys
.argv
525 # List of explicitly requested files to build. Any target references read
526 # from the command line parameter not recognized as one of the targets
527 # defined in the loaded Jamfiles will be interpreted as an explicitly
528 # requested file to build. If any such files are explicitly requested then
529 # only those files and the targets they depend on will be built and they
530 # will be searched for among targets that would have been built had there
531 # been no explicitly requested files.
532 explicitly_requested_files
= []
534 # List of Boost Build meta-targets, virtual-targets and actual Jam targets
535 # constructed in this build system run.
540 explicitly_requested_files
= []
542 # Process each target specified on the command-line and convert it into
543 # internal Boost Build target objects. Detect special clean target. If no
544 # main Boost Build targets were explicitly requested use the current project
546 for id in target_ids
:
552 t
= current_project
.find(id, no_error
=1)
557 print "notice: could not find main target '%s'" % id
558 print "notice: assuming it's a name of file to create " ;
559 explicitly_requested_files
.append(id)
564 targets
= [projects
.target(projects
.module_name("."))]
566 # FIXME: put this BACK.
568 ## if [ option.get dump-generators : : true ]
574 # We wish to put config.log in the build directory corresponding
575 # to Jamroot, so that the location does not differ depending on
576 # directory where we do build. The amount of indirection necessary
578 first_project
= targets
[0].project()
579 first_project_root_location
= first_project
.get('project-root')
580 first_project_root_module
= manager
.projects().load(first_project_root_location
)
581 first_project_root
= manager
.projects().target(first_project_root_module
)
582 first_build_build_dir
= first_project_root
.build_dir()
583 configure
.set_log_file(os
.path
.join(first_build_build_dir
, "config.log"))
587 global results_of_main_targets
589 # Expand properties specified on the command line into multiple property
590 # sets consisting of all legal property combinations. Each expanded property
591 # set will be used for a single build run. E.g. if multiple toolsets are
592 # specified then requested targets will be built with each of them.
593 # The expansion is being performed as late as possible so that the feature
594 # validation is performed after all necessary modules (including project targets
595 # on the command line) have been loaded.
599 expanded
.extend(build_request
.convert_command_line_element(p
))
601 expanded
= build_request
.expand_no_defaults(expanded
)
603 expanded
= [property_set
.empty()]
605 # Now that we have a set of targets to build and a set of property sets to
606 # build the targets with, we can start the main build process by using each
607 # property set to generate virtual targets from all of our listed targets
608 # and any of their dependants.
610 manager
.set_command_line_free_features(property_set
.create(p
.free()))
615 if not isinstance(t
, ProjectTarget
):
616 results_of_main_targets
.extend(g
.targets())
617 virtual_targets
.extend(g
.targets())
618 except ExceptionWithUserContext
, e
:
623 # Convert collected virtual targets into actual raw Jam targets.
624 for t
in virtual_targets
:
625 actual_targets
.append(t
.actualize())
627 j
= option
.get("jobs")
629 bjam
.call("set-variable", 'PARALLELISM', j
)
631 k
= option
.get("keep-going", "true", "true")
632 if k
in ["on", "yes", "true"]:
633 bjam
.call("set-variable", "KEEP_GOING", "1")
634 elif k
in ["off", "no", "false"]:
635 bjam
.call("set-variable", "KEEP_GOING", "0")
637 print "error: Invalid value for the --keep-going option"
640 # The 'all' pseudo target is not strictly needed expect in the case when we
641 # use it below but people often assume they always have this target
642 # available and do not declare it themselves before use which may cause
643 # build failures with an error message about not being able to build the
645 bjam
.call("NOTFILE", "all")
647 # And now that all the actual raw Jam targets and all the dependencies
648 # between them have been prepared all that is left is to tell Jam to update
650 if explicitly_requested_files
:
651 # Note that this case can not be joined with the regular one when only
652 # exact Boost Build targets are requested as here we do not build those
653 # requested targets but only use them to construct the dependency tree
654 # needed to build the explicitly requested files.
655 # FIXME: add $(.out-xml)
656 bjam
.call("UPDATE", ["<e>%s" % x
for x
in explicitly_requested_files
])
658 bjam
.call("UPDATE", "clean-all")
660 manager
.engine().set_update_action("common.Clean", "clean",
661 actual_clean_targets(targets
))
662 bjam
.call("UPDATE", "clean")
665 #configure.print-configure-checks-summary ;
668 for h
in pre_build_hook
:
671 bjam
.call("DEPENDS", "all", actual_targets
)
672 ok
= bjam
.call("UPDATE_NOW", "all") # FIXME: add out-xml
675 # Prevent automatic update of the 'all' target, now that
676 # we have explicitly updated what we wanted.
679 if manager
.errors().count() == 0: