diff options
author | Joseph Brill <48932340+jcbrill@users.noreply.github.com> | 2022-06-16 20:11:05 (GMT) |
---|---|---|
committer | Joseph Brill <48932340+jcbrill@users.noreply.github.com> | 2022-06-16 20:11:05 (GMT) |
commit | 126a34e27d9aa2837ad271b31162ffb02ec2290a (patch) | |
tree | d4efef8a883b880577646115367d7c4317cf43ae /SCons/Tool | |
parent | ac6045d37df4d53d73cbf33323b33141dbb67e58 (diff) | |
parent | 067e290d832e17158aeb068bddb39dc9ad46d5cc (diff) | |
download | SCons-126a34e27d9aa2837ad271b31162ffb02ec2290a.zip SCons-126a34e27d9aa2837ad271b31162ffb02ec2290a.tar.gz SCons-126a34e27d9aa2837ad271b31162ffb02ec2290a.tar.bz2 |
Merge branch 'master' into jbrill-msvc-batchargs
Diffstat (limited to 'SCons/Tool')
-rw-r--r-- | SCons/Tool/FortranCommon.py | 27 | ||||
-rw-r--r-- | SCons/Tool/ToolTests.py | 13 | ||||
-rw-r--r-- | SCons/Tool/__init__.py | 32 | ||||
-rw-r--r-- | SCons/Tool/fortran.py | 2 | ||||
-rw-r--r-- | SCons/Tool/g77.py | 26 | ||||
-rw-r--r-- | SCons/Tool/gfortran.py | 29 | ||||
-rw-r--r-- | SCons/Tool/ifort.py | 29 | ||||
-rw-r--r-- | SCons/Tool/ninja/NinjaState.py | 130 | ||||
-rw-r--r-- | SCons/Tool/ninja/Utils.py | 25 | ||||
-rw-r--r-- | SCons/Tool/ninja/__init__.py | 25 | ||||
-rw-r--r-- | SCons/Tool/ninja/ninja.xml | 19 | ||||
-rw-r--r-- | SCons/Tool/ninja/ninja_daemon_build.py | 16 | ||||
-rw-r--r-- | SCons/Tool/ninja/ninja_scons_daemon.py | 5 | ||||
-rw-r--r-- | SCons/Tool/packaging/__init__.py | 3 |
14 files changed, 260 insertions, 121 deletions
diff --git a/SCons/Tool/FortranCommon.py b/SCons/Tool/FortranCommon.py index a73de5d..cad16b6 100644 --- a/SCons/Tool/FortranCommon.py +++ b/SCons/Tool/FortranCommon.py @@ -20,7 +20,8 @@ # 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. -"""SCons.Tool.FortranCommon + +""" Stuff for processing Fortran, common to all fortran dialects. @@ -55,6 +56,7 @@ def isfortran(env, source): return 1 return 0 + def _fortranEmitter(target, source, env): node = source[0].rfile() if not node.exists() and not node.is_derived(): @@ -75,16 +77,19 @@ def _fortranEmitter(target, source, env): target.append(env.fs.File(m, moddir)) return (target, source) + def FortranEmitter(target, source, env): import SCons.Defaults target, source = _fortranEmitter(target, source, env) return SCons.Defaults.StaticObjectEmitter(target, source, env) + def ShFortranEmitter(target, source, env): import SCons.Defaults target, source = _fortranEmitter(target, source, env) return SCons.Defaults.SharedObjectEmitter(target, source, env) + def ComputeFortranSuffixes(suffixes, ppsuffixes): """suffixes are fortran source files, and ppsuffixes the ones to be pre-processed. Both should be sequences, not strings.""" @@ -97,6 +102,7 @@ def ComputeFortranSuffixes(suffixes, ppsuffixes): else: suffixes.extend(upper_suffixes) + def CreateDialectActions(dialect): """Create dialect specific actions.""" CompAction = SCons.Action.Action('$%sCOM ' % dialect, '$%sCOMSTR' % dialect) @@ -106,7 +112,8 @@ def CreateDialectActions(dialect): return CompAction, CompPPAction, ShCompAction, ShCompPPAction -def DialectAddToEnv(env, dialect, suffixes, ppsuffixes, support_module = 0): + +def DialectAddToEnv(env, dialect, suffixes, ppsuffixes, support_module=False): """Add dialect specific construction variables.""" ComputeFortranSuffixes(suffixes, ppsuffixes) @@ -149,7 +156,7 @@ def DialectAddToEnv(env, dialect, suffixes, ppsuffixes, support_module = 0): env['_%sINCFLAGS' % dialect] = '${_concat(INC%sPREFIX, %sPATH, INC%sSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}' % (dialect, dialect, dialect) - if support_module == 1: + if support_module: env['%sCOM' % dialect] = '$%s -o $TARGET -c $%sFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect) env['%sPPCOM' % dialect] = '$%s -o $TARGET -c $%sFLAGS $CPPFLAGS $_CPPDEFFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect) env['SH%sCOM' % dialect] = '$SH%s -o $TARGET -c $SH%sFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect) @@ -174,7 +181,7 @@ def add_fortran_to_env(env): FortranPPSuffixes = ['.fpp', '.FPP'] DialectAddToEnv(env, "FORTRAN", FortranSuffixes, - FortranPPSuffixes, support_module = 1) + FortranPPSuffixes, support_module=True) env['FORTRANMODPREFIX'] = '' # like $LIBPREFIX env['FORTRANMODSUFFIX'] = '.mod' # like $LIBSUFFIX @@ -213,7 +220,8 @@ def add_f90_to_env(env): F90PPSuffixes = [] DialectAddToEnv(env, "F90", F90Suffixes, F90PPSuffixes, - support_module = 1) + support_module=True) + def add_f95_to_env(env): """Add Builders and construction variables for f95 to an Environment.""" @@ -229,7 +237,8 @@ def add_f95_to_env(env): F95PPSuffixes = [] DialectAddToEnv(env, "F95", F95Suffixes, F95PPSuffixes, - support_module = 1) + support_module=True) + def add_f03_to_env(env): """Add Builders and construction variables for f03 to an Environment.""" @@ -245,7 +254,8 @@ def add_f03_to_env(env): F03PPSuffixes = [] DialectAddToEnv(env, "F03", F03Suffixes, F03PPSuffixes, - support_module = 1) + support_module=True) + def add_f08_to_env(env): """Add Builders and construction variables for f08 to an Environment.""" @@ -260,7 +270,8 @@ def add_f08_to_env(env): F08PPSuffixes = [] DialectAddToEnv(env, "F08", F08Suffixes, F08PPSuffixes, - support_module = 1) + support_module=True) + def add_all_to_env(env): """Add builders and construction variables for all supported fortran diff --git a/SCons/Tool/ToolTests.py b/SCons/Tool/ToolTests.py index 9338c2d..59d093b 100644 --- a/SCons/Tool/ToolTests.py +++ b/SCons/Tool/ToolTests.py @@ -1,5 +1,6 @@ +# MIT License # -# __COPYRIGHT__ +# 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 @@ -19,9 +20,6 @@ # 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. -# - -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" import os import unittest @@ -99,14 +97,14 @@ class ToolTestCase(unittest.TestCase): def test_pathfind(self): - """Test that find_program_path() does not alter PATH""" + """Test that find_program_path() alters PATH only if add_path is true""" env = DummyEnvironment() PHONY_PATHS = [ r'C:\cygwin64\bin', r'C:\cygwin\bin', '/usr/local/dummy/bin', - env.PHONY_PATH, # will be recognized by dummy WhereIs + env.PHONY_PATH, # will be recognized by dummy WhereIs ] env['ENV'] = {} env['ENV']['PATH'] = '/usr/local/bin:/opt/bin:/bin:/usr/bin' @@ -114,6 +112,9 @@ class ToolTestCase(unittest.TestCase): _ = SCons.Tool.find_program_path(env, 'no_tool', default_paths=PHONY_PATHS) assert env['ENV']['PATH'] == pre_path, env['ENV']['PATH'] + _ = SCons.Tool.find_program_path(env, 'no_tool', default_paths=PHONY_PATHS, add_path=True) + assert env.PHONY_PATH in env['ENV']['PATH'], env['ENV']['PATH'] + if __name__ == "__main__": loader = unittest.TestLoader() diff --git a/SCons/Tool/__init__.py b/SCons/Tool/__init__.py index eda9b0d..8e4a22d 100644 --- a/SCons/Tool/__init__.py +++ b/SCons/Tool/__init__.py @@ -21,13 +21,10 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""SCons.Tool +"""SCons tool selection. -SCons tool selection. - -This looks for modules that define a callable object that can modify -a construction environment as appropriate for a given tool (or tool -chain). +Looks for modules that define a callable object that can modify a +construction environment as appropriate for a given tool (or tool chain). Note that because this subsystem just *selects* a callable that can modify a construction environment, it's possible for people to define @@ -36,8 +33,6 @@ one needs to use or tie in to this subsystem in order to roll their own tool specifications. """ - - import sys import os import importlib.util @@ -834,15 +829,17 @@ def tool_list(platform, env): return [x for x in tools if x] -def find_program_path(env, key_program, default_paths=None): +def find_program_path(env, key_program, default_paths=None, add_path=False) -> str: """ Find the location of a tool using various means. Mainly for windows where tools aren't all installed in /usr/bin, etc. - :param env: Current Construction Environment. - :param key_program: Tool to locate. - :param default_paths: List of additional paths this tool might be found in. + Args: + env: Current Construction Environment. + key_program: Tool to locate. + default_paths: List of additional paths this tool might be found in. + add_path: If true, add path found if it was from *default_paths*. """ # First search in the SCons path path = env.WhereIs(key_program) @@ -854,15 +851,22 @@ def find_program_path(env, key_program, default_paths=None): if path: return path - # Finally, add the defaults and check again. Do not change - # ['ENV']['PATH'] permananetly, the caller can do that if needed. + # Finally, add the defaults and check again. if default_paths is None: return path + save_path = env['ENV']['PATH'] for p in default_paths: env.AppendENVPath('PATH', p) path = env.WhereIs(key_program) + + # By default, do not change ['ENV']['PATH'] permananetly + # leave that to the caller, unless add_path is true. env['ENV']['PATH'] = save_path + if path and add_path: + bin_dir = os.path.dirname(path) + env.AppendENVPath('PATH', bin_dir) + return path # Local Variables: diff --git a/SCons/Tool/fortran.py b/SCons/Tool/fortran.py index 9a095ae..d21b930 100644 --- a/SCons/Tool/fortran.py +++ b/SCons/Tool/fortran.py @@ -32,6 +32,7 @@ from SCons.Tool.FortranCommon import add_all_to_env, add_fortran_to_env compilers = ['f95', 'f90', 'f77'] + def generate(env): add_all_to_env(env) add_fortran_to_env(env) @@ -41,6 +42,7 @@ def generate(env): if 'SHFORTRAN' not in env: env['SHFORTRAN'] = '$FORTRAN' + def exists(env): return env.Detect(compilers) diff --git a/SCons/Tool/g77.py b/SCons/Tool/g77.py index 764235e..e61181e 100644 --- a/SCons/Tool/g77.py +++ b/SCons/Tool/g77.py @@ -1,15 +1,6 @@ -"""SCons.Tool.g77 - -Tool-specific initialization for g77. - -There normally shouldn't be any need to import this module directly. -It will usually be imported through the generic SCons.Tool.Tool() -selection method. - -""" - +# MIT License # -# __COPYRIGHT__ +# 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 @@ -29,15 +20,23 @@ selection method. # 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. -# -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +""" + +Tool-specific initialization for g77. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" import SCons.Util from SCons.Tool.FortranCommon import add_all_to_env, add_f77_to_env compilers = ['g77', 'f77'] + def generate(env): """Add Builders and construction variables for g77 to an Environment.""" add_all_to_env(env) @@ -63,6 +62,7 @@ def generate(env): env['INCF77PREFIX'] = "-I" env['INCF77SUFFIX'] = "" + def exists(env): return env.Detect(compilers) diff --git a/SCons/Tool/gfortran.py b/SCons/Tool/gfortran.py index 45750d6..c4ca295 100644 --- a/SCons/Tool/gfortran.py +++ b/SCons/Tool/gfortran.py @@ -1,16 +1,6 @@ -"""SCons.Tool.gfortran - -Tool-specific initialization for gfortran, the GNU Fortran 95/Fortran -2003 compiler. - -There normally shouldn't be any need to import this module directly. -It will usually be imported through the generic SCons.Tool.Tool() -selection method. - -""" - +# MIT License # -# __COPYRIGHT__ +# 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 @@ -30,14 +20,24 @@ selection method. # 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. -# -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +""" + +Tool-specific initialization for gfortran, the GNU Fortran 95/Fortran +2003 compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + import SCons.Util from . import fortran + def generate(env): """Add Builders and construction variables for gfortran to an Environment.""" @@ -56,6 +56,7 @@ def generate(env): env['FORTRANMODDIRPREFIX'] = "-J" + def exists(env): return env.Detect('gfortran') diff --git a/SCons/Tool/ifort.py b/SCons/Tool/ifort.py index 638bd12..c8a6035 100644 --- a/SCons/Tool/ifort.py +++ b/SCons/Tool/ifort.py @@ -1,16 +1,6 @@ -"""SCons.Tool.ifort - -Tool-specific initialization for newer versions of the Intel Fortran Compiler -for Linux/Windows (and possibly Mac OS X). - -There normally shouldn't be any need to import this module directly. -It will usually be imported through the generic SCons.Tool.Tool() -selection method. - -""" - +# MIT License # -# __COPYRIGHT__ +# 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 @@ -30,14 +20,24 @@ selection method. # 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. -# -__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" +""" + +Tool-specific initialization for newer versions of the Intel Fortran Compiler +for Linux/Windows (and possibly Mac OS X). + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + import SCons.Defaults from SCons.Scanner.Fortran import FortranScan from .FortranCommon import add_all_to_env + def generate(env): """Add Builders and construction variables for ifort to an Environment.""" # ifort supports Fortran 90 and Fortran 95 @@ -78,6 +78,7 @@ def generate(env): else: env['FORTRANMODDIRPREFIX'] = "-module " + def exists(env): return env.Detect('ifort') diff --git a/SCons/Tool/ninja/NinjaState.py b/SCons/Tool/ninja/NinjaState.py index c168c7f..5e7c289 100644 --- a/SCons/Tool/ninja/NinjaState.py +++ b/SCons/Tool/ninja/NinjaState.py @@ -29,6 +29,8 @@ import signal import tempfile import shutil import sys +import random +import filecmp from os.path import splitext from tempfile import NamedTemporaryFile import ninja @@ -42,7 +44,7 @@ from .Globals import COMMAND_TYPES, NINJA_RULES, NINJA_POOLS, \ NINJA_CUSTOM_HANDLERS, NINJA_DEFAULT_TARGETS from .Rules import _install_action_function, _mkdir_action_function, _lib_symlink_action_function, _copy_action_function from .Utils import get_path, alias_to_ninja_build, generate_depfile, ninja_noop, get_order_only, \ - get_outputs, get_inputs, get_dependencies, get_rule, get_command_env, to_escaped_list + get_outputs, get_inputs, get_dependencies, get_rule, get_command_env, to_escaped_list, ninja_sorted_build from .Methods import get_command @@ -82,7 +84,26 @@ class NinjaState: # to make the SCONS_INVOCATION variable properly quoted for things # like CCFLAGS scons_escape = env.get("ESCAPE", lambda x: x) - scons_daemon_port = int(env.get('NINJA_SCONS_DAEMON_PORT',-1)) + + # The daemon port should be the same across runs, unless explicitly set + # or if the portfile is deleted. This ensures the ninja file is deterministic + # across regen's if nothings changed. The construction var should take preference, + # then portfile is next, and then otherwise create a new random port to persist in + # use. + scons_daemon_port = None + os.makedirs(get_path(self.env.get("NINJA_DIR")), exist_ok=True) + scons_daemon_port_file = str(pathlib.Path(get_path(self.env.get("NINJA_DIR"))) / "scons_daemon_portfile") + + if env.get('NINJA_SCONS_DAEMON_PORT') is not None: + scons_daemon_port = int(env.get('NINJA_SCONS_DAEMON_PORT')) + elif os.path.exists(scons_daemon_port_file): + with open(scons_daemon_port_file) as f: + scons_daemon_port = int(f.read()) + else: + scons_daemon_port = random.randint(10000, 60000) + + with open(scons_daemon_port_file, 'w') as f: + f.write(str(scons_daemon_port)) # if SCons was invoked from python, we expect the first arg to be the scons.py # script, otherwise scons was invoked from the scons script @@ -214,6 +235,12 @@ class NinjaState: "pool": "local_pool", "restat": 1 }, + "EXIT_SCONS_DAEMON": { + "command": "$PYTHON_BIN $NINJA_TOOL_DIR/ninja_daemon_build.py $PORT $NINJA_DIR_PATH --exit", + "description": "Shutting down ninja scons daemon server", + "pool": "local_pool", + "restat": 1 + }, "SCONS": { "command": "$SCONS_INVOCATION $out", "description": "$SCONS_INVOCATION $out", @@ -381,13 +408,13 @@ class NinjaState: ninja.variable("builddir", get_path(self.env.Dir(self.env['NINJA_DIR']).path)) - for pool_name, size in self.pools.items(): + for pool_name, size in sorted(self.pools.items()): ninja.pool(pool_name, min(self.env.get('NINJA_MAX_JOBS', size), size)) - for var, val in self.variables.items(): + for var, val in sorted(self.variables.items()): ninja.variable(var, val) - for rule, kwargs in self.rules.items(): + for rule, kwargs in sorted(self.rules.items()): if self.env.get('NINJA_MAX_JOBS') is not None and 'pool' not in kwargs: kwargs['pool'] = 'local_pool' ninja.rule(rule, **kwargs) @@ -529,8 +556,9 @@ class NinjaState: ) if remaining_outputs: - ninja.build( - outputs=sorted(remaining_outputs), rule="phony", implicit=first_output, + ninja_sorted_build( + ninja, + outputs=remaining_outputs, rule="phony", implicit=first_output, ) build["outputs"] = first_output @@ -548,12 +576,18 @@ class NinjaState: if "inputs" in build: build["inputs"].sort() - ninja.build(**build) + ninja_sorted_build( + ninja, + **build + ) scons_daemon_dirty = str(pathlib.Path(get_path(self.env.get("NINJA_DIR"))) / "scons_daemon_dirty") for template_builder in template_builders: template_builder["implicit"] += [scons_daemon_dirty] - ninja.build(**template_builder) + ninja_sorted_build( + ninja, + **template_builder + ) # We have to glob the SCons files here to teach the ninja file # how to regenerate itself. We'll never see ourselves in the @@ -563,17 +597,19 @@ class NinjaState: ninja_file_path = self.env.File(self.ninja_file).path regenerate_deps = to_escaped_list(self.env, self.env['NINJA_REGENERATE_DEPS']) - ninja.build( - ninja_file_path, + ninja_sorted_build( + ninja, + outputs=ninja_file_path, rule="REGENERATE", implicit=regenerate_deps, variables={ - "self": ninja_file_path, + "self": ninja_file_path } ) - ninja.build( - regenerate_deps, + ninja_sorted_build( + ninja, + outputs=regenerate_deps, rule="phony", variables={ "self": ninja_file_path, @@ -584,8 +620,9 @@ class NinjaState: # If we ever change the name/s of the rules that include # compile commands (i.e. something like CC) we will need to # update this build to reflect that complete list. - ninja.build( - "compile_commands.json", + ninja_sorted_build( + ninja, + outputs="compile_commands.json", rule="CMD", pool="console", implicit=[str(self.ninja_file)], @@ -601,15 +638,22 @@ class NinjaState: }, ) - ninja.build( - "compiledb", rule="phony", implicit=["compile_commands.json"], + ninja_sorted_build( + ninja, + outputs="compiledb", rule="phony", implicit=["compile_commands.json"], ) - ninja.build( - ["run_ninja_scons_daemon_phony", scons_daemon_dirty], + ninja_sorted_build( + ninja, + outputs=["run_ninja_scons_daemon_phony", scons_daemon_dirty], rule="SCONS_DAEMON", ) + ninja.build( + "shutdown_ninja_scons_daemon_phony", + rule="EXIT_SCONS_DAEMON", + ) + if all_targets is None: # Look in SCons's list of DEFAULT_TARGETS, find the ones that @@ -620,39 +664,45 @@ class NinjaState: if len(all_targets) == 0: all_targets = ["phony_default"] - ninja.build( + ninja_sorted_build( + ninja, outputs=all_targets, rule="phony", ) ninja.default([self.ninja_syntax.escape_path(path) for path in sorted(all_targets)]) - daemon_dir = pathlib.Path(tempfile.gettempdir()) / ('scons_daemon_' + str(hashlib.md5(str(get_path(self.env["NINJA_DIR"])).encode()).hexdigest())) - pidfile = None - if os.path.exists(scons_daemon_dirty): - pidfile = scons_daemon_dirty - elif os.path.exists(daemon_dir / 'pidfile'): - pidfile = daemon_dir / 'pidfile' - - if pidfile: - with open(pidfile) as f: - pid = int(f.readline()) - try: - os.kill(pid, signal.SIGINT) - except OSError: - pass + with NamedTemporaryFile(delete=False, mode='w') as temp_ninja_file: + temp_ninja_file.write(content.getvalue()) + + if self.env.GetOption('skip_ninja_regen') and os.path.exists(ninja_file_path) and filecmp.cmp(temp_ninja_file.name, ninja_file_path): + os.unlink(temp_ninja_file.name) + else: + + daemon_dir = pathlib.Path(tempfile.gettempdir()) / ('scons_daemon_' + str(hashlib.md5(str(get_path(self.env["NINJA_DIR"])).encode()).hexdigest())) + pidfile = None + if os.path.exists(scons_daemon_dirty): + pidfile = scons_daemon_dirty + elif os.path.exists(daemon_dir / 'pidfile'): + pidfile = daemon_dir / 'pidfile' + + if pidfile: + with open(pidfile) as f: + pid = int(f.readline()) + try: + os.kill(pid, signal.SIGINT) + except OSError: + pass # wait for the server process to fully killed # TODO: update wait_for_process_to_die() to handle timeout and then catch exception # here and do something smart. wait_for_process_to_die(pid) - if os.path.exists(scons_daemon_dirty): - os.unlink(scons_daemon_dirty) + if os.path.exists(scons_daemon_dirty): + os.unlink(scons_daemon_dirty) - with NamedTemporaryFile(delete=False, mode='w') as temp_ninja_file: - temp_ninja_file.write(content.getvalue()) - shutil.move(temp_ninja_file.name, ninja_file_path) + shutil.move(temp_ninja_file.name, ninja_file_path) self.__generated = True diff --git a/SCons/Tool/ninja/Utils.py b/SCons/Tool/ninja/Utils.py index b2c3cd3..583301e 100644 --- a/SCons/Tool/ninja/Utils.py +++ b/SCons/Tool/ninja/Utils.py @@ -23,6 +23,7 @@ import os import shutil from os.path import join as joinpath +from collections import OrderedDict import SCons from SCons.Action import get_default_ENV, _string_from_cmd_list @@ -52,6 +53,15 @@ def ninja_add_command_line_options(): help='Disable ninja generation and build with scons even if tool is loaded. '+ 'Also used by ninja to build targets which only scons can build.') + AddOption('--skip-ninja-regen', + dest='skip_ninja_regen', + metavar='BOOL', + action="store_true", + default=False, + help='Allow scons to skip regeneration of the ninja file and restarting of the daemon. ' + + 'Care should be taken in cases where Glob is in use or SCons generated files are used in ' + + 'command lines.') + def is_valid_dependent_node(node): """ @@ -126,7 +136,7 @@ def get_dependencies(node, skip_sources=False): get_path(src_file(child)) for child in filter_ninja_nodes(node.children()) if child not in node.sources - ] + ] return [get_path(src_file(child)) for child in filter_ninja_nodes(node.children())] @@ -261,6 +271,19 @@ def ninja_noop(*_args, **_kwargs): """ return None +def ninja_recursive_sorted_dict(build): + sorted_dict = OrderedDict() + for key, val in sorted(build.items()): + if isinstance(val, dict): + sorted_dict[key] = ninja_recursive_sorted_dict(val) + else: + sorted_dict[key] = val + return sorted_dict + + +def ninja_sorted_build(ninja, **build): + sorted_dict = ninja_recursive_sorted_dict(build) + ninja.build(**sorted_dict) def get_command_env(env, target, source): """ diff --git a/SCons/Tool/ninja/__init__.py b/SCons/Tool/ninja/__init__.py index 04a7abb..f0bdee5 100644 --- a/SCons/Tool/ninja/__init__.py +++ b/SCons/Tool/ninja/__init__.py @@ -26,7 +26,7 @@ import importlib import os -import random +import traceback import subprocess import sys @@ -66,7 +66,12 @@ def ninja_builder(env, target, source): print("Generating:", str(target[0])) generated_build_ninja = target[0].get_abspath() - NINJA_STATE.generate() + try: + NINJA_STATE.generate() + except Exception: + raise SCons.Errors.BuildError( + errstr=f"ERROR: an exception occurred while generating the ninja file:\n{traceback.format_exc()}", + node=target) if env["PLATFORM"] == "win32": # TODO: Is this necessary as you set env variable in the ninja build file per target? @@ -87,7 +92,7 @@ def ninja_builder(env, target, source): if str(env.get("NINJA_DISABLE_AUTO_RUN")).lower() not in ['1', 'true']: num_jobs = env.get('NINJA_MAX_JOBS', env.GetOption("num_jobs")) - cmd += ['-j' + str(num_jobs)] + NINJA_CMDLINE_TARGETS + cmd += ['-j' + str(num_jobs)] + env.get('NINJA_CMD_ARGS', '').split() + NINJA_CMDLINE_TARGETS print(f"ninja will be run with command line targets: {' '.join(NINJA_CMDLINE_TARGETS)}") print("Executing:", str(' '.join(cmd))) @@ -125,6 +130,14 @@ def ninja_builder(env, target, source): sys.stdout.write("\n") +def options(opts): + """ + Add command line Variables for Ninja builder. + """ + opts.AddVariables( + ("NINJA_CMD_ARGS", "Arguments to pass to ninja"), + ) + def exists(env): """Enable if called.""" @@ -194,10 +207,9 @@ def generate(env): env["NINJA_ALIAS_NAME"] = env.get("NINJA_ALIAS_NAME", "generate-ninja") env['NINJA_DIR'] = env.Dir(env.get("NINJA_DIR", '#/.ninja')) env["NINJA_SCONS_DAEMON_KEEP_ALIVE"] = env.get("NINJA_SCONS_DAEMON_KEEP_ALIVE", 180000) - env["NINJA_SCONS_DAEMON_PORT"] = env.get('NINJA_SCONS_DAEMON_PORT', random.randint(10000, 60000)) if GetOption("disable_ninja"): - env.SConsignFile(os.path.join(str(env['NINJA_DIR']),'.ninja.sconsign')) + env.SConsignFile(os.path.join(str(env['NINJA_DIR']), '.ninja.sconsign')) # here we allow multiple environments to construct rules and builds # into the same ninja file @@ -457,7 +469,6 @@ def generate(env): # date-ness. SCons.Script.Main.BuildTask.needs_execute = lambda x: True - def ninja_Set_Default_Targets(env, tlist): """ Record the default targets if they were ever set by the user. Ninja @@ -493,5 +504,5 @@ def generate(env): env['TEMPFILEDIR'] = "$NINJA_DIR/.response_files" env["TEMPFILE"] = NinjaNoResponseFiles - env.Alias('run-ninja-scons-daemon', 'run_ninja_scons_daemon_phony') + env.Alias('shutdown-ninja-scons-daemon', 'shutdown_ninja_scons_daemon_phony') diff --git a/SCons/Tool/ninja/ninja.xml b/SCons/Tool/ninja/ninja.xml index 6b247d0..664d521 100644 --- a/SCons/Tool/ninja/ninja.xml +++ b/SCons/Tool/ninja/ninja.xml @@ -77,7 +77,7 @@ See its __doc__ string for a discussion of the format. <item>IMPLICIT_COMMAND_DEPENDENCIES</item> <item>NINJA_SCONS_DAEMON_KEEP_ALIVE</item> <item>NINJA_SCONS_DAEMON_PORT</item> - + <item>NINJA_CMD_ARGS</item> <!-- TODO: Document these --> <!-- <item>NINJA_RULES</item>--> @@ -395,5 +395,22 @@ python -m pip install ninja </summary> </cvar> + <cvar name="NINJA_CMD_ARGS"> + <summary> + <para> + A string which will pass arguments through SCons to the ninja command when scons executes ninja. + Has no effect if &cv-NINJA_DISABLE_AUTO_RUN; is set. + </para> + <para> + This value can also be passed on the command line: + </para> + <example_commands> +scons NINJA_CMD_ARGS=-v +or +scons NINJA_CMD_ARGS="-v -j 3" + </example_commands> + </summary> + </cvar> + </sconsdoc> diff --git a/SCons/Tool/ninja/ninja_daemon_build.py b/SCons/Tool/ninja/ninja_daemon_build.py index 48156f5..32c375d 100644 --- a/SCons/Tool/ninja/ninja_daemon_build.py +++ b/SCons/Tool/ninja/ninja_daemon_build.py @@ -54,17 +54,30 @@ logging.basicConfig( level=logging.DEBUG, ) + def log_error(msg): logging.debug(msg) sys.stderr.write(msg) + while True: try: + if not os.path.exists(daemon_dir / "pidfile"): + if sys.argv[3] != '--exit': + logging.debug(f"ERROR: Server pid not found {daemon_dir / 'pidfile'} for request {sys.argv[3]}") + exit(1) + else: + logging.debug("WARNING: Unnecessary request to shutdown server, it's already shutdown.") + exit(0) + logging.debug(f"Sending request: {sys.argv[3]}") conn = http.client.HTTPConnection( "127.0.0.1", port=int(sys.argv[1]), timeout=60 ) - conn.request("GET", "/?build=" + sys.argv[3]) + if sys.argv[3] == '--exit': + conn.request("GET", "/?exit=1") + else: + conn.request("GET", "/?build=" + sys.argv[3]) response = None while not response: @@ -81,6 +94,7 @@ while True: if status != 200: log_error(msg.decode("utf-8")) exit(1) + logging.debug(f"Request Done: {sys.argv[3]}") exit(0) diff --git a/SCons/Tool/ninja/ninja_scons_daemon.py b/SCons/Tool/ninja/ninja_scons_daemon.py index c4a1d11..6802af2 100644 --- a/SCons/Tool/ninja/ninja_scons_daemon.py +++ b/SCons/Tool/ninja/ninja_scons_daemon.py @@ -168,6 +168,7 @@ def daemon_thread_func(): te.start() daemon_ready = False + building_node = None startup_complete = False @@ -218,12 +219,14 @@ def daemon_thread_func(): except queue.Empty: break if "exit" in building_node: + daemon_log("input: " + "exit") p.stdin.write("exit\n".encode("utf-8")) p.stdin.flush() with building_cv: shared_state.finished_building += [building_node] daemon_ready = False - raise + shared_state.daemon_needs_to_shutdown = True + break else: input_command = "build " + building_node + "\n" diff --git a/SCons/Tool/packaging/__init__.py b/SCons/Tool/packaging/__init__.py index 68cedeb..6fe01c1 100644 --- a/SCons/Tool/packaging/__init__.py +++ b/SCons/Tool/packaging/__init__.py @@ -217,10 +217,11 @@ def generate(env): env['BUILDERS']['Package'] = Package env['BUILDERS']['Tag'] = Tag + def exists(env): return 1 -# XXX + def options(opts): opts.AddVariables( EnumVariable('PACKAGETYPE', |