summaryrefslogtreecommitdiffstats
path: root/SCons/Tool
diff options
context:
space:
mode:
authorJoseph Brill <48932340+jcbrill@users.noreply.github.com>2022-06-16 20:11:05 (GMT)
committerJoseph Brill <48932340+jcbrill@users.noreply.github.com>2022-06-16 20:11:05 (GMT)
commit126a34e27d9aa2837ad271b31162ffb02ec2290a (patch)
treed4efef8a883b880577646115367d7c4317cf43ae /SCons/Tool
parentac6045d37df4d53d73cbf33323b33141dbb67e58 (diff)
parent067e290d832e17158aeb068bddb39dc9ad46d5cc (diff)
downloadSCons-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.py27
-rw-r--r--SCons/Tool/ToolTests.py13
-rw-r--r--SCons/Tool/__init__.py32
-rw-r--r--SCons/Tool/fortran.py2
-rw-r--r--SCons/Tool/g77.py26
-rw-r--r--SCons/Tool/gfortran.py29
-rw-r--r--SCons/Tool/ifort.py29
-rw-r--r--SCons/Tool/ninja/NinjaState.py130
-rw-r--r--SCons/Tool/ninja/Utils.py25
-rw-r--r--SCons/Tool/ninja/__init__.py25
-rw-r--r--SCons/Tool/ninja/ninja.xml19
-rw-r--r--SCons/Tool/ninja/ninja_daemon_build.py16
-rw-r--r--SCons/Tool/ninja/ninja_scons_daemon.py5
-rw-r--r--SCons/Tool/packaging/__init__.py3
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',