From 176711bd1fde814e9c441ae44132eb600063ec7a Mon Sep 17 00:00:00 2001 From: William Deegan Date: Thu, 13 Apr 2023 14:51:01 -0700 Subject: updated copyright and added csv results output to bench.py --- bench/bench.py | 113 +++++++++++++++++++++++++++++++++++++++------------ bench/lvars-gvars.py | 33 ++++++++++++--- 2 files changed, 116 insertions(+), 30 deletions(-) diff --git a/bench/bench.py b/bench/bench.py index aad0fb4..5adac23 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -1,34 +1,59 @@ #!/usr/bin/env python +# MIT License # -# __COPYRIGHT__ +# Copyright The SCons Foundation # -# A script for timing snippets of Python code. +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: # -# By default, this script will execute a single Python file specified on -# the command line and time any functions in a list named "FunctionList" -# set by the Python file under test, or (by default) time any functions -# in the file whose names begin with "Func". +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. # -# All functions are assumed to get passed the same arguments, and the -# inputs are specified in a list named "Data," each element of which -# is a list consisting of a tag name, a list of positional arguments, -# and a dictionary of keyword arguments. -# -# Each function is expected to test a single, comparable snippet of -# of Python code. IMPORTANT: We want to test the timing of the code -# itself, not Python function call overhead, so every function should -# put its code under test within the following block: -# -# for i in IterationList: -# -# This will allow (as much as possible) us to time just the code itself, -# not Python function call overhead. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +A script for timing snippets of Python code. + +By default, this script will execute a single Python file specified on +the command line and time any functions in a list named "FunctionList" +set by the Python file under test, or (by default) time any functions +in the file whose names begin with "Func". + +All functions are assumed to get passed the same arguments, and the +inputs are specified in a list named "Data," each element of which +is a list consisting of a tag name, a list of positional arguments, +and a dictionary of keyword arguments. + +Each function is expected to test a single, comparable snippet of +of Python code. IMPORTANT: We want to test the timing of the code +itself, not Python function call overhead, so every function should +put its code under test within the following block: + + for i in IterationList: + +This will allow (as much as possible) us to time just the code itself, +not Python function call overhead. +""" from __future__ import division, print_function import getopt +import os.path import sys import time import types +from pprint import pprint + Usage = """\ Usage: bench.py OPTIONS file.py @@ -105,6 +130,11 @@ if len(args) != 1: sys.stderr.write(Usage) sys.exit(1) + +(bench_name,_) = os.path.splitext(args[0]) +print(f"Bench: {bench_name} : {args[0]}") +results_filename = f"{bench_name}_results.csv" + with open(args[0], 'r') as f: exec(f.read()) @@ -118,6 +148,7 @@ except NameError: IterationList = [None] * Iterations + def timer(func, *args, **kw): results = [] for i in range(Runs): @@ -127,20 +158,52 @@ def timer(func, *args, **kw): results.append((finish - start) / Iterations) return results -def display(label, results): + +results_dict = {} + + +def display(func, result_label, results): total = 0.0 for r in results: total += r - print(" %8.3f" % ((total * 1e6) / len(results)), ':', label) + + test_result = (total * 1e6) / len(results) + print(" %8.3f" % (test_result), ':', result_label) + if results_dict.get(func.__doc__, False): + results_dict[func.__doc__][result_label] = test_result + else: + results_dict[func.__doc__] = { result_label: test_result} + for func in FunctionList: - if func.__doc__: d = ' (' + func.__doc__ + ')' - else: d = '' + if func.__doc__: + d = ' (' + func.__doc__ + ')' + else: + d = '' print(func.__name__ + d + ':') for label, args, kw in Data: r = timer(func, *args, **kw) - display(label, r) + # print(f"{label} {r}") + display(func, label, r) + +py_ver_string = "%d.%d"%(sys.version_info.major, sys.version_info.minor) + + +# pprint(results_dict) +# breakpoint() +tests = [label for label, args, kw in Data] +columns = ['Python Version', 'Implementation', 'Test'] + tests +with open(results_filename, 'a') as r: + print("Python Version,%s" % ".".join(columns), file=r) + # print("Python Version,%s" % ".".join(columns)) + + for implementation in results_dict.keys(): + for test in tests: + print(f'{py_ver_string},"{implementation}","{test}",%8.3f' % results_dict[implementation][test], file=r) + # print(f'{py_ver_string},"{implementation}","{test}",%8.3f' % results_dict[implementation][test]) + + # Local Variables: # tab-width:4 diff --git a/bench/lvars-gvars.py b/bench/lvars-gvars.py index 1511203..0a81337 100644 --- a/bench/lvars-gvars.py +++ b/bench/lvars-gvars.py @@ -1,9 +1,32 @@ -# __COPYRIGHT__ +# MIT License # -# Functions and data for timing different idioms for fetching a keyword -# value from a pair of dictionaries for localand global values. This was -# used to select how to most efficiently expand single $KEYWORD strings -# in src/engine/SCons/Subst.py. +# Copyright The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +Functions and data for timing different idioms for fetching a keyword +value from a pair of dictionaries for localand global values. This was +used to select how to most efficiently expand single $KEYWORD strings +in src/engine/SCons/Subst.py. +""" def Func1(var, gvars, lvars): """lvars try:-except:, gvars try:-except:""" -- cgit v0.12 From b3744e8862927899e3d0ebcb41297f9b4c142c63 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sat, 22 Apr 2023 16:49:38 -0700 Subject: added --debug=json, which will dump object counts and memory into scons_stats.json in invocation directory --- SCons/Script/Main.py | 106 +++++++++-------------------- SCons/Script/SConsOptions.py | 2 +- SCons/Util/stats.py | 156 +++++++++++++++++++++++++++++++++++++++++++ SCons/__init__.py | 10 +-- 4 files changed, 192 insertions(+), 82 deletions(-) create mode 100644 SCons/Util/stats.py diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index d4228fa..ec34c12 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -37,7 +37,6 @@ deprecated_python_version = (3, 6, 0) import SCons.compat -import atexit import importlib.util import os import re @@ -63,6 +62,7 @@ import SCons.Taskmaster import SCons.Util import SCons.Warnings import SCons.Script.Interactive +from SCons.Util.stats import COUNT_STATS, MEMORY_STATS, ENABLE_JSON, WriteJsonFile from SCons import __version__ as SConsVersion @@ -82,10 +82,18 @@ num_jobs = None delayed_warnings = [] +display = SCons.Util.display +progress_display = SCons.Util.DisplayEngine() +ProgressObject = SCons.Util.Null() +_BuildFailures = [] + + def revert_io(): - # This call is added to revert stderr and stdout to the original - # ones just in case some build rule or something else in the system - # has redirected them elsewhere. + """ + This call is added to revert stderr and stdout to the original + ones just in case some build rule or something else in the system + has redirected them elsewhere. + """ sys.stderr = sys.__stderr__ sys.stdout = sys.__stdout__ @@ -94,10 +102,6 @@ class SConsPrintHelpException(Exception): pass -display = SCons.Util.display -progress_display = SCons.Util.DisplayEngine() - - class Progressor: prev = '' count = 0 @@ -150,7 +154,6 @@ class Progressor: self.erase_previous() self.func(node) -ProgressObject = SCons.Util.Null() def Progress(*args, **kw): global ProgressObject @@ -159,9 +162,6 @@ def Progress(*args, **kw): # Task control. # -_BuildFailures = [] - - def GetBuildFailures(): return _BuildFailures @@ -515,59 +515,8 @@ def ValidateOptions(throw_exception=False) -> None: def PrintHelp(file=None): OptionsParser.print_help(file=file) -class Stats: - def __init__(self): - self.stats = [] - self.labels = [] - self.append = self.do_nothing - self.print_stats = self.do_nothing - def enable(self, outfp): - self.outfp = outfp - self.append = self.do_append - self.print_stats = self.do_print - def do_nothing(self, *args, **kw): - pass -class CountStats(Stats): - def do_append(self, label): - self.labels.append(label) - self.stats.append(SCons.Debug.fetchLoggedInstances()) - def do_print(self): - stats_table = {} - for s in self.stats: - for n in [t[0] for t in s]: - stats_table[n] = [0, 0, 0, 0] - i = 0 - for s in self.stats: - for n, c in s: - stats_table[n][i] = c - i = i + 1 - self.outfp.write("Object counts:\n") - pre = [" "] - post = [" %s\n"] - l = len(self.stats) - fmt1 = ''.join(pre + [' %7s']*l + post) - fmt2 = ''.join(pre + [' %7d']*l + post) - labels = self.labels[:l] - labels.append(("", "Class")) - self.outfp.write(fmt1 % tuple([x[0] for x in labels])) - self.outfp.write(fmt1 % tuple([x[1] for x in labels])) - for k in sorted(stats_table.keys()): - r = stats_table[k][:l] + [k] - self.outfp.write(fmt2 % tuple(r)) - -count_stats = CountStats() - -class MemStats(Stats): - def do_append(self, label): - self.labels.append(label) - self.stats.append(SCons.Debug.memory()) - def do_print(self): - fmt = 'Memory %-32s %12d\n' - for label, stats in zip(self.labels, self.stats): - self.outfp.write(fmt % (label, stats)) - -memory_stats = MemStats() + # utility functions @@ -671,7 +620,7 @@ def _set_debug_values(options): enable_count = False if __debug__: enable_count = True if enable_count: - count_stats.enable(sys.stdout) + COUNT_STATS.enable(sys.stdout) SCons.Debug.track_instances = True else: msg = "--debug=count is not supported when running SCons\n" + \ @@ -685,7 +634,7 @@ def _set_debug_values(options): options.debug_includes = ("includes" in debug_values) print_memoizer = ("memoizer" in debug_values) if "memory" in debug_values: - memory_stats.enable(sys.stdout) + MEMORY_STATS.enable(sys.stdout) print_objects = ("objects" in debug_values) if print_objects: SCons.Debug.track_instances = True @@ -706,6 +655,8 @@ def _set_debug_values(options): SCons.Taskmaster.print_prepare = True if "duplicate" in debug_values: SCons.Node.print_duplicate = True + if "json" in debug_values: + SCons.Util.stats.ENABLE_JSON = True def _create_path(plist): path = '.' @@ -1039,8 +990,8 @@ def _main(parser): if not hasattr(sys.stderr, 'isatty') or not sys.stderr.isatty(): sys.stderr = SCons.Util.Unbuffered(sys.stderr) - memory_stats.append('before reading SConscript files:') - count_stats.append(('pre-', 'read')) + MEMORY_STATS.append('before reading SConscript files:') + COUNT_STATS.append(('pre-', 'read')) # And here's where we (finally) read the SConscript files. @@ -1066,8 +1017,8 @@ def _main(parser): progress_display("scons: done reading SConscript files.") - memory_stats.append('after reading SConscript files:') - count_stats.append(('post-', 'read')) + MEMORY_STATS.append('after reading SConscript files:') + COUNT_STATS.append(('post-', 'read')) # Re-{enable,disable} warnings in case they disabled some in # the SConscript file. @@ -1319,8 +1270,8 @@ def _build_targets(fs, options, targets, target_top): if msg: SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg) - memory_stats.append('before building targets:') - count_stats.append(('pre-', 'build')) + MEMORY_STATS.append('before building targets:') + COUNT_STATS.append(('pre-', 'build')) def jobs_postfunc( jobs=jobs, @@ -1348,8 +1299,8 @@ def _build_targets(fs, options, targets, target_top): progress_display("scons: " + opening_message) jobs.run(postfunc = jobs_postfunc) - memory_stats.append('after building targets:') - count_stats.append(('post-', 'build')) + MEMORY_STATS.append('after building targets:') + COUNT_STATS.append(('post-', 'build')) return nodes @@ -1437,8 +1388,8 @@ def main(): SCons.Script._SConscript.SConscript_exception() sys.exit(2) - memory_stats.print_stats() - count_stats.print_stats() + MEMORY_STATS.print_stats() + COUNT_STATS.print_stats() if print_objects: SCons.Debug.listLoggedInstances('*') @@ -1469,6 +1420,9 @@ def main(): print("Total SCons execution time: %f seconds"%scons_time) print("Total command execution time: %f seconds"%ct) + if SCons.Util.stats.ENABLE_JSON: + WriteJsonFile() + sys.exit(exit_status) # Local Variables: diff --git a/SCons/Script/SConsOptions.py b/SCons/Script/SConsOptions.py index 8391d62..25c6046 100644 --- a/SCons/Script/SConsOptions.py +++ b/SCons/Script/SConsOptions.py @@ -701,7 +701,7 @@ def Parser(version): debug_options = ["count", "duplicate", "explain", "findlibs", "includes", "memoizer", "memory", "objects", "pdb", "prepare", "presub", "stacktrace", - "time", "action-timestamps"] + "time", "action-timestamps", "json"] def opt_debug(option, opt, value__, parser, debug_options=debug_options, diff --git a/SCons/Util/stats.py b/SCons/Util/stats.py new file mode 100644 index 0000000..ccad165 --- /dev/null +++ b/SCons/Util/stats.py @@ -0,0 +1,156 @@ +# MIT License +# +# Copyright The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +This package provides a way to gather various statistics during a SCons run and dump that info in several formats + +Additionally, it probably makes sense to do stderr/stdout output of those statistics here as well + +There are basically two types of stats. +1. Timer (start/stop/time) for specific event. These events can be hierarchical. So you can record the children events of some parent. + Think program compile could contain the total Program builder time, which could include linking, and stripping the executable +2. Counter. Counting the number of events and/or objects created. This would likely only be reported at the end of a given SCons run, + though it might be useful to query during a run. + +""" +import json + +import SCons.Debug + +ALL_STATS = {} +ENABLE_JSON = False + + +def AddStatType(name, stat_object): + """ + Add a statistic type to the global collection + """ + if name in ALL_STATS: + raise UserWarning(f'Stat type {name} already exists') + else: + ALL_STATS[name] = stat_object + + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: +class Stats: + def __init__(self): + self.stats = [] + self.labels = [] + self.append = self.do_nothing + self.print_stats = self.do_nothing + self.enabled = False + + def do_append(self, label): + raise NotImplementedError + + def do_print(self): + raise NotImplementedError + + def enable(self, outfp): + self.outfp = outfp + self.append = self.do_append + self.print_stats = self.do_print + self.enabled = True + + def do_nothing(self, *args, **kw): + pass + + +class CountStats(Stats): + + def __init__(self): + super().__init__() + self.stats_table = {} + + def do_append(self, label): + self.labels.append(label) + self.stats.append(SCons.Debug.fetchLoggedInstances()) + + def do_print(self): + self.stats_table = {} + for s in self.stats: + for n in [t[0] for t in s]: + self.stats_table[n] = [0, 0, 0, 0] + i = 0 + for s in self.stats: + for n, c in s: + self.stats_table[n][i] = c + i = i + 1 + self.outfp.write("Object counts:\n") + pre = [" "] + post = [" %s\n"] + l = len(self.stats) + fmt1 = ''.join(pre + [' %7s'] * l + post) + fmt2 = ''.join(pre + [' %7d'] * l + post) + labels = self.labels[:l] + labels.append(("", "Class")) + self.outfp.write(fmt1 % tuple([x[0] for x in labels])) + self.outfp.write(fmt1 % tuple([x[1] for x in labels])) + for k in sorted(self.stats_table.keys()): + r = self.stats_table[k][:l] + [k] + self.outfp.write(fmt2 % tuple(r)) + + +class MemStats(Stats): + def do_append(self, label): + self.labels.append(label) + self.stats.append(SCons.Debug.memory()) + + def do_print(self): + fmt = 'Memory %-32s %12d\n' + for label, stats in zip(self.labels, self.stats): + self.outfp.write(fmt % (label, stats)) + + +COUNT_STATS = CountStats() +MEMORY_STATS = MemStats() + + +def WriteJsonFile(): + """ + + """ + print("DUMPING JSON FILE") + json_structure = {} + if COUNT_STATS.enabled: + json_structure['Object counts'] = {} + + oc = json_structure['Object counts'] + for c in COUNT_STATS.stats_table: + oc[c] = {} + for l, v in zip(COUNT_STATS.labels, COUNT_STATS.stats_table[c]): + oc[c][''.join(l)] = v + + if MEMORY_STATS.enabled: + json_structure['Memory'] = {} + + m = json_structure['Memory'] + for label, stats in zip(MEMORY_STATS.labels, MEMORY_STATS.stats): + m[label] =stats + + with open("scons_stats.json", 'w') as sf: + sf.write(json.dumps(json_structure)) diff --git a/SCons/__init__.py b/SCons/__init__.py index f53583b..9804f4f 100644 --- a/SCons/__init__.py +++ b/SCons/__init__.py @@ -1,9 +1,9 @@ -__version__="4.5.1" +__version__="4.5.2" __copyright__="Copyright (c) 2001 - 2023 The SCons Foundation" __developer__="bdbaddog" -__date__="Mon, 06 Mar 2023 23:32:38 -0400" -__buildsys__="M1DOG2021" -__revision__="49578b34582d9e92dac7d713a8e58599ae35aa63" -__build__="49578b34582d9e92dac7d713a8e58599ae35aa63" +__date__="Sat, 22 Apr 2023 16:48:09 -0700" +__buildsys__="M1Dog2021" +__revision__="176711bd1fde814e9c441ae44132eb600063ec7a" +__build__="176711bd1fde814e9c441ae44132eb600063ec7a" # make sure compatibility is always in place import SCons.compat # noqa \ No newline at end of file -- cgit v0.12 From c7920c9ebf0031dd2c7664ea5cc128af51e64167 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 4 Jun 2023 15:39:36 -0700 Subject: Initial support for writing --debug=time, count into a JSON file --- SCons/Debug.py | 1 + SCons/Script/Main.py | 6 +++++- SCons/Util/stats.py | 38 ++++++++++++++++++++++++++++++++------ SCons/__init__.py | 6 +++--- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/SCons/Debug.py b/SCons/Debug.py index fa07743..ea359f6 100644 --- a/SCons/Debug.py +++ b/SCons/Debug.py @@ -196,6 +196,7 @@ TimeStampDefault = False StartTime = time.perf_counter() PreviousTime = StartTime + def Trace(msg, tracefile=None, mode='w', tstamp=False): """Write a trace message. diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index ec34c12..572a2e7 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -62,7 +62,7 @@ import SCons.Taskmaster import SCons.Util import SCons.Warnings import SCons.Script.Interactive -from SCons.Util.stats import COUNT_STATS, MEMORY_STATS, ENABLE_JSON, WriteJsonFile +from SCons.Util.stats import COUNT_STATS, MEMORY_STATS, TIME_STATS, ENABLE_JSON, WriteJsonFile from SCons import __version__ as SConsVersion @@ -208,6 +208,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): "Command execution end timestamp: %s: %f\n" % (str(self.node), finish_time) ) + TIME_STATS.add_command(str(self.node), (finish_time - start_time)) sys.stdout.write( "Command execution time: %s: %f seconds\n" % (str(self.node), (finish_time - start_time)) @@ -646,6 +647,7 @@ def _set_debug_values(options): options.tree_printers.append(TreePrinter(status=True)) if "time" in debug_values: print_time = True + TIME_STATS.enable(sys.stdout) if "action-timestamps" in debug_values: print_time = True print_action_timestamps = True @@ -1419,6 +1421,8 @@ def main(): print("Total SConscript file execution time: %f seconds"%sconscript_time) print("Total SCons execution time: %f seconds"%scons_time) print("Total command execution time: %f seconds"%ct) + TIME_STATS.total_times(total_time, sconscript_time, scons_time, ct) + if SCons.Util.stats.ENABLE_JSON: WriteJsonFile() diff --git a/SCons/Util/stats.py b/SCons/Util/stats.py index ccad165..177f363 100644 --- a/SCons/Util/stats.py +++ b/SCons/Util/stats.py @@ -51,11 +51,6 @@ def AddStatType(name, stat_object): ALL_STATS[name] = stat_object -# Local Variables: -# tab-width:4 -# indent-tabs-mode:nil -# End: -# vim: set expandtab tabstop=4 shiftwidth=4: class Stats: def __init__(self): self.stats = [] @@ -126,8 +121,29 @@ class MemStats(Stats): self.outfp.write(fmt % (label, stats)) +class TimeStats(Stats): + def __init__(self): + super().__init__() + self.totals = {} + self.commands = {} # we get order from insertion order, and can address individual via dict + + def total_times(self, build_time, sconscript_time, scons_exec_time, command_exec_time): + self.totals = { + 'build_time': build_time, + 'sconscript_time': sconscript_time, + 'scons_exec_time': scons_exec_time, + 'command_exec_time': command_exec_time + } + + def add_command(self, command, command_time): + if command in self.commands: + print("Duplicate command %s" % command) + self.commands[command] = command_time + + COUNT_STATS = CountStats() MEMORY_STATS = MemStats() +TIME_STATS = TimeStats() def WriteJsonFile(): @@ -150,7 +166,17 @@ def WriteJsonFile(): m = json_structure['Memory'] for label, stats in zip(MEMORY_STATS.labels, MEMORY_STATS.stats): - m[label] =stats + m[label] = stats + + if TIME_STATS.enabled: + json_structure['Time'] = {'Commands': TIME_STATS.commands, + 'Totals': TIME_STATS.totals} with open("scons_stats.json", 'w') as sf: sf.write(json.dumps(json_structure)) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/SCons/__init__.py b/SCons/__init__.py index 9804f4f..f0f241d 100644 --- a/SCons/__init__.py +++ b/SCons/__init__.py @@ -1,9 +1,9 @@ __version__="4.5.2" __copyright__="Copyright (c) 2001 - 2023 The SCons Foundation" __developer__="bdbaddog" -__date__="Sat, 22 Apr 2023 16:48:09 -0700" +__date__="Sun, 04 Jun 2023 15:36:48 -0700" __buildsys__="M1Dog2021" -__revision__="176711bd1fde814e9c441ae44132eb600063ec7a" -__build__="176711bd1fde814e9c441ae44132eb600063ec7a" +__revision__="b3744e8862927899e3d0ebcb41297f9b4c142c63" +__build__="b3744e8862927899e3d0ebcb41297f9b4c142c63" # make sure compatibility is always in place import SCons.compat # noqa \ No newline at end of file -- cgit v0.12 From 071b019942366375884eb0301cc37289e6101849 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 4 Jun 2023 16:36:45 -0700 Subject: Added build info to json, added --debug=json to docs --- SCons/Script/Main.py | 2 +- SCons/Util/stats.py | 39 +++++++++++++++++++++++++++++++++------ doc/man/scons.xml | 13 +++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index 572a2e7..46b6e6a 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -208,7 +208,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): "Command execution end timestamp: %s: %f\n" % (str(self.node), finish_time) ) - TIME_STATS.add_command(str(self.node), (finish_time - start_time)) + TIME_STATS.add_command(str(self.node), start_time, finish_time) sys.stdout.write( "Command execution time: %s: %f seconds\n" % (str(self.node), (finish_time - start_time)) diff --git a/SCons/Util/stats.py b/SCons/Util/stats.py index 177f363..5a05f1c 100644 --- a/SCons/Util/stats.py +++ b/SCons/Util/stats.py @@ -33,13 +33,16 @@ There are basically two types of stats. though it might be useful to query during a run. """ +import os import json +import sys +from datetime import datetime import SCons.Debug ALL_STATS = {} ENABLE_JSON = False - +JSON_OUTPUT_FILE = 'scons_stats.json' def AddStatType(name, stat_object): """ @@ -135,10 +138,12 @@ class TimeStats(Stats): 'command_exec_time': command_exec_time } - def add_command(self, command, command_time): + def add_command(self, command, start_time, finish_time): if command in self.commands: print("Duplicate command %s" % command) - self.commands[command] = command_time + self.commands[command] = {'start': start_time, + 'end' : finish_time, + 'duration': finish_time - start_time} COUNT_STATS = CountStats() @@ -148,8 +153,12 @@ TIME_STATS = TimeStats() def WriteJsonFile(): """ - + Actually write the JSON file with debug information. + Depending which of : count, time, action-timestamps,memory their information will be written. """ + + from SCons.Script import BUILD_TARGETS, COMMAND_LINE_TARGETS, ARGUMENTS, ARGLIST + print("DUMPING JSON FILE") json_structure = {} if COUNT_STATS.enabled: @@ -172,8 +181,26 @@ def WriteJsonFile(): json_structure['Time'] = {'Commands': TIME_STATS.commands, 'Totals': TIME_STATS.totals} - with open("scons_stats.json", 'w') as sf: - sf.write(json.dumps(json_structure)) + # Now add information about this build to the JSON file + json_structure['Build_Info'] = { + 'BUILD_TARGETS' : [str(t) for t in BUILD_TARGETS], + 'ARGUMENTS' : [str(a) for a in ARGUMENTS], + 'ARGLIST' : [ str(al) for al in ARGLIST], + 'COMMAND_LINE_TARGETS' : [ str(clt) for clt in COMMAND_LINE_TARGETS], + 'ARGV' : sys.argv, + 'TIME' : datetime.now().isoformat(), + 'HOST' : os.uname().nodename, + 'PYTHON_VERSION' : { + 'major' : sys.version_info.major, + 'minor' : sys.version_info.minor, + 'micro' : sys.version_info.micro, + 'releaselevel' : sys.version_info.releaselevel, + 'serial' : sys.version_info.serial, + } + } + + with open(JSON_OUTPUT_FILE, 'w') as sf: + sf.write(json.dumps(json_structure, indent=4)) # Local Variables: # tab-width:4 diff --git a/doc/man/scons.xml b/doc/man/scons.xml index e79a267..0625efa 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -929,6 +929,19 @@ $ scons --debug=includes foo.o + + json + +Write info for any of the following debug options if they are enabled: memory, + count, time, action-timestamps + + +$ scons --debug=memory,json foo.o + + + + + memoizer -- cgit v0.12 From 1960eaffb7089069e645a648f13d1ffda93f4d28 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Sun, 4 Jun 2023 18:09:49 -0700 Subject: Added test, CHANGES/RELEASE text. Fixturized SConstruct from test/option/debug-count.py, and used that in test/option/debug-json.py --- CHANGES.txt | 5 ++ RELEASE.txt | 3 ++ SCons/Script/Main.py | 1 - SCons/Util/stats.py | 2 +- test/option/debug-count.py | 16 ++---- test/option/debug-json.py | 82 ++++++++++++++++++++++++++++++ test/option/fixture/SConstruct_debug_count | 6 +++ 7 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 test/option/debug-json.py create mode 100644 test/option/fixture/SConstruct_debug_count diff --git a/CHANGES.txt b/CHANGES.txt index 2584583..b9f0c49 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -9,6 +9,11 @@ NOTE: 4.3.0 now requires Python 3.6.0 and above. Python 3.5.x is no longer suppo RELEASE VERSION/DATE TO BE FILLED IN LATER + From William Deegan + - The --debug flag now has a 'json' option which will write information + generated by --debug={count, memory, time, action-timestamps} and about + the build. + From Mats Wichmann - C scanner's dictifyCPPDEFINES routine did not understand the possible combinations of CPPDEFINES - not aware of a "name=value" string either diff --git a/RELEASE.txt b/RELEASE.txt index e71e2b6..f7566fb 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -34,6 +34,9 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY were dropped: get_src_sig_type, get_tgt_sig_type, _changed_source. These were unused remnants of the previously removed SourceSignatures and TargetSignatures features (dropped in 3.1.2). +- The --debug flag now has a 'json' option which will write information + generated by --debug={count, memory, time, action-timestamps} and about + the build. FIXES ----- diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index 05a45b5..73070b2 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -1506,7 +1506,6 @@ def main() -> None: if SCons.Util.stats.ENABLE_JSON: WriteJsonFile() - TIME_STATS.total_times(total_time, sconscript_time, scons_time, ct) if SCons.Util.stats.ENABLE_JSON: diff --git a/SCons/Util/stats.py b/SCons/Util/stats.py index 5a05f1c..c407871 100644 --- a/SCons/Util/stats.py +++ b/SCons/Util/stats.py @@ -159,7 +159,7 @@ def WriteJsonFile(): from SCons.Script import BUILD_TARGETS, COMMAND_LINE_TARGETS, ARGUMENTS, ARGLIST - print("DUMPING JSON FILE") + # print("DUMPING JSON FILE") json_structure = {} if COUNT_STATS.enabled: json_structure['Object counts'] = {} diff --git a/test/option/debug-count.py b/test/option/debug-count.py index cf46feb..dd252fc 100644 --- a/test/option/debug-count.py +++ b/test/option/debug-count.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # -# __COPYRIGHT__ +# MIT License +# +# Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -25,8 +27,6 @@ Test that the --debug=count option works. """ -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - import re import sys @@ -34,15 +34,7 @@ import TestSCons test = TestSCons.TestSCons() - -test.write('SConstruct', """ -DefaultEnvironment(tools=[]) -def cat(target, source, env): - with open(str(target[0]), 'wb') as f, open(str(source[0]), 'rb') as infp: - f.write(infp.read()) -env = Environment(BUILDERS={'Cat':Builder(action=Action(cat))}) -env.Cat('file.out', 'file.in') -""") +test.file_fixture('fixture/SConstruct_debug_count', 'SConstruct') test.write('file.in', "file.in\n") diff --git a/test/option/debug-json.py b/test/option/debug-json.py new file mode 100644 index 0000000..283b67d --- /dev/null +++ b/test/option/debug-json.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# +# MIT License +# +# Copyright The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +Test that the --debug=json option works. +""" + +import json +import re +import sys + +import TestSCons + +test = TestSCons.TestSCons() + +test.file_fixture('fixture/SConstruct_debug_count', 'SConstruct') +test.write('file.in', "file.in\n") + + +# Just check that object counts for some representative classes +# show up in the output. + +def find_object_count(s, stdout): + re_string = r'\d+ +\d+ %s' % re.escape(s) + return re.search(re_string, stdout) + + +objects = [ + 'Action.CommandAction', + 'Builder.BuilderBase', + 'Environment.Base', + 'Executor.Executor', + 'Node.FS.File', + 'Node.FS.Base', + 'Node.Node', +] + +test.run(arguments='--debug=count,json') +test.must_exist('scons_stats.json') +with open('scons_stats.json') as jf: + stats_info = json.load(jf) + if 'Build_Info' not in stats_info: + test.fail_test(message='No Build_Info in json') + if 'Object counts' not in stats_info: + test.fail_test(message='No "Object counts" in json') + + for o in objects: + if o not in stats_info['Object counts']: + test.fail_test(message=f"Object counts missing {o}") + + if stats_info['Build_Info']['PYTHON_VERSION']['major'] != sys.version_info.major: + test.fail_test(message=f"Build Info PYTHON_VERSION incorrect") + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/test/option/fixture/SConstruct_debug_count b/test/option/fixture/SConstruct_debug_count new file mode 100644 index 0000000..be55d35 --- /dev/null +++ b/test/option/fixture/SConstruct_debug_count @@ -0,0 +1,6 @@ +DefaultEnvironment(tools=[]) +def cat(target, source, env): + with open(str(target[0]), 'wb') as f, open(str(source[0]), 'rb') as infp: + f.write(infp.read()) +env = Environment(BUILDERS={'Cat':Builder(action=Action(cat))}) +env.Cat('file.out', 'file.in') -- cgit v0.12 From 8d2a4b55744150f151448a361f46e4c928db458b Mon Sep 17 00:00:00 2001 From: William Deegan Date: Mon, 5 Jun 2023 10:21:59 -0700 Subject: Added test for --debug=json. Implemented DebugOptions(json). Create containing directory, and issue UserError if the directory is not creatable.. --- CHANGES.txt | 2 +- SCons/Script/Main.py | 24 ++++++++++++++---- SCons/Script/__init__.py | 1 + SCons/Util/stats.py | 5 +++- test/option/debug-json.py | 39 ++++++++++++++++++++---------- test/option/fixture/SConstruct_debug_count | 4 +++ 6 files changed, 55 insertions(+), 20 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b9f0c49..edac6d2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -13,7 +13,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - The --debug flag now has a 'json' option which will write information generated by --debug={count, memory, time, action-timestamps} and about the build. - + From Mats Wichmann - C scanner's dictifyCPPDEFINES routine did not understand the possible combinations of CPPDEFINES - not aware of a "name=value" string either diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index 73070b2..d4b83b0 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -59,7 +59,7 @@ import SCons.Taskmaster import SCons.Util import SCons.Warnings import SCons.Script.Interactive -from SCons.Util.stats import COUNT_STATS, MEMORY_STATS, TIME_STATS, ENABLE_JSON, WriteJsonFile +from SCons.Util.stats import COUNT_STATS, MEMORY_STATS, TIME_STATS, ENABLE_JSON, WriteJsonFile, JSON_OUTPUT_FILE from SCons import __version__ as SConsVersion @@ -519,6 +519,24 @@ def GetOption(name): def SetOption(name, value): return OptionsParser.values.set_option(name, value) +def DebugOptions(json=None): + """ + API to allow specifying options to SCons debug logic + Currently only json is supported which changes the + json file written by --debug=json from the default + """ + if json is not None: + json_node = SCons.Defaults.DefaultEnvironment().arg2nodes(json) + SCons.Util.stats.JSON_OUTPUT_FILE = json_node[0].get_abspath() + # Check if parent dir to JSON_OUTPUT_FILE exists + json_dir = os.path.dirname(SCons.Util.stats.JSON_OUTPUT_FILE) + try: + if not os.path.isdir(json_dir): + os.makedirs(json_dir, exist_ok=True) + except (FileNotFoundError, OSError) as e: + raise SCons.Errors.UserError(f"Unable to create directory for JSON debug output file: {SCons.Util.stats.JSON_OUTPUT_FILE}") + + def ValidateOptions(throw_exception: bool=False) -> None: """Validate options passed to SCons on the command line. @@ -1507,10 +1525,6 @@ def main() -> None: if SCons.Util.stats.ENABLE_JSON: WriteJsonFile() - - if SCons.Util.stats.ENABLE_JSON: - WriteJsonFile() - sys.exit(exit_status) # Local Variables: diff --git a/SCons/Script/__init__.py b/SCons/Script/__init__.py index c8666c4..e398ecf 100644 --- a/SCons/Script/__init__.py +++ b/SCons/Script/__init__.py @@ -110,6 +110,7 @@ SetOption = Main.SetOption ValidateOptions = Main.ValidateOptions Progress = Main.Progress GetBuildFailures = Main.GetBuildFailures +DebugOptions = Main.DebugOptions #keep_going_on_error = Main.keep_going_on_error #print_dtree = Main.print_dtree diff --git a/SCons/Util/stats.py b/SCons/Util/stats.py index c407871..911910b 100644 --- a/SCons/Util/stats.py +++ b/SCons/Util/stats.py @@ -151,6 +151,7 @@ MEMORY_STATS = MemStats() TIME_STATS = TimeStats() + def WriteJsonFile(): """ Actually write the JSON file with debug information. @@ -159,7 +160,7 @@ def WriteJsonFile(): from SCons.Script import BUILD_TARGETS, COMMAND_LINE_TARGETS, ARGUMENTS, ARGLIST - # print("DUMPING JSON FILE") + print(f"DUMPING JSON FILE: {JSON_OUTPUT_FILE}") json_structure = {} if COUNT_STATS.enabled: json_structure['Object counts'] = {} @@ -199,6 +200,8 @@ def WriteJsonFile(): } } + + with open(JSON_OUTPUT_FILE, 'w') as sf: sf.write(json.dumps(json_structure, indent=4)) diff --git a/test/option/debug-json.py b/test/option/debug-json.py index 283b67d..f3e6e0f 100644 --- a/test/option/debug-json.py +++ b/test/option/debug-json.py @@ -46,6 +46,22 @@ def find_object_count(s, stdout): re_string = r'\d+ +\d+ %s' % re.escape(s) return re.search(re_string, stdout) +def check_json_file(filename): + with open(filename,'r') as jf: + stats_info = json.load(jf) + if 'Build_Info' not in stats_info: + test.fail_test(message='No Build_Info in json') + if 'Object counts' not in stats_info: + test.fail_test(message='No "Object counts" in json') + + for o in objects: + if o not in stats_info['Object counts']: + test.fail_test(message=f"Object counts missing {o}") + + if stats_info['Build_Info']['PYTHON_VERSION'][ + 'major'] != sys.version_info.major: + test.fail_test(message=f"Build Info PYTHON_VERSION incorrect") + objects = [ 'Action.CommandAction', @@ -59,19 +75,16 @@ objects = [ test.run(arguments='--debug=count,json') test.must_exist('scons_stats.json') -with open('scons_stats.json') as jf: - stats_info = json.load(jf) - if 'Build_Info' not in stats_info: - test.fail_test(message='No Build_Info in json') - if 'Object counts' not in stats_info: - test.fail_test(message='No "Object counts" in json') - - for o in objects: - if o not in stats_info['Object counts']: - test.fail_test(message=f"Object counts missing {o}") - - if stats_info['Build_Info']['PYTHON_VERSION']['major'] != sys.version_info.major: - test.fail_test(message=f"Build Info PYTHON_VERSION incorrect") +check_json_file('scons_stats.json') + +test.run(arguments='--debug=count,json JSON=build/output/stats.json') +test.must_exist('build/output/stats.json') +check_json_file('build/output/stats.json') + +test.run(arguments='--debug=count,json JSON=/cant/write/here/dumb.json', status=2, stderr=None) +test.must_not_exist('/cant/write/here/dumb.json') +test.pass_test('scons: *** Unable to create directory for JSON debug output file:' in test.stderr()) + test.pass_test() diff --git a/test/option/fixture/SConstruct_debug_count b/test/option/fixture/SConstruct_debug_count index be55d35..332815b 100644 --- a/test/option/fixture/SConstruct_debug_count +++ b/test/option/fixture/SConstruct_debug_count @@ -1,3 +1,7 @@ +if ARGUMENTS.get('JSON',False): + DebugOptions(json=ARGUMENTS.get('JSON')) + + DefaultEnvironment(tools=[]) def cat(target, source, env): with open(str(target[0]), 'wb') as f, open(str(source[0]), 'rb') as infp: -- cgit v0.12 From 0467f15ea25a719fa45ee86d74a1b1657a357105 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Mon, 5 Jun 2023 10:43:47 -0700 Subject: Added docs --- SCons/Script/Main.xml | 17 ++ SCons/Util/stats.py | 2 +- doc/generated/examples/caching_ex-random_1.xml | 2 +- doc/generated/examples/troubleshoot_explain1_3.xml | 2 +- .../examples/troubleshoot_stacktrace_2.xml | 3 +- doc/generated/functions.gen | 269 +++++++++++++-------- doc/generated/functions.mod | 4 + doc/man/scons.xml | 4 +- 8 files changed, 193 insertions(+), 110 deletions(-) diff --git a/SCons/Script/Main.xml b/SCons/Script/Main.xml index 379d534..9248668 100644 --- a/SCons/Script/Main.xml +++ b/SCons/Script/Main.xml @@ -183,6 +183,23 @@ Future versions of &SCons; will likely forbid such usage. + + +([json]) + + + +Allows setting options for SCons debug options. Currently the only supported value is + json which sets the path to the json file created when + --debug=json is set. + + +DebugOptions(json='#/build/output/scons_stats.json') + + + + + () diff --git a/SCons/Util/stats.py b/SCons/Util/stats.py index 911910b..58fa5dd 100644 --- a/SCons/Util/stats.py +++ b/SCons/Util/stats.py @@ -160,7 +160,7 @@ def WriteJsonFile(): from SCons.Script import BUILD_TARGETS, COMMAND_LINE_TARGETS, ARGUMENTS, ARGLIST - print(f"DUMPING JSON FILE: {JSON_OUTPUT_FILE}") + # print(f"DUMPING JSON FILE: {JSON_OUTPUT_FILE}") json_structure = {} if COUNT_STATS.enabled: json_structure['Object counts'] = {} diff --git a/doc/generated/examples/caching_ex-random_1.xml b/doc/generated/examples/caching_ex-random_1.xml index 4773380..422f37d 100644 --- a/doc/generated/examples/caching_ex-random_1.xml +++ b/doc/generated/examples/caching_ex-random_1.xml @@ -1,7 +1,7 @@ % scons -Q -cc -o f1.o -c f1.c cc -o f4.o -c f4.c cc -o f2.o -c f2.c +cc -o f1.o -c f1.c cc -o f5.o -c f5.c cc -o f3.o -c f3.c cc -o prog f1.o f2.o f3.o f4.o f5.o diff --git a/doc/generated/examples/troubleshoot_explain1_3.xml b/doc/generated/examples/troubleshoot_explain1_3.xml index e658d89..a0aec9f 100644 --- a/doc/generated/examples/troubleshoot_explain1_3.xml +++ b/doc/generated/examples/troubleshoot_explain1_3.xml @@ -2,5 +2,5 @@ cp file.in file.oout scons: warning: Cannot find target file.out after building -File "/Users/bdbaddog/devel/scons/git/as_scons/scripts/scons.py", line 97, in <module> +File "/Users/bdbaddog/devel/scons/git/scons-2/scripts/scons.py", line 97, in <module> diff --git a/doc/generated/examples/troubleshoot_stacktrace_2.xml b/doc/generated/examples/troubleshoot_stacktrace_2.xml index 79234f2..668e67f 100644 --- a/doc/generated/examples/troubleshoot_stacktrace_2.xml +++ b/doc/generated/examples/troubleshoot_stacktrace_2.xml @@ -3,8 +3,9 @@ scons: *** [prog.o] Source `prog.c' not found, needed by target `prog.o'. scons: internal stack trace: File "SCons/Taskmaster/Job.py", line 219, in start task.prepare() - File "SCons/Script/Main.py", line 180, in prepare + File "SCons/Script/Main.py", line 201, in prepare return SCons.Taskmaster.OutOfDateTask.prepare(self) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "SCons/Taskmaster/__init__.py", line 195, in prepare executor.prepare() File "SCons/Executor.py", line 418, in prepare diff --git a/doc/generated/functions.gen b/doc/generated/functions.gen index 5a74fc6..90d263d 100644 --- a/doc/generated/functions.gen +++ b/doc/generated/functions.gen @@ -248,9 +248,9 @@ to be performed after the specified target has been built. -The specified action(s) may be +action may be an Action object, or anything that -can be converted into an Action object +can be converted into an Action object. See the manpage section "Action Objects" for a complete explanation. @@ -261,6 +261,13 @@ the action may be called multiple times, once after each action that generates one or more targets in the list. + + +foo = Program('foo.c') +# remove execute permission from binary: +AddPostAction(foo, Chmod('$TARGET', "a-x")) + + @@ -273,9 +280,9 @@ to be performed before the specified target is built. -The specified action(s) may be +action may be an Action object, or anything that -can be converted into an Action object +can be converted into an Action object. See the manpage section "Action Objects" for a complete explanation. @@ -318,20 +325,34 @@ file into an object file. - Alias(alias, [targets, [action]]) - env.Alias(alias, [targets, [action]]) + Alias(alias, [source, [action]]) + env.Alias(alias, [source, [action]]) -Creates one or more phony targets that -expand to one or more other targets. -An optional +Creates a phony target (or targets) that +can be used as references to zero or more other targets, +as specified by the optional source +parameter. +alias and +source +may each be a string or Node object, +or a list of strings or Node objects; +if Nodes are used for +alias +they must be Alias nodes. +The optional action -(command) -or list of actions -can be specified that will be executed +parameter specifies an action or list of actions +that will be executed whenever the any of the alias targets are out-of-date. -Returns the Node object representing the alias, -which exists outside of any file system. -This Node object, or the alias name, + + + +Returns a list of Alias Node objects representing the alias(es), +which exist outside of any physical file system. + + + +The alias name, or an Alias Node object, may be used as a dependency of any other target, including another alias. &f-Alias; @@ -519,7 +540,7 @@ giving an easy way to enter multiple macros in one addition. Use an = to specify a valued macro. -A tuple is treated as a valued macro. +A tuple is treated as a valued macro. Use the value None if the macro should not have a value. It is an error to supply more than two elements in such a tuple. @@ -1117,13 +1138,25 @@ so you normally don't need to create directories by hand. Configure(env, [custom_tests, conf_dir, log_file, config_h]) env.Configure([custom_tests, conf_dir, log_file, config_h]) -Creates a Configure object for integrated -functionality similar to GNU autoconf. +Creates a &Configure; object for integrated +functionality similar to GNU autoconf. See the manpage section "Configure Contexts" for a complete explanation of the arguments and behavior. + + DebugOptions([json]) + +Allows setting options for SCons debug options. Currently the only supported value is + json which sets the path to the json file created when + --debug=json is set. + + +DebugOptions(json='#/build/output/scons_stats.json') + + + Decider(function) env.Decider(function) @@ -1141,50 +1174,24 @@ that will be applied: -"timestamp-newer" - - -Specifies that a target shall be considered out of date and rebuilt -if the dependency's timestamp is newer than the target file's timestamp. -This is the behavior of the classic Make utility, -and -make -can be used a synonym for -timestamp-newer. - - - - -"timestamp-match" - - -Specifies that a target shall be considered out of date and rebuilt -if the dependency's timestamp is different than the -timestamp recorded the last time the target was built. -This provides behavior very similar to the classic Make utility -(in particular, files are not opened up so that their -contents can be checksummed) -except that the target will also be rebuilt if a -dependency file has been restored to a version with an -earlier -timestamp, such as can happen when restoring files from backup archives. - - - - "content" Specifies that a target shall be considered out of date and rebuilt if the dependency's content has changed since the last time the target was built, -as determined be performing an checksum -on the dependency's contents +as determined by performing a checksum +on the dependency's contents using the selected hash function, and comparing it to the checksum recorded the last time the target was built. -MD5 -can be used as a synonym for -content, but it is deprecated. +content is the default decider. + + +Changed in version 4.1: +The decider was renamed to content +since the hash function is now selectable. +The former name, MD5, +can still be used as a synonym, but is deprecated. @@ -1215,9 +1222,44 @@ that runs a build, updates a file, and runs the build again, all within a single second. -MD5-timestamp -can be used as a synonym for -content-timestamp, but it is deprecated. + + +Changed in version 4.1: +The decider was renamed to content-timestamp +since the hash function is now selectable. +The former name, MD5-timestamp, +can still be used as a synonym, but is deprecated. + + + + +"timestamp-newer" + + +Specifies that a target shall be considered out of date and rebuilt +if the dependency's timestamp is newer than the target file's timestamp. +This is the behavior of the classic Make utility, +and +make +can be used a synonym for +timestamp-newer. + + + + +"timestamp-match" + + +Specifies that a target shall be considered out of date and rebuilt +if the dependency's timestamp is different than the +timestamp recorded the last time the target was built. +This provides behavior very similar to the classic Make utility +(in particular, files are not opened up so that their +contents can be checksummed) +except that the target will also be rebuilt if a +dependency file has been restored to a version with an +earlier +timestamp, such as can happen when restoring files from backup archives. @@ -3973,7 +4015,7 @@ Sets &scons; option variable name to value. These options are all also settable via command-line options but the variable name -may differ from the command-line option name - +may differ from the command-line option name - see the table for correspondences. A value set via command-line option will take precedence over one set with &f-SetOption;, which @@ -4481,57 +4523,74 @@ gltool(env) # adds 'opengl' to the TOOLS variable ValidateOptions([throw_exception=False]) - Check that all the options specified on the command line are either defined by SCons itself - or defined by calls to &f-link-AddOption;. - - - This function should only be called after the last &f-link-AddOption; call in your &SConscript; - logic. - - - Be aware that some tools call &f-link-AddOption;, if you are getting error messages for arguments - that they add, you will need to ensure that you load those tools before you call &f-ValidateOptions;. - - - If there are any command line options not defined, calling this function will cause SCons to issue an - error message and then exit with an error exit - status. - If the optional throw_exception is True, &f-ValidateOptions; will raise a - SConsBadOptionError - exception. This would allow the calling - &SConscript; logic can catch that exception and handle invalid options itself. - - - - Example: - - - + Check that all the options specified on the command line are either + &SCons; built-in options or defined via calls to &f-link-AddOption;. + &SCons; will eventually fail on unknown options anyway, but calling + this function allows the build to "fail fast" before executing + expensive logic later in the build. + + + + This function should only be called after the last &f-AddOption; + call in your &SConscript; logic. + Be aware that some tools call &f-AddOption;, if you are getting + error messages for arguments that they add, you will need to ensure + that those tools are loaded before calling &f-ValidateOptions;. + + + + If there are any unknown command line options, &f-ValidateOptions; + prints an error message and exits with an error exit status. + If the optional throw_exception argument is + True (default is False), + a SConsBadOptionError is raised, + giving an opportunity for the &SConscript; logic to catch that + exception and handle invalid options appropriately. Note that + this exception name needs to be imported (see the example below). + + + + A common build problem is typos (or thinkos) - a user enters an option + that is just a little off the expected value, or perhaps a different + word with a similar meaning. It may be useful to abort the build + before going too far down the wrong path. For example: + + + +$ scons --compilers=mingw # the correct flag is --compiler + + + + Here &SCons; could go off and run a bunch of configure steps with + the default value of --compiler, since the + incorrect command line did not actually supply a value to it, + costing developer time to track down why the configure logic + made the "wrong" choices. This example shows catching this: + + + +from SCons.Script.SConsOptions import SConsBadOptionError + +AddOption( + '--compiler', + dest='compiler', + action='store', + default='gcc', + type='string', +) + +# ... other SConscript logic ... + try: ValidateOptions(throw_exception=True) except SConsBadOptionError as e: - print("Parser is SConsOptionParser:%s" % (isinstance(e.parser, SConsOptionParser))) - print("Message is :%s" % e.opt_str) + print(f"ValidateOptions detects a fail: ", e.opt_str) Exit(3) - - - - This function is useful to force SCons to fail fast before you execute any expensive logic later in your - build logic. - For example if you specify build options via any flags, a simple typo could yield the incorrect build - option throughout your entire build. - - -scons --compilers=mingw (the correct flag is --compiler) - - - Could cause SCons to run configure steps with the incorrect compiler. Costing developer time trying to - track down why the configure logic failed with a compiler which should work. - - - New in version 4.5.0 - - + + + New in version 4.5.0 + + Value(value, [built_value], [name]) diff --git a/doc/generated/functions.mod b/doc/generated/functions.mod index 34424a5..7273aab 100644 --- a/doc/generated/functions.mod +++ b/doc/generated/functions.mod @@ -25,6 +25,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT. Clone"> Command"> Configure"> +DebugOptions"> Decider"> Default"> DefaultEnvironment"> @@ -106,6 +107,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT. env.Clone"> env.Command"> env.Configure"> +env.DebugOptions"> env.Decider"> env.Default"> env.DefaultEnvironment"> @@ -193,6 +195,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT. Clone"> Command"> Configure"> +DebugOptions"> Decider"> Default"> DefaultEnvironment"> @@ -274,6 +277,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT. env.Clone"> env.Command"> env.Configure"> +env.DebugOptions"> env.Decider"> env.Default"> env.DefaultEnvironment"> diff --git a/doc/man/scons.xml b/doc/man/scons.xml index 7579ce2..dd70c21 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -932,8 +932,10 @@ $ scons --debug=includes foo.o json -Write info for any of the following debug options if they are enabled: memory, +Write info to a JSON file for any of the following debug options if they are enabled: memory, count, time, action-timestamps + The default output file is in the top level directory named scons_stats.json + The file name/path can be modified by using DebugOptions(json='path/to/file.json') $ scons --debug=memory,json foo.o -- cgit v0.12 From a8016c61cdac9e73c77f83143cd7aa3118330cef Mon Sep 17 00:00:00 2001 From: William Deegan Date: Tue, 6 Jun 2023 10:42:35 -0700 Subject: Skip checking for exception when writing to unwritable dir in test. Change logic which get's nodename to work on all platforms (was failing on windows) --- SCons/Script/Main.py | 4 +++- SCons/Util/stats.py | 8 ++++---- test/option/debug-json.py | 9 ++++++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index d4b83b0..68a8183 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -533,7 +533,9 @@ def DebugOptions(json=None): try: if not os.path.isdir(json_dir): os.makedirs(json_dir, exist_ok=True) - except (FileNotFoundError, OSError) as e: + # Now try to open file and see if you can.. + open(SCons.Util.stats.JSON_OUTPUT_FILE,'w') + except OSError as e: raise SCons.Errors.UserError(f"Unable to create directory for JSON debug output file: {SCons.Util.stats.JSON_OUTPUT_FILE}") diff --git a/SCons/Util/stats.py b/SCons/Util/stats.py index 58fa5dd..d2f2574 100644 --- a/SCons/Util/stats.py +++ b/SCons/Util/stats.py @@ -33,7 +33,7 @@ There are basically two types of stats. though it might be useful to query during a run. """ -import os +import platform import json import sys from datetime import datetime @@ -190,7 +190,7 @@ def WriteJsonFile(): 'COMMAND_LINE_TARGETS' : [ str(clt) for clt in COMMAND_LINE_TARGETS], 'ARGV' : sys.argv, 'TIME' : datetime.now().isoformat(), - 'HOST' : os.uname().nodename, + 'HOST' : platform.node(), 'PYTHON_VERSION' : { 'major' : sys.version_info.major, 'minor' : sys.version_info.minor, @@ -201,9 +201,9 @@ def WriteJsonFile(): } - with open(JSON_OUTPUT_FILE, 'w') as sf: - sf.write(json.dumps(json_structure, indent=4)) + sf.write(json.dumps(json_structure, indent=4)) + # Local Variables: # tab-width:4 diff --git a/test/option/debug-json.py b/test/option/debug-json.py index f3e6e0f..867f3c2 100644 --- a/test/option/debug-json.py +++ b/test/option/debug-json.py @@ -32,6 +32,7 @@ import re import sys import TestSCons +from TestCmd import IS_WINDOWS test = TestSCons.TestSCons() @@ -81,9 +82,11 @@ test.run(arguments='--debug=count,json JSON=build/output/stats.json') test.must_exist('build/output/stats.json') check_json_file('build/output/stats.json') -test.run(arguments='--debug=count,json JSON=/cant/write/here/dumb.json', status=2, stderr=None) -test.must_not_exist('/cant/write/here/dumb.json') -test.pass_test('scons: *** Unable to create directory for JSON debug output file:' in test.stderr()) +# TODO: Not sure how to do this in a reasonable way on windows, so just skip for now +if not IS_WINDOWS: + test.run(arguments='--debug=count,json JSON=/cant/write/here/dumb.json', status=2, stderr=None) + test.must_not_exist('/cant/write/here/dumb.json') + test.pass_test('scons: *** Unable to create directory for JSON debug output file:' in test.stderr()) test.pass_test() -- cgit v0.12 From d0605a2fb9da48068c37440f6826b31d23a6805a Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Tue, 6 Jun 2023 07:26:31 -0600 Subject: Maint: turn ActionBase into abstract class Align some function sigs that were missing a kwarg. Sorted the imports. Signed-off-by: Mats Wichmann --- CHANGES.txt | 4 ++++ SCons/Action.py | 64 +++++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2584583..78cbadf 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -64,6 +64,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER sufficient just to check the build worked. Renamed the fixture dir to follow naming convention in test/MSVC overall, and added a sconstest.skip file, also to follow convention. + - Class ActionBase is now an abstract base class to more accurately + reflect its usage. Derived _ActionAction inherits the ABC, so it + now declares (actually raises NotImplementedError) two methods it + doesn't use so it can be instantiated by unittests and others. RELEASE 4.5.2 - Sun, 21 Mar 2023 14:08:29 -0700 diff --git a/SCons/Action.py b/SCons/Action.py index fcc6f3f..8748985 100644 --- a/SCons/Action.py +++ b/SCons/Action.py @@ -100,21 +100,22 @@ way for wrapping up the functions. """ +import inspect import os import pickle import re -import sys import subprocess -from subprocess import DEVNULL -import inspect +import sys +from abc import ABC, abstractmethod from collections import OrderedDict +from subprocess import DEVNULL +from typing import Union import SCons.Debug -import SCons.Util -from SCons.Debug import logInstanceCreation import SCons.Errors -import SCons.Util import SCons.Subst +import SCons.Util +from SCons.Debug import logInstanceCreation # we use these a lot, so try to optimize them from SCons.Util import is_String, is_List @@ -441,7 +442,7 @@ def _do_create_action(act, kw): This handles the fact that passing lists to :func:`Action` itself has different semantics than passing lists as elements of lists. The former will create a :class:`ListAction`, the latter will create a - :class:`CommandAction by converting the inner list elements to strings. + :class:`CommandAction` by converting the inner list elements to strings. """ if isinstance(act, ActionBase): return act @@ -518,11 +519,26 @@ def Action(act, *args, **kw): return _do_create_action(act, kw) -class ActionBase: +class ActionBase(ABC): """Base class for all types of action objects that can be held by other objects (Builders, Executors, etc.) This provides the common methods for manipulating and combining those actions.""" + @abstractmethod + def __call__( + self, + target, + source, + env, + exitstatfunc=_null, + presub=_null, + show=_null, + execute=_null, + chdir=_null, + executor=None, + ): + raise NotImplementedError + def __eq__(self, other): return self.__dict__ == other @@ -534,6 +550,14 @@ class ActionBase: def genstring(self, target, source, env): return str(self) + @abstractmethod + def get_presig(self, target, source, env, executor=None): + raise NotImplementedError + + @abstractmethod + def get_implicit_deps(self, target, source, env, executor=None): + raise NotImplementedError + def get_contents(self, target, source, env): result = self.get_presig(target, source, env) @@ -722,6 +746,16 @@ class _ActionAction(ActionBase): return stat + # Stub these two only so _ActionAction can be instantiated. It's really + # an ABC like parent ActionBase, but things reach in and use it. It's + # not just unittests or we could fix it up with a concrete subclass there. + + def get_presig(self, target, source, env, executor=None): + raise NotImplementedError + + def get_implicit_deps(self, target, source, env, executor=None): + raise NotImplementedError + def _string_from_cmd_list(cmd_list): """Takes a list of command line arguments and returns a pretty @@ -1223,10 +1257,14 @@ class LazyAction(CommandGeneratorAction, CommandAction): c = self.get_parent_class(env) return c.__call__(self, target, source, env, *args, **kw) - def get_presig(self, target, source, env): + def get_presig(self, target, source, env, executor=None): c = self.get_parent_class(env) return c.get_presig(self, target, source, env) + def get_implicit_deps(self, target, source, env, executor=None): + c = self.get_parent_class(env) + return c.get_implicit_deps(self, target, source, env) + def get_varlist(self, target, source, env, executor=None): c = self.get_parent_class(env) return c.get_varlist(self, target, source, env, executor) @@ -1345,14 +1383,14 @@ class FunctionAction(_ActionAction): # more information about this issue. del exc_info - def get_presig(self, target, source, env): + def get_presig(self, target, source, env, executor=None): """Return the signature contents of this callable action.""" try: return self.gc(target, source, env) except AttributeError: return self.funccontents - def get_implicit_deps(self, target, source, env): + def get_implicit_deps(self, target, source, env, executor=None): return [] class ListAction(ActionBase): @@ -1379,7 +1417,7 @@ class ListAction(ActionBase): return SCons.Util.flatten_sequence( [a.presub_lines(env) for a in self.list]) - def get_presig(self, target, source, env): + def get_presig(self, target, source, env, executor=None): """Return the signature contents of this action list. Simple concatenation of the signatures of the elements. @@ -1398,7 +1436,7 @@ class ListAction(ActionBase): return stat return 0 - def get_implicit_deps(self, target, source, env): + def get_implicit_deps(self, target, source, env, executor=None): result = [] for act in self.list: result.extend(act.get_implicit_deps(target, source, env)) -- cgit v0.12 From eb440a02930e3aff35b6dcc85462a4035e764aee Mon Sep 17 00:00:00 2001 From: William Deegan Date: Thu, 8 Jun 2023 07:51:50 -0700 Subject: [ci skip] Add blurb to RELEASE.txt --- RELEASE.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASE.txt b/RELEASE.txt index e71e2b6..865ae30 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -72,7 +72,10 @@ DEVELOPMENT to test files, which allows test lists, exclude lists, and tests on the command line to "not care" about the OS convention for pathname separators. - +- Class ActionBase is now an abstract base class to more accurately + reflect its usage. Derived _ActionAction inherits the ABC, so it + now declares (actually raises NotImplementedError) two methods it + doesn't use so it can be instantiated by unittests and others. Thanks to the following contributors listed below for their contributions to this release. ========================================================================================== -- cgit v0.12 From c8e8cae80d894b5e7971ae3bef1e6acebfccb201 Mon Sep 17 00:00:00 2001 From: William Deegan Date: Thu, 8 Jun 2023 10:07:25 -0700 Subject: Address mwichmann's comments on PR. pep8 naming, abstract base class Stats, quiet some linting warnings where the use is intentional --- SCons/Script/Main.py | 18 ++++++++++-------- SCons/Util/stats.py | 21 ++++++++++++--------- doc/man/scons.xml | 6 +++--- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index 68a8183..861f57a 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -59,7 +59,7 @@ import SCons.Taskmaster import SCons.Util import SCons.Warnings import SCons.Script.Interactive -from SCons.Util.stats import COUNT_STATS, MEMORY_STATS, TIME_STATS, ENABLE_JSON, WriteJsonFile, JSON_OUTPUT_FILE +from SCons.Util.stats import COUNT_STATS, MEMORY_STATS, TIME_STATS, ENABLE_JSON, write_scons_stats_file, JSON_OUTPUT_FILE from SCons import __version__ as SConsVersion @@ -534,7 +534,8 @@ def DebugOptions(json=None): if not os.path.isdir(json_dir): os.makedirs(json_dir, exist_ok=True) # Now try to open file and see if you can.. - open(SCons.Util.stats.JSON_OUTPUT_FILE,'w') + with open(SCons.Util.stats.JSON_OUTPUT_FILE,'w') as js: + pass except OSError as e: raise SCons.Errors.UserError(f"Unable to create directory for JSON debug output file: {SCons.Util.stats.JSON_OUTPUT_FILE}") @@ -669,8 +670,10 @@ def _SConstruct_exists( return sfile return None + def _set_debug_values(options) -> None: - global print_memoizer, print_objects, print_stacktrace, print_time, print_action_timestamps + global print_memoizer, print_objects, print_stacktrace, print_time, \ + print_action_timestamps, ENABLE_JSON debug_values = options.debug @@ -720,9 +723,7 @@ def _set_debug_values(options) -> None: if "duplicate" in debug_values: SCons.Node.print_duplicate = True if "json" in debug_values: - SCons.Util.stats.ENABLE_JSON = True - if "json" in debug_values: - SCons.Util.stats.ENABLE_JSON = True + ENABLE_JSON = True def _create_path(plist): path = '.' @@ -1431,6 +1432,7 @@ def main() -> None: global OptionsParser global exit_status global first_command_start + global ENABLE_JSON # Check up front for a Python version we do not support. We # delay the check for deprecated Python versions until later, @@ -1524,8 +1526,8 @@ def main() -> None: TIME_STATS.total_times(total_time, sconscript_time, scons_time, ct) - if SCons.Util.stats.ENABLE_JSON: - WriteJsonFile() + if ENABLE_JSON: + write_scons_stats_file() sys.exit(exit_status) diff --git a/SCons/Util/stats.py b/SCons/Util/stats.py index d2f2574..3223c86 100644 --- a/SCons/Util/stats.py +++ b/SCons/Util/stats.py @@ -33,6 +33,8 @@ There are basically two types of stats. though it might be useful to query during a run. """ +from abc import ABC + import platform import json import sys @@ -40,21 +42,21 @@ from datetime import datetime import SCons.Debug -ALL_STATS = {} +all_stats = {} ENABLE_JSON = False JSON_OUTPUT_FILE = 'scons_stats.json' -def AddStatType(name, stat_object): +def add_stat_type(name, stat_object): """ Add a statistic type to the global collection """ - if name in ALL_STATS: + if name in all_stats: raise UserWarning(f'Stat type {name} already exists') else: - ALL_STATS[name] = stat_object + all_stats[name] = stat_object -class Stats: +class Stats(ABC): def __init__(self): self.stats = [] self.labels = [] @@ -151,14 +153,15 @@ MEMORY_STATS = MemStats() TIME_STATS = TimeStats() - -def WriteJsonFile(): +def write_scons_stats_file(): """ Actually write the JSON file with debug information. Depending which of : count, time, action-timestamps,memory their information will be written. """ - from SCons.Script import BUILD_TARGETS, COMMAND_LINE_TARGETS, ARGUMENTS, ARGLIST + # Have to import where used to avoid import loop + from SCons.Script import BUILD_TARGETS, COMMAND_LINE_TARGETS, ARGUMENTS, \ + ARGLIST # [import-outside-toplevel] # print(f"DUMPING JSON FILE: {JSON_OUTPUT_FILE}") json_structure = {} @@ -202,7 +205,7 @@ def WriteJsonFile(): with open(JSON_OUTPUT_FILE, 'w') as sf: - sf.write(json.dumps(json_structure, indent=4)) + sf.write(json.dumps(json_structure, indent=4)) # Local Variables: diff --git a/doc/man/scons.xml b/doc/man/scons.xml index dd70c21..80d16c0 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -932,10 +932,10 @@ $ scons --debug=includes foo.o json -Write info to a JSON file for any of the following debug options if they are enabled: memory, + Write info to a JSON file for any of the following debug options if they are enabled: memory, count, time, action-timestamps - The default output file is in the top level directory named scons_stats.json - The file name/path can be modified by using DebugOptions(json='path/to/file.json') + The default output file is scons_stats.json + The file name/path can be modified by using &f-link-DebugOptions; for example DebugOptions(json='path/to/file.json') $ scons --debug=memory,json foo.o -- cgit v0.12 From 68594e090d543e9698b1c60d34b236dbc95bc6bf Mon Sep 17 00:00:00 2001 From: William Deegan Date: Thu, 8 Jun 2023 19:42:01 -0700 Subject: refeactor module variables to be lower case per mwichmann's review --- SCons/Script/Main.py | 34 +++++++++++++++++----------------- SCons/Util/stats.py | 22 +++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/SCons/Script/Main.py b/SCons/Script/Main.py index 861f57a..c9cd486 100644 --- a/SCons/Script/Main.py +++ b/SCons/Script/Main.py @@ -59,7 +59,7 @@ import SCons.Taskmaster import SCons.Util import SCons.Warnings import SCons.Script.Interactive -from SCons.Util.stats import COUNT_STATS, MEMORY_STATS, TIME_STATS, ENABLE_JSON, write_scons_stats_file, JSON_OUTPUT_FILE +from SCons.Util.stats import count_stats, memory_stats, time_stats, ENABLE_JSON, write_scons_stats_file, JSON_OUTPUT_FILE from SCons import __version__ as SConsVersion @@ -229,7 +229,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask): "Command execution end timestamp: %s: %f\n" % (str(self.node), finish_time) ) - TIME_STATS.add_command(str(self.node), start_time, finish_time) + time_stats.add_command(str(self.node), start_time, finish_time) sys.stdout.write( "Command execution time: %s: %f seconds\n" % (str(self.node), (finish_time - start_time)) @@ -685,7 +685,7 @@ def _set_debug_values(options) -> None: enable_count = False if __debug__: enable_count = True if enable_count: - COUNT_STATS.enable(sys.stdout) + count_stats.enable(sys.stdout) SCons.Debug.track_instances = True else: msg = "--debug=count is not supported when running SCons\n" + \ @@ -699,7 +699,7 @@ def _set_debug_values(options) -> None: options.debug_includes = "includes" in debug_values print_memoizer = "memoizer" in debug_values if "memory" in debug_values: - MEMORY_STATS.enable(sys.stdout) + memory_stats.enable(sys.stdout) print_objects = ("objects" in debug_values) if print_objects: SCons.Debug.track_instances = True @@ -711,8 +711,8 @@ def _set_debug_values(options) -> None: options.tree_printers.append(TreePrinter(status=True)) if "time" in debug_values: print_time = True - TIME_STATS.enable(sys.stdout) - TIME_STATS.enable(sys.stdout) + time_stats.enable(sys.stdout) + time_stats.enable(sys.stdout) if "action-timestamps" in debug_values: print_time = True print_action_timestamps = True @@ -1056,8 +1056,8 @@ def _main(parser): if not hasattr(sys.stderr, 'isatty') or not sys.stderr.isatty(): sys.stderr = SCons.Util.Unbuffered(sys.stderr) - MEMORY_STATS.append('before reading SConscript files:') - COUNT_STATS.append(('pre-', 'read')) + memory_stats.append('before reading SConscript files:') + count_stats.append(('pre-', 'read')) # And here's where we (finally) read the SConscript files. @@ -1083,8 +1083,8 @@ def _main(parser): progress_display("scons: done reading SConscript files.") - MEMORY_STATS.append('after reading SConscript files:') - COUNT_STATS.append(('post-', 'read')) + memory_stats.append('after reading SConscript files:') + count_stats.append(('post-', 'read')) # Re-{enable,disable} warnings in case they disabled some in # the SConscript file. @@ -1336,8 +1336,8 @@ def _build_targets(fs, options, targets, target_top): if msg: SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg) - MEMORY_STATS.append('before building targets:') - COUNT_STATS.append(('pre-', 'build')) + memory_stats.append('before building targets:') + count_stats.append(('pre-', 'build')) def jobs_postfunc( jobs=jobs, @@ -1365,8 +1365,8 @@ def _build_targets(fs, options, targets, target_top): progress_display("scons: " + opening_message) jobs.run(postfunc = jobs_postfunc) - MEMORY_STATS.append('after building targets:') - COUNT_STATS.append(('post-', 'build')) + memory_stats.append('after building targets:') + count_stats.append(('post-', 'build')) return nodes @@ -1492,8 +1492,8 @@ def main() -> None: SCons.Script._SConscript.SConscript_exception() sys.exit(2) - MEMORY_STATS.print_stats() - COUNT_STATS.print_stats() + memory_stats.print_stats() + count_stats.print_stats() if print_objects: SCons.Debug.listLoggedInstances('*') @@ -1523,7 +1523,7 @@ def main() -> None: print("Total SConscript file execution time: %f seconds"%sconscript_time) print("Total SCons execution time: %f seconds"%scons_time) print("Total command execution time: %f seconds"%ct) - TIME_STATS.total_times(total_time, sconscript_time, scons_time, ct) + time_stats.total_times(total_time, sconscript_time, scons_time, ct) if ENABLE_JSON: diff --git a/SCons/Util/stats.py b/SCons/Util/stats.py index 3223c86..22135e3 100644 --- a/SCons/Util/stats.py +++ b/SCons/Util/stats.py @@ -148,9 +148,9 @@ class TimeStats(Stats): 'duration': finish_time - start_time} -COUNT_STATS = CountStats() -MEMORY_STATS = MemStats() -TIME_STATS = TimeStats() +count_stats = CountStats() +memory_stats = MemStats() +time_stats = TimeStats() def write_scons_stats_file(): @@ -165,25 +165,25 @@ def write_scons_stats_file(): # print(f"DUMPING JSON FILE: {JSON_OUTPUT_FILE}") json_structure = {} - if COUNT_STATS.enabled: + if count_stats.enabled: json_structure['Object counts'] = {} oc = json_structure['Object counts'] - for c in COUNT_STATS.stats_table: + for c in count_stats.stats_table: oc[c] = {} - for l, v in zip(COUNT_STATS.labels, COUNT_STATS.stats_table[c]): + for l, v in zip(count_stats.labels, count_stats.stats_table[c]): oc[c][''.join(l)] = v - if MEMORY_STATS.enabled: + if memory_stats.enabled: json_structure['Memory'] = {} m = json_structure['Memory'] - for label, stats in zip(MEMORY_STATS.labels, MEMORY_STATS.stats): + for label, stats in zip(memory_stats.labels, memory_stats.stats): m[label] = stats - if TIME_STATS.enabled: - json_structure['Time'] = {'Commands': TIME_STATS.commands, - 'Totals': TIME_STATS.totals} + if time_stats.enabled: + json_structure['Time'] = {'Commands': time_stats.commands, + 'Totals': time_stats.totals} # Now add information about this build to the JSON file json_structure['Build_Info'] = { -- cgit v0.12