summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.codecov.yml2
-rw-r--r--.travis.yml2
-rwxr-xr-xCHANGES.txt15
-rwxr-xr-xREADME-package.rst2
-rwxr-xr-xREADME.rst2
-rwxr-xr-xRELEASE.txt7
-rw-r--r--SCons/CacheDir.py9
-rw-r--r--SCons/Defaults.py49
-rw-r--r--SCons/Defaults.xml29
-rw-r--r--SCons/Environment.xml73
-rw-r--r--SCons/EnvironmentTests.py5
-rw-r--r--SCons/Node/__init__.py5
-rw-r--r--SCons/SConf.py3
-rw-r--r--SCons/Script/SConsOptions.py4
-rw-r--r--SCons/Tool/FortranCommon.py16
-rw-r--r--SCons/Tool/install.py11
-rw-r--r--SCons/Tool/linkCommon/SharedLibrary.py4
-rw-r--r--SCons/Tool/mingw.py2
-rw-r--r--SCons/Tool/ninja/Globals.py40
-rw-r--r--SCons/Tool/ninja/Methods.py268
-rw-r--r--SCons/Tool/ninja/NinjaState.py711
-rw-r--r--SCons/Tool/ninja/Overrides.py96
-rw-r--r--SCons/Tool/ninja/Rules.py81
-rw-r--r--SCons/Tool/ninja/Utils.py419
-rw-r--r--SCons/Tool/ninja/__init__.py450
-rw-r--r--SCons/Tool/ninja/ninja.xml326
-rw-r--r--SCons/Tool/swig.py24
-rw-r--r--SCons/__init__.py6
-rw-r--r--bin/SConsDoc.py9
-rw-r--r--doc/generated/builders.gen64
-rw-r--r--doc/generated/builders.mod4
-rw-r--r--doc/generated/examples/caching_ex-random_1.xml4
-rw-r--r--doc/generated/examples/troubleshoot_Dump_1.xml12
-rw-r--r--doc/generated/examples/troubleshoot_Dump_2.xml12
-rw-r--r--doc/generated/examples/troubleshoot_stacktrace_2.xml4
-rw-r--r--doc/generated/functions.gen882
-rw-r--r--doc/generated/tools.gen13
-rw-r--r--doc/generated/tools.mod2
-rw-r--r--doc/generated/variables.gen179
-rw-r--r--doc/generated/variables.mod26
-rw-r--r--doc/scons.mod1
-rw-r--r--doc/user/external.xml589
-rw-r--r--requirements.txt1
-rwxr-xr-xruntest.py19
-rw-r--r--test/AddOption/.exclude_tests4
-rw-r--r--test/AddOption/args-and-targets.py65
-rw-r--r--test/AddOption/multi-arg.py124
-rw-r--r--test/Install/rec-sub-dir.py66
-rw-r--r--test/SWIG/SWIGOUTDIR.py6
-rw-r--r--test/SWIG/generated_swigfile.py23
-rw-r--r--test/_CPPINCFLAGS.py6
-rw-r--r--test/ninja/build_libraries.py118
-rw-r--r--test/ninja/copy_function_command.py89
-rw-r--r--test/ninja/generate_and_build.py81
-rw-r--r--test/ninja/generate_and_build_cxx.py101
-rw-r--r--test/ninja/generate_source.py115
-rw-r--r--test/ninja/iterative_speedup.py244
-rw-r--r--test/ninja/multi_env.py98
-rw-r--r--test/ninja/ninja-fixture/bar.c10
-rw-r--r--test/ninja/ninja-fixture/foo.c10
-rw-r--r--test/ninja/ninja-fixture/test1.c21
-rw-r--r--test/ninja/ninja-fixture/test2.cpp16
-rw-r--r--test/ninja/ninja-fixture/test2.hpp9
-rw-r--r--test/ninja/ninja-fixture/test_impl.c20
-rw-r--r--test/ninja/ninja_test_sconscripts/sconstest.skip0
-rw-r--r--test/ninja/ninja_test_sconscripts/sconstruct_generate_and_build6
-rw-r--r--test/ninja/ninja_test_sconscripts/sconstruct_generate_and_build_cxx5
-rw-r--r--test/ninja/shell_command.py92
-rw-r--r--test/option/option--experimental.py7
-rw-r--r--testing/framework/TestCmd.py1
70 files changed, 4996 insertions, 823 deletions
diff --git a/.codecov.yml b/.codecov.yml
index 375b10f..512670b 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -19,7 +19,7 @@ coverage:
notify:
irc:
default:
- server: "chat.freenode.net#scons"
+ server: "irc.libera.chat#scons"
branches: master
threshold: null
message: "Coverage {{changed}} for {{owner}}/{{repo}}" # customize the message
diff --git a/.travis.yml b/.travis.yml
index 054945d..7b93019 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
dist: xenial
language: python
-# Used: travis encrypt "chat.freenode.net#scons" --add notifications.irc
+# Used: travis encrypt "irc.libera.chat#scons" --add notifications.irc
notifications:
irc:
secure: TTb+41Bj1qIUc6vj+kDqBME8H3lqXdAe1RWAjjz5hL7bzFah6qCBHNJn4DwzqYs6+Pwuwp+6wFy8hgmQttJnXve4h6GtjtvlWprDqxaC7RkFqMWFDBzDalgbB54Bi4+TTZmSJ1K/duI3LrDaN873nyn+2GBnj+3TiNtgURp1fsJMpPxXJzAsoC8UthEsbx0Zkoal/WF+IfsT2q1yQRmAwB9r/drbahx/FfL16r1QjDbI9y1fKvN5J3PirLUvxtHfuH1r8zq1vlLew2fvldgVRtFv7+Lsk2waG/eiRpMf94V5JWP1rNreV/i4AUbZaTLb3bkrhtvTjSKhvx69Ydm+ygXdRgWOD/KRgqpLNAfA+t/a2J1R++89svQI4dPBpQjlfua1elcDCFddeIslgnjDUPO23Y0o7tHAy8sWkwhTcZH1Wm42uJP6Z6tHTH6+dMLvvZpkq4RUKUcrXvoUvCsVlWMGjcsBX+AEQSFGDJnLtLehO9x0QbgVga/IRKjgpDWgQDZgro3AkGg/zzVj5uFRUoU+rbmEXq9feh5i3HfExAvA3UoEtnQ6uadDyWqtQcLRFmPSWDU82CO+sanGdFL0jBjigE8ubPObzxEAz3Fg1xk56OYBkAdEd+2KEzeO1nqJmrhsnc3c/3+b1cBvaL5ozW4XB4XcWsOi268SoiBrcBo=
diff --git a/CHANGES.txt b/CHANGES.txt
index 78d1c7b..6afd4c0 100755
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -9,6 +9,12 @@ NOTE: The 4.2.0 Release of SCons will drop Python 3.5 Support
RELEASE VERSION/DATE TO BE FILLED IN LATER
+ From Byron Platt:
+ - Fix Install() issue when copytree recursion gives bad arguments that can
+ lead to install side-effects including keeping dangling symlinks and
+ silently failing to copy directories (and their subdirectories) when the
+ directory already exists in the target.
+
From Joseph Brill:
- Internal MSVS update: Remove unnecessary calls to find all installed versions of msvc
when constructing the installed visual studios list.
@@ -29,6 +35,10 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- Fix versioned shared library naming for MacOS platform. (Previously was libxyz.dylib.1.2.3,
has been fixed to libxyz.1.2.3.dylib. Additionally the sonamed symlink had the same issue,
that is now resolved as well)
+ - Add experimental ninja builder. (Contributed by MongoDB, Daniel Moody and many others).
+ - Fix #3955 - _LIBDIRFLAGS leaving $( and $) in *COMSTR output. Added affect_signature flag to
+ _concat function. If set to False, it will prepend and append $( and $). That way the various
+ Environment variables can use that rather than "$( _concat(...) $)".
From David H:
- Fix Issue #3906 - `IMPLICIT_COMMAND_DEPENDENCIES` was not properly disabled when
@@ -56,6 +66,11 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- Update BuildTask to pass all targets to the progress object fixing an issue
where multi-target build nodes only got the first target passed to the progress
object.
+ - Fix a potential race condition in shared cache environments where the permissions are
+ not writeable for a moment after the file has been renamed and other builds (users) will copy
+ it out of the cacheSmall reorganization of logic to copy files from cachedir. Moved CacheDir
+ writeable permission code for copy to cache behind the atomic rename operation.
+
From Mats Wichmann:
- Initial support in tests for Python 3.10 - expected bytecode and
diff --git a/README-package.rst b/README-package.rst
index 4aaea1c..bc0e7ef 100755
--- a/README-package.rst
+++ b/README-package.rst
@@ -2,7 +2,7 @@ SCons - a software construction tool
####################################
.. image:: https://img.shields.io/badge/IRC-scons-blue.svg
- :target: http://webchat.freenode.net/?channels=%23scons&uio=d4
+ :target: https://web.libera.chat/#scons
:alt: IRC
.. image:: https://img.shields.io/sourceforge/dm/scons.svg
diff --git a/README.rst b/README.rst
index 219151c..cc14a01 100755
--- a/README.rst
+++ b/README.rst
@@ -2,7 +2,7 @@ SCons - a software construction tool
####################################
.. image:: https://img.shields.io/badge/IRC-scons-blue.svg
- :target: http://webchat.freenode.net/?channels=%23scons&uio=d4
+ :target: https://web.libera.chat/#scons
:alt: IRC
.. image:: https://img.shields.io/sourceforge/dm/scons.svg
diff --git a/RELEASE.txt b/RELEASE.txt
index 733061e..71536c0 100755
--- a/RELEASE.txt
+++ b/RELEASE.txt
@@ -14,6 +14,9 @@ NEW FUNCTIONALITY
Fixes #396
- Added --experimental flag, to enable various experimental features/tools. You can specify
'all', 'none', or any combination of available experimental features.
+ - Added affect_signature flag to _concat function. If set to False, it will prepend and append $( and $).
+ That way the various Environment variables can use that rather than "$( _concat(...) $)".
+
DEPRECATED FUNCTIONALITY
------------------------
@@ -66,6 +69,10 @@ FIXES
that regressed as of 4.0.0.
- Fix issue #3790: Generators in CPPDEFINES now have access to populated source
and target lists
+ - Fix a potential race condition in shared cache environments where the permissions are
+ not writeable for a moment after the file has been renamed and other builds (users) will copy
+ it out of the cacheSmall reorganization of logic to copy files from cachedir. Moved CacheDir
+ writeable permission code for copy to cache behind the atomic rename operation.
IMPROVEMENTS
diff --git a/SCons/CacheDir.py b/SCons/CacheDir.py
index f021751..7390f38 100644
--- a/SCons/CacheDir.py
+++ b/SCons/CacheDir.py
@@ -117,8 +117,7 @@ def CachePushFunc(target, source, env):
else:
cd.copy_to_cache(env, t.get_internal_path(), tempfile)
fs.rename(tempfile, cachefile)
- st = fs.stat(t.get_internal_path())
- fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
+
except EnvironmentError:
# It's possible someone else tried writing the file at the
# same time we did, or else that there was some problem like
@@ -220,7 +219,11 @@ class CacheDir:
@classmethod
def copy_to_cache(cls, env, src, dst):
try:
- return env.fs.copy2(src, dst)
+ result = env.fs.copy2(src, dst)
+ fs = env.File(src).fs
+ st = fs.stat(src)
+ fs.chmod(dst, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
+ return result
except AttributeError as ex:
raise EnvironmentError from ex
diff --git a/SCons/Defaults.py b/SCons/Defaults.py
index 95a2e52..3edfe7b 100644
--- a/SCons/Defaults.py
+++ b/SCons/Defaults.py
@@ -42,9 +42,9 @@ import SCons.Builder
import SCons.CacheDir
import SCons.Environment
import SCons.PathList
+import SCons.Scanner.Dir
import SCons.Subst
import SCons.Tool
-import SCons.Scanner.Dir
# A placeholder for a default Environment (for fetching source files
# from source code management systems and the like). This must be
@@ -94,13 +94,13 @@ def DefaultEnvironment(*args, **kw):
def StaticObjectEmitter(target, source, env):
for tgt in target:
tgt.attributes.shared = None
- return (target, source)
+ return target, source
def SharedObjectEmitter(target, source, env):
for tgt in target:
tgt.attributes.shared = 1
- return (target, source)
+ return target, source
def SharedFlagChecker(source, target, env):
@@ -291,7 +291,7 @@ def delete_func(dest, must_exist=0):
continue
# os.path.isdir returns True when entry is a link to a dir
if os.path.isdir(entry) and not os.path.islink(entry):
- shutil.rmtree(entry, 1)
+ shutil.rmtree(entry, True)
continue
os.unlink(entry)
@@ -312,7 +312,7 @@ def mkdir_func(dest):
Mkdir = ActionFactory(mkdir_func,
- lambda dir: 'Mkdir(%s)' % get_paths_str(dir))
+ lambda _dir: 'Mkdir(%s)' % get_paths_str(_dir))
def move_func(dest, src):
@@ -347,27 +347,38 @@ Touch = ActionFactory(touch_func,
# Internal utility functions
-
-def _concat(prefix, iter, suffix, env, f=lambda x: x, target=None, source=None):
+# pylint: disable-msg=too-many-arguments
+def _concat(prefix, items_iter, suffix, env, f=lambda x: x, target=None, source=None, affect_signature=True):
"""
- Creates a new list from 'iter' by first interpolating each element
+ Creates a new list from 'items_iter' by first interpolating each element
in the list using the 'env' dictionary and then calling f on the
list, and finally calling _concat_ixes to concatenate 'prefix' and
'suffix' onto each element of the list.
"""
- if not iter:
- return iter
- l = f(SCons.PathList.PathList(iter).subst_path(env, target, source))
+ if not items_iter:
+ return items_iter
+
+ l = f(SCons.PathList.PathList(items_iter).subst_path(env, target, source))
if l is not None:
- iter = l
+ items_iter = l
- return _concat_ixes(prefix, iter, suffix, env)
+ if not affect_signature:
+ value = ['$(']
+ else:
+ value = []
+ value += _concat_ixes(prefix, items_iter, suffix, env)
+ if not affect_signature:
+ value += ["$)"]
-def _concat_ixes(prefix, iter, suffix, env):
+ return value
+# pylint: enable-msg=too-many-arguments
+
+
+def _concat_ixes(prefix, items_iter, suffix, env):
"""
- Creates a new list from 'iter' by concatenating the 'prefix' and
+ Creates a new list from 'items_iter' by concatenating the 'prefix' and
'suffix' arguments onto each element of the list. A trailing space
on 'prefix' or leading space on 'suffix' will cause them to be put
into separate list elements rather than being concatenated.
@@ -379,7 +390,7 @@ def _concat_ixes(prefix, iter, suffix, env):
prefix = str(env.subst(prefix, SCons.Subst.SUBST_RAW))
suffix = str(env.subst(suffix, SCons.Subst.SUBST_RAW))
- for x in SCons.Util.flatten(iter):
+ for x in SCons.Util.flatten(items_iter):
if isinstance(x, SCons.Node.FS.File):
result.append(x)
continue
@@ -606,8 +617,10 @@ ConstructionEnvironment = {
'_defines': _defines,
'_stripixes': _stripixes,
'_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
- '_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
- '_CPPINCFLAGS': '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
+
+ '_LIBDIRFLAGS': '${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}',
+ '_CPPINCFLAGS': '${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}',
+
'_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__, TARGET, SOURCE)}',
'__libversionflags': __libversionflags,
diff --git a/SCons/Defaults.xml b/SCons/Defaults.xml
index 33d219b..27b0882 100644
--- a/SCons/Defaults.xml
+++ b/SCons/Defaults.xml
@@ -25,18 +25,23 @@ See its __doc__ string for a discussion of the format.
<cvar name ="_concat">
<summary>
-<para>
-A function used to produce variables like &cv-link-_CPPINCFLAGS;. It takes
-four or five
-arguments: a prefix to concatenate onto each element, a list of
-elements, a suffix to concatenate onto each element, an environment
-for variable interpolation, and an optional function that will be
-called to transform the list before concatenation.
-</para>
-
-<example_commands>
-env['_CPPINCFLAGS'] = '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs)} $)',
-</example_commands>
+ <para>
+ A function used to produce variables like &cv-link-_CPPINCFLAGS;. It takes
+ four mandatory arguments, and up to 4 additional optional arguments:
+ 1) a prefix to concatenate onto each element,
+ 2) a list of elements,
+ 3) a suffix to concatenate onto each element,
+ 4) an environment for variable interpolation,
+ 5) an optional function that will be called to transform the list before concatenation,
+ 6) an optionally specified target (Can use TARGET),
+ 7) an optionally specified source (Can use SOURCE),
+ 8) optional <parameter>affect_signature</parameter> flag which will wrap non-empty returned value with $( and $) to indicate the contents
+ should not affect the signature of the generated command line.
+ </para>
+
+ <example_commands>
+ env['_CPPINCFLAGS'] = '${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}'
+ </example_commands>
</summary>
</cvar>
diff --git a/SCons/Environment.xml b/SCons/Environment.xml
index 79af6bc..c2aab51 100644
--- a/SCons/Environment.xml
+++ b/SCons/Environment.xml
@@ -2168,12 +2168,12 @@ env.MergeShellPaths({'INCLUDE': ['c:/inc1', 'c:/inc2']}, prepend=0)
</arguments>
<summary>
<para>
-Merges the specified
+Merges values from
<parameter>arg</parameter>
-values to the construction environment's construction variables.
-If the
+into &consvars; in the current &consenv;.
+If
<parameter>arg</parameter>
-argument is not a dictionary,
+is not a dictionary,
it is converted to one by calling
&f-link-env-ParseFlags;
on the argument
@@ -2191,15 +2191,15 @@ not as separate arguments to
By default,
duplicate values are eliminated;
you can, however, specify
-<literal>unique=0</literal>
+<literal>unique=False</literal>
to allow duplicate
values to be added.
When eliminating duplicate values,
-any construction variables that end with
+any &consvars; that end with
the string
<literal>PATH</literal>
keep the left-most unique value.
-All other construction variables keep
+All other &consvars; keep
the right-most unique value.
</para>
@@ -2330,38 +2330,55 @@ NoClean(env.Program('hello', 'hello.c'))
</arguments>
<summary>
<para>
-Calls the specified
-<parameter>function</parameter>
-to modify the environment as specified by the output of
-<parameter>command</parameter>.
-The default
-<parameter>function</parameter>
-is
-&f-link-env-MergeFlags;,
-which expects the output of a typical
-<application>*-config</application>
-command
-(for example,
-<application>gtk-config</application>)
-and adds the options
-to the appropriate construction variables.
+Updates the current &consenv; with the values extracted
+from the output from running external <parameter>command</parameter>,
+by calling a helper function <parameter>function</parameter>
+which understands
+the output of <parameter>command</parameter>.
+<parameter>command</parameter> may be a string
+or a list of strings representing the command and
+its arguments.
+If <parameter>function</parameter>
+is not given,
+&f-link-env-MergeFlags; is used.
By default,
duplicate values are not
added to any construction variables;
you can specify
-<literal>unique=0</literal>
-to allow duplicate
-values to be added.
+<parameter>unique=False</parameter>
+to allow duplicate values to be added.
</para>
<para>
+If &f-env-MergeFlags; is used,
+it expects a response in the style of a
+<command>*-config</command>
+command typical of the POSIX programming environment
+(for example,
+<application>gtk-config</application>)
+and adds the options
+to the appropriate construction variables.
Interpreted options
and the construction variables they affect
are as specified for the
&f-link-env-ParseFlags;
-method (which this method calls).
+method (which
+&f-env-MergeFlags; calls).
See that method's description
-for a table of options and construction variables.
+for a table of options and corresponding construction variables.
+</para>
+
+<para>
+If &f-env-MergeFlags; cannot interpret the results of
+<parameter>command</parameter>,
+you can suppply a custom
+<parameter>function</parameter> to do so.
+<parameter>function</parameter>
+must accept three arguments:
+the &consenv; to modify, the string returned
+by running <parameter>command</parameter>,
+and the optional
+<parameter>unique</parameter> flag.
</para>
</summary>
</scons_function>
@@ -2452,7 +2469,7 @@ before merging them into the construction environment.
&f-env-MergeFlags;
will call this method if its argument is not a dictionary,
so it is usually not necessary to call
-&f-link-env-ParseFlags;
+&f-env-ParseFlags;
directly unless you want to manipulate the values.)
</para>
diff --git a/SCons/EnvironmentTests.py b/SCons/EnvironmentTests.py
index 78f6ac2..2ed897a 100644
--- a/SCons/EnvironmentTests.py
+++ b/SCons/EnvironmentTests.py
@@ -143,7 +143,7 @@ class DummyNode:
return self
def test_tool( env ):
- env['_F77INCFLAGS'] = '$( ${_concat(INCPREFIX, F77PATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)'
+ env['_F77INCFLAGS'] = '${_concat(INCPREFIX, F77PATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}'
class TestEnvironmentFixture:
def TestEnvironment(self, *args, **kw):
@@ -1488,6 +1488,9 @@ def exists(env):
assert x == 'prea bsuf', x
x = s("${_concat(PRE, LIST, SUF, __env__)}")
assert x == 'preasuf prebsuf', x
+ x = s("${_concat(PRE, LIST, SUF, __env__,affect_signature=False)}", raw=True)
+ assert x == '$( preasuf prebsuf $)', x
+
def test_concat_nested(self):
"""Test _concat() on a nested substitution strings."""
diff --git a/SCons/Node/__init__.py b/SCons/Node/__init__.py
index a449082..ec742a6 100644
--- a/SCons/Node/__init__.py
+++ b/SCons/Node/__init__.py
@@ -946,6 +946,11 @@ class Node(object, metaclass=NoSlotsPyPy):
return False
return True
+ def check_attributes(self, name):
+ """ Simple API to check if the node.attributes for name has been set"""
+ return getattr(getattr(self, "attributes", None), name, None)
+
+
def alter_targets(self):
"""Return a list of alternate targets for this Node.
"""
diff --git a/SCons/SConf.py b/SCons/SConf.py
index 68e890a..38a2b94 100644
--- a/SCons/SConf.py
+++ b/SCons/SConf.py
@@ -606,7 +606,7 @@ class SConfBase:
f = "_".join([f, textSig, textSigCounter])
textFile = self.confdir.File(f + extension)
- self._set_conftest_node(sourcetext)
+ self._set_conftest_node(textFile)
textFileNode = self.env.SConfSourceBuilder(target=textFile,
source=sourcetext)
nodesToBeBuilt.extend(textFileNode)
@@ -625,6 +625,7 @@ class SConfBase:
pref = self.env.subst( builder.builder.prefix )
suff = self.env.subst( builder.builder.suffix )
target = self.confdir.File(pref + f + suff)
+ self._set_conftest_node(target)
try:
# Slide our wrapper into the construction environment as
diff --git a/SCons/Script/SConsOptions.py b/SCons/Script/SConsOptions.py
index 4988a8d..def8663 100644
--- a/SCons/Script/SConsOptions.py
+++ b/SCons/Script/SConsOptions.py
@@ -39,7 +39,7 @@ SUPPRESS_HELP = optparse.SUPPRESS_HELP
diskcheck_all = SCons.Node.FS.diskcheck_types()
-experimental_features = {'warp_speed', 'transporter'}
+experimental_features = {'warp_speed', 'transporter', 'ninja'}
def diskcheck_convert(value):
@@ -749,7 +749,7 @@ def Parser(version):
op.add_option('--experimental',
dest='experimental',
action='callback',
- default={}, # empty set
+ default=set(), # empty set
type='str',
# choices=experimental_options+experimental_features,
callback =experimental_callback,
diff --git a/SCons/Tool/FortranCommon.py b/SCons/Tool/FortranCommon.py
index 16b75e2..a73de5d 100644
--- a/SCons/Tool/FortranCommon.py
+++ b/SCons/Tool/FortranCommon.py
@@ -1,9 +1,3 @@
-"""SCons.Tool.FortranCommon
-
-Stuff for processing Fortran, common to all fortran dialects.
-
-"""
-
# MIT License
#
# Copyright The SCons Foundation
@@ -25,7 +19,12 @@ Stuff for processing Fortran, common to all fortran dialects.
# 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.
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""SCons.Tool.FortranCommon
+
+Stuff for processing Fortran, common to all fortran dialects.
+
+"""
import re
import os.path
@@ -35,6 +34,7 @@ import SCons.Scanner.Fortran
import SCons.Tool
import SCons.Util
+
def isfortran(env, source):
"""Return 1 if any of code in source has fortran files in it, 0
otherwise."""
@@ -147,7 +147,7 @@ def DialectAddToEnv(env, dialect, suffixes, ppsuffixes, support_module = 0):
if 'INC%sSUFFIX' % dialect not in env:
env['INC%sSUFFIX' % dialect] = '$INCSUFFIX'
- env['_%sINCFLAGS' % dialect] = '$( ${_concat(INC%sPREFIX, %sPATH, INC%sSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)' % (dialect, dialect, dialect)
+ env['_%sINCFLAGS' % dialect] = '${_concat(INC%sPREFIX, %sPATH, INC%sSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}' % (dialect, dialect, dialect)
if support_module == 1:
env['%sCOM' % dialect] = '$%s -o $TARGET -c $%sFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect)
diff --git a/SCons/Tool/install.py b/SCons/Tool/install.py
index 59b4a52..97dd846 100644
--- a/SCons/Tool/install.py
+++ b/SCons/Tool/install.py
@@ -122,12 +122,17 @@ def scons_copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
continue
# otherwise let the copy occurs. copy2 will raise an error
if os.path.isdir(srcname):
- scons_copytree(srcname, dstname, symlinks, ignore,
- copy_function, dirs_exist_ok)
+ scons_copytree(srcname, dstname, symlinks=symlinks,
+ ignore=ignore, copy_function=copy_function,
+ ignore_dangling_symlinks=ignore_dangling_symlinks,
+ dirs_exist_ok=dirs_exist_ok)
else:
copy_function(srcname, dstname)
elif os.path.isdir(srcname):
- scons_copytree(srcname, dstname, symlinks, ignore, copy_function, dirs_exist_ok)
+ scons_copytree(srcname, dstname, symlinks=symlinks,
+ ignore=ignore, copy_function=copy_function,
+ ignore_dangling_symlinks=ignore_dangling_symlinks,
+ dirs_exist_ok=dirs_exist_ok)
else:
# Will raise a SpecialFileError for unsupported file types
copy_function(srcname, dstname)
diff --git a/SCons/Tool/linkCommon/SharedLibrary.py b/SCons/Tool/linkCommon/SharedLibrary.py
index 6a12dd4..d18aa6b 100644
--- a/SCons/Tool/linkCommon/SharedLibrary.py
+++ b/SCons/Tool/linkCommon/SharedLibrary.py
@@ -208,11 +208,11 @@ def setup_shared_lib_logic(env):
env["SHLIBEMITTER"] = [lib_emitter, shlib_symlink_emitter]
# If it's already set, then don't overwrite.
- env["SHLIBPREFIX"] = env.get('SHLIBPREFIX',"lib")
+ env["SHLIBPREFIX"] = env.get('SHLIBPREFIX', "lib")
env["_SHLIBSUFFIX"] = "${SHLIBSUFFIX}${_SHLIBVERSION}"
env["SHLINKFLAGS"] = CLVar("$LINKFLAGS -shared")
env["SHLINKCOM"] = "$SHLINK -o $TARGET $SHLINKFLAGS $__SHLIBVERSIONFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS"
- env["SHLINKCOMSTR"] = "$SHLINKCOM"
+
env["SHLINK"] = "$LINK"
diff --git a/SCons/Tool/mingw.py b/SCons/Tool/mingw.py
index 2df3c3b..0d2bd6d 100644
--- a/SCons/Tool/mingw.py
+++ b/SCons/Tool/mingw.py
@@ -180,7 +180,7 @@ def generate(env):
env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
env['RC'] = 'windres'
env['RCFLAGS'] = SCons.Util.CLVar('')
- env['RCINCFLAGS'] = '$( ${_concat(RCINCPREFIX, CPPPATH, RCINCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)'
+ env['RCINCFLAGS'] = '${_concat(RCINCPREFIX, CPPPATH, RCINCSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}'
env['RCINCPREFIX'] = '--include-dir '
env['RCINCSUFFIX'] = ''
env['RCCOM'] = '$RC $_CPPDEFFLAGS $RCINCFLAGS ${RCINCPREFIX} ${SOURCE.dir} $RCFLAGS -i $SOURCE -o $TARGET'
diff --git a/SCons/Tool/ninja/Globals.py b/SCons/Tool/ninja/Globals.py
new file mode 100644
index 0000000..0dc46ea
--- /dev/null
+++ b/SCons/Tool/ninja/Globals.py
@@ -0,0 +1,40 @@
+# 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.
+
+import SCons.Action
+
+NINJA_RULES = "__NINJA_CUSTOM_RULES"
+NINJA_POOLS = "__NINJA_CUSTOM_POOLS"
+NINJA_CUSTOM_HANDLERS = "__NINJA_CUSTOM_HANDLERS"
+NINJA_BUILD = "NINJA_BUILD"
+NINJA_WHEREIS_MEMO = {}
+NINJA_STAT_MEMO = {}
+__NINJA_RULE_MAPPING = {}
+
+
+# These are the types that get_command can do something with
+COMMAND_TYPES = (
+ SCons.Action.CommandAction,
+ SCons.Action.CommandGeneratorAction,
+)
+ninja_builder_initialized = False \ No newline at end of file
diff --git a/SCons/Tool/ninja/Methods.py b/SCons/Tool/ninja/Methods.py
new file mode 100644
index 0000000..ef8e44f
--- /dev/null
+++ b/SCons/Tool/ninja/Methods.py
@@ -0,0 +1,268 @@
+# 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.
+
+import os
+import shlex
+import textwrap
+
+import SCons
+from SCons.Tool.ninja import NINJA_CUSTOM_HANDLERS, NINJA_RULES, NINJA_POOLS
+from SCons.Tool.ninja.Globals import __NINJA_RULE_MAPPING
+from SCons.Tool.ninja.Utils import get_targets_sources, get_dependencies, get_order_only, get_outputs, get_inputs, \
+ get_rule, get_path, generate_command, get_command_env, get_comstr
+
+
+def register_custom_handler(env, name, handler):
+ """Register a custom handler for SCons function actions."""
+ env[NINJA_CUSTOM_HANDLERS][name] = handler
+
+
+def register_custom_rule_mapping(env, pre_subst_string, rule):
+ """Register a function to call for a given rule."""
+ SCons.Tool.ninja.Globals.__NINJA_RULE_MAPPING[pre_subst_string] = rule
+
+
+def register_custom_rule(env, rule, command, description="", deps=None, pool=None, use_depfile=False, use_response_file=False, response_file_content="$rspc"):
+ """Allows specification of Ninja rules from inside SCons files."""
+ rule_obj = {
+ "command": command,
+ "description": description if description else "{} $out".format(rule),
+ }
+
+ if use_depfile:
+ rule_obj["depfile"] = os.path.join(get_path(env['NINJA_DIR']), '$out.depfile')
+
+ if deps is not None:
+ rule_obj["deps"] = deps
+
+ if pool is not None:
+ rule_obj["pool"] = pool
+
+ if use_response_file:
+ rule_obj["rspfile"] = "$out.rsp"
+ rule_obj["rspfile_content"] = response_file_content
+
+ env[NINJA_RULES][rule] = rule_obj
+
+
+def register_custom_pool(env, pool, size):
+ """Allows the creation of custom Ninja pools"""
+ env[NINJA_POOLS][pool] = size
+
+
+def set_build_node_callback(env, node, callback):
+ if not node.is_conftest():
+ node.attributes.ninja_build_callback = callback
+
+
+def get_generic_shell_command(env, node, action, targets, sources, executor=None):
+ return (
+ "GENERATED_CMD",
+ {
+ "cmd": generate_command(env, node, action, targets, sources, executor=executor),
+ "env": get_command_env(env),
+ },
+ # Since this function is a rule mapping provider, it must return a list of dependencies,
+ # and usually this would be the path to a tool, such as a compiler, used for this rule.
+ # However this function is to generic to be able to reliably extract such deps
+ # from the command, so we return a placeholder empty list. It should be noted that
+ # generally this function will not be used solely and is more like a template to generate
+ # the basics for a custom provider which may have more specific options for a provider
+ # function for a custom NinjaRuleMapping.
+ []
+ )
+
+
+def CheckNinjaCompdbExpand(env, context):
+ """ Configure check testing if ninja's compdb can expand response files"""
+
+ # TODO: When would this be false?
+ context.Message('Checking if ninja compdb can expand response files... ')
+ ret, output = context.TryAction(
+ action='ninja -f $SOURCE -t compdb -x CMD_RSP > $TARGET',
+ extension='.ninja',
+ text=textwrap.dedent("""
+ rule CMD_RSP
+ command = $cmd @$out.rsp > fake_output.txt
+ description = Building $out
+ rspfile = $out.rsp
+ rspfile_content = $rspc
+ build fake_output.txt: CMD_RSP fake_input.txt
+ cmd = echo
+ pool = console
+ rspc = "test"
+ """))
+ result = '@fake_output.txt.rsp' not in output
+ context.Result(result)
+ return result
+
+
+def get_command(env, node, action): # pylint: disable=too-many-branches
+ """Get the command to execute for node."""
+ if node.env:
+ sub_env = node.env
+ else:
+ sub_env = env
+ executor = node.get_executor()
+ tlist, slist = get_targets_sources(node)
+
+ # Generate a real CommandAction
+ if isinstance(action, SCons.Action.CommandGeneratorAction):
+ # pylint: disable=protected-access
+ action = action._generate(tlist, slist, sub_env, 1, executor=executor)
+
+ variables = {}
+
+ comstr = get_comstr(sub_env, action, tlist, slist)
+ if not comstr:
+ return None
+
+ provider = __NINJA_RULE_MAPPING.get(comstr, get_generic_shell_command)
+ rule, variables, provider_deps = provider(sub_env, node, action, tlist, slist, executor=executor)
+
+ # Get the dependencies for all targets
+ implicit = list({dep for tgt in tlist for dep in get_dependencies(tgt)})
+
+ # Now add in the other dependencies related to the command,
+ # e.g. the compiler binary. The ninja rule can be user provided so
+ # we must do some validation to resolve the dependency path for ninja.
+ for provider_dep in provider_deps:
+
+ provider_dep = sub_env.subst(provider_dep)
+ if not provider_dep:
+ continue
+
+ # If the tool is a node, then SCons will resolve the path later, if its not
+ # a node then we assume it generated from build and make sure it is existing.
+ if isinstance(provider_dep, SCons.Node.Node) or os.path.exists(provider_dep):
+ implicit.append(provider_dep)
+ continue
+
+ # in some case the tool could be in the local directory and be supplied without the ext
+ # such as in windows, so append the executable suffix and check.
+ prog_suffix = sub_env.get('PROGSUFFIX', '')
+ provider_dep_ext = provider_dep if provider_dep.endswith(prog_suffix) else provider_dep + prog_suffix
+ if os.path.exists(provider_dep_ext):
+ implicit.append(provider_dep_ext)
+ continue
+
+ # Many commands will assume the binary is in the path, so
+ # we accept this as a possible input from a given command.
+
+ provider_dep_abspath = sub_env.WhereIs(provider_dep) or sub_env.WhereIs(provider_dep, path=os.environ["PATH"])
+ if provider_dep_abspath:
+ implicit.append(provider_dep_abspath)
+ continue
+
+ # Possibly these could be ignore and the build would still work, however it may not always
+ # rebuild correctly, so we hard stop, and force the user to fix the issue with the provided
+ # ninja rule.
+ raise Exception("Could not resolve path for %s dependency on node '%s'" % (provider_dep, node))
+
+ ninja_build = {
+ "order_only": get_order_only(node),
+ "outputs": get_outputs(node),
+ "inputs": get_inputs(node),
+ "implicit": implicit,
+ "rule": get_rule(node, rule),
+ "variables": variables,
+ }
+
+ # Don't use sub_env here because we require that NINJA_POOL be set
+ # on a per-builder call basis to prevent accidental strange
+ # behavior like env['NINJA_POOL'] = 'console' and sub_env can be
+ # the global Environment object if node.env is None.
+ # Example:
+ #
+ # Allowed:
+ #
+ # env.Command("ls", NINJA_POOL="ls_pool")
+ #
+ # Not allowed and ignored:
+ #
+ # env["NINJA_POOL"] = "ls_pool"
+ # env.Command("ls")
+ #
+ # TODO: Why not alloe env['NINJA_POOL'] ? (bdbaddog)
+ if node.env and node.env.get("NINJA_POOL", None) is not None:
+ ninja_build["pool"] = node.env["NINJA_POOL"]
+
+ return ninja_build
+
+
+def gen_get_response_file_command(env, rule, tool, tool_is_dynamic=False, custom_env={}):
+ """Generate a response file command provider for rule name."""
+
+ # If win32 using the environment with a response file command will cause
+ # ninja to fail to create the response file. Additionally since these rules
+ # generally are not piping through cmd.exe /c any environment variables will
+ # make CreateProcess fail to start.
+ #
+ # On POSIX we can still set environment variables even for compile
+ # commands so we do so.
+ use_command_env = not env["PLATFORM"] == "win32"
+ if "$" in tool:
+ tool_is_dynamic = True
+
+ def get_response_file_command(env, node, action, targets, sources, executor=None):
+ if hasattr(action, "process"):
+ cmd_list, _, _ = action.process(targets, sources, env, executor=executor)
+ cmd_list = [str(c).replace("$", "$$") for c in cmd_list[0]]
+ else:
+ command = generate_command(
+ env, node, action, targets, sources, executor=executor
+ )
+ cmd_list = shlex.split(command)
+
+ if tool_is_dynamic:
+ tool_command = env.subst(
+ tool, target=targets, source=sources, executor=executor
+ )
+ else:
+ tool_command = tool
+
+ try:
+ # Add 1 so we always keep the actual tool inside of cmd
+ tool_idx = cmd_list.index(tool_command) + 1
+ except ValueError:
+ raise Exception(
+ "Could not find tool {} in {} generated from {}".format(
+ tool, cmd_list, get_comstr(env, action, targets, sources)
+ )
+ )
+
+ cmd, rsp_content = cmd_list[:tool_idx], cmd_list[tool_idx:]
+ rsp_content = ['"' + rsp_content_item + '"' for rsp_content_item in rsp_content]
+ rsp_content = " ".join(rsp_content)
+
+ variables = {"rspc": rsp_content, rule: cmd}
+ if use_command_env:
+ variables["env"] = get_command_env(env)
+
+ for key, value in custom_env.items():
+ variables["env"] += env.subst(
+ "export %s=%s;" % (key, value), target=targets, source=sources, executor=executor
+ ) + " "
+ return rule, variables, [tool_command]
+
+ return get_response_file_command \ No newline at end of file
diff --git a/SCons/Tool/ninja/NinjaState.py b/SCons/Tool/ninja/NinjaState.py
new file mode 100644
index 0000000..e7d9882
--- /dev/null
+++ b/SCons/Tool/ninja/NinjaState.py
@@ -0,0 +1,711 @@
+# 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
+# "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.
+
+import io
+import os
+import sys
+from os.path import splitext
+import ninja
+
+import SCons
+from SCons.Script import COMMAND_LINE_TARGETS
+from SCons.Util import is_List
+from SCons.Errors import InternalError
+from .Globals import COMMAND_TYPES, NINJA_RULES, NINJA_POOLS, \
+ NINJA_CUSTOM_HANDLERS
+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
+from .Methods import get_command
+
+
+# pylint: disable=too-many-instance-attributes
+class NinjaState:
+ """Maintains state of Ninja build system as it's translated from SCons."""
+
+ def __init__(self, env, ninja_file, writer_class):
+ self.env = env
+ self.ninja_file = ninja_file
+
+ self.ninja_bin_path = env.get('NINJA')
+ if not self.ninja_bin_path:
+ # default to using ninja installed with python module
+ ninja_bin = 'ninja.exe' if env["PLATFORM"] == "win32" else 'ninja'
+ self.ninja_bin_path = os.path.abspath(os.path.join(
+ ninja.__file__,
+ os.pardir,
+ 'data',
+ 'bin',
+ ninja_bin))
+ if not os.path.exists(self.ninja_bin_path):
+ # couldn't find it, just give the bin name and hope
+ # its in the path later
+ self.ninja_bin_path = ninja_bin
+
+ self.writer_class = writer_class
+ self.__generated = False
+ self.translator = SConsToNinjaTranslator(env)
+ self.generated_suffixes = env.get("NINJA_GENERATED_SOURCE_SUFFIXES", [])
+
+ # List of generated builds that will be written at a later stage
+ self.builds = dict()
+
+ # List of targets for which we have generated a build. This
+ # allows us to take multiple Alias nodes as sources and to not
+ # fail to build if they have overlapping targets.
+ self.built = set()
+
+ # SCons sets this variable to a function which knows how to do
+ # shell quoting on whatever platform it's run on. Here we use it
+ # to make the SCONS_INVOCATION variable properly quoted for things
+ # like CCFLAGS
+ escape = env.get("ESCAPE", lambda x: x)
+
+ # 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
+ python_bin = ''
+ if os.path.basename(sys.argv[0]) == 'scons.py':
+ python_bin = escape(sys.executable)
+ self.variables = {
+ "COPY": "cmd.exe /c 1>NUL copy" if sys.platform == "win32" else "cp",
+ "SCONS_INVOCATION": '{} {} --disable-ninja __NINJA_NO=1 $out'.format(
+ python_bin,
+ " ".join(
+ [escape(arg) for arg in sys.argv if arg not in COMMAND_LINE_TARGETS]
+ ),
+ ),
+ "SCONS_INVOCATION_W_TARGETS": "{} {}".format(
+ python_bin, " ".join([escape(arg) for arg in sys.argv])
+ ),
+ # This must be set to a global default per:
+ # https://ninja-build.org/manual.html#_deps
+ # English Visual Studio will have the default below,
+ # otherwise the user can define the variable in the first environment
+ # that initialized ninja tool
+ "msvc_deps_prefix": env.get("NINJA_MSVC_DEPS_PREFIX", "Note: including file:")
+ }
+
+ self.rules = {
+ "CMD": {
+ "command": "cmd /c $env$cmd $in $out" if sys.platform == "win32" else "$env$cmd $in $out",
+ "description": "Building $out",
+ "pool": "local_pool",
+ },
+ "GENERATED_CMD": {
+ "command": "cmd /c $env$cmd" if sys.platform == "win32" else "$env$cmd",
+ "description": "Building $out",
+ "pool": "local_pool",
+ },
+ # We add the deps processing variables to this below. We
+ # don't pipe these through cmd.exe on Windows because we
+ # use this to generate a compile_commands.json database
+ # which can't use the shell command as it's compile
+ # command.
+ "CC": {
+ "command": "$env$CC @$out.rsp",
+ "description": "Compiling $out",
+ "rspfile": "$out.rsp",
+ "rspfile_content": "$rspc",
+ },
+ "CXX": {
+ "command": "$env$CXX @$out.rsp",
+ "description": "Compiling $out",
+ "rspfile": "$out.rsp",
+ "rspfile_content": "$rspc",
+ },
+ "LINK": {
+ "command": "$env$LINK @$out.rsp",
+ "description": "Linking $out",
+ "rspfile": "$out.rsp",
+ "rspfile_content": "$rspc",
+ "pool": "local_pool",
+ },
+ # Ninja does not automatically delete the archive before
+ # invoking ar. The ar utility will append to an existing archive, which
+ # can cause duplicate symbols if the symbols moved between object files.
+ # Native SCons will perform this operation so we need to force ninja
+ # to do the same. See related for more info:
+ # https://jira.mongodb.org/browse/SERVER-49457
+ "AR": {
+ "command": "{}$env$AR @$out.rsp".format(
+ '' if sys.platform == "win32" else "rm -f $out && "
+ ),
+ "description": "Archiving $out",
+ "rspfile": "$out.rsp",
+ "rspfile_content": "$rspc",
+ "pool": "local_pool",
+ },
+ "SYMLINK": {
+ "command": (
+ "cmd /c mklink $out $in"
+ if sys.platform == "win32"
+ else "ln -s $in $out"
+ ),
+ "description": "Symlink $in -> $out",
+ },
+ "INSTALL": {
+ "command": "$COPY $in $out",
+ "description": "Install $out",
+ "pool": "install_pool",
+ # On Windows cmd.exe /c copy does not always correctly
+ # update the timestamp on the output file. This leads
+ # to a stuck constant timestamp in the Ninja database
+ # and needless rebuilds.
+ #
+ # Adding restat here ensures that Ninja always checks
+ # the copy updated the timestamp and that Ninja has
+ # the correct information.
+ "restat": 1,
+ },
+ "TEMPLATE": {
+ "command": "$SCONS_INVOCATION $out",
+ "description": "Rendering $SCONS_INVOCATION $out",
+ "pool": "scons_pool",
+ "restat": 1,
+ },
+ "SCONS": {
+ "command": "$SCONS_INVOCATION $out",
+ "description": "$SCONS_INVOCATION $out",
+ "pool": "scons_pool",
+ # restat
+ # if present, causes Ninja to re-stat the command's outputs
+ # after execution of the command. Each output whose
+ # modification time the command did not change will be
+ # treated as though it had never needed to be built. This
+ # may cause the output's reverse dependencies to be removed
+ # from the list of pending build actions.
+ #
+ # We use restat any time we execute SCons because
+ # SCons calls in Ninja typically create multiple
+ # targets. But since SCons is doing it's own up to
+ # date-ness checks it may only update say one of
+ # them. Restat will find out which of the multiple
+ # build targets did actually change then only rebuild
+ # those targets which depend specifically on that
+ # output.
+ "restat": 1,
+ },
+ "REGENERATE": {
+ "command": "$SCONS_INVOCATION_W_TARGETS",
+ "description": "Regenerating $out",
+ "generator": 1,
+ "depfile": os.path.join(get_path(env['NINJA_DIR']), '$out.depfile'),
+ # Console pool restricts to 1 job running at a time,
+ # it additionally has some special handling about
+ # passing stdin, stdout, etc to process in this pool
+ # that we need for SCons to behave correctly when
+ # regenerating Ninja
+ "pool": "console",
+ # Again we restat in case Ninja thought the
+ # build.ninja should be regenerated but SCons knew
+ # better.
+ "restat": 1,
+ },
+ }
+
+ if env['PLATFORM'] == 'darwin' and env['AR'] == 'ar':
+ self.rules["AR"] = {
+ "command": "rm -f $out && $env$AR $rspc",
+ "description": "Archiving $out",
+ "pool": "local_pool",
+ }
+
+ num_jobs = self.env.get('NINJA_MAX_JOBS', self.env.GetOption("num_jobs"))
+ self.pools = {
+ "local_pool": num_jobs,
+ "install_pool": num_jobs / 2,
+ "scons_pool": 1,
+ }
+
+ for rule in ["CC", "CXX"]:
+ if env["PLATFORM"] == "win32":
+ self.rules[rule]["deps"] = "msvc"
+ else:
+ self.rules[rule]["deps"] = "gcc"
+ self.rules[rule]["depfile"] = "$out.d"
+
+ def add_build(self, node):
+ if not node.has_builder():
+ return False
+
+ if isinstance(node, SCons.Node.Alias.Alias):
+ build = alias_to_ninja_build(node)
+ else:
+ build = self.translator.action_to_ninja_build(node)
+
+ # Some things are unbuild-able or need not be built in Ninja
+ if build is None:
+ return False
+
+ node_string = str(node)
+ if node_string in self.builds:
+ raise InternalError("Node {} added to ninja build state more than once".format(node_string))
+ self.builds[node_string] = build
+ self.built.update(build["outputs"])
+ return True
+
+ # TODO: rely on SCons to tell us what is generated source
+ # or some form of user scanner maybe (Github Issue #3624)
+ def is_generated_source(self, output):
+ """Check if output ends with a known generated suffix."""
+ _, suffix = splitext(output)
+ return suffix in self.generated_suffixes
+
+ def has_generated_sources(self, output):
+ """
+ Determine if output indicates this is a generated header file.
+ """
+ for generated in output:
+ if self.is_generated_source(generated):
+ return True
+ return False
+
+ # pylint: disable=too-many-branches,too-many-locals
+ def generate(self):
+ """
+ Generate the build.ninja.
+
+ This should only be called once for the lifetime of this object.
+ """
+ if self.__generated:
+ return
+
+ self.rules.update(self.env.get(NINJA_RULES, {}))
+ self.pools.update(self.env.get(NINJA_POOLS, {}))
+
+ content = io.StringIO()
+ ninja = self.writer_class(content, width=100)
+
+ ninja.comment("Generated by scons. DO NOT EDIT.")
+
+ ninja.variable("builddir", get_path(self.env['NINJA_DIR']))
+
+ for pool_name, size in self.pools.items():
+ ninja.pool(pool_name, min(self.env.get('NINJA_MAX_JOBS', size), size))
+
+ for var, val in self.variables.items():
+ ninja.variable(var, val)
+
+ for rule, kwargs in 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)
+
+ generated_source_files = sorted({
+ output
+ # First find builds which have header files in their outputs.
+ for build in self.builds.values()
+ if self.has_generated_sources(build["outputs"])
+ for output in build["outputs"]
+ # Collect only the header files from the builds with them
+ # in their output. We do this because is_generated_source
+ # returns True if it finds a header in any of the outputs,
+ # here we need to filter so we only have the headers and
+ # not the other outputs.
+ if self.is_generated_source(output)
+ })
+
+ if generated_source_files:
+ ninja.build(
+ outputs="_generated_sources",
+ rule="phony",
+ implicit=generated_source_files
+ )
+
+ template_builders = []
+
+ for build in [self.builds[key] for key in sorted(self.builds.keys())]:
+ if build["rule"] == "TEMPLATE":
+ template_builders.append(build)
+ continue
+
+ if "implicit" in build:
+ build["implicit"].sort()
+
+ # Don't make generated sources depend on each other. We
+ # have to check that none of the outputs are generated
+ # sources and none of the direct implicit dependencies are
+ # generated sources or else we will create a dependency
+ # cycle.
+ if (
+ generated_source_files
+ and not build["rule"] == "INSTALL"
+ and set(build["outputs"]).isdisjoint(generated_source_files)
+ and set(build.get("implicit", [])).isdisjoint(generated_source_files)
+ ):
+ # Make all non-generated source targets depend on
+ # _generated_sources. We use order_only for generated
+ # sources so that we don't rebuild the world if one
+ # generated source was rebuilt. We just need to make
+ # sure that all of these sources are generated before
+ # other builds.
+ order_only = build.get("order_only", [])
+ order_only.append("_generated_sources")
+ build["order_only"] = order_only
+ if "order_only" in build:
+ build["order_only"].sort()
+
+ # When using a depfile Ninja can only have a single output
+ # but SCons will usually have emitted an output for every
+ # thing a command will create because it's caching is much
+ # more complex than Ninja's. This includes things like DWO
+ # files. Here we make sure that Ninja only ever sees one
+ # target when using a depfile. It will still have a command
+ # that will create all of the outputs but most targets don't
+ # depend directly on DWO files and so this assumption is safe
+ # to make.
+ rule = self.rules.get(build["rule"])
+
+ # Some rules like 'phony' and other builtins we don't have
+ # listed in self.rules so verify that we got a result
+ # before trying to check if it has a deps key.
+ #
+ # Anything using deps or rspfile in Ninja can only have a single
+ # output, but we may have a build which actually produces
+ # multiple outputs which other targets can depend on. Here we
+ # slice up the outputs so we have a single output which we will
+ # use for the "real" builder and multiple phony targets that
+ # match the file names of the remaining outputs. This way any
+ # build can depend on any output from any build.
+ #
+ # We assume that the first listed output is the 'key'
+ # output and is stably presented to us by SCons. For
+ # instance if -gsplit-dwarf is in play and we are
+ # producing foo.o and foo.dwo, we expect that outputs[0]
+ # from SCons will be the foo.o file and not the dwo
+ # file. If instead we just sorted the whole outputs array,
+ # we would find that the dwo file becomes the
+ # first_output, and this breaks, for instance, header
+ # dependency scanning.
+ if rule is not None and (rule.get("deps") or rule.get("rspfile")):
+ first_output, remaining_outputs = (
+ build["outputs"][0],
+ build["outputs"][1:],
+ )
+
+ if remaining_outputs:
+ ninja.build(
+ outputs=sorted(remaining_outputs), rule="phony", implicit=first_output,
+ )
+
+ build["outputs"] = first_output
+
+ # Optionally a rule can specify a depfile, and SCons can generate implicit
+ # dependencies into the depfile. This allows for dependencies to come and go
+ # without invalidating the ninja file. The depfile was created in ninja specifically
+ # for dealing with header files appearing and disappearing across rebuilds, but it can
+ # be repurposed for anything, as long as you have a way to regenerate the depfile.
+ # More specific info can be found here: https://ninja-build.org/manual.html#_depfile
+ if rule is not None and rule.get('depfile') and build.get('deps_files'):
+ path = build['outputs'] if SCons.Util.is_List(build['outputs']) else [build['outputs']]
+ generate_depfile(self.env, path[0], build.pop('deps_files', []))
+
+ if "inputs" in build:
+ build["inputs"].sort()
+
+ ninja.build(**build)
+
+ template_builds = dict()
+ for template_builder in template_builders:
+
+ # Special handling for outputs and implicit since we need to
+ # aggregate not replace for each builder.
+ for agg_key in ["outputs", "implicit", "inputs"]:
+ new_val = template_builds.get(agg_key, [])
+
+ # Use pop so the key is removed and so the update
+ # below will not overwrite our aggregated values.
+ cur_val = template_builder.pop(agg_key, [])
+ if is_List(cur_val):
+ new_val += cur_val
+ else:
+ new_val.append(cur_val)
+ template_builds[agg_key] = new_val
+
+ # Collect all other keys
+ template_builds.update(template_builder)
+
+ if template_builds.get("outputs", []):
+ ninja.build(**template_builds)
+
+ # We have to glob the SCons files here to teach the ninja file
+ # how to regenerate itself. We'll never see ourselves in the
+ # DAG walk so we can't rely on action_to_ninja_build to
+ # generate this rule even though SCons should know we're
+ # dependent on SCons files.
+ #
+ # The REGENERATE rule uses depfile, so we need to generate the depfile
+ # in case any of the SConscripts have changed. The depfile needs to be
+ # path with in the build and the passed ninja file is an abspath, so
+ # we will use SCons to give us the path within the build. Normally
+ # generate_depfile should not be called like this, but instead be called
+ # through the use of custom rules, and filtered out in the normal
+ # list of build generation about. However, because the generate rule
+ # is hardcoded here, we need to do this generate_depfile call manually.
+ ninja_file_path = self.env.File(self.ninja_file).path
+ generate_depfile(
+ self.env,
+ ninja_file_path,
+ self.env['NINJA_REGENERATE_DEPS']
+ )
+
+ ninja.build(
+ ninja_file_path,
+ rule="REGENERATE",
+ implicit=[__file__],
+ )
+
+ # 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",
+ rule="CMD",
+ pool="console",
+ implicit=[str(self.ninja_file)],
+ variables={
+ "cmd": "{} -f {} -t compdb {}CC CXX > compile_commands.json".format(
+ # NINJA_COMPDB_EXPAND - should only be true for ninja
+ # This was added to ninja's compdb tool in version 1.9.0 (merged April 2018)
+ # https://github.com/ninja-build/ninja/pull/1223
+ # TODO: add check in generate to check version and enable this by default if it's available.
+ self.ninja_bin_path, str(self.ninja_file),
+ '-x ' if self.env.get('NINJA_COMPDB_EXPAND', True) else ''
+ )
+ },
+ )
+
+ ninja.build(
+ "compiledb", rule="phony", implicit=["compile_commands.json"],
+ )
+
+ # Look in SCons's list of DEFAULT_TARGETS, find the ones that
+ # we generated a ninja build rule for.
+ scons_default_targets = [
+ get_path(tgt)
+ for tgt in SCons.Script.DEFAULT_TARGETS
+ if get_path(tgt) in self.built
+ ]
+
+ # If we found an overlap between SCons's list of default
+ # targets and the targets we created ninja builds for then use
+ # those as ninja's default as well.
+ if scons_default_targets:
+ ninja.default(" ".join(scons_default_targets))
+
+ with open(str(self.ninja_file), "w") as build_ninja:
+ build_ninja.write(content.getvalue())
+
+ self.__generated = True
+
+
+class SConsToNinjaTranslator:
+ """Translates SCons Actions into Ninja build objects."""
+
+ def __init__(self, env):
+ self.env = env
+ self.func_handlers = {
+ # Skip conftest builders
+ "_createSource": ninja_noop,
+ # SCons has a custom FunctionAction that just makes sure the
+ # target isn't static. We let the commands that ninja runs do
+ # this check for us.
+ "SharedFlagChecker": ninja_noop,
+ # The install builder is implemented as a function action.
+ # TODO: use command action #3573
+ "installFunc": _install_action_function,
+ "MkdirFunc": _mkdir_action_function,
+ "LibSymlinksActionFunction": _lib_symlink_action_function,
+ "Copy": _copy_action_function
+ }
+
+ self.loaded_custom = False
+
+ # pylint: disable=too-many-return-statements
+ def action_to_ninja_build(self, node, action=None):
+ """Generate build arguments dictionary for node."""
+
+ if not self.loaded_custom:
+ self.func_handlers.update(self.env[NINJA_CUSTOM_HANDLERS])
+ self.loaded_custom = True
+
+ if node.builder is None:
+ return None
+
+ if action is None:
+ action = node.builder.action
+
+ if node.env and node.env.get("NINJA_SKIP"):
+ return None
+
+ build = {}
+ env = node.env if node.env else self.env
+
+ # Ideally this should never happen, and we do try to filter
+ # Ninja builders out of being sources of ninja builders but I
+ # can't fix every DAG problem so we just skip ninja_builders
+ # if we find one
+ if SCons.Tool.ninja.NINJA_STATE.ninja_file == str(node):
+ build = None
+ elif isinstance(action, SCons.Action.FunctionAction):
+ build = self.handle_func_action(node, action)
+ elif isinstance(action, SCons.Action.LazyAction):
+ # pylint: disable=protected-access
+ action = action._generate_cache(env)
+ build = self.action_to_ninja_build(node, action=action)
+ elif isinstance(action, SCons.Action.ListAction):
+ build = self.handle_list_action(node, action)
+ elif isinstance(action, COMMAND_TYPES):
+ build = get_command(env, node, action)
+ else:
+ raise Exception("Got an unbuildable ListAction for: {}".format(str(node)))
+
+ if build is not None:
+ build["order_only"] = get_order_only(node)
+
+ # TODO: WPD Is this testing the filename to verify it's a configure context generated file?
+ if not node.is_conftest():
+ node_callback = node.check_attributes("ninja_build_callback")
+ if callable(node_callback):
+ node_callback(env, node, build)
+
+ return build
+
+ def handle_func_action(self, node, action):
+ """Determine how to handle the function action."""
+ name = action.function_name()
+ # This is the name given by the Subst/Textfile builders. So return the
+ # node to indicate that SCons is required. We skip sources here because
+ # dependencies don't really matter when we're going to shove these to
+ # the bottom of ninja's DAG anyway and Textfile builders can have text
+ # content as their source which doesn't work as an implicit dep in
+ # ninja.
+ if name == 'ninja_builder':
+ return None
+
+ handler = self.func_handlers.get(name, None)
+ if handler is not None:
+ return handler(node.env if node.env else self.env, node)
+ elif name == "ActionCaller":
+ action_to_call = str(action).split('(')[0].strip()
+ handler = self.func_handlers.get(action_to_call, None)
+ if handler is not None:
+ return handler(node.env if node.env else self.env, node)
+
+ SCons.Warnings.SConsWarning(
+ "Found unhandled function action {}, "
+ " generating scons command to build\n"
+ "Note: this is less efficient than Ninja,"
+ " you can write your own ninja build generator for"
+ " this function using NinjaRegisterFunctionHandler".format(name)
+ )
+
+ return {
+ "rule": "TEMPLATE",
+ "order_only": get_order_only(node),
+ "outputs": get_outputs(node),
+ "inputs": get_inputs(node),
+ "implicit": get_dependencies(node, skip_sources=True),
+ }
+
+ # pylint: disable=too-many-branches
+ def handle_list_action(self, node, action):
+ """TODO write this comment"""
+ results = [
+ self.action_to_ninja_build(node, action=act)
+ for act in action.list
+ if act is not None
+ ]
+ results = [
+ result for result in results if result is not None and result["outputs"]
+ ]
+ if not results:
+ return None
+
+ # No need to process the results if we only got a single result
+ if len(results) == 1:
+ return results[0]
+
+ all_outputs = list({output for build in results for output in build["outputs"]})
+ dependencies = list({dep for build in results for dep in build["implicit"]})
+
+ if results[0]["rule"] == "CMD" or results[0]["rule"] == "GENERATED_CMD":
+ cmdline = ""
+ for cmd in results:
+
+ # Occasionally a command line will expand to a
+ # whitespace only string (i.e. ' '). Which is not a
+ # valid command but does not trigger the empty command
+ # condition if not cmdstr. So here we strip preceding
+ # and proceeding whitespace to make strings like the
+ # above become empty strings and so will be skipped.
+ cmdstr = cmd["variables"]["cmd"].strip()
+ if not cmdstr:
+ continue
+
+ # Skip duplicate commands
+ if cmdstr in cmdline:
+ continue
+
+ if cmdline:
+ cmdline += " && "
+
+ cmdline += cmdstr
+
+ # Remove all preceding and proceeding whitespace
+ cmdline = cmdline.strip()
+
+ # Make sure we didn't generate an empty cmdline
+ if cmdline:
+ ninja_build = {
+ "outputs": all_outputs,
+ "rule": get_rule(node, "GENERATED_CMD"),
+ "variables": {
+ "cmd": cmdline,
+ "env": get_command_env(node.env if node.env else self.env),
+ },
+ "implicit": dependencies,
+ }
+
+ if node.env and node.env.get("NINJA_POOL", None) is not None:
+ ninja_build["pool"] = node.env["pool"]
+
+ return ninja_build
+
+ elif results[0]["rule"] == "phony":
+ return {
+ "outputs": all_outputs,
+ "rule": "phony",
+ "implicit": dependencies,
+ }
+
+ elif results[0]["rule"] == "INSTALL":
+ return {
+ "outputs": all_outputs,
+ "rule": get_rule(node, "INSTALL"),
+ "inputs": get_inputs(node),
+ "implicit": dependencies,
+ }
+
+ raise Exception("Unhandled list action with rule: " + results[0]["rule"])
diff --git a/SCons/Tool/ninja/Overrides.py b/SCons/Tool/ninja/Overrides.py
new file mode 100644
index 0000000..c6f1207
--- /dev/null
+++ b/SCons/Tool/ninja/Overrides.py
@@ -0,0 +1,96 @@
+# 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 module is to hold logic which overrides default SCons behaviors to enable
+ninja file generation
+"""
+import SCons
+
+
+def ninja_hack_linkcom(env):
+ # TODO: change LINKCOM and SHLINKCOM to handle embedding manifest exe checks
+ # without relying on the SCons hacks that SCons uses by default.
+ if env["PLATFORM"] == "win32":
+ from SCons.Tool.mslink import compositeLinkAction
+
+ if env.get("LINKCOM", None) == compositeLinkAction:
+ env[
+ "LINKCOM"
+ ] = '${TEMPFILE("$LINK $LINKFLAGS /OUT:$TARGET.windows $_LIBDIRFLAGS $_LIBFLAGS $_PDB $SOURCES.windows", "$LINKCOMSTR")}'
+ env[
+ "SHLINKCOM"
+ ] = '${TEMPFILE("$SHLINK $SHLINKFLAGS $_SHLINK_TARGETS $_LIBDIRFLAGS $_LIBFLAGS $_PDB $_SHLINK_SOURCES", "$SHLINKCOMSTR")}'
+
+
+def ninja_hack_arcom(env):
+ """
+ Force ARCOM so use 's' flag on ar instead of separately running ranlib
+ """
+ if env["PLATFORM"] != "win32" and env.get("RANLIBCOM"):
+ # There is no way to translate the ranlib list action into
+ # Ninja so add the s flag and disable ranlib.
+ #
+ # This is equivalent to Meson.
+ # https://github.com/mesonbuild/meson/blob/master/mesonbuild/linkers.py#L143
+ old_arflags = str(env["ARFLAGS"])
+ if "s" not in old_arflags:
+ old_arflags += "s"
+
+ env["ARFLAGS"] = SCons.Util.CLVar([old_arflags])
+
+ # Disable running ranlib, since we added 's' above
+ env["RANLIBCOM"] = ""
+
+
+class NinjaNoResponseFiles(SCons.Platform.TempFileMunge):
+ """Overwrite the __call__ method of SCons' TempFileMunge to not delete."""
+
+ def __call__(self, target, source, env, for_signature):
+ return self.cmd
+
+ def _print_cmd_str(*_args, **_kwargs):
+ """Disable this method"""
+ pass
+
+
+def ninja_always_serial(self, num, taskmaster):
+ """Replacement for SCons.Job.Jobs constructor which always uses the Serial Job class."""
+ # We still set self.num_jobs to num even though it's a lie. The
+ # only consumer of this attribute is the Parallel Job class AND
+ # the Main.py function which instantiates a Jobs class. It checks
+ # if Jobs.num_jobs is equal to options.num_jobs, so if the user
+ # provides -j12 but we set self.num_jobs = 1 they get an incorrect
+ # warning about this version of Python not supporting parallel
+ # builds. So here we lie so the Main.py will not give a false
+ # warning to users.
+ self.num_jobs = num
+ self.job = SCons.Job.Serial(taskmaster)
+
+
+# pylint: disable=too-few-public-methods
+class AlwaysExecAction(SCons.Action.FunctionAction):
+ """Override FunctionAction.__call__ to always execute."""
+
+ def __call__(self, *args, **kwargs):
+ kwargs["execute"] = 1
+ return super().__call__(*args, **kwargs) \ No newline at end of file
diff --git a/SCons/Tool/ninja/Rules.py b/SCons/Tool/ninja/Rules.py
new file mode 100644
index 0000000..a2f6bc5
--- /dev/null
+++ b/SCons/Tool/ninja/Rules.py
@@ -0,0 +1,81 @@
+# 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.
+
+from .Utils import get_outputs, get_rule, get_inputs, get_dependencies
+
+
+def _install_action_function(_env, node):
+ """Install files using the install or copy commands"""
+ return {
+ "outputs": get_outputs(node),
+ "rule": get_rule(node, "INSTALL"),
+ "inputs": get_inputs(node),
+ "implicit": get_dependencies(node),
+ }
+
+
+def _mkdir_action_function(env, node):
+ return {
+ "outputs": get_outputs(node),
+ "rule": get_rule(node, "CMD"),
+ # implicit explicitly omitted, we translate these so they can be
+ # used by anything that depends on these but commonly this is
+ # hit with a node that will depend on all of the fake
+ # srcnode's that SCons will never give us a rule for leading
+ # to an invalid ninja file.
+ "variables": {
+ # On Windows mkdir "-p" is always on
+ "cmd": "{mkdir}".format(
+ mkdir="mkdir $out & exit 0" if env["PLATFORM"] == "win32" else "mkdir -p $out",
+ ),
+ },
+ }
+
+
+def _copy_action_function(env, node):
+ return {
+ "outputs": get_outputs(node),
+ "inputs": get_inputs(node),
+ "rule": get_rule(node, "CMD"),
+ "variables": {
+ "cmd": "$COPY $in $out",
+ },
+ }
+
+
+def _lib_symlink_action_function(_env, node):
+ """Create shared object symlinks if any need to be created"""
+ symlinks = node.check_attributes("shliblinks")
+
+ if not symlinks or symlinks is None:
+ return None
+
+ outputs = [link.get_dir().rel_path(linktgt) for link, linktgt in symlinks]
+ inputs = [link.get_path() for link, _ in symlinks]
+
+ return {
+ "outputs": outputs,
+ "inputs": inputs,
+ "rule": get_rule(node, "SYMLINK"),
+ "implicit": get_dependencies(node),
+ }
diff --git a/SCons/Tool/ninja/Utils.py b/SCons/Tool/ninja/Utils.py
new file mode 100644
index 0000000..3bdefe8
--- /dev/null
+++ b/SCons/Tool/ninja/Utils.py
@@ -0,0 +1,419 @@
+# 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.
+import os
+import shutil
+from os.path import join as joinpath
+
+import SCons
+from SCons.Action import get_default_ENV, _string_from_cmd_list
+from SCons.Script import AddOption
+from SCons.Util import is_List, flatten_sequence
+
+
+def ninja_add_command_line_options():
+ """
+ Add additional command line arguments to SCons specific to the ninja tool
+ """
+ AddOption('--disable-execute-ninja',
+ dest='disable_execute_ninja',
+ metavar='BOOL',
+ action="store_true",
+ default=False,
+ help='Disable ninja automatically building after scons')
+
+ AddOption('--disable-ninja',
+ dest='disable_ninja',
+ metavar='BOOL',
+ action="store_true",
+ default=False,
+ help='Disable ninja automatically building after scons')
+
+
+def is_valid_dependent_node(node):
+ """
+ Return True if node is not an alias or is an alias that has children
+
+ This prevents us from making phony targets that depend on other
+ phony targets that will never have an associated ninja build
+ target.
+
+ We also have to specify that it's an alias when doing the builder
+ check because some nodes (like src files) won't have builders but
+ are valid implicit dependencies.
+ """
+ if isinstance(node, SCons.Node.Alias.Alias):
+ return node.children()
+
+ return not node.get_env().get("NINJA_SKIP")
+
+
+
+
+def alias_to_ninja_build(node):
+ """Convert an Alias node into a Ninja phony target"""
+ return {
+ "outputs": get_outputs(node),
+ "rule": "phony",
+ "implicit": [
+ get_path(src_file(n)) for n in node.children() if is_valid_dependent_node(n)
+ ],
+ }
+
+
+def check_invalid_ninja_node(node):
+ return not isinstance(node, (SCons.Node.FS.Base, SCons.Node.Alias.Alias))
+
+
+def filter_ninja_nodes(node_list):
+ ninja_nodes = []
+ for node in node_list:
+ if isinstance(node, (SCons.Node.FS.Base, SCons.Node.Alias.Alias)) and not node.get_env().get('NINJA_SKIP'):
+ ninja_nodes.append(node)
+ else:
+ continue
+ return ninja_nodes
+
+
+def get_input_nodes(node):
+ if node.get_executor() is not None:
+ inputs = node.get_executor().get_all_sources()
+ else:
+ inputs = node.sources
+ return inputs
+
+
+def invalid_ninja_nodes(node, targets):
+ result = False
+ for node_list in [node.prerequisites, get_input_nodes(node), node.children(), targets]:
+ if node_list:
+ result = result or any([check_invalid_ninja_node(node) for node in node_list])
+ return result
+
+
+def get_order_only(node):
+ """Return a list of order only dependencies for node."""
+ if node.prerequisites is None:
+ return []
+ return [get_path(src_file(prereq)) for prereq in filter_ninja_nodes(node.prerequisites)]
+
+
+def get_dependencies(node, skip_sources=False):
+ """Return a list of dependencies for node."""
+ if skip_sources:
+ return [
+ 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())]
+
+
+def get_inputs(node):
+ """Collect the Ninja inputs for node."""
+ return [get_path(src_file(o)) for o in filter_ninja_nodes(get_input_nodes(node))]
+
+
+def get_outputs(node):
+ """Collect the Ninja outputs for node."""
+ executor = node.get_executor()
+ if executor is not None:
+ outputs = executor.get_all_targets()
+ else:
+ if hasattr(node, "target_peers"):
+ outputs = node.target_peers
+ else:
+ outputs = [node]
+
+ outputs = [get_path(o) for o in filter_ninja_nodes(outputs)]
+
+ return outputs
+
+
+def get_targets_sources(node):
+ executor = node.get_executor()
+ if executor is not None:
+ tlist = executor.get_all_targets()
+ slist = executor.get_all_sources()
+ else:
+ if hasattr(node, "target_peers"):
+ tlist = node.target_peers
+ else:
+ tlist = [node]
+ slist = node.sources
+
+ # Retrieve the repository file for all sources
+ slist = [rfile(s) for s in slist]
+ return tlist, slist
+
+def get_path(node):
+ """
+ Return a fake path if necessary.
+
+ As an example Aliases use this as their target name in Ninja.
+ """
+ if hasattr(node, "get_path"):
+ return node.get_path()
+ return str(node)
+
+
+def rfile(node):
+ """
+ Return the repository file for node if it has one. Otherwise return node
+ """
+ if hasattr(node, "rfile"):
+ return node.rfile()
+ return node
+
+
+def src_file(node):
+ """Returns the src code file if it exists."""
+ if hasattr(node, "srcnode"):
+ src = node.srcnode()
+ if src.stat() is not None:
+ return src
+ return get_path(node)
+
+def get_rule(node, rule):
+ tlist, slist = get_targets_sources(node)
+ if invalid_ninja_nodes(node, tlist):
+ return "TEMPLATE"
+ else:
+ return rule
+
+
+def generate_depfile(env, node, dependencies):
+ """
+ Ninja tool function for writing a depfile. The depfile should include
+ the node path followed by all the dependent files in a makefile format.
+
+ dependencies arg can be a list or a subst generator which returns a list.
+ """
+
+ depfile = os.path.join(get_path(env['NINJA_DIR']), str(node) + '.depfile')
+
+ # subst_list will take in either a raw list or a subst callable which generates
+ # a list, and return a list of CmdStringHolders which can be converted into raw strings.
+ # If a raw list was passed in, then scons_list will make a list of lists from the original
+ # values and even subst items in the list if they are substitutable. Flatten will flatten
+ # the list in that case, to ensure for either input we have a list of CmdStringHolders.
+ deps_list = env.Flatten(env.subst_list(dependencies))
+
+ # Now that we have the deps in a list as CmdStringHolders, we can convert them into raw strings
+ # and make sure to escape the strings to handle spaces in paths. We also will sort the result
+ # keep the order of the list consistent.
+ escaped_depends = sorted([dep.escape(env.get("ESCAPE", lambda x: x)) for dep in deps_list])
+ depfile_contents = str(node) + ": " + ' '.join(escaped_depends)
+
+ need_rewrite = False
+ try:
+ with open(depfile, 'r') as f:
+ need_rewrite = (f.read() != depfile_contents)
+ except FileNotFoundError:
+ need_rewrite = True
+
+ if need_rewrite:
+ os.makedirs(os.path.dirname(depfile) or '.', exist_ok=True)
+ with open(depfile, 'w') as f:
+ f.write(depfile_contents)
+
+
+def ninja_noop(*_args, **_kwargs):
+ """
+ A general purpose no-op function.
+
+ There are many things that happen in SCons that we don't need and
+ also don't return anything. We use this to disable those functions
+ instead of creating multiple definitions of the same thing.
+ """
+ return None
+
+
+def get_command_env(env):
+ """
+ Return a string that sets the environment for any environment variables that
+ differ between the OS environment and the SCons command ENV.
+
+ It will be compatible with the default shell of the operating system.
+ """
+ try:
+ return env["NINJA_ENV_VAR_CACHE"]
+ except KeyError:
+ pass
+
+ # Scan the ENV looking for any keys which do not exist in
+ # os.environ or differ from it. We assume if it's a new or
+ # differing key from the process environment then it's
+ # important to pass down to commands in the Ninja file.
+ ENV = get_default_ENV(env)
+ scons_specified_env = {
+ key: value
+ for key, value in ENV.items()
+ # TODO: Remove this filter, unless there's a good reason to keep. SCons's behavior shouldn't depend on shell's.
+ if key not in os.environ or os.environ.get(key, None) != value
+ }
+
+ windows = env["PLATFORM"] == "win32"
+ command_env = ""
+ for key, value in scons_specified_env.items():
+ # Ensure that the ENV values are all strings:
+ if is_List(value):
+ # If the value is a list, then we assume it is a
+ # path list, because that's a pretty common list-like
+ # value to stick in an environment variable:
+ value = flatten_sequence(value)
+ value = joinpath(map(str, value))
+ else:
+ # If it isn't a string or a list, then we just coerce
+ # it to a string, which is the proper way to handle
+ # Dir and File instances and will produce something
+ # reasonable for just about everything else:
+ value = str(value)
+
+ if windows:
+ command_env += "set '{}={}' && ".format(key, value)
+ else:
+ # We address here *only* the specific case that a user might have
+ # an environment variable which somehow gets included and has
+ # spaces in the value. These are escapes that Ninja handles. This
+ # doesn't make builds on paths with spaces (Ninja and SCons issues)
+ # nor expanding response file paths with spaces (Ninja issue) work.
+ value = value.replace(r' ', r'$ ')
+ command_env += "export {}='{}';".format(key, value)
+
+ env["NINJA_ENV_VAR_CACHE"] = command_env
+ return command_env
+
+
+def get_comstr(env, action, targets, sources):
+ """Get the un-substituted string for action."""
+ # Despite being having "list" in it's name this member is not
+ # actually a list. It's the pre-subst'd string of the command. We
+ # use it to determine if the command we're about to generate needs
+ # to use a custom Ninja rule. By default this redirects CC, CXX,
+ # AR, SHLINK, and LINK commands to their respective rules but the
+ # user can inject custom Ninja rules and tie them to commands by
+ # using their pre-subst'd string.
+ if hasattr(action, "process"):
+ return action.cmd_list
+
+ return action.genstring(targets, sources, env)
+
+
+def generate_command(env, node, action, targets, sources, executor=None):
+ # Actions like CommandAction have a method called process that is
+ # used by SCons to generate the cmd_line they need to run. So
+ # check if it's a thing like CommandAction and call it if we can.
+ if hasattr(action, "process"):
+ cmd_list, _, _ = action.process(targets, sources, env, executor=executor)
+ cmd = _string_from_cmd_list(cmd_list[0])
+ else:
+ # Anything else works with genstring, this is most commonly hit by
+ # ListActions which essentially call process on all of their
+ # commands and concatenate it for us.
+ genstring = action.genstring(targets, sources, env)
+ if executor is not None:
+ cmd = env.subst(genstring, executor=executor)
+ else:
+ cmd = env.subst(genstring, targets, sources)
+
+ cmd = cmd.replace("\n", " && ").strip()
+ if cmd.endswith("&&"):
+ cmd = cmd[0:-2].strip()
+
+ # Escape dollars as necessary
+ return cmd.replace("$", "$$")
+
+
+def ninja_csig(original):
+ """Return a dummy csig"""
+
+ def wrapper(self):
+ if isinstance(self, SCons.Node.Node) and self.is_sconscript():
+ return original(self)
+ return "dummy_ninja_csig"
+
+ return wrapper
+
+
+def ninja_contents(original):
+ """Return a dummy content without doing IO"""
+
+ def wrapper(self):
+ if isinstance(self, SCons.Node.Node) and self.is_sconscript():
+ return original(self)
+ return bytes("dummy_ninja_contents", encoding="utf-8")
+
+ return wrapper
+
+
+def ninja_stat(_self, path):
+ """
+ Eternally memoized stat call.
+
+ SCons is very aggressive about clearing out cached values. For our
+ purposes everything should only ever call stat once since we're
+ running in a no_exec build the file system state should not
+ change. For these reasons we patch SCons.Node.FS.LocalFS.stat to
+ use our eternal memoized dictionary.
+ """
+
+ try:
+ return SCons.Tool.ninja.Globals.NINJA_STAT_MEMO[path]
+ except KeyError:
+ try:
+ result = os.stat(path)
+ except os.error:
+ result = None
+
+ SCons.Tool.ninja.Globals.NINJA_STAT_MEMO[path] = result
+ return result
+
+
+def ninja_whereis(thing, *_args, **_kwargs):
+ """Replace env.WhereIs with a much faster version"""
+
+ # Optimize for success, this gets called significantly more often
+ # when the value is already memoized than when it's not.
+ try:
+ return SCons.Tool.ninja.Globals.NINJA_WHEREIS_MEMO[thing]
+ except KeyError:
+ # TODO: Fix this to respect env['ENV']['PATH']... WPD
+ # We do not honor any env['ENV'] or env[*] variables in the
+ # generated ninja file. Ninja passes your raw shell environment
+ # down to it's subprocess so the only sane option is to do the
+ # same during generation. At some point, if and when we try to
+ # upstream this, I'm sure a sticking point will be respecting
+ # env['ENV'] variables and such but it's actually quite
+ # complicated. I have a naive version but making it always work
+ # with shell quoting is nigh impossible. So I've decided to
+ # cross that bridge when it's absolutely required.
+ path = shutil.which(thing)
+ SCons.Tool.ninja.Globals.NINJA_WHEREIS_MEMO[thing] = path
+ return path
+
+
+def ninja_print_conf_log(s, target, source, env):
+ """Command line print only for conftest to generate a correct conf log."""
+ if target and target[0].is_conftest():
+ action = SCons.Action._ActionAction()
+ action.print_cmd_line(s, target, source, env) \ No newline at end of file
diff --git a/SCons/Tool/ninja/__init__.py b/SCons/Tool/ninja/__init__.py
new file mode 100644
index 0000000..d940bb1
--- /dev/null
+++ b/SCons/Tool/ninja/__init__.py
@@ -0,0 +1,450 @@
+# MIT License
+#
+# Copyright 2020 MongoDB Inc.
+#
+# 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.
+#
+
+"""Generate build.ninja files from SCons aliases."""
+
+import importlib
+import os
+import subprocess
+import sys
+
+import SCons
+import SCons.Tool.ninja.Globals
+from SCons.Script import GetOption
+
+from .Globals import NINJA_RULES, NINJA_POOLS, NINJA_CUSTOM_HANDLERS
+from .Methods import register_custom_handler, register_custom_rule_mapping, register_custom_rule, register_custom_pool, \
+ set_build_node_callback, get_generic_shell_command, CheckNinjaCompdbExpand, get_command, \
+ gen_get_response_file_command
+from .NinjaState import NinjaState
+from .Overrides import ninja_hack_linkcom, ninja_hack_arcom, NinjaNoResponseFiles, ninja_always_serial, AlwaysExecAction
+from .Utils import ninja_add_command_line_options, \
+ ninja_noop, ninja_print_conf_log, ninja_csig, ninja_contents, ninja_stat, ninja_whereis
+
+NINJA_STATE = None
+
+
+def ninja_builder(env, target, source):
+ """Generate a build.ninja for source."""
+ if not isinstance(source, list):
+ source = [source]
+ if not isinstance(target, list):
+ target = [target]
+
+ # We have no COMSTR equivalent so print that we're generating
+ # here.
+ print("Generating:", str(target[0]))
+
+ generated_build_ninja = target[0].get_abspath()
+ NINJA_STATE.generate()
+
+ if env["PLATFORM"] == "win32":
+ # TODO: Is this necessary as you set env variable in the ninja build file per target?
+ # this is not great, its doesn't consider specific
+ # node environments, which means on linux the build could
+ # behave differently, because on linux you can set the environment
+ # per command in the ninja file. This is only needed if
+ # running ninja directly from a command line that hasn't
+ # had the environment setup (vcvarsall.bat)
+ with open('run_ninja_env.bat', 'w') as f:
+ for key in env['ENV']:
+ f.write('set {}={}\n'.format(key, env['ENV'][key]))
+ f.write('{} -f {} %*\n'.format(NINJA_STATE.ninja_bin_path, generated_build_ninja))
+ cmd = ['run_ninja_env.bat']
+
+ else:
+ cmd = [NINJA_STATE.ninja_bin_path, '-f', generated_build_ninja]
+
+ if not env.get("NINJA_DISABLE_AUTO_RUN"):
+ print("Executing:", str(' '.join(cmd)))
+
+ # execute the ninja build at the end of SCons, trying to
+ # reproduce the output like a ninja build would
+ def execute_ninja():
+
+ proc = subprocess.Popen(cmd,
+ stderr=sys.stderr,
+ stdout=subprocess.PIPE,
+ universal_newlines=True,
+ env=os.environ if env["PLATFORM"] == "win32" else env['ENV']
+ )
+ for stdout_line in iter(proc.stdout.readline, ""):
+ yield stdout_line
+ proc.stdout.close()
+ return_code = proc.wait()
+ if return_code:
+ raise subprocess.CalledProcessError(return_code, 'ninja')
+
+ erase_previous = False
+ for output in execute_ninja():
+ output = output.strip()
+ if erase_previous:
+ sys.stdout.write('\x1b[2K') # erase previous line
+ sys.stdout.write("\r")
+ else:
+ sys.stdout.write(os.linesep)
+ sys.stdout.write(output)
+ sys.stdout.flush()
+ # this will only erase ninjas [#/#] lines
+ # leaving warnings and other output, seems a bit
+ # prone to failure with such a simple check
+ erase_previous = output.startswith('[')
+
+
+def exists(env):
+ """Enable if called."""
+
+ if 'ninja' not in GetOption('experimental'):
+ return False
+
+ # This variable disables the tool when storing the SCons command in the
+ # generated ninja file to ensure that the ninja tool is not loaded when
+ # SCons should do actual work as a subprocess of a ninja build. The ninja
+ # tool is very invasive into the internals of SCons and so should never be
+ # enabled when SCons needs to build a target.
+ if env.get("__NINJA_NO", "0") == "1":
+ return False
+
+ try:
+ import ninja
+ return ninja.__file__
+ except ImportError:
+ SCons.Warnings.SConsWarning("Failed to import ninja, attempt normal SCons build.")
+ return False
+
+
+def ninja_emitter(target, source, env):
+ """ fix up the source/targets """
+
+ ninja_file = env.File(env.subst("$NINJA_FILE_NAME"))
+ ninja_file.attributes.ninja_file = True
+
+ # Someone called env.Ninja('my_targetname.ninja')
+ if not target and len(source) == 1:
+ target = source
+
+ # Default target name is $NINJA_PREFIX.$NINJA.SUFFIX
+ if not target:
+ target = [ninja_file, ]
+
+ # No source should have been passed. Drop it.
+ if source:
+ source = []
+
+ return target, source
+
+
+def generate(env):
+ """Generate the NINJA builders."""
+ global NINJA_STATE
+
+ if 'ninja' not in GetOption('experimental'):
+ return
+
+ if not SCons.Tool.ninja.Globals.ninja_builder_initialized:
+ SCons.Tool.ninja.Globals.ninja_builder_initialized = True
+
+ ninja_add_command_line_options()
+
+ try:
+ import ninja # noqa: F401
+ except ImportError:
+ SCons.Warnings.SConsWarning("Failed to import ninja, attempt normal SCons build.")
+ return
+
+ env["NINJA_DISABLE_AUTO_RUN"] = GetOption('disable_execute_ninja')
+
+ env["NINJA_FILE_NAME"] = env.get("NINJA_FILE_NAME", "build.ninja")
+
+ # Add the Ninja builder.
+ always_exec_ninja_action = AlwaysExecAction(ninja_builder, {})
+ ninja_builder_obj = SCons.Builder.Builder(action=always_exec_ninja_action,
+ emitter=ninja_emitter)
+ env.Append(BUILDERS={"Ninja": ninja_builder_obj})
+
+ env["NINJA_ALIAS_NAME"] = env.get("NINJA_ALIAS_NAME", "generate-ninja")
+ env['NINJA_DIR'] = env.get("NINJA_DIR", env.Dir(".ninja").path)
+
+ # here we allow multiple environments to construct rules and builds
+ # into the same ninja file
+ if NINJA_STATE is None:
+ ninja_file = env.Ninja()
+ env.AlwaysBuild(ninja_file)
+ env.Alias("$NINJA_ALIAS_NAME", ninja_file)
+ else:
+ if str(NINJA_STATE.ninja_file) != env["NINJA_FILE_NAME"]:
+ SCons.Warnings.SConsWarning("Generating multiple ninja files not supported, set ninja file name before tool initialization.")
+ ninja_file = [NINJA_STATE.ninja_file]
+
+ def ninja_generate_deps(env):
+ """Return a list of SConscripts
+ TODO: Should we also include files loaded from site_scons/***
+ or even all loaded modules? https://stackoverflow.com/questions/4858100/how-to-list-imported-modules
+ TODO: Do we want this to be Nodes?
+ """
+ return sorted([str(s) for s in SCons.Node.SConscriptNodes])
+
+ env['_NINJA_REGENERATE_DEPS_FUNC'] = ninja_generate_deps
+
+ env['NINJA_REGENERATE_DEPS'] = env.get('NINJA_REGENERATE_DEPS', '${_NINJA_REGENERATE_DEPS_FUNC(__env__)}')
+
+ # This adds the required flags such that the generated compile
+ # commands will create depfiles as appropriate in the Ninja file.
+ if env["PLATFORM"] == "win32":
+ env.Append(CCFLAGS=["/showIncludes"])
+ else:
+ env.Append(CCFLAGS=["-MMD", "-MF", "${TARGET}.d"])
+
+ env.AddMethod(CheckNinjaCompdbExpand, "CheckNinjaCompdbExpand")
+
+ # Provide a way for custom rule authors to easily access command
+ # generation.
+ env.AddMethod(get_generic_shell_command, "NinjaGetGenericShellCommand")
+ env.AddMethod(get_command, "NinjaGetCommand")
+ env.AddMethod(gen_get_response_file_command, "NinjaGenResponseFileProvider")
+ env.AddMethod(set_build_node_callback, "NinjaSetBuildNodeCallback")
+
+ # Provides a way for users to handle custom FunctionActions they
+ # want to translate to Ninja.
+ env[NINJA_CUSTOM_HANDLERS] = {}
+ env.AddMethod(register_custom_handler, "NinjaRegisterFunctionHandler")
+
+ # Provides a mechanism for inject custom Ninja rules which can
+ # then be mapped using NinjaRuleMapping.
+ env[NINJA_RULES] = {}
+ env.AddMethod(register_custom_rule, "NinjaRule")
+
+ # Provides a mechanism for inject custom Ninja pools which can
+ # be used by providing the NINJA_POOL="name" as an
+ # OverrideEnvironment variable in a builder call.
+ env[NINJA_POOLS] = {}
+ env.AddMethod(register_custom_pool, "NinjaPool")
+
+ # Add the ability to register custom NinjaRuleMappings for Command
+ # builders. We don't store this dictionary in the env to prevent
+ # accidental deletion of the CC/XXCOM mappings. You can still
+ # overwrite them if you really want to but you have to explicit
+ # about it this way. The reason is that if they were accidentally
+ # deleted you would get a very subtly incorrect Ninja file and
+ # might not catch it.
+ env.AddMethod(register_custom_rule_mapping, "NinjaRuleMapping")
+
+ # on windows we need to change the link action
+ ninja_hack_linkcom(env)
+
+ # Normally in SCons actions for the Program and *Library builders
+ # will return "${*COM}" as their pre-subst'd command line. However
+ # if a user in a SConscript overwrites those values via key access
+ # like env["LINKCOM"] = "$( $ICERUN $)" + env["LINKCOM"] then
+ # those actions no longer return the "bracketted" string and
+ # instead return something that looks more expanded. So to
+ # continue working even if a user has done this we map both the
+ # "bracketted" and semi-expanded versions.
+ def robust_rule_mapping(var, rule, tool):
+ provider = gen_get_response_file_command(env, rule, tool)
+ env.NinjaRuleMapping("${" + var + "}", provider)
+ env.NinjaRuleMapping(env.get(var, None), provider)
+
+ robust_rule_mapping("CCCOM", "CC", "$CC")
+ robust_rule_mapping("SHCCCOM", "CC", "$CC")
+ robust_rule_mapping("CXXCOM", "CXX", "$CXX")
+ robust_rule_mapping("SHCXXCOM", "CXX", "$CXX")
+ robust_rule_mapping("LINKCOM", "LINK", "$LINK")
+ robust_rule_mapping("SHLINKCOM", "LINK", "$SHLINK")
+ robust_rule_mapping("ARCOM", "AR", "$AR")
+
+ # Make SCons node walk faster by preventing unnecessary work
+ env.Decider("timestamp-match")
+
+ # Used to determine if a build generates a source file. Ninja
+ # requires that all generated sources are added as order_only
+ # dependencies to any builds that *might* use them.
+ # TODO: switch to using SCons to help determine this (Github Issue #3624)
+ env["NINJA_GENERATED_SOURCE_SUFFIXES"] = [".h", ".hpp"]
+
+ # Force ARCOM so use 's' flag on ar instead of separately running ranlib
+ ninja_hack_arcom(env)
+
+ if GetOption('disable_ninja'):
+ return env
+
+ SCons.Warnings.SConsWarning("Initializing ninja tool... this feature is experimental. SCons internals and all environments will be affected.")
+
+ # This is the point of no return, anything after this comment
+ # makes changes to SCons that are irreversible and incompatible
+ # with a normal SCons build. We return early if __NINJA_NO=1 has
+ # been given on the command line (i.e. by us in the generated
+ # ninja file) here to prevent these modifications from happening
+ # when we want SCons to do work. Everything before this was
+ # necessary to setup the builder and other functions so that the
+ # tool can be unconditionally used in the users's SCons files.
+
+ if not exists(env):
+ return
+
+ # Set a known variable that other tools can query so they can
+ # behave correctly during ninja generation.
+ env["GENERATING_NINJA"] = True
+
+ # These methods are no-op'd because they do not work during ninja
+ # generation, expected to do no work, or simply fail. All of which
+ # are slow in SCons. So we overwrite them with no logic.
+ SCons.Node.FS.File.make_ready = ninja_noop
+ SCons.Node.FS.File.prepare = ninja_noop
+ SCons.Node.FS.File.push_to_cache = ninja_noop
+ SCons.Executor.Executor.prepare = ninja_noop
+ SCons.Taskmaster.Task.prepare = ninja_noop
+ SCons.Node.FS.File.built = ninja_noop
+ SCons.Node.Node.visited = ninja_noop
+
+ # We make lstat a no-op because it is only used for SONAME
+ # symlinks which we're not producing.
+ SCons.Node.FS.LocalFS.lstat = ninja_noop
+
+ # This is a slow method that isn't memoized. We make it a noop
+ # since during our generation we will never use the results of
+ # this or change the results.
+ SCons.Node.FS.is_up_to_date = ninja_noop
+
+ # We overwrite stat and WhereIs with eternally memoized
+ # implementations. See the docstring of ninja_stat and
+ # ninja_whereis for detailed explanations.
+ SCons.Node.FS.LocalFS.stat = ninja_stat
+ SCons.Util.WhereIs = ninja_whereis
+
+ # Monkey patch get_csig and get_contents for some classes. It
+ # slows down the build significantly and we don't need contents or
+ # content signatures calculated when generating a ninja file since
+ # we're not doing any SCons caching or building.
+ SCons.Executor.Executor.get_contents = ninja_contents(
+ SCons.Executor.Executor.get_contents
+ )
+ SCons.Node.Alias.Alias.get_contents = ninja_contents(
+ SCons.Node.Alias.Alias.get_contents
+ )
+ SCons.Node.FS.File.get_contents = ninja_contents(SCons.Node.FS.File.get_contents)
+ SCons.Node.FS.File.get_csig = ninja_csig(SCons.Node.FS.File.get_csig)
+ SCons.Node.FS.Dir.get_csig = ninja_csig(SCons.Node.FS.Dir.get_csig)
+ SCons.Node.Alias.Alias.get_csig = ninja_csig(SCons.Node.Alias.Alias.get_csig)
+
+ # Ignore CHANGED_SOURCES and CHANGED_TARGETS. We don't want those
+ # to have effect in a generation pass because the generator
+ # shouldn't generate differently depending on the current local
+ # state. Without this, when generating on Windows, if you already
+ # had a foo.obj, you would omit foo.cpp from the response file. Do the same for UNCHANGED.
+ SCons.Executor.Executor._get_changed_sources = SCons.Executor.Executor._get_sources
+ SCons.Executor.Executor._get_changed_targets = SCons.Executor.Executor._get_targets
+ SCons.Executor.Executor._get_unchanged_sources = SCons.Executor.Executor._get_sources
+ SCons.Executor.Executor._get_unchanged_targets = SCons.Executor.Executor._get_targets
+
+ # Replace false action messages with nothing.
+ env["PRINT_CMD_LINE_FUNC"] = ninja_print_conf_log
+
+ # This reduces unnecessary subst_list calls to add the compiler to
+ # the implicit dependencies of targets. Since we encode full paths
+ # in our generated commands we do not need these slow subst calls
+ # as executing the command will fail if the file is not found
+ # where we expect it.
+ env["IMPLICIT_COMMAND_DEPENDENCIES"] = False
+
+ # This makes SCons more aggressively cache MD5 signatures in the
+ # SConsign file.
+ # TODO: WPD shouldn't this be set to 0?
+ env.SetOption("max_drift", 1)
+
+ # The Serial job class is SIGNIFICANTLY (almost twice as) faster
+ # than the Parallel job class for generating Ninja files. So we
+ # monkey the Jobs constructor to only use the Serial Job class.
+ SCons.Job.Jobs.__init__ = ninja_always_serial
+
+ ninja_syntax = importlib.import_module(".ninja_syntax", package='ninja')
+
+ if NINJA_STATE is None:
+ NINJA_STATE = NinjaState(env, ninja_file[0], ninja_syntax.Writer)
+
+
+ # TODO: this is hacking into scons, preferable if there were a less intrusive way
+ # We will subvert the normal builder execute to make sure all the ninja file is dependent
+ # on all targets generated from any builders
+ SCons_Builder_BuilderBase__execute = SCons.Builder.BuilderBase._execute
+
+ def NinjaBuilderExecute(self, env, target, source, overwarn={}, executor_kw={}):
+ # this ensures all environments in which a builder executes from will
+ # not create list actions for linking on windows
+ ninja_hack_linkcom(env)
+ targets = SCons_Builder_BuilderBase__execute(self, env, target, source, overwarn=overwarn, executor_kw=executor_kw)
+
+ if not SCons.Util.is_List(target):
+ target = [target]
+
+ for target in targets:
+ if target.check_attributes('ninja_file') is None and not target.is_conftest():
+ env.Depends(ninja_file, targets)
+ return targets
+ SCons.Builder.BuilderBase._execute = NinjaBuilderExecute
+
+ # Here we monkey patch the Task.execute method to not do a bunch of
+ # unnecessary work. If a build is a regular builder (i.e not a conftest and
+ # not our own Ninja builder) then we add it to the NINJA_STATE. Otherwise we
+ # build it like normal. This skips all of the caching work that this method
+ # would normally do since we aren't pulling any of these targets from the
+ # cache.
+ #
+ # In the future we may be able to use this to actually cache the build.ninja
+ # file once we have the upstream support for referencing SConscripts as File
+ # nodes.
+ def ninja_execute(self):
+
+ target = self.targets[0]
+ if target.get_env().get('NINJA_SKIP'):
+ return
+ if target.check_attributes('ninja_file') is None or not target.is_conftest:
+ NINJA_STATE.add_build(target)
+ else:
+ target.build()
+
+ SCons.Taskmaster.Task.execute = ninja_execute
+
+ # Make needs_execute always return true instead of determining out of
+ # date-ness.
+ SCons.Script.Main.BuildTask.needs_execute = lambda x: True
+
+ # We will eventually need to overwrite TempFileMunge to make it
+ # handle persistent tempfiles or get an upstreamed change to add
+ # some configurability to it's behavior in regards to tempfiles.
+ #
+ # Set all three environment variables that Python's
+ # tempfile.mkstemp looks at as it behaves differently on different
+ # platforms and versions of Python.
+ # build_dir = env.subst("$NINJA_DIR")
+ # if build_dir == "":
+ # build_dir = "."
+ # os.environ["TMPDIR"] = env.Dir("{}/.response_files".format(build_dir)).get_abspath()
+ # os.environ["TEMP"] = os.environ["TMPDIR"]
+ # os.environ["TMP"] = os.environ["TMPDIR"]
+ # if not os.path.isdir(os.environ["TMPDIR"]):
+ # env.Execute(SCons.Defaults.Mkdir(os.environ["TMPDIR"]))
+
+ env['TEMPFILEDIR'] = "$NINJA_DIR/.response_files"
+ env["TEMPFILE"] = NinjaNoResponseFiles
diff --git a/SCons/Tool/ninja/ninja.xml b/SCons/Tool/ninja/ninja.xml
new file mode 100644
index 0000000..b30732f
--- /dev/null
+++ b/SCons/Tool/ninja/ninja.xml
@@ -0,0 +1,326 @@
+<?xml version="1.0"?>
+<!--
+ 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 file is processed by the bin/SConsDoc.py module.
+See its __doc__ string for a discussion of the format.
+-->
+
+<!DOCTYPE sconsdoc [
+ <!ENTITY % scons SYSTEM '../../../doc/scons.mod'>
+ %scons;
+ <!ENTITY % builders-mod SYSTEM '../../../doc/generated/builders.mod'>
+ %builders-mod;
+ <!ENTITY % functions-mod SYSTEM '../../../doc/generated/functions.mod'>
+ %functions-mod;
+ <!ENTITY % tools-mod SYSTEM '../../../doc/generated/tools.mod'>
+ %tools-mod;
+ <!ENTITY % variables-mod SYSTEM '../../../doc/generated/variables.mod'>
+ %variables-mod;
+ ]>
+
+<sconsdoc xmlns="http://www.scons.org/dbxsd/v1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd">
+
+ <tool name="ninja">
+ <summary>
+ <para>
+ Sets up &b-link-Ninja; builder which generates a ninja build file, and then optionally runs &ninja;.
+ </para>
+ <note>
+ <para>This is an experimental feature.
+ </para>
+ <para>This functionality is subject to change and/or removal without deprecation cycle.</para>
+ </note>
+
+ </summary>
+ <sets>
+ <item>NINJA_DISABLE_AUTO_RUN</item>
+ <item>NINJA_ALIAS_NAME</item>
+ <item>NINJA_DIR</item>
+ <item>NINJA_COMPDB_EXPAND</item>
+ <item>NINJA_ENV_VAR_CACHE</item>
+ <item>NINJA_FILE_NAME</item>
+ <item>NINJA_GENERATED_SOURCE_SUFFIXES</item>
+ <item>NINJA_MSVC_DEPS_PREFIX</item>
+ <item>NINJA_POOL</item>
+ <item>NINJA_REGENERATE_DEPS</item>
+ <item>NINJA_SYNTAX</item>
+ <item>_NINJA_REGENERATE_DEPS_FUNC</item>
+ <item>__NINJA_NO</item>
+ <item>IMPLICIT_COMMAND_DEPENDENCIES</item>
+
+
+ <!-- TODO: Document these -->
+ <!-- <item>NINJA_RULES</item>-->
+ <!-- <item>NINJA_POOLS</item>-->
+ <!-- <item>NINJA</item>-->
+ <!-- <item>GENERATING_NINJA</item>-->
+ <!-- <item>NINJA_DIR</item>-->
+
+ </sets>
+
+ <uses>
+ <item>AR</item>
+ <item>ARCOM</item>
+ <item>ARFLAGS</item>
+ <item>CC</item>
+ <item>CCCOM</item>
+ <item>CCFLAGS</item>
+ <item>CXX</item>
+ <item>CXXCOM</item>
+ <item>ESCAPE</item>
+ <item>LINK</item>
+ <item>LINKCOM</item>
+ <item>PLATFORM</item>
+ <item>RANLIB</item>
+ <item>RANLIBCOM</item>
+ <item>SHCCCOM</item>
+ <item>SHCXXCOM</item>
+ <item>SHLINK</item>
+ <item>SHLINKCOM</item>
+ <item>PROGSUFFIX</item>
+ <item>PRINT_CMD_LINE_FUNC</item>
+ <!-- <item>TEMPFILE</item>-->
+
+ <!-- TODO: Document these
+ <item>NINJA_MAX_JOBS</item>
+ <item>NINJA_SKIP</item>
+ <item>NINJA_CUSTOM_HANDLERS</item>
+-->
+
+ </uses>
+ </tool>
+
+ <builder name="Ninja">
+ <summary>
+ <para>
+ &b-Ninja; is a special builder which
+ adds a target to create a ninja build file.
+ The builder does not require any source files to be specified.
+ </para>
+ <note>
+ <para>This is an experimental feature. To enable it you must use one of the following methods
+ </para>
+
+<!-- NOTE DO NOT INDENT THE FOLLOWING AS IT WILL ALTER THE FORMATTING-->
+ <example_commands>
+ # On the command line
+ --experimental=ninja
+
+ # Or in your SConstruct
+ SetOption('experimental', 'ninja')
+ </example_commands>
+
+ <para>This functionality is subject to change and/or removal without deprecation cycle.</para>
+
+
+ <para>
+ To use this tool you must install pypi's <ulink url="https://pypi.org/project/ninja/">ninja
+ package</ulink>.
+ This can be done via
+ <command>pip install ninja</command>
+ </para>
+
+ </note>
+
+ <para>
+ If called with no arguments,
+ the builder will default to a target name of
+ <filename>ninja.build</filename>.
+ </para>
+ <para>
+ If called with a single positional argument,
+ &scons; will "deduce" the target name from that source
+ argument, giving it the same name, and then
+ ignore the source.
+ This is the usual way to call the builder if a
+ non-default target name is wanted.
+ </para>
+ <para>
+ If called with either the
+ <parameter>target=</parameter>
+ or <parameter>source=</parameter> keyword arguments,
+ the value of the argument is taken as the target name.
+ If called with both, the
+ <parameter>target=</parameter>
+ value is used and <parameter>source=</parameter> is ignored.
+ If called with multiple sources,
+ the source list will be ignored,
+ since there is no way to deduce what the intent was;
+ in this case the default target name will be used.
+ </para>
+ <para>
+ <emphasis>Available since &scons; 4.2.</emphasis>
+ </para>
+ </summary>
+ </builder>
+
+
+ <cvar name="NINJA_GENERATED_SOURCE_SUFFIXES">
+ <summary>
+ <para>
+ The list of source file suffixes which are generated by SCons build steps.
+ All source files which match these suffixes will be added to the _generated_sources alias in the output
+ <filename>ninja.build</filename> file.
+ Then all other source files will be made to depend on this in the <filename>ninja.build</filename> file, forcing the
+ generated sources to be built first.
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="NINJA_MSVC_DEPS_PREFIX">
+ <summary>
+ <para>
+ This propagates directly into the generated <filename>ninja.build</filename> file.
+ From Ninja's docs
+ <quote>defines the string which should be stripped from msvc’s /showIncludes output</quote>
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="NINJA_DIR">
+ <summary>
+ <para>
+ This propagates directly into the generated <filename>ninja.build</filename> file.
+ From Ninja's docs:
+ <blockquote>
+ <para>
+ builddir
+ A directory for some Ninja output files. ... (You can also store other build output in this
+ directory.)
+ </para>
+ </blockquote>
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="NINJA_REGENERATE_DEPS">
+ <summary>
+ <para>
+ A generator function used to create a ninja depsfile which includes all the files which would require
+ SCons to be invoked if they change.
+ Or a list of said files.
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="NINJA_COMPDB_EXPAND">
+ <summary>
+ <para>
+ Boolean value (True|False) to instruct ninja to expand the command line arguments normally put into
+ response files.
+ This prevents lines in the compilation database like <quote>gcc @rsp_file</quote> and instead yields
+ <quote>gcc -c -o myfile.o myfile.c -Ia -DXYZ</quote>
+ </para>
+ <para>
+ Ninja's compdb tool added the <quote>-x</quote> flag in Ninja V1.9.0
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="NINJA_ENV_VAR_CACHE">
+ <summary>
+ <para>
+ A string that sets the environment for any environment variables that
+ differ between the OS environment and the SCons command ENV.
+
+ It will be compatible with the default shell of the operating system.
+
+ If not explicitly specified, SCons will generate this dynamically from the Environment()'s 'ENV'
+ <quote>env['ENV']</quote>
+ where those values differ from the existing shell..
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="NINJA_POOL">
+ <summary>
+ <para>
+ Set the <quote>ninja_pool</quote> for this or all targets in scope for this env var.
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="NINJA_DISABLE_AUTO_RUN">
+ <summary>
+ <para>
+ Boolean (True|False). Default: False
+ When True, SCons will not run ninja automatically after creating the <filename>ninja.build</filename> file.
+
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="__NINJA_NO">
+ <summary>
+ <para>
+ Internal flag. Used to tell SCons whether or not to try to import pypi's ninja python package.
+ This is set to True when being called by Ninja?
+ </para>
+ </summary>
+ </cvar>
+
+
+ <cvar name="NINJA_FILE_NAME">
+ <summary>
+ <para>
+ The filename for the generated Ninja build file defaults to
+ <filename>ninja.build</filename>
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="NINJA_ALIAS_NAME">
+ <summary>
+ <para>
+ Name of the Alias() which is will cause SCons to create the <filename>ninja.build</filename> file, and
+ then (optionally) run ninja.
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="NINJA_SYNTAX">
+ <summary>
+ <para>
+ Theres also NINJA_SYNTAX which is the path to a custom ninja_syntax.py file which is used in generation.
+ The tool currently assumes you have ninja installed through pip, and grabs the syntax file from that
+ installation if none specified.
+ </para>
+ </summary>
+ </cvar>
+
+ <cvar name="_NINJA_REGENERATE_DEPS_FUNC">
+ <summary>
+ <para>
+ Internal value used to specify the function to call with argument env to generate the list of files
+ which if changed would require the ninja file to be regenerated.
+ </para>
+ </summary>
+ </cvar>
+
+
+</sconsdoc>
diff --git a/SCons/Tool/swig.py b/SCons/Tool/swig.py
index fa9d93b..ff0c80d 100644
--- a/SCons/Tool/swig.py
+++ b/SCons/Tool/swig.py
@@ -29,15 +29,15 @@ selection method.
"""
import os.path
-import sys
import re
import subprocess
+import sys
import SCons.Action
import SCons.Defaults
+import SCons.Node
import SCons.Tool
import SCons.Util
-import SCons.Node
import SCons.Warnings
verbose = False
@@ -194,17 +194,19 @@ def generate(env):
if 'SWIG' not in env:
env['SWIG'] = env.Detect(swigs) or swigs[0]
- env['SWIGVERSION'] = _get_swig_version(env, env['SWIG'])
- env['SWIGFLAGS'] = SCons.Util.CLVar('')
+
+ env['SWIGVERSION'] = _get_swig_version(env, env['SWIG'])
+ env['SWIGFLAGS'] = SCons.Util.CLVar('')
env['SWIGDIRECTORSUFFIX'] = '_wrap.h'
- env['SWIGCFILESUFFIX'] = '_wrap$CFILESUFFIX'
+ env['SWIGCFILESUFFIX'] = '_wrap$CFILESUFFIX'
env['SWIGCXXFILESUFFIX'] = '_wrap$CXXFILESUFFIX'
- env['_SWIGOUTDIR'] = r'${"-outdir \"%s\"" % SWIGOUTDIR}'
- env['SWIGPATH'] = []
- env['SWIGINCPREFIX'] = '-I'
- env['SWIGINCSUFFIX'] = ''
- env['_SWIGINCFLAGS'] = '$( ${_concat(SWIGINCPREFIX, SWIGPATH, SWIGINCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)'
- env['SWIGCOM'] = '$SWIG -o $TARGET ${_SWIGOUTDIR} ${_SWIGINCFLAGS} $SWIGFLAGS $SOURCES'
+ env['_SWIGOUTDIR'] = r'${"-outdir \"%s\"" % SWIGOUTDIR}'
+ env['SWIGPATH'] = []
+ env['SWIGINCPREFIX'] = '-I'
+ env['SWIGINCSUFFIX'] = ''
+ env['_SWIGINCFLAGS'] = '${_concat(SWIGINCPREFIX, SWIGPATH, SWIGINCSUFFIX,' \
+ '__env__, RDirs, TARGET, SOURCE, affect_signature=False)}'
+ env['SWIGCOM'] = '$SWIG -o $TARGET ${_SWIGOUTDIR} ${_SWIGINCFLAGS} $SWIGFLAGS $SOURCES'
def exists(env):
swig = env.get('SWIG') or env.Detect(['swig'])
diff --git a/SCons/__init__.py b/SCons/__init__.py
index bc0f428..cd521d1 100644
--- a/SCons/__init__.py
+++ b/SCons/__init__.py
@@ -1,9 +1,9 @@
__version__="4.1.1a"
__copyright__="Copyright (c) 2001 - 2021 The SCons Foundation"
__developer__="bdbaddog"
-__date__="2021-04-18 22:22:37"
+__date__="2021-05-30 21:33:41"
__buildsys__="ProDog2020"
-__revision__="1850bdd13c7668aac493b9f5b896578813e60ca9"
-__build__="1850bdd13c7668aac493b9f5b896578813e60ca9"
+__revision__="b4c7a0966c36d37d0c2ea125dd98bff0061e28f2"
+__build__="b4c7a0966c36d37d0c2ea125dd98bff0061e28f2"
# make sure compatibility is always in place
import SCons.compat # noqa \ No newline at end of file
diff --git a/bin/SConsDoc.py b/bin/SConsDoc.py
index baffbdc..a465057 100644
--- a/bin/SConsDoc.py
+++ b/bin/SConsDoc.py
@@ -364,9 +364,18 @@ class TreeFactory:
doc.xinclude()
try:
TreeFactory.xmlschema.assertValid(doc)
+ except etree.XMLSchemaValidateError as e:
+ print("ERROR: %s fails to validate:" % fpath)
+ print(e)
+ print(e.error_log.last_error.message)
+ print("In file: [%s]" % e.error_log.last_error.filename)
+ print("Line : %d" % e.error_log.last_error.line)
+ return False
+
except Exception as e:
print("ERROR: %s fails to validate:" % fpath)
print(e)
+
return False
return True
diff --git a/doc/generated/builders.gen b/doc/generated/builders.gen
index 14508ea..5f8e2af 100644
--- a/doc/generated/builders.gen
+++ b/doc/generated/builders.gen
@@ -1221,6 +1221,70 @@ env.MSVSSolution(
</example_commands>
</listitem>
</varlistentry>
+ <varlistentry id="b-Ninja">
+ <term><function>Ninja</function>()</term>
+ <term><replaceable>env</replaceable>.<methodname>Ninja</methodname>()</term>
+ <listitem><para>
+ &b-Ninja; is a special builder which
+ adds a target to create a ninja build file.
+ The builder does not require any source files to be specified.
+ </para>
+ <note>
+ <para>This is an experimental feature. To enable it you must use one of the following methods
+ </para>
+
+<!-- NOTE DO NOT INDENT THE FOLLOWING AS IT WILL ALTER THE FORMATTING-->
+ <example_commands>
+ # On the command line
+ --experimental=ninja
+
+ # Or in your SConstruct
+ SetOption('experimental', 'ninja')
+ </example_commands>
+
+ <para>This functionality is subject to change and/or removal without deprecation cycle.</para>
+
+
+ <para>
+ To use this tool you must install pypi's <ulink url="https://pypi.org/project/ninja/">ninja
+ package</ulink>.
+ This can be done via
+ <command>pip install ninja</command>
+ </para>
+
+ </note>
+
+ <para>
+ If called with no arguments,
+ the builder will default to a target name of
+ <filename>ninja.build</filename>.
+ </para>
+ <para>
+ If called with a single positional argument,
+ &scons; will "deduce" the target name from that source
+ argument, giving it the same name, and then
+ ignore the source.
+ This is the usual way to call the builder if a
+ non-default target name is wanted.
+ </para>
+ <para>
+ If called with either the
+ <parameter>target=</parameter>
+ or <parameter>source=</parameter> keyword arguments,
+ the value of the argument is taken as the target name.
+ If called with both, the
+ <parameter>target=</parameter>
+ value is used and <parameter>source=</parameter> is ignored.
+ If called with multiple sources,
+ the source list will be ignored,
+ since there is no way to deduce what the intent was;
+ in this case the default target name will be used.
+ </para>
+ <para>
+ <emphasis>Available since &scons; 4.2.</emphasis>
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry id="b-Object">
<term><function>Object</function>()</term>
<term><replaceable>env</replaceable>.<methodname>Object</methodname>()</term>
diff --git a/doc/generated/builders.mod b/doc/generated/builders.mod
index 4835118..3542b99 100644
--- a/doc/generated/builders.mod
+++ b/doc/generated/builders.mod
@@ -37,6 +37,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY b-MOFiles "<function xmlns='http://www.scons.org/dbxsd/v1.0'>MOFiles</function>">
<!ENTITY b-MSVSProject "<function xmlns='http://www.scons.org/dbxsd/v1.0'>MSVSProject</function>">
<!ENTITY b-MSVSSolution "<function xmlns='http://www.scons.org/dbxsd/v1.0'>MSVSSolution</function>">
+<!ENTITY b-Ninja "<function xmlns='http://www.scons.org/dbxsd/v1.0'>Ninja</function>">
<!ENTITY b-Object "<function xmlns='http://www.scons.org/dbxsd/v1.0'>Object</function>">
<!ENTITY b-Package "<function xmlns='http://www.scons.org/dbxsd/v1.0'>Package</function>">
<!ENTITY b-PCH "<function xmlns='http://www.scons.org/dbxsd/v1.0'>PCH</function>">
@@ -94,6 +95,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY b-env-MOFiles "<function xmlns='http://www.scons.org/dbxsd/v1.0'>env.MOFiles</function>">
<!ENTITY b-env-MSVSProject "<function xmlns='http://www.scons.org/dbxsd/v1.0'>env.MSVSProject</function>">
<!ENTITY b-env-MSVSSolution "<function xmlns='http://www.scons.org/dbxsd/v1.0'>env.MSVSSolution</function>">
+<!ENTITY b-env-Ninja "<function xmlns='http://www.scons.org/dbxsd/v1.0'>env.Ninja</function>">
<!ENTITY b-env-Object "<function xmlns='http://www.scons.org/dbxsd/v1.0'>env.Object</function>">
<!ENTITY b-env-Package "<function xmlns='http://www.scons.org/dbxsd/v1.0'>env.Package</function>">
<!ENTITY b-env-PCH "<function xmlns='http://www.scons.org/dbxsd/v1.0'>env.PCH</function>">
@@ -157,6 +159,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY b-link-MOFiles "<link linkend='b-MOFiles' xmlns='http://www.scons.org/dbxsd/v1.0'><function>MOFiles</function></link>">
<!ENTITY b-link-MSVSProject "<link linkend='b-MSVSProject' xmlns='http://www.scons.org/dbxsd/v1.0'><function>MSVSProject</function></link>">
<!ENTITY b-link-MSVSSolution "<link linkend='b-MSVSSolution' xmlns='http://www.scons.org/dbxsd/v1.0'><function>MSVSSolution</function></link>">
+<!ENTITY b-link-Ninja "<link linkend='b-Ninja' xmlns='http://www.scons.org/dbxsd/v1.0'><function>Ninja</function></link>">
<!ENTITY b-link-Object "<link linkend='b-Object' xmlns='http://www.scons.org/dbxsd/v1.0'><function>Object</function></link>">
<!ENTITY b-link-Package "<link linkend='b-Package' xmlns='http://www.scons.org/dbxsd/v1.0'><function>Package</function></link>">
<!ENTITY b-link-PCH "<link linkend='b-PCH' xmlns='http://www.scons.org/dbxsd/v1.0'><function>PCH</function></link>">
@@ -214,6 +217,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY b-link-env-MOFiles "<link linkend='b-MOFiles' xmlns='http://www.scons.org/dbxsd/v1.0'><function>env.MOFiles</function></link>">
<!ENTITY b-link-env-MSVSProject "<link linkend='b-MSVSProject' xmlns='http://www.scons.org/dbxsd/v1.0'><function>env.MSVSProject</function></link>">
<!ENTITY b-link-env-MSVSSolution "<link linkend='b-MSVSSolution' xmlns='http://www.scons.org/dbxsd/v1.0'><function>env.MSVSSolution</function></link>">
+<!ENTITY b-link-env-Ninja "<link linkend='b-Ninja' xmlns='http://www.scons.org/dbxsd/v1.0'><function>env.Ninja</function></link>">
<!ENTITY b-link-env-Object "<link linkend='b-Object' xmlns='http://www.scons.org/dbxsd/v1.0'><function>env.Object</function></link>">
<!ENTITY b-link-env-Package "<link linkend='b-Package' xmlns='http://www.scons.org/dbxsd/v1.0'><function>env.Package</function></link>">
<!ENTITY b-link-env-PCH "<link linkend='b-PCH' xmlns='http://www.scons.org/dbxsd/v1.0'><function>env.PCH</function></link>">
diff --git a/doc/generated/examples/caching_ex-random_1.xml b/doc/generated/examples/caching_ex-random_1.xml
index a5593d3..cc69bf1 100644
--- a/doc/generated/examples/caching_ex-random_1.xml
+++ b/doc/generated/examples/caching_ex-random_1.xml
@@ -1,8 +1,8 @@
<screen xmlns="http://www.scons.org/dbxsd/v1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd">% <userinput>scons -Q</userinput>
-cc -o f2.o -c f2.c
cc -o f1.o -c f1.c
-cc -o f3.o -c f3.c
cc -o f4.o -c f4.c
+cc -o f3.o -c f3.c
+cc -o f2.o -c f2.c
cc -o f5.o -c f5.c
cc -o prog f1.o f2.o f3.o f4.o f5.o
</screen>
diff --git a/doc/generated/examples/troubleshoot_Dump_1.xml b/doc/generated/examples/troubleshoot_Dump_1.xml
index 8f4e624..f099d41 100644
--- a/doc/generated/examples/troubleshoot_Dump_1.xml
+++ b/doc/generated/examples/troubleshoot_Dump_1.xml
@@ -62,12 +62,12 @@ scons: Reading SConscript files ...
'TEMPFILEARGJOIN': ' ',
'TEMPFILEPREFIX': '@',
'TOOLS': ['install', 'install'],
- '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, '
- '__env__)}',
- '_CPPINCFLAGS': '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, '
- 'TARGET, SOURCE)} $)',
- '_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, '
- 'RDirs, TARGET, SOURCE)} $)',
+ '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__, '
+ 'TARGET, SOURCE)}',
+ '_CPPINCFLAGS': '${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, '
+ 'TARGET, SOURCE, affect_signature=False)}',
+ '_LIBDIRFLAGS': '${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, '
+ 'RDirs, TARGET, SOURCE, affect_signature=False)}',
'_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
'__DRPATH': '$_DRPATH',
'__DSHLIBVERSIONFLAGS': '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}',
diff --git a/doc/generated/examples/troubleshoot_Dump_2.xml b/doc/generated/examples/troubleshoot_Dump_2.xml
index f2fc5e6..40163a0 100644
--- a/doc/generated/examples/troubleshoot_Dump_2.xml
+++ b/doc/generated/examples/troubleshoot_Dump_2.xml
@@ -107,12 +107,12 @@ scons: Reading SConscript files ...
'TOOLS': ['msvc', 'install', 'install'],
'VSWHERE': None,
'_CCCOMCOM': '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS $CCPCHFLAGS $CCPDBFLAGS',
- '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, '
- '__env__)}',
- '_CPPINCFLAGS': '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, '
- 'TARGET, SOURCE)} $)',
- '_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, '
- 'RDirs, TARGET, SOURCE)} $)',
+ '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__, '
+ 'TARGET, SOURCE)}',
+ '_CPPINCFLAGS': '${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, '
+ 'TARGET, SOURCE, affect_signature=False)}',
+ '_LIBDIRFLAGS': '${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, '
+ 'RDirs, TARGET, SOURCE, affect_signature=False)}',
'_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
'_MSVC_OUTPUT_FLAG': &lt;function msvc_output_flag at 0x700000&gt;,
'__DSHLIBVERSIONFLAGS': '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}',
diff --git a/doc/generated/examples/troubleshoot_stacktrace_2.xml b/doc/generated/examples/troubleshoot_stacktrace_2.xml
index 8e8809c..75dd6fb 100644
--- a/doc/generated/examples/troubleshoot_stacktrace_2.xml
+++ b/doc/generated/examples/troubleshoot_stacktrace_2.xml
@@ -3,10 +3,10 @@ scons: *** [prog.o] Source `prog.c' not found, needed by target `prog.o'.
scons: internal stack trace:
File "SCons/Job.py", line 195, in start
task.prepare()
- File "SCons/Script/Main.py", line 179, in prepare
+ File "SCons/Script/Main.py", line 178, in prepare
return SCons.Taskmaster.OutOfDateTask.prepare(self)
File "SCons/Taskmaster.py", line 186, in prepare
executor.prepare()
- File "SCons/Executor.py", line 424, in prepare
+ File "SCons/Executor.py", line 418, in prepare
raise SCons.Errors.StopError(msg % (s, self.batches[0].targets[0]))
</screen>
diff --git a/doc/generated/functions.gen b/doc/generated/functions.gen
index a43ed1a..f48c088 100644
--- a/doc/generated/functions.gen
+++ b/doc/generated/functions.gen
@@ -2124,261 +2124,242 @@ file is found.
<term><replaceable>env</replaceable>.<methodname>GetOption</methodname>(<parameter>name</parameter>)</term>
<listitem><para>
This function provides a way to query the value of
-SCons options set on scons command line
-(or set using the
-&f-link-SetOption;
-function).
-The options supported are:
+options which can be set via the command line or using the
+&f-link-SetOption; function.
</para>
-
-<para>
-<variablelist>
-<varlistentry>
-<term><literal>cache_debug</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--cache-debug</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>cache_disable</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--cache-disable</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>cache_force</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--cache-force</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>cache_show</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--cache-show</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>clean</literal></term>
-<listitem>
-<para>
-which corresponds to <option>-c</option>, <option>--clean</option>
-and <option>--remove</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>config</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--config</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>directory</literal></term>
-<listitem>
-<para>
-which corresponds to <option>-C</option> and <option>--directory</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>diskcheck</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--diskcheck</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>duplicate</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--duplicate</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>file</literal></term>
-<listitem>
-<para>
-which corresponds to <option>-f</option>, <option>--file</option>, <option>--makefile</option> and <option>--sconstruct</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>help</literal></term>
-<listitem>
-<para>
-which corresponds to <option>-h</option> and <option>--help</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>ignore_errors</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--ignore-errors</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>implicit_cache</literal></term>
-<listitem>
<para>
-which corresponds to <option>--implicit-cache</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>implicit_deps_changed</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--implicit-deps-changed</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>implicit_deps_unchanged</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--implicit-deps-unchanged</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>interactive</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--interact</option> and <option>--interactive</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>keep_going</literal></term>
-<listitem>
-<para>
-which corresponds to <option>-k</option> and <option>--keep-going</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>max_drift</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--max-drift</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>no_exec</literal></term>
-<listitem>
-<para>
-which corresponds to <option>-n</option>,
-<option>--no-exec</option>, <option>--just-print</option>,
-<option>--dry-run</option> and <option>--recon</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>no_site_dir</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--no-site-dir</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>num_jobs</literal></term>
-<listitem>
-<para>
-which corresponds to <option>-j</option> and <option>--jobs</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>profile_file</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--profile</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>question</literal></term>
-<listitem>
-<para>
-which corresponds to <option>-q</option> and <option>--question</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>random</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--random</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>repository</literal></term>
-<listitem>
-<para>
-which corresponds to <option>-Y</option>, <option>--repository</option> and <option>--srcdir</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>silent</literal></term>
-<listitem>
-<para>
-which corresponds to <option>-s</option>, <option>--silent</option> and <option>--quiet</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>site_dir</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--site-dir</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>stack_size</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--stack-size</option>;
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>taskmastertrace_file</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--taskmastertrace</option>; and
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term><literal>warn</literal></term>
-<listitem>
-<para>
-which corresponds to <option>--warn</option> and <option>--warning</option>.
-</para>
-</listitem>
-</varlistentry>
-</variablelist>
+<parameter>name</parameter> can be an entry from the following table,
+which shows the corresponding command line arguments
+that could affect the value.
+<parameter>name</parameter> can be also be the destination
+variable name from a project-specific option added using the
+&f-link-AddOption; function, as long as the addition
+happens prior to the &f-GetOption; call in the SConscript files.
</para>
+<informaltable rowsep="1" colsep="1" frame="topbot">
+<tgroup cols="3">
+<thead>
+<row>
+ <entry align="left">Query name</entry>
+ <entry align="left">Command-line options</entry>
+ <entry align="left">Notes</entry>
+</row>
+</thead>
+
+<tbody>
+<row>
+ <entry><varname>cache_debug</varname></entry>
+ <entry><option>--cache-debug</option></entry>
+</row>
+<row>
+ <entry><varname>cache_disable</varname></entry>
+ <entry>
+ <option>--cache-disable</option>,
+ <option>--no-cache</option>
+ </entry>
+</row>
+<row>
+ <entry><varname>cache_force</varname></entry>
+ <entry>
+ <option>--cache-force</option>,
+ <option>--cache-populate</option>
+ </entry>
+</row>
+<row>
+ <entry><varname>cache_readonly</varname></entry>
+ <entry><option>--cache-readonly</option></entry>
+</row>
+<row>
+ <entry><varname>cache_show</varname></entry>
+ <entry><option>--cache-show</option></entry>
+</row>
+<row>
+ <entry><varname>clean</varname></entry>
+ <entry>
+ <option>-c</option>,
+ <option>--clean</option>,
+ <option>--remove</option>
+ </entry>
+</row>
+<row>
+ <entry><varname>climb_up</varname></entry>
+ <entry>
+ <option>-D</option>
+ <option>-U</option>
+ <option>-u</option>
+ <option>--up</option>
+ <option>--search_up</option>
+ </entry>
+</row>
+<row>
+ <entry><varname>config</varname></entry>
+ <entry><option>--config</option></entry>
+</row>
+<row>
+ <entry><varname>debug</varname></entry>
+ <entry><option>--debug</option></entry>
+</row>
+<row>
+ <entry><varname>directory</varname></entry>
+ <entry><option>-C</option>, <option>--directory</option></entry>
+</row>
+<row>
+ <entry><varname>diskcheck</varname></entry>
+ <entry><option>--diskcheck</option></entry>
+</row>
+<row>
+ <entry><varname>duplicate</varname></entry>
+ <entry><option>--duplicate</option></entry>
+</row>
+<row>
+ <entry><varname>enable_virtualenv</varname></entry>
+ <entry><option>--enable-virtualenv</option></entry>
+</row>
+<row>
+ <entry><varname>experimental</varname></entry>
+ <entry><option>--experimental</option></entry>
+ <entry><emphasis>since 4.2</emphasis></entry>
+</row>
+<row>
+ <entry><varname>file</varname></entry>
+ <entry>
+ <option>-f</option>,
+ <option>--file</option>,
+ <option>--makefile</option>,
+ <option>--sconstruct</option>
+ </entry>
+</row>
+<row>
+ <entry><varname>hash_format</varname></entry>
+ <entry><option>--hash-format</option></entry>
+ <entry><emphasis>since 4.2</emphasis></entry>
+</row>
+<row>
+ <entry><varname>help</varname></entry>
+ <entry><option>-h</option>, <option>--help</option></entry>
+</row>
+<row>
+ <entry><varname>ignore_errors</varname></entry>
+ <entry><option>-i</option>, <option>--ignore-errors</option></entry>
+</row>
+<row>
+ <entry><varname>ignore_virtualenv</varname></entry>
+ <entry><option>--ignore-virtualenv</option></entry>
+</row>
+<row>
+ <entry><varname>implicit_cache</varname></entry>
+ <entry><option>--implicit-cache</option></entry>
+</row>
+<row>
+ <entry><varname>implicit_deps_changed</varname></entry>
+ <entry><option>--implicit-deps-changed</option></entry>
+</row>
+<row>
+ <entry><varname>implicit_deps_unchanged</varname></entry>
+ <entry><option>--implicit-deps-unchanged</option></entry>
+</row>
+<row>
+ <entry><varname>include_dir</varname></entry>
+ <entry><option>-I</option>, <option>--include-dir</option></entry>
+</row>
+<row>
+ <entry><varname>install_sandbox</varname></entry>
+ <entry><option>--install-sandbox</option></entry>
+ <entry>Available only if the &t-link-install; tool has been called</entry>
+</row>
+<row>
+ <entry><varname>keep_going</varname></entry>
+ <entry><option>-k</option>, <option>--keep-going</option></entry>
+</row>
+<row>
+ <entry><varname>max_drift</varname></entry>
+ <entry><option>--max-drift</option></entry>
+</row>
+<row>
+ <entry><varname>md5_chunksize</varname></entry>
+ <entry>
+ <option>--hash-chunksize</option>,
+ <option>--md5-chunksize</option>
+ </entry>
+ <entry><emphasis><option>--hash-chunksize</option> since 4.2</emphasis></entry>
+</row>
+<row>
+ <entry><varname>no_exec</varname></entry>
+ <entry>
+ <option>-n</option>,
+ <option>--no-exec</option>,
+ <option>--just-print</option>,
+ <option>--dry-run</option>,
+ <option>--recon</option>
+ </entry>
+</row>
+<row>
+ <entry><varname>no_progress</varname></entry>
+ <entry><option>-Q</option></entry>
+</row>
+<row>
+ <entry><varname>num_jobs</varname></entry>
+ <entry><option>-j</option>, <option>--jobs</option></entry>
+</row>
+<row>
+ <entry><varname>package_type</varname></entry>
+ <entry><option>--package-type</option></entry>
+ <entry>Available only if the &t-link-packaging; tool has been called</entry>
+</row>
+<row>
+ <entry><varname>profile_file</varname></entry>
+ <entry><option>--profile</option></entry>
+</row>
+<row>
+ <entry><varname>question</varname></entry>
+ <entry><option>-q</option>, <option>--question</option></entry>
+</row>
+<row>
+ <entry><varname>random</varname></entry>
+ <entry><option>--random</option></entry>
+</row>
+<row>
+ <entry><varname>repository</varname></entry>
+ <entry>
+ <option>-Y</option>,
+ <option>--repository</option>,
+ <option>--srcdir</option>
+ </entry>
+</row>
+<row>
+ <entry><varname>silent</varname></entry>
+ <entry>
+ <option>-s</option>,
+ <option>--silent</option>,
+ <option>--quiet</option>
+ </entry>
+</row>
+<row>
+ <entry><varname>site_dir</varname></entry>
+ <entry><option>--site-dir</option>, <option>--no-site-dir</option></entry>
+</row>
+<row>
+ <entry><varname>stack_size</varname></entry>
+ <entry><option>--stack-size</option></entry>
+</row>
+<row>
+ <entry><varname>taskmastertrace_file</varname></entry>
+ <entry><option>--taskmastertrace</option></entry>
+</row>
+<row>
+ <entry><varname>tree_printers</varname></entry>
+ <entry><option>--tree</option></entry>
+</row>
+<row>
+ <entry><varname>warn</varname></entry>
+ <entry><option>--warn</option>, <option>--warning</option></entry>
+</row>
+
+</tbody>
+</tgroup>
+</informaltable>
+
<para>
See the documentation for the
corresponding command line option for information about each specific
@@ -2639,12 +2620,12 @@ Returns a list of the target Node or Nodes.
<varlistentry id="f-MergeFlags">
<term><replaceable>env</replaceable>.<methodname>MergeFlags</methodname>(<parameter>arg, [unique]</parameter>)</term>
<listitem><para>
-Merges the specified
+Merges values from
<parameter>arg</parameter>
-values to the construction environment's construction variables.
-If the
+into &consvars; in the current &consenv;.
+If
<parameter>arg</parameter>
-argument is not a dictionary,
+is not a dictionary,
it is converted to one by calling
&f-link-env-ParseFlags;
on the argument
@@ -2662,15 +2643,15 @@ not as separate arguments to
By default,
duplicate values are eliminated;
you can, however, specify
-<literal>unique=0</literal>
+<literal>unique=False</literal>
to allow duplicate
values to be added.
When eliminating duplicate values,
-any construction variables that end with
+any &consvars; that end with
the string
<literal>PATH</literal>
keep the left-most unique value.
-All other construction variables keep
+All other &consvars; keep
the right-most unique value.
</para>
@@ -2791,38 +2772,55 @@ NoClean(env.Program('hello', 'hello.c'))
<varlistentry id="f-ParseConfig">
<term><replaceable>env</replaceable>.<methodname>ParseConfig</methodname>(<parameter>command, [function, unique]</parameter>)</term>
<listitem><para>
-Calls the specified
-<parameter>function</parameter>
-to modify the environment as specified by the output of
-<parameter>command</parameter>.
-The default
-<parameter>function</parameter>
-is
-&f-link-env-MergeFlags;,
-which expects the output of a typical
-<application>*-config</application>
-command
-(for example,
-<application>gtk-config</application>)
-and adds the options
-to the appropriate construction variables.
+Updates the current &consenv; with the values extracted
+from the output from running external <parameter>command</parameter>,
+by calling a helper function <parameter>function</parameter>
+which understands
+the output of <parameter>command</parameter>.
+<parameter>command</parameter> may be a string
+or a list of strings representing the command and
+its arguments.
+If <parameter>function</parameter>
+is not given,
+&f-link-env-MergeFlags; is used.
By default,
duplicate values are not
added to any construction variables;
you can specify
-<literal>unique=0</literal>
-to allow duplicate
-values to be added.
+<parameter>unique=False</parameter>
+to allow duplicate values to be added.
</para>
<para>
+If &f-env-MergeFlags; is used,
+it expects a response in the style of a
+<command>*-config</command>
+command typical of the POSIX programming environment
+(for example,
+<application>gtk-config</application>)
+and adds the options
+to the appropriate construction variables.
Interpreted options
and the construction variables they affect
are as specified for the
&f-link-env-ParseFlags;
-method (which this method calls).
+method (which
+&f-env-MergeFlags; calls).
See that method's description
-for a table of options and construction variables.
+for a table of options and corresponding construction variables.
+</para>
+
+<para>
+If &f-env-MergeFlags; cannot interpret the results of
+<parameter>command</parameter>,
+you can suppply a custom
+<parameter>function</parameter> to do so.
+<parameter>function</parameter>
+must accept three arguments:
+the &consenv; to modify, the string returned
+by running <parameter>command</parameter>,
+and the optional
+<parameter>unique</parameter> flag.
</para>
</listitem>
</varlistentry>
@@ -2906,7 +2904,7 @@ before merging them into the construction environment.
&f-env-MergeFlags;
will call this method if its argument is not a dictionary,
so it is usually not necessary to call
-&f-link-env-ParseFlags;
+&f-env-ParseFlags;
directly unless you want to manipulate the values.)
</para>
@@ -3879,15 +3877,33 @@ if 'FOO' not in env:
Sets &scons; option variable <parameter>name</parameter>
to <parameter>value</parameter>.
These options are all also settable via
-&scons; command-line options but the variable name
-may differ from the command-line option name (see table).
+command-line options but the variable 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
allows setting a project default in the scripts and
temporarily overriding it via command line.
+&f-SetOption; calls can also be placed in the
+<filename>site_init.py</filename> file.
+</para>
+
+<para>
+See the documentation in the manpage for the
+corresponding command line option for information about each specific option.
+The <parameter>value</parameter> parameter is mandatory,
+for option values which are boolean in nature
+(that is, the command line option does not take an argument)
+use a <parameter>value</parameter>
+which evaluates to true (e.g. <constant>True</constant>,
+<constant>1</constant>) or false (e.g. <constant>False</constant>,
+<constant>0</constant>).
+</para>
+
+<para>
Options which affect the reading and processing of SConscript files
-are not settable this way, since those files must
-be read in order to find the &f-SetOption; call.
+are not settable using &f-SetOption; since those files must
+be read in order to find the &f-SetOption; call in the first place.
</para>
<para>
@@ -3895,126 +3911,161 @@ The settable variables with their associated command-line options are:
</para>
<informaltable rowsep="1" colsep="1" frame="topbot">
-<tgroup cols="2">
+<tgroup cols="3">
<thead>
-<row><entry>Variable</entry><entry>Command-line options</entry></row>
+<row>
+ <entry align="left">Settable name</entry>
+ <entry align="left">Command-line options</entry>
+ <entry align="left">Notes</entry>
+</row>
</thead>
+
<tbody>
-<row><entry>
-<varname>clean</varname>
-</entry><entry>
-<option>-c</option>, <option>--clean</option>, <option>--remove</option>
-</entry></row>
-<row><entry>
-<varname>diskcheck</varname>
-</entry><entry>
-<option>--diskcheck</option>
-</entry></row>
-
- <row><entry>
-<varname>duplicate</varname>
-</entry><entry>
-<option>--duplicate</option>
-</entry></row>
-
-
- <row>
- <entry>
- <varname>experimental</varname>
- </entry>
- <entry>
- <option>--experimental</option>
- </entry>
- </row>
-
-
-<row><entry>
-<varname>help</varname>
-</entry><entry>
-<option>-h</option>, <option>--help</option>
-</entry></row>
-<row><entry>
-<varname>implicit_cache</varname>
-</entry><entry>
-<option>--implicit-cache</option>
-</entry></row>
-<!--TODO: add implicit-deps-changed, implicit-deps-unchanged ? -->
-<row><entry>
-<varname>max_drift</varname>
-</entry><entry>
-<option>--max-drift</option>
-</entry></row>
-<row><entry>
-<varname>md5_chunksize</varname>
-</entry><entry>
-<option>--md5-chunksize</option>
-</entry></row>
-<row><entry>
-<varname>no_exec</varname>
-</entry><entry>
-<option>-n</option>, <option>--no-exec</option>,
-<option>--just-print</option>, <option>--dry-run</option>,
-<option>--recon</option>
-</entry></row>
-<row><entry>
-<varname>no_progress</varname>
-</entry><entry>
-<option>-Q</option>
-</entry></row>
-<row><entry>
-<varname>num_jobs</varname>
-</entry><entry>
-<option>-j</option>, <option>--jobs</option>
-</entry></row>
-<row><entry>
-<varname>random</varname>
-</entry><entry>
-<option>--random</option>
-</entry></row>
-<row><entry>
-<varname>silent</varname>
-</entry><entry>
-<option>--silent</option>.
-</entry></row>
-<row><entry>
-<varname>stack_size</varname>
-</entry><entry>
-<option>--stack-size</option>
-</entry></row>
-<row><entry>
-<varname>warn</varname>
-</entry><entry>
-<option>--warn</option>.
-</entry></row>
+<row>
+ <entry><varname>clean</varname></entry>
+ <entry>
+ <option>-c</option>,
+ <option>--clean</option>,
+ <option>--remove</option>
+ </entry>
+</row>
+
+<row>
+ <entry><varname>diskcheck</varname></entry>
+ <entry><option>--diskcheck</option></entry>
+</row>
+
+<row>
+ <entry><varname>duplicate</varname></entry>
+ <entry><option>--duplicate</option></entry>
+</row>
+
+<row>
+ <entry><varname>experimental</varname></entry>
+ <entry><option>--experimental</option></entry>
+ <entry><emphasis>since 4.2</emphasis></entry>
+</row>
+
+<row>
+ <entry><varname>hash_chunksize</varname></entry>
+ <entry><option>--hash-chunksize</option></entry>
+ <entry>
+ Actually sets <varname>md5_chunksize</varname>.
+ <emphasis>since 4.2</emphasis>
+ </entry>
+</row>
+
+<row>
+ <entry><varname>hash_format</varname></entry>
+ <entry><option>--hash-format</option></entry>
+ <entry><emphasis>since 4.2</emphasis></entry>
+</row>
+
+<row>
+ <entry><varname>help</varname></entry>
+ <entry><option>-h</option>, <option>--help</option></entry>
+</row>
+
+<row>
+ <entry><varname>implicit_cache</varname></entry>
+ <entry><option>--implicit-cache</option></entry>
+</row>
+
+<row>
+ <entry><varname>implicit_deps_changed</varname></entry>
+ <entry><option>--implicit-deps-changed</option></entry>
+ <entry>
+ Also sets <varname>implicit_cache</varname>.
+ <emphasis>(settable since 4.2)</emphasis>
+ </entry>
+</row>
+
+<row>
+ <entry><varname>implicit_deps_unchanged</varname></entry>
+ <entry><option>--implicit-deps-unchanged</option></entry>
+ <entry>
+ Also sets <varname>implicit_cache</varname>.
+ <emphasis>(settable since 4.2)</emphasis>
+ </entry>
+</row>
+
+<row>
+ <entry><varname>max_drift</varname></entry>
+ <entry><option>--max-drift</option></entry>
+</row>
+
+<row>
+ <entry><varname>md5_chunksize</varname></entry>
+ <entry><option>--md5-chunksize</option></entry>
+</row>
+
+<row>
+ <entry><varname>no_exec</varname></entry>
+ <entry>
+ <option>-n</option>,
+ <option>--no-exec</option>,
+ <option>--just-print</option>,
+ <option>--dry-run</option>,
+ <option>--recon</option>
+ </entry>
+</row>
+
+<row>
+ <entry><varname>no_progress</varname></entry>
+ <entry><option>-Q</option></entry>
+ <entry>See
+ <footnote>
+ <para>If <varname>no_progress</varname> is set via &f-SetOption;
+ in an SConscript file
+ (but not if set in a <filename>site_init.py</filename> file)
+ there will still be an initial status message about
+ reading SConscript files since &SCons; has
+ to start reading them before it can see the
+ &f-SetOption;.
+ </para>
+ </footnote>
+ </entry>
+</row>
+
+<row>
+ <entry><varname>num_jobs</varname></entry>
+ <entry><option>-j</option>, <option>--jobs</option></entry>
+</row>
+
+<row>
+ <entry><varname>random</varname></entry>
+ <entry><option>--random</option></entry>
+</row>
+
+<row>
+ <entry><varname>silent</varname></entry>
+ <entry>
+ <option>-s</option>,
+ <option>--silent</option>,
+ <option>--quiet</option>
+ </entry>
+</row>
+
+<row>
+ <entry><varname>stack_size</varname></entry>
+ <entry><option>--stack-size</option></entry>
+</row>
+
+<row>
+ <entry><varname>warn</varname></entry>
+ <entry><option>--warn</option></entry>
+</row>
+
</tbody>
</tgroup>
</informaltable>
<para>
-See the documentation in the manpage for the
-corresponding command line option for information about each specific option.
-Option values which are boolean in nature (that is, they are
-either on or off) should be set to a true value (<constant>True</constant>,
-<constant>1</constant>) or a false value (<constant>False</constant>,
-<constant>0</constant>).
-</para>
-
-<note>
-<para>
-If <varname>no_progress</varname> is set via &f-SetOption;
-there will still be initial progress output as &SCons; has
-to start reading SConscript files before it can see the
-&f-SetOption; in an SConscript file:
-<computeroutput>scons: Reading SConscript files ...</computeroutput>
-</para>
-</note>
-
-<para>
Example:
</para>
<example_commands>
-SetOption('max_drift', True)
+SetOption('max_drift', 0)
</example_commands>
</listitem>
</varlistentry>
@@ -4248,31 +4299,34 @@ Tag('file2.txt', DOC)
<term><function>Tool</function>(<parameter>name, [toolpath, **kwargs]</parameter>)</term>
<term><replaceable>env</replaceable>.<methodname>Tool</methodname>(<parameter>name, [toolpath, **kwargs]</parameter>)</term>
<listitem><para>
-Locates and possibly runs the tool identified by
-<parameter>name</parameter>, which is
-searched for in standard locations and any
-paths specified by the optional
-<parameter>toolpath</parameter>.
-When run, the tool
+Locates the tool specification module <parameter>name</parameter>
+and returns a callable tool object for that tool.
+The tool module is searched for in standard locations
+and in any paths specified by the optional
+<parameter>toolpath</parameter> parameter.
+The standard locations are &SCons;' own internal
+path for tools plus the toolpath, if any (see the
+<emphasis role="bold">Tools</emphasis> section in the manual page
+for more details).
+Any additional keyword arguments
+<parameter>kwargs</parameter> are passed
+to the tool module's <function>generate</function> function
+during tool object construction.
+</para>
+
+<para>
+When called, the tool object
updates a &consenv; with &consvars; and arranges
any other initialization
needed to use the mechanisms that tool describes.
-Any additional keyword arguments
-<parameter>kwargs</parameter> are passed
-on to the tool module's <function>generate</function> function.
-Returns a callable tool object.
</para>
<para>
-When called as &f-env-Tool;,
-the tool module is called to update <varname>env</varname>
-and the <parameter>tool</parameter> is
+When the &f-env-Tool; form is used,
+the tool object is automatically called to update <varname>env</varname>
+and the value of <parameter>tool</parameter> is
appended to the &cv-link-TOOLS;
&consvar; in that environment.
-In this form, there is usually no need to save
-the returned tool object, since it is of no further use.
-<emphasis>Prior to &SCons; 4.2, &f-env-Tool; returned
-<constant>None</constant></emphasis>
</para>
<para>
@@ -4285,14 +4339,16 @@ env.Tool('opengl', toolpath=['build/tools'])
</example_commands>
<para>
-When called as a global function &f-Tool;,
+When the global function &f-Tool; form is used,
the tool object is constructed but not called,
as it lacks the context of an environment to update.
The tool object can be passed to an
&f-link-Environment; or &f-link-Clone; call
as part of the <parameter>tools</parameter> keyword argument,
+in which case the tool is applied to the environment being constructed,
or it can be called directly,
-passing a &consenv; to update as the argument.
+in which case a &consenv; to update must be
+passed as the argument.
Either approach will also update the
&cv-TOOLS; &consvar;.
</para>
@@ -4310,6 +4366,12 @@ msvctool(env) # adds 'msvc' to the TOOLS variable
gltool = Tool('opengl', toolpath = ['tools'])
gltool(env) # adds 'opengl' to the TOOLS variable
</example_commands>
+
+<para>
+<emphasis>Changed in &SCons; 4.2: &f-env-Tool; now returns
+the tool object, previously it did not return
+(i.e. returned <constant>None</constant>).</emphasis>
+</para>
</listitem>
</varlistentry>
<varlistentry id="f-Value">
diff --git a/doc/generated/tools.gen b/doc/generated/tools.gen
index dc15b6e..16d13e3 100644
--- a/doc/generated/tools.gen
+++ b/doc/generated/tools.gen
@@ -811,6 +811,19 @@ Sets construction variables for the
</para>
<para>Sets: &cv-link-AS;, &cv-link-ASCOM;, &cv-link-ASFLAGS;, &cv-link-ASPPCOM;, &cv-link-ASPPFLAGS;.</para><para>Uses: &cv-link-ASCOMSTR;, &cv-link-ASPPCOMSTR;.</para></listitem>
</varlistentry>
+ <varlistentry id="t-ninja">
+ <term>ninja</term>
+ <listitem><para>
+ Sets up &b-link-Ninja; builder which generates a ninja build file, and then optionally runs &ninja;.
+ </para>
+ <note>
+ <para>This is an experimental feature.
+ </para>
+ <para>This functionality is subject to change and/or removal without deprecation cycle.</para>
+ </note>
+
+ <para>Sets: &cv-link-IMPLICIT_COMMAND_DEPENDENCIES;, &cv-link-NINJA_ALIAS_NAME;, &cv-link-NINJA_COMPDB_EXPAND;, &cv-link-NINJA_DIR;, &cv-link-NINJA_DISABLE_AUTO_RUN;, &cv-link-NINJA_ENV_VAR_CACHE;, &cv-link-NINJA_FILE_NAME;, &cv-link-NINJA_GENERATED_SOURCE_SUFFIXES;, &cv-link-NINJA_MSVC_DEPS_PREFIX;, &cv-link-NINJA_POOL;, &cv-link-NINJA_REGENERATE_DEPS;, &cv-link-NINJA_SYNTAX;, &cv-link-_NINJA_REGENERATE_DEPS_FUNC;, &cv-link-__NINJA_NO;.</para><para>Uses: &cv-link-AR;, &cv-link-ARCOM;, &cv-link-ARFLAGS;, &cv-link-CC;, &cv-link-CCCOM;, &cv-link-CCFLAGS;, &cv-link-CXX;, &cv-link-CXXCOM;, &cv-link-ESCAPE;, &cv-link-LINK;, &cv-link-LINKCOM;, &cv-link-PLATFORM;, &cv-link-PRINT_CMD_LINE_FUNC;, &cv-link-PROGSUFFIX;, &cv-link-RANLIB;, &cv-link-RANLIBCOM;, &cv-link-SHCCCOM;, &cv-link-SHCXXCOM;, &cv-link-SHLINK;, &cv-link-SHLINKCOM;.</para></listitem>
+ </varlistentry>
<varlistentry id="t-packaging">
<term>packaging</term>
<listitem><para>
diff --git a/doc/generated/tools.mod b/doc/generated/tools.mod
index 78aa9ef..35eea5e 100644
--- a/doc/generated/tools.mod
+++ b/doc/generated/tools.mod
@@ -79,6 +79,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY t-mwcc "<literal xmlns='http://www.scons.org/dbxsd/v1.0'>mwcc</literal>">
<!ENTITY t-mwld "<literal xmlns='http://www.scons.org/dbxsd/v1.0'>mwld</literal>">
<!ENTITY t-nasm "<literal xmlns='http://www.scons.org/dbxsd/v1.0'>nasm</literal>">
+<!ENTITY t-ninja "<literal xmlns='http://www.scons.org/dbxsd/v1.0'>ninja</literal>">
<!ENTITY t-packaging "<literal xmlns='http://www.scons.org/dbxsd/v1.0'>packaging</literal>">
<!ENTITY t-pdf "<literal xmlns='http://www.scons.org/dbxsd/v1.0'>pdf</literal>">
<!ENTITY t-pdflatex "<literal xmlns='http://www.scons.org/dbxsd/v1.0'>pdflatex</literal>">
@@ -184,6 +185,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY t-link-mwcc "<link linkend='t-mwcc' xmlns='http://www.scons.org/dbxsd/v1.0'><literal>mwcc</literal></link>">
<!ENTITY t-link-mwld "<link linkend='t-mwld' xmlns='http://www.scons.org/dbxsd/v1.0'><literal>mwld</literal></link>">
<!ENTITY t-link-nasm "<link linkend='t-nasm' xmlns='http://www.scons.org/dbxsd/v1.0'><literal>nasm</literal></link>">
+<!ENTITY t-link-ninja "<link linkend='t-ninja' xmlns='http://www.scons.org/dbxsd/v1.0'><literal>ninja</literal></link>">
<!ENTITY t-link-packaging "<link linkend='t-packaging' xmlns='http://www.scons.org/dbxsd/v1.0'><literal>packaging</literal></link>">
<!ENTITY t-link-pdf "<link linkend='t-pdf' xmlns='http://www.scons.org/dbxsd/v1.0'><literal>pdf</literal></link>">
<!ENTITY t-link-pdflatex "<link linkend='t-pdflatex' xmlns='http://www.scons.org/dbxsd/v1.0'><literal>pdflatex</literal></link>">
diff --git a/doc/generated/variables.gen b/doc/generated/variables.gen
index a24fd34..064f0e3 100644
--- a/doc/generated/variables.gen
+++ b/doc/generated/variables.gen
@@ -22,6 +22,16 @@ if &cv-link-LDMODULEVERSION; is set. Othervise it evaluates to an empty string.
</para>
</listitem>
</varlistentry>
+ <varlistentry id="cv-__NINJA_NO">
+ <term>
+ <envar>__NINJA_NO</envar>
+ </term>
+ <listitem><para>
+ Internal flag. Used to tell SCons whether or not to try to import pypi's ninja python package.
+ This is set to True when being called by Ninja?
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry id="cv-__SHLIBVERSIONFLAGS">
<term>
<envar>__SHLIBVERSIONFLAGS</envar>
@@ -627,17 +637,22 @@ section of the RPM
<envar>_concat</envar>
</term>
<listitem><para>
-A function used to produce variables like &cv-link-_CPPINCFLAGS;. It takes
-four or five
-arguments: a prefix to concatenate onto each element, a list of
-elements, a suffix to concatenate onto each element, an environment
-for variable interpolation, and an optional function that will be
-called to transform the list before concatenation.
-</para>
+ A function used to produce variables like &cv-link-_CPPINCFLAGS;. It takes
+ four mandatory arguments, and up to 4 additional optional arguments:
+ 1) a prefix to concatenate onto each element,
+ 2) a list of elements,
+ 3) a suffix to concatenate onto each element,
+ 4) an environment for variable interpolation,
+ 5) an optional function that will be called to transform the list before concatenation,
+ 6) an optionally specified target (Can use TARGET),
+ 7) an optionally specified source (Can use SOURCE),
+ 8) optional <parameter>affect_signature</parameter> flag which will wrap non-empty returned value with $( and $) to indicate the contents
+ should not affect the signature of the generated command line.
+ </para>
-<example_commands>
-env['_CPPINCFLAGS'] = '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs)} $)',
-</example_commands>
+ <example_commands>
+ env['_CPPINCFLAGS'] = '${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}'
+ </example_commands>
</listitem>
</varlistentry>
<varlistentry id="cv-CONFIGUREDIR">
@@ -5156,6 +5171,150 @@ Specfies the name of the project to package.
<para>See the &b-link-Package; builder.</para>
</listitem>
</varlistentry>
+ <varlistentry id="cv-NINJA_ALIAS_NAME">
+ <term>
+ <envar>NINJA_ALIAS_NAME</envar>
+ </term>
+ <listitem><para>
+ Name of the Alias() which is will cause SCons to create the <filename>ninja.build</filename> file, and
+ then (optionally) run ninja.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-NINJA_COMPDB_EXPAND">
+ <term>
+ <envar>NINJA_COMPDB_EXPAND</envar>
+ </term>
+ <listitem><para>
+ Boolean value (True|False) to instruct ninja to expand the command line arguments normally put into
+ response files.
+ This prevents lines in the compilation database like <quote>gcc @rsp_file</quote> and instead yields
+ <quote>gcc -c -o myfile.o myfile.c -Ia -DXYZ</quote>
+ </para>
+ <para>
+ Ninja's compdb tool added the <quote>-x</quote> flag in Ninja V1.9.0
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-NINJA_DIR">
+ <term>
+ <envar>NINJA_DIR</envar>
+ </term>
+ <listitem><para>
+ This propagates directly into the generated <filename>ninja.build</filename> file.
+ From Ninja's docs:
+ <blockquote>
+ <para>
+ builddir
+ A directory for some Ninja output files. ... (You can also store other build output in this
+ directory.)
+ </para>
+ </blockquote>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-NINJA_DISABLE_AUTO_RUN">
+ <term>
+ <envar>NINJA_DISABLE_AUTO_RUN</envar>
+ </term>
+ <listitem><para>
+ Boolean (True|False). Default: False
+ When True, SCons will not run ninja automatically after creating the <filename>ninja.build</filename> file.
+
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-NINJA_ENV_VAR_CACHE">
+ <term>
+ <envar>NINJA_ENV_VAR_CACHE</envar>
+ </term>
+ <listitem><para>
+ A string that sets the environment for any environment variables that
+ differ between the OS environment and the SCons command ENV.
+
+ It will be compatible with the default shell of the operating system.
+
+ If not explicitly specified, SCons will generate this dynamically from the Environment()'s 'ENV'
+ <quote>env['ENV']</quote>
+ where those values differ from the existing shell..
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-NINJA_FILE_NAME">
+ <term>
+ <envar>NINJA_FILE_NAME</envar>
+ </term>
+ <listitem><para>
+ The filename for the generated Ninja build file defaults to
+ <filename>ninja.build</filename>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-NINJA_GENERATED_SOURCE_SUFFIXES">
+ <term>
+ <envar>NINJA_GENERATED_SOURCE_SUFFIXES</envar>
+ </term>
+ <listitem><para>
+ The list of source file suffixes which are generated by SCons build steps.
+ All source files which match these suffixes will be added to the _generated_sources alias in the output
+ <filename>ninja.build</filename> file.
+ Then all other source files will be made to depend on this in the <filename>ninja.build</filename> file, forcing the
+ generated sources to be built first.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-NINJA_MSVC_DEPS_PREFIX">
+ <term>
+ <envar>NINJA_MSVC_DEPS_PREFIX</envar>
+ </term>
+ <listitem><para>
+ This propagates directly into the generated <filename>ninja.build</filename> file.
+ From Ninja's docs
+ <quote>defines the string which should be stripped from msvc’s /showIncludes output</quote>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-NINJA_POOL">
+ <term>
+ <envar>NINJA_POOL</envar>
+ </term>
+ <listitem><para>
+ Set the <quote>ninja_pool</quote> for this or all targets in scope for this env var.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-NINJA_REGENERATE_DEPS">
+ <term>
+ <envar>NINJA_REGENERATE_DEPS</envar>
+ </term>
+ <listitem><para>
+ A generator function used to create a ninja depsfile which includes all the files which would require
+ SCons to be invoked if they change.
+ Or a list of said files.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-_NINJA_REGENERATE_DEPS_FUNC">
+ <term>
+ <envar>_NINJA_REGENERATE_DEPS_FUNC</envar>
+ </term>
+ <listitem><para>
+ Internal value used to specify the function to call with argument env to generate the list of files
+ which if changed would require the ninja file to be regenerated.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="cv-NINJA_SYNTAX">
+ <term>
+ <envar>NINJA_SYNTAX</envar>
+ </term>
+ <listitem><para>
+ Theres also NINJA_SYNTAX which is the path to a custom ninja_syntax.py file which is used in generation.
+ The tool currently assumes you have ninja installed through pip, and grabs the syntax file from that
+ installation if none specified.
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry id="cv-no_import_lib">
<term>
<envar>no_import_lib</envar>
diff --git a/doc/generated/variables.mod b/doc/generated/variables.mod
index a3dbc0d..08aa21d 100644
--- a/doc/generated/variables.mod
+++ b/doc/generated/variables.mod
@@ -9,6 +9,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
-->
<!ENTITY cv-__LDMODULEVERSIONFLAGS "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$__LDMODULEVERSIONFLAGS</envar>">
+<!ENTITY cv-__NINJA_NO "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$__NINJA_NO</envar>">
<!ENTITY cv-__SHLIBVERSIONFLAGS "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$__SHLIBVERSIONFLAGS</envar>">
<!ENTITY cv-APPLELINK_COMPATIBILITY_VERSION "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$APPLELINK_COMPATIBILITY_VERSION</envar>">
<!ENTITY cv-_APPLELINK_COMPATIBILITY_VERSION "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$_APPLELINK_COMPATIBILITY_VERSION</envar>">
@@ -349,6 +350,18 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY cv-MWCW_VERSION "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MWCW_VERSION</envar>">
<!ENTITY cv-MWCW_VERSIONS "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$MWCW_VERSIONS</envar>">
<!ENTITY cv-NAME "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NAME</envar>">
+<!ENTITY cv-NINJA_ALIAS_NAME "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_ALIAS_NAME</envar>">
+<!ENTITY cv-NINJA_COMPDB_EXPAND "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_COMPDB_EXPAND</envar>">
+<!ENTITY cv-NINJA_DIR "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_DIR</envar>">
+<!ENTITY cv-NINJA_DISABLE_AUTO_RUN "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_DISABLE_AUTO_RUN</envar>">
+<!ENTITY cv-NINJA_ENV_VAR_CACHE "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_ENV_VAR_CACHE</envar>">
+<!ENTITY cv-NINJA_FILE_NAME "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_FILE_NAME</envar>">
+<!ENTITY cv-NINJA_GENERATED_SOURCE_SUFFIXES "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_GENERATED_SOURCE_SUFFIXES</envar>">
+<!ENTITY cv-NINJA_MSVC_DEPS_PREFIX "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_MSVC_DEPS_PREFIX</envar>">
+<!ENTITY cv-NINJA_POOL "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_POOL</envar>">
+<!ENTITY cv-NINJA_REGENERATE_DEPS "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_REGENERATE_DEPS</envar>">
+<!ENTITY cv-_NINJA_REGENERATE_DEPS_FUNC "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$_NINJA_REGENERATE_DEPS_FUNC</envar>">
+<!ENTITY cv-NINJA_SYNTAX "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$NINJA_SYNTAX</envar>">
<!ENTITY cv-no_import_lib "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$no_import_lib</envar>">
<!ENTITY cv-OBJPREFIX "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$OBJPREFIX</envar>">
<!ENTITY cv-OBJSUFFIX "<envar xmlns='http://www.scons.org/dbxsd/v1.0'>$OBJSUFFIX</envar>">
@@ -649,6 +662,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
-->
<!ENTITY cv-link-__LDMODULEVERSIONFLAGS "<link linkend='cv-__LDMODULEVERSIONFLAGS' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$__LDMODULEVERSIONFLAGS</envar></link>">
+<!ENTITY cv-link-__NINJA_NO "<link linkend='cv-__NINJA_NO' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$__NINJA_NO</envar></link>">
<!ENTITY cv-link-__SHLIBVERSIONFLAGS "<link linkend='cv-__SHLIBVERSIONFLAGS' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$__SHLIBVERSIONFLAGS</envar></link>">
<!ENTITY cv-link-APPLELINK_COMPATIBILITY_VERSION "<link linkend='cv-APPLELINK_COMPATIBILITY_VERSION' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$APPLELINK_COMPATIBILITY_VERSION</envar></link>">
<!ENTITY cv-link-_APPLELINK_COMPATIBILITY_VERSION "<link linkend='cv-_APPLELINK_COMPATIBILITY_VERSION' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$_APPLELINK_COMPATIBILITY_VERSION</envar></link>">
@@ -989,6 +1003,18 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
<!ENTITY cv-link-MWCW_VERSION "<link linkend='cv-MWCW_VERSION' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MWCW_VERSION</envar></link>">
<!ENTITY cv-link-MWCW_VERSIONS "<link linkend='cv-MWCW_VERSIONS' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$MWCW_VERSIONS</envar></link>">
<!ENTITY cv-link-NAME "<link linkend='cv-NAME' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NAME</envar></link>">
+<!ENTITY cv-link-NINJA_ALIAS_NAME "<link linkend='cv-NINJA_ALIAS_NAME' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_ALIAS_NAME</envar></link>">
+<!ENTITY cv-link-NINJA_COMPDB_EXPAND "<link linkend='cv-NINJA_COMPDB_EXPAND' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_COMPDB_EXPAND</envar></link>">
+<!ENTITY cv-link-NINJA_DIR "<link linkend='cv-NINJA_DIR' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_DIR</envar></link>">
+<!ENTITY cv-link-NINJA_DISABLE_AUTO_RUN "<link linkend='cv-NINJA_DISABLE_AUTO_RUN' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_DISABLE_AUTO_RUN</envar></link>">
+<!ENTITY cv-link-NINJA_ENV_VAR_CACHE "<link linkend='cv-NINJA_ENV_VAR_CACHE' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_ENV_VAR_CACHE</envar></link>">
+<!ENTITY cv-link-NINJA_FILE_NAME "<link linkend='cv-NINJA_FILE_NAME' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_FILE_NAME</envar></link>">
+<!ENTITY cv-link-NINJA_GENERATED_SOURCE_SUFFIXES "<link linkend='cv-NINJA_GENERATED_SOURCE_SUFFIXES' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_GENERATED_SOURCE_SUFFIXES</envar></link>">
+<!ENTITY cv-link-NINJA_MSVC_DEPS_PREFIX "<link linkend='cv-NINJA_MSVC_DEPS_PREFIX' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_MSVC_DEPS_PREFIX</envar></link>">
+<!ENTITY cv-link-NINJA_POOL "<link linkend='cv-NINJA_POOL' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_POOL</envar></link>">
+<!ENTITY cv-link-NINJA_REGENERATE_DEPS "<link linkend='cv-NINJA_REGENERATE_DEPS' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_REGENERATE_DEPS</envar></link>">
+<!ENTITY cv-link-_NINJA_REGENERATE_DEPS_FUNC "<link linkend='cv-_NINJA_REGENERATE_DEPS_FUNC' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$_NINJA_REGENERATE_DEPS_FUNC</envar></link>">
+<!ENTITY cv-link-NINJA_SYNTAX "<link linkend='cv-NINJA_SYNTAX' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$NINJA_SYNTAX</envar></link>">
<!ENTITY cv-link-no_import_lib "<link linkend='cv-no_import_lib' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$no_import_lib</envar></link>">
<!ENTITY cv-link-OBJPREFIX "<link linkend='cv-OBJPREFIX' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$OBJPREFIX</envar></link>">
<!ENTITY cv-link-OBJSUFFIX "<link linkend='cv-OBJSUFFIX' xmlns='http://www.scons.org/dbxsd/v1.0'><envar>$OBJSUFFIX</envar></link>">
diff --git a/doc/scons.mod b/doc/scons.mod
index 5700454..72adf68 100644
--- a/doc/scons.mod
+++ b/doc/scons.mod
@@ -57,6 +57,7 @@
<!ENTITY m4 "<application xmlns='http://www.scons.org/dbxsd/v1.0'>m4</application>">
<!ENTITY Make "<application xmlns='http://www.scons.org/dbxsd/v1.0'>Make</application>">
<!ENTITY Makepp "<application xmlns='http://www.scons.org/dbxsd/v1.0'>Make++</application>">
+<!ENTITY ninja "<application xmlns='http://www.scons.org/dbxsd/v1.0'>ninja</application>">
<!ENTITY pdflatex "<application xmlns='http://www.scons.org/dbxsd/v1.0'>pdflatex</application>">
<!ENTITY pdftex "<application xmlns='http://www.scons.org/dbxsd/v1.0'>pdftex</application>">
<!ENTITY Python "<application xmlns='http://www.scons.org/dbxsd/v1.0'>Python</application>">
diff --git a/doc/user/external.xml b/doc/user/external.xml
index 9900e93..5f88f5a 100644
--- a/doc/user/external.xml
+++ b/doc/user/external.xml
@@ -1,286 +1,359 @@
<?xml version='1.0'?>
<!DOCTYPE sconsdoc [
- <!ENTITY % scons SYSTEM "../scons.mod">
- %scons;
+ <!ENTITY % scons SYSTEM "../scons.mod">
+ %scons;
- <!ENTITY % builders-mod SYSTEM "../generated/builders.mod">
- %builders-mod;
- <!ENTITY % functions-mod SYSTEM "../generated/functions.mod">
- %functions-mod;
- <!ENTITY % tools-mod SYSTEM "../generated/tools.mod">
- %tools-mod;
- <!ENTITY % variables-mod SYSTEM "../generated/variables.mod">
- %variables-mod;
+ <!ENTITY % builders-mod SYSTEM "../generated/builders.mod">
+ %builders-mod;
+ <!ENTITY % functions-mod SYSTEM "../generated/functions.mod">
+ %functions-mod;
+ <!ENTITY % tools-mod SYSTEM "../generated/tools.mod">
+ %tools-mod;
+ <!ENTITY % variables-mod SYSTEM "../generated/variables.mod">
+ %variables-mod;
-]>
+ ]>
<chapter id="chap-external"
xmlns="http://www.scons.org/dbxsd/v1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd">
-<title>Using SCons with other build tools</title>
+ <title>Using SCons with other build tools</title>
-<!--
+ <!--
- __COPYRIGHT__
+ MIT License
- 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:
+ Copyright The SCons Foundation
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
+ 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 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.
+ 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.
- <para>
-
- Sometimes a project needs to interact with other projects
- in various ways. For example, many open source projects
- make use of components from other open source projects,
- and want to use those in their released form, not recode
- their builds into &SCons;. As another example, sometimes
- the flexibility and power of &SCons; is useful for managing the
- overall project, but developers might like faster incremental
- builds when making small changes by using a different tool.
-
- </para>
-
- <para>
-
- This chapter shows some techniques for interacting with other
- projects and tools effectively from within &SCons;.
-
- </para>
-
- <section>
- <title>Creating a Compilation Database</title>
+ -->
<para>
- Tooling to perform analysis and modification
- of source code often needs to know not only the source code
- itself, but also how it will be compiled, as the compilation line
- affects the behavior of macros, includes, etc. &SCons; has a
- record of this information once it has run, in the form of
- Actions associated with the sources, and can emit this information
- so tools can use it.
+ Sometimes a project needs to interact with other projects
+ in various ways. For example, many open source projects
+ make use of components from other open source projects,
+ and want to use those in their released form, not recode
+ their builds into &SCons;. As another example, sometimes
+ the flexibility and power of &SCons; is useful for managing the
+ overall project, but developers might like faster incremental
+ builds when making small changes by using a different tool.
</para>
<para>
- The Clang project has defined a <firstterm>JSON Compilation Database</firstterm>.
- This database is in common use as input into Clang tools
- and many IDEs and editors as well.
- See
- <ulink url="https://clang.llvm.org/docs/JSONCompilationDatabase.html">
- <citetitle>JSON Compilation Database Format Specification</citetitle>
- </ulink>
- for complete information. &SCons; can emit a
- compilation database in this format
- by enabling the &t-link-compilation_db; tool
- and calling the &b-link-CompilationDatabase; builder
- (<emphasis>available since &scons; 4.0</emphasis>).
-
- </para>
-
- <para>
-
- The compilation database can be populated with
- source and output files either with paths relative
- to the top of the build, or using absolute paths.
- This is controlled by
- <envar>COMPILATIONDB_USE_ABSPATH=(True|False)</envar>
- which defaults to <constant>False</constant>.
- The entries in this file can be filtered by using
-
- <envar>COMPILATIONDB_PATH_FILTER='pattern'</envar>
- where the filter pattern is a string following the Python
- <ulink url="https://docs.python.org/3/library/fnmatch.html">
- <systemitem>fnmatch</systemitem>
- </ulink>
- syntax.
- This filtering can be used for outputting different
- build variants to different compilation database files.
-
- </para>
-
- <para>
-
- The following example illustrates generating a compilation
- database containing absolute paths:
-
- </para>
-
- <scons_example name="external_cdb_ex1">
- <file name="SConstruct" printme="1">
-env = Environment(COMPILATIONDB_USE_ABSPATH=True)
-env.Tool('compilation_db')
-env.CompilationDatabase()
-env.Program('hello.c')
- </file>
- <file name="hello.c">
-int main( int argc, char* argv[] )
-{
- return 0;
-}
- </file>
- </scons_example>
-
- <scons_output example="external_cdb_ex1" suffix="1">
- <scons_output_command>scons -Q</scons_output_command>
- </scons_output>
-
- <para><filename>compile_commands.json</filename> contains:</para>
-
- <programlisting language="json">
-[
- {
- "command": "gcc -o hello.o -c hello.c",
- "directory": "/home/user/sandbox",
- "file": "/home/user/sandbox/hello.c",
- "output": "/home/user/sandbox/hello.o"
- }
-]
- </programlisting>
-
- <para>
-
- Notice that the generated database contains only an entry for
- the <filename>hello.c/hello.o</filename> pairing,
- and nothing for the generation of the final executable
- <filename>hello</filename> - the transformation of
- <filename>hello.o</filename> to <filename>hello</filename>
- does not have any information that affects interpretation
- of the source code,
- so it is not interesting to the compilation database.
-
- </para>
-
- <para>
-
- Although it can be a little surprising at first glance,
- a compilation database target is, like any other target,
- subject to &scons; target selection rules.
- This means if you set a default target (that does not
- include the compilation database), or use command-line
- targets, it might not be selected for building.
- This can actually be an advantage, since you don't
- necessarily want to regenerate the compilation database
- every build.
- The following example
- shows selecting relative paths (the default)
- for output and source,
- and also giving a non-default name to the database.
- In order to be able to generate the database separately from building,
- an alias is set referring to the database,
- which can then be used as a target - here we are only
- building the compilation database target, not the code.
-
- </para>
-
- <scons_example name="external_cdb_ex2">
- <file name="SConstruct" printme="1">
-env = Environment()
-env.Tool('compilation_db')
-cdb = env.CompilationDatabase('compile_database.json')
-Alias('cdb', cdb)
-env.Program('test_main.c')
- </file>
- <file name="test_main.c">
-#include "test_main.h"
-int main( int argc, char* argv[] )
-{
- return 0;
-}
- </file>
- <file name="test_main.h">
-/* dummy include file */
- </file>
- </scons_example>
-
- <scons_output example="external_cdb_ex2" suffix="1">
- <scons_output_command>scons -Q cdb</scons_output_command>
- </scons_output>
-
- <para><filename>compile_database.json</filename> contains:</para>
-
- <programlisting language="json">
-[
- {
- "command": "gcc -o test_main.o -c test_main.c",
- "directory": "/home/user/sandbox",
- "file": "test_main.c",
- "output": "test_main.o"
- }
-]
- </programlisting>
-
- <para>
-
- The following (incomplete) example shows using filtering
- to separate build variants.
- In the case of using variants,
- you want different compilation databases for each,
- since the build parameters differ, so the code analysis
- needs to see the correct build lines for the 32-bit build
- and 64-bit build hinted at here.
- For simplicity of presentation,
- the example omits the setup details of the variant directories:
+ This chapter shows some techniques for interacting with other
+ projects and tools effectively from within &SCons;.
</para>
- <sconstruct>
-env = Environment()
-env.Tool('compilation_db')
-
-env1 = env.Clone()
-env1['COMPILATIONDB_PATH_FILTER'] = 'build/linux32/*'
-env1.CompilationDatabase('compile_commands-linux32.json')
-
-env2 = env.Clone()
-env2['COMPILATIONDB_PATH_FILTER'] = 'build/linux64/*'
-env2.CompilationDatabase('compile_commands-linux64.json')
- </sconstruct>
-
- <para><filename>compile_commands-linux32.json</filename> contains:</para>
-
- <programlisting language="json">
-[
- {
- "command": "gcc -m32 -o build/linux32/test_main.o -c test_main.c",
- "directory": "/home/user/sandbox",
- "file": "test_main.c",
- "output": "build/linux32/test_main.o"
- }
-]
- </programlisting>
-
- <para><filename>compile_commands-linux64.json</filename> contains:</para>
-
- <programlisting language="json">
-[
- {
- "command": "gcc -m64 -o build/linux64/test_main.o -c test_main.c",
- "directory": "/home/user/sandbox",
- "file": "test_main.c",
- "output": "build/linux64/test_main.o"
- }
-]
- </programlisting>
-
- </section>
+ <section>
+ <title>Creating a Compilation Database</title>
+
+ <para>
+
+ Tooling to perform analysis and modification
+ of source code often needs to know not only the source code
+ itself, but also how it will be compiled, as the compilation line
+ affects the behavior of macros, includes, etc. &SCons; has a
+ record of this information once it has run, in the form of
+ Actions associated with the sources, and can emit this information
+ so tools can use it.
+
+ </para>
+
+ <para>
+
+ The Clang project has defined a <firstterm>JSON Compilation Database</firstterm>.
+ This database is in common use as input into Clang tools
+ and many IDEs and editors as well.
+ See
+ <ulink url="https://clang.llvm.org/docs/JSONCompilationDatabase.html">
+ <citetitle>JSON Compilation Database Format Specification</citetitle>
+ </ulink>
+ for complete information. &SCons; can emit a
+ compilation database in this format
+ by enabling the &t-link-compilation_db; tool
+ and calling the &b-link-CompilationDatabase; builder
+ (<emphasis>available since &scons; 4.0</emphasis>).
+
+ </para>
+
+ <para>
+
+ The compilation database can be populated with
+ source and output files either with paths relative
+ to the top of the build, or using absolute paths.
+ This is controlled by
+ <envar>COMPILATIONDB_USE_ABSPATH=(True|False)</envar>
+ which defaults to <constant>False</constant>.
+ The entries in this file can be filtered by using
+
+ <envar>COMPILATIONDB_PATH_FILTER='pattern'</envar>
+ where the filter pattern is a string following the Python
+ <ulink url="https://docs.python.org/3/library/fnmatch.html">
+ <systemitem>fnmatch</systemitem>
+ </ulink>
+ syntax.
+ This filtering can be used for outputting different
+ build variants to different compilation database files.
+
+ </para>
+
+ <para>
+
+ The following example illustrates generating a compilation
+ database containing absolute paths:
+
+ </para>
+
+ <scons_example name="external_cdb_ex1">
+ <file name="SConstruct" printme="1">
+ env = Environment(COMPILATIONDB_USE_ABSPATH=True)
+ env.Tool('compilation_db')
+ env.CompilationDatabase()
+ env.Program('hello.c')
+ </file>
+ <file name="hello.c">
+ int main( int argc, char* argv[] )
+ {
+ return 0;
+ }
+ </file>
+ </scons_example>
+
+ <scons_output example="external_cdb_ex1" suffix="1">
+ <scons_output_command>scons -Q</scons_output_command>
+ </scons_output>
+
+ <para>
+ <filename>compile_commands.json</filename>
+ contains:
+ </para>
+
+ <programlisting language="json">
+ [
+ {
+ "command": "gcc -o hello.o -c hello.c",
+ "directory": "/home/user/sandbox",
+ "file": "/home/user/sandbox/hello.c",
+ "output": "/home/user/sandbox/hello.o"
+ }
+ ]
+ </programlisting>
+
+ <para>
+
+ Notice that the generated database contains only an entry for
+ the <filename>hello.c/hello.o</filename> pairing,
+ and nothing for the generation of the final executable
+ <filename>hello</filename>
+ - the transformation of
+ <filename>hello.o</filename>
+ to
+ <filename>hello</filename>
+ does not have any information that affects interpretation
+ of the source code,
+ so it is not interesting to the compilation database.
+
+ </para>
+
+ <para>
+
+ Although it can be a little surprising at first glance,
+ a compilation database target is, like any other target,
+ subject to &scons; target selection rules.
+ This means if you set a default target (that does not
+ include the compilation database), or use command-line
+ targets, it might not be selected for building.
+ This can actually be an advantage, since you don't
+ necessarily want to regenerate the compilation database
+ every build.
+ The following example
+ shows selecting relative paths (the default)
+ for output and source,
+ and also giving a non-default name to the database.
+ In order to be able to generate the database separately from building,
+ an alias is set referring to the database,
+ which can then be used as a target - here we are only
+ building the compilation database target, not the code.
+
+ </para>
+
+ <scons_example name="external_cdb_ex2">
+ <file name="SConstruct" printme="1">
+ env = Environment()
+ env.Tool('compilation_db')
+ cdb = env.CompilationDatabase('compile_database.json')
+ Alias('cdb', cdb)
+ env.Program('test_main.c')
+ </file>
+ <file name="test_main.c">
+ #include "test_main.h"
+ int main( int argc, char* argv[] )
+ {
+ return 0;
+ }
+ </file>
+ <file name="test_main.h">
+ /* dummy include file */
+ </file>
+ </scons_example>
+
+ <scons_output example="external_cdb_ex2" suffix="1">
+ <scons_output_command>scons -Q cdb</scons_output_command>
+ </scons_output>
+
+ <para>
+ <filename>compile_database.json</filename>
+ contains:
+ </para>
+
+ <programlisting language="json">
+ [
+ {
+ "command": "gcc -o test_main.o -c test_main.c",
+ "directory": "/home/user/sandbox",
+ "file": "test_main.c",
+ "output": "test_main.o"
+ }
+ ]
+ </programlisting>
+
+ <para>
+
+ The following (incomplete) example shows using filtering
+ to separate build variants.
+ In the case of using variants,
+ you want different compilation databases for each,
+ since the build parameters differ, so the code analysis
+ needs to see the correct build lines for the 32-bit build
+ and 64-bit build hinted at here.
+ For simplicity of presentation,
+ the example omits the setup details of the variant directories:
+
+ </para>
+
+ <sconstruct>
+ env = Environment()
+ env.Tool('compilation_db')
+
+ env1 = env.Clone()
+ env1['COMPILATIONDB_PATH_FILTER'] = 'build/linux32/*'
+ env1.CompilationDatabase('compile_commands-linux32.json')
+
+ env2 = env.Clone()
+ env2['COMPILATIONDB_PATH_FILTER'] = 'build/linux64/*'
+ env2.CompilationDatabase('compile_commands-linux64.json')
+ </sconstruct>
+
+ <para>
+ <filename>compile_commands-linux32.json</filename>
+ contains:
+ </para>
+
+ <programlisting language="json">
+ [
+ {
+ "command": "gcc -m32 -o build/linux32/test_main.o -c test_main.c",
+ "directory": "/home/user/sandbox",
+ "file": "test_main.c",
+ "output": "build/linux32/test_main.o"
+ }
+ ]
+ </programlisting>
+
+ <para>
+ <filename>compile_commands-linux64.json</filename>
+ contains:
+ </para>
+
+ <programlisting language="json">
+ [
+ {
+ "command": "gcc -m64 -o build/linux64/test_main.o -c test_main.c",
+ "directory": "/home/user/sandbox",
+ "file": "test_main.c",
+ "output": "build/linux64/test_main.o"
+ }
+ ]
+ </programlisting>
+
+ </section>
+
+ <section>
+ <title>Ninja Build Generator</title>
+
+ <note>
+ <para>
+ This is an experimental new feature. It is subject to change and/or removal without depreciation cycle
+ </para>
+ <para>
+ To use this tool you must install pypi's <ulink url="https://pypi.org/project/ninja/">ninja
+ package</ulink>.
+ This can be done via
+ <command>pip install ninja</command>
+ </para>
+ <para>
+ To enable this feature you'll need to use one of the following
+ </para>
+ <example_commands>
+ # On the command line
+ --experimental=ninja
+
+ # Or in your SConstruct
+ SetOption('experimental', 'ninja')
+ </example_commands>
+
+ </note>
+
+ <para>
+ This tool will enabled creating a ninja build file from your SCons based build system. It can then invoke
+ ninja to run your build. For most builds ninja will be significantly faster, but you may have to give up
+ some accuracy. You are NOT advised to use this for production builds. It can however significantly speed up
+ your build/debug/compile iterations.
+ </para>
+ <para>
+ It's not expected that the ninja builder will work for all builds at this point. It's still under active
+ development. If you find that your build doesn't work with ninja please bring this to the users mailing list
+ or devel channel on our discord server.
+ </para>
+ <para>
+ Specifically if your build has many (or even any) python function actions you may find that the ninja build
+ will be slower as it will run ninja, which will then run SCons for each target created by a python action.
+ To alleviate some of these, especially those python based actions built into SCons there is special logic to
+ implement those actions via shell commands in the ninja build file.
+ </para>
+ <para>
+ <ulink url="https://ninja-build.org/">
+ <citetitle>Ninja Build System</citetitle>
+ </ulink>
+ </para>
+
+ <para>
+ <ulink url="https://ninja-build.org/manual.html#ref_ninja_file">
+ <citetitle>Ninja File Format Specification</citetitle>
+ </ulink>
+ </para>
+ </section>
</chapter>
diff --git a/requirements.txt b/requirements.txt
index 520b90b..4093f19 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,3 +9,4 @@ sphinx<3.5.0
sphinx_rtd_theme
lxml==4.6.3
rst2pdf
+ninja \ No newline at end of file
diff --git a/runtest.py b/runtest.py
index 69b3a61..52ceea4 100755
--- a/runtest.py
+++ b/runtest.py
@@ -157,6 +157,7 @@ if args.testlistfile:
# args.testlistfile changes from a string to a pathlib Path object
try:
p = Path(args.testlistfile)
+ # TODO simplify when Py3.5 dropped
if sys.version_info.major == 3 and sys.version_info.minor < 6:
args.testlistfile = p.resolve()
else:
@@ -172,6 +173,7 @@ if args.excludelistfile:
# args.excludelistfile changes from a string to a pathlib Path object
try:
p = Path(args.excludelistfile)
+ # TODO simplify when Py3.5 dropped
if sys.version_info.major == 3 and sys.version_info.minor < 6:
args.excludelistfile = p.resolve()
else:
@@ -563,14 +565,23 @@ def find_e2e_tests(directory):
# Skip folders containing a sconstest.skip file
if 'sconstest.skip' in filenames:
continue
- try:
- with open(os.path.join(dirpath, ".exclude_tests")) as f:
+
+ # Slurp in any tests in exclude lists
+ excludes = []
+ if ".exclude_tests" in filenames:
+ p = Path(dirpath).joinpath(".exclude_tests")
+ # TODO simplify when Py3.5 dropped
+ if sys.version_info.major == 3 and sys.version_info.minor < 6:
+ excludefile = p.resolve()
+ else:
+ excludefile = p.resolve(strict=True)
+ with excludefile.open() as f:
excludes = scanlist(f)
- except EnvironmentError:
- excludes = []
+
for fname in filenames:
if fname.endswith(".py") and fname not in excludes:
result.append(os.path.join(dirpath, fname))
+
return sorted(result)
diff --git a/test/AddOption/.exclude_tests b/test/AddOption/.exclude_tests
new file mode 100644
index 0000000..acc32f5
--- /dev/null
+++ b/test/AddOption/.exclude_tests
@@ -0,0 +1,4 @@
+# for now, the tests showing problems with processing space-separated
+# arguments are excluded, pending an implementation that doesn't fail.
+args-and-targets.py
+multi-arg.py
diff --git a/test/AddOption/args-and-targets.py b/test/AddOption/args-and-targets.py
new file mode 100644
index 0000000..56bdd85
--- /dev/null
+++ b/test/AddOption/args-and-targets.py
@@ -0,0 +1,65 @@
+#!/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.
+
+"""
+Verify that when an option is specified which takes args,
+those do not end up treated as targets.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write(
+ 'SConstruct',
+ """\
+env = Environment()
+AddOption(
+ '--extra',
+ nargs=1,
+ dest='extra',
+ action='store',
+ type='string',
+ metavar='ARG1',
+ default=(),
+ help='An argument to the option',
+)
+print(str(GetOption('extra')))
+print(COMMAND_LINE_TARGETS)
+""",
+)
+
+# arg using =
+test.run('-Q -q --extra=A TARG', status=1, stdout="A\n['TARG']\n")
+# arg not using =
+test.run('-Q -q --extra A TARG', status=1, stdout="A\n['TARG']\n")
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/AddOption/multi-arg.py b/test/AddOption/multi-arg.py
new file mode 100644
index 0000000..44928c4
--- /dev/null
+++ b/test/AddOption/multi-arg.py
@@ -0,0 +1,124 @@
+#!/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.
+
+"""
+Verify that when an option is specified with nargs > 1,
+SCons consumes those correctly into the args.
+"""
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+# First, test an option with nargs=2 and no others:
+test.write(
+ 'SConstruct',
+ """\
+env = Environment()
+AddOption('--extras',
+ nargs=2,
+ dest='extras',
+ action='store',
+ type='string',
+ metavar='FILE1 FILE2',
+ default=(),
+ help='two extra files to install')
+print(str(GetOption('extras')))
+""",
+)
+
+# no args
+test.run('-Q -q .', stdout="()\n")
+# one arg, should fail
+test.run(
+ '-Q -q . --extras A',
+ status=2,
+ stderr="""\
+usage: scons [OPTION] [TARGET] ...
+
+SCons Error: --extras option requires 2 arguments
+""",
+)
+# two args
+test.run('-Q -q . --extras A B', status=1, stdout="('A', 'B')\n")
+# -- means the rest are not processed as args
+test.run('-Q -q . -- --extras A B', status=1, stdout="()\n")
+
+# Now test what has been a bug: another option is
+# also defined, this impacts the collection of args for the nargs>1 opt
+test.write(
+ 'SConstruct',
+ """\
+env = Environment()
+AddOption(
+ '--prefix',
+ nargs=1,
+ dest='prefix',
+ action='store',
+ type='string',
+ metavar='DIR',
+ help='installation prefix',
+)
+AddOption(
+ '--extras',
+ nargs=2,
+ dest='extras',
+ action='store',
+ type='string',
+ metavar='FILE1 FILE2',
+ default=(),
+ help='two extra files to install',
+)
+print(str(GetOption('prefix')))
+print(str(GetOption('extras')))
+""",
+)
+
+# no options
+test.run('-Q -q .', stdout="None\n()\n")
+# one single-arg option
+test.run('-Q -q . --prefix=/home/foo', stdout="/home/foo\n()\n")
+# one two-arg option
+test.run('-Q -q . --extras A B', status=1, stdout="None\n('A', 'B')\n")
+# single-arg option followed by two-arg option
+test.run(
+ '-Q -q . --prefix=/home/foo --extras A B',
+ status=1,
+ stdout="/home/foo\n('A', 'B')\n",
+)
+# two-arg option followed by single-arg option
+test.run(
+ '-Q -q . --extras A B --prefix=/home/foo',
+ status=1,
+ stdout="/home/foo\n('A', 'B')\n",
+)
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/Install/rec-sub-dir.py b/test/Install/rec-sub-dir.py
new file mode 100644
index 0000000..0a11928
--- /dev/null
+++ b/test/Install/rec-sub-dir.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+#
+# MIT Licenxe
+#
+# 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 using Install() on directory that contains existing subdirectories
+causing copytree recursion where the directory already exists.
+"""
+
+import os.path
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+DefaultEnvironment(tools=[])
+Execute(Mkdir('a/b/c'))
+Execute(Mkdir('b/c/d'))
+Install('z', 'a')
+Install('z/a', 'b')
+""")
+
+expect="""\
+Mkdir("a/b/c")
+Mkdir("b/c/d")
+Install directory: "a" as "z%sa"
+Install directory: "b" as "z%sa%sb"
+""" % (os.sep, os.sep, os.sep)
+test.run(arguments=["-Q"], stdout=expect)
+
+test.must_exist(test.workpath('a', 'b', 'c'))
+test.must_exist(test.workpath('b', 'c', 'd'))
+test.must_exist(test.workpath('z', 'a', 'b', 'c', 'd'))
+
+# this run used to fail on Windows with an OS error before the copytree fix
+test.run(arguments=["-Q"])
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/SWIG/SWIGOUTDIR.py b/test/SWIG/SWIGOUTDIR.py
index 10b1575..6b600d7 100644
--- a/test/SWIG/SWIGOUTDIR.py
+++ b/test/SWIG/SWIGOUTDIR.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
+# 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
@@ -20,9 +21,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__"
"""
Verify that use of the $SWIGOUTDIR variable causes SCons to recognize
diff --git a/test/SWIG/generated_swigfile.py b/test/SWIG/generated_swigfile.py
index 145349b..8d2a2c9 100644
--- a/test/SWIG/generated_swigfile.py
+++ b/test/SWIG/generated_swigfile.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
+# 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
@@ -20,9 +21,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__"
"""
Verify that SCons realizes the -noproxy option means no .py file will
@@ -38,14 +36,8 @@ import TestSCons
if sys.platform == 'win32':
_dll = '.dll'
else:
- _dll = '.so'
+ _dll = '.so'
-# swig-python expects specific filenames.
-# the platform specific suffix won't necessarily work.
-if sys.platform == 'win32':
- _dll = '.dll'
-else:
- _dll = '.so'
test = TestSCons.TestSCons()
@@ -54,12 +46,11 @@ if not swig:
test.skip_test('Can not find installed "swig", skipping test.\n')
python, python_include, python_libpath, python_lib = \
- test.get_platform_python_info(python_h_required=True)
+ test.get_platform_python_info(python_h_required=True)
# handle testing on other platforms:
ldmodule_prefix = '_'
-
test.write('SConstruct', """
foo = Environment(CPPPATH=[r'%(python_include)s'],
SWIG=[r'%(swig)s'],
@@ -69,7 +60,6 @@ python_interface = foo.Command( 'test_py_swig.i', Value(1), 'echo %%module test_
python_c_file = foo.CFile( target='python_swig_test',source=python_interface, SWIGFLAGS = '-python -c++' )
java_interface = foo.Command( 'test_java_swig.i', Value(1),'echo %%module test_java_swig > test_java_swig.i' )
java_c_file = foo.CFile( target='java_swig_test' ,source=java_interface, SWIGFLAGS = '-java -c++' )
-
""" % locals())
expected_stdout = """\
@@ -78,12 +68,11 @@ echo %%module test_java_swig > test_java_swig.i
echo %%module test_py_swig > test_py_swig.i
%(swig)s -o python_swig_test_wrap.cc -python -c++ test_py_swig.i
""" % locals()
-test.run(arguments = '.',stdout=test.wrap_stdout(expected_stdout))
-
+test.run(arguments='.', stdout=test.wrap_stdout(expected_stdout))
# If we mistakenly depend on the .py file that SWIG didn't create
# (suppressed by the -noproxy option) then the build won't be up-to-date.
-test.up_to_date(arguments = '.')
+test.up_to_date(arguments='.')
test.pass_test()
diff --git a/test/_CPPINCFLAGS.py b/test/_CPPINCFLAGS.py
index c5096ba..8bb8261 100644
--- a/test/_CPPINCFLAGS.py
+++ b/test/_CPPINCFLAGS.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
+# 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
@@ -20,9 +21,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__"
"""
Test that we can expand $_CPPINCFLAGS correctly regardless of whether
diff --git a/test/ninja/build_libraries.py b/test/ninja/build_libraries.py
new file mode 100644
index 0000000..eb8eb74
--- /dev/null
+++ b/test/ninja/build_libraries.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+
+import os
+
+import TestSCons
+from TestCmd import IS_WINDOWS, IS_MACOS
+
+test = TestSCons.TestSCons()
+
+try:
+ import ninja
+except ImportError:
+ test.skip_test("Could not find ninja module in python")
+
+ninja_binary = test.where_is('ninja')
+if not ninja_binary:
+ test.skip_test("Could not find ninja. Skipping test.")
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+ninja_bin = os.path.abspath(os.path.join(
+ ninja.__file__,
+ os.pardir,
+ 'data',
+ 'bin',
+ 'ninja' + _exe))
+
+test.dir_fixture('ninja-fixture')
+
+if IS_WINDOWS:
+ lib_suffix = '.lib'
+ staticlib_suffix = '.lib'
+ lib_prefix = ''
+ win32 = ", 'WIN32'"
+else:
+ lib_suffix = '.so'
+ staticlib_suffix = '.a'
+ lib_prefix = 'lib'
+ win32 = ''
+
+if IS_MACOS:
+ lib_suffix = '.dylib'
+
+test.write('SConstruct', """
+SetOption('experimental','ninja')
+DefaultEnvironment(tools=[])
+env = Environment()
+env.Tool('ninja')
+env['NINJA'] = "%(ninja_bin)s"
+
+shared_lib = env.SharedLibrary(target = 'test_impl', source = 'test_impl.c', CPPDEFINES=['LIBRARY_BUILD'%(win32)s])
+env.Program(target = 'test', source = 'test1.c', LIBS=['test_impl'], LIBPATH=['.'], RPATH='.')
+
+static_obj = env.Object(target = 'test_impl_static', source = 'test_impl.c')
+static_lib = env.StaticLibrary(target = 'test_impl_static', source = static_obj)
+static_obj = env.Object(target = 'test_static', source = 'test1.c')
+env.Program(target = 'test_static', source = static_obj, LIBS=[static_lib], LIBPATH=['.'])
+""" % locals())
+# generate simple build
+test.run(stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_contain_all(test.stdout(), 'Executing:')
+test.must_contain_all(test.stdout(), 'ninja%(_exe)s -f' % locals())
+test.run(program=test.workpath('test'), stdout="library_function")
+test.run(program=test.workpath('test_static'), stdout="library_function")
+
+# clean build and ninja files
+test.run(arguments='-c', stdout=None)
+test.must_contain_all_lines(test.stdout(), [
+ ('Removed %stest_impl' % lib_prefix) + lib_suffix,
+ 'Removed test' + _exe,
+ ('Removed %stest_impl_static' % lib_prefix) + staticlib_suffix,
+ 'Removed test_static' + _exe,
+ 'Removed build.ninja'])
+
+# only generate the ninja file
+test.run(arguments='--disable-execute-ninja', stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_not_exist(test.workpath('test'))
+test.must_not_exist(test.workpath('test_static'))
+
+# run ninja independently
+program = test.workpath('run_ninja_env.bat') if IS_WINDOWS else ninja_bin
+test.run(program=program, stdout=None)
+test.run(program=test.workpath('test'), stdout="library_function")
+test.run(program=test.workpath('test_static'), stdout="library_function")
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/ninja/copy_function_command.py b/test/ninja/copy_function_command.py
new file mode 100644
index 0000000..7e999b3
--- /dev/null
+++ b/test/ninja/copy_function_command.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+import os
+
+import TestSCons
+from TestCmd import IS_WINDOWS
+
+test = TestSCons.TestSCons()
+
+try:
+ import ninja
+except ImportError:
+ test.skip_test("Could not find module in python")
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+ninja_bin = os.path.abspath(os.path.join(
+ ninja.__file__,
+ os.pardir,
+ 'data',
+ 'bin',
+ 'ninja' + _exe))
+
+test.dir_fixture('ninja-fixture')
+
+test.write('SConstruct', """
+SetOption('experimental','ninja')
+DefaultEnvironment(tools=[])
+env = Environment()
+env.Tool('ninja')
+env.Command('foo2.c', ['foo.c'], Copy('$TARGET','$SOURCE'))
+env.Program(target = 'foo', source = 'foo2.c')
+""")
+
+# generate simple build
+test.run(stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_contain_all(test.stdout(), 'Executing:')
+test.must_contain_all(test.stdout(), 'ninja%(_exe)s -f' % locals())
+test.run(program=test.workpath('foo'), stdout="foo.c")
+
+# clean build and ninja files
+test.run(arguments='-c', stdout=None)
+test.must_contain_all_lines(test.stdout(), [
+ 'Removed foo2.o',
+ 'Removed foo2.c',
+ 'Removed foo' + _exe,
+ 'Removed build.ninja'])
+
+# only generate the ninja file
+test.run(arguments='--disable-execute-ninja', stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_not_exist(test.workpath('foo'))
+
+# run ninja independently
+program = test.workpath('run_ninja_env.bat') if IS_WINDOWS else ninja_bin
+test.run(program=program, stdout=None)
+test.run(program=test.workpath('foo'), stdout="foo.c")
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/ninja/generate_and_build.py b/test/ninja/generate_and_build.py
new file mode 100644
index 0000000..91be108
--- /dev/null
+++ b/test/ninja/generate_and_build.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+import os
+
+import TestSCons
+from TestCmd import IS_WINDOWS
+
+test = TestSCons.TestSCons()
+
+try:
+ import ninja
+except ImportError:
+ test.skip_test("Could not find module in python")
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+ninja_bin = os.path.abspath(os.path.join(
+ ninja.__file__,
+ os.pardir,
+ 'data',
+ 'bin',
+ 'ninja' + _exe))
+
+test.dir_fixture('ninja-fixture')
+
+test.file_fixture('ninja_test_sconscripts/sconstruct_generate_and_build', 'SConstruct')
+
+# generate simple build
+test.run(stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_contain_all(test.stdout(), 'Executing:')
+test.must_contain_all(test.stdout(), 'ninja%(_exe)s -f' % locals())
+test.run(program=test.workpath('foo' + _exe), stdout="foo.c")
+
+# clean build and ninja files
+test.run(arguments='-c', stdout=None)
+test.must_contain_all_lines(test.stdout(), [
+ 'Removed foo.o',
+ 'Removed foo',
+ 'Removed build.ninja'])
+
+# only generate the ninja file
+test.run(arguments='--disable-execute-ninja', stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_not_exist(test.workpath('foo' + _exe))
+
+# run ninja independently
+program = test.workpath('run_ninja_env.bat') if IS_WINDOWS else ninja_bin
+test.run(program=program, stdout=None)
+test.run(program=test.workpath('foo' + _exe), stdout="foo.c")
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/ninja/generate_and_build_cxx.py b/test/ninja/generate_and_build_cxx.py
new file mode 100644
index 0000000..074a5cb
--- /dev/null
+++ b/test/ninja/generate_and_build_cxx.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+import os
+
+import TestSCons
+from TestCmd import IS_WINDOWS
+
+test = TestSCons.TestSCons()
+
+try:
+ import ninja
+except ImportError:
+ test.skip_test("Could not find module in python")
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+ninja_bin = os.path.abspath(os.path.join(
+ ninja.__file__,
+ os.pardir,
+ 'data',
+ 'bin',
+ 'ninja' + _exe))
+
+test.dir_fixture('ninja-fixture')
+
+test.file_fixture('ninja_test_sconscripts/sconstruct_generate_and_build_cxx',
+ 'SConstruct')
+
+# generate simple build
+test.run()
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_contain_all(test.stdout(), 'Executing:')
+test.must_contain_all(test.stdout(), 'ninja%(_exe)s -f' % locals())
+test.run(program=test.workpath('test2' + _exe), stdout="print_function")
+
+# clean build and ninja files
+test.run(arguments='-c', stdout=None)
+test.must_contain_all_lines(test.stdout(), [
+ 'Removed test2.o',
+ 'Removed test2',
+ 'Removed build.ninja'])
+
+# only generate the ninja file
+test.run(arguments='--disable-execute-ninja', stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_not_exist(test.workpath('test2' + _exe))
+
+# run ninja independently
+program = test.workpath('run_ninja_env.bat') if IS_WINDOWS else ninja_bin
+test.run(program=program, stdout=None)
+test.run(program=test.workpath('test2' + _exe), stdout="print_function")
+
+test.write('test2.hpp', """
+#include <string>
+#include <iostream>
+
+class Foo
+{
+public:
+ int print_function();
+ int print_function2(){
+ std::cout << "2";
+ return 0;
+ };
+};
+""")
+
+# generate simple build
+test.run(program=program, stdout=None)
+test.run(program=test.workpath('test2' + _exe), stdout="print_function2")
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/ninja/generate_source.py b/test/ninja/generate_source.py
new file mode 100644
index 0000000..8300176
--- /dev/null
+++ b/test/ninja/generate_source.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+import os
+
+import TestSCons
+from TestCmd import IS_WINDOWS
+
+test = TestSCons.TestSCons()
+
+try:
+ import ninja
+except ImportError:
+ test.skip_test("Could not find module in python")
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+ninja_bin = os.path.abspath(os.path.join(
+ ninja.__file__,
+ os.pardir,
+ 'data',
+ 'bin',
+ 'ninja' + _exe))
+
+test.dir_fixture('ninja-fixture')
+
+shell = '' if IS_WINDOWS else './'
+
+test.write('SConstruct', """
+SetOption('experimental','ninja')
+DefaultEnvironment(tools=[])
+
+env = Environment()
+env.Tool('ninja')
+prog = env.Program(target = 'generate_source', source = 'generate_source.c')
+env.Command('generated_source.c', prog, '%(shell)sgenerate_source%(_exe)s')
+env.Program(target = 'generated_source', source = 'generated_source.c')
+""" % locals())
+
+test.write('generate_source.c', """
+#include <stdio.h>
+
+int main(int argc, char *argv[]) {
+ FILE *fp;
+
+ fp = fopen("generated_source.c", "w");
+ fprintf(fp, "#include <stdio.h>\\n");
+ fprintf(fp, "#include <stdlib.h>\\n");
+ fprintf(fp, "\\n");
+ fprintf(fp, "int\\n");
+ fprintf(fp, "main(int argc, char *argv[])\\n");
+ fprintf(fp, "{\\n");
+ fprintf(fp, " printf(\\"generated_source.c\\");\\n");
+ fprintf(fp, " exit (0);\\n");
+ fprintf(fp, "}\\n");
+ fclose(fp);
+}
+""")
+
+# generate simple build
+test.run(stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_contain_all(test.stdout(), 'Executing:')
+test.must_contain_all(test.stdout(), 'ninja%(_exe)s -f' % locals())
+test.run(program=test.workpath('generated_source' + _exe), stdout="generated_source.c")
+
+# clean build and ninja files
+test.run(arguments='-c', stdout=None)
+test.must_contain_all_lines(test.stdout(), [
+ 'Removed generate_source.o',
+ 'Removed generate_source' + _exe,
+ 'Removed generated_source.c',
+ 'Removed generated_source.o',
+ 'Removed generated_source' + _exe,
+ 'Removed build.ninja'])
+
+# only generate the ninja file
+test.run(arguments='--disable-execute-ninja', stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_not_exist(test.workpath('generated_source' + _exe))
+
+# run ninja independently
+program = test.workpath('run_ninja_env.bat') if IS_WINDOWS else ninja_bin
+test.run(program=program, stdout=None)
+test.run(program=test.workpath('generated_source' + _exe), stdout="generated_source.c")
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/ninja/iterative_speedup.py b/test/ninja/iterative_speedup.py
new file mode 100644
index 0000000..05e372c
--- /dev/null
+++ b/test/ninja/iterative_speedup.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+import os
+import random
+import time
+
+import TestSCons
+from TestCmd import IS_WINDOWS
+
+test = TestSCons.TestSCons()
+
+try:
+ import ninja
+except ImportError:
+ test.skip_test("Could not find module in python")
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+ninja_bin = os.path.abspath(os.path.join(
+ ninja.__file__,
+ os.pardir,
+ 'data',
+ 'bin',
+ 'ninja' + _exe))
+
+test.dir_fixture('ninja-fixture')
+
+test.write('source_0.c', """
+#include <stdio.h>
+#include <stdlib.h>
+#include "source_0.h"
+
+int
+print_function0()
+{
+ printf("main print");
+ return 0;
+}
+""")
+
+test.write('source_0.h', """
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+print_function0();
+""")
+
+
+def get_num_cpus():
+ """
+ Function to get the number of CPUs the system has.
+ """
+ # Linux, Unix and MacOS:
+ if hasattr(os, "sysconf"):
+ if 'SC_NPROCESSORS_ONLN' in os.sysconf_names:
+ # Linux & Unix:
+ ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
+ if isinstance(ncpus, int) and ncpus > 0:
+ return ncpus
+ # OSX:
+ return int(os.popen("sysctl -n hw.ncpu")[1].read())
+ # Windows:
+ if 'NUMBER_OF_PROCESSORS' in os.environ:
+ ncpus = int(os.environ["NUMBER_OF_PROCESSORS"])
+ if ncpus > 0:
+ return ncpus
+ # Default
+ return 1
+
+
+def generate_source(parent_source, current_source):
+ test.write('source_{}.c'.format(current_source), """
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include "source_%(parent_source)s.h"
+ #include "source_%(current_source)s.h"
+
+ int
+ print_function%(current_source)s()
+ {
+ return print_function%(parent_source)s();
+ }
+ """ % locals())
+
+ test.write('source_{}.h'.format(current_source), """
+ #include <stdio.h>
+ #include <stdlib.h>
+
+ int
+ print_function%(current_source)s();
+ """ % locals())
+
+
+def mod_source_return(test_num):
+ parent_source = test_num - 1
+ test.write('source_{}.c'.format(test_num), """
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include "source_%(parent_source)s.h"
+ #include "source_%(test_num)s.h"
+
+ int
+ print_function%(test_num)s()
+ {
+ int test = 5 + 5;
+ print_function%(parent_source)s();
+ return test;
+ }
+ """ % locals())
+
+
+def mod_source_orig(test_num):
+ parent_source = test_num - 1
+ test.write('source_{}.c'.format(test_num), """
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include "source_%(parent_source)s.h"
+ #include "source_%(test_num)s.h"
+
+ int
+ print_function%(test_num)s()
+ {
+ return print_function%(parent_source)s();
+ }
+ """ % locals())
+
+
+num_source = 200
+for i in range(1, num_source + 1):
+ generate_source(i - 1, i)
+
+test.write('main.c', """
+#include <stdio.h>
+#include <stdlib.h>
+#include "source_%(num_source)s.h"
+int
+main()
+{
+ print_function%(num_source)s();
+ exit(0);
+}
+""" % locals())
+
+test.write('SConstruct', """
+SetOption('experimental','ninja')
+DefaultEnvironment(tools=[])
+
+env = Environment()
+env.Tool('ninja')
+sources = ['main.c'] + env.Glob('source*.c')
+env.Program(target = 'print_bin', source = sources)
+""")
+
+test.write('SConstruct_no_ninja', """
+env = Environment()
+sources = ['main.c'] + env.Glob('source*.c')
+env.Program(target = 'print_bin', source = sources)
+""")
+
+tests_mods = []
+ninja_times = []
+scons_times = []
+for _ in range(10):
+ tests_mods += [random.randrange(1, num_source, 1)]
+jobs = '-j' + str(get_num_cpus())
+
+ninja_program = [test.workpath('run_ninja_env.bat'), jobs] if IS_WINDOWS else [ninja_bin, jobs]
+
+start = time.perf_counter()
+test.run(arguments='--disable-execute-ninja', stdout=None)
+test.run(program=ninja_program, stdout=None)
+stop = time.perf_counter()
+ninja_times += [stop - start]
+test.run(program=test.workpath('print_bin'), stdout="main print")
+
+for test_mod in tests_mods:
+ mod_source_return(test_mod)
+ start = time.perf_counter()
+ test.run(program=ninja_program, stdout=None)
+ stop = time.perf_counter()
+ ninja_times += [stop - start]
+
+for test_mod in tests_mods:
+ mod_source_orig(test_mod)
+
+# clean build and ninja files
+test.run(arguments='-c', stdout=None)
+test.must_contain_all_lines(test.stdout(), [
+ 'Removed build.ninja'])
+
+start = time.perf_counter()
+test.run(arguments=["-f", "SConstruct_no_ninja", jobs], stdout=None)
+stop = time.perf_counter()
+scons_times += [stop - start]
+test.run(program=test.workpath('print_bin'), stdout="main print")
+
+for test_mod in tests_mods:
+ mod_source_return(test_mod)
+ start = time.perf_counter()
+ test.run(arguments=["-f", "SConstruct_no_ninja", jobs], stdout=None)
+ stop = time.perf_counter()
+ scons_times += [stop - start]
+
+full_build_print = True
+for ninja_time, scons_time in zip(ninja_times, scons_times):
+ if ninja_time > scons_time:
+ test.fail_test(message="Ninja was slower than SCons: SCons: {:.3f}s Ninja: {:.3f}s".format(scons_time, ninja_time))
+ if full_build_print:
+ full_build_print = False
+ print("Clean build {} files - SCons: {:.3f}s Ninja: {:.3f}s".format(num_source, scons_time, ninja_time))
+ else:
+ print("Single File Rebuild - SCons: {:.3f}s Ninja: {:.3f}s".format(scons_time, ninja_time))
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/ninja/multi_env.py b/test/ninja/multi_env.py
new file mode 100644
index 0000000..e5da6cf
--- /dev/null
+++ b/test/ninja/multi_env.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+import os
+
+import TestSCons
+from TestCmd import IS_WINDOWS
+
+test = TestSCons.TestSCons()
+
+try:
+ import ninja
+except ImportError:
+ test.skip_test("Could not find module in python")
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+ninja_bin = os.path.abspath(os.path.join(
+ ninja.__file__,
+ os.pardir,
+ 'data',
+ 'bin',
+ 'ninja' + _exe))
+
+test = TestSCons.TestSCons()
+
+test.dir_fixture('ninja-fixture')
+
+test.write('SConstruct', """
+SetOption('experimental','ninja')
+DefaultEnvironment(tools=[])
+
+env = Environment()
+env.Tool('ninja')
+env.Program(target = 'foo', source = 'foo.c')
+
+env2 = Environment()
+env2.Program(target = 'bar', source = 'bar.c')
+""")
+
+# generate simple build
+test.run(stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_contain_all(test.stdout(), 'Executing:')
+test.must_contain_all(test.stdout(), 'ninja%(_exe)s -f' % locals())
+test.run(program=test.workpath('foo' + _exe), stdout="foo.c")
+test.run(program=test.workpath('bar' + _exe), stdout="bar.c")
+
+# clean build and ninja files
+test.run(arguments='-c', stdout=None)
+test.must_contain_all_lines(test.stdout(), [
+ 'Removed foo.o',
+ 'Removed foo' + _exe,
+ 'Removed bar.o',
+ 'Removed bar' + _exe,
+ 'Removed build.ninja'])
+
+# only generate the ninja file
+test.run(arguments='--disable-execute-ninja', stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_not_exist(test.workpath('foo' + _exe))
+test.must_not_exist(test.workpath('bar' + _exe))
+
+# run ninja independently
+program = test.workpath('run_ninja_env.bat') if IS_WINDOWS else ninja_bin
+test.run(program=program, stdout=None)
+test.run(program=test.workpath('foo' + _exe), stdout="foo.c")
+test.run(program=test.workpath('bar' + _exe), stdout="bar.c")
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/ninja/ninja-fixture/bar.c b/test/ninja/ninja-fixture/bar.c
new file mode 100644
index 0000000..15b2ecc
--- /dev/null
+++ b/test/ninja/ninja-fixture/bar.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("bar.c");
+ exit (0);
+}
diff --git a/test/ninja/ninja-fixture/foo.c b/test/ninja/ninja-fixture/foo.c
new file mode 100644
index 0000000..ba35c68
--- /dev/null
+++ b/test/ninja/ninja-fixture/foo.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+int
+main(int argc, char *argv[])
+{
+ argv[argc++] = "--";
+ printf("foo.c");
+ exit (0);
+}
diff --git a/test/ninja/ninja-fixture/test1.c b/test/ninja/ninja-fixture/test1.c
new file mode 100644
index 0000000..678461f
--- /dev/null
+++ b/test/ninja/ninja-fixture/test1.c
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef WIN32
+#ifdef LIBRARY_BUILD
+#define DLLEXPORT __declspec(dllexport)
+#else
+#define DLLEXPORT __declspec(dllimport)
+#endif
+#else
+#define DLLEXPORT
+#endif
+
+DLLEXPORT extern int library_function(void);
+
+int
+main(int argc, char *argv[])
+{
+ library_function();
+ exit(0);
+}
diff --git a/test/ninja/ninja-fixture/test2.cpp b/test/ninja/ninja-fixture/test2.cpp
new file mode 100644
index 0000000..69b54c9
--- /dev/null
+++ b/test/ninja/ninja-fixture/test2.cpp
@@ -0,0 +1,16 @@
+#include "test2.hpp"
+
+int
+main(int argc, char *argv[])
+{
+ Foo* test = new Foo();
+ test->print_function();
+ test->print_function2();
+ return 0;
+}
+
+int Foo::print_function()
+{
+ std::cout << "print_function";
+ return 0;
+} \ No newline at end of file
diff --git a/test/ninja/ninja-fixture/test2.hpp b/test/ninja/ninja-fixture/test2.hpp
new file mode 100644
index 0000000..f0583fc
--- /dev/null
+++ b/test/ninja/ninja-fixture/test2.hpp
@@ -0,0 +1,9 @@
+#include <string>
+#include <iostream>
+
+class Foo
+{
+public:
+ int print_function();
+ int print_function2(){return 0;};
+};
diff --git a/test/ninja/ninja-fixture/test_impl.c b/test/ninja/ninja-fixture/test_impl.c
new file mode 100644
index 0000000..ac3fd88
--- /dev/null
+++ b/test/ninja/ninja-fixture/test_impl.c
@@ -0,0 +1,20 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef WIN32
+#ifdef LIBRARY_BUILD
+#define DLLEXPORT __declspec(dllexport)
+#else
+#define DLLEXPORT __declspec(dllimport)
+#endif
+#else
+#define DLLEXPORT
+#endif
+
+
+DLLEXPORT int
+library_function(void)
+{
+ printf("library_function");
+ return 0;
+}
diff --git a/test/ninja/ninja_test_sconscripts/sconstest.skip b/test/ninja/ninja_test_sconscripts/sconstest.skip
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/ninja/ninja_test_sconscripts/sconstest.skip
diff --git a/test/ninja/ninja_test_sconscripts/sconstruct_generate_and_build b/test/ninja/ninja_test_sconscripts/sconstruct_generate_and_build
new file mode 100644
index 0000000..81a4366
--- /dev/null
+++ b/test/ninja/ninja_test_sconscripts/sconstruct_generate_and_build
@@ -0,0 +1,6 @@
+SetOption('experimental','ninja')
+DefaultEnvironment(tools=[])
+
+env = Environment()
+env.Tool('ninja')
+env.Program(target='foo', source='foo.c')
diff --git a/test/ninja/ninja_test_sconscripts/sconstruct_generate_and_build_cxx b/test/ninja/ninja_test_sconscripts/sconstruct_generate_and_build_cxx
new file mode 100644
index 0000000..f7137df
--- /dev/null
+++ b/test/ninja/ninja_test_sconscripts/sconstruct_generate_and_build_cxx
@@ -0,0 +1,5 @@
+SetOption('experimental','ninja')
+DefaultEnvironment(tools=[])
+env = Environment()
+env.Tool('ninja')
+env.Program(target = 'test2', source = 'test2.cpp')
diff --git a/test/ninja/shell_command.py b/test/ninja/shell_command.py
new file mode 100644
index 0000000..a6926c7
--- /dev/null
+++ b/test/ninja/shell_command.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+import os
+
+import TestSCons
+from TestCmd import IS_WINDOWS
+
+test = TestSCons.TestSCons()
+
+try:
+ import ninja
+except ImportError:
+ test.skip_test("Could not find module in python")
+
+_python_ = TestSCons._python_
+_exe = TestSCons._exe
+
+ninja_bin = os.path.abspath(os.path.join(
+ ninja.__file__,
+ os.pardir,
+ 'data',
+ 'bin',
+ 'ninja' + _exe))
+
+test.dir_fixture('ninja-fixture')
+
+shell = '' if IS_WINDOWS else './'
+
+test.write('SConstruct', """
+SetOption('experimental','ninja')
+DefaultEnvironment(tools=[])
+
+env = Environment()
+env.Tool('ninja')
+prog = env.Program(target = 'foo', source = 'foo.c')
+env.Command('foo.out', prog, '%(shell)sfoo%(_exe)s > foo.out')
+""" % locals())
+
+# generate simple build
+test.run(stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_contain_all(test.stdout(), 'Executing:')
+test.must_contain_all(test.stdout(), 'ninja%(_exe)s -f' % locals())
+test.must_match('foo.out', 'foo.c')
+
+# clean build and ninja files
+test.run(arguments='-c', stdout=None)
+test.must_contain_all_lines(test.stdout(), [
+ 'Removed foo.o',
+ 'Removed foo%(_exe)s' % locals(),
+ 'Removed foo.out',
+ 'Removed build.ninja'])
+
+# only generate the ninja file
+test.run(arguments='--disable-execute-ninja', stdout=None)
+test.must_contain_all_lines(test.stdout(), ['Generating: build.ninja'])
+test.must_not_exist(test.workpath('foo.out'))
+
+# run ninja independently
+program = test.workpath('run_ninja_env.bat') if IS_WINDOWS else ninja_bin
+test.run(program=program, stdout=None)
+test.must_match('foo.out', 'foo.c')
+
+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/option--experimental.py b/test/option/option--experimental.py
index 0f0e9ec..2e06dc3 100644
--- a/test/option/option--experimental.py
+++ b/test/option/option--experimental.py
@@ -35,12 +35,13 @@ test.file_fixture('fixture/SConstruct__experimental', 'SConstruct')
tests = [
('.', []),
- ('--experimental=all', ['transporter', 'warp_speed']),
+ ('--experimental=ninja', ['ninja']),
+ ('--experimental=all', ['ninja', 'transporter', 'warp_speed']),
('--experimental=none', []),
]
for args, exper in tests:
- read_string = """All Features=transporter,warp_speed
+ read_string = """All Features=ninja,transporter,warp_speed
Experimental=%s
""" % (exper)
test.run(arguments=args,
@@ -49,7 +50,7 @@ Experimental=%s
test.run(arguments='--experimental=warp_drive',
stderr="""usage: scons [OPTION] [TARGET] ...
-SCons Error: option --experimental: invalid choice: 'warp_drive' (choose from 'all','none','transporter','warp_speed')
+SCons Error: option --experimental: invalid choice: 'warp_drive' (choose from 'all','none','ninja','transporter','warp_speed')
""",
status=2)
diff --git a/testing/framework/TestCmd.py b/testing/framework/TestCmd.py
index 333901c..903bee6 100644
--- a/testing/framework/TestCmd.py
+++ b/testing/framework/TestCmd.py
@@ -314,6 +314,7 @@ from subprocess import PIPE, STDOUT
IS_WINDOWS = sys.platform == 'win32'
+IS_MACOS = sys.platform == 'darwin'
IS_64_BIT = sys.maxsize > 2**32
IS_PYPY = hasattr(sys, 'pypy_translation_info')