Imported Upstream version 1.57.0
[platform/upstream/boost.git] / tools / build / src / build_system.py
1 # Status: mostly ported. Missing is --out-xml support, 'configure' integration
2 # and some FIXME.
3 # Base revision: 64351
4
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)
11
12
13 from b2.build.engine import Engine
14 from b2.manager import Manager
15 from b2.util.path import glob
16 from b2.build import feature, property_set
17 import b2.build.virtual_target
18 from b2.build.targets import ProjectTarget
19 from b2.util.sequence import unique
20 import b2.build.build_request
21 from b2.build.errors import ExceptionWithUserContext
22 import b2.tools.common
23 from b2.build.toolset import using
24
25 import b2.build.project as project
26 import b2.build.virtual_target as virtual_target
27 import b2.build.build_request as build_request
28
29 import b2.util.regex
30
31 from b2.manager import get_manager
32 from b2.util import cached
33 from b2.util import option
34
35
36 import bjam
37
38 import os
39 import sys
40 import re
41
42 ################################################################################
43 #
44 # Module global data.
45 #
46 ################################################################################
47
48 # Flag indicating we should display additional debugging information related to
49 # locating and loading Boost Build configuration files.
50 debug_config = False
51
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.
56 project_targets = []
57
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
61 # for it.
62 results_of_main_targets = []
63
64 # Was an XML dump requested?
65 out_xml = False
66
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
75
76 ################################################################################
77 #
78 # Public rules.
79 #
80 ################################################################################
81
82 # Returns the property set with the free features from the currently processed
83 # build request.
84 #
85 def command_line_free_features():
86     return command_line_free_features
87
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.
92 #
93 def set_default_toolset(toolset, version=None):
94     default_toolset = toolset
95     default_toolset_version = version
96
97
98 pre_build_hook = []
99
100 def add_pre_build_hook(callable):
101     pre_build_hook.append(callable)
102
103 post_build_hook = None
104
105 def set_post_build_hook(callable):
106     post_build_hook = callable
107
108 ################################################################################
109 #
110 # Local rules.
111 #
112 ################################################################################
113
114 # Returns actual Jam targets to be used for executing a clean request.
115 #
116 def actual_clean_targets(targets):
117
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.
120     for t in targets:
121         if isinstance(t, b2.build.targets.ProjectTarget):
122             project_targets.append(t.project_module())
123
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))
130
131     to_clean = []
132     for t in get_manager().virtual_targets().all_targets():
133
134         # Remove only derived targets.
135         if t.action():
136             p = t.project()
137             if t in targets_to_clean or should_clean_project(p.project_module()):
138                 to_clean.append(t)
139
140     return [t.actualize() for t in to_clean]
141
142 _target_id_split = re.compile("(.*)//(.*)")
143
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.
148 #
149 def find_target(target_id):
150
151     projects = get_manager().projects()
152     m = _target_id_split.match(target_id)
153     if m:
154         pm = projects.find(m.group(1), ".")
155     else:
156         pm = projects.find(target_id, ".")
157
158     if pm:
159         result = projects.target(pm)
160
161     if m:
162         result = result.find(m.group(2))
163
164     return result
165
166 def initialize_config_module(module_name, location=None):
167
168     get_manager().projects().initialize(module_name, location)
169
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.
175 #
176 def load_config(module_name, filename, paths, must_find=False):
177
178     if debug_config:
179         print "notice: Searching  '%s' for '%s' configuration file '%s." \
180               % (paths, module_name, filename)
181
182     where = None
183     for path in paths:
184         t = os.path.join(path, filename)
185         if os.path.exists(t):
186             where = t
187             break
188
189     if where:
190         where = os.path.realpath(where)
191
192         if debug_config:
193             print "notice: Loading '%s' configuration file '%s' from '%s'." \
194                   % (module_name, filename, where)
195
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)
203
204     else:
205         msg = "Configuration file '%s' not found in '%s'." % (filename, path)
206         if must_find:
207             get_manager().errors()(msg)
208
209         elif debug_config:
210             print msg
211
212     return where
213
214 # Loads all the configuration files used by Boost Build in the following order:
215 #
216 #   -- test-config --
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.
222 #
223 #   -- site-config --
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.
228 #
229 #   -- user-config --
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.
238 #
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.
241 #
242 def load_configuration_files():
243
244     # Flag indicating that site configuration should not be loaded.
245     ignore_site_config = "--ignore-site-config" in sys.argv
246
247     initialize_config_module("test-config")
248     test_config = None
249     for a in sys.argv:
250         m = re.match("--test-config=(.*)$", a)
251         if m:
252             test_config = b2.util.unquote(m.group(1))
253             break
254
255     if test_config:
256         where = load_config("test-config", os.path.basename(test_config), [os.path.dirname(test_config)])
257         if where:
258             if debug_config:
259                 print "notice: Regular site and user configuration files will"
260                 print "notice: be ignored due to the test configuration being loaded."
261
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
266
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."
270
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)
274
275     initialize_config_module('user-config')
276     if not test_config:
277
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.
281         user_config = None
282         for a in sys.argv:
283             m = re.match("--user-config=(.*)$", a)
284             if m:
285                 user_config = m.group(1)
286                 break
287
288         if user_config is None:
289             user_config = os.getenv("BOOST_BUILD_USER_CONFIG")
290
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
295
296         if user_config is None:
297             user_config = "user-config.jam"
298
299         if user_config:
300             if explicitly_requested:
301
302                 user_config = os.path.abspath(user_config)
303
304                 if debug_config:
305                     print "notice: Loading explicitly specified user configuration file:"
306                     print "    " + user_config
307
308                     load_config('user-config', os.path.basename(user_config), [os.path.dirname(user_config)], True)
309             else:
310                 load_config('user-config', os.path.basename(user_config), user_path)
311         else:
312             if debug_config:
313                 print "notice: User configuration file loading explicitly disabled."
314
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"]
323     else:
324         file = b2.util.path.glob_in_parents(".", ["project-config.jam"])
325
326     if file:
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)
329
330
331 # Autoconfigure toolsets based on any instances of --toolset=xx,yy,...zz or
332 # toolset=xx,yy,...zz in the command line. May return additional properties to
333 # be processed as if they had been specified by the user.
334 #
335 def process_explicit_toolset_requests():
336
337     extra_properties = []
338
339     option_toolsets = [e for option in b2.util.regex.transform(sys.argv, "^--toolset=(.*)$")
340                        for e in option.split(',')]
341     feature_toolsets = [e for option in b2.util.regex.transform(sys.argv, "^toolset=(.*)$")
342                        for e in option.split(',')]
343
344     for t in option_toolsets + feature_toolsets:
345
346         # Parse toolset-version/properties.
347         (toolset_version, toolset, version) = re.match("(([^-/]+)-?([^/]+)?)/?.*", t).groups()
348
349         if debug_config:
350             print "notice: [cmdline-cfg] Detected command-line request for '%s': toolset= %s version=%s" \
351             % (toolset_version, toolset, version)
352
353         # If the toolset is not known, configure it now.
354         known = False
355         if toolset in feature.values("toolset"):
356             known = True
357
358         if known and version and not feature.is_subvalue("toolset", toolset, "version", version):
359             known = False
360         # TODO: we should do 'using $(toolset)' in case no version has been
361         # specified and there are no versions defined for the given toolset to
362         # allow the toolset to configure its default version. For this we need
363         # to know how to detect whether a given toolset has any versions
364         # defined. An alternative would be to do this whenever version is not
365         # specified but that would require that toolsets correctly handle the
366         # case when their default version is configured multiple times which
367         # should be checked for all existing toolsets first.
368
369         if not known:
370
371             if debug_config:
372                 print "notice: [cmdline-cfg] toolset '%s' not previously configured; attempting to auto-configure now" % toolset_version
373             if version is not None:
374                using(toolset, version)
375             else:
376                using(toolset)
377
378         else:
379
380             if debug_config:
381
382                 print "notice: [cmdline-cfg] toolset '%s' already configured" % toolset_version
383
384         # Make sure we get an appropriate property into the build request in
385         # case toolset has been specified using the "--toolset=..." command-line
386         # option form.
387         if not t in sys.argv and not t in feature_toolsets:
388
389             if debug_config:
390                 print "notice: [cmdline-cfg] adding toolset=%s) to the build request." % t ;
391             extra_properties += "toolset=%s" % t
392
393     return extra_properties
394
395
396
397 # Returns 'true' if the given 'project' is equal to or is a (possibly indirect)
398 # child to any of the projects requested to be cleaned in this build system run.
399 # Returns 'false' otherwise. Expects the .project-targets list to have already
400 # been constructed.
401 #
402 @cached
403 def should_clean_project(project):
404
405     if project in project_targets:
406         return True
407     else:
408
409         parent = get_manager().projects().attribute(project, "parent-module")
410         if parent and parent != "user-config":
411             return should_clean_project(parent)
412         else:
413             return False
414
415 ################################################################################
416 #
417 # main()
418 # ------
419 #
420 ################################################################################
421
422 def main():
423
424     sys.argv = bjam.variable("ARGV")
425
426     # FIXME: document this option.
427     if "--profiling" in sys.argv:
428         import cProfile
429         r = cProfile.runctx('main_real()', globals(), locals(), "stones.prof")
430
431         import pstats
432         stats = pstats.Stats("stones.prof")
433         stats.strip_dirs()
434         stats.sort_stats('time', 'calls')
435         stats.print_callers(20)
436         return r
437     else:
438         try:
439             return main_real()
440         except ExceptionWithUserContext, e:
441             e.report()
442
443 def main_real():
444
445     global debug_config, out_xml
446
447     debug_config = "--debug-configuration" in sys.argv
448     out_xml = any(re.match("^--out-xml=(.*)$", a) for a in sys.argv)
449
450     engine = Engine()
451
452     global_build_dir = option.get("build-dir")
453     manager = Manager(engine, global_build_dir)
454
455     import b2.build.configure as configure
456
457     if "--version" in sys.argv:
458
459         version.report()
460         return
461
462     # This module defines types and generator and what not,
463     # and depends on manager's existence
464     import b2.tools.builtin
465
466     b2.tools.common.init(manager)
467
468     load_configuration_files()
469
470     # Load explicitly specified toolset modules.
471     extra_properties = process_explicit_toolset_requests()
472
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
476     # target ids.
477     current_project = None
478     projects = get_manager().projects()
479     if projects.find(".", "."):
480         current_project = projects.target(projects.load("."))
481
482     # Load the default toolset module if no other has already been specified.
483     if not feature.values("toolset"):
484
485         dt = default_toolset
486         dtv = None
487         if default_toolset:
488             dtv = default_toolset_version
489         else:
490             dt = "gcc"
491             if os.name == 'nt':
492                 dt = "msvc"
493             # FIXME:
494             #else if [ os.name ] = MACOSX
495             #{
496             #    default-toolset = darwin ;
497             #}
498
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"
505
506         using(dt, dtv)
507
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)
511
512     # Expand properties specified on the command line into multiple property
513     # sets consisting of all legal property combinations. Each expanded property
514     # set will be used for a single build run. E.g. if multiple toolsets are
515     # specified then requested targets will be built with each of them.
516     if properties:
517         expanded = build_request.expand_no_defaults(properties)
518     else:
519         expanded = [property_set.empty()]
520
521     # Check that we actually found something to build.
522     if not current_project and not target_ids:
523         get_manager().errors()("no Jamfile in current directory found, and no target references specified.")
524         # FIXME:
525         # EXIT
526
527     # Flags indicating that this build system run has been started in order to
528     # clean existing instead of create new targets. Note that these are not the
529     # final flag values as they may get changed later on due to some special
530     # targets being specified on the command line.
531     clean = "--clean" in sys.argv
532     cleanall = "--clean-all" in sys.argv
533
534     # List of explicitly requested files to build. Any target references read
535     # from the command line parameter not recognized as one of the targets
536     # defined in the loaded Jamfiles will be interpreted as an explicitly
537     # requested file to build. If any such files are explicitly requested then
538     # only those files and the targets they depend on will be built and they
539     # will be searched for among targets that would have been built had there
540     # been no explicitly requested files.
541     explicitly_requested_files = []
542
543     # List of Boost Build meta-targets, virtual-targets and actual Jam targets
544     # constructed in this build system run.
545     targets = []
546     virtual_targets = []
547     actual_targets = []
548
549     explicitly_requested_files = []
550
551     # Process each target specified on the command-line and convert it into
552     # internal Boost Build target objects. Detect special clean target. If no
553     # main Boost Build targets were explictly requested use the current project
554     # as the target.
555     for id in target_ids:
556         if id == "clean":
557             clean = 1
558         else:
559             t = None
560             if current_project:
561                 t = current_project.find(id, no_error=1)
562             else:
563                 t = find_target(id)
564
565             if not t:
566                 print "notice: could not find main target '%s'" % id
567                 print "notice: assuming it's a name of file to create " ;
568                 explicitly_requested_files.append(id)
569             else:
570                 targets.append(t)
571
572     if not targets:
573         targets = [projects.target(projects.module_name("."))]
574
575     # FIXME: put this BACK.
576
577     ## if [ option.get dump-generators : : true ]
578     ## {
579     ##     generators.dump ;
580     ## }
581
582
583     # We wish to put config.log in the build directory corresponding
584     # to Jamroot, so that the location does not differ depending on
585     # directory where we do build.  The amount of indirection necessary
586     # here is scary.
587     first_project = targets[0].project()
588     first_project_root_location = first_project.get('project-root')
589     first_project_root_module = manager.projects().load(first_project_root_location)
590     first_project_root = manager.projects().target(first_project_root_module)
591     first_build_build_dir = first_project_root.build_dir()
592     configure.set_log_file(os.path.join(first_build_build_dir, "config.log"))
593
594     virtual_targets = []
595
596     global results_of_main_targets
597
598     # Now that we have a set of targets to build and a set of property sets to
599     # build the targets with, we can start the main build process by using each
600     # property set to generate virtual targets from all of our listed targets
601     # and any of their dependants.
602     for p in expanded:
603         manager.set_command_line_free_features(property_set.create(p.free()))
604
605         for t in targets:
606             try:
607                 g = t.generate(p)
608                 if not isinstance(t, ProjectTarget):
609                     results_of_main_targets.extend(g.targets())
610                 virtual_targets.extend(g.targets())
611             except ExceptionWithUserContext, e:
612                 e.report()
613             except Exception:
614                 raise
615
616     # Convert collected virtual targets into actual raw Jam targets.
617     for t in virtual_targets:
618         actual_targets.append(t.actualize())
619
620
621      # FIXME: restore
622 ##     # If XML data output has been requested prepare additional rules and targets
623 ##     # so we can hook into Jam to collect build data while its building and have
624 ##     # it trigger the final XML report generation after all the planned targets
625 ##     # have been built.
626 ##     if $(.out-xml)
627 ##     {
628 ##         # Get a qualified virtual target name.
629 ##         rule full-target-name ( target )
630 ##         {
631 ##             local name = [ $(target).name ] ;
632 ##             local project = [ $(target).project ] ;
633 ##             local project-path = [ $(project).get location ] ;
634 ##             return $(project-path)//$(name) ;
635 ##         }
636
637 ##         # Generate an XML file containing build statistics for each constituent.
638 ##         #
639 ##         rule out-xml ( xml-file : constituents * )
640 ##         {
641 ##             # Prepare valid XML header and footer with some basic info.
642 ##             local nl = "
643 ## " ;
644 ##             local jam       = [ version.jam ] ;
645 ##             local os        = [ modules.peek : OS OSPLAT JAMUNAME ] "" ;
646 ##             local timestamp = [ modules.peek : JAMDATE ] ;
647 ##             local cwd       = [ PWD ] ;
648 ##             local command   = $(.sys.argv) ;
649 ##             local bb-version = [ version.boost-build ] ;
650 ##             .header on $(xml-file) =
651 ##                 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
652 ##                 "$(nl)<build format=\"1.0\" version=\"$(bb-version)\">"
653 ##                 "$(nl)  <jam version=\"$(jam:J=.)\" />"
654 ##                 "$(nl)  <os name=\"$(os[1])\" platform=\"$(os[2])\"><![CDATA[$(os[3-]:J= )]]></os>"
655 ##                 "$(nl)  <timestamp><![CDATA[$(timestamp)]]></timestamp>"
656 ##                 "$(nl)  <directory><![CDATA[$(cwd)]]></directory>"
657 ##                 "$(nl)  <command><![CDATA[\"$(command:J=\" \")\"]]></command>"
658 ##                 ;
659 ##             .footer on $(xml-file) =
660 ##                 "$(nl)</build>" ;
661
662 ##             # Generate the target dependency graph.
663 ##             .contents on $(xml-file) +=
664 ##                 "$(nl)  <targets>" ;
665 ##             for local t in [ virtual-target.all-targets ]
666 ##             {
667 ##                 local action = [ $(t).action ] ;
668 ##                 if $(action)
669 ##                     # If a target has no action, it has no dependencies.
670 ##                 {
671 ##                     local name = [ full-target-name $(t) ] ;
672 ##                     local sources = [ $(action).sources ] ;
673 ##                     local dependencies ;
674 ##                     for local s in $(sources)
675 ##                     {
676 ##                         dependencies += [ full-target-name $(s) ] ;
677 ##                     }
678
679 ##                     local path = [ $(t).path ] ;
680 ##                     local jam-target = [ $(t).actual-name ] ;
681
682 ##                     .contents on $(xml-file) +=
683 ##                         "$(nl)    <target>"
684 ##                         "$(nl)      <name><![CDATA[$(name)]]></name>"
685 ##                         "$(nl)      <dependencies>"
686 ##                         "$(nl)        <dependency><![CDATA[$(dependencies)]]></dependency>"
687 ##                         "$(nl)      </dependencies>"
688 ##                         "$(nl)      <path><![CDATA[$(path)]]></path>"
689 ##                         "$(nl)      <jam-target><![CDATA[$(jam-target)]]></jam-target>"
690 ##                         "$(nl)    </target>"
691 ##                         ;
692 ##                 }
693 ##             }
694 ##             .contents on $(xml-file) +=
695 ##                 "$(nl)  </targets>" ;
696
697 ##             # Build $(xml-file) after $(constituents). Do so even if a
698 ##             # constituent action fails and regenerate the xml on every bjam run.
699 ##             INCLUDES $(xml-file) : $(constituents) ;
700 ##             ALWAYS $(xml-file) ;
701 ##             __ACTION_RULE__ on $(xml-file) = build-system.out-xml.generate-action ;
702 ##             out-xml.generate $(xml-file) ;
703 ##         }
704
705 ##         # The actual build actions are here; if we did this work in the actions
706 ##         # clause we would have to form a valid command line containing the
707 ##         # result of @(...) below (the name of the XML file).
708 ##         #
709 ##         rule out-xml.generate-action ( args * : xml-file
710 ##             : command status start end user system : output ? )
711 ##         {
712 ##             local contents =
713 ##                 [ on $(xml-file) return $(.header) $(.contents) $(.footer) ] ;
714 ##             local f = @($(xml-file):E=$(contents)) ;
715 ##         }
716
717 ##         # Nothing to do here; the *real* actions happen in
718 ##         # out-xml.generate-action.
719 ##         actions quietly out-xml.generate { }
720
721 ##         # Define the out-xml file target, which depends on all the targets so
722 ##         # that it runs the collection after the targets have run.
723 ##         out-xml $(.out-xml) : $(actual-targets) ;
724
725 ##         # Set up a global __ACTION_RULE__ that records all the available
726 ##         # statistics about each actual target in a variable "on" the --out-xml
727 ##         # target.
728 ##         #
729 ##         rule out-xml.collect ( xml-file : target : command status start end user
730 ##             system : output ? )
731 ##         {
732 ##             local nl = "
733 ## " ;
734 ##             # Open the action with some basic info.
735 ##             .contents on $(xml-file) +=
736 ##                 "$(nl)  <action status=\"$(status)\" start=\"$(start)\" end=\"$(end)\" user=\"$(user)\" system=\"$(system)\">" ;
737
738 ##             # If we have an action object we can print out more detailed info.
739 ##             local action = [ on $(target) return $(.action) ] ;
740 ##             if $(action)
741 ##             {
742 ##                 local action-name    = [ $(action).action-name ] ;
743 ##                 local action-sources = [ $(action).sources     ] ;
744 ##                 local action-props   = [ $(action).properties  ] ;
745
746 ##                 # The qualified name of the action which we created the target.
747 ##                 .contents on $(xml-file) +=
748 ##                     "$(nl)    <name><![CDATA[$(action-name)]]></name>" ;
749
750 ##                 # The sources that made up the target.
751 ##                 .contents on $(xml-file) +=
752 ##                     "$(nl)    <sources>" ;
753 ##                 for local source in $(action-sources)
754 ##                 {
755 ##                     local source-actual = [ $(source).actual-name ] ;
756 ##                     .contents on $(xml-file) +=
757 ##                         "$(nl)      <source><![CDATA[$(source-actual)]]></source>" ;
758 ##                 }
759 ##                 .contents on $(xml-file) +=
760 ##                     "$(nl)    </sources>" ;
761
762 ##                 # The properties that define the conditions under which the
763 ##                 # target was built.
764 ##                 .contents on $(xml-file) +=
765 ##                     "$(nl)    <properties>" ;
766 ##                 for local prop in [ $(action-props).raw ]
767 ##                 {
768 ##                     local prop-name = [ MATCH ^<(.*)>$ : $(prop:G) ] ;
769 ##                     .contents on $(xml-file) +=
770 ##                         "$(nl)      <property name=\"$(prop-name)\"><![CDATA[$(prop:G=)]]></property>" ;
771 ##                 }
772 ##                 .contents on $(xml-file) +=
773 ##                     "$(nl)    </properties>" ;
774 ##             }
775
776 ##             local locate = [ on $(target) return $(LOCATE) ] ;
777 ##             locate ?= "" ;
778 ##             .contents on $(xml-file) +=
779 ##                 "$(nl)    <jam-target><![CDATA[$(target)]]></jam-target>"
780 ##                 "$(nl)    <path><![CDATA[$(target:G=:R=$(locate))]]></path>"
781 ##                 "$(nl)    <command><![CDATA[$(command)]]></command>"
782 ##                 "$(nl)    <output><![CDATA[$(output)]]></output>" ;
783 ##             .contents on $(xml-file) +=
784 ##                 "$(nl)  </action>" ;
785 ##         }
786
787 ##         # When no __ACTION_RULE__ is set "on" a target, the search falls back to
788 ##         # the global module.
789 ##         module
790 ##         {
791 ##             __ACTION_RULE__ = build-system.out-xml.collect
792 ##                 [ modules.peek build-system : .out-xml ] ;
793 ##         }
794
795 ##         IMPORT
796 ##             build-system :
797 ##             out-xml.collect
798 ##             out-xml.generate-action
799 ##             : :
800 ##             build-system.out-xml.collect
801 ##             build-system.out-xml.generate-action
802 ##             ;
803 ##     }
804
805     j = option.get("jobs")
806     if j:
807         bjam.call("set-variable", PARALLELISM, j)
808
809     k = option.get("keep-going", "true", "true")
810     if k in ["on", "yes", "true"]:
811         bjam.call("set-variable", "KEEP_GOING", "1")
812     elif k in ["off", "no", "false"]:
813         bjam.call("set-variable", "KEEP_GOING", "0")
814     else:
815         print "error: Invalid value for the --keep-going option"
816         sys.exit()
817
818     # The 'all' pseudo target is not strictly needed expect in the case when we
819     # use it below but people often assume they always have this target
820     # available and do not declare it themselves before use which may cause
821     # build failures with an error message about not being able to build the
822     # 'all' target.
823     bjam.call("NOTFILE", "all")
824
825     # And now that all the actual raw Jam targets and all the dependencies
826     # between them have been prepared all that is left is to tell Jam to update
827     # those targets.
828     if explicitly_requested_files:
829         # Note that this case can not be joined with the regular one when only
830         # exact Boost Build targets are requested as here we do not build those
831         # requested targets but only use them to construct the dependency tree
832         # needed to build the explicitly requested files.
833         # FIXME: add $(.out-xml)
834         bjam.call("UPDATE", ["<e>%s" % x for x in explicitly_requested_files])
835     elif cleanall:
836         bjam.call("UPDATE", "clean-all")
837     elif clean:
838         manager.engine().set_update_action("common.Clean", "clean",
839                                            actual_clean_targets(targets))
840         bjam.call("UPDATE", "clean")
841     else:
842         # FIXME:
843         #configure.print-configure-checks-summary ;
844
845         if pre_build_hook:
846             for h in pre_build_hook:
847                 h()
848
849         bjam.call("DEPENDS", "all", actual_targets)
850         ok = bjam.call("UPDATE_NOW", "all") # FIXME: add out-xml
851         if post_build_hook:
852             post_build_hook(ok)
853         # Prevent automatic update of the 'all' target, now that
854         # we have explicitly updated what we wanted.
855         bjam.call("UPDATE")
856
857     if manager.errors().count() == 0:
858         return ["ok"]
859     else:
860         return []