3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """The main() function used by the scons script.
26 Architecturally, this *is* the scons script, and will likely only be
27 called from the external "scons" wrapper. Consequently, anything here
28 should not be, or be considered, part of the build engine. If it's
29 something that we expect other software to want to use, it should go in
30 some other module. If it's specific to the "scons" script invocation,
44 from typing
import Optional
, List
49 import SCons
.Environment
51 import SCons
.Taskmaster
.Job
55 import SCons
.Platform
.virtualenv
58 import SCons
.Taskmaster
61 import SCons
.Script
.Interactive
62 from SCons
.Util
.stats
import count_stats
, memory_stats
, time_stats
, ENABLE_JSON
, write_scons_stats_file
, JSON_OUTPUT_FILE
64 from SCons
import __version__
as SConsVersion
66 # these define the range of versions SCons supports
67 minimum_python_version
= (3, 6, 0)
68 deprecated_python_version
= (3, 6, 0)
70 # ordered list of SConsctruct names to look for if there is no -f flag
71 KNOWN_SCONSTRUCT_NAMES
= [
80 # list of names recognized by debugger as "SConscript files" (inc. SConstruct)
81 # files suffixed .py always work so don't need to be in this list.
91 first_command_start
= None
92 last_command_end
= None
94 print_memoizer
= False
95 print_stacktrace
= False
97 print_action_timestamps
= False
99 cumulative_command_time
= 0
100 exit_status
= 0 # final exit status, assume success by default
101 this_build_status
= 0 # "exit status" of an individual build
103 delayed_warnings
= []
106 def revert_io() -> None:
107 # This call is added to revert stderr and stdout to the original
108 # ones just in case some build rule or something else in the system
109 # has redirected them elsewhere.
110 sys
.stderr
= sys
.__stderr
__
111 sys
.stdout
= sys
.__stdout
__
114 class SConsPrintHelpException(Exception):
118 display
= SCons
.Util
.display
119 progress_display
= SCons
.Util
.DisplayEngine()
125 target_string
= '$TARGET'
127 def __init__(self
, obj
, interval
: int=1, file=None, overwrite
: bool=False) -> None:
133 self
.interval
= interval
134 self
.overwrite
= overwrite
138 elif SCons
.Util
.is_List(obj
):
139 self
.func
= self
.spinner
140 elif obj
.find(self
.target_string
) != -1:
141 self
.func
= self
.replace_string
143 self
.func
= self
.string
145 def write(self
, s
) -> None:
150 def erase_previous(self
) -> None:
152 length
= len(self
.prev
)
153 if self
.prev
[-1] in ('\n', '\r'):
155 self
.write(' ' * length
+ '\r')
158 def spinner(self
, node
) -> None:
159 self
.write(self
.obj
[self
.count
% len(self
.obj
)])
161 def string(self
, node
) -> None:
164 def replace_string(self
, node
) -> None:
165 self
.write(self
.obj
.replace(self
.target_string
, str(node
)))
167 def __call__(self
, node
) -> None:
168 self
.count
= self
.count
+ 1
169 if (self
.count
% self
.interval
) == 0:
171 self
.erase_previous()
174 ProgressObject
= SCons
.Util
.Null()
176 def Progress(*args
, **kw
) -> None:
177 global ProgressObject
178 ProgressObject
= Progressor(*args
, **kw
)
186 def GetBuildFailures():
187 return _BuildFailures
190 class BuildTask(SCons
.Taskmaster
.OutOfDateTask
):
191 """An SCons build task."""
192 progress
= ProgressObject
194 def display(self
, message
) -> None:
195 display('scons: ' + message
)
198 if not isinstance(self
.progress
, SCons
.Util
.Null
):
199 for target
in self
.targets
:
200 self
.progress(target
)
201 return SCons
.Taskmaster
.OutOfDateTask
.prepare(self
)
203 def needs_execute(self
) -> bool:
204 if SCons
.Taskmaster
.OutOfDateTask
.needs_execute(self
):
206 if self
.top
and self
.targets
[0].has_builder():
207 display("scons: `%s' is up to date." % str(self
.node
))
210 def execute(self
) -> None:
212 start_time
= time
.time()
213 global first_command_start
214 if first_command_start
is None:
215 first_command_start
= start_time
216 SCons
.Taskmaster
.OutOfDateTask
.execute(self
)
218 global cumulative_command_time
219 global last_command_end
220 finish_time
= time
.time()
221 last_command_end
= finish_time
222 cumulative_command_time
+= finish_time
- start_time
223 if print_action_timestamps
:
225 "Command execution start timestamp: %s: %f\n"
226 % (str(self
.node
), start_time
)
229 "Command execution end timestamp: %s: %f\n"
230 % (str(self
.node
), finish_time
)
232 time_stats
.add_command(str(self
.node
), start_time
, finish_time
)
234 "Command execution time: %s: %f seconds\n"
235 % (str(self
.node
), (finish_time
- start_time
))
238 def do_failed(self
, status
: int=2) -> None:
239 _BuildFailures
.append(self
.exception
[1])
241 global this_build_status
242 if self
.options
.ignore_errors
:
243 SCons
.Taskmaster
.OutOfDateTask
.executed(self
)
244 elif self
.options
.keep_going
:
245 SCons
.Taskmaster
.OutOfDateTask
.fail_continue(self
)
247 this_build_status
= status
249 SCons
.Taskmaster
.OutOfDateTask
.fail_stop(self
)
251 this_build_status
= status
255 if self
.top
and not t
.has_builder() and not t
.side_effect
:
257 if t
.__class
__.__name
__ in ('File', 'Dir', 'Entry'):
258 errstr
="Do not know how to make %s target `%s' (%s)." % (t
.__class
__.__name
__, t
, t
.get_abspath())
259 else: # Alias or Python or ...
260 errstr
="Do not know how to make %s target `%s'." % (t
.__class
__.__name
__, t
)
261 sys
.stderr
.write("scons: *** " + errstr
)
262 if not self
.options
.keep_going
:
263 sys
.stderr
.write(" Stop.")
264 sys
.stderr
.write("\n")
266 raise SCons
.Errors
.BuildError(t
, errstr
)
267 except KeyboardInterrupt:
273 print("scons: Nothing to be done for `%s'." % t
)
274 SCons
.Taskmaster
.OutOfDateTask
.executed(self
)
276 SCons
.Taskmaster
.OutOfDateTask
.executed(self
)
278 def failed(self
) -> None:
279 # Handle the failure of a build task. The primary purpose here
280 # is to display the various types of Errors and Exceptions
282 exc_info
= self
.exc_info()
290 # The Taskmaster didn't record an exception for this Task;
291 # see if the sys module has one.
293 t
, e
, tb
= sys
.exc_info()[:]
298 # Deprecated string exceptions will have their string stored
299 # in the first entry of the tuple.
303 buildError
= SCons
.Errors
.convert_to_BuildError(e
)
304 if not buildError
.node
:
305 buildError
.node
= self
.node
307 node
= buildError
.node
308 if not SCons
.Util
.is_List(node
):
310 nodename
= ', '.join(map(str, node
))
312 errfmt
= "scons: *** [%s] %s\n"
313 sys
.stderr
.write(errfmt
% (nodename
, buildError
))
315 if (buildError
.exc_info
[2] and buildError
.exc_info
[1] and
317 buildError
.exc_info
[1],
318 (EnvironmentError, SCons
.Errors
.StopError
,
319 SCons
.Errors
.UserError
))):
320 type, value
, trace
= buildError
.exc_info
321 if tb
and print_stacktrace
:
322 sys
.stderr
.write("scons: internal stack trace:\n")
323 traceback
.print_tb(tb
, file=sys
.stderr
)
324 traceback
.print_exception(type, value
, trace
)
325 elif tb
and print_stacktrace
:
326 sys
.stderr
.write("scons: internal stack trace:\n")
327 traceback
.print_tb(tb
, file=sys
.stderr
)
329 self
.exception
= (e
, buildError
, tb
) # type, value, traceback
330 self
.do_failed(buildError
.exitstatus
)
334 def postprocess(self
) -> None:
337 for tp
in self
.options
.tree_printers
:
339 if self
.options
.debug_includes
:
340 tree
= t
.render_include_tree()
344 SCons
.Taskmaster
.OutOfDateTask
.postprocess(self
)
346 def make_ready(self
) -> None:
347 """Make a task ready for execution"""
348 SCons
.Taskmaster
.OutOfDateTask
.make_ready(self
)
349 if self
.out_of_date
and self
.options
.debug_explain
:
350 explanation
= self
.out_of_date
[0].explain()
352 sys
.stdout
.write("scons: " + explanation
)
355 class CleanTask(SCons
.Taskmaster
.AlwaysTask
):
356 """An SCons clean task."""
357 def fs_delete(self
, path
, pathstr
, remove
: bool=True):
359 if os
.path
.lexists(path
):
360 if os
.path
.isfile(path
) or os
.path
.islink(path
):
361 if remove
: os
.unlink(path
)
362 display("Removed " + pathstr
)
363 elif os
.path
.isdir(path
) and not os
.path
.islink(path
):
364 # delete everything in the dir
365 for e
in sorted(os
.listdir(path
)):
366 p
= os
.path
.join(path
, e
)
367 s
= os
.path
.join(pathstr
, e
)
368 if os
.path
.isfile(p
):
369 if remove
: os
.unlink(p
)
370 display("Removed " + s
)
372 self
.fs_delete(p
, s
, remove
)
373 # then delete dir itself
374 if remove
: os
.rmdir(path
)
375 display("Removed directory " + pathstr
)
377 errstr
= "Path '%s' exists but isn't a file or directory."
378 raise SCons
.Errors
.UserError(errstr
% pathstr
)
379 except SCons
.Errors
.UserError
as e
:
382 print("scons: Could not remove '%s':" % pathstr
, e
.strerror
)
384 def _get_files_to_clean(self
):
386 target
= self
.targets
[0]
387 if target
.has_builder() or target
.side_effect
:
388 result
= [t
for t
in self
.targets
if not t
.noclean
]
391 def _clean_targets(self
, remove
: bool=True) -> None:
392 target
= self
.targets
[0]
393 if target
in SCons
.Environment
.CleanTargets
:
394 files
= SCons
.Environment
.CleanTargets
[target
]
396 self
.fs_delete(f
.get_abspath(), str(f
), remove
)
398 def show(self
) -> None:
399 for t
in self
._get
_files
_to
_clean
():
401 display("Removed " + str(t
))
402 self
._clean
_targets
(remove
=False)
404 def remove(self
) -> None:
405 for t
in self
._get
_files
_to
_clean
():
409 # An OSError may indicate something like a permissions
410 # issue, an IOError would indicate something like
411 # the file not existing. In either case, print a
412 # message and keep going to try to remove as many
413 # targets as possible.
414 print(f
"scons: Could not remove '{str(t)}'", e
.strerror
)
417 display("Removed " + str(t
))
418 self
._clean
_targets
(remove
=True)
422 # We want the Taskmaster to update the Node states (and therefore
423 # handle reference counts, etc.), but we don't want to call
424 # back to the Node's post-build methods, which would do things
425 # we don't want, like store .sconsign information.
426 executed
= SCons
.Taskmaster
.Task
.executed_without_callbacks
428 # Have the Taskmaster arrange to "execute" all of the targets, because
429 # we'll figure out ourselves (in remove() or show() above) whether
430 # anything really needs to be done.
431 make_ready
= SCons
.Taskmaster
.Task
.make_ready_all
433 def prepare(self
) -> None:
436 class QuestionTask(SCons
.Taskmaster
.AlwaysTask
):
437 """An SCons task for the -q (question) option."""
438 def prepare(self
) -> None:
441 def execute(self
) -> None:
442 if self
.targets
[0].get_state() != SCons
.Node
.up_to_date
or \
443 (self
.top
and not self
.targets
[0].exists()):
445 global this_build_status
447 this_build_status
= 1
450 def executed(self
) -> None:
455 def __init__(self
, derived
: bool=False, prune
: bool=False, status
: bool=False, sLineDraw
: bool=False) -> None:
456 self
.derived
= derived
459 self
.sLineDraw
= sLineDraw
460 def get_all_children(self
, node
):
461 return node
.all_children()
462 def get_derived_children(self
, node
):
463 children
= node
.all_children(None)
464 return [x
for x
in children
if x
.has_builder()]
465 def display(self
, t
) -> None:
467 func
= self
.get_derived_children
469 func
= self
.get_all_children
470 s
= self
.status
and 2 or 0
471 SCons
.Util
.print_tree(t
, func
, prune
=self
.prune
, showtags
=s
, lastChild
=True, singleLineDraw
=self
.sLineDraw
)
474 def python_version_string():
475 return sys
.version
.split()[0]
477 def python_version_unsupported(version
=sys
.version_info
):
478 return version
< minimum_python_version
480 def python_version_deprecated(version
=sys
.version_info
):
481 return version
< deprecated_python_version
484 class FakeOptionParser
:
485 """A do-nothing option parser, used for the initial OptionsParser value.
487 During normal SCons operation, the OptionsParser is created right
488 away by the main() function. Certain test scripts however, can
489 introspect on different Tool modules, the initialization of which
490 can try to add a new, local option to an otherwise uninitialized
491 OptionsParser object. This allows that introspection to happen
495 class FakeOptionValues
:
496 def __getattr__(self
, attr
):
499 values
= FakeOptionValues()
501 # TODO: to quiet checkers, FakeOptionParser should also define
502 # raise_exception_on_error, preserve_unknown_options, largs and parse_args
504 def add_local_option(self
, *args
, **kw
) -> None:
508 OptionsParser
= FakeOptionParser()
510 def AddOption(*args
, **kw
):
511 if 'default' not in kw
:
513 result
= OptionsParser
.add_local_option(*args
, **kw
)
517 return getattr(OptionsParser
.values
, name
)
519 def SetOption(name
, value
):
520 return OptionsParser
.values
.set_option(name
, value
)
522 def DebugOptions(json
=None):
524 API to allow specifying options to SCons debug logic
525 Currently only json is supported which changes the
526 json file written by --debug=json from the default
529 json_node
= SCons
.Defaults
.DefaultEnvironment().arg2nodes(json
)
530 SCons
.Util
.stats
.JSON_OUTPUT_FILE
= json_node
[0].get_abspath()
531 # Check if parent dir to JSON_OUTPUT_FILE exists
532 json_dir
= os
.path
.dirname(SCons
.Util
.stats
.JSON_OUTPUT_FILE
)
534 if not os
.path
.isdir(json_dir
):
535 os
.makedirs(json_dir
, exist_ok
=True)
536 # Now try to open file and see if you can..
537 with
open(SCons
.Util
.stats
.JSON_OUTPUT_FILE
,'w') as js
:
540 raise SCons
.Errors
.UserError(f
"Unable to create directory for JSON debug output file: {SCons.Util.stats.JSON_OUTPUT_FILE}")
543 def ValidateOptions(throw_exception
: bool=False) -> None:
544 """Validate options passed to SCons on the command line.
546 Checks that all options given on the command line are known to this
547 instance of SCons. Call after all of the cli options have been set
548 up through :func:`AddOption` calls. For example, if you added an
549 option ``--xyz`` and you call SCons with ``--xyy`` you can cause
550 SCons to issue an error message and exit by calling this function.
553 throw_exception: if an invalid option is present on the command line,
554 raises an exception if this optional parameter evaluates true;
555 if false (the default), issue a message and exit with error status.
558 SConsBadOptionError: If *throw_exception* is true and there are invalid
559 options on the command line.
561 .. versionadded:: 4.5.0
563 OptionsParser
.raise_exception_on_error
= throw_exception
564 OptionsParser
.preserve_unknown_options
= False
565 OptionsParser
.parse_args(OptionsParser
.largs
, OptionsParser
.values
)
567 def PrintHelp(file=None, local_only
: bool = False) -> None:
569 OptionsParser
.print_local_option_help(file=file)
571 OptionsParser
.print_help(file=file)
576 def _scons_syntax_error(e
) -> None:
577 """Handle syntax errors. Print out a message and show where the error
580 etype
, value
, tb
= sys
.exc_info()
581 lines
= traceback
.format_exception_only(etype
, value
)
583 sys
.stderr
.write(line
+'\n')
586 def find_deepest_user_frame(tb
):
588 Find the deepest stack frame that is not part of SCons.
590 Input is a "pre-processed" stack trace in the form
591 returned by traceback.extract_tb() or traceback.extract_stack()
596 # find the deepest traceback frame that is not part
600 if filename
.find(os
.sep
+'SCons'+os
.sep
) == -1:
604 def _scons_user_error(e
) -> None:
605 """Handle user errors. Print out a message and a description of the
606 error, along with the line number and routine where it occured.
607 The file and line number will be the deepest stack frame that is
608 not part of SCons itself.
610 global print_stacktrace
611 etype
, value
, tb
= sys
.exc_info()
613 traceback
.print_exception(etype
, value
, tb
)
614 filename
, lineno
, routine
, dummy
= find_deepest_user_frame(traceback
.extract_tb(tb
))
615 sys
.stderr
.write("\nscons: *** %s\n" % value
)
616 sys
.stderr
.write('File "%s", line %d, in %s\n' % (filename
, lineno
, routine
))
619 def _scons_user_warning(e
) -> None:
620 """Handle user warnings. Print out a message and a description of
621 the warning, along with the line number and routine where it occured.
622 The file and line number will be the deepest stack frame that is
623 not part of SCons itself.
625 etype
, value
, tb
= sys
.exc_info()
626 filename
, lineno
, routine
, dummy
= find_deepest_user_frame(traceback
.extract_tb(tb
))
627 sys
.stderr
.write("\nscons: warning: %s\n" % e
)
628 sys
.stderr
.write('File "%s", line %d, in %s\n' % (filename
, lineno
, routine
))
630 def _scons_internal_warning(e
) -> None:
631 """Slightly different from _scons_user_warning in that we use the
632 *current call stack* rather than sys.exc_info() to get our stack trace.
633 This is used by the warnings framework to print warnings."""
634 filename
, lineno
, routine
, dummy
= find_deepest_user_frame(traceback
.extract_stack())
635 sys
.stderr
.write("\nscons: warning: %s\n" % e
.args
[0])
636 sys
.stderr
.write('File "%s", line %d, in %s\n' % (filename
, lineno
, routine
))
638 def _scons_internal_error() -> None:
639 """Handle all errors but user errors. Print out a message telling
640 the user what to do in this case and print a normal trace.
642 print('internal error')
643 traceback
.print_exc()
646 def _SConstruct_exists(
647 dirname
: str, repositories
: List
[str], filelist
: List
[str]
649 """Check that an SConstruct file exists in a directory.
652 dirname: the directory to search. If empty, look in cwd.
653 repositories: a list of repositories to search in addition to the
654 project directory tree.
655 filelist: names of SConstruct file(s) to search for.
656 If empty list, use the built-in list of names.
659 The path to the located SConstruct file, or ``None``.
663 filelist
= KNOWN_SCONSTRUCT_NAMES
664 for file in filelist
:
665 sfile
= os
.path
.join(dirname
, file)
666 if os
.path
.isfile(sfile
):
668 if not os
.path
.isabs(sfile
):
669 for rep
in repositories
:
670 if os
.path
.isfile(os
.path
.join(rep
, sfile
)):
675 def _set_debug_values(options
) -> None:
676 global print_memoizer
, print_objects
, print_stacktrace
, print_time
, \
677 print_action_timestamps
, ENABLE_JSON
679 debug_values
= options
.debug
681 if "count" in debug_values
:
682 # All of the object counts are within "if track_instances:" blocks,
683 # which get stripped when running optimized (with python -O or
684 # from compiled *.pyo files). Provide a warning if __debug__ is
685 # stripped, so it doesn't just look like --debug=count is broken.
687 if __debug__
: enable_count
= True
689 count_stats
.enable(sys
.stdout
)
690 SCons
.Debug
.track_instances
= True
692 msg
= "--debug=count is not supported when running SCons\n" + \
693 "\twith the python -O option or optimized (.pyo) modules."
694 SCons
.Warnings
.warn(SCons
.Warnings
.NoObjectCountWarning
, msg
)
695 if "dtree" in debug_values
:
696 options
.tree_printers
.append(TreePrinter(derived
=True))
697 options
.debug_explain
= "explain" in debug_values
698 if "findlibs" in debug_values
:
699 SCons
.Scanner
.Prog
.print_find_libs
= "findlibs"
700 options
.debug_includes
= "includes" in debug_values
701 print_memoizer
= "memoizer" in debug_values
702 if "memory" in debug_values
:
703 memory_stats
.enable(sys
.stdout
)
704 print_objects
= ("objects" in debug_values
)
706 SCons
.Debug
.track_instances
= True
707 if "presub" in debug_values
:
708 SCons
.Action
.print_actions_presub
= True
709 if "stacktrace" in debug_values
:
710 print_stacktrace
= True
711 if "stree" in debug_values
:
712 options
.tree_printers
.append(TreePrinter(status
=True))
713 if "time" in debug_values
:
715 time_stats
.enable(sys
.stdout
)
716 time_stats
.enable(sys
.stdout
)
717 if "action-timestamps" in debug_values
:
719 print_action_timestamps
= True
720 if "tree" in debug_values
:
721 options
.tree_printers
.append(TreePrinter())
722 if "prepare" in debug_values
:
723 SCons
.Taskmaster
.print_prepare
= True
724 if "duplicate" in debug_values
:
725 SCons
.Node
.print_duplicate
= True
726 if "json" in debug_values
:
728 if "sconscript" in debug_values
:
729 SCons
.Debug
.sconscript_trace
= True
731 def _create_path(plist
):
737 path
= path
+ '/' + d
740 def _load_site_scons_dir(topdir
, site_dir_name
=None):
741 """Load the site directory under topdir.
743 If a site dir name is supplied use it, else use default "site_scons"
744 Prepend site dir to sys.path.
745 If a "site_tools" subdir exists, prepend to toolpath.
746 Import "site_init.py" from site dir if it exists.
749 err_if_not_found
= True # user specified: err if missing
751 site_dir_name
= "site_scons"
752 err_if_not_found
= False # scons default: okay to be missing
753 site_dir
= os
.path
.join(topdir
, site_dir_name
)
755 if not os
.path
.exists(site_dir
):
757 raise SCons
.Errors
.UserError("site dir %s not found." % site_dir
)
759 sys
.path
.insert(0, os
.path
.abspath(site_dir
))
761 site_init_filename
= "site_init.py"
762 site_init_modname
= "site_init"
763 site_tools_dirname
= "site_tools"
764 site_init_file
= os
.path
.join(site_dir
, site_init_filename
)
765 site_tools_dir
= os
.path
.join(site_dir
, site_tools_dirname
)
767 if os
.path
.exists(site_tools_dir
):
768 SCons
.Tool
.DefaultToolpath
.insert(0, os
.path
.abspath(site_tools_dir
))
770 if not os
.path
.exists(site_init_file
):
773 # "import" the site_init.py file into the SCons.Script namespace.
774 # This is a variant on the basic Python import flow in that the globals
775 # dict for the compile step is prepopulated from the SCons.Script
776 # module object; on success the SCons.Script globals are refilled
777 # from the site_init globals so it all appears in SCons.Script
778 # instead of as a separate module.
781 m
= sys
.modules
['SCons.Script']
783 fmt
= 'cannot import {}: missing SCons.Script module'
784 raise SCons
.Errors
.InternalError(fmt
.format(site_init_file
))
786 spec
= importlib
.util
.spec_from_file_location(site_init_modname
, site_init_file
)
788 "__file__": spec
.origin
,
789 "__name__": spec
.name
,
792 re_dunder
= re
.compile(r
"__[^_]+__")
793 # update site dict with all but magic (dunder) methods
794 for k
, v
in m
.__dict
__.items():
795 if not re_dunder
.match(k
):
798 with
open(spec
.origin
) as f
:
801 codeobj
= compile(code
, spec
.name
, "exec")
802 exec(codeobj
, site_m
)
803 except KeyboardInterrupt:
806 fmt
= "*** Error loading site_init file {}:\n"
807 sys
.stderr
.write(fmt
.format(site_init_file
))
810 # now refill globals with site_init's symbols
811 for k
, v
in site_m
.items():
812 if not re_dunder
.match(k
):
814 except KeyboardInterrupt:
817 fmt
= "*** cannot import site init file {}:\n"
818 sys
.stderr
.write(fmt
.format(site_init_file
))
822 def _load_all_site_scons_dirs(topdir
, verbose
: bool=False) -> None:
823 """Load all of the predefined site_scons dir.
824 Order is significant; we load them in order from most generic
825 (machine-wide) to most specific (topdir).
826 The verbose argument is only for testing.
828 platform
= SCons
.Platform
.platform_default()
831 return os
.path
.expanduser('~/'+d
)
833 if platform
== 'win32' or platform
== 'cygwin':
835 os
.path
.expandvars('%AllUsersProfile%\\scons'),
836 # TODO older path, kept for compat
837 os
.path
.expandvars('%AllUsersProfile%\\Application Data\\scons'),
838 os
.path
.expandvars('%LocalAppData%\\scons')]
839 appdatadir
= os
.path
.expandvars('%AppData%\\scons')
840 if appdatadir
not in sysdirs
:
841 sysdirs
.append(appdatadir
)
842 sysdirs
.append(homedir('.scons'))
844 elif platform
== 'darwin': # MacOS X
845 sysdirs
=['/Library/Application Support/SCons',
846 '/opt/local/share/scons', # (for MacPorts)
847 '/sw/share/scons', # (for Fink)
848 homedir('Library/Application Support/SCons'),
850 elif platform
== 'sunos': # Solaris
851 sysdirs
=['/opt/sfw/scons',
854 else: # Linux, HPUX, etc.
855 # assume posix-like, i.e. platform == 'posix'
856 sysdirs
=['/usr/share/scons',
859 dirs
= sysdirs
+ [topdir
]
861 if verbose
: # this is used by unit tests.
862 print("Loading site dir ", d
)
863 _load_site_scons_dir(d
)
865 def test_load_all_site_scons_dirs(d
) -> None:
866 _load_all_site_scons_dirs(d
, True)
868 def version_string(label
, module
):
869 version
= module
.__version
__
870 build
= module
.__build
__
874 version
= version
+ build
875 fmt
= "\t%s: v%s, %s, by %s on %s\n"
879 module
.__developer
__,
882 def path_string(label
, module
) -> str:
883 path
= module
.__path
__
884 return "\t%s path: %s\n"%(label
,path
)
888 global this_build_status
890 options
= parser
.values
892 # Here's where everything really happens.
894 # First order of business: set up default warnings and then
895 # handle the user's warning options, so that we can issue (or
896 # suppress) appropriate warnings about anything that might happen,
897 # as configured by the user.
899 default_warnings
= [ SCons
.Warnings
.WarningOnByDefault
,
900 SCons
.Warnings
.DeprecatedWarning
,
903 for warning
in default_warnings
:
904 SCons
.Warnings
.enableWarningClass(warning
)
905 SCons
.Warnings
._warningOut
= _scons_internal_warning
906 SCons
.Warnings
.process_warn_strings(options
.warn
)
908 # Now that we have the warnings configuration set up, we can actually
909 # issue (or suppress) any warnings about warning-worthy things that
910 # occurred while the command-line options were getting parsed.
912 dw
= options
.delayed_warnings
913 except AttributeError:
916 delayed_warnings
.extend(dw
)
917 for warning_type
, message
in delayed_warnings
:
918 SCons
.Warnings
.warn(warning_type
, message
)
920 if not SCons
.Platform
.virtualenv
.virtualenv_enabled_by_default
:
921 if options
.enable_virtualenv
:
922 SCons
.Platform
.virtualenv
.enable_virtualenv
= True
924 if options
.ignore_virtualenv
:
925 SCons
.Platform
.virtualenv
.ignore_virtualenv
= True
927 if options
.diskcheck
:
928 SCons
.Node
.FS
.set_diskcheck(options
.diskcheck
)
930 # Next, we want to create the FS object that represents the outside
931 # world's file system, as that's central to a lot of initialization.
932 # To do this, however, we need to be in the directory from which we
933 # want to start everything, which means first handling any relevant
934 # options that might cause us to chdir somewhere (-C, -D, -U, -u).
935 if options
.directory
:
936 script_dir
= os
.path
.abspath(_create_path(options
.directory
))
938 script_dir
= os
.getcwd()
942 target_top
= '.' # directory to prepend to targets
943 while script_dir
and not _SConstruct_exists(
944 script_dir
, options
.repository
, options
.file
946 script_dir
, last_part
= os
.path
.split(script_dir
)
948 target_top
= os
.path
.join(last_part
, target_top
)
952 if script_dir
and script_dir
!= os
.getcwd():
953 if not options
.silent
:
954 display("scons: Entering directory `%s'" % script_dir
)
958 sys
.stderr
.write("Could not change directory to %s\n" % script_dir
)
960 # Now that we're in the top-level SConstruct directory, go ahead
961 # and initialize the FS object that represents the file system,
962 # and make it the build engine default.
963 fs
= SCons
.Node
.FS
.get_default_fs()
965 for rep
in options
.repository
:
968 # Now that we have the FS object, the next order of business is to
969 # check for an SConstruct file (or other specified config file).
970 # If there isn't one, we can bail before doing any more work.
973 scripts
.extend(options
.file)
975 sfile
= _SConstruct_exists("", options
.repository
, options
.file)
977 scripts
.append(sfile
)
981 # There's no SConstruct, but they specified -h.
982 # Give them the options usage now, before we fail
983 # trying to read a non-existent SConstruct file.
984 raise SConsPrintHelpException
985 raise SCons
.Errors
.UserError("No SConstruct file found.")
987 if scripts
[0] == "-":
990 d
= fs
.File(scripts
[0]).dir
991 fs
.set_SConstruct_dir(d
)
993 _set_debug_values(options
)
994 SCons
.Node
.implicit_cache
= options
.implicit_cache
995 SCons
.Node
.implicit_deps_changed
= options
.implicit_deps_changed
996 SCons
.Node
.implicit_deps_unchanged
= options
.implicit_deps_unchanged
999 SCons
.SConf
.dryrun
= 1
1000 SCons
.Action
.execute_actions
= None
1001 if options
.question
:
1002 SCons
.SConf
.dryrun
= 1
1004 SCons
.SConf
.SetBuildType('clean')
1006 SCons
.SConf
.SetBuildType('help')
1007 SCons
.SConf
.SetCacheMode(options
.config
)
1008 SCons
.SConf
.SetProgressDisplay(progress_display
)
1010 if options
.no_progress
or options
.silent
:
1011 progress_display
.set_mode(0)
1013 # if site_dir unchanged from default None, neither --site-dir
1014 # nor --no-site-dir was seen, use SCons default
1015 if options
.site_dir
is None:
1016 _load_all_site_scons_dirs(d
.get_internal_path())
1017 elif options
.site_dir
: # if a dir was set, use it
1018 _load_site_scons_dir(d
.get_internal_path(), options
.site_dir
)
1020 if options
.include_dir
:
1021 sys
.path
= options
.include_dir
+ sys
.path
1023 # If we're about to start SCons in the interactive mode,
1024 # inform the FS about this right here. Else, the release_target_info
1025 # method could get called on some nodes, like the used "gcc" compiler,
1026 # when using the Configure methods within the SConscripts.
1027 # This would then cause subtle bugs, as already happened in #2971.
1028 if options
.interactive
:
1029 SCons
.Node
.interactive
= True
1030 # That should cover (most of) the options.
1031 # Next, set up the variables that hold command-line arguments,
1032 # so the SConscript files that we read and execute have access to them.
1033 # TODO: for options defined via AddOption which take space-separated
1034 # option-args, the option-args will collect into targets here,
1035 # because we don't yet know to do any different.
1038 for a
in parser
.largs
:
1039 # Skip so-far unrecognized options, and empty string args
1040 if a
.startswith('-') or a
in ('', '""', "''"):
1046 SCons
.Script
._Add
_Targets
(targets
+ parser
.rargs
)
1047 SCons
.Script
._Add
_Arguments
(xmit_args
)
1049 # If stdout is not a tty, replace it with a wrapper object to call flush
1050 # after every write.
1052 # Tty devices automatically flush after every newline, so the replacement
1053 # isn't necessary. Furthermore, if we replace sys.stdout, the readline
1054 # module will no longer work. This affects the behavior during
1055 # --interactive mode. --interactive should only be used when stdin and
1056 # stdout refer to a tty.
1057 if not hasattr(sys
.stdout
, 'isatty') or not sys
.stdout
.isatty():
1058 sys
.stdout
= SCons
.Util
.Unbuffered(sys
.stdout
)
1059 if not hasattr(sys
.stderr
, 'isatty') or not sys
.stderr
.isatty():
1060 sys
.stderr
= SCons
.Util
.Unbuffered(sys
.stderr
)
1062 memory_stats
.append('before reading SConscript files:')
1063 count_stats
.append(('pre-', 'read'))
1065 # And here's where we (finally) read the SConscript files.
1067 progress_display("scons: Reading SConscript files ...")
1070 start_time
= time
.time()
1072 for script
in scripts
:
1073 SCons
.Script
._SConscript
._SConscript
(fs
, script
)
1074 except SCons
.Errors
.StopError
as e
:
1075 # We had problems reading an SConscript file, such as it
1076 # couldn't be copied in to the VariantDir. Since we're just
1077 # reading SConscript files and haven't started building
1078 # things yet, stop regardless of whether they used -i or -k
1081 sys
.stderr
.write("scons: *** %s Stop.\n" % e
)
1084 global sconscript_time
1085 sconscript_time
= time
.time() - start_time
1087 progress_display("scons: done reading SConscript files.")
1089 memory_stats
.append('after reading SConscript files:')
1090 count_stats
.append(('post-', 'read'))
1092 # Re-{enable,disable} warnings in case they disabled some in
1093 # the SConscript file.
1095 # We delay enabling the PythonVersionWarning class until here so that,
1096 # if they explicitly disabled it in either in the command line or in
1097 # $SCONSFLAGS, or in the SConscript file, then the search through
1098 # the list of deprecated warning classes will find that disabling
1099 # first and not issue the warning.
1100 #SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning)
1101 SCons
.Warnings
.process_warn_strings(options
.warn
)
1103 # Now that we've read the SConscript files, we can check for the
1104 # warning about deprecated Python versions--delayed until here
1105 # in case they disabled the warning in the SConscript files.
1106 if python_version_deprecated():
1107 msg
= "Support for pre-%s Python version (%s) is deprecated.\n" + \
1108 " If this will cause hardship, contact scons-dev@scons.org"
1109 deprecated_version_string
= ".".join(map(str, deprecated_python_version
))
1110 SCons
.Warnings
.warn(SCons
.Warnings
.PythonVersionWarning
,
1111 msg
% (deprecated_version_string
, python_version_string()))
1113 if not options
.help:
1114 # [ ] Clarify why we need to create Builder here at all, and
1115 # why it is created in DefaultEnvironment
1116 # https://bitbucket.org/scons/scons/commits/d27a548aeee8ad5e67ea75c2d19a7d305f784e30
1117 if SCons
.SConf
.NeedConfigHBuilder():
1118 SCons
.SConf
.CreateConfigHBuilder(SCons
.Defaults
.DefaultEnvironment())
1120 # Now re-parse the command-line options (any to the left of a '--'
1121 # argument, that is) with any user-defined command-line options that
1122 # the SConscript files may have added to the parser object. This will
1123 # emit the appropriate error message and exit if any unknown option
1124 # was specified on the command line.
1126 parser
.preserve_unknown_options
= False
1127 parser
.parse_args(parser
.largs
, options
)
1130 help_text
= SCons
.Script
.help_text
1131 if help_text
is None:
1132 # They specified -h, but there was no Help() inside the
1133 # SConscript files. Give them the options usage.
1134 raise SConsPrintHelpException
1137 print("Use scons -H for help about SCons built-in command-line options.")
1141 # Change directory to the top-level SConstruct directory, then tell
1142 # the Node.FS subsystem that we're all done reading the SConscript
1143 # files and calling Repository() and VariantDir() and changing
1144 # directories and the like, so it can go ahead and start memoizing
1145 # the string values of file system nodes.
1149 SCons
.Node
.FS
.save_strings(1)
1151 # Now that we've read the SConscripts we can set the options
1152 # that are SConscript settable:
1153 SCons
.Node
.implicit_cache
= options
.implicit_cache
1154 SCons
.Node
.FS
.set_duplicate(options
.duplicate
)
1155 fs
.set_max_drift(options
.max_drift
)
1157 SCons
.Taskmaster
.Job
.explicit_stack_size
= options
.stack_size
1159 # Hash format and chunksize are set late to support SetOption being called
1160 # in a SConscript or SConstruct file.
1161 SCons
.Util
.set_hash_format(options
.hash_format
)
1162 if options
.md5_chunksize
:
1163 SCons
.Node
.FS
.File
.hash_chunksize
= options
.md5_chunksize
* 1024
1165 platform
= SCons
.Platform
.platform_module()
1167 if options
.interactive
:
1168 SCons
.Script
.Interactive
.interact(fs
, OptionsParser
, options
,
1169 targets
, target_top
)
1174 nodes
= _build_targets(fs
, options
, targets
, target_top
)
1177 print('Found nothing to build')
1180 def _build_targets(fs
, options
, targets
, target_top
):
1182 global this_build_status
1183 this_build_status
= 0
1185 progress_display
.set_mode(not (options
.no_progress
or options
.silent
))
1186 display
.set_mode(not options
.silent
)
1187 SCons
.Action
.print_actions
= not options
.silent
1188 SCons
.Action
.execute_actions
= not options
.no_exec
1189 SCons
.Node
.do_store_info
= not options
.no_exec
1190 SCons
.SConf
.dryrun
= options
.no_exec
1192 if options
.diskcheck
:
1193 SCons
.Node
.FS
.set_diskcheck(options
.diskcheck
)
1195 SCons
.CacheDir
.cache_enabled
= not options
.cache_disable
1196 SCons
.CacheDir
.cache_readonly
= options
.cache_readonly
1197 SCons
.CacheDir
.cache_debug
= options
.cache_debug
1198 SCons
.CacheDir
.cache_force
= options
.cache_force
1199 SCons
.CacheDir
.cache_show
= options
.cache_show
1202 CleanTask
.execute
= CleanTask
.show
1204 CleanTask
.execute
= CleanTask
.remove
1207 if targets
or SCons
.Script
.BUILD_TARGETS
!= SCons
.Script
._build
_plus
_default
:
1208 # They specified targets on the command line or modified
1209 # BUILD_TARGETS in the SConscript file(s), so if they used -u,
1210 # -U or -D, we have to look up targets relative to the top,
1211 # but we build whatever they specified.
1213 lookup_top
= fs
.Dir(target_top
)
1216 targets
= SCons
.Script
.BUILD_TARGETS
1218 # There are no targets specified on the command line,
1219 # so if they used -u, -U or -D, we may have to restrict
1220 # what actually gets built.
1223 if options
.climb_up
== 1:
1224 # -u, local directory and below
1225 target_top
= fs
.Dir(target_top
)
1226 lookup_top
= target_top
1227 elif options
.climb_up
== 2:
1228 # -D, all Default() targets
1231 elif options
.climb_up
== 3:
1232 # -U, local SConscript Default() targets
1233 target_top
= fs
.Dir(target_top
)
1234 def check_dir(x
, target_top
=target_top
):
1235 if hasattr(x
, 'cwd') and x
.cwd
is not None:
1236 cwd
= x
.cwd
.srcnode()
1237 return cwd
== target_top
1239 # x doesn't have a cwd, so it's either not a target,
1240 # or not a file, so go ahead and keep it as a default
1241 # target and let the engine sort it out:
1243 d
= [tgt
for tgt
in SCons
.Script
.DEFAULT_TARGETS
if check_dir(tgt
)]
1244 SCons
.Script
.DEFAULT_TARGETS
[:] = d
1248 targets
= SCons
.Script
._Get
_Default
_Targets
(d
, fs
)
1251 sys
.stderr
.write("scons: *** No targets specified and no Default() targets found. Stop.\n")
1254 def Entry(x
, ltop
=lookup_top
, ttop
=target_top
, fs
=fs
):
1255 if isinstance(x
, SCons
.Node
.Node
):
1259 # Why would ltop be None? Unfortunately this happens.
1260 if ltop
is None: ltop
= ''
1261 # Curdir becomes important when SCons is called with -u, -C,
1262 # or similar option that changes directory, and so the paths
1263 # of targets given on the command line need to be adjusted.
1264 curdir
= os
.path
.join(os
.getcwd(), str(ltop
))
1265 for lookup
in SCons
.Node
.arg2nodes_lookups
:
1266 node
= lookup(x
, curdir
=curdir
)
1267 if node
is not None:
1270 node
= fs
.Entry(x
, directory
=ltop
, create
=1)
1271 if ttop
and not node
.is_under(ttop
):
1272 if isinstance(node
, SCons
.Node
.FS
.Dir
) and ttop
.is_under(node
):
1278 nodes
= [_f
for _f
in map(Entry
, targets
) if _f
]
1280 task_class
= BuildTask
# default action is to build targets
1281 opening_message
= "Building targets ..."
1282 closing_message
= "done building targets."
1283 if options
.keep_going
:
1284 failure_message
= "done building targets (errors occurred during build)."
1286 failure_message
= "building terminated because of errors."
1287 if options
.question
:
1288 task_class
= QuestionTask
1291 task_class
= CleanTask
1292 opening_message
= "Cleaning targets ..."
1293 closing_message
= "done cleaning targets."
1294 if options
.keep_going
:
1295 failure_message
= "done cleaning targets (errors occurred during clean)."
1297 failure_message
= "cleaning terminated because of errors."
1298 except AttributeError:
1301 task_class
.progress
= ProgressObject
1304 def order(dependencies
):
1305 """Randomize the dependencies."""
1307 random
.shuffle(dependencies
)
1310 def order(dependencies
):
1311 """Leave the order of dependencies alone."""
1314 taskmaster
= SCons
.Taskmaster
.Taskmaster(nodes
, task_class
, order
, options
.taskmastertrace_file
)
1316 # Let the BuildTask objects get at the options to respond to the
1317 # various print_* settings, tree_printer list, etc.
1318 BuildTask
.options
= options
1320 is_pypy
= platform
.python_implementation() == 'PyPy'
1321 # As of 3.7, python removed support for threadless platforms.
1322 # See https://www.python.org/dev/peps/pep-0011/
1323 is_37_or_later
= sys
.version_info
>= (3, 7)
1324 # python_has_threads = sysconfig.get_config_var('WITH_THREAD') or is_pypy or is_37_or_later
1326 # As of python 3.4 threading has a dummy_threading module for use when there is no threading
1327 # it's get_ident() will allways return -1, while real threading modules get_ident() will
1328 # always return a positive integer
1329 python_has_threads
= threading
.get_ident() != -1
1330 # to check if python configured with threads.
1332 num_jobs
= options
.num_jobs
1333 jobs
= SCons
.Taskmaster
.Job
.Jobs(num_jobs
, taskmaster
)
1336 if jobs
.num_jobs
== 1 or not python_has_threads
:
1337 msg
= "parallel builds are unsupported by this version of Python;\n" + \
1338 "\tignoring -j or num_jobs option.\n"
1340 SCons
.Warnings
.warn(SCons
.Warnings
.NoParallelSupportWarning
, msg
)
1342 memory_stats
.append('before building targets:')
1343 count_stats
.append(('pre-', 'build'))
1348 closing_message
=closing_message
,
1349 failure_message
=failure_message
1351 if jobs
.were_interrupted():
1352 if not options
.no_progress
and not options
.silent
:
1353 sys
.stderr
.write("scons: Build interrupted.\n")
1355 global this_build_status
1357 this_build_status
= 2
1359 if this_build_status
:
1360 progress_display("scons: " + failure_message
)
1362 progress_display("scons: " + closing_message
)
1363 if not options
.no_exec
:
1364 if jobs
.were_interrupted():
1365 progress_display("scons: writing .sconsign file.")
1366 SCons
.SConsign
.write()
1368 progress_display("scons: " + opening_message
)
1369 jobs
.run(postfunc
= jobs_postfunc
)
1371 memory_stats
.append('after building targets:')
1372 count_stats
.append(('post-', 'build'))
1376 def _exec_main(parser
, values
) -> None:
1377 sconsflags
= os
.environ
.get('SCONSFLAGS', '')
1378 all_args
= sconsflags
.split() + sys
.argv
[1:]
1380 options
, args
= parser
.parse_args(all_args
, values
)
1382 if isinstance(options
.debug
, list) and "pdb" in options
.debug
:
1385 class SConsPdb(pdb
.Pdb
):
1386 """Specialization of Pdb to help find SConscript files."""
1388 def lookupmodule(self
, filename
: str) -> Optional
[str]:
1389 """Helper function for break/clear parsing -- SCons version.
1391 Translates (possibly incomplete) file or module name
1392 into an absolute file name. The "possibly incomplete"
1393 means adding a ``.py`` suffix if not present, which breaks
1394 picking breakpoints in sconscript files, which often don't
1395 have a suffix. This version fixes for some known names of
1396 sconscript files that don't have the suffix.
1398 .. versionadded:: 4.6.0
1400 if os
.path
.isabs(filename
) and os
.path
.exists(filename
):
1402 f
= os
.path
.join(sys
.path
[0], filename
)
1403 if os
.path
.exists(f
) and self
.canonic(f
) == self
.mainpyfile
:
1405 root
, ext
= os
.path
.splitext(filename
)
1406 base
= os
.path
.split(filename
)[-1]
1407 if ext
== '' and base
not in KNOWN_SCONSCRIPTS
: # SCons mod
1408 filename
= filename
+ '.py'
1409 if os
.path
.isabs(filename
):
1411 for dirname
in sys
.path
:
1412 while os
.path
.islink(dirname
):
1413 dirname
= os
.readlink(dirname
)
1414 fullname
= os
.path
.join(dirname
, filename
)
1415 if os
.path
.exists(fullname
):
1419 SConsPdb().runcall(_main
, parser
)
1421 elif options
.profile_file
:
1422 from cProfile
import Profile
1426 prof
.runcall(_main
, parser
)
1428 prof
.dump_stats(options
.profile_file
)
1435 global OptionsParser
1437 global first_command_start
1440 # Check up front for a Python version we do not support. We
1441 # delay the check for deprecated Python versions until later,
1442 # after the SConscript files have been read, in case they
1443 # disable that warning.
1444 if python_version_unsupported():
1445 msg
= "scons: *** SCons version %s does not run under Python version %s.\n"
1446 sys
.stderr
.write(msg
% (SConsVersion
, python_version_string()))
1447 sys
.stderr
.write("scons: *** Minimum Python version is %d.%d.%d\n" %minimum_python_version
)
1453 msg
= "scons: *** SCons version %s requires a Python interpreter with support for the `threading` package"
1454 sys
.stderr
.write(msg
% SConsVersion
)
1457 parts
= ["SCons by Steven Knight et al.:\n"]
1460 parts
.append(version_string("SCons", SCons
))
1461 except (ImportError, AttributeError):
1462 # On Windows there is no scons.py, so there is no
1463 # __main__.__version__, hence there is no script version.
1465 parts
.append(path_string("SCons", SCons
))
1466 parts
.append(SCons
.__copyright
__)
1467 version
= ''.join(parts
)
1469 from . import SConsOptions
1470 parser
= SConsOptions
.Parser(version
)
1471 values
= SConsOptions
.SConsValues(parser
.get_default_values())
1473 OptionsParser
= parser
1477 _exec_main(parser
, values
)
1480 except SystemExit as s
:
1482 exit_status
= s
.code
1483 except KeyboardInterrupt:
1484 print("scons: Build interrupted.")
1486 except SyntaxError as e
:
1487 _scons_syntax_error(e
)
1488 except SCons
.Errors
.InternalError
:
1489 _scons_internal_error()
1490 except SCons
.Errors
.UserError
as e
:
1491 _scons_user_error(e
)
1492 except SConsPrintHelpException
:
1495 except SCons
.Errors
.BuildError
as e
:
1497 exit_status
= e
.exitstatus
1499 # An exception here is likely a builtin Python exception Python
1500 # code in an SConscript file. Show them precisely what the
1501 # problem was and where it happened.
1502 SCons
.Script
._SConscript
.SConscript_exception()
1505 memory_stats
.print_stats()
1506 count_stats
.print_stats()
1509 SCons
.Debug
.listLoggedInstances('*')
1510 #SCons.Debug.dumpLoggedInstances('*')
1513 SCons
.Memoize
.Dump("Memoizer (memory cache) hits and misses:")
1515 # Dump any development debug info that may have been enabled.
1516 # These are purely for internal debugging during development, so
1517 # there's no need to control them with --debug= options; they're
1518 # controlled by changing the source code.
1519 SCons
.Debug
.dump_caller_counts()
1520 SCons
.Taskmaster
.dump_stats()
1523 total_time
= time
.time() - SCons
.Script
.start_time
1525 ct
= cumulative_command_time
1527 if last_command_end
is None or first_command_start
is None:
1530 ct
= last_command_end
- first_command_start
1531 scons_time
= total_time
- sconscript_time
- ct
1532 print("Total build time: %f seconds"%total_time
)
1533 print("Total SConscript file execution time: %f seconds"%sconscript
_time
)
1534 print("Total SCons execution time: %f seconds"%scons
_time
)
1535 print("Total command execution time: %f seconds"%ct
)
1536 time_stats
.total_times(total_time
, sconscript_time
, scons_time
, ct
)
1540 write_scons_stats_file()
1542 sys
.exit(exit_status
)
1546 # indent-tabs-mode:nil
1548 # vim: set expandtab tabstop=4 shiftwidth=4: