summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons
diff options
context:
space:
mode:
authorWilliam Deegan <bill@baddogconsulting.com>2020-04-27 00:05:14 (GMT)
committerGitHub <noreply@github.com>2020-04-27 00:05:14 (GMT)
commit5e70c65f633dcecc035751c9f0c6f894088df8a0 (patch)
tree40ab00e0e5d3606906f8503664c06b918ee023ad /src/engine/SCons
parente2f22641ceeb8a61215e5f649d8e97d3d67dd4a8 (diff)
parentb235883c0be3ae674634d2ce8b24250732273432 (diff)
downloadSCons-5e70c65f633dcecc035751c9f0c6f894088df8a0.zip
SCons-5e70c65f633dcecc035751c9f0c6f894088df8a0.tar.gz
SCons-5e70c65f633dcecc035751c9f0c6f894088df8a0.tar.bz2
Merge branch 'master' into c-conditional-scanner
Diffstat (limited to 'src/engine/SCons')
-rw-r--r--src/engine/SCons/Action.py112
-rw-r--r--src/engine/SCons/Action.xml81
-rw-r--r--src/engine/SCons/CacheDir.py67
-rw-r--r--src/engine/SCons/CacheDirTests.py2
-rw-r--r--src/engine/SCons/Defaults.xml11
-rw-r--r--src/engine/SCons/Environment.xml84
-rw-r--r--src/engine/SCons/Node/FS.py8
-rw-r--r--src/engine/SCons/Script/Main.py5
-rw-r--r--src/engine/SCons/Script/SConsOptions.py4
-rw-r--r--src/engine/SCons/Script/__init__.py4
-rw-r--r--src/engine/SCons/SubstTests.py7
-rw-r--r--src/engine/SCons/Tool/JavaCommon.py2
-rw-r--r--src/engine/SCons/Tool/JavaCommonTests.py24
-rw-r--r--src/engine/SCons/Tool/MSCommon/__init__.py3
-rw-r--r--src/engine/SCons/Tool/MSCommon/common.py83
-rw-r--r--src/engine/SCons/Tool/MSCommon/vc.py70
-rw-r--r--src/engine/SCons/Tool/MSCommon/vcTests.py34
-rw-r--r--src/engine/SCons/Tool/MSCommon/vs.py36
-rw-r--r--src/engine/SCons/Tool/applelink.xml6
-rw-r--r--src/engine/SCons/Tool/docbook/__init__.py9
-rw-r--r--src/engine/SCons/Tool/gnulink.py17
-rw-r--r--src/engine/SCons/Tool/jar.xml2
-rw-r--r--src/engine/SCons/Tool/javac.xml2
-rw-r--r--src/engine/SCons/Tool/javah.xml17
-rw-r--r--src/engine/SCons/Tool/linkloc.py2
-rw-r--r--src/engine/SCons/Tool/mingw.py1
-rw-r--r--src/engine/SCons/Tool/msvc.py6
-rw-r--r--src/engine/SCons/Tool/msvc.xml50
-rw-r--r--src/engine/SCons/Tool/msvs.xml6
-rw-r--r--src/engine/SCons/Tool/msvsTests.py2
-rw-r--r--src/engine/SCons/Tool/packaging/__init__.py13
-rw-r--r--src/engine/SCons/Tool/packaging/__init__.xml90
-rw-r--r--src/engine/SCons/Tool/textfile.py2
-rw-r--r--src/engine/SCons/Util.py58
-rw-r--r--src/engine/SCons/Utilities/ConfigureCache.py182
-rw-r--r--src/engine/SCons/Utilities/__init__.py0
-rw-r--r--src/engine/SCons/Utilities/sconsign.py507
-rw-r--r--src/engine/SCons/Variables/PathVariable.py15
-rw-r--r--src/engine/SCons/Variables/__init__.py13
39 files changed, 1300 insertions, 337 deletions
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index 0b7282c..5bc85d5 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -105,6 +105,7 @@ import pickle
import re
import sys
import subprocess
+from subprocess import DEVNULL
import itertools
import inspect
from collections import OrderedDict
@@ -751,30 +752,20 @@ def get_default_ENV(env):
return default_ENV
-def _subproc(scons_env, cmd, error = 'ignore', **kw):
- """Do common setup for a subprocess.Popen() call
+def _subproc(scons_env, cmd, error='ignore', **kw):
+ """Wrapper for subprocess which pulls from construction env.
- This function is still in draft mode. We're going to need something like
- it in the long run as more and more places use subprocess, but I'm sure
- it'll have to be tweaked to get the full desired functionality.
- one special arg (so far?), 'error', to tell what to do with exceptions.
+ Use for calls to subprocess which need to interpolate values from
+ an SCons construction environment into the environment passed to
+ subprocess. Adds an an error-handling argument. Adds ability
+ to specify std{in,out,err} with "'devnull'" tag.
"""
- # allow std{in,out,err} to be "'devnull'". This is like
- # subprocess.DEVNULL, which does not exist for Py2. Use the
- # subprocess one if possible.
- # Clean this up when Py2 support is dropped
- try:
- from subprocess import DEVNULL
- except ImportError:
- DEVNULL = None
-
+ # TODO: just uses subprocess.DEVNULL now, we can drop the "devnull"
+ # string now - it is a holdover from Py2, which didn't have DEVNULL.
for stream in 'stdin', 'stdout', 'stderr':
io = kw.get(stream)
if is_String(io) and io == 'devnull':
- if DEVNULL:
- kw[stream] = DEVNULL
- else:
- kw[stream] = open(os.devnull, "r+")
+ kw[stream] = DEVNULL
# Figure out what shell environment to use
ENV = kw.get('env', None)
@@ -968,11 +959,33 @@ class CommandAction(_ActionAction):
return env.subst_target_source(cmd, SUBST_SIG, target, source)
def get_implicit_deps(self, target, source, env, executor=None):
+ """Return the implicit dependencies of this action's command line."""
icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
if is_String(icd) and icd[:1] == '$':
icd = env.subst(icd)
- if not icd or icd in ('0', 'None'):
+
+ if not icd or str(icd).lower in ('0', 'none', 'false', 'no', 'off'):
return []
+
+ try:
+ icd_int = int(icd)
+ except ValueError:
+ icd_int = None
+
+ if (icd_int and icd_int > 1) or icd == 'all':
+ # An integer value greater than 1 specifies the number of entries
+ # to scan. "all" means to scan all.
+ return self._get_implicit_deps_heavyweight(target, source, env, executor, icd_int)
+ else:
+ # Everything else (usually 1 or True) means that we want
+ # lightweight dependency scanning.
+ return self._get_implicit_deps_lightweight(target, source, env, executor)
+
+ def _get_implicit_deps_lightweight(self, target, source, env, executor):
+ """
+ Lightweight dependency scanning involves only scanning the first entry
+ in an action string, even if it contains &&.
+ """
from SCons.Subst import SUBST_SIG
if executor:
cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=executor)
@@ -990,6 +1003,65 @@ class CommandAction(_ActionAction):
res.append(env.fs.File(d))
return res
+ def _get_implicit_deps_heavyweight(self, target, source, env, executor,
+ icd_int):
+ """
+ Heavyweight dependency scanning involves scanning more than just the
+ first entry in an action string. The exact behavior depends on the
+ value of icd_int. Only files are taken as implicit dependencies;
+ directories are ignored.
+
+ If icd_int is an integer value, it specifies the number of entries to
+ scan for implicit dependencies. Action strings are also scanned after
+ a &&. So for example, if icd_int=2 and the action string is
+ "cd <some_dir> && $PYTHON $SCRIPT_PATH <another_path>", the implicit
+ dependencies would be the path to the python binary and the path to the
+ script.
+
+ If icd_int is None, all entries are scanned for implicit dependencies.
+ """
+
+ # Avoid circular and duplicate dependencies by not providing source,
+ # target, or executor to subst_list. This causes references to
+ # $SOURCES, $TARGETS, and all related variables to disappear.
+ from SCons.Subst import SUBST_SIG
+ cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, conv=lambda x: x)
+ res = []
+
+ for cmd_line in cmd_list:
+ if cmd_line:
+ entry_count = 0
+ for entry in cmd_line:
+ d = str(entry)
+ if ((icd_int is None or entry_count < icd_int) and
+ not d.startswith(('&', '-', '/') if os.name == 'nt'
+ else ('&', '-'))):
+ m = strip_quotes.match(d)
+ if m:
+ d = m.group(1)
+
+ if d:
+ # Resolve the first entry in the command string using
+ # PATH, which env.WhereIs() looks in.
+ # For now, only match files, not directories.
+ p = os.path.abspath(d) if os.path.isfile(d) else None
+ if not p and entry_count == 0:
+ p = env.WhereIs(d)
+
+ if p:
+ res.append(env.fs.File(p))
+
+ entry_count = entry_count + 1
+ else:
+ entry_count = 0 if d == '&&' else entry_count + 1
+
+ # Despite not providing source and target to env.subst() above, we
+ # can still end up with sources in this list. For example, files in
+ # LIBS will still resolve in env.subst(). This won't result in
+ # circular dependencies, but it causes problems with cache signatures
+ # changing between full and incremental builds.
+ return [r for r in res if r not in target and r not in source]
+
class CommandGeneratorAction(ActionBase):
"""Class for command-generator actions."""
diff --git a/src/engine/SCons/Action.xml b/src/engine/SCons/Action.xml
index 7a8194e..b519cdc 100644
--- a/src/engine/SCons/Action.xml
+++ b/src/engine/SCons/Action.xml
@@ -35,31 +35,88 @@ executed to build targets.
By default, SCons will add
to each target
an implicit dependency on the command
-represented by the first argument on any
-command line it executes.
+represented by the first argument of any
+command line it executes (which is typically
+the command itself). By setting such
+a dependency, &SCons; can determine that
+a target should be rebuilt if the command changes,
+such as when a compiler is upgraded to a new version.
The specific file for the dependency is
found by searching the
<varname>PATH</varname>
variable in the
-<varname>ENV</varname>
-environment used to execute the command.
+<varname>ENV</varname> dictionary
+in the &consenv; used to execute the command.
+The default is the same as
+setting the &consvar;
+&cv-IMPLICIT_COMMAND_DEPENDENCIES;
+to a True-like value (<quote>true</quote>,
+<quote>yes</quote>,
+or <quote>1</quote> - but not a number
+greater than one, as that has a different meaning).
</para>
<para>
-If the construction variable
-&cv-IMPLICIT_COMMAND_DEPENDENCIES;
-is set to a false value
-(<literal>None</literal>,
-<literal>False</literal>,
-<literal>0</literal>,
+Action strings can be segmented by the
+use of an AND operator, <literal>&amp;&amp;</literal>.
+In a segemented string, each segment is a separate
+<quote>command line</quote>, these are run
+sequentially until one fails or the entire
+sequence has been executed. If an
+action string is segmented, then the selected
+behavior of &cv-IMPLICIT_COMMAND_DEPENDENCIES;
+is applied to each segment.
+</para>
+
+<para>
+If &cv-IMPLICIT_COMMAND_DEPENDENCIES;
+is set to a False-like value
+(<quote>none</quote>,
+<quote>false</quote>,
+<quote>no</quote>,
+<quote>0</quote>,
etc.),
then the implicit dependency will
not be added to the targets
-built with that construction environment.
+built with that &consenv;.
+</para>
+
+<para>
+If &cv-IMPLICIT_COMMAND_DEPENDENCIES;
+is set to <quote>2</quote> or higher,
+then that number of arguments in the command line
+will be scanned for relative or absolute paths.
+If any are present, they will be added as
+implicit dependencies to the targets built
+with that &consenv;.
+The first argument in the command line will be
+searched for using the <varname>PATH</varname>
+variable in the <varname>ENV</varname> dictionary
+in the &consenv; used to execute the command.
+The other arguments will only be found if they
+are absolute paths or valid paths relative
+to the working directory.
+</para>
+
+<para>
+If &cv-IMPLICIT_COMMAND_DEPENDENCIES;
+is set to <quote>all</quote>,
+then all arguments in the command line will be
+scanned for relative or absolute paths.
+If any are present, they will be added as
+implicit dependencies to the targets built
+with that &consenv;.
+The first argument in the command line will be
+searched for using the <varname>PATH</varname>
+variable in the <varname>ENV</varname> dictionary
+in the &consenv; used to execute the command.
+The other arguments will only be found if they
+are absolute paths or valid paths relative
+to the working directory.
</para>
<example_commands>
-env = Environment(IMPLICIT_COMMAND_DEPENDENCIES = 0)
+env = Environment(IMPLICIT_COMMAND_DEPENDENCIES=False)
</example_commands>
</summary>
</cvar>
diff --git a/src/engine/SCons/CacheDir.py b/src/engine/SCons/CacheDir.py
index 9bb7ef3..117f6b0 100644
--- a/src/engine/SCons/CacheDir.py
+++ b/src/engine/SCons/CacheDir.py
@@ -135,9 +135,6 @@ def CachePushFunc(target, source, env):
CachePush = SCons.Action.Action(CachePushFunc, None)
-# Nasty hack to cut down to one warning for each cachedir path that needs
-# upgrading.
-warned = dict()
class CacheDir(object):
@@ -159,12 +156,12 @@ class CacheDir(object):
if path is None:
return
- self._readconfig3(path)
+ self._readconfig(path)
- def _readconfig3(self, path):
+ def _readconfig(self, path):
"""
- Python3 version of reading the cache config.
+ Read the cache config.
If directory or config file do not exist, create. Take advantage
of Py3 capability in os.makedirs() and in file open(): just try
@@ -201,64 +198,6 @@ class CacheDir(object):
raise SCons.Errors.SConsEnvironmentError(msg)
- def _readconfig2(self, path):
- """
- Python2 version of reading cache config.
-
- See if there is a config file in the cache directory. If there is,
- use it. If there isn't, and the directory exists and isn't empty,
- produce a warning. If the directory does not exist or is empty,
- write a config file.
-
- :param path: path to the cache directory
- """
- config_file = os.path.join(path, 'config')
- if not os.path.exists(config_file):
- # A note: There is a race hazard here if two processes start and
- # attempt to create the cache directory at the same time. However,
- # Python 2.x does not give you the option to do exclusive file
- # creation (not even the option to error on opening an existing
- # file for writing...). The ordering of events here is an attempt
- # to alleviate this, on the basis that it's a pretty unlikely
- # occurrence (would require two builds with a brand new cache
- # directory)
- if os.path.isdir(path) and any(f != "config" for f in os.listdir(path)):
- self.config['prefix_len'] = 1
- # When building the project I was testing this on, the warning
- # was output over 20 times. That seems excessive
- global warned
- if self.path not in warned:
- msg = "Please upgrade your cache by running " +\
- "scons-configure-cache.py " + self.path
- SCons.Warnings.warn(SCons.Warnings.CacheVersionWarning, msg)
- warned[self.path] = True
- else:
- if not os.path.isdir(path):
- try:
- os.makedirs(path)
- except OSError:
- # If someone else is trying to create the directory at
- # the same time as me, bad things will happen
- msg = "Failed to create cache directory " + path
- raise SCons.Errors.SConsEnvironmentError(msg)
-
- self.config['prefix_len'] = 2
- if not os.path.exists(config_file):
- try:
- with open(config_file, 'w') as config:
- json.dump(self.config, config)
- except Exception:
- msg = "Failed to write cache configuration for " + path
- raise SCons.Errors.SConsEnvironmentError(msg)
- else:
- try:
- with open(config_file) as config:
- self.config = json.load(config)
- except ValueError:
- msg = "Failed to read cache configuration for " + path
- raise SCons.Errors.SConsEnvironmentError(msg)
-
-
def CacheDebug(self, fmt, target, cachefile):
if cache_debug != self.current_cache_debug:
if cache_debug == '-':
diff --git a/src/engine/SCons/CacheDirTests.py b/src/engine/SCons/CacheDirTests.py
index ff22d01..a3114c1 100644
--- a/src/engine/SCons/CacheDirTests.py
+++ b/src/engine/SCons/CacheDirTests.py
@@ -168,7 +168,7 @@ class ExceptionTestCase(unittest.TestCase):
os.remove(old_config)
try:
- self._CacheDir._readconfig3(self._CacheDir.path)
+ self._CacheDir._readconfig(self._CacheDir.path)
assert False, "Should have raised exception and did not"
except SCons.Errors.SConsEnvironmentError as e:
assert str(e) == "Failed to write cache configuration for {}".format(self._CacheDir.path)
diff --git a/src/engine/SCons/Defaults.xml b/src/engine/SCons/Defaults.xml
index 22f46fe..06fad1c 100644
--- a/src/engine/SCons/Defaults.xml
+++ b/src/engine/SCons/Defaults.xml
@@ -268,12 +268,8 @@ into a list of Dir instances relative to the target being built.
<para>
The list of suffixes of files that will be scanned
for imported D package files.
-The default list is:
+The default list is <literal>['.d']</literal>.
</para>
-
-<example_commands>
-['.d']
-</example_commands>
</summary>
</cvar>
@@ -584,9 +580,12 @@ in order to execute many of the global functions in this list
from source code management systems.
The default environment is a singleton, so the keyword
arguments affect it only on the first call, on subsequent
-calls the already-constructed object is returned.
+calls the already-constructed object is returned and
+any arguments are ignored.
The default environment can be modified in the same way
as any &consenv;.
+Modifying the &defenv; has no effect on the environment
+constructed by a subsequent &f-Environment; call.
</para>
</summary>
</scons_function>
diff --git a/src/engine/SCons/Environment.xml b/src/engine/SCons/Environment.xml
index e1d5e74..5dea66e 100644
--- a/src/engine/SCons/Environment.xml
+++ b/src/engine/SCons/Environment.xml
@@ -844,7 +844,7 @@ env3 = env.Clone(CCFLAGS = '-g')
<para>
Additionally, a list of tools and a toolpath may be specified, as in
-the Environment constructor:
+the &f-link-Environment; constructor:
</para>
<example_commands>
@@ -1836,9 +1836,10 @@ Examples:
</para>
<example_commands>
-Program('foo', Glob('*.c'))
-Zip('/tmp/everything', Glob('.??*') + Glob('*'))
-sources = Glob('*.cpp', exclude=['os_*_specific_*.cpp']) + Glob('os_%s_specific_*.cpp'%currentOS)
+Program("foo", Glob("*.c"))
+Zip("/tmp/everything", Glob(".??*") + Glob("*"))
+sources = Glob("*.cpp", exclude=["os_*_specific_*.cpp"]) + \
+ Glob( "os_%s_specific_*.cpp" % currentOS)
</example_commands>
</summary>
</scons_function>
@@ -3016,31 +3017,29 @@ source_nodes = env.subst('$EXPAND_TO_NODELIST',
<scons_function name="Tool">
<arguments>
-(string, [toolpath, **kw])
+(name, [toolpath, **kwargs])
</arguments>
<summary>
+
<para>
-The
-&f-Tool;
-form of the function
-returns a callable object
-that can be used to initialize
-a construction environment using the
-tools keyword of the Environment() method.
-The object may be called with a construction
-environment as an argument,
-in which case the object will
-add the necessary variables
-to the construction environment
-and the name of the tool will be added to the
-&cv-link-TOOLS;
-construction variable.
+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>,
+to update a &consenv; with &consvars;
+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.
</para>
<para>
-Additional keyword arguments are passed to the tool's
-<function>generate</function>()
-method.
+When called as a &consenv; method,
+the tool module is called to update the
+&consenv; and the name of the tool is
+appended to the &cv-link-TOOLS;
+&consvar; in that environment.
</para>
<para>
@@ -3048,33 +3047,36 @@ Examples:
</para>
<example_commands>
-env = Environment(tools = [ Tool('msvc') ])
-
-env = Environment()
-t = Tool('msvc')
-t(env) # adds 'msvc' to the TOOLS variable
-u = Tool('opengl', toolpath = ['tools'])
-u(env) # adds 'opengl' to the TOOLS variable
+env.Tool('gcc')
+env.Tool('opengl', toolpath=['build/tools'])
</example_commands>
<para>
-The
-&f-env-Tool;
-form of the function
-applies the callable object for the specified tool
-<varname>string</varname>
-to the environment through which the method was called.
+When called as a global function,
+returns a callable tool object;
+the tool is not called at this time,
+as it lacks the context of an environment to update.
+This tool object can be passed to an
+&f-link-Environment; or &f-link-Clone; call
+as part of the <parameter>tools</parameter> keyword argument,
+or it can be called directly,
+passing a &consenv; to update as the argument.
+Either approach will also update the
+<varname>TOOLS</varname> &consvar;.
</para>
<para>
-Additional keyword arguments are passed to the tool's
-<function>generate</function>()
-method.
+Examples:
</para>
<example_commands>
-env.Tool('gcc')
-env.Tool('opengl', toolpath = ['build/tools'])
+env = Environment(tools=[Tool('msvc')])
+
+env = Environment()
+t = Tool('msvc')
+t(env) # adds 'msvc' to the TOOLS variable
+u = Tool('opengl', toolpath = ['tools'])
+u(env) # adds 'opengl' to the TOOLS variable
</example_commands>
</summary>
</scons_function>
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index bb965db..b7f6abe 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -2246,7 +2246,7 @@ class RootDir(Dir):
this directory.
"""
- __slots__ = ('_lookupDict', )
+ __slots__ = ('_lookupDict', 'abspath', 'path')
def __init__(self, drive, fs):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir')
@@ -2288,6 +2288,12 @@ class RootDir(Dir):
self._tpath = dirname
self.dirname = dirname
+ # EntryProxy interferes with this class and turns drive paths on
+ # Windows such as "C:" into "C:\C:". Avoid this problem by setting
+ # commonly-accessed attributes directly.
+ self.abspath = self._abspath
+ self.path = self._path
+
self._morph()
self.duplicate = 0
diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py
index ce948a0..62cd908 100644
--- a/src/engine/SCons/Script/Main.py
+++ b/src/engine/SCons/Script/Main.py
@@ -430,10 +430,11 @@ class QuestionTask(SCons.Taskmaster.AlwaysTask):
class TreePrinter(object):
- def __init__(self, derived=False, prune=False, status=False):
+ def __init__(self, derived=False, prune=False, status=False, sLineDraw=False):
self.derived = derived
self.prune = prune
self.status = status
+ self.sLineDraw = sLineDraw
def get_all_children(self, node):
return node.all_children()
def get_derived_children(self, node):
@@ -445,7 +446,7 @@ class TreePrinter(object):
else:
func = self.get_all_children
s = self.status and 2 or 0
- SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
+ SCons.Util.print_tree(t, func, prune=self.prune, showtags=s, lastChild=True, singleLineDraw=self.sLineDraw)
def python_version_string():
diff --git a/src/engine/SCons/Script/SConsOptions.py b/src/engine/SCons/Script/SConsOptions.py
index 8ec8ccf..0c82faf 100644
--- a/src/engine/SCons/Script/SConsOptions.py
+++ b/src/engine/SCons/Script/SConsOptions.py
@@ -834,7 +834,7 @@ def Parser(version):
help="Trace Node evaluation to FILE.",
metavar="FILE")
- tree_options = ["all", "derived", "prune", "status"]
+ tree_options = ["all", "derived", "prune", "status", "linedraw"]
def opt_tree(option, opt, value, parser, tree_options=tree_options):
from . import Main
@@ -848,6 +848,8 @@ def Parser(version):
tp.prune = True
elif o == 'status':
tp.status = True
+ elif o == 'linedraw':
+ tp.sLineDraw = True
else:
raise OptionValueError(opt_invalid('--tree', o, tree_options))
parser.values.tree_printers.append(tp)
diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py
index 8f526be..0a672fc 100644
--- a/src/engine/SCons/Script/__init__.py
+++ b/src/engine/SCons/Script/__init__.py
@@ -288,8 +288,8 @@ def set_missing_sconscript_error(flag=1):
_no_missing_sconscript = flag
return old
-#
-def Variables(files=[], args=ARGUMENTS):
+
+def Variables(files=None, args=ARGUMENTS):
return SCons.Variables.Variables(files, args)
diff --git a/src/engine/SCons/SubstTests.py b/src/engine/SCons/SubstTests.py
index 574e714..f2def65 100644
--- a/src/engine/SCons/SubstTests.py
+++ b/src/engine/SCons/SubstTests.py
@@ -577,10 +577,6 @@ class scons_subst_TestCase(SubstTestCase):
scons_subst("${NONE[2]}", env, gvars={'NONE':None})
except SCons.Errors.UserError as e:
expect = [
- # Python 2.3, 2.4
- "TypeError `unsubscriptable object' trying to evaluate `${NONE[2]}'",
- # Python 2.5, 2.6
- "TypeError `'NoneType' object is unsubscriptable' trying to evaluate `${NONE[2]}'",
# Python 2.7 and later
"TypeError `'NoneType' object is not subscriptable' trying to evaluate `${NONE[2]}'",
# Python 2.7 and later under Fedora
@@ -596,9 +592,6 @@ class scons_subst_TestCase(SubstTestCase):
scons_subst("${func(1)}", env, gvars={'func':func})
except SCons.Errors.UserError as e:
expect = [
- # Python 2.3, 2.4, 2.5
- "TypeError `func() takes exactly 3 arguments (1 given)' trying to evaluate `${func(1)}'",
-
# Python 3.5 (and 3.x?)
"TypeError `func() missing 2 required positional arguments: 'b' and 'c'' trying to evaluate `${func(1)}'"
]
diff --git a/src/engine/SCons/Tool/JavaCommon.py b/src/engine/SCons/Tool/JavaCommon.py
index 1711de1..c6c19ae 100644
--- a/src/engine/SCons/Tool/JavaCommon.py
+++ b/src/engine/SCons/Tool/JavaCommon.py
@@ -404,7 +404,7 @@ if java_parsing:
def parse_java_file(fn, version=default_java_version):
- with open(fn, 'r') as f:
+ with open(fn, 'r', encoding='utf-8') as f:
data = f.read()
return parse_java(data, version)
diff --git a/src/engine/SCons/Tool/JavaCommonTests.py b/src/engine/SCons/Tool/JavaCommonTests.py
index 9242624..b0a788e 100644
--- a/src/engine/SCons/Tool/JavaCommonTests.py
+++ b/src/engine/SCons/Tool/JavaCommonTests.py
@@ -68,6 +68,30 @@ public class Foo
assert classes == ['Foo'], classes
+ def test_file_parser(self):
+ """Test the file parser"""
+ input = """\
+package com.sub.bar;
+
+public class Foo
+{
+ public static void main(String[] args)
+ {
+ /* This tests that unicde is handled . */
+ String hello1 = new String("ఎత్తువెడల్పు");
+ }
+}
+"""
+ file_name = 'test_file_parser.java'
+ with open(file_name, 'w', encoding='UTF-8') as jf:
+ print(input, file=jf)
+
+ pkg_dir, classes = SCons.Tool.JavaCommon.parse_java_file(file_name)
+ if os.path.exists(file_name):
+ os.remove(file_name)
+ assert pkg_dir == os.path.join('com', 'sub', 'bar'), pkg_dir
+ assert classes == ['Foo'], classes
+
def test_dollar_sign(self):
"""Test class names with $ in them"""
diff --git a/src/engine/SCons/Tool/MSCommon/__init__.py b/src/engine/SCons/Tool/MSCommon/__init__.py
index c87bf71..be7720a 100644
--- a/src/engine/SCons/Tool/MSCommon/__init__.py
+++ b/src/engine/SCons/Tool/MSCommon/__init__.py
@@ -42,7 +42,8 @@ from SCons.Tool.MSCommon.sdk import mssdk_exists, \
from SCons.Tool.MSCommon.vc import msvc_exists, \
msvc_setup_env, \
msvc_setup_env_once, \
- msvc_version_to_maj_min
+ msvc_version_to_maj_min, \
+ msvc_find_vswhere
from SCons.Tool.MSCommon.vs import get_default_version, \
get_vs_by_version, \
diff --git a/src/engine/SCons/Tool/MSCommon/common.py b/src/engine/SCons/Tool/MSCommon/common.py
index 505136e..b2b5de9 100644
--- a/src/engine/SCons/Tool/MSCommon/common.py
+++ b/src/engine/SCons/Tool/MSCommon/common.py
@@ -64,8 +64,7 @@ def read_script_env_cache():
try:
with open(CONFIG_CACHE, 'r') as f:
envcache = json.load(f)
- # TODO can use more specific FileNotFoundError when py2 dropped
- except IOError:
+ except FileNotFoundError:
# don't fail if no cache file, just proceed without it
pass
return envcache
@@ -157,15 +156,14 @@ def normalize_env(env, keys, force=False):
if k in os.environ and (force or k not in normenv):
normenv[k] = os.environ[k]
- # This shouldn't be necessary, since the default environment should include system32,
- # but keep this here to be safe, since it's needed to find reg.exe which the MSVC
- # bat scripts use.
- sys32_dir = os.path.join(os.environ.get("SystemRoot",
- os.environ.get("windir", r"C:\Windows\system32")),
- "System32")
-
- if sys32_dir not in normenv['PATH']:
- normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_dir
+ # add some things to PATH to prevent problems:
+ # Shouldn't be necessary to add system32, since the default environment
+ # should include it, but keep this here to be safe (needed for reg.exe)
+ sys32_dir = os.path.join(
+ os.environ.get("SystemRoot", os.environ.get("windir", r"C:\Windows")), "System32"
+)
+ if sys32_dir not in normenv["PATH"]:
+ normenv["PATH"] = normenv["PATH"] + os.pathsep + sys32_dir
# Without Wbem in PATH, vcvarsall.bat has a "'wmic' is not recognized"
# error starting with Visual Studio 2017, although the script still
@@ -174,8 +172,14 @@ def normalize_env(env, keys, force=False):
if sys32_wbem_dir not in normenv['PATH']:
normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_wbem_dir
- debug("PATH: %s" % normenv['PATH'])
+ # Without Powershell in PATH, an internal call to a telemetry
+ # function (starting with a VS2019 update) can fail
+ # Note can also set VSCMD_SKIP_SENDTELEMETRY to avoid this.
+ sys32_ps_dir = os.path.join(sys32_dir, r'WindowsPowerShell\v1.0')
+ if sys32_ps_dir not in normenv['PATH']:
+ normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_ps_dir
+ debug("PATH: %s" % normenv['PATH'])
return normenv
@@ -186,16 +190,21 @@ def get_output(vcbat, args=None, env=None):
# Create a blank environment, for use in launching the tools
env = SCons.Environment.Environment(tools=[])
- # TODO: This is a hard-coded list of the variables that (may) need
- # to be imported from os.environ[] for v[sc]*vars*.bat file
- # execution to work. This list should really be either directly
- # controlled by vc.py, or else derived from the common_tools_var
- # settings in vs.py.
+ # TODO: Hard-coded list of the variables that (may) need to be
+ # imported from os.environ[] for the chain of development batch
+ # files to execute correctly. One call to vcvars*.bat may
+ # end up running a dozen or more scripts, changes not only with
+ # each release but with what is installed at the time. We think
+ # in modern installations most are set along the way and don't
+ # need to be picked from the env, but include these for safety's sake.
+ # Any VSCMD variables definitely are picked from the env and
+ # control execution in interesting ways.
+ # Note these really should be unified - either controlled by vs.py,
+ # or synced with the the common_tools_var # settings in vs.py.
vs_vc_vars = [
- 'COMSPEC',
- # VS100 and VS110: Still set, but modern MSVC setup scripts will
- # discard these if registry has values. However Intel compiler setup
- # script still requires these as of 2013/2014.
+ 'COMSPEC', # path to "shell"
+ 'VS160COMNTOOLS', # path to common tools for given version
+ 'VS150COMNTOOLS',
'VS140COMNTOOLS',
'VS120COMNTOOLS',
'VS110COMNTOOLS',
@@ -205,6 +214,8 @@ def get_output(vcbat, args=None, env=None):
'VS71COMNTOOLS',
'VS70COMNTOOLS',
'VS60COMNTOOLS',
+ 'VSCMD_DEBUG', # enable logging and other debug aids
+ 'VSCMD_SKIP_SENDTELEMETRY',
]
env['ENV'] = normalize_env(env['ENV'], vs_vc_vars, force=False)
@@ -235,21 +246,35 @@ def get_output(vcbat, args=None, env=None):
# debug('get_output():stdout:%s'%stdout)
# debug('get_output():stderr:%s'%stderr)
+ # Ongoing problems getting non-corrupted text led to this
+ # changing to "oem" from "mbcs" - the scripts run presumably
+ # attached to a console, so some particular rules apply.
+ # Unfortunately, "oem" not defined in Python 3.5, so get another way
+ if sys.version_info.major == 3 and sys.version_info.minor < 6:
+ from ctypes import windll
+
+ OEM = "cp{}".format(windll.kernel32.GetConsoleOutputCP())
+ else:
+ OEM = "oem"
if stderr:
# TODO: find something better to do with stderr;
# this at least prevents errors from getting swallowed.
- sys.stderr.write(stderr)
+ sys.stderr.write(stderr.decode(OEM))
if popen.wait() != 0:
- raise IOError(stderr.decode("mbcs"))
+ raise IOError(stderr.decode(OEM))
- output = stdout.decode("mbcs")
- return output
+ return stdout.decode(OEM)
-KEEPLIST = ("INCLUDE", "LIB", "LIBPATH", "PATH", 'VSCMD_ARG_app_plat',
- 'VCINSTALLDIR', # needed by clang -VS 2017 and newer
- 'VCToolsInstallDir', # needed by clang - VS 2015 and older
- )
+KEEPLIST = (
+ "INCLUDE",
+ "LIB",
+ "LIBPATH",
+ "PATH",
+ "VSCMD_ARG_app_plat",
+ "VCINSTALLDIR", # needed by clang -VS 2017 and newer
+ "VCToolsInstallDir", # needed by clang - VS 2015 and older
+)
def parse_output(output, keep=KEEPLIST):
diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py
index 82fb6b9..58be07c 100644
--- a/src/engine/SCons/Tool/MSCommon/vc.py
+++ b/src/engine/SCons/Tool/MSCommon/vc.py
@@ -233,16 +233,16 @@ _VCVER = ["14.2", "14.1", "14.1Exp", "14.0", "14.0Exp", "12.0", "12.0Exp", "11.0
# if using vswhere, a further mapping is needed
_VCVER_TO_VSWHERE_VER = {
- '14.2' : '[16.0, 17.0)',
- '14.1' : '[15.0, 16.0)',
+ '14.2': '[16.0, 17.0)',
+ '14.1': '[15.0, 16.0)',
}
_VCVER_TO_PRODUCT_DIR = {
- '14.2' : [
+ '14.2': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version
- '14.1' : [
+ '14.1': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version
- '14.1Exp' : [
+ '14.1Exp': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version
'14.0' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')],
@@ -290,6 +290,7 @@ _VCVER_TO_PRODUCT_DIR = {
]
}
+
def msvc_version_to_maj_min(msvc_version):
msvc_version_numeric = get_msvc_version_numeric(msvc_version)
@@ -303,6 +304,7 @@ def msvc_version_to_maj_min(msvc_version):
except ValueError as e:
raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric))
+
def is_host_target_supported(host_target, msvc_version):
"""Check if (host, target) pair is supported for a VC version.
@@ -321,7 +323,30 @@ def is_host_target_supported(host_target, msvc_version):
return True
-def find_vc_pdir_vswhere(msvc_version):
+VSWHERE_PATHS = [os.path.join(p,'vswhere.exe') for p in [
+ os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"),
+ os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"),
+ os.path.expandvars(r"%ChocolateyInstall%\bin"),
+]]
+
+def msvc_find_vswhere():
+ """
+ Find the location of vswhere
+ """
+ # For bug 3333: support default location of vswhere for both
+ # 64 and 32 bit windows installs.
+ # For bug 3542: also accommodate not being on C: drive.
+ # NB: this gets called from testsuite on non-Windows platforms.
+ # Whether that makes sense or not, don't break it for those.
+ vswhere_path = None
+ for pf in VSWHERE_PATHS:
+ if os.path.exists(pf):
+ vswhere_path = pf
+ break
+
+ return vswhere_path
+
+def find_vc_pdir_vswhere(msvc_version, env=None):
"""
Find the MSVC product directory using the vswhere program.
@@ -336,26 +361,15 @@ def find_vc_pdir_vswhere(msvc_version):
debug("Unknown version of MSVC: %s" % msvc_version)
raise UnsupportedVersion("Unknown version %s" % msvc_version)
- # For bug 3333: support default location of vswhere for both
- # 64 and 32 bit windows installs.
- # For bug 3542: also accommodate not being on C: drive.
- # NB: this gets called from testsuite on non-Windows platforms.
- # Whether that makes sense or not, don't break it for those.
- # TODO: requested to add a user-specified path to vswhere
- # and have this routine set the same var if it finds it.
- pfpaths = [
- os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"),
- os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"),
- os.path.expandvars(r"%ChocolateyInstall%\bin"),
- ]
- for pf in pfpaths:
- vswhere_path = os.path.join(pf, "vswhere.exe")
- if os.path.exists(vswhere_path):
- break
+ if env is None or not env.get('VSWHERE'):
+ vswhere_path = msvc_find_vswhere()
else:
- # No vswhere on system, no install info available this way
+ vswhere_path = env.subst('$VSWHERE')
+
+ if vswhere_path is None:
return None
+ debug('find_vc_pdir_vswhere(): VSWHERE = %s'%vswhere_path)
vswhere_cmd = [
vswhere_path,
"-products", "*",
@@ -363,6 +377,8 @@ def find_vc_pdir_vswhere(msvc_version):
"-property", "installationPath",
]
+ debug("find_vc_pdir_vswhere(): running: %s" % vswhere_cmd)
+
#cp = subprocess.run(vswhere_cmd, capture_output=True) # 3.7+ only
cp = subprocess.run(vswhere_cmd, stdout=PIPE, stderr=PIPE)
@@ -379,7 +395,7 @@ def find_vc_pdir_vswhere(msvc_version):
return None
-def find_vc_pdir(msvc_version):
+def find_vc_pdir(env, msvc_version):
"""Find the MSVC product directory for the given version.
Tries to look up the path using a registry key from the table
@@ -410,7 +426,7 @@ def find_vc_pdir(msvc_version):
try:
comps = None
if not key:
- comps = find_vc_pdir_vswhere(msvc_version)
+ comps = find_vc_pdir_vswhere(msvc_version, env)
if not comps:
debug('find_vc_pdir_vswhere(): no VC found for version {}'.format(repr(msvc_version)))
raise SCons.Util.WinError
@@ -448,7 +464,7 @@ def find_batch_file(env,msvc_version,host_arch,target_arch):
so use that and return an indication we don't need the argument
we would have computed to run vcvarsall.bat.
"""
- pdir = find_vc_pdir(msvc_version)
+ pdir = find_vc_pdir(env, msvc_version)
if pdir is None:
raise NoVersionFound("No version of Visual Studio found")
debug('find_batch_file() in {}'.format(pdir))
@@ -633,7 +649,7 @@ def get_installed_vcs(env=None):
for ver in _VCVER:
debug('trying to find VC %s' % ver)
try:
- VC_DIR = find_vc_pdir(ver)
+ VC_DIR = find_vc_pdir(env, ver)
if VC_DIR:
debug('found VC %s' % ver)
if _check_cl_exists_in_vc_dir(env, VC_DIR, ver):
diff --git a/src/engine/SCons/Tool/MSCommon/vcTests.py b/src/engine/SCons/Tool/MSCommon/vcTests.py
index 09991f5..eb09def 100644
--- a/src/engine/SCons/Tool/MSCommon/vcTests.py
+++ b/src/engine/SCons/Tool/MSCommon/vcTests.py
@@ -45,12 +45,42 @@ MSVCUnsupportedTargetArch = SCons.Tool.MSCommon.vc.MSVCUnsupportedTargetArch
MS_TOOLS_VERSION='1.1.1'
+class VswhereTestCase(unittest.TestCase):
+ @staticmethod
+ def _createVSWhere(path):
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ with open(path, 'w') as f:
+ f.write("Created:%s"%f)
+
+ def testDefaults(self):
+ """
+ Verify that msvc_find_vswhere() find's files in the specified paths
+ """
+ # import pdb; pdb.set_trace()
+ vswhere_dirs = [os.path.splitdrive(p)[1] for p in SCons.Tool.MSCommon.vc.VSWHERE_PATHS]
+ base_dir = test.workpath('fake_vswhere')
+ test_vswhere_dirs = [os.path.join(base_dir,d[1:]) for d in vswhere_dirs]
+
+ SCons.Tool.MSCommon.vc.VSWHERE_PATHS = test_vswhere_dirs
+ for vsw in test_vswhere_dirs:
+ VswhereTestCase._createVSWhere(vsw)
+ find_path = SCons.Tool.MSCommon.vc.msvc_find_vswhere()
+ self.assertTrue(vsw == find_path, "Didn't find vswhere in %s found in %s"%(vsw, find_path))
+ os.remove(vsw)
+
+ # def specifiedVswherePathTest(self):
+ # "Verify that msvc.generate() respects VSWHERE Specified"
+
+
+
+
+
class MSVcTestCase(unittest.TestCase):
@staticmethod
def _createDummyCl(path, add_bin=True):
"""
- Creates a dummy cl.ex in the correct directory.
+ Creates a dummy cl.exe in the correct directory.
It will create all missing parent directories as well
Args:
@@ -74,8 +104,6 @@ class MSVcTestCase(unittest.TestCase):
ct.write('created')
-
-
def runTest(self):
"""
Check that all proper HOST_PLATFORM and TARGET_PLATFORM are handled.
diff --git a/src/engine/SCons/Tool/MSCommon/vs.py b/src/engine/SCons/Tool/MSCommon/vs.py
index e13f52f..e71eb27 100644
--- a/src/engine/SCons/Tool/MSCommon/vs.py
+++ b/src/engine/SCons/Tool/MSCommon/vs.py
@@ -64,22 +64,22 @@ class VisualStudio(object):
return None
return batch_file
- def find_vs_dir_by_vc(self):
- SCons.Tool.MSCommon.vc.get_installed_vcs()
- dir = SCons.Tool.MSCommon.vc.find_vc_pdir(self.vc_version)
+ def find_vs_dir_by_vc(self, env):
+ SCons.Tool.MSCommon.vc.get_installed_vcs(env)
+ dir = SCons.Tool.MSCommon.vc.find_vc_pdir(env, self.vc_version)
if not dir:
debug('find_vs_dir_by_vc(): no installed VC %s' % self.vc_version)
return None
return os.path.abspath(os.path.join(dir, os.pardir))
- def find_vs_dir_by_reg(self):
+ def find_vs_dir_by_reg(self, env):
root = 'Software\\'
if is_win64():
root = root + 'Wow6432Node\\'
for key in self.hkeys:
if key=='use_dir':
- return self.find_vs_dir_by_vc()
+ return self.find_vs_dir_by_vc(env)
key = root + key
try:
comps = read_reg(key)
@@ -90,19 +90,19 @@ class VisualStudio(object):
return comps
return None
- def find_vs_dir(self):
+ def find_vs_dir(self, env):
""" Can use registry or location of VC to find vs dir
First try to find by registry, and if that fails find via VC dir
"""
- vs_dir=self.find_vs_dir_by_reg()
+ vs_dir=self.find_vs_dir_by_reg(env)
if not vs_dir:
- vs_dir = self.find_vs_dir_by_vc()
+ vs_dir = self.find_vs_dir_by_vc(env)
debug('find_vs_dir(): found VS in ' + str(vs_dir ))
return vs_dir
- def find_executable(self):
- vs_dir = self.get_vs_dir()
+ def find_executable(self, env):
+ vs_dir = self.get_vs_dir(env)
if not vs_dir:
debug('find_executable(): no vs_dir ({})'.format(vs_dir))
return None
@@ -121,21 +121,21 @@ class VisualStudio(object):
self._cache['batch_file'] = batch_file
return batch_file
- def get_executable(self):
+ def get_executable(self, env=None):
try:
debug('get_executable using cache:%s'%self._cache['executable'])
return self._cache['executable']
except KeyError:
- executable = self.find_executable()
+ executable = self.find_executable(env)
self._cache['executable'] = executable
debug('get_executable not in cache:%s'%executable)
return executable
- def get_vs_dir(self):
+ def get_vs_dir(self, env):
try:
return self._cache['vs_dir']
except KeyError:
- vs_dir = self.find_vs_dir()
+ vs_dir = self.find_vs_dir(env)
self._cache['vs_dir'] = vs_dir
return vs_dir
@@ -413,7 +413,7 @@ for vs in SupportedVSList:
InstalledVSList = None
InstalledVSMap = None
-def get_installed_visual_studios():
+def get_installed_visual_studios(env=None):
global InstalledVSList
global InstalledVSMap
if InstalledVSList is None:
@@ -421,7 +421,7 @@ def get_installed_visual_studios():
InstalledVSMap = {}
for vs in SupportedVSList:
debug('trying to find VS %s' % vs.version)
- if vs.get_executable():
+ if vs.get_executable(env):
debug('found VS %s' % vs.version)
InstalledVSList.append(vs)
InstalledVSMap[vs.version] = vs
@@ -472,8 +472,8 @@ def reset_installed_visual_studios():
# for variable, directory in env_tuple_list:
# env.PrependENVPath(variable, directory)
-def msvs_exists():
- return (len(get_installed_visual_studios()) > 0)
+def msvs_exists(env=None):
+ return (len(get_installed_visual_studios(env)) > 0)
def get_vs_by_version(msvs):
global InstalledVSMap
diff --git a/src/engine/SCons/Tool/applelink.xml b/src/engine/SCons/Tool/applelink.xml
index fc0cf63..977f2ce 100644
--- a/src/engine/SCons/Tool/applelink.xml
+++ b/src/engine/SCons/Tool/applelink.xml
@@ -171,7 +171,7 @@ See its __doc__ string for a discussion of the format.
</para>
<example_commands>
- env.AppendUnique(FRAMEWORKS=Split('System Cocoa SystemConfiguration'))
+env.AppendUnique(FRAMEWORKS=Split('System Cocoa SystemConfiguration'))
</example_commands>
</summary>
@@ -213,7 +213,7 @@ See its __doc__ string for a discussion of the format.
</para>
<example_commands>
- env.AppendUnique(FRAMEWORKPATH='#myframeworkdir')
+env.AppendUnique(FRAMEWORKPATH='#myframeworkdir')
</example_commands>
<para>
@@ -221,7 +221,7 @@ See its __doc__ string for a discussion of the format.
</para>
<example_commands>
- ... -Fmyframeworkdir
+... -Fmyframeworkdir
</example_commands>
<para>
diff --git a/src/engine/SCons/Tool/docbook/__init__.py b/src/engine/SCons/Tool/docbook/__init__.py
index c4fdfba..7f47e9d 100644
--- a/src/engine/SCons/Tool/docbook/__init__.py
+++ b/src/engine/SCons/Tool/docbook/__init__.py
@@ -351,11 +351,16 @@ def __build_lxml(target, source, env):
else:
result = transform(doc)
+ # we'd like the resulting output to be readably formatted,
+ # so try pretty-print. Sometimes (esp. if the output is
+ # not an xml file) we end up with a None type somewhere in
+ # the transformed tree and tostring throws TypeError,
+ # so provide a fallback.
try:
with open(str(target[0]), "wb") as of:
of.write(etree.tostring(result, pretty_print=True))
- except:
- pass
+ except TypeError:
+ result.write_output(str(target[0]))
return None
diff --git a/src/engine/SCons/Tool/gnulink.py b/src/engine/SCons/Tool/gnulink.py
index b1d5088..5372322 100644
--- a/src/engine/SCons/Tool/gnulink.py
+++ b/src/engine/SCons/Tool/gnulink.py
@@ -35,9 +35,7 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Util
import SCons.Tool
-import os
import sys
-import re
from . import link
@@ -57,21 +55,22 @@ def generate(env):
# OpenBSD doesn't usually use SONAME for libraries
use_soname = not sys.platform.startswith('openbsd')
- link._setup_versioned_lib_variables(env, tool = 'gnulink', use_soname = use_soname)
+ link._setup_versioned_lib_variables(env, tool='gnulink', use_soname=use_soname)
env['LINKCALLBACKS'] = link._versioned_lib_callbacks()
- # For backward-compatibility with older SCons versions
- env['SHLIBVERSIONFLAGS'] = SCons.Util.CLVar('-Wl,-Bsymbolic')
-
+ # # For backward-compatibility with older SCons versions
+ # env['SHLIBVERSIONFLAGS'] = SCons.Util.CLVar('')
+
+
def exists(env):
# TODO: sync with link.smart_link() to choose a linker
- linkers = { 'CXX': ['g++'], 'CC': ['gcc'] }
+ linkers = {'CXX': ['g++'], 'CC': ['gcc']}
alltools = []
for langvar, linktools in linkers.items():
- if langvar in env: # use CC over CXX when user specified CC but not CXX
+ if langvar in env: # use CC over CXX when user specified CC but not CXX
return SCons.Tool.FindTool(linktools, env)
alltools.extend(linktools)
- return SCons.Tool.FindTool(alltools, env) # find CXX or CC
+ return SCons.Tool.FindTool(alltools, env) # find CXX or CC
# Local Variables:
# tab-width:4
diff --git a/src/engine/SCons/Tool/jar.xml b/src/engine/SCons/Tool/jar.xml
index 30c51f8..151dda1 100644
--- a/src/engine/SCons/Tool/jar.xml
+++ b/src/engine/SCons/Tool/jar.xml
@@ -120,7 +120,7 @@ If this is not set, then &cv-link-JARCOM; (the command line) is displayed.
</para>
<example_commands>
-env = Environment(JARCOMSTR = "JARchiving $SOURCES into $TARGET")
+env = Environment(JARCOMSTR="JARchiving $SOURCES into $TARGET")
</example_commands>
</summary>
</cvar>
diff --git a/src/engine/SCons/Tool/javac.xml b/src/engine/SCons/Tool/javac.xml
index 893130b..b1548c7 100644
--- a/src/engine/SCons/Tool/javac.xml
+++ b/src/engine/SCons/Tool/javac.xml
@@ -174,7 +174,7 @@ env['ENV']['LANG'] = 'en_GB.UTF-8'
</para>
<example_commands>
-env = Environment(JAVACCOMSTR = "Compiling class files $TARGETS from $SOURCES")
+env = Environment(JAVACCOMSTR="Compiling class files $TARGETS from $SOURCES")
</example_commands>
</summary>
</cvar>
diff --git a/src/engine/SCons/Tool/javah.xml b/src/engine/SCons/Tool/javah.xml
index 4d436b1..5a2840f 100644
--- a/src/engine/SCons/Tool/javah.xml
+++ b/src/engine/SCons/Tool/javah.xml
@@ -77,17 +77,18 @@ Examples:
<example_commands>
# builds java_native.h
-classes = env.Java(target = 'classdir', source = 'src')
-env.JavaH(target = 'java_native.h', source = classes)
+classes = env.Java(target="classdir", source="src")
+env.JavaH(target="java_native.h", source=classes)
# builds include/package_foo.h and include/package_bar.h
-env.JavaH(target = 'include',
- source = ['package/foo.class', 'package/bar.class'])
+env.JavaH(target="include", source=["package/foo.class", "package/bar.class"])
# builds export/foo.h and export/bar.h
-env.JavaH(target = 'export',
- source = ['classes/foo.class', 'classes/bar.class'],
- JAVACLASSDIR = 'classes')
+env.JavaH(
+ target="export",
+ source=["classes/foo.class", "classes/bar.class"],
+ JAVACLASSDIR="classes",
+)
</example_commands>
</summary>
</builder>
@@ -120,7 +121,7 @@ If this is not set, then &cv-link-JAVAHCOM; (the command line) is displayed.
</para>
<example_commands>
-env = Environment(JAVAHCOMSTR = "Generating header/stub file(s) $TARGETS from $SOURCES")
+env = Environment(JAVAHCOMSTR="Generating header/stub file(s) $TARGETS from $SOURCES")
</example_commands>
</summary>
</cvar>
diff --git a/src/engine/SCons/Tool/linkloc.py b/src/engine/SCons/Tool/linkloc.py
index c73852b..ad189b2 100644
--- a/src/engine/SCons/Tool/linkloc.py
+++ b/src/engine/SCons/Tool/linkloc.py
@@ -101,7 +101,7 @@ def generate(env):
addPharLapPaths(env)
def exists(env):
- if msvs_exists():
+ if msvs_exists(env):
return env.Detect('linkloc')
else:
return 0
diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py
index e5f472a..d6a27ae 100644
--- a/src/engine/SCons/Tool/mingw.py
+++ b/src/engine/SCons/Tool/mingw.py
@@ -47,6 +47,7 @@ mingw_paths = [
r'c:\MinGW\bin',
r'C:\cygwin64\bin',
r'C:\msys64',
+ r'C:\msys64\mingw64\bin',
r'C:\cygwin\bin',
r'C:\msys',
]
diff --git a/src/engine/SCons/Tool/msvc.py b/src/engine/SCons/Tool/msvc.py
index 65c0e91..463e372 100644
--- a/src/engine/SCons/Tool/msvc.py
+++ b/src/engine/SCons/Tool/msvc.py
@@ -48,7 +48,7 @@ import SCons.Util
import SCons.Warnings
import SCons.Scanner.RC
-from .MSCommon import msvc_exists, msvc_setup_env_once, msvc_version_to_maj_min
+from .MSCommon import msvc_exists, msvc_setup_env_once, msvc_version_to_maj_min, msvc_find_vswhere
CSuffixes = ['.c', '.C']
CXXSuffixes = ['.cc', '.cpp', '.cxx', '.c++', '.C++']
@@ -214,6 +214,7 @@ ShCXXAction = SCons.Action.Action("$SHCXXCOM", "$SHCXXCOMSTR",
batch_key=msvc_batch_key,
targets='$CHANGED_TARGETS')
+
def generate(env):
"""Add Builders and construction variables for MSVC++ to an Environment."""
static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
@@ -276,6 +277,9 @@ def generate(env):
# without it for lex generation
env["LEXUNISTD"] = SCons.Util.CLVar("--nounistd")
+ # Get user specified vswhere location or locate.
+ env['VSWHERE'] = env.get('VSWHERE', msvc_find_vswhere())
+
# Set-up ms tools paths
msvc_setup_env_once(env)
diff --git a/src/engine/SCons/Tool/msvc.xml b/src/engine/SCons/Tool/msvc.xml
index 100c84c..e92fa9b 100644
--- a/src/engine/SCons/Tool/msvc.xml
+++ b/src/engine/SCons/Tool/msvc.xml
@@ -475,4 +475,54 @@ Valid values are '1' or '0'
</summary>
</cvar>
+<cvar name="VSWHERE">
+<summary>
+<para>
+Specify the location of <filename>vswhere.exe</filename>.
+</para>
+
+<para>
+ The <filename>vswhere.exe</filename> executable is distributed with Microsoft Visual Studio and Build
+ Tools since the 2017 edition, but is also available standalone.
+ It provides full information about installations of 2017 and later editions.
+ With the <option>-legacy</option> argument, <filename>vswhere.exe</filename> can detect installations of the 2010 through 2015
+ editions with limited data returned.
+If <envar>VSWHERE</envar> is set, SCons will use that location.
+</para>
+<para>
+ Otherwise SCons will look in the following locations and set <envar>VSWHERE</envar> to the path of the first <filename>vswhere.exe</filename>
+located.
+</para>
+
+<itemizedlist>
+<listitem><para><literal>%ProgramFiles(x86)%\Microsoft Visual Studio\Installer</literal></para></listitem>
+<listitem><para><literal>%ProgramFiles%\Microsoft Visual Studio\Installer</literal></para></listitem>
+<listitem><para><literal>%ChocolateyInstall%\bin</literal></para></listitem>
+</itemizedlist>
+
+<para>
+ Note that <envar>VSWHERE</envar> must be set at the same time or prior to any of &t-link-msvc;, &t-link-msvs; , and/or &t-link-mslink; &f-link-Tool; being initialized.
+
+ Either set it as follows
+<programlisting>
+env = Environment(VSWHERE='c:/my/path/to/vswhere')
+</programlisting>
+
+or if your &consenv; is created specifying an empty tools list
+(or a list of tools which omits all of default, msvs, msvc, and mslink),
+and also before &f-link-env-Tool; is called to ininitialize any of those tools:
+
+<programlisting>
+ env = Environment(tools=[])
+ env['VSWHERE'] = r'c:/my/vswhere/install/location/vswhere.exe'
+ env.Tool('msvc')
+ env.Tool('mslink')
+ env.Tool('msvs')
+ </programlisting>
+</para>
+
+</summary>
+</cvar>
+
+
</sconsdoc>
diff --git a/src/engine/SCons/Tool/msvs.xml b/src/engine/SCons/Tool/msvs.xml
index b1f79b2..f6c9a39 100644
--- a/src/engine/SCons/Tool/msvs.xml
+++ b/src/engine/SCons/Tool/msvs.xml
@@ -422,7 +422,11 @@ env.MSVSProject(target='Bar' + env['MSVSPROJECTSUFFIX'],
</variablelist>
<para>Example Usage:</para>
<example_commands>
-env.MSVSSolution(target='Bar' + env['MSVSSOLUTIONSUFFIX'], projects=['bar' + env['MSVSPROJECTSUFFIX']], variant='Release')
+env.MSVSSolution(
+ target="Bar" + env["MSVSSOLUTIONSUFFIX"],
+ projects=["bar" + env["MSVSPROJECTSUFFIX"]],
+ variant="Release",
+)
</example_commands>
</summary>
</builder> <cvar name="MSVS">
diff --git a/src/engine/SCons/Tool/msvsTests.py b/src/engine/SCons/Tool/msvsTests.py
index b3373ea..1bacc2c 100644
--- a/src/engine/SCons/Tool/msvsTests.py
+++ b/src/engine/SCons/Tool/msvsTests.py
@@ -580,7 +580,7 @@ def DummyQueryValue(key, value):
def DummyExists(path):
return 1
-def DummyVsWhere(msvc_version):
+def DummyVsWhere(msvc_version, env):
# not testing versions with vswhere, so return none
return None
diff --git a/src/engine/SCons/Tool/packaging/__init__.py b/src/engine/SCons/Tool/packaging/__init__.py
index 5795396..b6dd42e 100644
--- a/src/engine/SCons/Tool/packaging/__init__.py
+++ b/src/engine/SCons/Tool/packaging/__init__.py
@@ -27,6 +27,9 @@ SCons Packaging Tool.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import importlib
+from inspect import getfullargspec
+
import SCons.Defaults
import SCons.Environment
from SCons.Variables import *
@@ -34,8 +37,6 @@ from SCons.Errors import *
from SCons.Util import is_List, make_path_relative
from SCons.Warnings import warn, Warning
-import os
-import importlib
__all__ = [
'src_targz', 'src_tarbz2', 'src_tarxz', 'src_zip',
@@ -168,13 +169,7 @@ def Package(env, target=None, source=None, **kw):
# this exception means that a needed argument for the packager is
# missing. As our packagers get their "tags" as named function
# arguments we need to find out which one is missing.
- #TODO: getargspec deprecated in Py3. cleanup when Py2.7 dropped.
- try:
- from inspect import getfullargspec
- argspec = getfullargspec(packager.package)
- except ImportError:
- from inspect import getargspec
- argspec = getargspec(packager.package)
+ argspec = getfullargspec(packager.package)
args = argspec.args
if argspec.defaults:
# throw away arguments with default values
diff --git a/src/engine/SCons/Tool/packaging/__init__.xml b/src/engine/SCons/Tool/packaging/__init__.xml
index 66a7aa0..37e97ff 100644
--- a/src/engine/SCons/Tool/packaging/__init__.xml
+++ b/src/engine/SCons/Tool/packaging/__init__.xml
@@ -62,17 +62,21 @@ option or with the &cv-PACKAGETYPE; construction variable. Currently
the following packagers available:
</para>
-<para><literal>msi</literal> - Microsoft Installer</para>
-<para><literal>rpm</literal> - RPM Package Manger</para>
-<para><literal>ipkg</literal> - Itsy Package Management System</para>
-<para><literal>tarbz2</literal> - bzip2 compressed tar</para>
-<para><literal>targz</literal> - gzip compressed tar</para>
-<para><literal>tarxz</literal> - xz compressed tar</para>
-<para><literal>zip</literal> - zip file</para>
-<para><literal>src_tarbz2</literal> - bzip2 compressed tar source</para>
-<para><literal>src_targz</literal> - gzip compressed tar source</para>
-<para><literal>src_tarxz</literal> - xz compressed tar source</para>
-<para><literal>src_zip</literal> - zip file source</para>
+<blockquote>
+<simplelist type='vert' columns='1'>
+<member><literal>msi</literal> - Microsoft Installer</member>
+<member><literal>rpm</literal> - RPM Package Manger</member>
+<member><literal>ipkg</literal> - Itsy Package Management System</member>
+<member><literal>tarbz2</literal> - bzip2 compressed tar</member>
+<member><literal>targz</literal> - gzip compressed tar</member>
+<member><literal>tarxz</literal> - xz compressed tar</member>
+<member><literal>zip</literal> - zip file</member>
+<member><literal>src_tarbz2</literal> - bzip2 compressed tar source</member>
+<member><literal>src_targz</literal> - gzip compressed tar source</member>
+<member><literal>src_tarxz</literal> - xz compressed tar source</member>
+<member><literal>src_zip</literal> - zip file source</member>
+</simplelist>
+</blockquote>
<para>
An updated list is always available under the
@@ -82,18 +86,19 @@ on a project that has packaging activated.
</para>
<example_commands>
-env = Environment(tools=['default', 'packaging'])
-env.Install('/bin/', 'my_program')
-env.Package( NAME = 'foo',
- VERSION = '1.2.3',
- PACKAGEVERSION = 0,
- PACKAGETYPE = 'rpm',
- LICENSE = 'gpl',
- SUMMARY = 'balalalalal',
- DESCRIPTION = 'this should be really really long',
- X_RPM_GROUP = 'Application/fu',
- SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz'
- )
+env = Environment(tools=["default", "packaging"])
+env.Install("/bin/", "my_program")
+env.Package(
+ NAME="foo",
+ VERSION="1.2.3",
+ PACKAGEVERSION=0,
+ PACKAGETYPE="rpm",
+ LICENSE="gpl",
+ SUMMARY="balalalalal",
+ DESCRIPTION="this should be really really long",
+ X_RPM_GROUP="Application/fu",
+ SOURCE_URL="http://foo.org/foo-1.2.3.tar.gz",
+)
</example_commands>
</summary>
</builder>
@@ -199,17 +204,21 @@ placed if applicable. The default value is "$NAME-$VERSION".
Selects the package type to build. Currently these are available:
</para>
-<para><literal>msi</literal> - Microsoft Installer</para>
-<para><literal>rpm</literal> - RPM Package Manger</para>
-<para><literal>ipkg</literal> - Itsy Package Management System</para>
-<para><literal>tarbz2</literal> - bzip2 compressed tar</para>
-<para><literal>targz</literal> - gzip compressed tar</para>
-<para><literal>tarxz</literal> - xz compressed tar</para>
-<para><literal>zip</literal> - zip file</para>
-<para><literal>src_tarbz2</literal> - bzip2 compressed tar source</para>
-<para><literal>src_targz</literal> - gzip compressed tar source</para>
-<para><literal>src_tarxz</literal> - xz compressed tar source</para>
-<para><literal>src_zip</literal> - zip file source</para>
+<blockquote>
+<simplelist type='vert' columns='1'>
+<member><literal>msi</literal> - Microsoft Installer</member>
+<member><literal>rpm</literal> - RPM Package Manger</member>
+<member><literal>ipkg</literal> - Itsy Package Management System</member>
+<member><literal>tarbz2</literal> - bzip2 compressed tar</member>
+<member><literal>targz</literal> - gzip compressed tar</member>
+<member><literal>tarxz</literal> - xz compressed tar</member>
+<member><literal>zip</literal> - zip file</member>
+<member><literal>src_tarbz2</literal> - bzip2 compressed tar source</member>
+<member><literal>src_targz</literal> - gzip compressed tar source</member>
+<member><literal>src_tarxz</literal> - xz compressed tar source</member>
+<member><literal>src_zip</literal> - zip file source</member>
+</simplelist>
+</blockquote>
<para>
This may be overridden with the <option>package_type</option>
@@ -495,13 +504,14 @@ Added in version 3.1.
<example_commands>
env.Package(
- NAME = 'foo',
-...
- X_RPM_EXTRADEFS = [
- '%define _unpackaged_files_terminate_build 0'
- '%define _missing_doc_files_terminate_build 0'
+ NAME="foo",
+ ...
+ X_RPM_EXTRADEFS=[
+ "%define _unpackaged_files_terminate_build 0"
+ "%define _missing_doc_files_terminate_build 0"
],
-... )
+ ...
+)
</example_commands>
</summary>
diff --git a/src/engine/SCons/Tool/textfile.py b/src/engine/SCons/Tool/textfile.py
index c233658..b404304 100644
--- a/src/engine/SCons/Tool/textfile.py
+++ b/src/engine/SCons/Tool/textfile.py
@@ -171,7 +171,7 @@ _text_builder = SCons.Builder.Builder(
suffix='$TEXTFILESUFFIX',
)
-_subst_varlist = _common_varlist + ['SUBSTFILEPREFIX', 'TEXTFILESUFFIX']
+_subst_varlist = _common_varlist + ['SUBSTFILEPREFIX', 'SUBSTFILESUFFIX']
_subst_builder = SCons.Builder.Builder(
action=SCons.Action.Action(_action, _strfunc, varlist=_subst_varlist),
source_factory=SCons.Node.FS.File,
diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py
index 22df6fa..588d02f 100644
--- a/src/engine/SCons/Util.py
+++ b/src/engine/SCons/Util.py
@@ -253,8 +253,18 @@ def render_tree(root, child_func, prune=0, margin=[0], visited=None):
IDX = lambda N: N and 1 or 0
-
-def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited=None):
+# unicode line drawing chars:
+BOX_HORIZ = chr(0x2500) # '─'
+BOX_VERT = chr(0x2502) # '│'
+BOX_UP_RIGHT = chr(0x2514) # '└'
+BOX_DOWN_RIGHT = chr(0x250c) # '┌'
+BOX_DOWN_LEFT = chr(0x2510) # '┐'
+BOX_UP_LEFT = chr(0x2518) # '┘'
+BOX_VERT_RIGHT = chr(0x251c) # '├'
+BOX_HORIZ_DOWN = chr(0x252c) # '┬'
+
+
+def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited=None, lastChild=False, singleLineDraw=False):
"""
Print a tree of nodes. This is like render_tree, except it prints
lines directly instead of creating a string representation in memory,
@@ -267,6 +277,7 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited=None):
- `showtags` - print status information to the left of each node line
- `margin` - the format of the left margin to use for children of root. 1 results in a pipe, and 0 results in no pipe.
- `visited` - a dictionary of visited nodes in the current branch if not prune, or in the whole tree if prune.
+ - `singleLineDraw` - use line-drawing characters rather than ASCII.
"""
rname = str(root)
@@ -300,7 +311,8 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited=None):
[0, 1][IDX(root.has_explicit_builder())] +
[0, 2][IDX(root.has_builder())]
],
- ' S'[IDX(root.side_effect)], ' P'[IDX(root.precious)],
+ ' S'[IDX(root.side_effect)],
+ ' P'[IDX(root.precious)],
' A'[IDX(root.always_build)],
' C'[IDX(root.is_up_to_date())],
' N'[IDX(root.noclean)],
@@ -312,27 +324,51 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited=None):
tags = []
def MMM(m):
- return [" ","| "][m]
+ if singleLineDraw:
+ return [" ", BOX_VERT + " "][m]
+ else:
+ return [" ", "| "][m]
+
margins = list(map(MMM, margin[:-1]))
children = child_func(root)
+
+ cross = "+-"
+ if singleLineDraw:
+ cross = BOX_VERT_RIGHT + BOX_HORIZ # sign used to point to the leaf.
+ # check if this is the last leaf of the branch
+ if lastChild:
+ #if this if the last leaf, then terminate:
+ cross = BOX_UP_RIGHT + BOX_HORIZ # sign for the last leaf
+
+ # if this branch has children then split it
+ if children:
+ # if it's a leaf:
+ if prune and rname in visited and children:
+ cross += BOX_HORIZ
+ else:
+ cross += BOX_HORIZ_DOWN
+
if prune and rname in visited and children:
- sys.stdout.write(''.join(tags + margins + ['+-[', rname, ']']) + '\n')
+ sys.stdout.write(''.join(tags + margins + [cross,'[', rname, ']']) + '\n')
return
- sys.stdout.write(''.join(tags + margins + ['+-', rname]) + '\n')
+ sys.stdout.write(''.join(tags + margins + [cross, rname]) + '\n')
visited[rname] = 1
+ # if this item has children:
if children:
- margin.append(1)
+ margin.append(1) # Initialize margin with 1 for vertical bar.
idx = IDX(showtags)
+ _child = 0 # Initialize this for the first child.
for C in children[:-1]:
- print_tree(C, child_func, prune, idx, margin, visited)
- margin[-1] = 0
- print_tree(children[-1], child_func, prune, idx, margin, visited)
- margin.pop()
+ _child = _child + 1 # number the children
+ print_tree(C, child_func, prune, idx, margin, visited, (len(children) - _child) <= 0 ,singleLineDraw)
+ margin[-1] = 0 # margins are with space (index 0) because we arrived to the last child.
+ print_tree(children[-1], child_func, prune, idx, margin, visited, True ,singleLineDraw) # for this call child and nr of children needs to be set 0, to signal the second phase.
+ margin.pop() # destroy the last margin added
# Functions for deciding if things are like various types, mainly to
diff --git a/src/engine/SCons/Utilities/ConfigureCache.py b/src/engine/SCons/Utilities/ConfigureCache.py
new file mode 100644
index 0000000..80783a9
--- /dev/null
+++ b/src/engine/SCons/Utilities/ConfigureCache.py
@@ -0,0 +1,182 @@
+#! /usr/bin/env python
+#
+# SCons - a Software Constructor
+#
+# __COPYRIGHT__
+#
+# 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.
+
+"""Show or convert the configuration of an SCons cache directory.
+
+A cache of derived files is stored by file signature.
+The files are split into directories named by the first few
+digits of the signature. The prefix length used for directory
+names can be changed by this script.
+"""
+
+import argparse
+import glob
+import json
+import os
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+__version__ = "__VERSION__"
+
+__build__ = "__BUILD__"
+
+__buildsys__ = "__BUILDSYS__"
+
+__date__ = "__DATE__"
+
+__developer__ = "__DEVELOPER__"
+
+
+def rearrange_cache_entries(current_prefix_len, new_prefix_len):
+ """Move cache files if prefix length changed.
+
+ Move the existing cache files to new directories of the
+ appropriate name length and clean up the old directories.
+ """
+ print('Changing prefix length from', current_prefix_len,
+ 'to', new_prefix_len)
+ dirs = set()
+ old_dirs = set()
+ for file in glob.iglob(os.path.join('*', '*')):
+ name = os.path.basename(file)
+ dname = name[:current_prefix_len].upper()
+ if dname not in old_dirs:
+ print('Migrating', dname)
+ old_dirs.add(dname)
+ dname = name[:new_prefix_len].upper()
+ if dname not in dirs:
+ os.mkdir(dname)
+ dirs.add(dname)
+ os.rename(file, os.path.join(dname, name))
+
+ # Now delete the original directories
+ for dname in old_dirs:
+ os.rmdir(dname)
+
+
+# The configuration dictionary should have one entry per entry in the
+# cache config. The value of each entry should include the following:
+# implicit - (optional) This is to allow adding a new config entry and also
+# changing the behaviour of the system at the same time. This
+# indicates the value the config entry would have had if it had
+# been specified.
+# default - The value the config entry should have if it wasn't previously
+# specified
+# command-line - parameters to pass to ArgumentParser.add_argument
+# converter - (optional) Function to call if conversion is required
+# if this configuration entry changes
+config_entries = {
+ 'prefix_len': {
+ 'implicit': 1,
+ 'default': 2,
+ 'command-line': {
+ 'help': 'Length of cache file name used as subdirectory prefix',
+ 'metavar': '<number>',
+ 'type': int
+ },
+ 'converter': rearrange_cache_entries
+ }
+}
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Modify the configuration of an scons cache directory',
+ epilog='''
+ Unspecified options will not be changed unless they are not
+ set at all, in which case they are set to an appropriate default.
+ ''')
+
+ parser.add_argument('cache-dir', help='Path to scons cache directory')
+ for param in config_entries:
+ parser.add_argument('--' + param.replace('_', '-'),
+ **config_entries[param]['command-line'])
+ parser.add_argument('--version',
+ action='version',
+ version='%(prog)s 1.0')
+ parser.add_argument('--show',
+ action="store_true",
+ help="show current configuration")
+
+ # Get the command line as a dict without any of the unspecified entries.
+ args = dict([x for x in vars(parser.parse_args()).items() if x[1]])
+
+ # It seems somewhat strange to me, but positional arguments don't get the -
+ # in the name changed to _, whereas optional arguments do...
+ cache = args['cache-dir']
+ if not os.path.isdir(cache):
+ raise RuntimeError("There is no cache directory named %s" % cache)
+ os.chdir(cache)
+ del args['cache-dir']
+
+ if not os.path.exists('config'):
+ # old config dirs did not have a 'config' file. Try to update.
+ # Validate the only files in the directory are directories 0-9, a-f
+ expected = ['{:X}'.format(x) for x in range(0, 16)]
+ if not set(os.listdir('.')).issubset(expected):
+ raise RuntimeError(
+ "%s does not look like a valid version 1 cache directory" % cache)
+ config = dict()
+ else:
+ with open('config') as conf:
+ config = json.load(conf)
+
+ if args.get('show', None):
+ print("Current configuration in '%s':" % cache)
+ print(json.dumps(config, sort_keys=True,
+ indent=4, separators=(',', ': ')))
+ # in case of the show argument, emit some stats as well
+ file_count = 0
+ for _, _, files in os.walk('.'):
+ file_count += len(files)
+ if file_count: # skip config file if it exists
+ file_count -= 1
+ print("Cache contains %s files" % file_count)
+ del args['show']
+
+ # Find any keys that are not currently set but should be
+ for key in config_entries:
+ if key not in config:
+ if 'implicit' in config_entries[key]:
+ config[key] = config_entries[key]['implicit']
+ else:
+ config[key] = config_entries[key]['default']
+ if key not in args:
+ args[key] = config_entries[key]['default']
+
+ # Now go through each entry in args to see if it changes an existing config
+ # setting.
+ for key in args:
+ if args[key] != config[key]:
+ if 'converter' in config_entries[key]:
+ config_entries[key]['converter'](config[key], args[key])
+ config[key] = args[key]
+
+ # and write the updated config file
+ with open('config', 'w') as conf:
+ json.dump(config, conf)
+
+if __name__ == "__main__":
+ main() \ No newline at end of file
diff --git a/src/engine/SCons/Utilities/__init__.py b/src/engine/SCons/Utilities/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/engine/SCons/Utilities/__init__.py
diff --git a/src/engine/SCons/Utilities/sconsign.py b/src/engine/SCons/Utilities/sconsign.py
new file mode 100644
index 0000000..89084ba
--- /dev/null
+++ b/src/engine/SCons/Utilities/sconsign.py
@@ -0,0 +1,507 @@
+#! /usr/bin/env python
+#
+# SCons - a Software Constructor
+#
+# __COPYRIGHT__
+#
+# 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.
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+__version__ = "__VERSION__"
+
+__build__ = "__BUILD__"
+
+__buildsys__ = "__BUILDSYS__"
+
+__date__ = "__DATE__"
+
+__developer__ = "__DEVELOPER__"
+
+import getopt
+import os
+import sys
+from dbm import whichdb
+
+import time
+import pickle
+
+import SCons.compat
+import SCons.SConsign
+
+
+def my_whichdb(filename):
+ if filename[-7:] == ".dblite":
+ return "SCons.dblite"
+ try:
+ with open(filename + ".dblite", "rb"):
+ return "SCons.dblite"
+ except IOError:
+ pass
+ return whichdb(filename)
+
+
+def my_import(mname):
+ import imp
+
+ if '.' in mname:
+ i = mname.rfind('.')
+ parent = my_import(mname[:i])
+ fp, pathname, description = imp.find_module(mname[i+1:],
+ parent.__path__)
+ else:
+ fp, pathname, description = imp.find_module(mname)
+ return imp.load_module(mname, fp, pathname, description)
+
+
+class Flagger(object):
+ default_value = 1
+
+ def __setitem__(self, item, value):
+ self.__dict__[item] = value
+ self.default_value = 0
+
+ def __getitem__(self, item):
+ return self.__dict__.get(item, self.default_value)
+
+
+Do_Call = None
+Print_Directories = []
+Print_Entries = []
+Print_Flags = Flagger()
+Verbose = 0
+Readable = 0
+Warns = 0
+
+
+def default_mapper(entry, name):
+ """
+ Stringify an entry that doesn't have an explicit mapping.
+
+ Args:
+ entry: entry
+ name: field name
+
+ Returns: str
+
+ """
+ try:
+ val = eval("entry." + name)
+ except AttributeError:
+ val = None
+ if sys.version_info.major >= 3 and isinstance(val, bytes):
+ # This is a dirty hack for py 2/3 compatibility. csig is a bytes object
+ # in Python3 while Python2 bytes are str. Hence, we decode the csig to a
+ # Python3 string
+ val = val.decode()
+ return str(val)
+
+
+def map_action(entry, _):
+ """
+ Stringify an action entry and signature.
+
+ Args:
+ entry: action entry
+ second argument is not used
+
+ Returns: str
+
+ """
+ try:
+ bact = entry.bact
+ bactsig = entry.bactsig
+ except AttributeError:
+ return None
+ return '%s [%s]' % (bactsig, bact)
+
+
+def map_timestamp(entry, _):
+ """
+ Stringify a timestamp entry.
+
+ Args:
+ entry: timestamp entry
+ second argument is not used
+
+ Returns: str
+
+ """
+ try:
+ timestamp = entry.timestamp
+ except AttributeError:
+ timestamp = None
+ if Readable and timestamp:
+ return "'" + time.ctime(timestamp) + "'"
+ else:
+ return str(timestamp)
+
+
+def map_bkids(entry, _):
+ """
+ Stringify an implicit entry.
+
+ Args:
+ entry:
+ second argument is not used
+
+ Returns: str
+
+ """
+ try:
+ bkids = entry.bsources + entry.bdepends + entry.bimplicit
+ bkidsigs = entry.bsourcesigs + entry.bdependsigs + entry.bimplicitsigs
+ except AttributeError:
+ return None
+
+ if len(bkids) != len(bkidsigs):
+ global Warns
+ Warns += 1
+ # add warning to result rather than direct print so it will line up
+ msg = "Warning: missing information, {} ids but {} sigs"
+ result = [msg.format(len(bkids), len(bkidsigs))]
+ else:
+ result = []
+ result += [nodeinfo_string(bkid, bkidsig, " ")
+ for bkid, bkidsig in zip(bkids, bkidsigs)]
+ if not result:
+ return None
+ return "\n ".join(result)
+
+
+map_field = {
+ 'action' : map_action,
+ 'timestamp' : map_timestamp,
+ 'bkids' : map_bkids,
+}
+
+map_name = {
+ 'implicit' : 'bkids',
+}
+
+
+def field(name, entry, verbose=Verbose):
+ if not Print_Flags[name]:
+ return None
+ fieldname = map_name.get(name, name)
+ mapper = map_field.get(fieldname, default_mapper)
+ val = mapper(entry, name)
+ if verbose:
+ val = name + ": " + val
+ return val
+
+
+def nodeinfo_raw(name, ninfo, prefix=""):
+ """
+ This just formats the dictionary, which we would normally use str()
+ to do, except that we want the keys sorted for deterministic output.
+ """
+ d = ninfo.__getstate__()
+ try:
+ keys = ninfo.field_list + ['_version_id']
+ except AttributeError:
+ keys = sorted(d.keys())
+ values = []
+ for key in keys:
+ values.append('%s: %s' % (repr(key), repr(d.get(key))))
+ if '\n' in name:
+ name = repr(name)
+ return name + ': {' + ', '.join(values) + '}'
+
+
+def nodeinfo_cooked(name, ninfo, prefix=""):
+ try:
+ field_list = ninfo.field_list
+ except AttributeError:
+ field_list = []
+ if '\n' in name:
+ name = repr(name)
+ outlist = [name + ':'] + [
+ f for f in [field(x, ninfo, Verbose) for x in field_list] if f
+ ]
+ if Verbose:
+ sep = '\n ' + prefix
+ else:
+ sep = ' '
+ return sep.join(outlist)
+
+
+nodeinfo_string = nodeinfo_cooked
+
+
+def printfield(name, entry, prefix=""):
+ outlist = field("implicit", entry, 0)
+ if outlist:
+ if Verbose:
+ print(" implicit:")
+ print(" " + outlist)
+ outact = field("action", entry, 0)
+ if outact:
+ if Verbose:
+ print(" action: " + outact)
+ else:
+ print(" " + outact)
+
+
+def printentries(entries, location):
+ if Print_Entries:
+ for name in Print_Entries:
+ try:
+ entry = entries[name]
+ except KeyError:
+ err = "sconsign: no entry `%s' in `%s'\n" % (name, location)
+ sys.stderr.write(err)
+ else:
+ try:
+ ninfo = entry.ninfo
+ except AttributeError:
+ print(name + ":")
+ else:
+ print(nodeinfo_string(name, entry.ninfo))
+ printfield(name, entry.binfo)
+ else:
+ for name in sorted(entries.keys()):
+ entry = entries[name]
+ try:
+ entry.ninfo
+ except AttributeError:
+ print(name + ":")
+ else:
+ print(nodeinfo_string(name, entry.ninfo))
+ printfield(name, entry.binfo)
+
+
+class Do_SConsignDB(object):
+ def __init__(self, dbm_name, dbm):
+ self.dbm_name = dbm_name
+ self.dbm = dbm
+
+ def __call__(self, fname):
+ # The *dbm modules stick their own file suffixes on the names
+ # that are passed in. This causes us to jump through some
+ # hoops here.
+ try:
+ # Try opening the specified file name. Example:
+ # SPECIFIED OPENED BY self.dbm.open()
+ # --------- -------------------------
+ # .sconsign => .sconsign.dblite
+ # .sconsign.dblite => .sconsign.dblite.dblite
+ db = self.dbm.open(fname, "r")
+ except (IOError, OSError) as e:
+ print_e = e
+ try:
+ # That didn't work, so try opening the base name,
+ # so that if they actually passed in 'sconsign.dblite'
+ # (for example), the dbm module will put the suffix back
+ # on for us and open it anyway.
+ db = self.dbm.open(os.path.splitext(fname)[0], "r")
+ except (IOError, OSError):
+ # That didn't work either. See if the file name
+ # they specified even exists (independent of the dbm
+ # suffix-mangling).
+ try:
+ with open(fname, "rb"):
+ pass # this is a touch only, we don't use it here.
+ except (IOError, OSError) as e:
+ # Nope, that file doesn't even exist, so report that
+ # fact back.
+ print_e = e
+ sys.stderr.write("sconsign: %s\n" % print_e)
+ return
+ except KeyboardInterrupt:
+ raise
+ except pickle.UnpicklingError:
+ sys.stderr.write("sconsign: ignoring invalid `%s' file `%s'\n"
+ % (self.dbm_name, fname))
+ return
+ except Exception as e:
+ sys.stderr.write("sconsign: ignoring invalid `%s' file `%s': %s\n"
+ % (self.dbm_name, fname, e))
+ exc_type, _, _ = sys.exc_info()
+ if exc_type.__name__ == "ValueError" and sys.version_info < (3,0,0):
+ sys.stderr.write("Python 2 only supports pickle protocols 0-2.\n")
+ return
+
+ if Print_Directories:
+ for dir in Print_Directories:
+ try:
+ val = db[dir]
+ except KeyError:
+ err = "sconsign: no dir `%s' in `%s'\n" % (dir, args[0])
+ sys.stderr.write(err)
+ else:
+ self.printentries(dir, val)
+ else:
+ for dir in sorted(db.keys()):
+ self.printentries(dir, db[dir])
+
+ @staticmethod
+ def printentries(dir, val):
+ try:
+ print('=== ' + dir + ':')
+ except TypeError:
+ print('=== ' + dir.decode() + ':')
+ printentries(pickle.loads(val), dir)
+
+
+def Do_SConsignDir(name):
+ try:
+ with open(name, 'rb') as fp:
+ try:
+ sconsign = SCons.SConsign.Dir(fp)
+ except KeyboardInterrupt:
+ raise
+ except pickle.UnpicklingError:
+ err = "sconsign: ignoring invalid .sconsign file `%s'\n" % name
+ sys.stderr.write(err)
+ return
+ except Exception as e:
+ err = "sconsign: ignoring invalid .sconsign file `%s': %s\n" % (name, e)
+ sys.stderr.write(err)
+ return
+ printentries(sconsign.entries, args[0])
+ except (IOError, OSError) as e:
+ sys.stderr.write("sconsign: %s\n" % e)
+ return
+
+
+##############################################################################
+def main():
+ global Do_Call
+ global nodeinfo_string
+ global args
+ global Verbose
+ global Readable
+
+ helpstr = """\
+ Usage: sconsign [OPTIONS] [FILE ...]
+ Options:
+ -a, --act, --action Print build action information.
+ -c, --csig Print content signature information.
+ -d DIR, --dir=DIR Print only info about DIR.
+ -e ENTRY, --entry=ENTRY Print only info about ENTRY.
+ -f FORMAT, --format=FORMAT FILE is in the specified FORMAT.
+ -h, --help Print this message and exit.
+ -i, --implicit Print implicit dependency information.
+ -r, --readable Print timestamps in human-readable form.
+ --raw Print raw Python object representations.
+ -s, --size Print file sizes.
+ -t, --timestamp Print timestamp information.
+ -v, --verbose Verbose, describe each field.
+ """
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "acd:e:f:hirstv",
+ ['act', 'action',
+ 'csig', 'dir=', 'entry=',
+ 'format=', 'help', 'implicit',
+ 'raw', 'readable',
+ 'size', 'timestamp', 'verbose'])
+ except getopt.GetoptError as err:
+ sys.stderr.write(str(err) + '\n')
+ print(helpstr)
+ sys.exit(2)
+
+ for o, a in opts:
+ if o in ('-a', '--act', '--action'):
+ Print_Flags['action'] = 1
+ elif o in ('-c', '--csig'):
+ Print_Flags['csig'] = 1
+ elif o in ('-d', '--dir'):
+ Print_Directories.append(a)
+ elif o in ('-e', '--entry'):
+ Print_Entries.append(a)
+ elif o in ('-f', '--format'):
+ # Try to map the given DB format to a known module
+ # name, that we can then try to import...
+ Module_Map = {'dblite': 'SCons.dblite', 'sconsign': None}
+ dbm_name = Module_Map.get(a, a)
+ if dbm_name:
+ try:
+ if dbm_name != "SCons.dblite":
+ dbm = my_import(dbm_name)
+ else:
+ import SCons.dblite
+
+ dbm = SCons.dblite
+ # Ensure that we don't ignore corrupt DB files,
+ # this was handled by calling my_import('SCons.dblite')
+ # again in earlier versions...
+ SCons.dblite.ignore_corrupt_dbfiles = 0
+ except ImportError:
+ sys.stderr.write("sconsign: illegal file format `%s'\n" % a)
+ print(helpstr)
+ sys.exit(2)
+ Do_Call = Do_SConsignDB(a, dbm)
+ else:
+ Do_Call = Do_SConsignDir
+ elif o in ('-h', '--help'):
+ print(helpstr)
+ sys.exit(0)
+ elif o in ('-i', '--implicit'):
+ Print_Flags['implicit'] = 1
+ elif o in ('--raw',):
+ nodeinfo_string = nodeinfo_raw
+ elif o in ('-r', '--readable'):
+ Readable = 1
+ elif o in ('-s', '--size'):
+ Print_Flags['size'] = 1
+ elif o in ('-t', '--timestamp'):
+ Print_Flags['timestamp'] = 1
+ elif o in ('-v', '--verbose'):
+ Verbose = 1
+
+ if Do_Call:
+ for a in args:
+ Do_Call(a)
+ else:
+ if not args:
+ args = [".sconsign.dblite"]
+ for a in args:
+ dbm_name = my_whichdb(a)
+ if dbm_name:
+ Map_Module = {'SCons.dblite': 'dblite'}
+ if dbm_name != "SCons.dblite":
+ dbm = my_import(dbm_name)
+ else:
+ import SCons.dblite
+
+ dbm = SCons.dblite
+ # Ensure that we don't ignore corrupt DB files,
+ # this was handled by calling my_import('SCons.dblite')
+ # again in earlier versions...
+ SCons.dblite.ignore_corrupt_dbfiles = 0
+ Do_SConsignDB(Map_Module.get(dbm_name, dbm_name), dbm)(a)
+ else:
+ Do_SConsignDir(a)
+
+ if Warns:
+ print("NOTE: there were %d warnings, please check output" % Warns)
+
+
+if __name__ == "__main__":
+ main()
+ sys.exit(0)
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/src/engine/SCons/Variables/PathVariable.py b/src/engine/SCons/Variables/PathVariable.py
index 0ffcbc5..eb9b299 100644
--- a/src/engine/SCons/Variables/PathVariable.py
+++ b/src/engine/SCons/Variables/PathVariable.py
@@ -78,11 +78,13 @@ import SCons.Errors
class _PathVariableClass(object):
- def PathAccept(self, key, val, env):
+ @staticmethod
+ def PathAccept(key, val, env):
"""Accepts any path, no checking done."""
pass
- def PathIsDir(self, key, val, env):
+ @staticmethod
+ def PathIsDir(key, val, env):
"""Validator to check if Path is a directory."""
if not os.path.isdir(val):
if os.path.isfile(val):
@@ -91,7 +93,8 @@ class _PathVariableClass(object):
m = 'Directory path for option %s does not exist: %s'
raise SCons.Errors.UserError(m % (key, val))
- def PathIsDirCreate(self, key, val, env):
+ @staticmethod
+ def PathIsDirCreate(key, val, env):
"""Validator to check if Path is a directory,
creating it if it does not exist."""
if os.path.isfile(val):
@@ -100,7 +103,8 @@ class _PathVariableClass(object):
if not os.path.isdir(val):
os.makedirs(val)
- def PathIsFile(self, key, val, env):
+ @staticmethod
+ def PathIsFile(key, val, env):
"""Validator to check if Path is a file"""
if not os.path.isfile(val):
if os.path.isdir(val):
@@ -109,7 +113,8 @@ class _PathVariableClass(object):
m = 'File path for option %s does not exist: %s'
raise SCons.Errors.UserError(m % (key, val))
- def PathExists(self, key, val, env):
+ @staticmethod
+ def PathExists(key, val, env):
"""Validator to check if Path exists"""
if not os.path.exists(val):
m = 'Path for option %s does not exist: %s'
diff --git a/src/engine/SCons/Variables/__init__.py b/src/engine/SCons/Variables/__init__.py
index 4ff11d0..7bd4cd9 100644
--- a/src/engine/SCons/Variables/__init__.py
+++ b/src/engine/SCons/Variables/__init__.py
@@ -45,21 +45,20 @@ from .PathVariable import PathVariable # okay
class Variables(object):
- instance=None
-
"""
Holds all the options, updates the environment with the variables,
and renders the help text.
"""
+ instance=None
+
def __init__(self, files=None, args=None, is_global=1):
"""
files - [optional] List of option configuration files to load
(backward compatibility) If a single string is passed it is
- automatically placed in a file list
+ automatically placed in a file list
+ args - dictionary to override values set from files.
"""
- # initialize arguments
- if files is None:
- files = []
+
if args is None:
args = {}
self.options = []
@@ -74,7 +73,7 @@ class Variables(object):
# create the singleton instance
if is_global:
- self=Variables.instance
+ self = Variables.instance
if not Variables.instance:
Variables.instance=self