Scons: Added newer inline copy with Scons that supports Visual Studio 2026

* Migrated and expanded our patches to avoid unused tools, deleted
  more than before.

* Remove annotations which is what we do now for inline copies, since
  they only waste space.
This commit is contained in:
Kay Hayen
2025-11-24 13:29:32 +00:00
parent 0691f4d716
commit a58610a85e
145 changed files with 52119 additions and 3 deletions

View File

@@ -44,10 +44,14 @@ def initScons():
# That's a noop, pylint: disable=unused-argument
pass
# Avoid scons writing the scons database at all, spell-checker: ignore dblite
# Avoid scons writing the scons database at all, we don't care for it,
# spell-checker: ignore dblite
import SCons.dblite # pylint: disable=I0021,import-error
SCons.dblite.dblite.sync = no_sync
try:
SCons.dblite.dblite.sync = no_sync
except AttributeError:
SCons.dblite._Dblite.sync = no_sync # pylint: disable=protected-access
# We use threads during build, so keep locks if necessary for progress bar
# updates.

View File

@@ -19,6 +19,8 @@ if __name__ == "__main__":
if sys.version_info < (2, 7):
# Non-Windows, Python 2.6, mostly older RHEL
scons_version = "scons-2.3.2"
elif os.name == "nt" and sys.version_info >= (3, 7):
scons_version = "scons-4.10.1"
elif os.name == "nt" and sys.version_info >= (3, 5):
# Windows can use latest, supported MSVC 2022 this way
scons_version = "scons-4.3.0"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,918 @@
# 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.
"""
SCons.Builder
Builder object subsystem.
A Builder object is a callable that encapsulates information about how
to execute actions to create a target Node (file) from source Nodes
(files), and how to create those dependencies for tracking.
The main entry point here is the Builder() factory method. This provides
a procedural interface that creates the right underlying Builder object
based on the keyword arguments supplied and the types of the arguments.
The goal is for this external interface to be simple enough that the
vast majority of users can create new Builders as necessary to support
building new types of files in their configurations, without having to
dive any deeper into this subsystem.
The base class here is BuilderBase. This is a concrete base class which
does, in fact, represent the Builder objects that we (or users) create.
There is also a proxy that looks like a Builder:
CompositeBuilder
This proxies for a Builder with an action that is actually a
dictionary that knows how to map file suffixes to a specific
action. This is so that we can invoke different actions
(compilers, compile options) for different flavors of source
files.
Builders and their proxies have the following public interface methods
used by other modules:
- __call__()
THE public interface. Calling a Builder object (with the
use of internal helper methods) sets up the target and source
dependencies, appropriate mapping to a specific action, and the
environment manipulation necessary for overridden construction
variable. This also takes care of warning about possible mistakes
in keyword arguments.
- add_emitter()
Adds an emitter for a specific file suffix, used by some Tool
modules to specify that (for example) a yacc invocation on a .y
can create a .h *and* a .c file.
- add_action()
Adds an action for a specific file suffix, heavily used by
Tool modules to add their specific action(s) for turning
a source file into an object file to the global static
and shared object file Builders.
There are the following methods for internal use within this module:
- _execute()
The internal method that handles the heavily lifting when a
Builder is called. This is used so that the __call__() methods
can set up warning about possible mistakes in keyword-argument
overrides, and *then* execute all of the steps necessary so that
the warnings only occur once.
- get_name()
Returns the Builder's name within a specific Environment,
primarily used to try to return helpful information in error
messages.
- adjust_suffix()
- get_prefix()
- get_suffix()
- get_src_suffix()
- set_src_suffix()
Miscellaneous stuff for handling the prefix and suffix
manipulation we use in turning source file names into target
file names.
"""
from __future__ import annotations
import os
from collections import UserDict, UserList
from contextlib import suppress
import SCons.Action
import SCons.Debug
import SCons.Executor
import SCons.Memoize
import SCons.Util
import SCons.Warnings
from SCons.Debug import logInstanceCreation
from SCons.Errors import InternalError, UserError
from SCons.Executor import Executor
from SCons.Node import Node
class _Null:
pass
_null = _Null
def match_splitext(path, suffixes = []):
if suffixes:
matchsuf = [S for S in suffixes if path[-len(S):] == S]
if matchsuf:
suf = max([(len(_f),_f) for _f in matchsuf])[1]
return [path[:-len(suf)], path[-len(suf):]]
return SCons.Util.splitext(path)
class DictCmdGenerator(SCons.Util.Selector):
"""This is a callable class that can be used as a
command generator function. It holds on to a dictionary
mapping file suffixes to Actions. It uses that dictionary
to return the proper action based on the file suffix of
the source file."""
def __init__(self, mapping=None, source_ext_match=True):
super().__init__(mapping)
self.source_ext_match = source_ext_match
def src_suffixes(self):
return list(self.keys())
def add_action(self, suffix, action):
"""Add a suffix-action pair to the mapping.
"""
self[suffix] = action
def __call__(self, target, source, env, for_signature):
if not source:
return []
if self.source_ext_match:
suffixes = self.src_suffixes()
ext = None
for src in map(str, source):
my_ext = match_splitext(src, suffixes)[1]
if ext and my_ext != ext:
raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s"
% (repr(list(map(str, target))), src, ext, my_ext))
ext = my_ext
else:
ext = match_splitext(str(source[0]), self.src_suffixes())[1]
if not ext:
#return ext
raise UserError("While building `%s': "
"Cannot deduce file extension from source files: %s"
% (repr(list(map(str, target))), repr(list(map(str, source)))))
try:
ret = SCons.Util.Selector.__call__(self, env, source, ext)
except KeyError as e:
raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e.args[0], e.args[1], e.args[2]))
if ret is None:
raise UserError("While building `%s' from `%s': Don't know how to build from a source file with suffix `%s'. Expected a suffix in this list: %s." % \
(repr(list(map(str, target))), repr(list(map(str, source))), ext, repr(list(self.keys()))))
return ret
class CallableSelector(SCons.Util.Selector):
"""A callable dictionary that will, in turn, call the value it
finds if it can."""
def __call__(self, env, source):
value = SCons.Util.Selector.__call__(self, env, source)
if callable(value):
value = value(env, source)
return value
class DictEmitter(SCons.Util.Selector):
"""A callable dictionary that maps file suffixes to emitters.
When called, it finds the right emitter in its dictionary for the
suffix of the first source file, and calls that emitter to get the
right lists of targets and sources to return. If there's no emitter
for the suffix in its dictionary, the original target and source are
returned.
"""
def __call__(self, target, source, env):
emitter = SCons.Util.Selector.__call__(self, env, source)
if emitter:
target, source = emitter(target, source, env)
return (target, source)
class ListEmitter(UserList):
"""A callable list of emitters that calls each in sequence,
returning the result.
"""
def __call__(self, target, source, env):
for e in self.data:
target, source = e(target, source, env)
return (target, source)
# These are a common errors when calling a Builder;
# they are similar to the 'target' and 'source' keyword args to builders,
# so we issue warnings when we see them. The warnings can, of course,
# be disabled.
misleading_keywords = {
'targets' : 'target',
'sources' : 'source',
}
class OverrideWarner(UserDict):
"""A class for warning about keyword arguments that we use as
overrides in a Builder call.
This class exists to handle the fact that a single Builder call
can actually invoke multiple builders. This class only emits the
warnings once, no matter how many Builders are invoked.
"""
def __init__(self, mapping):
super().__init__(mapping)
if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.OverrideWarner')
self.already_warned = None
def warn(self):
if self.already_warned:
return
for k in self.keys():
if k in misleading_keywords:
alt = misleading_keywords[k]
msg = "Did you mean to use `%s' instead of `%s'?" % (alt, k)
SCons.Warnings.warn(SCons.Warnings.MisleadingKeywordsWarning, msg)
self.already_warned = 1
def Builder(**kw):
"""A factory for builder objects."""
composite = None
if 'generator' in kw:
if 'action' in kw:
raise UserError("You must not specify both an action and a generator.")
kw['action'] = SCons.Action.CommandGeneratorAction(kw['generator'], {})
del kw['generator']
elif 'action' in kw:
source_ext_match = kw.get('source_ext_match', True)
if 'source_ext_match' in kw:
del kw['source_ext_match']
if SCons.Util.is_Dict(kw['action']):
composite = DictCmdGenerator(kw['action'], source_ext_match)
kw['action'] = SCons.Action.CommandGeneratorAction(composite, {})
kw['src_suffix'] = composite.src_suffixes()
else:
kw['action'] = SCons.Action.Action(kw['action'])
if 'emitter' in kw:
emitter = kw['emitter']
if SCons.Util.is_String(emitter):
# This allows users to pass in an Environment
# variable reference (like "$FOO") as an emitter.
# We will look in that Environment variable for
# a callable to use as the actual emitter.
var = SCons.Util.get_environment_var(emitter)
if not var:
raise UserError("Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter)
kw['emitter'] = EmitterProxy(var)
elif SCons.Util.is_Dict(emitter):
kw['emitter'] = DictEmitter(emitter)
elif SCons.Util.is_List(emitter):
kw['emitter'] = ListEmitter(emitter)
result = BuilderBase(**kw)
if composite is not None:
result = CompositeBuilder(result, composite)
return result
def _node_errors(builder, env, tlist, slist):
"""Validate that the lists of target and source nodes are
legal for this builder and environment. Raise errors or
issue warnings as appropriate.
"""
# First, figure out if there are any errors in the way the targets
# were specified.
for t in tlist:
if t.side_effect:
raise UserError("Multiple ways to build the same target were specified for: %s" % t)
if t.has_explicit_builder():
# Check for errors when the environments are different
# No error if environments are the same Environment instance
if (t.env is not None and t.env is not env and
# Check OverrideEnvironment case - no error if wrapped Environments
# are the same instance, and overrides lists match
not (getattr(t.env, '__subject', 0) is getattr(env, '__subject', 1) and
getattr(t.env, 'overrides', 0) == getattr(env, 'overrides', 1) and
not builder.multi)):
action = t.builder.action
t_contents = t.builder.action.get_contents(tlist, slist, t.env)
contents = builder.action.get_contents(tlist, slist, env)
if t_contents == contents:
msg = "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s" % (t, action.genstring(tlist, slist, t.env))
SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning, msg)
else:
try:
msg = "Two environments with different actions were specified for the same target: %s\n(action 1: %s)\n(action 2: %s)" % (t,t_contents.decode('utf-8'),contents.decode('utf-8'))
except UnicodeDecodeError:
msg = "Two environments with different actions were specified for the same target: %s"%t
raise UserError(msg)
if builder.multi:
if t.builder != builder:
msg = "Two different builders (%s and %s) were specified for the same target: %s" % (t.builder.get_name(env), builder.get_name(env), t)
raise UserError(msg)
# TODO(batch): list constructed each time!
if t.get_executor().get_all_targets() != tlist:
msg = "Two different target lists have a target in common: %s (from %s and from %s)" % (t, list(map(str, t.get_executor().get_all_targets())), list(map(str, tlist)))
raise UserError(msg)
elif t.sources != slist:
msg = "Multiple ways to build the same target were specified for: %s (from %s and from %s)" % (t, list(map(str, t.sources)), list(map(str, slist)))
raise UserError(msg)
if builder.single_source:
if len(slist) > 1:
raise UserError("More than one source given for single-source builder: targets=%s sources=%s" % (list(map(str,tlist)), list(map(str,slist))))
class EmitterProxy:
"""This is a callable class that can act as a
Builder emitter. It holds on to a string that
is a key into an Environment dictionary, and will
look there at actual build time to see if it holds
a callable. If so, we will call that as the actual
emitter."""
def __init__(self, var):
self.var = SCons.Util.to_String(var)
def __call__(self, target, source, env):
emitter = self.var
# Recursively substitute the variable.
# We can't use env.subst() because it deals only
# in strings. Maybe we should change that?
while SCons.Util.is_String(emitter) and emitter in env:
emitter = env[emitter]
if callable(emitter):
target, source = emitter(target, source, env)
elif SCons.Util.is_List(emitter):
for e in emitter:
target, source = e(target, source, env)
return (target, source)
def __eq__(self, other):
return self.var == other.var
def __lt__(self, other):
return self.var < other.var
def __le__(self, other):
return self.var <= other.var
def __gt__(self, other):
return self.var > other.var
def __ge__(self, other):
return self.var >= other.var
class BuilderBase:
"""Base class for Builders, objects that create output
nodes (files) from input nodes (files).
"""
def __init__(self, action = None,
prefix = '',
suffix = '',
src_suffix = '',
target_factory = None,
source_factory = None,
target_scanner = None,
source_scanner = None,
emitter = None,
multi = False,
env = None,
single_source = False,
name = None,
chdir = _null,
is_explicit = True,
src_builder = None,
ensure_suffix = False,
**overrides):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.BuilderBase')
self._memo = {}
self.action = action
self.multi = multi
if SCons.Util.is_Dict(prefix):
prefix = CallableSelector(prefix)
self.prefix = prefix
if SCons.Util.is_Dict(suffix):
suffix = CallableSelector(suffix)
self.env = env
self.single_source = single_source
if 'overrides' in overrides:
msg = "The \"overrides\" keyword to Builder() creation has been removed;\n" +\
"\tspecify the items as keyword arguments to the Builder() call instead."
raise TypeError(msg)
if 'scanner' in overrides:
msg = "The \"scanner\" keyword to Builder() creation has been removed;\n" +\
"\tuse: source_scanner or target_scanner as appropriate."
raise TypeError(msg)
self.overrides = overrides
self.set_suffix(suffix)
self.set_src_suffix(src_suffix)
self.ensure_suffix = ensure_suffix
self.target_factory = target_factory
self.source_factory = source_factory
self.target_scanner = target_scanner
self.source_scanner = source_scanner
self.emitter = emitter
# Optional Builder name should only be used for Builders
# that don't get attached to construction environments.
if name:
self.name = name
self.executor_kw = {}
if chdir is not _null:
self.executor_kw['chdir'] = chdir
self.is_explicit = is_explicit
if src_builder is None:
src_builder = []
elif not SCons.Util.is_List(src_builder):
src_builder = [ src_builder ]
self.src_builder = src_builder
def __bool__(self):
raise InternalError("Do not test for the Node.builder attribute directly; use Node.has_builder() instead")
def get_name(self, env):
"""Attempts to get the name of the Builder.
Look at the BUILDERS variable of env, expecting it to be a
dictionary containing this Builder, and return the key of the
dictionary. If there's no key, then return a directly-configured
name (if there is one) or the name of the class (by default)."""
try:
index = list(env['BUILDERS'].values()).index(self)
return list(env['BUILDERS'].keys())[index]
except (AttributeError, KeyError, TypeError, ValueError):
try:
return self.name
except AttributeError:
return str(self.__class__)
def __eq__(self, other):
return self.__dict__ == other.__dict__
def splitext(self, path, env=None):
if not env:
env = self.env
if env:
suffixes = self.src_suffixes(env)
else:
suffixes = []
return match_splitext(path, suffixes)
def _adjustixes(self, files, pre, suf, ensure_suffix=False):
if not files:
return []
result = []
if not SCons.Util.is_List(files):
files = [files]
for f in files:
# fspath() is to catch PathLike paths. We avoid the simpler
# str(f) so as not to "lose" files that are already Nodes:
# TypeError: expected str, bytes or os.PathLike object, not File
if not isinstance(f, Node):
with suppress(TypeError):
f = os.fspath(f)
if SCons.Util.is_String(f):
f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix)
result.append(f)
return result
def _create_nodes(self, env, target = None, source = None):
"""Create and return lists of target and source nodes.
"""
src_suf = self.get_src_suffix(env)
target_factory = env.get_factory(self.target_factory)
source_factory = env.get_factory(self.source_factory)
source = self._adjustixes(source, None, src_suf)
slist = env.arg2nodes(source, source_factory)
pre = self.get_prefix(env, slist)
suf = self.get_suffix(env, slist)
if target is None:
try:
t_from_s = slist[0].target_from_source
except AttributeError:
raise UserError("Do not know how to create a target from source `%s'" % slist[0])
except IndexError:
tlist = []
else:
splitext = lambda S: self.splitext(S,env)
tlist = [ t_from_s(pre, suf, splitext) ]
else:
# orig_target = target
target = self._adjustixes(target, pre, suf, self.ensure_suffix)
tlist = env.arg2nodes(target, target_factory, target=target, source=source)
if self.emitter:
# The emitter is going to do str(node), but because we're
# being called *from* a builder invocation, the new targets
# don't yet have a builder set on them and will look like
# source files. Fool the emitter's str() calls by setting
# up a temporary builder on the new targets.
new_targets = []
for t in tlist:
if not t.is_derived():
t.builder_set(self)
new_targets.append(t)
orig_tlist = tlist[:]
orig_slist = slist[:]
target, source = self.emitter(target=tlist, source=slist, env=env)
# Now delete the temporary builders that we attached to any
# new targets, so that _node_errors() doesn't do weird stuff
# to them because it thinks they already have builders.
for t in new_targets:
if t.builder is self:
# Only delete the temporary builder if the emitter
# didn't change it on us.
t.builder_set(None)
# Have to call arg2nodes yet again, since it is legal for
# emitters to spit out strings as well as Node instances.
tlist = env.arg2nodes(target, target_factory,
target=orig_tlist, source=orig_slist)
slist = env.arg2nodes(source, source_factory,
target=orig_tlist, source=orig_slist)
return tlist, slist
def _execute(self, env, target, source, overwarn={}, executor_kw={}):
# We now assume that target and source are lists or None.
if self.src_builder:
source = self.src_builder_sources(env, source, overwarn)
if self.single_source and len(source) > 1 and target is None:
result = []
if target is None: target = [None]*len(source)
for tgt, src in zip(target, source):
if tgt is not None:
tgt = [tgt]
if src is not None:
src = [src]
result.extend(self._execute(env, tgt, src, overwarn))
return SCons.Node.NodeList(result)
overwarn.warn()
tlist, slist = self._create_nodes(env, target, source)
# If there is more than one target ensure that if we need to reset
# the implicit list to new scan of dependency all targets implicit lists
# are cleared. (SCons GH Issue #2811 and MongoDB SERVER-33111)
if len(tlist) > 1:
for t in tlist:
t.target_peers = tlist
# Check for errors with the specified target/source lists.
_node_errors(self, env, tlist, slist)
# The targets are fine, so find or make the appropriate Executor to
# build this particular list of targets from this particular list of
# sources.
executor: Executor | None = None
key = None
if self.multi:
try:
executor = tlist[0].get_executor(create = 0)
except (AttributeError, IndexError):
pass
else:
executor.add_sources(slist)
if executor is None:
if not self.action:
fmt = "Builder %s must have an action to build %s."
raise UserError(fmt % (self.get_name(env or self.env),
list(map(str,tlist))))
key = self.action.batch_key(env or self.env, tlist, slist)
if key:
try:
executor = SCons.Executor.GetBatchExecutor(key)
except KeyError:
pass
else:
executor.add_batch(tlist, slist)
if executor is None:
executor = SCons.Executor.Executor(self.action, env, [],
tlist, slist, executor_kw)
if key:
SCons.Executor.AddBatchExecutor(key, executor)
# Now set up the relevant information in the target Nodes themselves.
for t in tlist:
t.cwd = env.fs.getcwd()
t.builder_set(self)
t.env_set(env)
t.add_source(slist)
t.set_executor(executor)
t.set_explicit(self.is_explicit)
if env.get("SCONF_NODE"):
for node in tlist:
node.attributes.conftest_node = 1
return SCons.Node.NodeList(tlist)
def __call__(self, env, target=None, source=None, chdir=_null, **kw):
# We now assume that target and source are lists or None.
# The caller (typically Environment.BuilderWrapper) is
# responsible for converting any scalar values to lists.
if chdir is _null:
ekw = self.executor_kw
else:
ekw = self.executor_kw.copy()
ekw['chdir'] = chdir
if 'chdir' in ekw and SCons.Util.is_String(ekw['chdir']):
ekw['chdir'] = env.subst(ekw['chdir'])
if kw:
if 'srcdir' in kw:
def prependDirIfRelative(f, srcdir=kw['srcdir']):
import os.path
if SCons.Util.is_String(f) and not os.path.isabs(f):
f = os.path.join(srcdir, f)
return f
if not SCons.Util.is_List(source):
source = [source]
source = list(map(prependDirIfRelative, source))
del kw['srcdir']
if self.overrides:
env_kw = self.overrides.copy()
env_kw.update(kw)
else:
env_kw = kw
else:
env_kw = self.overrides
# TODO if env_kw: then the following line. there's no purpose in calling if no overrides.
env = env.Override(env_kw)
return self._execute(env, target, source, OverrideWarner(kw), ekw)
def adjust_suffix(self, suff):
if suff and not suff[0] in [ '.', '_', '$' ]:
return '.' + suff
return suff
def get_prefix(self, env, sources=[]):
prefix = self.prefix
if callable(prefix):
prefix = prefix(env, sources)
return env.subst(prefix)
def set_suffix(self, suffix):
if not callable(suffix):
suffix = self.adjust_suffix(suffix)
self.suffix = suffix
def get_suffix(self, env, sources=[]):
suffix = self.suffix
if callable(suffix):
suffix = suffix(env, sources)
return env.subst(suffix)
def set_src_suffix(self, src_suffix):
if not src_suffix:
src_suffix = []
elif not SCons.Util.is_List(src_suffix):
src_suffix = [ src_suffix ]
self.src_suffix = [suf if callable(suf) else self.adjust_suffix(suf) for suf in src_suffix]
def get_src_suffix(self, env):
"""Get the first src_suffix in the list of src_suffixes."""
ret = self.src_suffixes(env)
if not ret:
return ''
return ret[0]
def add_emitter(self, suffix, emitter):
"""Add a suffix-emitter mapping to this Builder.
This assumes that emitter has been initialized with an
appropriate dictionary type, and will throw a TypeError if
not, so the caller is responsible for knowing that this is an
appropriate method to call for the Builder in question.
"""
self.emitter[suffix] = emitter
def add_src_builder(self, builder):
"""
Add a new Builder to the list of src_builders.
This requires wiping out cached values so that the computed
lists of source suffixes get re-calculated.
"""
self._memo = {}
self.src_builder.append(builder)
def _get_sdict(self, env):
"""
Returns a dictionary mapping all of the source suffixes of all
src_builders of this Builder to the underlying Builder that
should be called first.
This dictionary is used for each target specified, so we save a
lot of extra computation by memoizing it for each construction
environment.
Note that this is re-computed each time, not cached, because there
might be changes to one of our source Builders (or one of their
source Builders, and so on, and so on...) that we can't "see."
The underlying methods we call cache their computed values,
though, so we hope repeatedly aggregating them into a dictionary
like this won't be too big a hit. We may need to look for a
better way to do this if performance data show this has turned
into a significant bottleneck.
"""
sdict = {}
for bld in self.get_src_builders(env):
for suf in bld.src_suffixes(env):
sdict[suf] = bld
return sdict
def src_builder_sources(self, env, source, overwarn={}):
sdict = self._get_sdict(env)
src_suffixes = self.src_suffixes(env)
lengths = list(set(map(len, src_suffixes)))
def match_src_suffix(name, src_suffixes=src_suffixes, lengths=lengths):
node_suffixes = [name[-l:] for l in lengths]
for suf in src_suffixes:
if suf in node_suffixes:
return suf
return None
result = []
for s in SCons.Util.flatten(source):
if SCons.Util.is_String(s):
match_suffix = match_src_suffix(env.subst(s))
if not match_suffix and '.' not in s:
src_suf = self.get_src_suffix(env)
s = self._adjustixes(s, None, src_suf)[0]
else:
match_suffix = match_src_suffix(s.name)
if match_suffix:
try:
bld = sdict[match_suffix]
except KeyError:
result.append(s)
else:
tlist = bld._execute(env, None, [s], overwarn)
# If the subsidiary Builder returned more than one
# target, then filter out any sources that this
# Builder isn't capable of building.
if len(tlist) > 1:
tlist = [t for t in tlist if match_src_suffix(t.name)]
result.extend(tlist)
else:
result.append(s)
source_factory = env.get_factory(self.source_factory)
return env.arg2nodes(result, source_factory)
def _get_src_builders_key(self, env):
return id(env)
@SCons.Memoize.CountDictCall(_get_src_builders_key)
def get_src_builders(self, env):
"""
Returns the list of source Builders for this Builder.
This exists mainly to look up Builders referenced as
strings in the 'BUILDER' variable of the construction
environment and cache the result.
"""
memo_key = id(env)
try:
memo_dict = self._memo['get_src_builders']
except KeyError:
memo_dict = {}
self._memo['get_src_builders'] = memo_dict
else:
try:
return memo_dict[memo_key]
except KeyError:
pass
builders = []
for bld in self.src_builder:
if SCons.Util.is_String(bld):
try:
bld = env['BUILDERS'][bld]
except KeyError:
continue
builders.append(bld)
memo_dict[memo_key] = builders
return builders
def _subst_src_suffixes_key(self, env):
return id(env)
@SCons.Memoize.CountDictCall(_subst_src_suffixes_key)
def subst_src_suffixes(self, env):
"""
The suffix list may contain construction variable expansions,
so we have to evaluate the individual strings. To avoid doing
this over and over, we memoize the results for each construction
environment.
"""
memo_key = id(env)
try:
memo_dict = self._memo['subst_src_suffixes']
except KeyError:
memo_dict = {}
self._memo['subst_src_suffixes'] = memo_dict
else:
try:
return memo_dict[memo_key]
except KeyError:
pass
suffixes = [env.subst(x) for x in self.src_suffix]
memo_dict[memo_key] = suffixes
return suffixes
def src_suffixes(self, env):
"""
Returns the list of source suffixes for all src_builders of this
Builder.
This is essentially a recursive descent of the src_builder "tree."
(This value isn't cached because there may be changes in a
src_builder many levels deep that we can't see.)
"""
sdict = {}
suffixes = self.subst_src_suffixes(env)
for s in suffixes:
sdict[s] = 1
for builder in self.get_src_builders(env):
for s in builder.src_suffixes(env):
if s not in sdict:
sdict[s] = 1
suffixes.append(s)
return suffixes
class CompositeBuilder(SCons.Util.Proxy):
"""A Builder Proxy whose main purpose is to always have
a DictCmdGenerator as its action, and to provide access
to the DictCmdGenerator's add_action() method.
"""
def __init__(self, builder, cmdgen):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.CompositeBuilder')
super().__init__(builder)
# cmdgen should always be an instance of DictCmdGenerator.
self.cmdgen = cmdgen
self.builder = builder
__call__ = SCons.Util.Delegate('__call__')
def add_action(self, suffix, action):
self.cmdgen.add_action(suffix, action)
self.set_src_suffix(self.cmdgen.src_suffixes())
def is_a_Builder(obj):
""""Returns True if the specified obj is one of our Builder classes.
The test is complicated a bit by the fact that CompositeBuilder
is a proxy, not a subclass of BuilderBase.
"""
return (isinstance(obj, BuilderBase)
or isinstance(obj, CompositeBuilder)
or callable(obj))
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,404 @@
# 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.
"""CacheDir support
"""
import atexit
import json
import os
import shutil
import stat
import sys
import tempfile
import uuid
import SCons.Action
import SCons.Errors
import SCons.Warnings
import SCons.Util
CACHE_PREFIX_LEN = 2 # first two characters used as subdirectory name
CACHE_TAG = (
b"Signature: 8a477f597d28d172789f06886806bc55\n"
b"# SCons cache directory - see https://bford.info/cachedir/\n"
)
cache_enabled = True
cache_debug = False
cache_force = False
cache_show = False
cache_readonly = False
cache_tmp_uuid = uuid.uuid4().hex
def CacheRetrieveFunc(target, source, env):
t = target[0]
fs = t.fs
cd = env.get_CacheDir()
cd.requests += 1
cachedir, cachefile = cd.cachepath(t)
if not fs.exists(cachefile):
cd.CacheDebug('CacheRetrieve(%s): %s not in cache\n', t, cachefile)
return 1
cd.hits += 1
cd.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile)
if SCons.Action.execute_actions:
if fs.islink(cachefile):
fs.symlink(fs.readlink(cachefile), t.get_internal_path())
else:
cd.copy_from_cache(env, cachefile, t.get_internal_path())
try:
os.utime(cachefile, None)
except OSError:
pass
st = fs.stat(cachefile)
fs.chmod(t.get_internal_path(), stat.S_IMODE(st.st_mode) | stat.S_IWRITE)
return 0
def CacheRetrieveString(target, source, env):
t = target[0]
cd = env.get_CacheDir()
cachedir, cachefile = cd.cachepath(t)
if t.fs.exists(cachefile):
return "Retrieved `%s' from cache" % t.get_internal_path()
return ""
CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
def CachePushFunc(target, source, env):
if cache_readonly:
return
t = target[0]
if t.nocache:
return
fs = t.fs
cd = env.get_CacheDir()
cachedir, cachefile = cd.cachepath(t)
if fs.exists(cachefile):
# Don't bother copying it if it's already there. Note that
# usually this "shouldn't happen" because if the file already
# existed in cache, we'd have retrieved the file from there,
# not built it. This can happen, though, in a race, if some
# other person running the same build pushes their copy to
# the cache after we decide we need to build it but before our
# build completes.
cd.CacheDebug('CachePush(%s): %s already exists in cache\n', t, cachefile)
return
cd.CacheDebug('CachePush(%s): pushing to %s\n', t, cachefile)
tempfile = "%s.tmp%s"%(cachefile,cache_tmp_uuid)
errfmt = "Unable to copy %s to cache. Cache file is %s"
try:
fs.makedirs(cachedir, exist_ok=True)
except OSError:
msg = errfmt % (str(target), cachefile)
raise SCons.Errors.SConsEnvironmentError(msg)
try:
if fs.islink(t.get_internal_path()):
fs.symlink(fs.readlink(t.get_internal_path()), tempfile)
else:
cd.copy_to_cache(env, t.get_internal_path(), tempfile)
fs.rename(tempfile, cachefile)
except OSError:
# It's possible someone else tried writing the file at the
# same time we did, or else that there was some problem like
# the CacheDir being on a separate file system that's full.
# In any case, inability to push a file to cache doesn't affect
# the correctness of the build, so just print a warning.
msg = errfmt % (str(t), cachefile)
cd.CacheDebug(errfmt + '\n', str(t), cachefile)
SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning, msg)
CachePush = SCons.Action.Action(CachePushFunc, None)
class CacheDir:
def __init__(self, path):
"""Initialize a CacheDir object.
The cache configuration is stored in the object. It
is read from the config file in the supplied path if
one exists, if not the config file is created and
the default config is written, as well as saved in the object.
"""
self.requests = 0
self.hits = 0
self.path = path
self.current_cache_debug = None
self.debugFP = None
self.config = {}
if path is not None:
self._readconfig(path)
def _add_config(self, path):
"""Create the cache config file in *path*.
Locking isn't necessary in the normal case - when the cachedir is
being created - because it's written to a unique directory first,
before the directory is renamed. But it is legal to call CacheDir
with an existing directory, which may be missing the config file,
and in that case we do need locking. Simpler to always lock.
"""
config_file = os.path.join(path, 'config')
# TODO: this breaks the "unserializable config object" test which
# does some crazy stuff, so for now don't use setdefault. It does
# seem like it would be better to preserve an exisiting value.
# self.config.setdefault('prefix_len', CACHE_PREFIX_LEN)
self.config['prefix_len'] = CACHE_PREFIX_LEN
with SCons.Util.FileLock(config_file, timeout=5, writer=True), open(
config_file, "x"
) as config:
try:
json.dump(self.config, config)
except Exception:
msg = "Failed to write cache configuration for " + path
raise SCons.Errors.SConsEnvironmentError(msg)
# Add the tag file "carelessly" - the contents are not used by SCons
# so we don't care about the chance of concurrent writes.
try:
tagfile = os.path.join(path, "CACHEDIR.TAG")
with open(tagfile, 'xb') as cachedir_tag:
cachedir_tag.write(CACHE_TAG)
except FileExistsError:
pass
def _mkdir_atomic(self, path):
"""Create cache directory at *path*.
Uses directory renaming to avoid races. If we are actually
creating the dir, populate it with the metadata files at the
same time as that's the safest way. But it's not illegal to point
CacheDir at an existing directory that wasn't a cache previously,
so we may have to do that elsewhere, too.
Returns:
``True`` if it we created the dir, ``False`` if already existed,
Raises:
SConsEnvironmentError: if we tried and failed to create the cache.
"""
directory = os.path.abspath(path)
if os.path.exists(directory):
return False
try:
# TODO: Python 3.7. See comment below.
# tempdir = tempfile.TemporaryDirectory(dir=os.path.dirname(directory))
tempdir = tempfile.mkdtemp(dir=os.path.dirname(directory))
except OSError as e:
msg = "Failed to create cache directory " + path
raise SCons.Errors.SConsEnvironmentError(msg) from e
# TODO: Python 3.7: the context manager raises exception on cleanup
# if the temporary was moved successfully (File Not Found).
# Fixed in 3.8+. In the replacement below we manually clean up if
# the move failed as mkdtemp() does not. TemporaryDirectory's
# cleanup is more sophisitcated so prefer when we can use it.
# self._add_config(tempdir.name)
# with tempdir:
# try:
# os.replace(tempdir.name, directory)
# return True
# except OSError as e:
# # did someone else get there first?
# if os.path.isdir(directory):
# return False # context manager cleans up
# msg = "Failed to create cache directory " + path
# raise SCons.Errors.SConsEnvironmentError(msg) from e
self._add_config(tempdir)
try:
os.replace(tempdir, directory)
return True
except OSError as e:
# did someone else get there first? attempt cleanup.
if os.path.isdir(directory):
try:
shutil.rmtree(tempdir)
except Exception: # we tried, don't worry about it
pass
return False
msg = "Failed to create cache directory " + path
raise SCons.Errors.SConsEnvironmentError(msg) from e
def _readconfig(self, path):
"""Read the cache config from *path*.
If directory or config file do not exist, create and populate.
"""
config_file = os.path.join(path, 'config')
created = self._mkdir_atomic(path)
if not created and not os.path.isfile(config_file):
# Could have been passed an empty directory
self._add_config(path)
try:
with SCons.Util.FileLock(config_file, timeout=5, writer=False), open(
config_file
) as config:
self.config = json.load(config)
except (ValueError, json.decoder.JSONDecodeError):
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 == '-':
self.debugFP = sys.stdout
elif cache_debug:
def debug_cleanup(debugFP):
debugFP.close()
self.debugFP = open(cache_debug, 'w')
atexit.register(debug_cleanup, self.debugFP)
else:
self.debugFP = None
self.current_cache_debug = cache_debug
if self.debugFP:
self.debugFP.write(fmt % (target, os.path.split(cachefile)[1]))
self.debugFP.write("requests: %d, hits: %d, misses: %d, hit rate: %.2f%%\n" %
(self.requests, self.hits, self.misses, self.hit_ratio))
@classmethod
def copy_from_cache(cls, env, src, dst):
"""Copy a file from cache."""
if env.cache_timestamp_newer:
return env.fs.copy(src, dst)
else:
return env.fs.copy2(src, dst)
@classmethod
def copy_to_cache(cls, env, src, dst):
"""Copy a file to cache.
Just use the FS copy2 ("with metadata") method, except do an additional
check and if necessary a chmod to ensure the cachefile is writeable,
to forestall permission problems if the cache entry is later updated.
"""
try:
result = env.fs.copy2(src, dst)
st = stat.S_IMODE(os.stat(result).st_mode)
if not st | stat.S_IWRITE:
os.chmod(dst, st | stat.S_IWRITE)
return result
except AttributeError as ex:
raise OSError from ex
@property
def hit_ratio(self):
return (100.0 * self.hits / self.requests if self.requests > 0 else 100)
@property
def misses(self):
return self.requests - self.hits
def is_enabled(self):
return cache_enabled and self.path is not None
def is_readonly(self):
return cache_readonly
def get_cachedir_csig(self, node):
cachedir, cachefile = self.cachepath(node)
if cachefile and os.path.exists(cachefile):
return SCons.Util.hash_file_signature(cachefile, SCons.Node.FS.File.hash_chunksize)
def cachepath(self, node):
"""Return where to cache a file.
Given a Node, obtain the configured cache directory and
the path to the cached file, which is generated from the
node's build signature. If caching is not enabled for the
None, return a tuple of None.
"""
if not self.is_enabled():
return None, None
sig = node.get_cachedir_bsig()
subdir = sig[:self.config['prefix_len']].upper()
cachedir = os.path.join(self.path, subdir)
return cachedir, os.path.join(cachedir, sig)
def retrieve(self, node):
"""Retrieve a node from cache.
Returns True if a successful retrieval resulted.
This method is called from multiple threads in a parallel build,
so only do thread safe stuff here. Do thread unsafe stuff in
built().
Note that there's a special trick here with the execute flag
(one that's not normally done for other actions). Basically
if the user requested a no_exec (-n) build, then
SCons.Action.execute_actions is set to 0 and when any action
is called, it does its showing but then just returns zero
instead of actually calling the action execution operation.
The problem for caching is that if the file does NOT exist in
cache then the CacheRetrieveString won't return anything to
show for the task, but the Action.__call__ won't call
CacheRetrieveFunc; instead it just returns zero, which makes
the code below think that the file *was* successfully
retrieved from the cache, therefore it doesn't do any
subsequent building. However, the CacheRetrieveString didn't
print anything because it didn't actually exist in the cache,
and no more build actions will be performed, so the user just
sees nothing. The fix is to tell Action.__call__ to always
execute the CacheRetrieveFunc and then have the latter
explicitly check SCons.Action.execute_actions itself.
"""
if not self.is_enabled():
return False
env = node.get_build_env()
if cache_show:
if CacheRetrieveSilent(node, [], env, execute=1) == 0:
node.build(presub=0, execute=0)
return True
else:
if CacheRetrieve(node, [], env, execute=1) == 0:
return True
return False
def push(self, node):
if self.is_readonly() or not self.is_enabled():
return
return CachePush(node, [], node.get_build_env())
def push_if_forced(self, node):
if cache_force:
return self.push(node)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,870 @@
# MIT License
#
# Copyright The SCons Foundation
# Copyright (c) 2003 Stichting NLnet Labs
# Copyright (c) 2001, 2002, 2003 Steven Knight
#
# 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.
r"""Autoconf-like configuration support
The purpose of this module is to define how a check is to be performed.
A context class is used that defines functions for carrying out the tests,
logging and messages. The following methods and members must be present:
context.Display(msg)
Function called to print messages that are normally displayed
for the user. Newlines are explicitly used. The text should
also be written to the logfile!
context.Log(msg)
Function called to write to a log file.
context.BuildProg(text, ext)
Function called to build a program, using "ext" for the file
extension. Must return an empty string for success, an error
message for failure. For reliable test results building should
be done just like an actual program would be build, using the
same command and arguments (including configure results so far).
context.CompileProg(text, ext)
Function called to compile a program, using "ext" for the file
extension. Must return an empty string for success, an error
message for failure. For reliable test results compiling should be
done just like an actual source file would be compiled, using the
same command and arguments (including configure results so far).
context.AppendLIBS(lib_name_list)
Append "lib_name_list" to the value of LIBS. "lib_namelist" is
a list of strings. Return the value of LIBS before changing it
(any type can be used, it is passed to SetLIBS() later.)
context.PrependLIBS(lib_name_list)
Prepend "lib_name_list" to the value of LIBS. "lib_namelist" is
a list of strings. Return the value of LIBS before changing it
(any type can be used, it is passed to SetLIBS() later.)
context.SetLIBS(value)
Set LIBS to "value". The type of "value" is what AppendLIBS()
returned. Return the value of LIBS before changing it (any type
can be used, it is passed to SetLIBS() later.)
context.headerfilename
Name of file to append configure results to, usually "confdefs.h".
The file must not exist or be empty when starting. Empty or None
to skip this (some tests will not work!).
context.config_h (may be missing).
If present, must be a string, which will be filled with the
contents of a config_h file.
context.vardict
Dictionary holding variables used for the tests and stores results
from the tests, used for the build commands. Normally contains
"CC", "LIBS", "CPPFLAGS", etc.
context.havedict
Dictionary holding results from the tests that are to be used
inside a program. Names often start with "HAVE\_". These are zero
(feature not present) or one (feature present). Other variables
may have any value, e.g., "PERLVERSION" can be a number and
"SYSTEMNAME" a string.
"""
import re
#
# PUBLIC VARIABLES
#
LogInputFiles = 1 # Set that to log the input files in case of a failed test
LogErrorMessages = 1 # Set that to log Conftest-generated error messages
#
# PUBLIC FUNCTIONS
#
# Generic remarks:
# - When a language is specified which is not supported the test fails. The
# message is a bit different, because not all the arguments for the normal
# message are available yet (chicken-egg problem).
def CheckBuilder(context, text = None, language = None):
"""
Configure check to see if the compiler works.
Note that this uses the current value of compiler and linker flags, make
sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly.
"language" should be "C" or "C++" and is used to select the compiler.
Default is "C".
"text" may be used to specify the code to be build.
Returns an empty string for success, an error message for failure.
"""
lang, suffix, msg = _lang2suffix(language)
if msg:
context.Display("%s\n" % msg)
return msg
if not text:
text = """
int main(void) {
return 0;
}
"""
context.Display("Checking if building a %s file works... " % lang)
ret = context.BuildProg(text, suffix)
_YesNoResult(context, ret, None, text)
return ret
def CheckCC(context):
"""
Configure check for a working C compiler.
This checks whether the C compiler, as defined in the $CC construction
variable, can compile a C source file. It uses the current $CCCOM value
too, so that it can test against non working flags.
"""
context.Display("Checking whether the C compiler works... ")
text = """
int main(void)
{
return 0;
}
"""
ret = _check_empty_program(context, 'CC', text, 'C')
_YesNoResult(context, ret, None, text)
return ret
def CheckSHCC(context):
"""
Configure check for a working shared C compiler.
This checks whether the C compiler, as defined in the $SHCC construction
variable, can compile a C source file. It uses the current $SHCCCOM value
too, so that it can test against non working flags.
"""
context.Display("Checking whether the (shared) C compiler works... ")
text = """
int foo(void)
{
return 0;
}
"""
ret = _check_empty_program(context, 'SHCC', text, 'C', use_shared = True)
_YesNoResult(context, ret, None, text)
return ret
def CheckCXX(context):
"""
Configure check for a working CXX compiler.
This checks whether the CXX compiler, as defined in the $CXX construction
variable, can compile a CXX source file. It uses the current $CXXCOM value
too, so that it can test against non working flags.
"""
context.Display("Checking whether the C++ compiler works... ")
text = """
int main(void)
{
return 0;
}
"""
ret = _check_empty_program(context, 'CXX', text, 'C++')
_YesNoResult(context, ret, None, text)
return ret
def CheckSHCXX(context):
"""
Configure check for a working shared CXX compiler.
This checks whether the CXX compiler, as defined in the $SHCXX construction
variable, can compile a CXX source file. It uses the current $SHCXXCOM value
too, so that it can test against non working flags.
"""
context.Display("Checking whether the (shared) C++ compiler works... ")
text = """
int main(void)
{
return 0;
}
"""
ret = _check_empty_program(context, 'SHCXX', text, 'C++', use_shared = True)
_YesNoResult(context, ret, None, text)
return ret
def _check_empty_program(context, comp, text, language, use_shared = False):
"""Return 0 on success, 1 otherwise."""
if comp not in context.env or not context.env[comp]:
# The compiler construction variable is not set or empty
return 1
lang, suffix, msg = _lang2suffix(language)
if msg:
return 1
if use_shared:
return context.CompileSharedObject(text, suffix)
else:
return context.CompileProg(text, suffix)
def CheckFunc(context, function_name, header = None, language = None, funcargs = None):
"""
Configure check for a function "function_name".
"language" should be "C" or "C++" and is used to select the compiler.
Default is "C".
Optional "header" can be defined to define a function prototype, include a
header file or anything else that comes before main().
Optional "funcargs" can be defined to define an argument list for the
generated function invocation.
Sets HAVE_function_name in context.havedict according to the result.
Note that this uses the current value of compiler and linker flags, make
sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly.
Returns an empty string for success, an error message for failure.
.. versionchanged:: 4.7.0
The ``funcargs`` parameter was added.
"""
# Remarks from autoconf:
# - Don't include <ctype.h> because on OSF/1 3.0 it includes <sys/types.h>
# which includes <sys/select.h> which contains a prototype for select.
# Similarly for bzero.
# - assert.h is included to define __stub macros and hopefully few
# prototypes, which can conflict with char $1(); below.
# - Override any gcc2 internal prototype to avoid an error.
# - We use char for the function declaration because int might match the
# return type of a gcc2 builtin and then its argument prototype would
# still apply.
# - The GNU C library defines this for functions which it implements to
# always fail with ENOSYS. Some functions are actually named something
# starting with __ and the normal name is an alias.
if context.headerfilename:
includetext = '#include "%s"' % context.headerfilename
else:
includetext = ''
if not header:
header = """
#ifdef __cplusplus
extern "C"
#endif
char %s(void);""" % function_name
lang, suffix, msg = _lang2suffix(language)
if msg:
context.Display("Cannot check for %s(): %s\n" % (function_name, msg))
return msg
if not funcargs:
funcargs = ''
text = """
%(include)s
#include <assert.h>
%(hdr)s
#if _MSC_VER && !__INTEL_COMPILER
#pragma function(%(name)s)
#endif
int main(void) {
#if defined (__stub_%(name)s) || defined (__stub___%(name)s)
#error "%(name)s has a GNU stub, cannot check"
#else
%(name)s(%(args)s);
#endif
return 0;
}
""" % { 'name': function_name,
'include': includetext,
'hdr': header,
'args': funcargs}
context.Display("Checking for %s function %s()... " % (lang, function_name))
ret = context.BuildProg(text, suffix)
_YesNoResult(context, ret, "HAVE_" + function_name, text,
"Define to 1 if the system has the function `%s'." %\
function_name)
return ret
def CheckHeader(context, header_name, header=None, language=None,
include_quotes=None):
"""
Configure check for a C or C++ header file "header_name".
Optional "header" can be defined to do something before including the
header file (unusual, supported for consistency).
"language" should be "C" or "C++" and is used to select the compiler.
Default is "C".
Sets HAVE_header_name in context.havedict according to the result.
Note that this uses the current value of compiler and linker flags, make
sure $CFLAGS and $CPPFLAGS are set correctly.
Returns an empty string for success, an error message for failure.
"""
# Why compile the program instead of just running the preprocessor?
# It is possible that the header file exists, but actually using it may
# fail (e.g., because it depends on other header files). Thus this test is
# more strict. It may require using the "header" argument.
#
# Use <> by default, because the check is normally used for system header
# files. SCons passes '""' to overrule this.
# Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
if context.headerfilename:
includetext = '#include "%s"\n' % context.headerfilename
else:
includetext = ''
if not header:
header = ""
lang, suffix, msg = _lang2suffix(language)
if msg:
context.Display("Cannot check for header file %s: %s\n"
% (header_name, msg))
return msg
if not include_quotes:
include_quotes = "<>"
text = "%s%s\n#include %s%s%s\n\n" % (includetext, header,
include_quotes[0], header_name, include_quotes[1])
context.Display("Checking for %s header file %s... " % (lang, header_name))
ret = context.CompileProg(text, suffix)
_YesNoResult(context, ret, "HAVE_" + header_name, text,
"Define to 1 if you have the <%s> header file." % header_name)
return ret
def CheckType(context, type_name, fallback = None,
header = None, language = None):
"""
Configure check for a C or C++ type "type_name".
Optional "header" can be defined to include a header file.
"language" should be "C" or "C++" and is used to select the compiler.
Default is "C".
Sets HAVE_type_name in context.havedict according to the result.
Note that this uses the current value of compiler and linker flags, make
sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly.
Returns an empty string for success, an error message for failure.
"""
# Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
if context.headerfilename:
includetext = '#include "%s"' % context.headerfilename
else:
includetext = ''
if not header:
header = ""
lang, suffix, msg = _lang2suffix(language)
if msg:
context.Display("Cannot check for %s type: %s\n" % (type_name, msg))
return msg
# Remarks from autoconf about this test:
# - Grepping for the type in include files is not reliable (grep isn't
# portable anyway).
# - Using "TYPE my_var;" doesn't work for const qualified types in C++.
# Adding an initializer is not valid for some C++ classes.
# - Using the type as parameter to a function either fails for K&$ C or for
# C++.
# - Using "TYPE *my_var;" is valid in C for some types that are not
# declared (struct something).
# - Using "sizeof(TYPE)" is valid when TYPE is actually a variable.
# - Using the previous two together works reliably.
text = """
%(include)s
%(header)s
int main(void) {
if ((%(name)s *) 0)
return 0;
if (sizeof (%(name)s))
return 0;
}
""" % { 'include': includetext,
'header': header,
'name': type_name }
context.Display("Checking for %s type %s... " % (lang, type_name))
ret = context.BuildProg(text, suffix)
_YesNoResult(context, ret, "HAVE_" + type_name, text,
"Define to 1 if the system has the type `%s'." % type_name)
if ret and fallback and context.headerfilename:
f = open(context.headerfilename, "a")
f.write("typedef %s %s;\n" % (fallback, type_name))
f.close()
return ret
def CheckTypeSize(context, type_name, header = None, language = None, expect = None):
"""This check can be used to get the size of a given type, or to check whether
the type is of expected size.
Arguments:
- type : str
the type to check
- includes : sequence
list of headers to include in the test code before testing the type
- language : str
'C' or 'C++'
- expect : int
if given, will test wether the type has the given number of bytes.
If not given, will automatically find the size.
Returns:
status : int
0 if the check failed, or the found size of the type if the check succeeded."""
# Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
if context.headerfilename:
includetext = '#include "%s"' % context.headerfilename
else:
includetext = ''
if not header:
header = ""
lang, suffix, msg = _lang2suffix(language)
if msg:
context.Display("Cannot check for %s type: %s\n" % (type_name, msg))
return msg
src = includetext + header
if expect is not None:
# Only check if the given size is the right one
context.Display('Checking %s is %d bytes... ' % (type_name, expect))
# test code taken from autoconf: this is a pretty clever hack to find that
# a type is of a given size using only compilation. This speeds things up
# quite a bit compared to straightforward code using TryRun
src = src + r"""
typedef %s scons_check_type;
int main(void)
{
static int test_array[1 - 2 * !(((long int) (sizeof(scons_check_type))) == %d)];
test_array[0] = 0;
return 0;
}
"""
st = context.CompileProg(src % (type_name, expect), suffix)
if not st:
context.Display("yes\n")
_Have(context, "SIZEOF_%s" % type_name, expect,
"The size of `%s', as computed by sizeof." % type_name)
return expect
else:
context.Display("no\n")
_LogFailed(context, src, st)
return 0
else:
# Only check if the given size is the right one
context.Message('Checking size of %s ... ' % type_name)
# We have to be careful with the program we wish to test here since
# compilation will be attempted using the current environment's flags.
# So make sure that the program will compile without any warning. For
# example using: 'int main(int argc, char** argv)' will fail with the
# '-Wall -Werror' flags since the variables argc and argv would not be
# used in the program...
#
src = src + """
#include <stdlib.h>
#include <stdio.h>
int main(void) {
printf("%d", (int)sizeof(""" + type_name + """));
return 0;
}
"""
st, out = context.RunProg(src, suffix)
try:
size = int(out)
except ValueError:
# If cannot convert output of test prog to an integer (the size),
# something went wront, so just fail
st = 1
size = 0
if not st:
context.Display("yes\n")
_Have(context, "SIZEOF_%s" % type_name, size,
"The size of `%s', as computed by sizeof." % type_name)
return size
else:
context.Display("no\n")
_LogFailed(context, src, st)
return 0
return 0
def CheckDeclaration(context, symbol, includes = None, language = None):
"""Checks whether symbol is declared.
Use the same test as autoconf, that is test whether the symbol is defined
as a macro or can be used as an r-value.
Arguments:
symbol : str
the symbol to check
includes : str
Optional "header" can be defined to include a header file.
language : str
only C and C++ supported.
Returns:
status : bool
True if the check failed, False if succeeded."""
# Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
if context.headerfilename:
includetext = '#include "%s"' % context.headerfilename
else:
includetext = ''
if not includes:
includes = ""
lang, suffix, msg = _lang2suffix(language)
if msg:
context.Display("Cannot check for declaration %s: %s\n" % (symbol, msg))
return msg
src = includetext + includes
context.Display('Checking whether %s is declared... ' % symbol)
src = src + r"""
int main(void)
{
#ifndef %s
(void) %s;
#endif
;
return 0;
}
""" % (symbol, symbol)
st = context.CompileProg(src, suffix)
_YesNoResult(context, st, "HAVE_DECL_" + symbol, src,
"Set to 1 if %s is defined." % symbol)
return st
def CheckMember(context, aggregate_member, header = None, language = None):
"""
Configure check for a C or C++ member "aggregate_member".
Optional "header" can be defined to include a header file.
"language" should be "C" or "C++" and is used to select the compiler.
Default is "C".
Note that this uses the current value of compiler and linker flags, make
sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly.
Arguments:
aggregate_member : str
the member to check. For example, 'struct tm.tm_gmtoff'.
includes : str
Optional "header" can be defined to include a header file.
language : str
only C and C++ supported.
Returns the status (0 or False = Passed, True/non-zero = Failed).
"""
lang, suffix, msg = _lang2suffix(language)
if msg:
context.Display("Cannot check for member %s: %s\n" % (aggregate_member, msg))
return True
context.Display("Checking for %s member %s... " % (lang, aggregate_member))
fields = aggregate_member.split('.')
if len(fields) != 2:
msg = "shall contain just one dot, for example 'struct tm.tm_gmtoff'"
context.Display("Cannot check for member %s: %s\n" % (aggregate_member, msg))
return True
aggregate, member = fields[0], fields[1]
# Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
if context.headerfilename:
includetext = '#include "%s"' % context.headerfilename
else:
includetext = ''
if not header:
header = ''
text = '''
%(include)s
%(header)s
int main(void) {
if (sizeof ((%(aggregate)s *) 0)->%(member)s)
return 0;
}''' % {'include': includetext,
'header': header,
'aggregate': aggregate,
'member': member}
ret = context.BuildProg(text, suffix)
_YesNoResult(context, ret, "HAVE_" + aggregate_member, text,
"Define to 1 if the system has the member `%s`." % aggregate_member)
return ret
def CheckLib(context, libs, func_name = None, header = None,
extra_libs = None, call = None, language = None, autoadd = 1,
append=True, unique=False):
"""
Configure check for a C or C++ libraries "libs". Searches through
the list of libraries, until one is found where the test succeeds.
Tests if "func_name" or "call" exists in the library. Note: if it exists
in another library the test succeeds anyway!
Optional "header" can be defined to include a header file. If not given a
default prototype for "func_name" is added.
Optional "extra_libs" is a list of library names to be added after
"lib_name" in the build command. To be used for libraries that "lib_name"
depends on.
Optional "call" replaces the call to "func_name" in the test code. It must
consist of complete C statements, including a trailing ";".
Both "func_name" and "call" arguments are optional, and in that case, just
linking against the libs is tested.
"language" should be "C" or "C++" and is used to select the compiler.
Default is "C".
Note that this uses the current value of compiler and linker flags, make
sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly.
Returns an empty string for success, an error message for failure.
"""
# Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
if context.headerfilename:
includetext = '#include "%s"' % context.headerfilename
else:
includetext = ''
if not header:
header = ""
text = """
%s
%s""" % (includetext, header)
# Add a function declaration if needed.
if func_name and func_name != "main":
if not header:
text = text + """
#ifdef __cplusplus
extern "C"
#endif
char %s();
""" % func_name
# The actual test code.
if not call:
call = "%s();" % func_name
# if no function to test, leave main() blank
text = text + """
int main(void) {
%s
return 0;
}
""" % (call or "")
if call:
i = call.find("\n")
if i > 0:
calltext = call[:i] + ".."
elif call[-1] == ';':
calltext = call[:-1]
else:
calltext = call
for lib_name in libs:
lang, suffix, msg = _lang2suffix(language)
if msg:
context.Display("Cannot check for library %s: %s\n" % (lib_name, msg))
return msg
# if a function was specified to run in main(), say it
if call:
context.Display("Checking for %s in %s library %s... "
% (calltext, lang, lib_name))
# otherwise, just say the name of library and language
else:
context.Display("Checking for %s library %s... "
% (lang, lib_name))
if lib_name:
l = [ lib_name ]
if extra_libs:
l.extend(extra_libs)
if append:
oldLIBS = context.AppendLIBS(l, unique)
else:
oldLIBS = context.PrependLIBS(l, unique)
sym = "HAVE_LIB" + lib_name
else:
oldLIBS = -1
sym = None
ret = context.BuildProg(text, suffix)
_YesNoResult(context, ret, sym, text,
"Define to 1 if you have the `%s' library." % lib_name)
if oldLIBS != -1 and (ret or not autoadd):
context.SetLIBS(oldLIBS)
if not ret:
return ret
return ret
def CheckProg(context, prog_name):
"""
Configure check for a specific program.
Check whether program prog_name exists in path. If it is found,
returns the path for it, otherwise returns None.
"""
context.Display("Checking whether %s program exists..." % prog_name)
path = context.env.WhereIs(prog_name)
if path:
context.Display(path + "\n")
else:
context.Display("no\n")
return path
#
# END OF PUBLIC FUNCTIONS
#
def _YesNoResult(context, ret, key, text, comment = None):
r"""
Handle the result of a test with a "yes" or "no" result.
:Parameters:
- `ret` is the return value: empty if OK, error message when not.
- `key` is the name of the symbol to be defined (HAVE_foo).
- `text` is the source code of the program used for testing.
- `comment` is the C comment to add above the line defining the symbol (the comment is automatically put inside a /\* \*/). If None, no comment is added.
"""
if key:
_Have(context, key, not ret, comment)
if ret:
context.Display("no\n")
_LogFailed(context, text, ret)
else:
context.Display("yes\n")
def _Have(context, key, have, comment = None):
r"""
Store result of a test in context.havedict and context.headerfilename.
:Parameters:
- `key` - is a "HAVE_abc" name. It is turned into all CAPITALS and non-alphanumerics are replaced by an underscore.
- `have` - value as it should appear in the header file, include quotes when desired and escape special characters!
- `comment` is the C comment to add above the line defining the symbol (the comment is automatically put inside a /\* \*/). If None, no comment is added.
The value of "have" can be:
- 1 - Feature is defined, add "#define key".
- 0 - Feature is not defined, add "/\* #undef key \*/". Adding "undef" is what autoconf does. Not useful for the compiler, but it shows that the test was done.
- number - Feature is defined to this number "#define key have". Doesn't work for 0 or 1, use a string then.
- string - Feature is defined to this string "#define key have".
"""
key_up = key.upper()
key_up = re.sub('[^A-Z0-9_]', '_', key_up)
context.havedict[key_up] = have
if have == 1:
line = "#define %s 1\n" % key_up
elif have == 0:
line = "/* #undef %s */\n" % key_up
elif isinstance(have, int):
line = "#define %s %d\n" % (key_up, have)
else:
line = "#define %s %s\n" % (key_up, str(have))
if comment is not None:
lines = "\n/* %s */\n" % comment + line
else:
lines = "\n" + line
if context.headerfilename:
f = open(context.headerfilename, "a")
f.write(lines)
f.close()
elif hasattr(context,'config_h'):
context.config_h = context.config_h + lines
def _LogFailed(context, text, msg):
"""
Write to the log about a failed program.
Add line numbers, so that error messages can be understood.
"""
if LogInputFiles:
context.Log("Failed program was:\n")
lines = text.split('\n')
if len(lines) and lines[-1] == '':
lines = lines[:-1] # remove trailing empty line
n = 1
for line in lines:
context.Log("%d: %s\n" % (n, line))
n = n + 1
if LogErrorMessages:
context.Log("Error message: %s\n" % msg)
def _lang2suffix(lang):
"""
Convert a language name to a suffix.
When "lang" is empty or None C is assumed.
Returns a tuple (lang, suffix, None) when it works.
For an unrecognized language returns (None, None, msg).
Where:
- lang = the unified language name
- suffix = the suffix, including the leading dot
- msg = an error message
"""
if not lang or lang in ["C", "c"]:
return ("C", ".c", None)
if lang in ["c++", "C++", "cpp", "CXX", "cxx"]:
return ("C++", ".cpp", None)
return None, None, "Unsupported language: %s" % lang
# vim: set sw=4 et sts=4 tw=79 fo+=l:
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,254 @@
# 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.
"""Code for debugging SCons internal things.
Shouldn't be needed by most users. Quick shortcuts::
from SCons.Debug import caller_trace
caller_trace()
"""
import atexit
import os
import sys
import time
import weakref
import inspect
# Global variable that gets set to 'True' by the Main script,
# when the creation of class instances should get tracked.
track_instances = False
# List of currently tracked classes
tracked_classes = {}
# Global variable that gets set to 'True' by the Main script
# when SConscript call tracing should be enabled.
sconscript_trace = False
def logInstanceCreation(instance, name=None):
if name is None:
name = instance.__class__.__name__
if name not in tracked_classes:
tracked_classes[name] = []
if hasattr(instance, '__dict__'):
tracked_classes[name].append(weakref.ref(instance))
else:
# weakref doesn't seem to work when the instance
# contains only slots...
tracked_classes[name].append(instance)
def string_to_classes(s):
if s == '*':
return sorted(tracked_classes.keys())
else:
return s.split()
def fetchLoggedInstances(classes="*"):
classnames = string_to_classes(classes)
return [(cn, len(tracked_classes[cn])) for cn in classnames]
def countLoggedInstances(classes, file=sys.stdout):
for classname in string_to_classes(classes):
file.write("%s: %d\n" % (classname, len(tracked_classes[classname])))
def listLoggedInstances(classes, file=sys.stdout):
for classname in string_to_classes(classes):
file.write('\n%s:\n' % classname)
for ref in tracked_classes[classname]:
if inspect.isclass(ref):
obj = ref()
else:
obj = ref
if obj is not None:
file.write(' %s\n' % repr(obj))
def dumpLoggedInstances(classes, file=sys.stdout):
for classname in string_to_classes(classes):
file.write('\n%s:\n' % classname)
for ref in tracked_classes[classname]:
obj = ref()
if obj is not None:
file.write(' %s:\n' % obj)
for key, value in obj.__dict__.items():
file.write(' %20s : %s\n' % (key, value))
if sys.platform[:5] == "linux":
# Linux doesn't actually support memory usage stats from getrusage().
def memory():
with open('/proc/self/stat') as f:
mstr = f.read()
mstr = mstr.split()[22]
return int(mstr)
elif sys.platform[:6] == 'darwin':
#TODO really get memory stats for OS X
def memory():
return 0
elif sys.platform == 'win32':
from SCons.compat.win32 import get_peak_memory_usage
memory = get_peak_memory_usage
else:
try:
import resource
except ImportError:
def memory():
return 0
else:
def memory():
res = resource.getrusage(resource.RUSAGE_SELF)
return res[4]
def caller_stack():
"""return caller's stack"""
import traceback
tb = traceback.extract_stack()
# strip itself and the caller from the output
tb = tb[:-2]
result = []
for back in tb:
# (filename, line number, function name, text)
key = back[:3]
result.append('%s:%d(%s)' % func_shorten(key))
return result
caller_bases = {}
caller_dicts = {}
def caller_trace(back=0):
"""
Trace caller stack and save info into global dicts, which
are printed automatically at the end of SCons execution.
"""
global caller_bases, caller_dicts
import traceback
tb = traceback.extract_stack(limit=3+back)
tb.reverse()
callee = tb[1][:3]
caller_bases[callee] = caller_bases.get(callee, 0) + 1
for caller in tb[2:]:
caller = callee + caller[:3]
try:
entry = caller_dicts[callee]
except KeyError:
caller_dicts[callee] = entry = {}
entry[caller] = entry.get(caller, 0) + 1
callee = caller
# print a single caller and its callers, if any
def _dump_one_caller(key, file, level=0):
leader = ' '*level
for v,c in sorted([(-v,c) for c,v in caller_dicts[key].items()]):
file.write("%s %6d %s:%d(%s)\n" % ((leader,-v) + func_shorten(c[-3:])))
if c in caller_dicts:
_dump_one_caller(c, file, level+1)
# print each call tree
def dump_caller_counts(file=sys.stdout):
for k in sorted(caller_bases.keys()):
file.write("Callers of %s:%d(%s), %d calls:\n"
% (func_shorten(k) + (caller_bases[k],)))
_dump_one_caller(k, file)
shorten_list = [
( '/scons/SCons/', 1),
( '/src/engine/SCons/', 1),
( '/usr/lib/python', 0),
]
if os.sep != '/':
shorten_list = [(t[0].replace('/', os.sep), t[1]) for t in shorten_list]
def func_shorten(func_tuple):
f = func_tuple[0]
for t in shorten_list:
i = f.find(t[0])
if i >= 0:
if t[1]:
i = i + len(t[0])
return (f[i:],)+func_tuple[1:]
return func_tuple
TraceFP = {}
if sys.platform == 'win32':
TraceDefault = 'con'
else:
TraceDefault = '/dev/tty'
TimeStampDefault = False
StartTime = time.perf_counter()
PreviousTime = StartTime
def Trace(msg, tracefile=None, mode='w', tstamp=False):
"""Write a trace message.
Write messages when debugging which do not interfere with stdout.
Useful in tests, which monitor stdout and would break with
unexpected output. Trace messages can go to the console (which is
opened as a file), or to a disk file; the tracefile argument persists
across calls unless overridden.
Args:
tracefile: file to write trace message to. If omitted,
write to the previous trace file (default: console).
mode: file open mode (default: 'w')
tstamp: write relative timestamps with trace. Outputs time since
scons was started, and time since last trace (default: False)
"""
global TraceDefault
global TimeStampDefault
global PreviousTime
def trace_cleanup(traceFP):
traceFP.close()
if tracefile is None:
tracefile = TraceDefault
else:
TraceDefault = tracefile
if not tstamp:
tstamp = TimeStampDefault
else:
TimeStampDefault = tstamp
try:
fp = TraceFP[tracefile]
except KeyError:
try:
fp = TraceFP[tracefile] = open(tracefile, mode)
atexit.register(trace_cleanup, fp)
except TypeError:
# Assume we were passed an open file pointer.
fp = tracefile
if tstamp:
now = time.perf_counter()
fp.write('%8.4f %8.4f: ' % (now - StartTime, now - PreviousTime))
PreviousTime = now
fp.write(msg)
fp.flush()
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,760 @@
# 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.
#
"""Builders and other things for the local site.
Here's where we'll duplicate the functionality of autoconf until we
move it into the installation procedure or use something like qmconf.
The code that reads the registry to find MSVC components was borrowed
from distutils.msvccompiler.
"""
from __future__ import annotations
import os
import shutil
import stat
import sys
import time
from typing import Callable
import SCons.Action
import SCons.Builder
import SCons.CacheDir
import SCons.Environment
import SCons.Errors
import SCons.PathList
import SCons.Scanner.Dir
import SCons.Subst
import SCons.Tool
from SCons.Util import is_List, is_String, is_Sequence, is_Tuple, is_Dict, flatten
# A placeholder for a default Environment (for fetching source files
# from source code management systems and the like). This must be
# initialized later, after the top-level directory is set by the calling
# interface.
_default_env = None
# Lazily instantiate the default environment so the overhead of creating
# it doesn't apply when it's not needed.
def _fetch_DefaultEnvironment(*args, **kwargs):
"""Returns the already-created default construction environment."""
return _default_env
def DefaultEnvironment(*args, **kwargs):
"""Construct the global ("default") construction environment.
The environment is provisioned with the values from *kwargs*.
After the environment is created, this function is replaced with
a reference to :func:`_fetch_DefaultEnvironment` which efficiently
returns the initialized default construction environment without
checking for its existence.
Historically, some parts of the code held references to this function.
Thus it still has the existence check for :data:`_default_env` rather
than just blindly creating the environment and overwriting itself.
"""
global _default_env
if not _default_env:
_default_env = SCons.Environment.Environment(*args, **kwargs)
_default_env.Decider('content')
global DefaultEnvironment
DefaultEnvironment = _fetch_DefaultEnvironment
_default_env._CacheDir_path = None
return _default_env
# Emitters for setting the shared attribute on object files,
# and an action for checking that all of the source files
# going into a shared library are, in fact, shared.
def StaticObjectEmitter(target, source, env):
for tgt in target:
tgt.attributes.shared = False
return target, source
def SharedObjectEmitter(target, source, env):
for tgt in target:
tgt.attributes.shared = 1
return target, source
def SharedFlagChecker(source, target, env):
same = env.subst('$STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME')
if same == '0' or same == '' or same == 'False':
for src in source:
try:
shared = src.attributes.shared
except AttributeError:
shared = False
if not shared:
raise SCons.Errors.UserError(
"Source file: %s is static and is not compatible with shared target: %s" % (src, target[0]))
SharedCheck = SCons.Action.Action(SharedFlagChecker, None)
# Some people were using these variable name before we made
# SourceFileScanner part of the public interface. Don't break their
# SConscript files until we've given them some fair warning and a
# transition period.
CScan = SCons.Tool.CScanner
# Nuitka: Avoid unused tools
# DScan = SCons.Tool.DScanner
# LaTeXScan = SCons.Tool.LaTeXScanner
# ObjSourceScan = SCons.Tool.SourceFileScanner
ProgScan = SCons.Tool.ProgramScanner
# These aren't really tool scanners, so they don't quite belong with
# the rest of those in Tool/__init__.py, but I'm not sure where else
# they should go. Leave them here for now.
DirScanner = SCons.Scanner.Dir.DirScanner()
DirEntryScanner = SCons.Scanner.Dir.DirEntryScanner()
# Actions for common languages.
CAction = SCons.Action.Action("$CCCOM", "$CCCOMSTR")
ShCAction = SCons.Action.Action("$SHCCCOM", "$SHCCCOMSTR")
CXXAction = SCons.Action.Action("$CXXCOM", "$CXXCOMSTR")
ShCXXAction = SCons.Action.Action("$SHCXXCOM", "$SHCXXCOMSTR")
DAction = SCons.Action.Action("$DCOM", "$DCOMSTR")
ShDAction = SCons.Action.Action("$SHDCOM", "$SHDCOMSTR")
ASAction = SCons.Action.Action("$ASCOM", "$ASCOMSTR")
ASPPAction = SCons.Action.Action("$ASPPCOM", "$ASPPCOMSTR")
LinkAction = SCons.Action.Action("$LINKCOM", "$LINKCOMSTR")
ShLinkAction = SCons.Action.Action("$SHLINKCOM", "$SHLINKCOMSTR")
LdModuleLinkAction = SCons.Action.Action("$LDMODULECOM", "$LDMODULECOMSTR")
# Common tasks that we allow users to perform in platform-independent
# ways by creating ActionFactory instances.
ActionFactory = SCons.Action.ActionFactory
def get_paths_str(dest):
"""Generates a string from *dest* for use in a strfunction.
If *dest* is a list, manually converts each elem to a string.
"""
def quote(arg):
return f'"{arg}"'
if is_List(dest):
elem_strs = [quote(d) for d in dest]
return f'[{", ".join(elem_strs)}]'
else:
return quote(dest)
permission_dic = {
'u': {
'r': stat.S_IRUSR,
'w': stat.S_IWUSR,
'x': stat.S_IXUSR
},
'g': {
'r': stat.S_IRGRP,
'w': stat.S_IWGRP,
'x': stat.S_IXGRP
},
'o': {
'r': stat.S_IROTH,
'w': stat.S_IWOTH,
'x': stat.S_IXOTH
}
}
def chmod_func(dest, mode):
"""Implementation of the Chmod action function.
*mode* can be either an integer (normally expressed in octal mode,
as in 0o755) or a string following the syntax of the POSIX chmod
command (for example "ugo+w"). The latter must be converted, since
the underlying Python only takes the numeric form.
"""
from string import digits
SCons.Node.FS.invalidate_node_memos(dest)
if not is_List(dest):
dest = [dest]
if is_String(mode) and 0 not in [i in digits for i in mode]:
mode = int(mode, 8)
if not is_String(mode):
for element in dest:
os.chmod(str(element), mode)
else:
mode = str(mode)
for operation in mode.split(","):
if "=" in operation:
operator = "="
elif "+" in operation:
operator = "+"
elif "-" in operation:
operator = "-"
else:
raise SyntaxError("Could not find +, - or =")
operation_list = operation.split(operator)
if len(operation_list) != 2:
raise SyntaxError("More than one operator found")
user = operation_list[0].strip().replace("a", "ugo")
permission = operation_list[1].strip()
new_perm = 0
for u in user:
for p in permission:
try:
new_perm = new_perm | permission_dic[u][p]
except KeyError:
raise SyntaxError("Unrecognized user or permission format")
for element in dest:
curr_perm = os.stat(str(element)).st_mode
if operator == "=":
os.chmod(str(element), new_perm)
elif operator == "+":
os.chmod(str(element), curr_perm | new_perm)
elif operator == "-":
os.chmod(str(element), curr_perm & ~new_perm)
def chmod_strfunc(dest, mode):
"""strfunction for the Chmod action function."""
if not is_String(mode):
return f'Chmod({get_paths_str(dest)}, {mode:#o})'
else:
return f'Chmod({get_paths_str(dest)}, "{mode}")'
Chmod = ActionFactory(chmod_func, chmod_strfunc)
def copy_func(dest, src, symlinks=True):
"""Implementation of the Copy action function.
Copies *src* to *dest*. If *src* is a list, *dest* must be
a directory, or not exist (will be created).
Since Python :mod:`shutil` methods, which know nothing about
SCons Nodes, will be called to perform the actual copying,
args are converted to strings first.
If *symlinks* evaluates true, then a symbolic link will be
shallow copied and recreated as a symbolic link; otherwise, copying
a symbolic link will be equivalent to copying the symbolic link's
final target regardless of symbolic link depth.
"""
dest = str(dest)
src = [str(n) for n in src] if is_List(src) else str(src)
SCons.Node.FS.invalidate_node_memos(dest)
if is_List(src):
# this fails only if dest exists and is not a dir
try:
os.makedirs(dest, exist_ok=True)
except FileExistsError:
raise SCons.Errors.BuildError(
errstr=(
'Error: Copy() called with a list of sources, '
'which requires target to be a directory, '
f'but "{dest}" is not a directory.'
)
)
for file in src:
shutil.copy2(file, dest)
return 0
elif os.path.islink(src):
if symlinks:
try:
os.symlink(os.readlink(src), dest)
except FileExistsError:
raise SCons.Errors.BuildError(
errstr=(
f'Error: Copy() called to create symlink at "{dest}",'
' but a file already exists at that location.'
)
)
return 0
return copy_func(dest, os.path.realpath(src))
elif os.path.isfile(src):
shutil.copy2(src, dest)
return 0
else:
shutil.copytree(src, dest, symlinks)
return 0
def copy_strfunc(dest, src, symlinks=True):
"""strfunction for the Copy action function."""
return f'Copy({get_paths_str(dest)}, {get_paths_str(src)})'
Copy = ActionFactory(copy_func, copy_strfunc)
def delete_func(dest, must_exist=False):
"""Implementation of the Delete action function.
Lets the Python :func:`os.unlink` raise an error if *dest* does not exist,
unless *must_exist* evaluates false (the default).
"""
SCons.Node.FS.invalidate_node_memos(dest)
if not is_List(dest):
dest = [dest]
for entry in dest:
entry = str(entry)
# os.path.exists returns False with broken links that exist
entry_exists = os.path.exists(entry) or os.path.islink(entry)
if not entry_exists and not must_exist:
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, True)
continue
os.unlink(entry)
def delete_strfunc(dest, must_exist=False):
"""strfunction for the Delete action function."""
return f'Delete({get_paths_str(dest)})'
Delete = ActionFactory(delete_func, delete_strfunc)
def mkdir_func(dest):
"""Implementation of the Mkdir action function."""
SCons.Node.FS.invalidate_node_memos(dest)
if not is_List(dest):
dest = [dest]
for entry in dest:
os.makedirs(str(entry), exist_ok=True)
Mkdir = ActionFactory(mkdir_func, lambda _dir: f'Mkdir({get_paths_str(_dir)})')
def move_func(dest, src):
"""Implementation of the Move action function."""
SCons.Node.FS.invalidate_node_memos(dest)
SCons.Node.FS.invalidate_node_memos(src)
shutil.move(src, dest)
Move = ActionFactory(
move_func, lambda dest, src: f'Move("{dest}", "{src}")', convert=str
)
def touch_func(dest):
"""Implementation of the Touch action function."""
SCons.Node.FS.invalidate_node_memos(dest)
if not is_List(dest):
dest = [dest]
for file in dest:
file = str(file)
mtime = int(time.time())
if os.path.exists(file):
atime = os.path.getatime(file)
else:
with open(file, 'w'):
atime = mtime
os.utime(file, (atime, mtime))
Touch = ActionFactory(touch_func, lambda file: f'Touch({get_paths_str(file)})')
# Internal utility functions
# 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 '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 items_iter:
return items_iter
l = f(SCons.PathList.PathList(items_iter).subst_path(env, target, source))
if l is not None:
items_iter = l
if not affect_signature:
value = ['$(']
else:
value = []
value += _concat_ixes(prefix, items_iter, suffix, env)
if not affect_signature:
value += ["$)"]
return value
# pylint: enable-msg=too-many-arguments
def _concat_ixes(prefix, items_iter, suffix, env):
"""
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.
"""
result = []
# ensure that prefix and suffix are strings
prefix = str(env.subst(prefix, SCons.Subst.SUBST_RAW))
suffix = str(env.subst(suffix, SCons.Subst.SUBST_RAW))
for x in flatten(items_iter):
if isinstance(x, SCons.Node.FS.File):
result.append(x)
continue
x = str(x)
if x:
if prefix:
if prefix[-1] == ' ':
result.append(prefix[:-1])
elif x[:len(prefix)] != prefix:
x = prefix + x
result.append(x)
if suffix:
if suffix[0] == ' ':
result.append(suffix[1:])
elif x[-len(suffix):] != suffix:
result[-1] = result[-1] + suffix
return result
def _stripixes(
prefix,
items,
suffix,
stripprefixes,
stripsuffixes,
env,
literal_prefix = "",
c = None,
):
"""Returns a list with text added to items after first stripping them.
A companion to :func:`_concat_ixes`, used by tools (like the GNU
linker) that need to turn something like ``libfoo.a`` into ``-lfoo``.
*stripprefixes* and *stripsuffixes* are stripped from *items*.
Calls function *c* to postprocess the result.
Args:
prefix: string to prepend to elements
items: string or iterable to transform
suffix: string to append to elements
stripprefixes: prefix string(s) to strip from elements
stripsuffixes: suffix string(s) to strip from elements
env: construction environment for variable interpolation
c: optional function to perform a transformation on the list.
The default is `None`, which will select :func:`_concat_ixes`.
"""
if not items:
return items
if not callable(c):
env_c = env['_concat']
if env_c != _concat and callable(env_c):
# There's a custom _concat() method in the construction
# environment, and we've allowed people to set that in
# the past (see test/custom-concat.py), so preserve the
# backwards compatibility.
c = env_c
else:
c = _concat_ixes
stripprefixes = list(map(env.subst, flatten(stripprefixes)))
stripsuffixes = list(map(env.subst, flatten(stripsuffixes)))
# This is a little funky: if literal_prefix is the same as os.pathsep
# (e.g. both ':'), the normal conversion to a PathList will drop the
# literal_prefix prefix. Tell it not to split in that case, which *should*
# be okay because if we come through here, we're normally processing
# library names and won't have strings like "path:secondpath:thirdpath"
# which is why PathList() otherwise wants to split strings.
do_split = not literal_prefix == os.pathsep
stripped = []
for l in SCons.PathList.PathList(items, do_split).subst_path(env, None, None):
if isinstance(l, SCons.Node.FS.File):
stripped.append(l)
continue
if not is_String(l):
l = str(l)
if literal_prefix and l.startswith(literal_prefix):
stripped.append(l)
continue
for stripprefix in stripprefixes:
lsp = len(stripprefix)
if l[:lsp] == stripprefix:
l = l[lsp:]
# Do not strip more than one prefix
break
for stripsuffix in stripsuffixes:
lss = len(stripsuffix)
if l[-lss:] == stripsuffix:
l = l[:-lss]
# Do not strip more than one suffix
break
stripped.append(l)
return c(prefix, stripped, suffix, env)
def processDefines(defs):
"""Return list of strings for preprocessor defines from *defs*.
Resolves the different forms ``CPPDEFINES`` can be assembled in:
if the Append/Prepend routines are used beyond a initial setting it
will be a deque, but if written to only once (Environment initializer,
or direct write) it can be a multitude of types.
Any prefix/suffix is handled elsewhere (usually :func:`_concat_ixes`).
.. versionchanged:: 4.5.0
Bare tuples are now treated the same as tuple-in-sequence, assumed
to describe a valued macro. Bare strings are now split on space.
A dictionary is no longer sorted before handling.
"""
dlist = []
if is_List(defs):
for define in defs:
if define is None:
continue
elif is_Sequence(define):
if len(define) > 2:
raise SCons.Errors.UserError(
f"Invalid tuple in CPPDEFINES: {define!r}, "
"must be a tuple with only two elements"
)
name, *value = define
if value and value[0] is not None:
# TODO: do we need to quote value if it contains space?
dlist.append(f"{name}={value[0]}")
else:
dlist.append(str(define[0]))
elif is_Dict(define):
for macro, value in define.items():
if value is not None:
# TODO: do we need to quote value if it contains space?
dlist.append(f"{macro}={value}")
else:
dlist.append(str(macro))
elif is_String(define):
dlist.append(str(define))
else:
raise SCons.Errors.UserError(
f"CPPDEFINES entry {define!r} is not a tuple, list, "
"dict, string or None."
)
elif is_Tuple(defs):
if len(defs) > 2:
raise SCons.Errors.UserError(
f"Invalid tuple in CPPDEFINES: {defs!r}, "
"must be a tuple with only two elements"
)
name, *value = defs
if value and value[0] is not None:
# TODO: do we need to quote value if it contains space?
dlist.append(f"{name}={value[0]}")
else:
dlist.append(str(define[0]))
elif is_Dict(defs):
for macro, value in defs.items():
if value is None:
dlist.append(str(macro))
else:
dlist.append(f"{macro}={value}")
elif is_String(defs):
return defs.split()
else:
dlist.append(str(defs))
return dlist
def _defines(prefix, defs, suffix, env, target=None, source=None, c=_concat_ixes):
"""A wrapper around :func:`_concat_ixes` that turns a list or string
into a list of C preprocessor command-line definitions.
"""
return c(prefix, env.subst_list(processDefines(defs), target=target, source=source), suffix, env)
class NullCmdGenerator:
"""Callable class for use as a no-effect command generator.
The ``__call__`` method for this class simply returns the thing
you instantiated it with. Example usage::
env["DO_NOTHING"] = NullCmdGenerator
env["LINKCOM"] = "${DO_NOTHING('$LINK $SOURCES $TARGET')}"
"""
def __init__(self, cmd):
self.cmd = cmd
def __call__(self, target, source, env, for_signature=None):
return self.cmd
class Variable_Method_Caller:
"""A class for finding a construction variable on the stack and
calling one of its methods.
Used to support "construction variables" appearing in string
``eval``s that actually stand in for methods--specifically, the use
of "RDirs" in a call to :func:`_concat` that should actually execute the
``TARGET.RDirs`` method.
Historical note: This was formerly supported by creating a little
"build dictionary" that mapped RDirs to the method, but this got
in the way of Memoizing construction environments, because we had to
create new environment objects to hold the variables.
"""
def __init__(self, variable, method):
self.variable = variable
self.method = method
def __call__(self, *args, **kw):
try:
1 // 0
except ZeroDivisionError:
# Don't start iterating with the current stack-frame to
# prevent creating reference cycles (f_back is safe).
frame = sys.exc_info()[2].tb_frame.f_back
variable = self.variable
while frame:
if variable in frame.f_locals:
v = frame.f_locals[variable]
if v:
method = getattr(v, self.method)
return method(*args, **kw)
frame = frame.f_back
return None
def __libversionflags(env, version_var, flags_var):
"""
if version_var is not empty, returns env[flags_var], otherwise returns None
:param env:
:param version_var:
:param flags_var:
:return:
"""
try:
if env.subst('$' + version_var):
return env[flags_var]
except KeyError:
pass
return None
def __lib_either_version_flag(env, version_var1, version_var2, flags_var):
"""
if $version_var1 or $version_var2 is not empty, returns env[flags_var], otherwise returns None
:param env:
:param version_var1:
:param version_var2:
:param flags_var:
:return:
"""
try:
if env.subst('$' + version_var1) or env.subst('$' + version_var2):
return env[flags_var]
except KeyError:
pass
return None
ConstructionEnvironment = {
'BUILDERS': {},
'SCANNERS': [SCons.Tool.SourceFileScanner],
'CONFIGUREDIR': '#/.sconf_temp',
'CONFIGURELOG': '#/config.log',
'CPPSUFFIXES': SCons.Tool.CSuffixes,
'DSUFFIXES': SCons.Tool.DSuffixes,
'ENV': {},
'IDLSUFFIXES': SCons.Tool.IDLSuffixes,
'_concat': _concat,
'_defines': _defines,
'_stripixes': _stripixes,
'_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
'_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,
'__SHLIBVERSIONFLAGS': '${__libversionflags(__env__,"SHLIBVERSION","_SHLIBVERSIONFLAGS")}',
'__LDMODULEVERSIONFLAGS': '${__libversionflags(__env__,"LDMODULEVERSION","_LDMODULEVERSIONFLAGS")}',
'__DSHLIBVERSIONFLAGS': '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}',
'__lib_either_version_flag': __lib_either_version_flag,
'TEMPFILE': NullCmdGenerator,
'TEMPFILEARGJOIN': ' ',
'TEMPFILEARGESCFUNC': SCons.Subst.quote_spaces,
'Dir': Variable_Method_Caller('TARGET', 'Dir'),
'Dirs': Variable_Method_Caller('TARGET', 'Dirs'),
'File': Variable_Method_Caller('TARGET', 'File'),
'RDirs': Variable_Method_Caller('TARGET', 'RDirs'),
}
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
# 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 re
_is_valid_var = re.compile(r'[_a-zA-Z]\w*$')
_rm = re.compile(r'\$[()]')
_remove = re.compile(r'\$\([^$]*(\$[^)][^$]*)*\$\)')
# Regular expressions for splitting strings and handling substitutions,
# for use by the scons_subst() and scons_subst_list() functions:
#
# The first expression compiled matches all of the $-introduced tokens
# that we need to process in some way, and is used for substitutions.
# The expressions it matches are:
#
# "$$"
# "$("
# "$)"
# "$variable" [must begin with alphabetic or underscore]
# "${any stuff}"
#
# The second expression compiled is used for splitting strings into tokens
# to be processed, and it matches all of the tokens listed above, plus
# the following that affect how arguments do or don't get joined together:
#
# " " [white space]
# "non-white-space" [without any dollar signs]
# "$" [single dollar sign]
#
_dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
_dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
_separate_args = re.compile(r'(%s|\s+|[^\s$]+|\$)' % _dollar_exps_str)
# This regular expression is used to replace strings of multiple white
# space characters in the string result from the scons_subst() function.
_space_sep = re.compile(r'[\t ]+(?![^{]*})')
class ValueTypes:
"""
Enum to store what type of value the variable holds.
"""
UNKNOWN = 0
STRING = 1
CALLABLE = 2
VARIABLE = 3
class EnvironmentValue:
"""
Hold a single value. We're going to cache parsed version of the file
We're going to keep track of variables which feed into this values evaluation
"""
def __init__(self, value):
self.value = value
self.var_type = ValueTypes.UNKNOWN
if callable(self.value):
self.var_type = ValueTypes.CALLABLE
else:
self.parse_value()
def parse_value(self):
"""
Scan the string and break into component values
"""
try:
if '$' not in self.value:
self._parsed = self.value
self.var_type = ValueTypes.STRING
else:
# Now we need to parse the specified string
result = _dollar_exps.sub(sub_match, args)
print(result)
except TypeError:
# likely callable? either way we don't parse
self._parsed = self.value
def parse_trial(self):
"""
Try alternate parsing methods.
:return:
"""
parts = []
for c in self.value:
pass
class EnvironmentValues:
"""
A class to hold all the environment variables
"""
def __init__(self, **kw):
self._dict = {}
for k in kw:
self._dict[k] = EnvironmentValue(kw[k])

View File

@@ -0,0 +1,222 @@
# 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.
"""SCons exception classes.
Used to handle internal and user errors in SCons.
"""
from __future__ import annotations
import shutil
from typing import TYPE_CHECKING
from SCons.Util.sctypes import to_String, is_String
if TYPE_CHECKING:
from SCons.Executor import Executor
# Note that not all Errors are defined here, some are at the point of use
class BuildError(Exception):
"""SCons Errors that can occur while building.
A :class:`BuildError` exception contains information both
about the erorr itself, and what caused the error.
Attributes:
node: (*cause*) the error occurred while building this target node(s)
errstr: (*info*) a description of the error message
status: (*info*) the return code of the action that caused the build error.
Must be set to a non-zero value even if the build error is not due
to an action returning a non-zero returned code.
exitstatus: (*info*) SCons exit status due to this build error.
Must be nonzero unless due to an explicit :meth:`Exit` call.
Not always the same as ``status``, since actions return a status
code that should be respected, but SCons typically exits with 2
irrespective of the return value of the failed action.
filename: (*info*) The name of the file or directory that caused the
build error. Set to ``None`` if no files are associated with
this error. This might be different from the target
being built. For example, failure to create the
directory in which the target file will appear. It
can be ``None`` if the error is not due to a particular
filename.
executor: (*cause*) the executor that caused the build to fail (might
be ``None`` if the build failures is not due to the executor failing)
action: (*cause*) the action that caused the build to fail (might be
``None`` if the build failures is not due to the an
action failure)
command: (*cause*) the command line for the action that caused the
build to fail (might be ``None`` if the build failures
is not due to the an action failure)
exc_info: (*info*) Info about exception that caused the build
error. Set to ``(None, None, None)`` if this build
error is not due to an exception.
"""
def __init__(self,
node=None, errstr="Unknown error", status=2, exitstatus=2,
filename=None, executor = None, action=None, command=None,
exc_info=(None, None, None)):
# py3: errstr should be string and not bytes.
self.errstr = to_String(errstr)
self.status = status
self.exitstatus = exitstatus
self.filename = filename
self.exc_info = exc_info
self.node = node
self.executor = executor
self.action = action
self.command = command
super().__init__(node, errstr, status, exitstatus, filename,
executor, action, command, exc_info)
def __str__(self):
if self.filename:
return self.filename + ': ' + self.errstr
else:
return self.errstr
class InternalError(Exception):
pass
class UserError(Exception):
pass
class StopError(Exception):
pass
class SConsEnvironmentError(Exception):
pass
class MSVCError(IOError):
pass
class ExplicitExit(Exception):
def __init__(self, node=None, status=None, *args):
self.node = node
self.status = status
self.exitstatus = status
super().__init__(*args)
def convert_to_BuildError(status, exc_info=None):
"""Convert a return code to a BuildError Exception.
The `buildError.status` we set here will normally be
used as the exit status of the "scons" process.
Args:
status: can either be a return code or an Exception.
exc_info (tuple, optional): explicit exception information.
"""
if not exc_info and isinstance(status, Exception):
exc_info = (status.__class__, status, None)
if isinstance(status, BuildError):
buildError = status
buildError.exitstatus = 2 # always exit with 2 on build errors
elif isinstance(status, ExplicitExit):
status = status.status
errstr = 'Explicit exit, status %s' % status
buildError = BuildError(
errstr=errstr,
status=status, # might be 0, OK here
exitstatus=status, # might be 0, OK here
exc_info=exc_info)
elif isinstance(status, (StopError, UserError)):
buildError = BuildError(
errstr=str(status),
status=2,
exitstatus=2,
exc_info=exc_info)
elif isinstance(status, shutil.SameFileError):
# PY3 has a exception for when copying file to itself
# It's object provides info differently than below
try:
filename = status.filename
except AttributeError:
filename = None
buildError = BuildError(
errstr=status.args[0],
status=status.errno,
exitstatus=2,
filename=filename,
exc_info=exc_info)
elif isinstance(status, (SConsEnvironmentError, OSError, IOError)):
# If an IOError/OSError happens, raise a BuildError.
# Report the name of the file or directory that caused the
# error, which might be different from the target being built
# (for example, failure to create the directory in which the
# target file will appear).
filename = getattr(status, 'filename', None)
strerror = getattr(status, 'strerror', None)
if strerror is None:
strerror = str(status)
errno = getattr(status, 'errno', None)
if errno is None:
errno = 2
buildError = BuildError(
errstr=strerror,
status=errno,
exitstatus=2,
filename=filename,
exc_info=exc_info)
elif isinstance(status, Exception):
buildError = BuildError(
errstr='%s : %s' % (status.__class__.__name__, status),
status=2,
exitstatus=2,
exc_info=exc_info)
elif is_String(status):
buildError = BuildError(
errstr=status,
status=2,
exitstatus=2)
else:
buildError = BuildError(
errstr="Error %s" % status,
status=status,
exitstatus=2)
#import sys
#sys.stderr.write("convert_to_BuildError: status %s => (errstr %s, status %s)\n"%(status,buildError.errstr, buildError.status))
return buildError
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,665 @@
# 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.
"""Execute actions with specific lists of target and source Nodes."""
from __future__ import annotations
import collections
from contextlib import suppress
import SCons.Errors
import SCons.Memoize
import SCons.Util
from SCons.compat import NoSlotsPyPy
import SCons.Debug
from SCons.Debug import logInstanceCreation
class Batch:
"""Remembers exact association between targets
and sources of executor."""
__slots__ = ('targets',
'sources')
def __init__(self, targets=[], sources=[]):
self.targets = targets
self.sources = sources
class TSList(collections.UserList):
"""A class that implements $TARGETS or $SOURCES expansions by wrapping
an executor Method. This class is used in the Executor.lvars()
to delay creation of NodeList objects until they're needed.
Note that we subclass collections.UserList purely so that the
is_Sequence() function will identify an object of this class as
a list during variable expansion. We're not really using any
collections.UserList methods in practice.
"""
def __init__(self, func):
self.func = func
def __getattr__(self, attr):
nl = self.func()
return getattr(nl, attr)
def __getitem__(self, i):
nl = self.func()
return nl[i]
def __str__(self):
nl = self.func()
return str(nl)
def __repr__(self):
nl = self.func()
return repr(nl)
class TSObject:
"""A class that implements $TARGET or $SOURCE expansions by wrapping
an Executor method.
"""
def __init__(self, func):
self.func = func
def __getattr__(self, attr):
n = self.func()
return getattr(n, attr)
def __str__(self):
n = self.func()
if n:
return str(n)
return ''
def __repr__(self):
n = self.func()
if n:
return repr(n)
return ''
def rfile(node):
"""
A function to return the results of a Node's rfile() method,
if it exists, and the Node itself otherwise (if it's a Value
Node, e.g.).
"""
try:
rfile = node.rfile
except AttributeError:
return node
else:
return rfile()
def execute_nothing(obj, target, kw):
return 0
def execute_action_list(obj, target, kw):
"""Actually execute the action list."""
env = obj.get_build_env()
kw = obj.get_kw(kw)
status = 0
for act in obj.get_action_list():
args = ([], [], env)
status = act(*args, **kw)
if isinstance(status, SCons.Errors.BuildError):
status.executor = obj
raise status # TODO pylint E0702: raising int not allowed
elif status:
msg = "Error %s" % status
raise SCons.Errors.BuildError(
errstr=msg,
node=obj.batches[0].targets,
executor=obj,
action=act)
return status
_do_execute_map = {0 : execute_nothing,
1 : execute_action_list}
def execute_actions_str(obj):
env = obj.get_build_env()
return "\n".join([action.genstring(obj.get_all_targets(),
obj.get_all_sources(),
env)
for action in obj.get_action_list()])
def execute_null_str(obj):
return ''
_execute_str_map = {0 : execute_null_str,
1 : execute_actions_str}
class Executor(metaclass=NoSlotsPyPy):
"""A class for controlling instances of executing an action.
This largely exists to hold a single association of an action,
environment, list of environment override dictionaries, targets
and sources for later processing as needed.
"""
__slots__ = ('pre_actions',
'post_actions',
'env',
'overridelist',
'batches',
'builder_kw',
'_memo',
'lvars',
'action_list',
'_do_execute',
'_execute_str')
def __init__(self, action, env=None, overridelist=[{}],
targets=[], sources=[], builder_kw={}):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Executor.Executor')
self.set_action_list(action)
self.pre_actions = []
self.post_actions = []
self.env = env
self.overridelist = overridelist
if targets or sources:
self.batches = [Batch(targets[:], sources[:])]
else:
self.batches = []
self.builder_kw = builder_kw
self._do_execute = 1
self._execute_str = 1
self._memo = {}
def get_lvars(self):
try:
return self.lvars
except AttributeError:
self.lvars = {
'CHANGED_SOURCES': TSList(self._get_changed_sources),
'CHANGED_TARGETS': TSList(self._get_changed_targets),
'SOURCE': TSObject(self._get_source),
'SOURCES': TSList(self._get_sources),
'TARGET': TSObject(self._get_target),
'TARGETS': TSList(self._get_targets),
'UNCHANGED_SOURCES': TSList(self._get_unchanged_sources),
'UNCHANGED_TARGETS': TSList(self._get_unchanged_targets),
}
return self.lvars
def _get_changes(self):
"""Populate all the changed/unchanged lists.
.. versionchanged:: 4.10.0
``_changed_sources``, ``_changed_targets``, ``_unchanged_sources``
and ``_unchanged_targets`` are no longer separate instance
attributes, but rather saved in the :attr:`_memo` dict.
"""
changed_sources = []
changed_targets = []
unchanged_sources = []
unchanged_targets = []
for b in self.batches:
# don't add targets marked always build to unchanged lists
# add to changed list as they always need to build
if not b.targets[0].always_build and b.targets[0].is_up_to_date():
unchanged_sources.extend(list(map(rfile, b.sources)))
unchanged_targets.extend(b.targets)
else:
changed_sources.extend(list(map(rfile, b.sources)))
changed_targets.extend(b.targets)
self._memo["_get_changed_sources"] = changed_sources
self._memo["_get_changed_targets"] = changed_targets
self._memo["_get_unchanged_sources"] = unchanged_sources
self._memo["_get_unchanged_targets"] = unchanged_targets
@SCons.Memoize.CountMethodCall
def _get_changed_sources(self, *args, **kw):
with suppress(KeyError):
return self._memo["_get_changed_sources"]
self._get_changes() # sets the memo entry
return self._memo["_get_changed_sources"]
@SCons.Memoize.CountMethodCall
def _get_changed_targets(self, *args, **kw):
with suppress(KeyError):
return self._memo["_get_changed_targets"]
self._get_changes() # sets the memo entry
return self._memo["_get_changed_targets"]
def _get_source(self, *args, **kw):
return rfile(self.batches[0].sources[0]).get_subst_proxy()
def _get_sources(self, *args, **kw):
return SCons.Util.NodeList([rfile(n).get_subst_proxy() for n in self.get_all_sources()])
def _get_target(self, *args, **kw):
return self.batches[0].targets[0].get_subst_proxy()
def _get_targets(self, *args, **kw):
return SCons.Util.NodeList([n.get_subst_proxy() for n in self.get_all_targets()])
@SCons.Memoize.CountMethodCall
def _get_unchanged_sources(self, *args, **kw):
with suppress(KeyError):
return self._memo["_get_unchanged_sources"]
self._get_changes() # sets the memo entry
return self._memo["_get_unchanged_sources"]
@SCons.Memoize.CountMethodCall
def _get_unchanged_targets(self, *args, **kw):
with suppress(KeyError):
return self._memo["_get_unchanged_targets"]
self._get_changes() # sets the memo entry
return self._memo["_get_unchanged_targets"]
def get_action_targets(self):
if not self.action_list:
return []
targets_string = self.action_list[0].get_targets(self.env, self)
if targets_string[0] == '$':
targets_string = targets_string[1:]
return self.get_lvars()[targets_string]
def set_action_list(self, action):
if not SCons.Util.is_List(action):
if not action:
raise SCons.Errors.UserError("Executor must have an action.")
action = [action]
self.action_list = action
def get_action_list(self):
if self.action_list is None:
return []
return self.pre_actions + self.action_list + self.post_actions
def get_all_targets(self):
"""Returns all targets for all batches of this Executor."""
result = []
for batch in self.batches:
result.extend(batch.targets)
return result
def get_all_sources(self):
"""Returns all sources for all batches of this Executor."""
result = []
for batch in self.batches:
result.extend(batch.sources)
return result
def get_all_children(self):
"""Returns all unique children (dependencies) for all batches
of this Executor.
The Taskmaster can recognize when it's already evaluated a
Node, so we don't have to make this list unique for its intended
canonical use case, but we expect there to be a lot of redundancy
(long lists of batched .cc files #including the same .h files
over and over), so removing the duplicates once up front should
save the Taskmaster a lot of work.
"""
result = []
for target in self.get_all_targets():
result.extend(target.children())
return SCons.Util.uniquer_hashables(result)
def get_all_prerequisites(self):
"""Returns all unique (order-only) prerequisites for all batches
of this Executor.
"""
result = []
for target in self.get_all_targets():
if target.prerequisites is not None:
result.extend(target.prerequisites)
return SCons.Util.uniquer_hashables(result)
def get_action_side_effects(self):
"""Returns all side effects for all batches of this
Executor used by the underlying Action.
"""
result = []
for target in self.get_action_targets():
result.extend(target.side_effects)
return SCons.Util.uniquer_hashables(result)
@SCons.Memoize.CountMethodCall
def get_build_env(self):
"""Fetch or create the appropriate build Environment
for this Executor.
"""
try:
return self._memo['get_build_env']
except KeyError:
pass
# Create the build environment instance with appropriate
# overrides. These get evaluated against the current
# environment's construction variables so that users can
# add to existing values by referencing the variable in
# the expansion.
overrides = {}
for odict in self.overridelist:
overrides.update(odict)
import SCons.Defaults
env = self.env or SCons.Defaults.DefaultEnvironment()
build_env = env.Override(overrides)
self._memo['get_build_env'] = build_env
return build_env
def get_build_scanner_path(self, scanner):
"""Fetch the scanner path for this executor's targets and sources.
"""
env = self.get_build_env()
try:
cwd = self.batches[0].targets[0].cwd
except (IndexError, AttributeError):
cwd = None
return scanner.path(env, cwd,
self.get_all_targets(),
self.get_all_sources())
def get_kw(self, kw={}):
result = self.builder_kw.copy()
result.update(kw)
result['executor'] = self
return result
# use extra indirection because with new-style objects (Python 2.2
# and above) we can't override special methods, and nullify() needs
# to be able to do this.
def __call__(self, target, **kw):
return _do_execute_map[self._do_execute](self, target, kw)
def cleanup(self):
self._memo = {}
def add_sources(self, sources):
"""Add source files to this Executor's list. This is necessary
for "multi" Builders that can be called repeatedly to build up
a source file list for a given target."""
# TODO(batch): extend to multiple batches
assert (len(self.batches) == 1)
# TODO(batch): remove duplicates?
sources = [x for x in sources if x not in self.batches[0].sources]
self.batches[0].sources.extend(sources)
def get_sources(self):
return self.batches[0].sources
def add_batch(self, targets, sources):
"""Add pair of associated target and source to this Executor's list.
This is necessary for "batch" Builders that can be called repeatedly
to build up a list of matching target and source files that will be
used in order to update multiple target files at once from multiple
corresponding source files, for tools like MSVC that support it."""
self.batches.append(Batch(targets, sources))
def prepare(self):
"""
Preparatory checks for whether this Executor can go ahead
and (try to) build its targets.
"""
for s in self.get_all_sources():
if s.missing():
msg = "Source `%s' not found, needed by target `%s'."
raise SCons.Errors.StopError(msg % (s, self.batches[0].targets[0]))
def add_pre_action(self, action):
self.pre_actions.append(action)
def add_post_action(self, action):
self.post_actions.append(action)
# another extra indirection for new-style objects and nullify...
def __str__(self):
return _execute_str_map[self._execute_str](self)
def nullify(self):
self.cleanup()
self._do_execute = 0
self._execute_str = 0
@SCons.Memoize.CountMethodCall
def get_contents(self):
"""Fetch the signature contents. This is the main reason this
class exists, so we can compute this once and cache it regardless
of how many target or source Nodes there are.
Returns bytes
"""
try:
return self._memo['get_contents']
except KeyError:
pass
env = self.get_build_env()
action_list = self.get_action_list()
all_targets = self.get_all_targets()
all_sources = self.get_all_sources()
result = bytearray("",'utf-8').join([action.get_contents(all_targets,
all_sources,
env)
for action in action_list])
self._memo['get_contents'] = result
return result
def get_timestamp(self):
"""Fetch a time stamp for this Executor. We don't have one, of
course (only files do), but this is the interface used by the
timestamp module.
"""
return 0
def scan_targets(self, scanner):
# TODO(batch): scan by batches
self.scan(scanner, self.get_all_targets())
def scan_sources(self, scanner):
# TODO(batch): scan by batches
if self.batches[0].sources:
self.scan(scanner, self.get_all_sources())
def scan(self, scanner, node_list):
"""Scan a list of this Executor's files (targets or sources) for
implicit dependencies and update all of the targets with them.
This essentially short-circuits an N*M scan of the sources for
each individual target, which is a hell of a lot more efficient.
"""
env = self.get_build_env()
path = self.get_build_scanner_path
kw = self.get_kw()
# TODO(batch): scan by batches)
deps = []
for node in node_list:
node.disambiguate()
deps.extend(node.get_implicit_deps(env, scanner, path, kw))
deps.extend(self.get_implicit_deps())
for tgt in self.get_all_targets():
tgt.add_to_implicit(deps)
def _get_unignored_sources_key(self, node, ignore=()):
return (node,) + tuple(ignore)
@SCons.Memoize.CountDictCall(_get_unignored_sources_key)
def get_unignored_sources(self, node, ignore=()):
key = (node,) + tuple(ignore)
try:
memo_dict = self._memo['get_unignored_sources']
except KeyError:
memo_dict = {}
self._memo['get_unignored_sources'] = memo_dict
else:
try:
return memo_dict[key]
except KeyError:
pass
if node:
# TODO: better way to do this (it's a linear search,
# but it may not be critical path)?
sourcelist = []
for b in self.batches:
if node in b.targets:
sourcelist = b.sources
break
else:
sourcelist = self.get_all_sources()
if ignore:
idict = {}
for i in ignore:
idict[i] = 1
sourcelist = [s for s in sourcelist if s not in idict]
memo_dict[key] = sourcelist
return sourcelist
def get_implicit_deps(self):
"""Return the executor's implicit dependencies, i.e. the nodes of
the commands to be executed."""
result = []
build_env = self.get_build_env()
for act in self.get_action_list():
deps = act.get_implicit_deps(self.get_all_targets(),
self.get_all_sources(),
build_env)
result.extend(deps)
return result
_batch_executors: dict[str, Executor] = {}
def GetBatchExecutor(key):
return _batch_executors[key]
def AddBatchExecutor(key, executor):
assert key not in _batch_executors
_batch_executors[key] = executor
nullenv = None
class NullEnvironment(SCons.Util.Null):
import SCons.CacheDir
_CacheDir_path = None
_CacheDir = SCons.CacheDir.CacheDir(None)
def get_CacheDir(self):
return self._CacheDir
def get_NullEnvironment():
"""Use singleton pattern for Null Environments."""
global nullenv
if nullenv is None:
nullenv = NullEnvironment()
return nullenv
class Null(metaclass=NoSlotsPyPy):
"""A null Executor, with a null build Environment, that does
nothing when the rest of the methods call it.
This might be able to disappear when we refactor things to
disassociate Builders from Nodes entirely, so we're not
going to worry about unit tests for this--at least for now.
Note the slots have to match :class:`Executor` exactly,
or the :meth:`_morph` will fail.
"""
__slots__ = ('pre_actions',
'post_actions',
'env',
'overridelist',
'batches',
'builder_kw',
'_memo',
'lvars',
'action_list',
'_do_execute',
'_execute_str')
def __init__(self, *args, **kw):
if SCons.Debug.track_instances:
logInstanceCreation(self, 'Executor.Null')
self.batches = [Batch(kw['targets'][:], [])]
def get_build_env(self):
return get_NullEnvironment()
def get_build_scanner_path(self):
return None
def cleanup(self):
pass
def prepare(self):
pass
def get_unignored_sources(self, *args, **kw):
return tuple(())
def get_action_targets(self):
return []
def get_action_list(self):
return []
def get_all_targets(self):
return self.batches[0].targets
def get_all_sources(self):
return self.batches[0].targets[0].sources
def get_all_children(self):
return self.batches[0].targets[0].children()
def get_all_prerequisites(self):
return []
def get_action_side_effects(self):
return []
def __call__(self, *args, **kw):
return 0
def get_contents(self):
return ''
def _morph(self):
"""Morph this Null executor to a real Executor object."""
batches = self.batches
self.__class__ = Executor
self.__init__([])
self.batches = batches
# The following methods require morphing this Null Executor to a
# real Executor object.
def add_pre_action(self, action):
self._morph()
self.add_pre_action(action)
def add_post_action(self, action):
self._morph()
self.add_post_action(action)
def set_action_list(self, action):
self._morph()
self.set_action_list(action)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,242 @@
# 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.
"""Decorator-based memoizer to count caching stats.
A decorator-based implementation to count hits and misses of the computed
values that various methods cache in memory.
Use of this modules assumes that wrapped methods be coded to cache their
values in a consistent way. In particular, it requires that the class uses a
dictionary named "_memo" to store the cached values.
Here is an example of wrapping a method that returns a computed value,
with no input parameters::
@SCons.Memoize.CountMethodCall
def foo(self):
try: # Memoization
return self._memo['foo'] # Memoization
except KeyError: # Memoization
pass # Memoization
result = self.compute_foo_value()
self._memo['foo'] = result # Memoization
return result
Here is an example of wrapping a method that will return different values
based on one or more input arguments::
def _bar_key(self, argument): # Memoization
return argument # Memoization
@SCons.Memoize.CountDictCall(_bar_key)
def bar(self, argument):
memo_key = argument # Memoization
try: # Memoization
memo_dict = self._memo['bar'] # Memoization
except KeyError: # Memoization
memo_dict = {} # Memoization
self._memo['dict'] = memo_dict # Memoization
else: # Memoization
try: # Memoization
return memo_dict[memo_key] # Memoization
except KeyError: # Memoization
pass # Memoization
result = self.compute_bar_value(argument)
memo_dict[memo_key] = result # Memoization
return result
Deciding what to cache is tricky, because different configurations
can have radically different performance tradeoffs, and because the
tradeoffs involved are often so non-obvious. Consequently, deciding
whether or not to cache a given method will likely be more of an art than
a science, but should still be based on available data from this module.
Here are some VERY GENERAL guidelines about deciding whether or not to
cache return values from a method that's being called a lot:
-- The first question to ask is, "Can we change the calling code
so this method isn't called so often?" Sometimes this can be
done by changing the algorithm. Sometimes the *caller* should
be memoized, not the method you're looking at.
-- The memoized function should be timed with multiple configurations
to make sure it doesn't inadvertently slow down some other
configuration.
-- When memoizing values based on a dictionary key composed of
input arguments, you don't need to use all of the arguments
if some of them don't affect the return values.
"""
# A flag controlling whether or not we actually use memoization.
use_memoizer = None
# Global list of counter objects
CounterList = {}
class Counter:
"""
Base class for counting memoization hits and misses.
We expect that the initialization in a matching decorator will
fill in the correct class name and method name that represents
the name of the function being counted.
"""
def __init__(self, cls_name, method_name):
"""
"""
self.cls_name = cls_name
self.method_name = method_name
self.hit = 0
self.miss = 0
def key(self):
return self.cls_name+'.'+self.method_name
def display(self):
print(f" {self.hit:7d} hits {self.miss:7d} misses {self.key()}()")
def __eq__(self, other):
try:
return self.key() == other.key()
except AttributeError:
return True
class CountValue(Counter):
"""
A counter class for simple, atomic memoized values.
A CountValue object should be instantiated in a decorator for each of
the class's methods that memoizes its return value by simply storing
the return value in its _memo dictionary.
"""
def count(self, *args, **kw):
""" Counts whether the memoized value has already been
set (a hit) or not (a miss).
"""
obj = args[0]
if self.method_name in obj._memo:
self.hit = self.hit + 1
else:
self.miss = self.miss + 1
class CountDict(Counter):
"""
A counter class for memoized values stored in a dictionary, with
keys based on the method's input arguments.
A CountDict object is instantiated in a decorator for each of the
class's methods that memoizes its return value in a dictionary,
indexed by some key that can be computed from one or more of
its input arguments.
"""
def __init__(self, cls_name, method_name, keymaker):
"""
"""
super().__init__(cls_name, method_name)
self.keymaker = keymaker
def count(self, *args, **kw):
""" Counts whether the computed key value is already present
in the memoization dictionary (a hit) or not (a miss).
"""
obj = args[0]
try:
memo_dict = obj._memo[self.method_name]
except KeyError:
self.miss = self.miss + 1
else:
key = self.keymaker(*args, **kw)
if key in memo_dict:
self.hit = self.hit + 1
else:
self.miss = self.miss + 1
def Dump(title=None):
""" Dump the hit/miss count for all the counters
collected so far.
"""
if title:
print(title)
for counter in sorted(CounterList):
CounterList[counter].display()
def EnableMemoization():
global use_memoizer
use_memoizer = 1
def CountMethodCall(fn):
""" Decorator for counting memoizer hits/misses while retrieving
a simple value in a class method. It wraps the given method
fn and uses a CountValue object to keep track of the
caching statistics.
Wrapping gets enabled by calling EnableMemoization().
"""
if use_memoizer:
def wrapper(self, *args, **kwargs):
global CounterList
key = self.__class__.__name__+'.'+fn.__name__
if key not in CounterList:
CounterList[key] = CountValue(self.__class__.__name__, fn.__name__)
CounterList[key].count(self, *args, **kwargs)
return fn(self, *args, **kwargs)
wrapper.__name__= fn.__name__
return wrapper
else:
return fn
def CountDictCall(keyfunc):
""" Decorator for counting memoizer hits/misses while accessing
dictionary values with a key-generating function. Like
CountMethodCall above, it wraps the given method
fn and uses a CountDict object to keep track of the
caching statistics. The dict-key function keyfunc has to
get passed in the decorator call and gets stored in the
CountDict instance.
Wrapping gets enabled by calling EnableMemoization().
"""
def decorator(fn):
if use_memoizer:
def wrapper(self, *args, **kwargs):
global CounterList
key = self.__class__.__name__+'.'+fn.__name__
if key not in CounterList:
CounterList[key] = CountDict(self.__class__.__name__, fn.__name__, keyfunc)
CounterList[key].count(self, *args, **kwargs)
return fn(self, *args, **kwargs)
wrapper.__name__= fn.__name__
return wrapper
else:
return fn
return decorator
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,150 @@
# 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.
"""Alias nodes.
This creates a hash of global Aliases (dummy targets).
"""
import collections
import SCons.Errors
import SCons.Node
import SCons.Util
from SCons.Util import hash_signature
class AliasNameSpace(collections.UserDict):
def Alias(self, name, **kw):
if isinstance(name, SCons.Node.Alias.Alias):
return name
try:
a = self[name]
except KeyError:
a = SCons.Node.Alias.Alias(name, **kw)
self[name] = a
return a
def lookup(self, name, **kw):
try:
return self[name]
except KeyError:
return None
class AliasNodeInfo(SCons.Node.NodeInfoBase):
__slots__ = ('csig',)
current_version_id = 2
field_list = ['csig']
def str_to_node(self, s):
return default_ans.Alias(s)
class AliasBuildInfo(SCons.Node.BuildInfoBase):
__slots__ = ()
current_version_id = 2
class Alias(SCons.Node.Node):
NodeInfo = AliasNodeInfo
BuildInfo = AliasBuildInfo
def __init__(self, name):
super().__init__()
self.name = name
self.changed_since_last_build = 1
self.store_info = 0
def str_for_display(self):
return '"' + self.__str__() + '"'
def __str__(self):
return self.name
def make_ready(self):
self.get_csig()
really_build = SCons.Node.Node.build
is_up_to_date = SCons.Node.Node.children_are_up_to_date
def is_under(self, dir):
# Make Alias nodes get built regardless of
# what directory scons was run from. Alias nodes
# are outside the filesystem:
return True
def get_contents(self):
"""The contents of an alias is the concatenation
of the content signatures of all its sources."""
childsigs = [n.get_csig() for n in self.children()]
return ''.join(childsigs)
def sconsign(self):
"""An Alias is not recorded in .sconsign files"""
pass
#
#
#
def build(self, **kw):
"""A "builder" for aliases."""
if len(self.executor.post_actions) + len(self.executor.pre_actions) > 0:
# Only actually call Node's build() if there are any
# pre or post actions.
# Alias nodes will get 1 action and Alias.build()
# This fixes GH Issue #2281
return self.really_build(**kw)
def convert(self):
try: del self.builder
except AttributeError: pass
self.reset_executor()
self.build = self.really_build
def get_csig(self):
"""
Generate a node's content signature, the digested signature
of its content.
node - the node
cache - alternate node to use for the signature cache
returns - the content signature
"""
try:
return self.ninfo.csig
except AttributeError:
pass
contents = self.get_contents()
csig = hash_signature(contents)
self.get_ninfo().csig = csig
return csig
default_ans = AliasNameSpace()
SCons.Node.arg2nodes_lookups.append(default_ans.lookup)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,227 @@
# 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.
"""Handle lists of directory paths.
These are the path lists that get set as ``CPPPATH``, ``LIBPATH``,
etc.) with as much caching of data and efficiency as we can, while
still keeping the evaluation delayed so that we Do the Right Thing
(almost) regardless of how the variable is specified.
"""
import os
import SCons.Memoize
import SCons.Node
import SCons.Util
#
# Variables to specify the different types of entries in a PathList object:
#
TYPE_STRING_NO_SUBST = 0 # string with no '$'
TYPE_STRING_SUBST = 1 # string containing '$'
TYPE_OBJECT = 2 # other object
def node_conv(obj):
"""
This is the "string conversion" routine that we have our substitutions
use to return Nodes, not strings. This relies on the fact that an
:class:`~SCons.Node.FS.EntryProxy` object has a ``get()`` method that
returns the underlying Node that it wraps, which is a bit of
architectural dependence that we might need to break or modify in the
future in response to additional requirements.
"""
try:
get = obj.get
except AttributeError:
if isinstance(obj, SCons.Node.Node) or SCons.Util.is_Sequence( obj ):
result = obj
else:
result = str(obj)
else:
result = get()
return result
class _PathList:
"""An actual PathList object.
Initializes a :class:`PathList` object, canonicalizing the input and
pre-processing it for quicker substitution later.
The stored representation of the :class:`PathList` is a list of tuples
containing (type, value), where the "type" is one of the ``TYPE_*``
variables defined above. We distinguish between:
* Strings that contain no ``$`` and therefore need no
delayed-evaluation string substitution (we expect that there
will be many of these and that we therefore get a pretty
big win from avoiding string substitution)
* Strings that contain ``$`` and therefore need substitution
(the hard case is things like ``${TARGET.dir}/include``,
which require re-evaluation for every target + source)
* Other objects (which may be something like an
:class:`~SCons.Node.FS.EntryProxy`
that needs a method called to return a Node)
Pre-identifying the type of each element in the :class:`PathList`
up-front and storing the type in the list of tuples is intended to
reduce the amount of calculation when we actually do the substitution
over and over for each target.
"""
def __init__(self, pathlist, split=True):
if SCons.Util.is_String(pathlist):
if split:
pathlist = pathlist.split(os.pathsep)
else: # no splitting, but still need a list
pathlist = [pathlist]
elif not SCons.Util.is_Sequence(pathlist):
pathlist = [pathlist]
pl = []
for p in pathlist:
try:
found = '$' in p
except (AttributeError, TypeError):
type = TYPE_OBJECT
else:
if not found:
type = TYPE_STRING_NO_SUBST
else:
type = TYPE_STRING_SUBST
pl.append((type, p))
self.pathlist = tuple(pl)
def __len__(self): return len(self.pathlist)
def __getitem__(self, i): return self.pathlist[i]
def subst_path(self, env, target, source):
"""
Performs construction variable substitution on a pre-digested
PathList for a specific target and source.
"""
result = []
for type, value in self.pathlist:
if type == TYPE_STRING_SUBST:
value = env.subst(value, target=target, source=source,
conv=node_conv)
if SCons.Util.is_Sequence(value):
result.extend(SCons.Util.flatten(value))
elif value:
result.append(value)
elif type == TYPE_OBJECT:
value = node_conv(value)
if value:
result.append(value)
elif value:
result.append(value)
return tuple(result)
class PathListCache:
"""A class to handle caching of PathList lookups.
This class gets instantiated once and then deleted from the namespace,
so it's used as a Singleton (although we don't enforce that in the
usual Pythonic ways). We could have just made the cache a dictionary
in the module namespace, but putting it in this class allows us to
use the same Memoizer pattern that we use elsewhere to count cache
hits and misses, which is very valuable.
Lookup keys in the cache are computed by the :meth:`_PathList_key` method.
Cache lookup should be quick, so we don't spend cycles canonicalizing
all forms of the same lookup key. For example, ``x:y`` and ``['x', 'y']``
logically represent the same list, but we don't bother to
split string representations and treat those two equivalently.
(Note, however, that we do, treat lists and tuples the same.)
The main type of duplication we're trying to catch will come from
looking up the same path list from two different clones of the
same construction environment. That is, given::
env2 = env1.Clone()
both ``env1`` and ``env2`` will have the same ``CPPPATH`` value, and we can
cheaply avoid re-parsing both values of ``CPPPATH`` by using the
common value from this cache.
"""
def __init__(self):
self._memo = {}
def _PathList_key(self, pathlist):
"""Returns the key for memoization of PathLists.
Note that we want this to be pretty quick, so we don't completely
canonicalize all forms of the same list. For example,
``dir1:$ROOT/dir2`` and ``['$ROOT/dir1', 'dir']`` may logically
represent the same list if you're executing from ``$ROOT``, but
we're not going to bother splitting strings into path elements,
or massaging strings into Nodes, to identify that equivalence.
We just want to eliminate obvious redundancy from the normal
case of re-using exactly the same cloned value for a path.
"""
if SCons.Util.is_Sequence(pathlist):
pathlist = tuple(SCons.Util.flatten(pathlist))
return pathlist
@SCons.Memoize.CountDictCall(_PathList_key)
def PathList(self, pathlist, split=True):
"""Entry point for getting PathLists.
Returns the cached :class:`_PathList` object for the specified
pathlist, creating and caching a new object as necessary.
"""
pathlist = self._PathList_key(pathlist)
try:
memo_dict = self._memo['PathList']
except KeyError:
memo_dict = {}
self._memo['PathList'] = memo_dict
else:
try:
return memo_dict[pathlist]
except KeyError:
pass
result = _PathList(pathlist, split)
memo_dict[pathlist] = result
return result
PathList = PathListCache().PathList
# TODO: removing the class object here means Sphinx doesn't pick up its
# docstrings: they're fine for reading here, but are not in API Docs.
del PathListCache
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,381 @@
# 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.
"""SCons platform selection.
Looks for modules that define a callable object that can modify a
construction environment as appropriate for a given platform.
Note that we take a more simplistic view of "platform" than Python does.
We're looking for a single string that determines a set of
tool-independent variables with which to initialize a construction
environment. Consequently, we'll examine both sys.platform and os.name
(and anything else that might come in to play) in order to return some
specification which is unique enough for our purposes.
Note that because this subsystem just *selects* a callable that can
modify a construction environment, it's possible for people to define
their own "platform specification" in an arbitrary callable function.
No one needs to use or tie in to this subsystem in order to roll
their own platform definition.
"""
import SCons.compat
import atexit
import contextlib
import importlib
import locale
import os
import sys
import tempfile
import SCons.Action
import SCons.Errors
import SCons.Platform
import SCons.Subst
import SCons.Tool
import SCons.Util
TEMPFILE_DEFAULT_ENCODING = "utf-8"
def platform_default():
r"""Return the platform string for our execution environment.
The returned value should map to one of the SCons/Platform/\*.py
files. Since scons is architecture independent, though, we don't
care about the machine architecture.
"""
osname = os.name
if osname == 'java':
osname = os._osType
if osname == 'posix':
if sys.platform == 'cygwin':
return 'cygwin'
elif 'irix' in sys.platform:
return 'irix'
elif 'sunos' in sys.platform:
return 'sunos'
elif 'hp-ux' in sys.platform:
return 'hpux'
elif 'aix' in sys.platform:
return 'aix'
elif 'darwin' in sys.platform:
return 'darwin'
else:
return 'posix'
elif os.name == 'os2':
return 'os2'
else:
return sys.platform
def platform_module(name=platform_default()):
"""Return the imported module for the platform.
This looks for a module name that matches the specified argument.
If the name is unspecified, we fetch the appropriate default for
our execution environment.
"""
full_name = 'SCons.Platform.' + name
try:
return sys.modules[full_name]
except KeyError:
try:
# the specific platform module is a relative import
mod = importlib.import_module("." + name, __name__)
except ModuleNotFoundError:
try:
# This support was added to enable running inside
# a py2exe bundle a long time ago - unclear if it's
# still needed. It is *not* intended to load individual
# platform modules stored in a zipfile.
import zipimport
platform = sys.modules['SCons.Platform'].__path__[0]
importer = zipimport.zipimporter(platform)
if not hasattr(importer, 'find_spec'):
# zipimport only added find_spec, exec_module in 3.10,
# unlike importlib, where they've been around since 3.4.
# If we don't have 'em, use the old way.
mod = importer.load_module(full_name)
else:
spec = importer.find_spec(full_name)
mod = importlib.util.module_from_spec(spec)
importer.exec_module(mod)
sys.modules[full_name] = mod
except zipimport.ZipImportError:
raise SCons.Errors.UserError("No platform named '%s'" % name)
setattr(SCons.Platform, name, mod)
return mod
def DefaultToolList(platform, env):
"""Select a default tool list for the specified platform."""
return SCons.Tool.tool_list(platform, env)
class PlatformSpec:
def __init__(self, name, generate):
self.name = name
self.generate = generate
def __call__(self, *args, **kw):
return self.generate(*args, **kw)
def __str__(self):
return self.name
class TempFileMunge:
"""Convert long command lines to use a temporary file.
You can set an Environment variable (usually ``TEMPFILE``) to this,
then call it with a string argument, and it will perform temporary
file substitution on it. This is used to circumvent limitations on
the length of command lines. Example::
env["TEMPFILE"] = TempFileMunge
env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES', '$LINKCOMSTR')}"
By default, the name of the temporary file used begins with a
prefix of '@'. This may be configured for other tool chains by
setting the ``TEMPFILEPREFIX`` variable. Example::
env["TEMPFILEPREFIX"] = '-@' # diab compiler
env["TEMPFILEPREFIX"] = '-via' # arm tool chain
env["TEMPFILEPREFIX"] = '' # (the empty string) PC Lint
You can configure the extension of the temporary file through the
``TEMPFILESUFFIX`` variable, which defaults to '.lnk' (see comments
in the code below). Example::
env["TEMPFILESUFFIX"] = '.lnt' # PC Lint
Entries in the temporary file are separated by the value of the
``TEMPFILEARGJOIN`` variable, which defaults to an OS-appropriate value.
A default argument escape function is ``SCons.Subst.quote_spaces``.
If you need to apply extra operations on a command argument before
writing to a temporary file(fix Windows slashes, normalize paths, etc.),
please set `TEMPFILEARGESCFUNC` variable to a custom function. Example::
import sys
import re
from SCons.Subst import quote_spaces
WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)")
def tempfile_arg_esc_func(arg):
arg = quote_spaces(arg)
if sys.platform != "win32":
return arg
# GCC requires double Windows slashes, let's use UNIX separator
return WINPATHSEP_RE.sub(r"/\1", arg)
env["TEMPFILEARGESCFUNC"] = tempfile_arg_esc_func
"""
def __init__(self, cmd, cmdstr = None):
self.cmd = cmd
self.cmdstr = cmdstr
def __call__(self, target, source, env, for_signature):
if for_signature:
# If we're being called for signature calculation, it's
# because we're being called by the string expansion in
# Subst.py, which has the logic to strip any $( $) that
# may be in the command line we squirreled away. So we
# just return the raw command line and let the upper
# string substitution layers do their thing.
return self.cmd
# Now we're actually being called because someone is actually
# going to try to execute the command, so we have to do our
# own expansion.
cmd = env.subst_list(self.cmd, SCons.Subst.SUBST_CMD, target, source)[0]
try:
maxline = int(env.subst('$MAXLINELENGTH'))
except ValueError:
maxline = 2048
length = 0
for c in cmd:
length += len(c)
length += len(cmd) - 1
if length <= maxline:
return self.cmd
# Check if we already created the temporary file for this target
# It should have been previously done by Action.strfunction() call
if SCons.Util.is_List(target):
node = target[0]
else:
node = target
cmdlist = None
if SCons.Util.is_List(self.cmd):
cmdlist_key = tuple(self.cmd)
else:
cmdlist_key = self.cmd
if node and hasattr(node.attributes, 'tempfile_cmdlist'):
cmdlist = node.attributes.tempfile_cmdlist.get(cmdlist_key, None)
if cmdlist is not None:
return cmdlist
# try encoding the tempfile data before creating the file -
# avoid orphaned files
tempfile_esc_func = env.get('TEMPFILEARGESCFUNC', SCons.Subst.quote_spaces)
args = [tempfile_esc_func(arg) for arg in cmd[1:]]
join_char = env.get('TEMPFILEARGJOIN', ' ')
contents = join_char.join(args) + "\n"
encoding = env.get('TEMPFILEENCODING', TEMPFILE_DEFAULT_ENCODING)
try:
tempfile_contents = bytes(contents, encoding=encoding)
except (UnicodeError, LookupError, TypeError):
exc_type, exc_value, _ = sys.exc_info()
if 'TEMPFILEENCODING' in env:
encoding_msg = "env['TEMPFILEENCODING']"
else:
encoding_msg = "default"
err_msg = f"tempfile encoding error: [{exc_type.__name__}] {exc_value!s}"
err_msg += f"\n {type(self).__name__} encoding: {encoding_msg} = {encoding!r}"
raise SCons.Errors.UserError(err_msg)
# Default to the .lnk suffix for the benefit of the Phar Lap
# linkloc linker, which likes to append an .lnk suffix if
# none is given.
if 'TEMPFILESUFFIX' in env:
suffix = env.subst('$TEMPFILESUFFIX')
else:
suffix = '.lnk'
if 'TEMPFILEDIR' in env:
tempfile_dir = env.subst('$TEMPFILEDIR')
os.makedirs(tempfile_dir, exist_ok=True)
else:
tempfile_dir = None
fd, tmp = tempfile.mkstemp(suffix, dir=tempfile_dir)
try:
os.write(fd, tempfile_contents)
finally:
os.close(fd)
native_tmp = SCons.Util.get_native_path(tmp)
# arrange for cleanup on exit:
def tmpfile_cleanup(file):
with contextlib.suppress(FileNotFoundError):
os.remove(file)
atexit.register(tmpfile_cleanup, tmp)
# XXX Using the SCons.Action.print_actions value directly
# like this is bogus, but expedient. This class should
# really be rewritten as an Action that defines the
# __call__() and strfunction() methods and lets the
# normal action-execution logic handle whether or not to
# print/execute the action. The problem, though, is all
# of that is decided before we execute this method as
# part of expanding the $TEMPFILE construction variable.
# Consequently, refactoring this will have to wait until
# we get more flexible with allowing Actions to exist
# independently and get strung together arbitrarily like
# Ant tasks. In the meantime, it's going to be more
# user-friendly to not let obsession with architectural
# purity get in the way of just being helpful, so we'll
# reach into SCons.Action directly.
if SCons.Action.print_actions:
cmdstr = (
env.subst(self.cmdstr, SCons.Subst.SUBST_RAW, target, source)
if self.cmdstr is not None
else ''
)
# Print our message only if XXXCOMSTR returns an empty string
if not cmdstr:
cmdstr = (
f"Using tempfile {native_tmp} for command line:\n"
f'{cmd[0]} {" ".join(args)}'
)
self._print_cmd_str(target, source, env, cmdstr)
if env.get('SHELL', None) == 'sh':
# The sh shell will try to escape the backslashes in the
# path, so unescape them.
native_tmp = native_tmp.replace('\\', r'\\\\')
if 'TEMPFILEPREFIX' in env:
prefix = env.subst('$TEMPFILEPREFIX')
else:
prefix = "@"
cmdlist = [cmd[0], prefix + native_tmp]
# Store the temporary file command list into the target Node.attributes
# to avoid creating separate temporary files for print and execute.
if node is not None:
try:
# Storing in tempfile_cmdlist by self.cmd provided when intializing
# $TEMPFILE{} fixes issue raised in PR #3140 and #3553
node.attributes.tempfile_cmdlist[cmdlist_key] = cmdlist
except AttributeError:
node.attributes.tempfile_cmdlist = {cmdlist_key: cmdlist}
return cmdlist
def _print_cmd_str(self, target, source, env, cmdstr):
# check if the user has specified a cmd line print function
print_func = None
try:
get = env.get
except AttributeError:
pass
else:
print_func = get('PRINT_CMD_LINE_FUNC')
# use the default action cmd line print if user did not supply one
if not print_func:
action = SCons.Action._ActionAction()
action.print_cmd_line(cmdstr, target, source, env)
else:
print_func(cmdstr, target, source, env)
def Platform(name = platform_default()):
"""Select a canned Platform specification."""
module = platform_module(name)
spec = PlatformSpec(name, module.generate)
return spec
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,80 @@
# 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.
"""Platform-specific initialization for IBM AIX systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
from subprocess import PIPE
from . import posix
import SCons.Util
import SCons.Action
def get_xlc(env, xlc=None, packages=[]):
# Use the AIX package installer tool lslpp to figure out where a
# given xl* compiler is installed and what version it is.
xlcPath = None
xlcVersion = None
if xlc is None:
xlc = env.get('CC', 'xlc')
if SCons.Util.is_List(xlc):
xlc = xlc[0]
for package in packages:
# find the installed filename, which may be a symlink as well
cp = SCons.Action.scons_subproc_run(
env, ['lslpp', '-fc', package], universal_newlines=True, stdout=PIPE
)
# output of lslpp is something like this:
# #Path:Fileset:File
# /usr/lib/objrepos:vac.C 6.0.0.0:/usr/vac/exe/xlCcpp
# /usr/lib/objrepos:vac.C 6.0.0.0:/usr/vac/bin/xlc_r -> /usr/vac/bin/xlc
for line in cp.stdout.splitlines():
if xlcPath:
continue # read everything to let lslpp terminate
fileset, filename = line.split(':')[1:3]
filename = filename.split()[0]
if ('/' in xlc and filename == xlc) or (
'/' not in xlc and filename.endswith('/' + xlc)
):
xlcVersion = fileset.split()[1]
xlcPath, sep, xlc = filename.rpartition('/')
return (xlcPath, xlc, xlcVersion)
def generate(env):
posix.generate(env)
#Based on AIX 5.2: ARG_MAX=24576 - 3000 for environment expansion
env['MAXLINELENGTH'] = 21576
env['SHLIBSUFFIX'] = '.a'
env['HOST_OS'] = 'aix'
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,62 @@
# 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.
"""Platform-specific initialization for Cygwin systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
import sys
from . import posix
from SCons.Platform import TempFileMunge
CYGWIN_DEFAULT_PATHS = []
if sys.platform == 'win32':
CYGWIN_DEFAULT_PATHS = [
r'C:\cygwin64\bin',
r'C:\cygwin\bin'
]
def generate(env):
posix.generate(env)
env['PROGPREFIX'] = ''
env['PROGSUFFIX'] = '.exe'
env['SHLIBPREFIX'] = ''
env['SHLIBSUFFIX'] = '.dll'
env['LIBPREFIXES'] = ['$LIBPREFIX', '$SHLIBPREFIX', '$IMPLIBPREFIX']
env['LIBSUFFIXES'] = ['$LIBSUFFIX', '$SHLIBSUFFIX', '$IMPLIBSUFFIX']
env['LIBLITERAPPREFIX'] = ':'
env['TEMPFILE'] = TempFileMunge
env['TEMPFILEPREFIX'] = '@'
env['MAXLINELENGTH'] = 2048
env['HOST_OS'] = 'cygwin'
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,71 @@
# 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.
"""Platform-specific initialization for Mac OS X systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
from . import posix
import os
def generate(env):
posix.generate(env)
env['SHLIBSUFFIX'] = '.dylib'
env['HOST_OS'] = 'darwin'
# put macports paths at front to override Apple's versions, fink path is after
# For now let people who want Macports or Fink tools specify it!
# env['ENV']['PATH'] = '/opt/local/bin:/opt/local/sbin:' + env['ENV']['PATH'] + ':/sw/bin'
# Store extra system paths in env['ENV']['PATHOSX']
filelist = ['/etc/paths',]
# make sure this works on Macs with Tiger or earlier
try:
dirlist = os.listdir('/etc/paths.d')
except (FileNotFoundError, PermissionError):
dirlist = []
for file in dirlist:
filelist.append('/etc/paths.d/'+file)
for file in filelist:
if os.path.isfile(file):
with open(file) as f:
lines = f.readlines()
for line in lines:
if line:
env.AppendENVPath('PATHOSX', line.strip('\n'))
# Not sure why this wasn't the case all along?
if env['ENV'].get('PATHOSX', False) and os.environ.get('SCONS_USE_MAC_PATHS', False):
env.AppendENVPath('PATH',env['ENV']['PATHOSX'])
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,45 @@
# 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.
"""Platform-specific initialization for HP-UX systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
from . import posix
def generate(env):
posix.generate(env)
#Based on HP-UX11i: ARG_MAX=2048000 - 3000 for environment expansion
env['MAXLINELENGTH'] = 2045000
env['SHLIBSUFFIX'] = '.sl'
env['HOST_OS'] = 'hpux'
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,41 @@
# 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.
"""Platform-specific initialization for SGI IRIX systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
from . import posix
def generate(env):
posix.generate(env)
env['HOST_OS'] = 'irix'
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,35 @@
# 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.
"""Platform-specific initialization for the MinGW system."""
import sys
MINGW_DEFAULT_PATHS = []
if sys.platform == 'win32':
MINGW_DEFAULT_PATHS = [
r'C:\msys64',
r'C:\msys64\usr\bin',
r'C:\msys',
r'C:\msys\usr\bin'
]

View File

@@ -0,0 +1,56 @@
# 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.
"""Platform-specific initialization for OS/2 systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
from . import win32
def generate(env):
if 'ENV' not in env:
env['ENV'] = {}
env['OBJPREFIX'] = ''
env['OBJSUFFIX'] = '.obj'
env['SHOBJPREFIX'] = '$OBJPREFIX'
env['SHOBJSUFFIX'] = '$OBJSUFFIX'
env['PROGPREFIX'] = ''
env['PROGSUFFIX'] = '.exe'
env['LIBPREFIX'] = ''
env['LIBSUFFIX'] = '.lib'
env['SHLIBPREFIX'] = ''
env['SHLIBSUFFIX'] = '.dll'
env['LIBPREFIXES'] = ['$LIBPREFIX']
env['LIBSUFFIXES'] = ['$LIBSUFFIX', '$SHLIBSUFFIX']
env['LIBLITERAPPREFIX'] = ''
env['HOST_OS'] = 'os2'
env['HOST_ARCH'] = win32.get_architecture().arch
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,125 @@
# 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.
"""Platform-specific initialization for POSIX (Linux, UNIX, etc.) systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
import platform
import subprocess
from SCons.Platform import TempFileMunge
from SCons.Platform.virtualenv import ImportVirtualenv
from SCons.Platform.virtualenv import ignore_virtualenv, enable_virtualenv
exitvalmap = {
2 : 127,
13 : 126,
}
def escape(arg):
"""escape shell special characters"""
slash = '\\'
special = '"$'
arg = arg.replace(slash, slash+slash)
for c in special:
arg = arg.replace(c, slash+c)
# print("ESCAPE RESULT: %s" % arg)
return '"' + arg + '"'
def exec_subprocess(l, env):
proc = subprocess.Popen(l, env = env, close_fds = True)
return proc.wait()
def subprocess_spawn(sh, escape, cmd, args, env):
return exec_subprocess([sh, '-c', ' '.join(args)], env)
def exec_popen3(l, env, stdout, stderr):
proc = subprocess.Popen(l, env = env, close_fds = True,
stdout = stdout,
stderr = stderr)
return proc.wait()
def piped_env_spawn(sh, escape, cmd, args, env, stdout, stderr):
# spawn using Popen3 combined with the env command
# the command name and the command's stdout is written to stdout
# the command's stderr is written to stderr
return exec_popen3([sh, '-c', ' '.join(args)],
env, stdout, stderr)
def generate(env):
# Bearing in mind we have python 2.4 as a baseline, we can just do this:
spawn = subprocess_spawn
pspawn = piped_env_spawn
# Note that this means that 'escape' is no longer used
if 'ENV' not in env:
env['ENV'] = {}
env['ENV']['PATH'] = '/usr/local/bin:/opt/bin:/bin:/usr/bin:/snap/bin'
env['OBJPREFIX'] = ''
env['OBJSUFFIX'] = '.o'
env['SHOBJPREFIX'] = '$OBJPREFIX'
env['SHOBJSUFFIX'] = '$OBJSUFFIX'
env['PROGPREFIX'] = ''
env['PROGSUFFIX'] = ''
env['LIBPREFIX'] = 'lib'
env['LIBSUFFIX'] = '.a'
env['SHLIBPREFIX'] = '$LIBPREFIX'
env['SHLIBSUFFIX'] = '.so'
env['LIBPREFIXES'] = ['$LIBPREFIX']
env['LIBSUFFIXES'] = ['$LIBSUFFIX', '$SHLIBSUFFIX']
env['LIBLITERALPREFIX'] = ''
env['HOST_OS'] = 'posix'
env['HOST_ARCH'] = platform.machine()
env['PSPAWN'] = pspawn
env['SPAWN'] = spawn
env['SHELL'] = 'sh'
env['ESCAPE'] = escape
env['TEMPFILE'] = TempFileMunge
env['TEMPFILEPREFIX'] = '@'
#Based on LINUX: ARG_MAX=ARG_MAX=131072 - 3000 for environment expansion
#Note: specific platforms might rise or lower this value
env['MAXLINELENGTH'] = 128072
# This platform supports RPATH specifications.
env['__RPATH'] = '$_RPATH'
# GDC is GCC family, but DMD and LDC have different options.
# Must be able to have GCC and DMD work in the same build, so:
env['__DRPATH'] = '$_DRPATH'
if enable_virtualenv and not ignore_virtualenv:
ImportVirtualenv(env)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,135 @@
# 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.
"""Platform support for a Python virtualenv.
This is support code, not a loadable Platform module.
"""
from __future__ import annotations
import os
import sys
import SCons.Util
virtualenv_enabled_by_default = False
def _enable_virtualenv_default():
return SCons.Util.get_os_env_bool('SCONS_ENABLE_VIRTUALENV', virtualenv_enabled_by_default)
def _ignore_virtualenv_default():
return SCons.Util.get_os_env_bool('SCONS_IGNORE_VIRTUALENV', False)
enable_virtualenv = _enable_virtualenv_default()
ignore_virtualenv = _ignore_virtualenv_default()
# Variables to export:
# - Python docs:
# When a virtual environment has been activated, the VIRTUAL_ENV environment
# variable is set to the path of the environment. Since explicitly
# activating a virtual environment is not required to use it, VIRTUAL_ENV
# cannot be relied upon to determine whether a virtual environment is being
# used.
# - pipenv: shell sets PIPENV_ACTIVE, cannot find it documented.
# Any others we should include?
VIRTUALENV_VARIABLES = ['VIRTUAL_ENV', 'PIPENV_ACTIVE']
def _running_in_virtualenv():
"""Check whether scons is running in a virtualenv."""
# TODO: the virtualenv command used to inject a sys.real_prefix before
# Python started officially tracking virtualenvs with the venv module.
# All Pythons since 3.3 use sys.base_prefix for tracking (PEP 405);
# virtualenv has retired their old behavior and now only makes
# venv-style virtualenvs. We're now using the detection suggested in
# PEP 668, and should be able to drop the real_prefix check soon.
return sys.base_prefix != sys.prefix or hasattr(sys, 'real_prefix')
def _is_path_in(path, base):
"""Check if *path* is located under the *base* directory."""
if not path or not base: # empty path or base are possible
return False
rp = os.path.relpath(path, base)
return (not rp.startswith(os.path.pardir)) and (not rp == os.path.curdir)
def _inject_venv_variables(env):
"""Copy any set virtualenv variables from ``os.environ`` to *env*."""
if 'ENV' not in env:
env['ENV'] = {}
ENV = env['ENV']
for name in VIRTUALENV_VARIABLES:
try:
ENV[name] = os.environ[name]
except KeyError:
pass
def _inject_venv_path(env, path_list=None):
"""Insert virtualenv-related paths from ``os.environe`` to *env*."""
if path_list is None:
path_list = os.getenv('PATH')
env.PrependENVPath('PATH', select_paths_in_venv(path_list))
def select_paths_in_venv(path_list):
"""Filter *path_list*, returning values under the virtualenv."""
if SCons.Util.is_String(path_list):
path_list = path_list.split(os.path.pathsep)
return [path for path in path_list if IsInVirtualenv(path)]
def ImportVirtualenv(env):
"""Add virtualenv information to *env*."""
_inject_venv_variables(env)
_inject_venv_path(env)
def Virtualenv():
"""Return whether operating in a virtualenv.
Returns the path to the virtualenv home if scons is executing
within a virtualenv, else and empty string.
"""
if _running_in_virtualenv():
return sys.prefix
return ""
def IsInVirtualenv(path):
"""Check whether *path* is under the virtualenv's directory.
Returns ``False`` if not using a virtualenv.
"""
return _is_path_in(path, Virtualenv())
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,447 @@
# 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.
"""Platform-specific initialization for Win32 systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
import os
import os.path
import platform
import sys
import tempfile
from SCons.Platform.posix import exitvalmap
from SCons.Platform import TempFileMunge
from SCons.Platform.virtualenv import ImportVirtualenv
from SCons.Platform.virtualenv import ignore_virtualenv, enable_virtualenv
import SCons.Util
CHOCO_DEFAULT_PATH = [
r'C:\ProgramData\chocolatey\bin'
]
if False:
# Now swap out shutil.filecopy and filecopy2 for win32 api native CopyFile
try:
from ctypes import windll
import shutil
CopyFile = windll.kernel32.CopyFileA
SetFileTime = windll.kernel32.SetFileTime
_shutil_copy = shutil.copy
_shutil_copy2 = shutil.copy2
shutil.copy2 = CopyFile
def win_api_copyfile(src,dst):
CopyFile(src,dst)
os.utime(dst)
shutil.copy = win_api_copyfile
except AttributeError:
parallel_msg = \
"Couldn't override shutil.copy or shutil.copy2 falling back to shutil defaults"
try:
import threading
spawn_lock = threading.Lock()
# This locked version of spawnve works around a Windows
# MSVCRT bug, because its spawnve is not thread-safe.
# Without this, python can randomly crash while using -jN.
# See the python bug at https://github.com/python/cpython/issues/50725
# and SCons issue at https://github.com/SCons/scons/issues/2449
def spawnve(mode, file, args, env):
spawn_lock.acquire()
try:
if mode == os.P_WAIT:
ret = os.spawnve(os.P_NOWAIT, file, args, env)
else:
ret = os.spawnve(mode, file, args, env)
finally:
spawn_lock.release()
if mode == os.P_WAIT:
pid, status = os.waitpid(ret, 0)
ret = status >> 8
return ret
except ImportError:
# Use the unsafe method of spawnve.
# Please, don't try to optimize this try-except block
# away by assuming that the threading module is always present.
# In the test test/option-j.py we intentionally call SCons with
# a fake threading.py that raises an import exception right away,
# simulating a non-existent package.
def spawnve(mode, file, args, env):
return os.spawnve(mode, file, args, env)
# The upshot of all this is that, if you are using Python 1.5.2,
# you had better have cmd or command.com in your PATH when you run
# scons.
def piped_spawn(sh, escape, cmd, args, env, stdout, stderr):
# There is no direct way to do that in python. What we do
# here should work for most cases:
# In case stdout (stderr) is not redirected to a file,
# we redirect it into a temporary file tmpFileStdout
# (tmpFileStderr) and copy the contents of this file
# to stdout (stderr) given in the argument
# Note that because this will paste shell redirection syntax
# into the cmdline, we have to call a shell to run the command,
# even though that's a bit of a performance hit.
if not sh:
sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
return 127
# one temporary file for stdout and stderr
tmpFileStdout, tmpFileStdoutName = tempfile.mkstemp(text=True)
os.close(tmpFileStdout) # don't need open until the subproc is done
tmpFileStderr, tmpFileStderrName = tempfile.mkstemp(text=True)
os.close(tmpFileStderr)
# check if output is redirected
stdoutRedirected = False
stderrRedirected = False
for arg in args:
# are there more possibilities to redirect stdout ?
if arg.startswith(">")or arg.startswith("1>"):
stdoutRedirected = True
# are there more possibilities to redirect stderr ?
if arg.startswith("2>"):
stderrRedirected = True
# redirect output of non-redirected streams to our tempfiles
if not stdoutRedirected:
args.append(">" + tmpFileStdoutName)
if not stderrRedirected:
args.append("2>" + tmpFileStderrName)
# actually do the spawn
try:
args = [sh, '/C', escape(' '.join(args))]
ret = spawnve(os.P_WAIT, sh, args, env)
except OSError as e:
# catch any error
try:
ret = exitvalmap[e.errno]
except KeyError:
sys.stderr.write("scons: unknown OSError exception code %d - %s: %s\n" % (e.errno, cmd, e.strerror))
if stderr is not None:
stderr.write("scons: %s: %s\n" % (cmd, e.strerror))
# copy child output from tempfiles to our streams
# and do clean up stuff
if stdout is not None and not stdoutRedirected:
try:
with open(tmpFileStdoutName, "rb") as tmpFileStdout:
output = tmpFileStdout.read()
stdout.write(output.decode('oem', "replace").replace("\r\n", "\n"))
os.remove(tmpFileStdoutName)
except OSError:
pass
if stderr is not None and not stderrRedirected:
try:
with open(tmpFileStderrName, "rb") as tmpFileStderr:
errors = tmpFileStderr.read()
stderr.write(errors.decode('oem', "replace").replace("\r\n", "\n"))
os.remove(tmpFileStderrName)
except OSError:
pass
return ret
def exec_spawn(l, env):
try:
result = spawnve(os.P_WAIT, l[0], l, env)
except OSError as e:
try:
result = exitvalmap[e.errno]
sys.stderr.write("scons: %s: %s\n" % (l[0], e.strerror))
except KeyError:
result = 127
if len(l) > 2:
if len(l[2]) < 1000:
command = ' '.join(l[0:3])
else:
command = l[0]
else:
command = l[0]
sys.stderr.write("scons: unknown OSError exception code %d - '%s': %s\n" % (e.errno, command, e.strerror))
return result
def spawn(sh, escape, cmd, args, env):
if not sh:
sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
return 127
return exec_spawn([sh, '/C', escape(' '.join(args))], env)
# Windows does not allow special characters in file names anyway, so no
# need for a complex escape function, we will just quote the arg, except
# that "cmd /c" requires that if an argument ends with a backslash it
# needs to be escaped so as not to interfere with closing double quote
# that we add.
def escape(x):
if x[-1] == '\\':
x = x + '\\'
return '"' + x + '"'
# Get the windows system directory name
_system_root = None
def get_system_root():
global _system_root
if _system_root is not None:
return _system_root
# A resonable default if we can't read the registry
val = os.environ.get('SystemRoot', "C:\\WINDOWS")
if SCons.Util.can_read_reg:
try:
# Look for Windows NT system root
k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
'Software\\Microsoft\\Windows NT\\CurrentVersion')
val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
except SCons.Util.RegError:
try:
# Okay, try the Windows 9x system root
k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
'Software\\Microsoft\\Windows\\CurrentVersion')
val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
except KeyboardInterrupt:
raise
except:
pass
_system_root = val
return val
def get_program_files_dir():
"""
Get the location of the program files directory
Returns
-------
"""
# Now see if we can look in the registry...
val = ''
if SCons.Util.can_read_reg:
try:
# Look for Windows Program Files directory
k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
'Software\\Microsoft\\Windows\\CurrentVersion')
val, tok = SCons.Util.RegQueryValueEx(k, 'ProgramFilesDir')
except SCons.Util.RegError:
val = ''
if val == '':
# A reasonable default if we can't read the registry
# (Actually, it's pretty reasonable even if we can :-)
val = os.path.join(os.path.dirname(get_system_root()),"Program Files")
return val
class ArchDefinition:
"""
Determine which windows CPU were running on.
A class for defining architecture-specific settings and logic.
"""
def __init__(self, arch, synonyms=[]):
self.arch = arch
self.synonyms = synonyms
SupportedArchitectureList = [
ArchDefinition(
'x86',
['i386', 'i486', 'i586', 'i686'],
),
ArchDefinition(
'x86_64',
['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'],
),
ArchDefinition(
'arm64',
['ARM64', 'aarch64', 'AARCH64', 'AArch64'],
),
ArchDefinition(
'ia64',
['IA64'],
),
]
SupportedArchitectureMap = {}
for a in SupportedArchitectureList:
SupportedArchitectureMap[a.arch] = a
for s in a.synonyms:
SupportedArchitectureMap[s] = a
def get_architecture(arch=None):
"""Returns the definition for the specified architecture string.
If no string is specified, the system default is returned (as defined
by the registry PROCESSOR_ARCHITECTURE value, PROCESSOR_ARCHITEW6432
environment variable, PROCESSOR_ARCHITECTURE environment variable, or
the platform machine).
"""
if arch is None:
if SCons.Util.can_read_reg:
try:
k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment')
val, tok = SCons.Util.RegQueryValueEx(k, 'PROCESSOR_ARCHITECTURE')
except SCons.Util.RegError:
val = ''
if val and val in SupportedArchitectureMap:
arch = val
if arch is None:
arch = os.environ.get('PROCESSOR_ARCHITEW6432')
if not arch:
arch = os.environ.get('PROCESSOR_ARCHITECTURE')
return SupportedArchitectureMap.get(arch, ArchDefinition(platform.machine(), [platform.machine()]))
def generate(env):
# Attempt to find cmd.exe (for WinNT/2k/XP) or
# command.com for Win9x
cmd_interp = ''
# First see if we can look in the registry...
if SCons.Util.can_read_reg:
try:
# Look for Windows NT system root
k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
'Software\\Microsoft\\Windows NT\\CurrentVersion')
val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
cmd_interp = os.path.join(val, 'System32\\cmd.exe')
except SCons.Util.RegError:
try:
# Okay, try the Windows 9x system root
k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
'Software\\Microsoft\\Windows\\CurrentVersion')
val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
cmd_interp = os.path.join(val, 'command.com')
except KeyboardInterrupt:
raise
except:
pass
# For the special case of not having access to the registry, we
# use a temporary path and pathext to attempt to find the command
# interpreter. If we fail, we try to find the interpreter through
# the env's PATH. The problem with that is that it might not
# contain an ENV and a PATH.
if not cmd_interp:
systemroot = get_system_root()
tmp_path = systemroot + os.pathsep + \
os.path.join(systemroot,'System32')
tmp_pathext = '.com;.exe;.bat;.cmd'
if 'PATHEXT' in os.environ:
tmp_pathext = os.environ['PATHEXT']
cmd_interp = SCons.Util.WhereIs('cmd', tmp_path, tmp_pathext)
if not cmd_interp:
cmd_interp = SCons.Util.WhereIs('command', tmp_path, tmp_pathext)
if not cmd_interp:
cmd_interp = env.Detect('cmd')
if not cmd_interp:
cmd_interp = env.Detect('command')
if 'ENV' not in env:
env['ENV'] = {}
# Import things from the external environment to the construction
# environment's ENV. This is a potential slippery slope, because we
# *don't* want to make builds dependent on the user's environment by
# default. We're doing this for SystemRoot, though, because it's
# needed for anything that uses sockets, and seldom changes, and
# for SystemDrive because it's related.
#
# Weigh the impact carefully before adding other variables to this list.
import_env = ['SystemDrive', 'SystemRoot', 'TEMP', 'TMP', 'USERPROFILE']
for var in import_env:
v = os.environ.get(var)
if v:
env['ENV'][var] = v
if 'COMSPEC' not in env['ENV']:
v = os.environ.get("COMSPEC")
if v:
env['ENV']['COMSPEC'] = v
env.AppendENVPath('PATH', get_system_root() + '\\System32')
env['ENV']['PATHEXT'] = '.COM;.EXE;.BAT;.CMD'
env['OBJPREFIX'] = ''
env['OBJSUFFIX'] = '.obj'
env['SHOBJPREFIX'] = '$OBJPREFIX'
env['SHOBJSUFFIX'] = '$OBJSUFFIX'
env['PROGPREFIX'] = ''
env['PROGSUFFIX'] = '.exe'
env['LIBPREFIX'] = ''
env['LIBSUFFIX'] = '.lib'
env['SHLIBPREFIX'] = ''
env['SHLIBSUFFIX'] = '.dll'
env['LIBPREFIXES'] = ['$LIBPREFIX']
env['LIBSUFFIXES'] = ['$LIBSUFFIX']
env['LIBLITERALPREFIX'] = ''
env['PSPAWN'] = piped_spawn
env['SPAWN'] = spawn
env['SHELL'] = cmd_interp
env['TEMPFILE'] = TempFileMunge
env['TEMPFILEPREFIX'] = '@'
env['MAXLINELENGTH'] = 2048
env['ESCAPE'] = escape
env['HOST_OS'] = 'win32'
env['HOST_ARCH'] = get_architecture().arch
if enable_virtualenv and not ignore_virtualenv:
ImportVirtualenv(env)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,442 @@
# 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.
"""Operations on signature database files (.sconsign). """
import SCons.compat # pylint: disable=wrong-import-order
import os
import pickle
import time
import SCons.dblite
import SCons.Warnings
from SCons.compat import PICKLE_PROTOCOL
from SCons.Util import print_time
def corrupt_dblite_warning(filename):
SCons.Warnings.warn(
SCons.Warnings.CorruptSConsignWarning,
"Ignoring corrupt .sconsign file: %s" % filename,
)
SCons.dblite.IGNORE_CORRUPT_DBFILES = True
SCons.dblite.corruption_warning = corrupt_dblite_warning
# XXX Get rid of the global array so this becomes re-entrant.
sig_files = []
# Info for the database SConsign implementation (now the default):
# "DataBase" is a dictionary that maps top-level SConstruct directories
# to open database handles.
# "DB_Module" is the Python database module to create the handles.
# "DB_Name" is the base name of the database file (minus any
# extension the underlying DB module will add).
DataBase = {}
DB_Module = SCons.dblite
DB_Name = None
DB_sync_list = []
def current_sconsign_filename():
hash_format = SCons.Util.get_hash_format()
current_hash_algorithm = SCons.Util.get_current_hash_algorithm_used()
# if the user left the options defaulted AND the default algorithm set by
# SCons is md5, then set the database name to be the special default name
#
# otherwise, if it defaults to something like 'sha1' or the user explicitly
# set 'md5' as the hash format, set the database name to .sconsign_<algorithm>
# eg .sconsign_sha1, etc.
if hash_format is None and current_hash_algorithm == 'md5':
return ".sconsign"
return ".sconsign_" + current_hash_algorithm
def Get_DataBase(dir):
global DB_Name
if DB_Name is None:
DB_Name = current_sconsign_filename()
top = dir.fs.Top
if not os.path.isabs(DB_Name) and top.repositories:
mode = "c"
for d in [top] + top.repositories:
if dir.is_under(d):
try:
return DataBase[d], mode
except KeyError:
path = d.entry_abspath(DB_Name)
try: db = DataBase[d] = DB_Module.open(path, mode)
except OSError:
pass
else:
if mode != "r":
DB_sync_list.append(db)
return db, mode
mode = "r"
try:
return DataBase[top], "c"
except KeyError:
db = DataBase[top] = DB_Module.open(DB_Name, "c")
DB_sync_list.append(db)
return db, "c"
except TypeError:
print("DataBase =", DataBase)
raise
def Reset():
"""Reset global state. Used by unit tests that end up using
SConsign multiple times to get a clean slate for each test."""
global sig_files, DB_sync_list
sig_files = []
DB_sync_list = []
normcase = os.path.normcase
def write():
if print_time():
start_time = time.perf_counter()
for sig_file in sig_files:
sig_file.write(sync=0)
for db in DB_sync_list:
try:
syncmethod = db.sync
except AttributeError:
pass # Not all dbm modules have sync() methods.
else:
syncmethod()
try:
closemethod = db.close
except AttributeError:
pass # Not all dbm modules have close() methods.
else:
closemethod()
if print_time():
elapsed = time.perf_counter() - start_time
print('Total SConsign sync time: %f seconds' % elapsed)
class SConsignEntry:
"""
Wrapper class for the generic entry in a .sconsign file.
The Node subclass populates it with attributes as it pleases.
XXX As coded below, we do expect a '.binfo' attribute to be added,
but we'll probably generalize this in the next refactorings.
"""
__slots__ = ("binfo", "ninfo", "__weakref__")
current_version_id = 2
def __init__(self):
# Create an object attribute from the class attribute so it ends up
# in the pickled data in the .sconsign file.
#_version_id = self.current_version_id
pass
def convert_to_sconsign(self):
self.binfo.convert_to_sconsign()
def convert_from_sconsign(self, dir, name):
self.binfo.convert_from_sconsign(dir, name)
def __getstate__(self):
state = getattr(self, '__dict__', {}).copy()
for obj in type(self).mro():
for name in getattr(obj, '__slots__', ()):
if hasattr(self, name):
state[name] = getattr(self, name)
state['_version_id'] = self.current_version_id
try:
del state['__weakref__']
except KeyError:
pass
return state
def __setstate__(self, state):
for key, value in state.items():
if key not in ('_version_id', '__weakref__'):
setattr(self, key, value)
class Base:
"""
This is the controlling class for the signatures for the collection of
entries associated with a specific directory. The actual directory
association will be maintained by a subclass that is specific to
the underlying storage method. This class provides a common set of
methods for fetching and storing the individual bits of information
that make up signature entry.
"""
def __init__(self):
self.entries = {}
self.dirty = False
self.to_be_merged = {}
def get_entry(self, filename):
"""
Fetch the specified entry attribute.
"""
return self.entries[filename]
def set_entry(self, filename, obj):
"""
Set the entry.
"""
self.entries[filename] = obj
self.dirty = True
def do_not_set_entry(self, filename, obj):
pass
def store_info(self, filename, node):
entry = node.get_stored_info()
entry.binfo.merge(node.get_binfo())
self.to_be_merged[filename] = node
self.dirty = True
def do_not_store_info(self, filename, node):
pass
def merge(self):
for key, node in self.to_be_merged.items():
entry = node.get_stored_info()
try:
ninfo = entry.ninfo
except AttributeError:
# This happens with SConf Nodes, because the configuration
# subsystem takes direct control over how the build decision
# is made and its information stored.
pass
else:
ninfo.merge(node.get_ninfo())
self.entries[key] = entry
self.to_be_merged = {}
class DB(Base):
"""
A Base subclass that reads and writes signature information
from a global .sconsign.db* file--the actual file suffix is
determined by the database module.
"""
def __init__(self, dir):
super().__init__()
self.dir = dir
db, mode = Get_DataBase(dir)
# Read using the path relative to the top of the Repository
# (self.dir.tpath) from which we're fetching the signature
# information.
path = normcase(dir.get_tpath())
try:
rawentries = db[path]
except KeyError:
pass
else:
try:
self.entries = pickle.loads(rawentries)
if not isinstance(self.entries, dict):
self.entries = {}
raise TypeError
except KeyboardInterrupt:
raise
except Exception as e:
SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
"Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.get_tpath(), e))
for key, entry in self.entries.items():
entry.convert_from_sconsign(dir, key)
if mode == "r":
# This directory is actually under a repository, which means
# likely they're reaching in directly for a dependency on
# a file there. Don't actually set any entry info, so we
# won't try to write to that .sconsign.dblite file.
self.set_entry = self.do_not_set_entry
self.store_info = self.do_not_store_info
sig_files.append(self)
def write(self, sync=1):
if not self.dirty:
return
self.merge()
db, mode = Get_DataBase(self.dir)
# Write using the path relative to the top of the SConstruct
# directory (self.dir.path), not relative to the top of
# the Repository; we only write to our own .sconsign file,
# not to .sconsign files in Repositories.
path = normcase(self.dir.get_internal_path())
for key, entry in self.entries.items():
entry.convert_to_sconsign()
db[path] = pickle.dumps(self.entries, PICKLE_PROTOCOL)
if sync:
try:
syncmethod = db.sync
except AttributeError:
# Not all anydbm modules have sync() methods.
pass
else:
syncmethod()
class Dir(Base):
def __init__(self, fp=None, dir=None):
"""fp - file pointer to read entries from."""
super().__init__()
if not fp:
return
self.entries = pickle.load(fp)
if not isinstance(self.entries, dict):
self.entries = {}
raise TypeError
if dir:
for key, entry in self.entries.items():
entry.convert_from_sconsign(dir, key)
class DirFile(Dir):
"""Encapsulates reading and writing a per-directory .sconsign file."""
def __init__(self, dir):
"""dir - the directory for the file."""
self.dir = dir
self.sconsign = os.path.join(dir.get_internal_path(), current_sconsign_filename())
try:
fp = open(self.sconsign, 'rb')
except OSError:
fp = None
try:
super().__init__(fp, dir)
except KeyboardInterrupt:
raise
except Exception:
SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
"Ignoring corrupt .sconsign file: %s"%self.sconsign)
try:
fp.close()
except AttributeError:
pass
sig_files.append(self)
def write(self, sync=1):
"""Write the .sconsign file to disk.
Try to write to a temporary file first, and rename it if we
succeed. If we can't write to the temporary file, it's
probably because the directory isn't writable (and if so,
how did we build anything in this directory, anyway?), so
try to write directly to the .sconsign file as a backup.
If we can't rename, try to copy the temporary contents back
to the .sconsign file. Either way, always try to remove
the temporary file at the end.
"""
if not self.dirty:
return
self.merge()
temp = os.path.join(self.dir.get_internal_path(), '.scons%d' % os.getpid())
try:
file = open(temp, 'wb')
fname = temp
except OSError:
try:
file = open(self.sconsign, 'wb')
fname = self.sconsign
except OSError:
return
for key, entry in self.entries.items():
entry.convert_to_sconsign()
pickle.dump(self.entries, file, PICKLE_PROTOCOL)
file.close()
if fname != self.sconsign:
try:
mode = os.stat(self.sconsign)[0]
os.chmod(self.sconsign, 0o666)
os.unlink(self.sconsign)
except OSError:
# Try to carry on in the face of either OSError
# (things like permission issues) or IOError (disk
# or network issues). If there's a really dangerous
# issue, it should get re-raised by the calls below.
pass
try:
os.rename(fname, self.sconsign)
except OSError:
# An OSError failure to rename may indicate something
# like the directory has no write permission, but
# the .sconsign file itself might still be writable,
# so try writing on top of it directly. An IOError
# here, or in any of the following calls, would get
# raised, indicating something like a potentially
# serious disk or network issue.
with open(self.sconsign, 'wb') as f, open(fname, 'rb') as f2:
f.write(f2.read())
os.chmod(self.sconsign, mode)
try:
os.unlink(temp)
except OSError:
pass
ForDirectory = DB
def File(name, dbm_module=None):
"""
Arrange for all signatures to be stored in a global .sconsign.db*
file.
"""
global ForDirectory, DB_Name, DB_Module
if name is None:
ForDirectory = DirFile
DB_Module = None
else:
ForDirectory = DB
DB_Name = name
if dbm_module is not None:
DB_Module = dbm_module
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,333 @@
# 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.
"""Dependency scanner for C/C++ code.
Two scanners are defined here: the default CScanner, and the optional
CConditionalScanner, which must be explicitly selected by calling
add_scanner() for each affected suffix.
"""
from typing import Dict
import SCons.Node.FS
import SCons.cpp
import SCons.Util
from . import ClassicCPP, FindPathDirs
class SConsCPPScanner(SCons.cpp.PreProcessor):
"""SCons-specific subclass of the cpp.py module's processing.
We subclass this so that: 1) we can deal with files represented
by Nodes, not strings; 2) we can keep track of the files that are
missing.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.missing = []
def initialize_result(self, fname):
self.result = SCons.Util.UniqueList([fname])
def finalize_result(self, fname):
return self.result[1:]
def find_include_file(self, t):
keyword, quote, fname = t
result = SCons.Node.FS.find_file(fname, self.searchpath[quote])
if not result:
self.missing.append((fname, self.current_file))
return result
def read_file(self, file):
try:
return file.rfile().get_text_contents()
except OSError as e:
self.missing.append((file, self.current_file))
return ''
def dictify_CPPDEFINES(env, replace = False):
"""Return CPPDEFINES converted to a dict for preprocessor emulation.
The concept is similar to :func:`~SCons.Defaults.processDefines`:
turn the values stored in an internal form in ``env['CPPDEFINES']``
into one needed for a specific context - in this case the cpp-like
work the C/C++ scanner will do. We can't reuse ``processDefines``
output as that's a list of strings for the command line. We also can't
pass the ``CPPDEFINES`` variable directly to the ``dict`` constructor,
as SCons allows it to be stored in several different ways - it's only
after ``Append`` and relatives has been called we know for sure it will
be a deque of tuples.
If requested (*replace* is true), simulate some of the macro
replacement that would take place if an actual preprocessor ran,
to avoid some conditional inclusions comeing out wrong. A bit
of an edge case, but does happen (GH #4623). See 6.10.5 in the C
standard and 15.6 in the C++ standard).
Args:
replace: if true, simulate macro replacement
.. versionchanged:: 4.9.0
Simple macro replacement added, and *replace* arg to enable it.
"""
def _replace(mapping):
"""Simplistic macro replacer for dictify_CPPDEFINES.
Scan *mapping* for a value that is the same as a key in the dict,
and replace with the value of that key; the process is repeated a few
times, but not forever in case someone left a case that can't be
fully resolved. This is a cheap approximation of the preprocessor's
macro replacement rules with no smarts - it doesn't "look inside"
the values, so only triggers on object-like macros, not on
function-like macros, and will not work on complex values, e.g.
a value like ``(1UL << PR_MTE_TCF_SHIFT)`` would not have
``PR_MTE_TCF_SHIFT`` replaced if that was also a key in ``CPPDEFINES``.
Args:
mapping: a dictionary representing macro names and replacements.
Returns:
a dictionary with replacements made.
"""
old_ns = mapping
loops = 0
while loops < 5: # don't recurse forever in case there's circular data
# this was originally written as a dict comprehension, but unrolling
# lets us add a finer-grained check for whether another loop is
# needed, rather than comparing two dicts to see if one changed.
again = False
ns = {}
for k, v in old_ns.items():
if v in old_ns:
ns[k] = old_ns[v]
if not again and ns[k] != v:
again = True
else:
ns[k] = v
if not again:
break
old_ns = ns
loops += 1
return ns
cppdefines = env.get('CPPDEFINES', {})
if not cppdefines:
return {}
if SCons.Util.is_Tuple(cppdefines):
# single macro defined in a tuple
try:
return {cppdefines[0]: cppdefines[1]}
except IndexError:
return {cppdefines[0]: None}
if SCons.Util.is_Sequence(cppdefines):
# multiple (presumably) macro defines in a deque, list, etc.
result = {}
for c in cppdefines:
if SCons.Util.is_Sequence(c):
try:
result[c[0]] = c[1]
except IndexError:
# could be a one-item sequence
result[c[0]] = None
elif SCons.Util.is_String(c):
try:
name, value = c.split('=')
result[name] = value
except ValueError:
result[c] = None
else:
# don't really know what to do here
result[c] = None
if replace:
return _replace(result)
return(result)
if SCons.Util.is_String(cppdefines):
# single macro define in a string
try:
name, value = cppdefines.split('=')
return {name: value}
except ValueError:
return {cppdefines: None}
if SCons.Util.is_Dict(cppdefines):
# already in the desired form
if replace:
return _replace(cppdefines)
return cppdefines
return {cppdefines: None}
class SConsCPPScannerWrapper:
"""The SCons wrapper around a cpp.py scanner.
This is the actual glue between the calling conventions of generic
SCons scanners, and the (subclass of) cpp.py class that knows how
to look for #include lines with reasonably real C-preprocessor-like
evaluation of #if/#ifdef/#else/#elif lines.
"""
def __init__(self, name, variable):
self.name = name
self.path = FindPathDirs(variable)
def __call__(self, node, env, path=()):
cpp = SConsCPPScanner(
current=node.get_dir(),
cpppath=path,
dict=dictify_CPPDEFINES(env, replace=True),
)
result = cpp(node)
for included, includer in cpp.missing:
SCons.Warnings.warn(
SCons.Warnings.DependencyWarning,
"No dependency generated for file: %s (included from: %s) "
"-- file not found" % (included, includer),
)
return result
def recurse_nodes(self, nodes):
return nodes
def select(self, node):
return self
def CScanner():
"""Return a prototype Scanner instance for scanning source files
that use the C pre-processor"""
# Here's how we would (or might) use the CPP scanner code above that
# knows how to evaluate #if/#ifdef/#else/#elif lines when searching
# for #includes. This is commented out for now until we add the
# right configurability to let users pick between the scanners.
# return SConsCPPScannerWrapper("CScanner", "CPPPATH")
cs = ClassicCPP(
"CScanner",
"$CPPSUFFIXES",
"CPPPATH",
r'^[ \t]*#[ \t]*(?:include|import)[ \t]*(<|")([^>"]+)(>|")',
)
return cs
#
# ConditionalScanner
#
class SConsCPPConditionalScanner(SCons.cpp.PreProcessor):
"""SCons-specific subclass of the cpp.py module's processing.
We subclass this so that: 1) we can deal with files represented
by Nodes, not strings; 2) we can keep track of the files that are
missing.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.missing = []
self._known_paths = []
def initialize_result(self, fname):
self.result = SCons.Util.UniqueList([fname])
def find_include_file(self, t):
keyword, quote, fname = t
paths = tuple(self._known_paths) + self.searchpath[quote]
if quote == '"':
paths = (self.current_file.dir,) + paths
result = SCons.Node.FS.find_file(fname, paths)
if result:
result_path = result.get_abspath()
for p in self.searchpath[quote]:
if result_path.startswith(p.get_abspath()):
self._known_paths.append(p)
break
else:
self.missing.append((fname, self.current_file))
return result
def read_file(self, file):
try:
return file.rfile().get_text_contents()
except OSError:
self.missing.append((file, self.current_file))
return ""
class SConsCPPConditionalScannerWrapper:
"""
The SCons wrapper around a cpp.py scanner.
This is the actual glue between the calling conventions of generic
SCons scanners, and the (subclass of) cpp.py class that knows how
to look for #include lines with reasonably real C-preprocessor-like
evaluation of #if/#ifdef/#else/#elif lines.
"""
def __init__(self, name, variable):
self.name = name
self.path = FindPathDirs(variable)
def __call__(self, node, env, path=(), depth=-1):
cpp = SConsCPPConditionalScanner(
current=node.get_dir(),
cpppath=path,
dict=dictify_CPPDEFINES(env),
depth=depth,
)
result = cpp(node)
for included, includer in cpp.missing:
fmt = "No dependency generated for file: %s (included from: %s) -- file not found"
SCons.Warnings.warn(
SCons.Warnings.DependencyWarning, fmt % (included, includer)
)
return result
def recurse_nodes(self, nodes):
return nodes
def select(self, node):
return self
def CConditionalScanner():
"""
Return an advanced conditional Scanner instance for scanning source files
Interprets C/C++ Preprocessor conditional syntax
(#ifdef, #if, defined, #else, #elif, etc.).
"""
return SConsCPPConditionalScannerWrapper("CConditionalScanner", "CPPPATH")
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,131 @@
# 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.Node.FS
from . import ScannerBase
def only_dirs(nodes):
is_Dir = lambda n: isinstance(n.disambiguate(), SCons.Node.FS.Dir)
return [node for node in nodes if is_Dir(node)]
def DirScanner(**kwargs):
"""Return a prototype Scanner instance for scanning
directories for on-disk files"""
kwargs['node_factory'] = SCons.Node.FS.Entry
kwargs['recursive'] = only_dirs
return ScannerBase(scan_on_disk, "DirScanner", **kwargs)
def DirEntryScanner(**kwargs):
"""Return a prototype Scanner instance for "scanning"
directory Nodes for their in-memory entries"""
kwargs['node_factory'] = SCons.Node.FS.Entry
kwargs['recursive'] = None
return ScannerBase(scan_in_memory, "DirEntryScanner", **kwargs)
skip_entry = {}
skip_entry_list = [
'.',
'..',
'.sconsign',
# Used by the native dblite.py module.
'.sconsign.dblite',
# Used by dbm and dumbdbm.
'.sconsign.dir',
# Used by dbm.
'.sconsign.pag',
# Used by dumbdbm.
'.sconsign.dat',
'.sconsign.bak',
# Used by some dbm emulations using Berkeley DB.
'.sconsign.db',
# new filenames since multiple hash formats allowed:
'.sconsign_md5.dblite',
'.sconsign_sha1.dblite',
'.sconsign_sha256.dblite',
# and all the duplicate files for each sub-sconsfile type
'.sconsign_md5',
'.sconsign_md5.dir',
'.sconsign_md5.pag',
'.sconsign_md5.dat',
'.sconsign_md5.bak',
'.sconsign_md5.db',
'.sconsign_sha1',
'.sconsign_sha1.dir',
'.sconsign_sha1.pag',
'.sconsign_sha1.dat',
'.sconsign_sha1.bak',
'.sconsign_sha1.db',
'.sconsign_sha256',
'.sconsign_sha256.dir',
'.sconsign_sha256.pag',
'.sconsign_sha256.dat',
'.sconsign_sha256.bak',
'.sconsign_sha256.db',
]
for skip in skip_entry_list:
skip_entry[skip] = 1
skip_entry[SCons.Node.FS._my_normcase(skip)] = 1
do_not_scan = lambda k: k not in skip_entry
def scan_on_disk(node, env, path=()):
"""
Scans a directory for on-disk files and directories therein.
Looking up the entries will add these to the in-memory Node tree
representation of the file system, so all we have to do is just
that and then call the in-memory scanning function.
"""
try:
flist = node.fs.listdir(node.get_abspath())
except OSError:
return []
e = node.Entry
for f in filter(do_not_scan, flist):
# Add ./ to the beginning of the file name so if it begins with a
# '#' we don't look it up relative to the top-level directory.
e('./' + f)
return scan_in_memory(node, env, path)
def scan_in_memory(node, env, path=()):
"""
"Scans" a Node.FS.Dir for its in-memory entries.
"""
try:
entries = node.entries
except AttributeError:
# It's not a Node.FS.Dir (or doesn't look enough like one for
# our purposes), which can happen if a target list containing
# mixed Node types (Dirs and Files, for example) has a Dir as
# the first entry.
return []
entry_list = sorted(filter(do_not_scan, list(entries.keys())))
return [entries[n] for n in entry_list]
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,114 @@
# 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.
"""Dependency scanner for program files."""
import SCons.Node
import SCons.Node.FS
import SCons.Util
from . import ScannerBase, FindPathDirs
# global, set by --debug=findlibs
print_find_libs = None
def ProgramScanner(**kwargs):
"""Return a prototype Scanner instance for scanning executable
files for static-lib dependencies"""
kwargs['path_function'] = FindPathDirs('LIBPATH')
ps = ScannerBase(scan, "ProgramScanner", **kwargs)
return ps
def _subst_libs(env, libs):
"""Substitute environment variables and split into list."""
if SCons.Util.is_String(libs):
libs = env.subst(libs)
if SCons.Util.is_String(libs):
libs = libs.split()
elif SCons.Util.is_Sequence(libs):
_libs = []
for l in libs:
_libs += _subst_libs(env, l)
libs = _libs
else:
# libs is an object (Node, for example)
libs = [libs]
return libs
def scan(node, env, libpath = ()):
"""Scans program files for static-library dependencies.
It will search the LIBPATH environment variable
for libraries specified in the LIBS variable, returning any
files it finds as dependencies.
"""
try:
libs = env['LIBS']
except KeyError:
# There are no LIBS in this environment, so just return a null list:
return []
libs = _subst_libs(env, libs)
try:
prefix = env['LIBPREFIXES']
if not SCons.Util.is_List(prefix):
prefix = [ prefix ]
except KeyError:
prefix = [ '' ]
try:
suffix = env['LIBSUFFIXES']
if not SCons.Util.is_List(suffix):
suffix = [ suffix ]
except KeyError:
suffix = [ '' ]
pairs = []
for suf in map(env.subst, suffix):
for pref in map(env.subst, prefix):
pairs.append((pref, suf))
result = []
if callable(libpath):
libpath = libpath()
find_file = SCons.Node.FS.find_file
adjustixes = SCons.Util.adjustixes
for lib in libs:
if SCons.Util.is_String(lib):
for pref, suf in pairs:
l = adjustixes(lib, pref, suf)
l = find_file(l, libpath, verbose=print_find_libs)
if l:
result.append(l)
else:
result.append(lib)
return result
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,57 @@
# 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.
"""Dependency scanner for RC (Interface Definition Language) files."""
import SCons.Node.FS
from . import ClassicCPP
def no_tlb(nodes):
"""Filter out .tlb files as they are binary and shouldn't be scanned."""
# print("Nodes:%s"%[str(n) for n in nodes])
return [n for n in nodes if str(n)[-4:] != '.tlb']
def RCScan():
"""Return a prototype Scanner instance for scanning RC source files"""
res_re = (
r'^(?:\s*#\s*(?:include)|'
r'.*?\s+(?:ICON|BITMAP|CURSOR|HTML|FONT|MESSAGETABLE|TYPELIB|REGISTRY|D3DFX)'
r'\s*.*?)'
r'\s*(<|"| )([^>"\s]+)(?:[>"\s])*$'
)
resScanner = ClassicCPP(
"ResourceScanner", "$RCSUFFIXES", "CPPPATH", res_re, recursive=no_tlb
)
return resScanner
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,438 @@
# 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.
"""The Scanner package for the SCons software construction utility."""
import re
import SCons.Node.FS
import SCons.PathList
import SCons.Util
class _Null:
pass
# This is used instead of None as a default argument value so None can be
# used as an actual argument value.
_null = _Null
def Scanner(function, *args, **kwargs):
"""Factory function to create a Scanner Object.
Creates the appropriate Scanner based on the type of "function".
TODO: Deprecate this some day. We've moved the functionality
inside the ScannerBase class and really don't need this factory function
any more. It was, however, used by some of our Tool modules, so
the call probably ended up in various people's custom modules
patterned on SCons code.
"""
if SCons.Util.is_Dict(function):
return Selector(function, *args, **kwargs)
return ScannerBase(function, *args, **kwargs)
class FindPathDirs:
"""Class to bind a specific E{*}PATH variable name to a function that
will return all of the E{*}path directories.
"""
def __init__(self, variable):
self.variable = variable
def __call__(self, env, dir=None, target=None, source=None, argument=None):
try:
path = env[self.variable]
except KeyError:
return ()
dir = dir or env.fs._cwd
path = SCons.PathList.PathList(path).subst_path(env, target, source)
return tuple(dir.Rfindalldirs(path))
class ScannerBase:
"""Base class for dependency scanners.
Implements straightforward, single-pass scanning of a single file.
A Scanner is usually set up with a scanner function (and optionally
a path function), but can also be a kind of dispatcher which
passes control to other Scanners.
A scanner function takes three arguments: a Node to scan for
dependecies, the construction environment to use, and an optional
tuple of paths (as generated by the optional path function).
It must return a list containing the Nodes for all the direct
dependencies of the file.
The optional path function is called to return paths that can be
searched for implicit dependency files. It takes five arguments:
a construction environment, a Node for the directory containing
the SConscript file that defined the primary target, a list of
target nodes, a list of source nodes, and the optional argument
for this instance.
Examples::
s = Scanner(my_scanner_function)
s = Scanner(function=my_scanner_function)
s = Scanner(function=my_scanner_function, argument='foo')
Args:
function: either a scanner function taking two or three arguments
and returning a list of File Nodes; or a mapping of keys to
other Scanner objects.
name: an optional name for identifying this scanner object
(defaults to "NONE").
argument: an optional argument that will be passed to both
*function* and *path_function*.
skeys: an optional list argument that can be used
to determine if this scanner can be used for a given Node.
In the case of File nodes, for example, the *skeys*
would be file suffixes.
path_function: an optional function which returns a tuple
of the directories that can be searched for implicit
dependency files. May also return a callable which
is called with no args and returns the tuple (supporting
Bindable class).
node_class: optional class of Nodes which this scan will return.
If not specified, defaults to :class:`SCons.Node.FS.Base`.
If *node_class* is ``None``, then this scanner will not enforce
any Node conversion and will return the raw results from *function*.
node_factory: optional factory function to be called to
translate the raw results returned by *function*
into the expected *node_class* objects.
scan_check: optional function to be called to first check whether
this node really needs to be scanned.
recursive: optional specifier of whether this scanner should be
invoked recursively on all of the implicit dependencies it returns
(for example `#include` lines in C source files, which may refer
to header files which should themselves be scanned).
May be a callable, which will be called to filter
the list of nodes found to select a subset for recursive
scanning (the canonical example being only recursively
scanning subdirectories within a directory). The default
is to not do recursive scanning.
"""
def __init__(
self,
function,
name="NONE",
argument=_null,
skeys=_null,
path_function=None,
# Node.FS.Base so that, by default, it's okay for a
# scanner to return a Dir, File or Entry.
node_class=SCons.Node.FS.Base,
node_factory=None,
scan_check=None,
recursive=None,
):
"""Construct a new scanner object given a scanner function."""
# Note: this class could easily work with scanner functions that take
# something other than a filename as an argument (e.g. a database
# node) and a dependencies list that aren't file names. All that
# would need to be changed is the documentation.
self.function = function
self.path_function = path_function
self.name = name
self.argument = argument
if skeys is _null:
if SCons.Util.is_Dict(function):
skeys = list(function.keys())
else:
skeys = []
self.skeys = skeys
self.node_class = node_class
self.node_factory = node_factory
self.scan_check = scan_check
if callable(recursive):
self.recurse_nodes = recursive
elif recursive:
self.recurse_nodes = self._recurse_all_nodes
else:
self.recurse_nodes = self._recurse_no_nodes
def path(self, env, dir=None, target=None, source=None):
if not self.path_function:
return ()
if self.argument is not _null:
return self.path_function(env, dir, target, source, self.argument)
return self.path_function(env, dir, target, source)
def __call__(self, node, env, path=()):
"""Scans a single object.
Args:
node: the node that will be passed to the scanner function
env: the environment that will be passed to the scanner function.
path: tuple of paths from the `path_function`
Returns:
A list of direct dependency nodes for the specified node.
"""
if self.scan_check and not self.scan_check(node, env):
return []
# here we may morph into a different Scanner instance:
self = self.select(node) # pylint: disable=self-cls-assignment
if self.argument is not _null:
node_list = self.function(node, env, path, self.argument)
else:
node_list = self.function(node, env, path)
kw = {}
if hasattr(node, 'dir'):
kw['directory'] = node.dir
conv = env.get_factory(self.node_factory)
cls = self.node_class
nl = [conv(n, **kw) if cls and not isinstance(n, cls) else n for n in node_list]
return nl
def __eq__(self, other):
try:
return self.__dict__ == other.__dict__
except AttributeError:
# other probably doesn't have a __dict__
return self.__dict__ == other
def __hash__(self):
return id(self)
def __str__(self):
return self.name
def add_skey(self, skey):
"""Add a skey to the list of skeys"""
self.skeys.append(skey)
def get_skeys(self, env=None):
if env and SCons.Util.is_String(self.skeys):
return env.subst_list(self.skeys)[0]
return self.skeys
def select(self, node):
if SCons.Util.is_Dict(self.function):
key = node.scanner_key()
try:
return self.function[key]
except KeyError:
return None
else:
return self
@staticmethod
def _recurse_all_nodes(nodes):
return nodes
@staticmethod
def _recurse_no_nodes(nodes):
return []
# recurse_nodes = _recurse_no_nodes
def add_scanner(self, skey, scanner):
self.function[skey] = scanner
self.add_skey(skey)
# keep the old name for a while in case external users are using.
# there are no more internal uses of this class by the name "Base"
Base = ScannerBase
class Selector(ScannerBase):
"""
A class for selecting a more specific scanner based on the
:func:`scanner_key` (suffix) for a specific Node.
TODO: This functionality has been moved into the inner workings of
the ScannerBase class, and this class will be deprecated at some point.
(It was never exposed directly as part of the public interface,
although it is used by the :func:`Scanner` factory function that was
used by various Tool modules and therefore was likely a template
for custom modules that may be out there.)
"""
def __init__(self, mapping, *args, **kwargs):
super().__init__(None, *args, **kwargs)
self.mapping = mapping
self.skeys = list(mapping.keys())
def __call__(self, node, env, path=()):
return self.select(node)(node, env, path)
def select(self, node):
try:
return self.mapping[node.scanner_key()]
except KeyError:
return None
def add_scanner(self, skey, scanner):
self.mapping[skey] = scanner
self.add_skey(skey)
class Current(ScannerBase):
"""
A class for scanning files that are source files (have no builder)
or are derived files and are current (which implies that they exist,
either locally or in a repository).
"""
def __init__(self, *args, **kwargs):
def current_check(node, env):
return not node.has_builder() or node.is_up_to_date()
kwargs['scan_check'] = current_check
super().__init__(*args, **kwargs)
class Classic(Current):
"""
A Scanner subclass to contain the common logic for classic CPP-style
include scanning, but which can be customized to use different
regular expressions to find the includes.
Note that in order for this to work "out of the box" (without
overriding the :meth:`find_include` and :meth:`sort_key1` methods),
the regular expression passed to the constructor must return the
name of the include file in group 0.
"""
def __init__(self, name, suffixes, path_variable, regex, *args, **kwargs):
self.cre = re.compile(regex, re.M)
def _scan(node, _, path=(), self=self):
node = node.rfile()
if not node.exists():
return []
return self.scan(node, path)
kwargs['function'] = _scan
kwargs['path_function'] = FindPathDirs(path_variable)
# Allow recursive to propagate if child class specifies.
# In this case resource scanner needs to specify a filter on which files
# get recursively processed. Previously was hardcoded to 1 instead of
# defaulted to 1.
kwargs['recursive'] = kwargs.get('recursive', True)
kwargs['skeys'] = suffixes
kwargs['name'] = name
super().__init__(*args, **kwargs)
@staticmethod
def find_include(include, source_dir, path):
n = SCons.Node.FS.find_file(include, (source_dir,) + tuple(path))
return n, include
@staticmethod
def sort_key(include):
return SCons.Node.FS._my_normcase(include)
def find_include_names(self, node):
return self.cre.findall(node.get_text_contents())
def scan(self, node, path=()):
# cache the includes list in node so we only scan it once:
if node.includes is not None:
includes = node.includes
else:
includes = self.find_include_names(node)
# Intern the names of the include files. Saves some memory
# if the same header is included many times.
node.includes = list(map(SCons.Util.silent_intern, includes))
# This is a hand-coded DSU (decorate-sort-undecorate, or
# Schwartzian transform) pattern. The sort key is the raw name
# of the file as specifed on the #include line (including the
# " or <, since that may affect what file is found), which lets
# us keep the sort order constant regardless of whether the file
# is actually found in a Repository or locally.
nodes = []
source_dir = node.get_dir()
if callable(path):
path = path()
for include in includes:
n, i = self.find_include(include, source_dir, path)
if n is None:
SCons.Warnings.warn(
SCons.Warnings.DependencyWarning,
"No dependency generated for file: %s "
"(included from: %s) -- file not found" % (i, node),
)
else:
nodes.append((self.sort_key(include), n))
return [pair[1] for pair in sorted(nodes)]
class ClassicCPP(Classic):
"""
A Classic Scanner subclass which takes into account the type of
bracketing used to include the file, and uses classic CPP rules
for searching for the files based on the bracketing.
Note that in order for this to work, the regular expression passed
to the constructor must return the leading bracket in group 0, and
the contained filename in group 1.
"""
@staticmethod
def find_include(include, source_dir, path):
include = list(map(SCons.Util.to_str, include))
if include[0] == '"':
paths = (source_dir,) + tuple(path)
else:
paths = tuple(path) + (source_dir,)
n = SCons.Node.FS.find_file(include[1], paths)
i = SCons.Util.silent_intern(include[1])
return n, i
@staticmethod
def sort_key(include):
return SCons.Node.FS._my_normcase(' '.join(include))
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,377 @@
# 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.
"""SCons interactive mode. """
# TODO:
#
# This has the potential to grow into something with a really big life
# of its own, which might or might not be a good thing. Nevertheless,
# here are some enhancements that will probably be requested some day
# and are worth keeping in mind (assuming this takes off):
#
# - A command to re-read / re-load the SConscript files. This may
# involve allowing people to specify command-line options (e.g. -f,
# -I, --no-site-dir) that affect how the SConscript files are read.
#
# - Additional command-line options on the "build" command.
#
# Of the supported options that seemed to make sense (after a quick
# pass through the list), the ones that seemed likely enough to be
# used are listed in the man page and have explicit test scripts.
#
# These had code changed in Script/Main.py to support them, but didn't
# seem likely to be used regularly, so had no test scripts added:
#
# build --diskcheck=*
# build --implicit-cache=*
# build --implicit-deps-changed=*
# build --implicit-deps-unchanged=*
#
# These look like they should "just work" with no changes to the
# existing code, but like those above, look unlikely to be used and
# therefore had no test scripts added:
#
# build --random
#
# These I'm not sure about. They might be useful for individual
# "build" commands, and may even work, but they seem unlikely enough
# that we'll wait until they're requested before spending any time on
# writing test scripts for them, or investigating whether they work.
#
# build -q [??? is there a useful analog to the exit status?]
# build --duplicate=
# build --profile=
# build --max-drift=
# build --warn=*
# build --Y
#
# - Most of the SCons command-line options that the "build" command
# supports should be settable as default options that apply to all
# subsequent "build" commands. Maybe a "set {option}" command that
# maps to "SetOption('{option}')".
#
# - Need something in the 'help' command that prints the -h output.
#
# - A command to run the configure subsystem separately (must see how
# this interacts with the new automake model).
#
# - Command-line completion of target names; maybe even of SCons options?
# Completion is something that's supported by the Python cmd module,
# so this should be doable without too much trouble.
#
import cmd
import copy
import os
import re
import shlex
import sys
try:
import readline
except ImportError:
pass
class SConsInteractiveCmd(cmd.Cmd):
"""\
build [TARGETS] Build the specified TARGETS and their dependencies. 'b' is a synonym.
clean [TARGETS] Clean (remove) the specified TARGETS and their dependencies. 'c' is a synonym.
exit Exit SCons interactive mode.
help [COMMAND] Prints help for the specified COMMAND. 'h' and '?' are synonyms.
shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and '!' are synonyms.
version Prints SCons version information.
"""
synonyms = {
'b' : 'build',
'c' : 'clean',
'h' : 'help',
'scons' : 'build',
'sh' : 'shell',
}
def __init__(self, **kw):
cmd.Cmd.__init__(self)
for key, val in kw.items():
setattr(self, key, val)
if sys.platform == 'win32':
self.shell_variable = 'COMSPEC'
else:
self.shell_variable = 'SHELL'
def default(self, argv):
print("*** Unknown command: %s" % argv[0])
def onecmd(self, line):
line = line.strip()
if not line:
print(self.lastcmd)
return self.emptyline()
self.lastcmd = line
if line[0] == '!':
line = 'shell ' + line[1:]
elif line[0] == '?':
line = 'help ' + line[1:]
if os.sep == '\\':
line = line.replace('\\', '\\\\')
argv = shlex.split(line)
argv[0] = self.synonyms.get(argv[0], argv[0])
if not argv[0]:
return self.default(line)
else:
try:
func = getattr(self, 'do_' + argv[0])
except AttributeError:
return self.default(argv)
return func(argv)
def do_build(self, argv):
"""\
build [TARGETS] Build the specified TARGETS and their
dependencies. 'b' is a synonym.
"""
import SCons.Node
import SCons.SConsign
import SCons.Script.Main
options = copy.deepcopy(self.options)
options, targets = self.parser.parse_args(argv[1:], values=options)
SCons.Script.COMMAND_LINE_TARGETS = targets
if targets:
SCons.Script.BUILD_TARGETS = targets
else:
# If the user didn't specify any targets on the command line,
# use the list of default targets.
SCons.Script.BUILD_TARGETS = SCons.Script._build_plus_default
nodes = SCons.Script.Main._build_targets(self.fs,
options,
targets,
self.target_top)
if not nodes:
return
# Call each of the Node's alter_targets() methods, which may
# provide additional targets that ended up as part of the build
# (the canonical example being a VariantDir() when we're building
# from a source directory) and which we therefore need their
# state cleared, too.
x = []
for n in nodes:
x.extend(n.alter_targets()[0])
nodes.extend(x)
# Clean up so that we can perform the next build correctly.
#
# We do this by walking over all the children of the targets,
# and clearing their state.
#
# We currently have to re-scan each node to find their
# children, because built nodes have already been partially
# cleared and don't remember their children. (In scons
# 0.96.1 and earlier, this wasn't the case, and we didn't
# have to re-scan the nodes.)
#
# Because we have to re-scan each node, we can't clear the
# nodes as we walk over them, because we may end up rescanning
# a cleared node as we scan a later node. Therefore, only
# store the list of nodes that need to be cleared as we walk
# the tree, and clear them in a separate pass.
#
# XXX: Someone more familiar with the inner workings of scons
# may be able to point out a more efficient way to do this.
SCons.Script.Main.progress_display("scons: Clearing cached node information ...")
seen_nodes = {}
def get_unseen_children(node, parent, seen_nodes=seen_nodes):
def is_unseen(node, seen_nodes=seen_nodes):
return node not in seen_nodes
return [child for child in node.children(scan=1) if is_unseen(child)]
def add_to_seen_nodes(node, parent, seen_nodes=seen_nodes):
seen_nodes[node] = 1
# If this file is in a VariantDir and has a
# corresponding source file in the source tree, remember the
# node in the source tree, too. This is needed in
# particular to clear cached implicit dependencies on the
# source file, since the scanner will scan it if the
# VariantDir was created with duplicate=0.
try:
rfile_method = node.rfile
except AttributeError:
return
else:
rfile = rfile_method()
if rfile != node:
seen_nodes[rfile] = 1
for node in nodes:
walker = SCons.Node.Walker(node,
kids_func=get_unseen_children,
eval_func=add_to_seen_nodes)
n = walker.get_next()
while n:
n = walker.get_next()
for node in seen_nodes.keys():
# Call node.clear() to clear most of the state
node.clear()
# node.clear() doesn't reset node.state, so call
# node.set_state() to reset it manually
node.set_state(SCons.Node.no_state)
node.implicit = None
# Debug: Uncomment to verify that all Taskmaster reference
# counts have been reset to zero.
#if node.ref_count != 0:
# from SCons.Debug import Trace
# Trace('node %s, ref_count %s !!!\n' % (node, node.ref_count))
# TODO: REMOVE WPD DEBUG 02/14/2022
# This call was clearing the list of sconsign files to be written, so it would
# only write the results of the first build command. All others wouldn't be written
# to .SConsign.
# Pretty sure commenting this out is the correct fix.
# SCons.SConsign.Reset()
SCons.Script.Main.progress_display("scons: done clearing node information.")
def do_clean(self, argv):
"""\
clean [TARGETS] Clean (remove) the specified TARGETS
and their dependencies. 'c' is a synonym.
"""
return self.do_build(['build', '--clean'] + argv[1:])
def do_EOF(self, argv):
print()
self.do_exit(argv)
def _do_one_help(self, arg):
try:
# If help_<arg>() exists, then call it.
func = getattr(self, 'help_' + arg)
except AttributeError:
try:
func = getattr(self, 'do_' + arg)
except AttributeError:
doc = None
else:
doc = self._doc_to_help(func)
if doc:
sys.stdout.write(doc + '\n')
sys.stdout.flush()
else:
doc = self.strip_initial_spaces(func())
if doc:
sys.stdout.write(doc + '\n')
sys.stdout.flush()
def _doc_to_help(self, obj):
doc = obj.__doc__
if doc is None:
return ''
return self._strip_initial_spaces(doc)
def _strip_initial_spaces(self, s):
lines = s.split('\n')
spaces = re.match(' *', lines[0]).group(0)
def strip_spaces(l, spaces=spaces):
if l[:len(spaces)] == spaces:
l = l[len(spaces):]
return l
lines = list(map(strip_spaces, lines))
return '\n'.join(lines)
def do_exit(self, argv):
"""\
exit Exit SCons interactive mode.
"""
sys.exit(0)
def do_help(self, argv):
"""\
help [COMMAND] Prints help for the specified COMMAND. 'h'
and '?' are synonyms.
"""
if argv[1:]:
for arg in argv[1:]:
if self._do_one_help(arg):
break
else:
# If bare 'help' is called, print this class's doc
# string (if it has one).
doc = self._doc_to_help(self.__class__)
if doc:
sys.stdout.write(doc + '\n')
sys.stdout.flush()
def do_shell(self, argv):
"""\
shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and
'!' are synonyms.
"""
import subprocess
argv = argv[1:]
if not argv:
argv = os.environ[self.shell_variable]
try:
# Per "[Python-Dev] subprocess insufficiently platform-independent?"
# https://mail.python.org/pipermail/python-dev/2008-August/081979.html "+
# Doing the right thing with an argument list currently
# requires different shell= values on Windows and Linux.
p = subprocess.Popen(argv, shell=(sys.platform=='win32'))
except OSError as e:
sys.stderr.write('scons: %s: %s\n' % (argv[0], e.strerror))
else:
p.wait()
def do_version(self, argv):
"""\
version Prints SCons version information.
"""
sys.stdout.write(self.parser.version + '\n')
def interact(fs, parser, options, targets, target_top):
c = SConsInteractiveCmd(prompt = 'scons>>> ',
fs = fs,
parser = parser,
options = options,
targets = targets,
target_top = target_top)
c.cmdloop()
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,713 @@
# 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 defines the Python API provided to SConscript files."""
from __future__ import annotations
import SCons
import SCons.Action
import SCons.Builder
import SCons.Defaults
import SCons.Environment
import SCons.Errors
import SCons.Node
import SCons.Node.Alias
import SCons.Node.FS
import SCons.Platform
import SCons.SConf
import SCons.Tool
from SCons.Util import is_List, is_String, is_Dict, flatten
from SCons.Node import SConscriptNodes
from . import Main
import os
import os.path
import re
import sys
import traceback
import time
class SConscriptReturn(Exception):
pass
launch_dir = os.path.abspath(os.curdir)
GlobalDict = None
# global exports set by Export():
global_exports = {}
# chdir flag
sconscript_chdir: bool = True
def get_calling_namespaces():
"""Return the locals and globals for the function that called
into this module in the current call stack."""
try: 1//0
except ZeroDivisionError:
# Don't start iterating with the current stack-frame to
# prevent creating reference cycles (f_back is safe).
frame = sys.exc_info()[2].tb_frame.f_back
# Find the first frame that *isn't* from this file. This means
# that we expect all of the SCons frames that implement an Export()
# or SConscript() call to be in this file, so that we can identify
# the first non-Script.SConscript frame as the user's local calling
# environment, and the locals and globals dictionaries from that
# frame as the calling namespaces. See the comment below preceding
# the DefaultEnvironmentCall block for even more explanation.
while frame.f_globals.get("__name__") == __name__:
frame = frame.f_back
return frame.f_locals, frame.f_globals
def compute_exports(exports):
"""Compute a dictionary of exports given one of the parameters
to the Export() function or the exports argument to SConscript()."""
loc, glob = get_calling_namespaces()
retval = {}
try:
for export in exports:
if is_Dict(export):
retval.update(export)
else:
try:
retval[export] = loc[export]
except KeyError:
retval[export] = glob[export]
except KeyError as x:
raise SCons.Errors.UserError("Export of non-existent variable '%s'"%x)
return retval
class Frame:
"""A frame on the SConstruct/SConscript call stack"""
def __init__(self, fs, exports, sconscript):
self.globals = BuildDefaultGlobals()
self.retval = None
self.prev_dir = fs.getcwd()
self.exports = compute_exports(exports) # exports from the calling SConscript
# make sure the sconscript attr is a Node.
if isinstance(sconscript, SCons.Node.Node):
self.sconscript = sconscript
elif sconscript == '-':
self.sconscript = None
else:
self.sconscript = fs.File(str(sconscript))
# the SConstruct/SConscript call stack:
call_stack = []
# For documentation on the methods in this file, see the scons man-page
def Return(*vars, **kw):
retval = []
try:
fvars = flatten(vars)
for var in fvars:
for v in var.split():
retval.append(call_stack[-1].globals[v])
except KeyError as x:
raise SCons.Errors.UserError("Return of non-existent variable '%s'"%x)
if len(retval) == 1:
call_stack[-1].retval = retval[0]
else:
call_stack[-1].retval = tuple(retval)
stop = kw.get('stop', True)
if stop:
raise SConscriptReturn
stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
def handle_missing_SConscript(f, must_exist = True):
"""Take appropriate action on missing file in SConscript() call.
Print a warning or raise an exception on missing file, unless
missing is explicitly allowed by the *must_exist* parameter or by
a global flag.
Args:
f: path to missing configuration file
must_exist: if true (the default), fail. If false
do nothing, allowing a build to declare it's okay to be missing.
Raises:
UserError: if *must_exist* is true or if global
:data:`SCons.Script._no_missing_sconscript` is true.
.. versionchanged: 4.6.0
Changed default from False.
"""
if not must_exist: # explicitly set False: ok
return
if not SCons.Script._no_missing_sconscript: # system default changed: ok
return
msg = f"missing SConscript file {f.get_internal_path()!r}"
raise SCons.Errors.UserError(msg)
def _SConscript(fs, *files, **kw):
top = fs.Top
sd = fs.SConstruct_dir.rdir()
exports = kw.get('exports', [])
# evaluate each SConscript file
results = []
for fn in files:
call_stack.append(Frame(fs, exports, fn))
old_sys_path = sys.path
try:
SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1
if fn == "-":
exec(sys.stdin.read(), call_stack[-1].globals)
else:
if isinstance(fn, SCons.Node.Node):
f = fn
else:
f = fs.File(str(fn))
_file_ = None
SConscriptNodes.add(f)
# Change directory to the top of the source
# tree to make sure the os's cwd and the cwd of
# fs match so we can open the SConscript.
fs.chdir(top, change_os_dir=True)
if f.rexists():
actual = f.rfile()
_file_ = open(actual.get_abspath(), "rb")
elif f.srcnode().rexists():
actual = f.srcnode().rfile()
_file_ = open(actual.get_abspath(), "rb")
elif f.has_src_builder():
# The SConscript file apparently exists in a source
# code management system. Build it, but then clear
# the builder so that it doesn't get built *again*
# during the actual build phase.
f.build()
f.built()
f.builder_set(None)
if f.exists():
_file_ = open(f.get_abspath(), "rb")
if _file_:
# Chdir to the SConscript directory. Use a path
# name relative to the SConstruct file so that if
# we're using the -f option, we're essentially
# creating a parallel SConscript directory structure
# in our local directory tree.
#
# XXX This is broken for multiple-repository cases
# where the SConstruct and SConscript files might be
# in different Repositories. For now, cross that
# bridge when someone comes to it.
try:
src_dir = kw['src_dir']
except KeyError:
ldir = fs.Dir(f.dir.get_path(sd))
else:
ldir = fs.Dir(src_dir)
if not ldir.is_under(f.dir):
# They specified a source directory, but
# it's above the SConscript directory.
# Do the sensible thing and just use the
# SConcript directory.
ldir = fs.Dir(f.dir.get_path(sd))
try:
fs.chdir(ldir, change_os_dir=sconscript_chdir)
except OSError:
# There was no local directory, so we should be
# able to chdir to the Repository directory.
# Note that we do this directly, not through
# fs.chdir(), because we still need to
# interpret the stuff within the SConscript file
# relative to where we are logically.
fs.chdir(ldir, change_os_dir=False)
os.chdir(actual.dir.get_abspath())
# Append the SConscript directory to the beginning
# of sys.path so Python modules in the SConscript
# directory can be easily imported.
sys.path = [ f.dir.get_abspath() ] + sys.path
# This is the magic line that actually reads up
# and executes the stuff in the SConscript file.
# The locals for this frame contain the special
# bottom-of-the-stack marker so that any
# exceptions that occur when processing this
# SConscript can base the printed frames at this
# level and not show SCons internals as well.
call_stack[-1].globals.update({stack_bottom:1})
old_file = call_stack[-1].globals.get('__file__')
try:
del call_stack[-1].globals['__file__']
except KeyError:
pass
try:
try:
if Main.print_time:
start_time = time.perf_counter()
scriptdata = _file_.read()
scriptname = _file_.name
_file_.close()
if SCons.Debug.sconscript_trace:
print("scons: Entering "+str(scriptname))
exec(compile(scriptdata, scriptname, 'exec'), call_stack[-1].globals)
if SCons.Debug.sconscript_trace:
print("scons: Exiting "+str(scriptname))
except SConscriptReturn:
if SCons.Debug.sconscript_trace:
print("scons: Exiting "+str(scriptname))
else:
pass
finally:
if Main.print_time:
elapsed = time.perf_counter() - start_time
print('SConscript:%s took %0.3f ms' % (f.get_abspath(), elapsed * 1000.0))
if old_file is not None:
call_stack[-1].globals.update({__file__:old_file})
else:
handle_missing_SConscript(f, kw.get('must_exist', True))
finally:
SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
sys.path = old_sys_path
frame = call_stack.pop()
try:
fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
except OSError:
# There was no local directory, so chdir to the
# Repository directory. Like above, we do this
# directly.
fs.chdir(frame.prev_dir, change_os_dir=False)
rdir = frame.prev_dir.rdir()
rdir._create() # Make sure there's a directory there.
try:
os.chdir(rdir.get_abspath())
except OSError as e:
# We still couldn't chdir there, so raise the error,
# but only if actions are being executed.
#
# If the -n option was used, the directory would *not*
# have been created and we should just carry on and
# let things muddle through. This isn't guaranteed
# to work if the SConscript files are reading things
# from disk (for example), but it should work well
# enough for most configurations.
if SCons.Action.execute_actions:
raise e
results.append(frame.retval)
# if we only have one script, don't return a tuple
if len(results) == 1:
return results[0]
else:
return tuple(results)
def SConscript_exception(file=sys.stderr):
"""Print an exception stack trace just for the SConscript file(s).
This will show users who have Python errors where the problem is,
without cluttering the output with all of the internal calls leading
up to where we exec the SConscript."""
exc_type, exc_value, exc_tb = sys.exc_info()
tb = exc_tb
while tb and stack_bottom not in tb.tb_frame.f_locals:
tb = tb.tb_next
if not tb:
# We did not find our exec statement, so this was actually a bug
# in SCons itself. Show the whole stack.
tb = exc_tb
stack = traceback.extract_tb(tb)
try:
type = exc_type.__name__
except AttributeError:
type = str(exc_type)
if type[:11] == "exceptions.":
type = type[11:]
file.write('%s: %s:\n' % (type, exc_value))
for fname, line, func, text in stack:
file.write(' File "%s", line %d:\n' % (fname, line))
file.write(' %s\n' % text)
def annotate(node):
"""Annotate a node with the stack frame describing the
SConscript file and line number that created it."""
tb = sys.exc_info()[2]
while tb and stack_bottom not in tb.tb_frame.f_locals:
tb = tb.tb_next
if not tb:
# We did not find any exec of an SConscript file: what?!
raise SCons.Errors.InternalError("could not find SConscript stack frame")
node.creator = traceback.extract_stack(tb)[0]
# The following line would cause each Node to be annotated using the
# above function. Unfortunately, this is a *huge* performance hit, so
# leave this disabled until we find a more efficient mechanism.
#SCons.Node.Annotate = annotate
class SConsEnvironment(SCons.Environment.Base):
"""An Environment subclass that contains all of the methods that
are particular to the wrapper SCons interface and which aren't
(or shouldn't be) part of the build engine itself.
Note that not all of the methods of this class have corresponding
global functions, there are some private methods.
"""
#
# Private methods of an SConsEnvironment.
#
@staticmethod
def _get_major_minor_revision(version_string):
"""Split a version string into major, minor and (optionally)
revision parts.
This is complicated by the fact that a version string can be
something like 3.2b1."""
version = version_string.split(' ')[0].split('.')
v_major = int(version[0])
v_minor = int(re.match(r'\d+', version[1]).group())
if len(version) >= 3:
v_revision = int(re.match(r'\d+', version[2]).group())
else:
v_revision = 0
return v_major, v_minor, v_revision
def _get_SConscript_filenames(self, ls, kw):
"""
Convert the parameters passed to SConscript() calls into a list
of files and export variables. If the parameters are invalid,
throws SCons.Errors.UserError. Returns a tuple (l, e) where l
is a list of SConscript filenames and e is a list of exports.
"""
exports = []
if len(ls) == 0:
try:
dirs = kw["dirs"]
except KeyError:
raise SCons.Errors.UserError("Invalid SConscript usage - no parameters")
if not is_List(dirs):
dirs = [ dirs ]
dirs = list(map(str, dirs))
name = kw.get('name', 'SConscript')
files = [os.path.join(n, name) for n in dirs]
elif len(ls) == 1:
files = ls[0]
elif len(ls) == 2:
files = ls[0]
exports = self.Split(ls[1])
else:
raise SCons.Errors.UserError("Invalid SConscript() usage - too many arguments")
if not is_List(files):
files = [ files ]
if kw.get('exports'):
exports.extend(self.Split(kw['exports']))
variant_dir = kw.get('variant_dir')
if variant_dir:
if len(files) != 1:
raise SCons.Errors.UserError("Invalid SConscript() usage - can only specify one SConscript with a variant_dir")
duplicate = kw.get('duplicate', 1)
src_dir = kw.get('src_dir')
if not src_dir:
src_dir, fname = os.path.split(str(files[0]))
files = [os.path.join(str(variant_dir), fname)]
else:
if not isinstance(src_dir, SCons.Node.Node):
src_dir = self.fs.Dir(src_dir)
fn = files[0]
if not isinstance(fn, SCons.Node.Node):
fn = self.fs.File(fn)
if fn.is_under(src_dir):
# Get path relative to the source directory.
fname = fn.get_path(src_dir)
files = [os.path.join(str(variant_dir), fname)]
else:
files = [fn.get_abspath()]
kw['src_dir'] = variant_dir
self.fs.VariantDir(variant_dir, src_dir, duplicate)
return (files, exports)
#
# Public methods of an SConsEnvironment. These get
# entry points in the global namespace so they can be called
# as global functions.
#
def Configure(self, *args, **kw):
if not SCons.Script.sconscript_reading:
raise SCons.Errors.UserError("Calling Configure from Builders is not supported.")
kw['_depth'] = kw.get('_depth', 0) + 1
return SCons.Environment.Base.Configure(self, *args, **kw)
def Default(self, *targets):
SCons.Script._Set_Default_Targets(self, targets)
@staticmethod
def GetSConsVersion():
"""Return the current SCons version.
.. versionadded:: 4.8.0
"""
return SConsEnvironment._get_major_minor_revision(SCons.__version__)
@staticmethod
def EnsureSConsVersion(major, minor, revision = 0):
"""Exit abnormally if the SCons version is not late enough."""
# split string to avoid replacement during build process
if SCons.__version__ == '__' + 'VERSION__':
SCons.Warnings.warn(SCons.Warnings.DevelopmentVersionWarning,
"EnsureSConsVersion is ignored for development version")
return
if SConsEnvironment.GetSConsVersion() < (major, minor, revision):
if revision:
scons_ver_string = '%d.%d.%d' % (major, minor, revision)
else:
scons_ver_string = '%d.%d' % (major, minor)
print("SCons %s or greater required, but you have SCons %s" % \
(scons_ver_string, SCons.__version__))
sys.exit(2)
@staticmethod
def EnsurePythonVersion(major, minor):
"""Exit abnormally if the Python version is not late enough."""
if sys.version_info < (major, minor):
v = sys.version.split()[0]
print("Python %d.%d or greater required, but you have Python %s" %(major,minor,v))
sys.exit(2)
@staticmethod
def Exit(value=0):
sys.exit(value)
def Export(self, *vars, **kw):
for var in vars:
global_exports.update(compute_exports(self.Split(var)))
global_exports.update(kw)
@staticmethod
def GetLaunchDir():
global launch_dir
return launch_dir
def GetOption(self, name):
name = self.subst(name)
return SCons.Script.Main.GetOption(name)
def Help(self, text, append = False, local_only = False):
"""Update the help text.
The previous help text has *text* appended to it, except on the
first call. On first call, the values of *append* and *local_only*
are considered to determine what is appended to.
Arguments:
text: string to add to the help text.
append: on first call, if true, keep the existing help text
(default False).
local_only: on first call, if true and *append* is also true,
keep only the help text from AddOption calls.
.. versionchanged:: 4.6.0
The *keep_local* parameter was added.
.. versionchanged:: 4.9.0
The *keep_local* parameter was renamed *local_only* to match manpage
"""
text = self.subst(text, raw=1)
SCons.Script.HelpFunction(text, append=append, local_only=local_only)
def Import(self, *vars):
try:
frame = call_stack[-1]
globals = frame.globals
exports = frame.exports
for var in vars:
var = self.Split(var)
for v in var:
if v == '*':
globals.update(global_exports)
globals.update(exports)
else:
if v in exports:
globals[v] = exports[v]
else:
globals[v] = global_exports[v]
except KeyError as x:
raise SCons.Errors.UserError("Import of non-existent variable '%s'"%x)
def SConscript(self, *ls, **kw):
"""Execute SCons configuration files.
Parameters:
*ls (str or list): configuration file(s) to execute.
Keyword arguments:
dirs (list): execute SConscript in each listed directory.
name (str): execute script 'name' (used only with 'dirs').
exports (list or dict): locally export variables the
called script(s) can import.
variant_dir (str): mirror sources needed for the build in
a variant directory to allow building in it.
duplicate (bool): physically duplicate sources instead of just
adjusting paths of derived files (used only with 'variant_dir')
(default is True).
must_exist (bool): fail if a requested script is missing
(default is False, default is deprecated).
Returns:
list of variables returned by the called script
Raises:
UserError: a script is not found and such exceptions are enabled.
"""
def subst_element(x, subst=self.subst):
if SCons.Util.is_List(x):
x = list(map(subst, x))
else:
x = subst(x)
return x
ls = list(map(subst_element, ls))
subst_kw = {}
for key, val in kw.items():
if is_String(val):
val = self.subst(val)
elif SCons.Util.is_List(val):
val = [self.subst(v) if is_String(v) else v for v in val]
subst_kw[key] = val
files, exports = self._get_SConscript_filenames(ls, subst_kw)
subst_kw['exports'] = exports
return _SConscript(self.fs, *files, **subst_kw)
@staticmethod
def SConscriptChdir(flag):
global sconscript_chdir
sconscript_chdir = flag
def SetOption(self, name, value):
name = self.subst(name)
SCons.Script.Main.SetOption(name, value)
#
#
#
SCons.Environment.Environment = SConsEnvironment
def Configure(*args, **kw):
if not SCons.Script.sconscript_reading:
raise SCons.Errors.UserError("Calling Configure from Builders is not supported.")
kw['_depth'] = 1
return SCons.SConf.SConf(*args, **kw)
# It's very important that the DefaultEnvironmentCall() class stay in this
# file, with the get_calling_namespaces() function, the compute_exports()
# function, the Frame class and the SConsEnvironment.Export() method.
# These things make up the calling stack leading up to the actual global
# Export() or SConscript() call that the user issued. We want to allow
# users to export local variables that they define, like so:
#
# def func():
# x = 1
# Export('x')
#
# To support this, the get_calling_namespaces() function assumes that
# the *first* stack frame that's not from this file is the local frame
# for the Export() or SConscript() call.
_DefaultEnvironmentProxy = None
def get_DefaultEnvironmentProxy():
global _DefaultEnvironmentProxy
if not _DefaultEnvironmentProxy:
default_env = SCons.Defaults.DefaultEnvironment()
_DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env)
return _DefaultEnvironmentProxy
class DefaultEnvironmentCall:
"""A class that implements "global function" calls of
Environment methods by fetching the specified method from the
DefaultEnvironment's class. Note that this uses an intermediate
proxy class instead of calling the DefaultEnvironment method
directly so that the proxy can override the subst() method and
thereby prevent expansion of construction variables (since from
the user's point of view this was called as a global function,
with no associated construction environment)."""
def __init__(self, method_name, subst=0):
self.method_name = method_name
if subst:
self.factory = SCons.Defaults.DefaultEnvironment
else:
self.factory = get_DefaultEnvironmentProxy
def __call__(self, *args, **kw):
env = self.factory()
method = getattr(env, self.method_name)
return method(*args, **kw)
def BuildDefaultGlobals():
"""
Create a dictionary containing all the default globals for
SConstruct and SConscript files.
"""
global GlobalDict
if GlobalDict is None:
GlobalDict = {}
import SCons.Script
d = SCons.Script.__dict__
def not_a_module(m, d=d, mtype=type(SCons.Script)):
return not isinstance(d[m], mtype)
for m in filter(not_a_module, dir(SCons.Script)):
GlobalDict[m] = d[m]
return GlobalDict.copy()
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,498 @@
# 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.
"""The main() function used by the scons script.
Architecturally, this *is* the scons script, and will likely only be
called from the external "scons" wrapper. Consequently, anything here
should not be, or be considered, part of the build engine. If it's
something that we expect other software to want to use, it should go in
some other module. If it's specific to the "scons" script invocation,
it goes here.
"""
from __future__ import annotations
import collections
import itertools
import os
import sys
import time
from io import StringIO
start_time = time.time()
# Special chicken-and-egg handling of the "--debug=memoizer" flag:
#
# SCons.Memoize contains a metaclass implementation that affects how
# the other classes are instantiated. The Memoizer may add shim methods
# to classes that have methods that cache computed values in order to
# count and report the hits and misses.
#
# If we wait to enable the Memoization until after we've parsed the
# command line options normally, it will be too late, because the Memoizer
# will have already analyzed the classes that it's Memoizing and decided
# to not add the shims. So we use a special-case, up-front check for
# the "--debug=memoizer" flag and enable Memoizer before we import any
# of the other modules that use it.
# Update: this breaks if the option isn't exactly "--debug=memoizer",
# like if there is more than one debug option as a csv. Do a bit more work.
_args = sys.argv + os.environ.get("SCONSFLAGS", "").split()
_args = (
arg[len("--debug=") :].split(",")
for arg in _args
if arg.startswith("--debug=")
)
_args = list(itertools.chain.from_iterable(_args))
if "memoizer" in _args:
import SCons.Memoize
import SCons.Warnings
try:
SCons.Memoize.EnableMemoization()
except SCons.Warnings.SConsWarning:
# Some warning was thrown. Arrange for it to be displayed
# or not after warnings are configured.
from . import Main
exc_type, exc_value, tb = sys.exc_info()
Main.delayed_warnings.append((exc_type, exc_value))
del _args
import SCons.Action
import SCons.Builder
import SCons.Environment
import SCons.Node.FS
import SCons.Platform
import SCons.Platform.virtualenv
import SCons.Scanner
import SCons.SConf
import SCons.Subst
import SCons.Tool
import SCons.Util
import SCons.Variables
import SCons.Defaults
from . import Main
main = Main.main
# The following are global class definitions and variables that used to
# live directly in this module back before 0.96.90, when it contained
# a lot of code. Some SConscript files in widely-distributed packages
# (Blender is the specific example) actually reached into SCons.Script
# directly to use some of these. Rather than break those SConscript
# files, we're going to propagate these names into the SCons.Script
# namespace here.
#
# Some of these are commented out because it's *really* unlikely anyone
# used them, but we're going to leave the comment here to try to make
# it obvious what to do if the situation arises.
BuildTask = Main.BuildTask
CleanTask = Main.CleanTask
QuestionTask = Main.QuestionTask
#SConscriptSettableOptions = Main.SConscriptSettableOptions
AddOption = Main.AddOption
PrintHelp = Main.PrintHelp
GetOption = Main.GetOption
SetOption = Main.SetOption
ValidateOptions = Main.ValidateOptions
Progress = Main.Progress
GetBuildFailures = Main.GetBuildFailures
DebugOptions = Main.DebugOptions
#keep_going_on_error = Main.keep_going_on_error
#print_dtree = Main.print_dtree
#print_explanations = Main.print_explanations
#print_includes = Main.print_includes
#print_objects = Main.print_objects
#print_time = Main.print_time
#print_tree = Main.print_tree
#memory_stats = Main.memory_stats
#ignore_errors = Main.ignore_errors
#sconscript_time = Main.sconscript_time
#command_time = Main.command_time
#exit_status = Main.exit_status
#profiling = Main.profiling
#repositories = Main.repositories
from . import SConscript as _SConscript # pylint: disable=import-outside-toplevel
call_stack = _SConscript.call_stack
#
Action = SCons.Action.Action
AddMethod = SCons.Util.AddMethod
AllowSubstExceptions = SCons.Subst.SetAllowableExceptions
Builder = SCons.Builder.Builder
Configure = _SConscript.Configure
Environment = SCons.Environment.Environment
#OptParser = SCons.SConsOptions.OptParser
FindPathDirs = SCons.Scanner.FindPathDirs
Platform = SCons.Platform.Platform
Virtualenv = SCons.Platform.virtualenv.Virtualenv
Return = _SConscript.Return
Scanner = SCons.Scanner.ScannerBase
Tool = SCons.Tool.Tool
WhereIs = SCons.Util.WhereIs
#
BoolVariable = SCons.Variables.BoolVariable
EnumVariable = SCons.Variables.EnumVariable
ListVariable = SCons.Variables.ListVariable
PackageVariable = SCons.Variables.PackageVariable
PathVariable = SCons.Variables.PathVariable
# Action factories.
Chmod = SCons.Defaults.Chmod
Copy = SCons.Defaults.Copy
Delete = SCons.Defaults.Delete
Mkdir = SCons.Defaults.Mkdir
Move = SCons.Defaults.Move
Touch = SCons.Defaults.Touch
# Pre-made, public scanners.
CScanner = SCons.Tool.CScanner
# Nuitka: Avoid unused tools
# DScanner = SCons.Tool.DScanner
DirScanner = SCons.Defaults.DirScanner
ProgramScanner = SCons.Tool.ProgramScanner
SourceFileScanner = SCons.Tool.SourceFileScanner
# Functions we might still convert to Environment methods.
CScan = SCons.Defaults.CScan
DefaultEnvironment = SCons.Defaults.DefaultEnvironment
# Other variables we provide.
class TargetList(collections.UserList):
def _do_nothing(self, *args, **kw):
pass
def _add_Default(self, list):
self.extend(list)
def _clear(self):
del self[:]
ARGUMENTS = {}
ARGLIST = []
BUILD_TARGETS = TargetList()
COMMAND_LINE_TARGETS = []
DEFAULT_TARGETS = []
# BUILD_TARGETS can be modified in the SConscript files. If so, we
# want to treat the modified BUILD_TARGETS list as if they specified
# targets on the command line. To do that, though, we need to know if
# BUILD_TARGETS was modified through "official" APIs or by hand. We do
# this by updating two lists in parallel, the documented BUILD_TARGETS
# list, above, and this internal _build_plus_default targets list which
# should only have "official" API changes. Then Script/Main.py can
# compare these two afterwards to figure out if the user added their
# own targets to BUILD_TARGETS.
_build_plus_default = TargetList()
def _Add_Arguments(alist):
"""Add value(s) to ``ARGLIST`` and ``ARGUMENTS``."""
for arg in alist:
a, b = arg.split('=', 1)
ARGUMENTS[a] = b
ARGLIST.append((a, b))
def _Add_Targets(tlist):
"""Add value(s) to ``COMMAND_LINE_TARGETS`` and ``BUILD_TARGETS``."""
if tlist:
COMMAND_LINE_TARGETS.extend(tlist)
BUILD_TARGETS.extend(tlist)
BUILD_TARGETS._add_Default = BUILD_TARGETS._do_nothing
BUILD_TARGETS._clear = BUILD_TARGETS._do_nothing
_build_plus_default.extend(tlist)
_build_plus_default._add_Default = _build_plus_default._do_nothing
_build_plus_default._clear = _build_plus_default._do_nothing
def _Remove_Argument(aarg):
"""Remove *aarg* from ``ARGLIST`` and ``ARGUMENTS``.
Used to remove a variables-style argument that is no longer valid.
This can happpen because the command line is processed once early,
before we see any :func:`SCons.Script.Main.AddOption` calls, so we
could not recognize it belongs to an option and is not a standalone
variable=value argument.
.. versionadded:: 4.10.0
"""
if aarg:
a, b = aarg.split('=', 1)
if (a, b) in ARGLIST:
ARGLIST.remove((a, b))
ARGUMENTS.pop(a, None)
# ARGLIST might have had multiple values for 'a'. If there
# are any left, put that in ARGUMENTS, keeping the last one
# (retaining cmdline order)
for item in ARGLIST:
if item[0] == a:
ARGUMENTS[a] = item[1]
def _Remove_Target(targ):
"""Remove *targ* from ``BUILD_TARGETS`` and ``COMMAND_LINE_TARGETS``.
Used to remove a target that is no longer valid. This can happpen
because the command line is processed once early, before we see any
:func:`SCons.Script.Main.AddOption` calls, so we could not recognize
it belongs to an option and is not a standalone target argument.
Since we are "correcting an error", we also have to fix up the internal
:data:`_build_plus_default` list.
.. versionadded:: 4.10.0
"""
if targ:
if targ in COMMAND_LINE_TARGETS:
COMMAND_LINE_TARGETS.remove(targ)
if targ in BUILD_TARGETS:
BUILD_TARGETS.remove(targ)
if targ in _build_plus_default:
_build_plus_default.remove(targ)
def _Set_Default_Targets_Has_Been_Called(d, fs):
return DEFAULT_TARGETS
def _Set_Default_Targets_Has_Not_Been_Called(d, fs):
if d is None:
d = [fs.Dir('.')]
return d
_Get_Default_Targets = _Set_Default_Targets_Has_Not_Been_Called
def _Set_Default_Targets(env, tlist):
global DEFAULT_TARGETS
global _Get_Default_Targets
_Get_Default_Targets = _Set_Default_Targets_Has_Been_Called
for t in tlist:
if t is None:
# Delete the elements from the list in-place, don't
# reassign an empty list to DEFAULT_TARGETS, so that the
# variables will still point to the same object we point to.
del DEFAULT_TARGETS[:]
BUILD_TARGETS._clear()
_build_plus_default._clear()
elif isinstance(t, SCons.Node.Node):
DEFAULT_TARGETS.append(t)
BUILD_TARGETS._add_Default([t])
_build_plus_default._add_Default([t])
else:
nodes = env.arg2nodes(t, env.fs.Entry)
DEFAULT_TARGETS.extend(nodes)
BUILD_TARGETS._add_Default(nodes)
_build_plus_default._add_Default(nodes)
help_text = None
def HelpFunction(text, append = False, local_only = False):
"""The implementaion of the the ``Help`` method.
See :meth:`~SCons.Script.SConscript.Help`.
.. versionchanged:: 4.6.0
The *keep_local* parameter was added.
.. versionchanged:: 4.9.0
The *keep_local* parameter was renamed *local_only* to match manpage
"""
global help_text
if help_text is None:
if append:
with StringIO() as s:
PrintHelp(s, local_only=local_only)
help_text = s.getvalue()
else:
help_text = ""
help_text += text
# Will be non-zero if we are reading an SConscript file.
sconscript_reading = 0
_no_missing_sconscript = True
_warn_missing_sconscript_deprecated = False # TODO: now unused
def set_missing_sconscript_error(flag = True):
"""Set behavior on missing file in SConscript() call.
Returns:
previous value
"""
global _no_missing_sconscript
old = _no_missing_sconscript
_no_missing_sconscript = flag
return old
def Variables(files=None, args=ARGUMENTS):
return SCons.Variables.Variables(files, args)
# Adding global functions to the SConscript name space.
#
# Static functions that do not trigger initialization of
# DefaultEnvironment() and don't use its state.
GetSConsVersion = _SConscript.SConsEnvironment.GetSConsVersion
EnsureSConsVersion = _SConscript.SConsEnvironment.EnsureSConsVersion
EnsurePythonVersion = _SConscript.SConsEnvironment.EnsurePythonVersion
Exit = _SConscript.SConsEnvironment.Exit
GetLaunchDir = _SConscript.SConsEnvironment.GetLaunchDir
SConscriptChdir = _SConscript.SConsEnvironment.SConscriptChdir
# Functions that end up calling methods or Builders in the
# DefaultEnvironment().
GlobalDefaultEnvironmentFunctions = [
# Methods from the SConsEnvironment class, above.
'Default',
'Export',
'Help',
'Import',
#'SConscript', is handled separately, below.
# Methods from the Environment.Base class.
'AddPostAction',
'AddPreAction',
'Alias',
'AlwaysBuild',
'CacheDir',
'Clean',
#The Command() method is handled separately, below.
'Decider',
'Depends',
'Dir',
'NoClean',
'NoCache',
'Entry',
'Execute',
'File',
'FindFile',
'FindInstalledFiles',
'FindSourceFiles',
'Flatten',
'GetBuildPath',
'Glob',
'Ignore',
'Install',
'InstallAs',
'InstallVersionedLib',
'Literal',
'Local',
'ParseDepends',
'Precious',
'Pseudo',
'PyPackageDir',
'Repository',
'Requires',
'SConsignFile',
'SideEffect',
'Split',
'Tag',
'Value',
'VariantDir',
]
GlobalDefaultBuilders = [
# Supported builders.
'CFile',
'CXXFile',
'DVI',
'Jar',
'Java',
'JavaH',
'Library',
'LoadableModule',
'M4',
'MSVSProject',
'Object',
'PCH',
'PDF',
'PostScript',
'Program',
'RES',
'RMIC',
'SharedLibrary',
'SharedObject',
'StaticLibrary',
'StaticObject',
'Substfile',
'Tar',
'Textfile',
'TypeLibrary',
'Zip',
'Package',
]
# DefaultEnvironmentCall() initializes DefaultEnvironment() if it is not
# created yet.
for name in GlobalDefaultEnvironmentFunctions + GlobalDefaultBuilders:
exec ("%s = _SConscript.DefaultEnvironmentCall(%s)" % (name, repr(name)))
del name
# There are a handful of variables that used to live in the
# Script/SConscript.py module that some SConscript files out there were
# accessing directly as SCons.Script.SConscript.*. The problem is that
# "SConscript" in this namespace is no longer a module, it's a global
# function call--or more precisely, an object that implements a global
# function call through the default Environment. Nevertheless, we can
# maintain backwards compatibility for SConscripts that were reaching in
# this way by hanging some attributes off the "SConscript" object here.
SConscript = _SConscript.DefaultEnvironmentCall('SConscript')
# Make SConscript look enough like the module it used to be so
# that pychecker doesn't barf.
SConscript.__name__ = 'SConscript'
SConscript.Arguments = ARGUMENTS
SConscript.ArgList = ARGLIST
SConscript.BuildTargets = BUILD_TARGETS
SConscript.CommandLineTargets = COMMAND_LINE_TARGETS
SConscript.DefaultTargets = DEFAULT_TARGETS
# The global Command() function must be handled differently than the
# global functions for other construction environment methods because
# we want people to be able to use Actions that must expand $TARGET
# and $SOURCE later, when (and if) the Action is invoked to build
# the target(s). We do this with the subst=1 argument, which creates
# a DefaultEnvironmentCall instance that wraps up a normal default
# construction environment that performs variable substitution, not a
# proxy that doesn't.
#
# There's a flaw here, though, because any other $-variables on a command
# line will *also* be expanded, each to a null string, but that should
# only be a problem in the unusual case where someone was passing a '$'
# on a command line and *expected* the $ to get through to the shell
# because they were calling Command() and not env.Command()... This is
# unlikely enough that we're going to leave this as is and cross that
# bridge if someone actually comes to it.
Command = _SConscript.DefaultEnvironmentCall('Command', subst=1)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,762 @@
# 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.
"""Serial and Parallel classes to execute build tasks.
The Jobs class provides a higher level interface to start,
stop, and wait on jobs.
"""
import SCons.compat
import logging
import os
import queue
import signal
import sys
import threading
from enum import Enum
import SCons.Errors
import SCons.Warnings
# The default stack size (in kilobytes) of the threads used to execute
# jobs in parallel.
#
# We use a stack size of 256 kilobytes. The default on some platforms
# is too large and prevents us from creating enough threads to fully
# parallelized the build. For example, the default stack size on linux
# is 8 MBytes.
explicit_stack_size = None
default_stack_size = 256
interrupt_msg = 'Build interrupted.'
class InterruptState:
def __init__(self):
self.interrupted = False
def set(self):
self.interrupted = True
def __call__(self):
return self.interrupted
class Jobs:
"""An instance of this class initializes N jobs, and provides
methods for starting, stopping, and waiting on all N jobs.
"""
def __init__(self, num, taskmaster):
"""
Create 'num' jobs using the given taskmaster. The exact implementation
used varies with the number of jobs requested and the state of the `legacy_sched` flag
to `--experimental`.
"""
# Importing GetOption here instead of at top of file to avoid
# circular imports
# pylint: disable=import-outside-toplevel
from SCons.Script import GetOption
stack_size = explicit_stack_size
if stack_size is None:
stack_size = default_stack_size
experimental_option = GetOption('experimental') or []
if 'legacy_sched' in experimental_option:
if num > 1:
self.job = LegacyParallel(taskmaster, num, stack_size)
else:
self.job = Serial(taskmaster)
else:
self.job = NewParallel(taskmaster, num, stack_size)
self.num_jobs = num
def run(self, postfunc=lambda: None):
"""Run the jobs.
postfunc() will be invoked after the jobs has run. It will be
invoked even if the jobs are interrupted by a keyboard
interrupt (well, in fact by a signal such as either SIGINT,
SIGTERM or SIGHUP). The execution of postfunc() is protected
against keyboard interrupts and is guaranteed to run to
completion."""
self._setup_sig_handler()
try:
self.job.start()
finally:
postfunc()
self._reset_sig_handler()
def were_interrupted(self):
"""Returns whether the jobs were interrupted by a signal."""
return self.job.interrupted()
def _setup_sig_handler(self):
"""Setup an interrupt handler so that SCons can shutdown cleanly in
various conditions:
a) SIGINT: Keyboard interrupt
b) SIGTERM: kill or system shutdown
c) SIGHUP: Controlling shell exiting
We handle all of these cases by stopping the taskmaster. It
turns out that it's very difficult to stop the build process
by throwing asynchronously an exception such as
KeyboardInterrupt. For example, the python Condition
variables (threading.Condition) and queues do not seem to be
asynchronous-exception-safe. It would require adding a whole
bunch of try/finally block and except KeyboardInterrupt all
over the place.
Note also that we have to be careful to handle the case when
SCons forks before executing another process. In that case, we
want the child to exit immediately.
"""
def handler(signum, stack, self=self, parentpid=os.getpid()):
if os.getpid() == parentpid:
self.job.taskmaster.stop()
self.job.interrupted.set()
else:
os._exit(2) # pylint: disable=protected-access
self.old_sigint = signal.signal(signal.SIGINT, handler)
self.old_sigterm = signal.signal(signal.SIGTERM, handler)
try:
self.old_sighup = signal.signal(signal.SIGHUP, handler)
except AttributeError:
pass
if (self.old_sigint is None) or (self.old_sigterm is None) or \
(hasattr(self, "old_sighup") and self.old_sighup is None):
msg = "Overwritting previous signal handler which was not installed from Python. " + \
"Will not be able to reinstate and so will return to default handler."
SCons.Warnings.warn(SCons.Warnings.SConsWarning, msg)
def _reset_sig_handler(self):
"""Restore the signal handlers to their previous state (before the
call to _setup_sig_handler()."""
sigint_to_use = self.old_sigint if self.old_sigint is not None else signal.SIG_DFL
sigterm_to_use = self.old_sigterm if self.old_sigterm is not None else signal.SIG_DFL
signal.signal(signal.SIGINT, sigint_to_use)
signal.signal(signal.SIGTERM, sigterm_to_use)
try:
sigterm_to_use = self.old_sighup if self.old_sighup is not None else signal.SIG_DFL
signal.signal(signal.SIGHUP, sigterm_to_use)
except AttributeError:
pass
class Serial:
"""This class is used to execute tasks in series, and is more efficient
than Parallel, but is only appropriate for non-parallel builds. Only
one instance of this class should be in existence at a time.
This class is not thread safe.
"""
def __init__(self, taskmaster):
"""Create a new serial job given a taskmaster.
The taskmaster's next_task() method should return the next task
that needs to be executed, or None if there are no more tasks. The
taskmaster's executed() method will be called for each task when it
is successfully executed, or failed() will be called if it failed to
execute (e.g. execute() raised an exception)."""
self.taskmaster = taskmaster
self.interrupted = InterruptState()
def start(self):
"""Start the job. This will begin pulling tasks from the taskmaster
and executing them, and return when there are no more tasks. If a task
fails to execute (i.e. execute() raises an exception), then the job will
stop."""
while True:
task = self.taskmaster.next_task()
if task is None:
break
try:
task.prepare()
if task.needs_execute():
task.execute()
except Exception:
if self.interrupted():
try:
raise SCons.Errors.BuildError(
task.targets[0], errstr=interrupt_msg)
except Exception:
task.exception_set()
else:
task.exception_set()
# Let the failed() callback function arrange for the
# build to stop if that's appropriate.
task.failed()
else:
task.executed()
task.postprocess()
self.taskmaster.cleanup()
class Worker(threading.Thread):
"""A worker thread waits on a task to be posted to its request queue,
dequeues the task, executes it, and posts a tuple including the task
and a boolean indicating whether the task executed successfully. """
def __init__(self, requestQueue, resultsQueue, interrupted):
super().__init__()
self.daemon = True
self.requestQueue = requestQueue
self.resultsQueue = resultsQueue
self.interrupted = interrupted
self.start()
def run(self):
while True:
task = self.requestQueue.get()
if task is None:
# The "None" value is used as a sentinel by
# ThreadPool.cleanup(). This indicates that there
# are no more tasks, so we should quit.
break
try:
if self.interrupted():
raise SCons.Errors.BuildError(
task.targets[0], errstr=interrupt_msg)
task.execute()
except Exception:
task.exception_set()
ok = False
else:
ok = True
self.resultsQueue.put((task, ok))
class ThreadPool:
"""This class is responsible for spawning and managing worker threads."""
def __init__(self, num, stack_size, interrupted):
"""Create the request and reply queues, and 'num' worker threads.
One must specify the stack size of the worker threads. The
stack size is specified in kilobytes.
"""
self.requestQueue = queue.Queue(0)
self.resultsQueue = queue.Queue(0)
try:
prev_size = threading.stack_size(stack_size * 1024)
except RuntimeError as e:
# Only print a warning if the stack size has been explicitly set.
if explicit_stack_size is not None:
msg = "Setting stack size is unsupported by this version of Python:\n " + \
e.args[0]
SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg)
except ValueError as e:
msg = "Setting stack size failed:\n " + str(e)
SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg)
# Create worker threads
self.workers = []
for _ in range(num):
worker = Worker(self.requestQueue, self.resultsQueue, interrupted)
self.workers.append(worker)
if 'prev_size' in locals():
threading.stack_size(prev_size)
def put(self, task):
"""Put task into request queue."""
self.requestQueue.put(task)
def get(self):
"""Remove and return a result tuple from the results queue."""
return self.resultsQueue.get()
def preparation_failed(self, task):
self.resultsQueue.put((task, False))
def cleanup(self):
"""
Shuts down the thread pool, giving each worker thread a
chance to shut down gracefully.
"""
# For each worker thread, put a sentinel "None" value
# on the requestQueue (indicating that there's no work
# to be done) so that each worker thread will get one and
# terminate gracefully.
for _ in self.workers:
self.requestQueue.put(None)
# Wait for all of the workers to terminate.
#
# If we don't do this, later Python versions (2.4, 2.5) often
# seem to raise exceptions during shutdown. This happens
# in requestQueue.get(), as an assertion failure that
# requestQueue.not_full is notified while not acquired,
# seemingly because the main thread has shut down (or is
# in the process of doing so) while the workers are still
# trying to pull sentinels off the requestQueue.
#
# Normally these terminations should happen fairly quickly,
# but we'll stick a one-second timeout on here just in case
# someone gets hung.
for worker in self.workers:
worker.join(1.0)
self.workers = []
class LegacyParallel:
"""This class is used to execute tasks in parallel, and is somewhat
less efficient than Serial, but is appropriate for parallel builds.
This class is thread safe.
"""
def __init__(self, taskmaster, num, stack_size):
"""Create a new parallel job given a taskmaster.
The taskmaster's next_task() method should return the next
task that needs to be executed, or None if there are no more
tasks. The taskmaster's executed() method will be called
for each task when it is successfully executed, or failed()
will be called if the task failed to execute (i.e. execute()
raised an exception).
Note: calls to taskmaster are serialized, but calls to
execute() on distinct tasks are not serialized, because
that is the whole point of parallel jobs: they can execute
multiple tasks simultaneously. """
self.taskmaster = taskmaster
self.interrupted = InterruptState()
self.tp = ThreadPool(num, stack_size, self.interrupted)
self.maxjobs = num
def start(self):
"""Start the job. This will begin pulling tasks from the
taskmaster and executing them, and return when there are no
more tasks. If a task fails to execute (i.e. execute() raises
an exception), then the job will stop."""
jobs = 0
while True:
# Start up as many available tasks as we're
# allowed to.
while jobs < self.maxjobs:
task = self.taskmaster.next_task()
if task is None:
break
try:
# prepare task for execution
task.prepare()
except Exception:
task.exception_set()
task.failed()
task.postprocess()
else:
if task.needs_execute():
# dispatch task
self.tp.put(task)
jobs += 1
else:
task.executed()
task.postprocess()
if not task and not jobs:
break
# Let any/all completed tasks finish up before we go
# back and put the next batch of tasks on the queue.
while True:
task, ok = self.tp.get()
jobs -= 1
if ok:
task.executed()
else:
if self.interrupted():
try:
raise SCons.Errors.BuildError(
task.targets[0], errstr=interrupt_msg)
except Exception:
task.exception_set()
# Let the failed() callback function arrange
# for the build to stop if that's appropriate.
task.failed()
task.postprocess()
if self.tp.resultsQueue.empty():
break
self.tp.cleanup()
self.taskmaster.cleanup()
# An experimental new parallel scheduler that uses a leaders/followers pattern.
class NewParallel:
class State(Enum):
READY = 0
SEARCHING = 1
STALLED = 2
COMPLETED = 3
class Worker(threading.Thread):
def __init__(self, owner):
super().__init__()
self.daemon = True
self.owner = owner
self.start()
def run(self):
self.owner._work()
class FakeLock(object):
def lock(self):
pass
def unlock(self):
pass
def __enter__(self):
pass
def __exit__(self, *args):
pass
class FakeCondition(object):
def __init__(self, lock):
pass
def wait(self):
fatal();
def notify(self):
pass
def notify_all(self):
pass
def __enter__(self):
pass
def __exit__(self, *args):
pass
def __init__(self, taskmaster, num, stack_size):
self.taskmaster = taskmaster
self.max_workers = num
self.stack_size = stack_size
self.interrupted = InterruptState()
self.workers = []
# The `tm_lock` is what ensures that we only have one
# thread interacting with the taskmaster at a time. It
# also protects access to our state that gets updated
# concurrently. The `can_search_cv` is associated with
# this mutex.
self.tm_lock = (threading.Lock if self.max_workers > 1 else NewParallel.FakeLock)()
# Guarded under `tm_lock`.
self.jobs = 0
self.state = NewParallel.State.READY
# The `can_search_cv` is used to manage a leader /
# follower pattern for access to the taskmaster, and to
# awaken from stalls.
self.can_search_cv = (threading.Condition if self.max_workers > 1 else NewParallel.FakeCondition)(self.tm_lock)
# The queue of tasks that have completed execution. The
# next thread to obtain `tm_lock`` will retire them.
self.results_queue_lock = (threading.Lock if self.max_workers > 1 else NewParallel.FakeLock)()
self.results_queue = []
if self.taskmaster.trace:
self.trace = self._setup_logging()
else:
self.trace = False
def _setup_logging(self):
jl = logging.getLogger("Job")
jl.setLevel(level=logging.DEBUG)
jl.addHandler(self.taskmaster.trace.log_handler)
return jl
def trace_message(self, message):
# This grabs the name of the function which calls trace_message()
method_name = sys._getframe(1).f_code.co_name + "():"
thread_id=threading.get_ident()
self.trace.debug('%s.%s [Thread:%s] %s' % (type(self).__name__, method_name, thread_id, message))
def start(self):
if self.max_workers == 1:
self._work()
else:
self._start_worker()
while len(self.workers) > 0:
self.workers[0].join()
self.workers.pop(0)
self.taskmaster.cleanup()
def _maybe_start_worker(self):
if self.max_workers > 1 and len(self.workers) < self.max_workers:
if self.jobs >= len(self.workers):
self._start_worker()
def _start_worker(self):
prev_size = self._adjust_stack_size()
if self.trace:
self.trace_message("Starting new worker thread")
self.workers.append(NewParallel.Worker(self))
self._restore_stack_size(prev_size)
def _adjust_stack_size(self):
try:
prev_size = threading.stack_size(self.stack_size * 1024)
return prev_size
except AttributeError as e:
# Only print a warning if the stack size has been
# explicitly set.
if explicit_stack_size is not None:
msg = "Setting stack size is unsupported by this version of Python:\n " + \
e.args[0]
SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg)
except ValueError as e:
msg = "Setting stack size failed:\n " + str(e)
SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg)
return None
def _restore_stack_size(self, prev_size):
if prev_size is not None:
threading.stack_size(prev_size)
def _work(self):
task = None
while True:
# Obtain `tm_lock`, granting exclusive access to the taskmaster.
with self.can_search_cv:
if self.trace:
self.trace_message("Gained exclusive access")
# Capture whether we got here with `task` set,
# then drop our reference to the task as we are no
# longer interested in the actual object.
completed_task = (task is not None)
task = None
# We will only have `completed_task` set here if
# we have looped back after executing a task. If
# we have completed a task and find that we are
# stalled, we should speculatively indicate that
# we are no longer stalled by transitioning to the
# 'ready' state which will bypass the condition
# wait so that we immediately process the results
# queue and hopefully light up new
# work. Otherwise, stay stalled, and we will wait
# in the condvar. Some other thread will come back
# here with a completed task.
if self.state == NewParallel.State.STALLED and completed_task:
if self.trace:
self.trace_message("Detected stall with completed task, bypassing wait")
self.state = NewParallel.State.READY
# Wait until we are neither searching nor stalled.
while self.state == NewParallel.State.SEARCHING or self.state == NewParallel.State.STALLED:
if self.trace:
self.trace_message("Search already in progress, waiting")
self.can_search_cv.wait()
# If someone set the completed flag, bail.
if self.state == NewParallel.State.COMPLETED:
if self.trace:
self.trace_message("Completion detected, breaking from main loop")
break
# Set the searching flag to indicate that a thread
# is currently in the critical section for
# taskmaster work.
#
if self.trace:
self.trace_message("Starting search")
self.state = NewParallel.State.SEARCHING
# Bulk acquire the tasks in the results queue
# under the result queue lock, then process them
# all outside that lock. We need to process the
# tasks in the results queue before looking for
# new work because we might be unable to find new
# work if we don't.
results_queue = []
with self.results_queue_lock:
results_queue, self.results_queue = self.results_queue, results_queue
if self.trace:
self.trace_message(f"Found {len(results_queue)} completed tasks to process")
for (rtask, rresult) in results_queue:
if rresult:
rtask.executed()
else:
if self.interrupted():
try:
raise SCons.Errors.BuildError(
rtask.targets[0], errstr=interrupt_msg)
except Exception:
rtask.exception_set()
# Let the failed() callback function arrange
# for the build to stop if that's appropriate.
rtask.failed()
rtask.postprocess()
self.jobs -= 1
# We are done with any task objects that were in
# the results queue.
results_queue.clear()
# Now, turn the crank on the taskmaster until we
# either run out of tasks, or find a task that
# needs execution. If we run out of tasks, go idle
# until results arrive if jobs are pending, or
# mark the walk as complete if not.
while self.state == NewParallel.State.SEARCHING:
if self.trace:
self.trace_message("Searching for new tasks")
task = self.taskmaster.next_task()
if task:
# We found a task. Walk it through the
# task lifecycle. If it does not need
# execution, just complete the task and
# look for the next one. Otherwise,
# indicate that we are no longer searching
# so we can drop out of this loop, execute
# the task outside the lock, and allow
# another thread in to search.
try:
task.prepare()
except Exception:
task.exception_set()
task.failed()
task.postprocess()
else:
if not task.needs_execute():
if self.trace:
self.trace_message("Found internal task")
task.executed()
task.postprocess()
else:
self.jobs += 1
if self.trace:
self.trace_message("Found task requiring execution")
self.state = NewParallel.State.READY
self.can_search_cv.notify()
# This thread will be busy taking care of
# `execute`ing this task. If we haven't
# reached the limit, spawn a new thread to
# turn the crank and find the next task.
self._maybe_start_worker()
else:
# We failed to find a task, so this thread
# cannot continue turning the taskmaster
# crank. We must exit the loop.
if self.jobs:
# No task was found, but there are
# outstanding jobs executing that
# might unblock new tasks when they
# complete. Transition to the stalled
# state. We do not need a notify,
# because we know there are threads
# outstanding that will re-enter the
# loop.
#
if self.trace:
self.trace_message("Found no task requiring execution, but have jobs: marking stalled")
self.state = NewParallel.State.STALLED
else:
# We didn't find a task and there are
# no jobs outstanding, so there is
# nothing that will ever return
# results which might unblock new
# tasks. We can conclude that the walk
# is complete. Update our state to
# note completion and awaken anyone
# sleeping on the condvar.
#
if self.trace:
self.trace_message("Found no task requiring execution, and have no jobs: marking complete")
self.state = NewParallel.State.COMPLETED
self.can_search_cv.notify_all()
# We no longer hold `tm_lock` here. If we have a task,
# we can now execute it. If there are threads waiting
# to search, one of them can now begin turning the
# taskmaster crank in NewParallel.
if task:
if self.trace:
self.trace_message("Executing task")
ok = True
try:
if self.interrupted():
raise SCons.Errors.BuildError(
task.targets[0], errstr=interrupt_msg)
task.execute()
except Exception:
ok = False
task.exception_set()
# Grab the results queue lock and enqueue the
# executed task and state. The next thread into
# the searching loop will complete the
# postprocessing work under the taskmaster lock.
#
if self.trace:
self.trace_message("Enqueueing executed task results")
with self.results_queue_lock:
self.results_queue.append((task, ok))
# Tricky state "fallthrough" here. We are going back
# to the top of the loop, which behaves differently
# depending on whether `task` is set. Do not perturb
# the value of the `task` variable if you add new code
# after this comment.
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
"""SCons.Tool.386asm
Tool specification for the 386ASM assembler for the Phar Lap ETS embedded
operating system.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
from SCons.Tool.PharLapCommon import addPharLapPaths
import SCons.Util
as_module = __import__('as', globals(), locals(), [], 1)
def generate(env):
"""Add Builders and construction variables for ar to an Environment."""
as_module.generate(env)
env['AS'] = '386asm'
env['ASFLAGS'] = SCons.Util.CLVar('')
env['ASPPFLAGS'] = '$ASFLAGS'
env['ASCOM'] = '$AS $ASFLAGS $SOURCES -o $TARGET'
env['ASPPCOM'] = '$CC $ASPPFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS $SOURCES -o $TARGET'
addPharLapPaths(env)
def exists(env):
return env.Detect('386asm')
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,429 @@
# 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.
"""Common routines for gettext tools
Used by several tools of `gettext` toolset.
"""
import os
import re
import SCons.Util
import SCons.Warnings
class XgettextToolWarning(SCons.Warnings.SConsWarning):
pass
class XgettextNotFound(XgettextToolWarning):
pass
class MsginitToolWarning(SCons.Warnings.SConsWarning):
pass
class MsginitNotFound(MsginitToolWarning):
pass
class MsgmergeToolWarning(SCons.Warnings.SConsWarning):
pass
class MsgmergeNotFound(MsgmergeToolWarning):
pass
class MsgfmtToolWarning(SCons.Warnings.SConsWarning):
pass
class MsgfmtNotFound(MsgfmtToolWarning):
pass
SCons.Warnings.enableWarningClass(XgettextToolWarning)
SCons.Warnings.enableWarningClass(XgettextNotFound)
SCons.Warnings.enableWarningClass(MsginitToolWarning)
SCons.Warnings.enableWarningClass(MsginitNotFound)
SCons.Warnings.enableWarningClass(MsgmergeToolWarning)
SCons.Warnings.enableWarningClass(MsgmergeNotFound)
SCons.Warnings.enableWarningClass(MsgfmtToolWarning)
SCons.Warnings.enableWarningClass(MsgfmtNotFound)
class _POTargetFactory:
""" A factory of `PO` target files.
Factory defaults differ from these of `SCons.Node.FS.FS`. We set `precious`
(this is required by builders and actions gettext) and `noclean` flags by
default for all produced nodes.
"""
def __init__(self, env, nodefault=True, alias=None, precious=True
, noclean=True):
""" Object constructor.
**Arguments**
- *env* (`SCons.Environment.Environment`)
- *nodefault* (`boolean`) - if `True`, produced nodes will be ignored
from default target `'.'`
- *alias* (`string`) - if provided, produced nodes will be automatically
added to this alias, and alias will be set as `AlwaysBuild`
- *precious* (`boolean`) - if `True`, the produced nodes will be set as
`Precious`.
- *noclen* (`boolean`) - if `True`, the produced nodes will be excluded
from `Clean`.
"""
self.env = env
self.alias = alias
self.precious = precious
self.noclean = noclean
self.nodefault = nodefault
def _create_node(self, name, factory, directory=None, create=1):
""" Create node, and set it up to factory settings. """
node = factory(name, directory, create)
node.set_noclean(self.noclean)
node.set_precious(self.precious)
if self.nodefault:
self.env.Ignore('.', node)
if self.alias:
self.env.AlwaysBuild(self.env.Alias(self.alias, node))
return node
def Entry(self, name, directory=None, create=1):
""" Create `SCons.Node.FS.Entry` """
return self._create_node(name, self.env.fs.Entry, directory, create)
def File(self, name, directory=None, create=1):
""" Create `SCons.Node.FS.File` """
return self._create_node(name, self.env.fs.File, directory, create)
_re_comment = re.compile(r'(#[^\n\r]+)$', re.M)
_re_lang = re.compile(r'([a-zA-Z0-9_]+)', re.M)
def _read_linguas_from_files(env, linguas_files=None):
""" Parse `LINGUAS` file and return list of extracted languages """
global _re_comment
global _re_lang
if not SCons.Util.is_List(linguas_files) \
and not SCons.Util.is_String(linguas_files) \
and not isinstance(linguas_files, SCons.Node.FS.Base) \
and linguas_files:
# If, linguas_files==True or such, then read 'LINGUAS' file.
linguas_files = ['LINGUAS']
if linguas_files is None:
return []
fnodes = env.arg2nodes(linguas_files)
linguas = []
for fnode in fnodes:
contents = _re_comment.sub("", fnode.get_text_contents())
ls = [l for l in _re_lang.findall(contents) if l]
linguas.extend(ls)
return linguas
from SCons.Builder import BuilderBase
class _POFileBuilder(BuilderBase):
""" `PO` file builder.
This is multi-target single-source builder. In typical situation the source
is single `POT` file, e.g. `messages.pot`, and there are multiple `PO`
targets to be updated from this `POT`. We must run
`SCons.Builder.BuilderBase._execute()` separatelly for each target to track
dependencies separatelly for each target file.
**NOTE**: if we call `SCons.Builder.BuilderBase._execute(.., target, ...)`
with target being list of all targets, all targets would be rebuilt each time
one of the targets from this list is missing. This would happen, for example,
when new language `ll` enters `LINGUAS_FILE` (at this moment there is no
`ll.po` file yet). To avoid this, we override
`SCons.Builder.BuilerBase._execute()` and call it separatelly for each
target. Here we also append to the target list the languages read from
`LINGUAS_FILE`.
"""
#
# * The argument for overriding _execute(): We must use environment with
# builder overrides applied (see BuilderBase.__init__(). Here it comes for
# free.
# * The argument against using 'emitter': The emitter is called too late
# by BuilderBase._execute(). If user calls, for example:
#
# env.POUpdate(LINGUAS_FILE = 'LINGUAS')
#
# the builder throws error, because it is called with target=None,
# source=None and is trying to "generate" sources or target list first.
# If user calls
#
# env.POUpdate(['foo', 'baz'], LINGUAS_FILE = 'LINGUAS')
#
# the env.BuilderWrapper() calls our builder with target=None,
# source=['foo', 'baz']. The BuilderBase._execute() then splits execution
# and execute iterativelly (recursion) self._execute(None, source[i]).
# After that it calls emitter (which is quite too late). The emitter is
# also called in each iteration, what makes things yet worse.
def __init__(self, env, **kw):
if 'suffix' not in kw:
kw['suffix'] = '$POSUFFIX'
if 'src_suffix' not in kw:
kw['src_suffix'] = '$POTSUFFIX'
if 'src_builder' not in kw:
kw['src_builder'] = '_POTUpdateBuilder'
if 'single_source' not in kw:
kw['single_source'] = True
alias = None
if 'target_alias' in kw:
alias = kw['target_alias']
del kw['target_alias']
if 'target_factory' not in kw:
kw['target_factory'] = _POTargetFactory(env, alias=alias).File
super().__init__(**kw)
def _execute(self, env, target, source, *args, **kw):
""" Execute builder's actions.
Here we append to `target` the languages read from `$LINGUAS_FILE` and
apply `SCons.Builder.BuilderBase._execute()` separatelly to each target.
The arguments and return value are same as for
`SCons.Builder.BuilderBase._execute()`.
"""
import SCons.Node
linguas_files = None
if 'LINGUAS_FILE' in env and env['LINGUAS_FILE']:
linguas_files = env['LINGUAS_FILE']
# This prevents endless recursion loop (we'll be invoked once for
# each target appended here, we must not extend the list again).
env['LINGUAS_FILE'] = None
linguas = _read_linguas_from_files(env, linguas_files)
if SCons.Util.is_List(target):
target.extend(linguas)
elif target is not None:
target = [target] + linguas
else:
target = linguas
if not target:
# Let the SCons.BuilderBase to handle this patologic situation
return BuilderBase._execute(self, env, target, source, *args, **kw)
# The rest is ours
if not SCons.Util.is_List(target):
target = [target]
result = []
for tgt in target:
r = BuilderBase._execute(self, env, [tgt], source, *args, **kw)
result.extend(r)
if linguas_files is not None:
env['LINGUAS_FILE'] = linguas_files
return SCons.Node.NodeList(result)
def _translate(env, target=None, source=SCons.Environment._null, *args, **kw):
""" Function for `Translate()` pseudo-builder """
if target is None: target = []
pot = env.POTUpdate(None, source, *args, **kw)
po = env.POUpdate(target, pot, *args, **kw)
return po
class RPaths:
""" Callable object, which returns pathnames relative to SCons current
working directory.
It seems like `SCons.Node.FS.Base.get_path()` returns absolute paths
for nodes that are outside of current working directory (`env.fs.getcwd()`).
Here, we often have `SConscript`, `POT` and `PO` files within `po/`
directory and source files (e.g. `*.c`) outside of it. When generating `POT`
template file, references to source files are written to `POT` template, so
a translator may later quickly jump to appropriate source file and line from
its `PO` editor (e.g. `poedit`). Relative paths in `PO` file are usually
interpreted by `PO` editor as paths relative to the place, where `PO` file
lives. The absolute paths would make resultant `POT` file nonportable, as
the references would be correct only on the machine, where `POT` file was
recently re-created. For such reason, we need a function, which always
returns relative paths. This is the purpose of `RPaths` callable object.
The `__call__` method returns paths relative to current working directory, but
we assume, that *xgettext(1)* is run from the directory, where target file is
going to be created.
Note, that this may not work for files distributed over several hosts or
across different drives on windows. We assume here, that single local
filesystem holds both source files and target `POT` templates.
Intended use of `RPaths` - in `xgettext.py`::
def generate(env):
from GettextCommon import RPaths
...
sources = '$( ${_concat( "", SOURCES, "", __env__, XgettextRPaths, TARGET, SOURCES)} $)'
env.Append(
...
XGETTEXTCOM = 'XGETTEXT ... ' + sources,
...
XgettextRPaths = RPaths(env)
)
"""
# NOTE: This callable object returns pathnames of dirs/files relative to
# current working directory. The pathname remains relative also for entries
# that are outside of current working directory (node, that
# SCons.Node.FS.File and siblings return absolute path in such case). For
# simplicity we compute path relative to current working directory, this
# seems be enough for our purposes (don't need TARGET variable and
# SCons.Defaults.Variable_Caller stuff).
def __init__(self, env):
""" Initialize `RPaths` callable object.
**Arguments**:
- *env* - a `SCons.Environment.Environment` object, defines *current
working dir*.
"""
self.env = env
# FIXME: I'm not sure, how it should be implemented (what the *args are in
# general, what is **kw).
def __call__(self, nodes, *args, **kw):
""" Return nodes' paths (strings) relative to current working directory.
**Arguments**:
- *nodes* ([`SCons.Node.FS.Base`]) - list of nodes.
- *args* - currently unused.
- *kw* - currently unused.
**Returns**:
- Tuple of strings, which represent paths relative to current working
directory (for given environment).
"""
import SCons.Node.FS
rpaths = ()
cwd = self.env.fs.getcwd().get_abspath()
for node in nodes:
rpath = None
if isinstance(node, SCons.Node.FS.Base):
rpath = os.path.relpath(node.get_abspath(), cwd)
# FIXME: Other types possible here?
if rpath is not None:
rpaths += (rpath,)
return rpaths
def _init_po_files(target, source, env):
""" Action function for `POInit` builder. """
nop = lambda target, source, env: 0
if 'POAUTOINIT' in env:
autoinit = env['POAUTOINIT']
else:
autoinit = False
# Well, if everything outside works well, this loop should do single
# iteration. Otherwise we are rebuilding all the targets even, if just
# one has changed (but is this our fault?).
for tgt in target:
if not tgt.exists():
if autoinit:
action = SCons.Action.Action('$MSGINITCOM', '$MSGINITCOMSTR')
else:
msg = 'File ' + repr(str(tgt)) + ' does not exist. ' \
+ 'If you are a translator, you can create it through: \n' \
+ '$MSGINITCOM'
action = SCons.Action.Action(nop, msg)
status = action([tgt], source, env)
if status: return status
return 0
def _detect_xgettext(env):
""" Detects *xgettext(1)* binary """
if 'XGETTEXT' in env:
return env['XGETTEXT']
xgettext = env.Detect('xgettext')
if xgettext:
return xgettext
raise SCons.Errors.StopError(XgettextNotFound, "Could not detect xgettext")
return None
def _xgettext_exists(env):
return _detect_xgettext(env)
def _detect_msginit(env):
""" Detects *msginit(1)* program. """
if 'MSGINIT' in env:
return env['MSGINIT']
msginit = env.Detect('msginit')
if msginit:
return msginit
raise SCons.Errors.StopError(MsginitNotFound, "Could not detect msginit")
return None
def _msginit_exists(env):
return _detect_msginit(env)
def _detect_msgmerge(env):
""" Detects *msgmerge(1)* program. """
if 'MSGMERGE' in env:
return env['MSGMERGE']
msgmerge = env.Detect('msgmerge')
if msgmerge:
return msgmerge
raise SCons.Errors.StopError(MsgmergeNotFound, "Could not detect msgmerge")
return None
def _msgmerge_exists(env):
return _detect_msgmerge(env)
def _detect_msgfmt(env):
""" Detects *msgmfmt(1)* program. """
if 'MSGFMT' in env:
return env['MSGFMT']
msgfmt = env.Detect('msgfmt')
if msgfmt:
return msgfmt
raise SCons.Errors.StopError(MsgfmtNotFound, "Could not detect msgfmt")
return None
def _msgfmt_exists(env):
return _detect_msgfmt(env)
def tool_list(platform, env):
""" List tools that shall be generated by top-level `gettext` tool """
return ['xgettext', 'msginit', 'msgmerge', 'msgfmt']

View File

@@ -0,0 +1,396 @@
# 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.
"""
Constants and initialized data structures for Microsoft Visual C/C++.
"""
from collections import (
namedtuple,
)
from .Exceptions import (
MSVCInternalError,
)
from . import Dispatcher
Dispatcher.register_modulename(__name__)
UNDEFINED = object()
BOOLEAN_SYMBOLS = {}
BOOLEAN_EXTERNAL = {}
for bool_val, symbol_list, symbol_case_list in [
(False, (False, 0, '0', None, ''), ('False', 'No', 'F', 'N')),
(True, (True, 1, '1'), ('True', 'Yes', 'T', 'Y')),
]:
BOOLEAN_SYMBOLS[bool_val] = list(symbol_list)
for symbol in symbol_case_list:
BOOLEAN_SYMBOLS[bool_val].extend([symbol, symbol.lower(), symbol.upper()])
for symbol in BOOLEAN_SYMBOLS[bool_val]:
BOOLEAN_EXTERNAL[symbol] = bool_val
MSVC_PLATFORM_DEFINITION = namedtuple('MSVCPlatform', [
'vc_platform',
'is_uwp',
])
MSVC_PLATFORM_DEFINITION_LIST = []
MSVC_PLATFORM_INTERNAL = {}
MSVC_PLATFORM_EXTERNAL = {}
for vc_platform, is_uwp in [
('Desktop', False),
('UWP', True),
]:
vc_platform_def = MSVC_PLATFORM_DEFINITION(
vc_platform = vc_platform,
is_uwp = is_uwp,
)
MSVC_PLATFORM_DEFINITION_LIST.append(vc_platform_def)
MSVC_PLATFORM_INTERNAL[vc_platform] = vc_platform_def
for symbol in [vc_platform, vc_platform.lower(), vc_platform.upper()]:
MSVC_PLATFORM_EXTERNAL[symbol] = vc_platform_def
MSVC_RUNTIME_DEFINITION = namedtuple('MSVCRuntime', [
'vc_runtime',
'vc_runtime_numeric',
'vc_runtime_alias_list',
'vc_runtime_vsdef_list',
])
MSVC_RUNTIME_DEFINITION_LIST = []
MSVC_RUNTIME_INTERNAL = {}
MSVC_RUNTIME_EXTERNAL = {}
for vc_runtime, vc_runtime_numeric, vc_runtime_alias_list in [
('140', 140, ['ucrt']),
('120', 120, ['msvcr120']),
('110', 110, ['msvcr110']),
('100', 100, ['msvcr100']),
('90', 90, ['msvcr90']),
('80', 80, ['msvcr80']),
('71', 71, ['msvcr71']),
('70', 70, ['msvcr70']),
('60', 60, ['msvcrt']),
]:
vc_runtime_def = MSVC_RUNTIME_DEFINITION(
vc_runtime = vc_runtime,
vc_runtime_numeric = vc_runtime_numeric,
vc_runtime_alias_list = vc_runtime_alias_list,
vc_runtime_vsdef_list = [],
)
MSVC_RUNTIME_DEFINITION_LIST.append(vc_runtime_def)
MSVC_RUNTIME_INTERNAL[vc_runtime] = vc_runtime_def
MSVC_RUNTIME_EXTERNAL[vc_runtime] = vc_runtime_def
for vc_runtime_alias in vc_runtime_alias_list:
MSVC_RUNTIME_EXTERNAL[vc_runtime_alias] = vc_runtime_def
MSVC_BUILDSERIES_DEFINITION = namedtuple('MSVCBuildSeries', [
'vc_buildseries',
'vc_buildseries_numeric',
'vc_version',
'vc_version_numeric',
'cl_version',
'cl_version_numeric',
])
MSVC_BUILDSERIES_DEFINITION_LIST = []
MSVC_BUILDSERIES_INTERNAL = {}
MSVC_BUILDSERIES_EXTERNAL = {}
VC_BUILDTOOLS_MAP = {}
VC_VERSION_MAP = {}
CL_VERSION_MAP = {}
for (vc_buildseries, vc_version, cl_version) in [
('145', '14.5', '19.5'),
('144', '14.4', '19.4'),
('143', '14.3', '19.3'),
('142', '14.2', '19.2'),
('141', '14.1', '19.1'),
('140', '14.0', '19.0'),
('120', '12.0', '18.0'),
('110', '11.0', '17.0'),
('100', '10.0', '16.0'),
('90', '9.0', '15.0'),
('80', '8.0', '14.0'),
('71', '7.1', '13.1'),
('70', '7.0', '13.0'),
('60', '6.0', '12.0'),
]:
vc_buildseries_def = MSVC_BUILDSERIES_DEFINITION(
vc_buildseries=vc_buildseries,
vc_buildseries_numeric=int(vc_buildseries),
vc_version=vc_version,
vc_version_numeric=float(vc_version),
cl_version=cl_version,
cl_version_numeric=float(cl_version),
)
MSVC_BUILDSERIES_DEFINITION_LIST.append(vc_buildseries_def)
MSVC_BUILDSERIES_INTERNAL[vc_buildseries] = vc_buildseries_def
MSVC_BUILDSERIES_EXTERNAL[vc_buildseries] = vc_buildseries_def
MSVC_BUILDSERIES_EXTERNAL[vc_version] = vc_buildseries_def
VC_VERSION_MAP[vc_version] = vc_buildseries_def
CL_VERSION_MAP[cl_version] = vc_buildseries_def
MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildtools', [
'vc_buildtools',
'vc_buildtools_numeric',
'vc_buildseries_list',
'vc_runtime_def',
'vc_istoolset',
'msvc_version',
'msvc_version_numeric',
])
MSVC_BUILDTOOLS_DEFINITION_LIST = []
MSVC_BUILDTOOLS_INTERNAL = {}
MSVC_BUILDTOOLS_EXTERNAL = {}
MSVC_VERSION_NEWEST = None
MSVC_VERSION_NEWEST_NUMERIC = 0.0
for vc_buildtools, vc_buildseries_list, vc_runtime, vc_istoolset in [
('v145', ['145'], '140', True),
('v143', ['144', '143'], '140', True),
('v142', ['142'], '140', True),
('v141', ['141'], '140', True),
('v140', ['140'], '140', True),
('v120', ['120'], '120', False),
('v110', ['110'], '110', False),
('v100', ['100'], '100', False),
('v90', ['90'], '90', False),
('v80', ['80'], '80', False),
('v71', ['71'], '71', False),
('v70', ['70'], '70', False),
('v60', ['60'], '60', False),
]:
vc_runtime_def = MSVC_RUNTIME_INTERNAL[vc_runtime]
vc_buildseries_list = tuple(
MSVC_BUILDSERIES_INTERNAL[vc_buildseries]
for vc_buildseries in vc_buildseries_list
)
vc_buildtools_numstr = vc_buildtools[1:]
msvc_version = vc_buildtools_numstr[:-1] + '.' + vc_buildtools_numstr[-1]
msvc_version_numeric = float(msvc_version)
vc_buildtools_def = MSVC_BUILDTOOLS_DEFINITION(
vc_buildtools = vc_buildtools,
vc_buildtools_numeric = int(vc_buildtools[1:]),
vc_buildseries_list = vc_buildseries_list,
vc_runtime_def = vc_runtime_def,
vc_istoolset = vc_istoolset,
msvc_version = msvc_version,
msvc_version_numeric = msvc_version_numeric,
)
MSVC_BUILDTOOLS_DEFINITION_LIST.append(vc_buildtools_def)
MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] = vc_buildtools_def
MSVC_BUILDTOOLS_EXTERNAL[vc_buildtools] = vc_buildtools_def
MSVC_BUILDTOOLS_EXTERNAL[msvc_version] = vc_buildtools_def
for vc_buildseries_def in vc_buildseries_list:
VC_BUILDTOOLS_MAP[vc_buildseries_def.vc_buildseries] = vc_buildtools_def
if vc_buildtools_def.msvc_version_numeric > MSVC_VERSION_NEWEST_NUMERIC:
MSVC_VERSION_NEWEST_NUMERIC = vc_buildtools_def.msvc_version_numeric
MSVC_VERSION_NEWEST = vc_buildtools_def.msvc_version
MSVS_VERSION_INTERNAL = {}
MSVS_VERSION_EXTERNAL = {}
MSVC_VERSION_INTERNAL = {}
MSVC_VERSION_EXTERNAL = {}
MSVC_VERSION_SUFFIX = {}
MSVS_VERSION_MAJOR_MAP = {}
MSVC_SDK_VERSIONS = set()
VISUALSTUDIO_DEFINITION = namedtuple('VisualStudioDefinition', [
'vs_product',
'vs_product_alias_list',
'vs_version',
'vs_version_major',
'vs_envvar',
'vs_express',
'vs_lookup',
'vc_sdk_versions',
'vc_ucrt_versions',
'vc_uwp',
'vc_buildtools_def',
'vc_buildtools_all',
])
VISUALSTUDIO_DEFINITION_LIST = []
VS_PRODUCT_ALIAS = {
'1998': ['6']
}
# vs_envvar: VisualStudioVersion defined in environment for MSVS 2012 and later
# MSVS 2010 and earlier cl_version -> vs_def is a 1:1 mapping
# SDK attached to product or buildtools?
for vs_product, vs_version, vs_envvar, vs_express, vs_lookup, vc_sdk, vc_ucrt, vc_uwp, vc_buildtools_all in [
('2026', '18.0', True, False, 'vswhere' , ['10.0', '8.1'], ['10'], 'uwp', ['v145', 'v143', 'v142', 'v141', 'v140']),
('2022', '17.0', True, False, 'vswhere' , ['10.0', '8.1'], ['10'], 'uwp', ['v143', 'v142', 'v141', 'v140']),
('2019', '16.0', True, False, 'vswhere' , ['10.0', '8.1'], ['10'], 'uwp', ['v142', 'v141', 'v140']),
('2017', '15.0', True, True, 'vswhere' , ['10.0', '8.1'], ['10'], 'uwp', ['v141', 'v140']),
('2015', '14.0', True, True, 'registry', ['10.0', '8.1'], ['10'], 'store', ['v140']),
('2013', '12.0', True, True, 'registry', None, None, None, ['v120']),
('2012', '11.0', True, True, 'registry', None, None, None, ['v110']),
('2010', '10.0', False, True, 'registry', None, None, None, ['v100']),
('2008', '9.0', False, True, 'registry', None, None, None, ['v90']),
('2005', '8.0', False, True, 'registry', None, None, None, ['v80']),
('2003', '7.1', False, False, 'registry', None, None, None, ['v71']),
('2002', '7.0', False, False, 'registry', None, None, None, ['v70']),
('1998', '6.0', False, False, 'registry', None, None, None, ['v60']),
]:
vs_version_major = vs_version.split('.')[0]
vc_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools_all[0]]
vs_def = VISUALSTUDIO_DEFINITION(
vs_product = vs_product,
vs_product_alias_list = [],
vs_version = vs_version,
vs_version_major = vs_version_major,
vs_envvar = vs_envvar,
vs_express = vs_express,
vs_lookup = vs_lookup,
vc_sdk_versions = vc_sdk,
vc_ucrt_versions = vc_ucrt,
vc_uwp = vc_uwp,
vc_buildtools_def = vc_buildtools_def,
vc_buildtools_all = vc_buildtools_all,
)
VISUALSTUDIO_DEFINITION_LIST.append(vs_def)
vc_buildtools_def.vc_runtime_def.vc_runtime_vsdef_list.append(vs_def)
msvc_version = vc_buildtools_def.msvc_version
MSVS_VERSION_INTERNAL[vs_product] = vs_def
MSVS_VERSION_EXTERNAL[vs_product] = vs_def
MSVS_VERSION_EXTERNAL[vs_version] = vs_def
MSVC_VERSION_INTERNAL[msvc_version] = vs_def
MSVC_VERSION_EXTERNAL[vs_product] = vs_def
MSVC_VERSION_EXTERNAL[msvc_version] = vs_def
MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_buildtools] = vs_def
if vs_product in VS_PRODUCT_ALIAS:
for vs_product_alias in VS_PRODUCT_ALIAS[vs_product]:
vs_def.vs_product_alias_list.append(vs_product_alias)
MSVS_VERSION_EXTERNAL[vs_product_alias] = vs_def
MSVC_VERSION_EXTERNAL[vs_product_alias] = vs_def
MSVC_VERSION_SUFFIX[msvc_version] = vs_def
if vs_express:
MSVC_VERSION_SUFFIX[msvc_version + 'Exp'] = vs_def
MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_def
if vc_sdk:
MSVC_SDK_VERSIONS.update(vc_sdk)
# EXPERIMENTAL: msvc version/toolset search lists
#
# VS2017 example:
#
# defaults['14.1'] = ['14.1', '14.1Exp']
# defaults['14.1Exp'] = ['14.1Exp']
#
# search['14.1'] = ['14.3', '14.2', '14.1', '14.1Exp']
# search['14.1Exp'] = ['14.1Exp']
MSVC_VERSION_TOOLSET_DEFAULTS_MAP = {}
MSVC_VERSION_TOOLSET_SEARCH_MAP = {}
# Pass 1: Build defaults lists and setup express versions
for vs_def in VISUALSTUDIO_DEFINITION_LIST:
if not vs_def.vc_buildtools_def.vc_istoolset:
continue
version_key = vs_def.vc_buildtools_def.msvc_version
MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key] = [version_key]
MSVC_VERSION_TOOLSET_SEARCH_MAP[version_key] = []
if vs_def.vs_express:
express_key = version_key + 'Exp'
MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key].append(express_key)
MSVC_VERSION_TOOLSET_DEFAULTS_MAP[express_key] = [express_key]
MSVC_VERSION_TOOLSET_SEARCH_MAP[express_key] = [express_key]
# Pass 2: Extend search lists (decreasing version order)
for vs_def in VISUALSTUDIO_DEFINITION_LIST:
if not vs_def.vc_buildtools_def.vc_istoolset:
continue
version_key = vs_def.vc_buildtools_def.msvc_version
for vc_buildtools in vs_def.vc_buildtools_all:
toolset_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools]
toolset_vs_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.msvc_version]
buildtools_key = toolset_buildtools_def.msvc_version
MSVC_VERSION_TOOLSET_SEARCH_MAP[buildtools_key].extend(MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key])
# convert string version set to string version list ranked in descending order
MSVC_SDK_VERSIONS = [str(f) for f in sorted([float(s) for s in MSVC_SDK_VERSIONS], reverse=True)]
def verify():
from . import Util
from .. import vc
for msvc_version in vc._VCVER:
if msvc_version not in MSVC_VERSION_SUFFIX:
err_msg = f'msvc_version {msvc_version!r} not in MSVC_VERSION_SUFFIX'
raise MSVCInternalError(err_msg)
vc_version = Util.get_msvc_version_prefix(msvc_version)
if vc_version not in MSVC_VERSION_INTERNAL:
err_msg = f'vc_version {vc_version!r} not in MSVC_VERSION_INTERNAL'
raise MSVCInternalError(err_msg)

View File

@@ -0,0 +1,84 @@
# 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.
"""
Internal method dispatcher for Microsoft Visual C/C++.
MSVC modules can register their module (register_modulename) and individual
classes (register_class) with the method dispatcher during initialization. MSVC
modules tend to be registered immediately after the Dispatcher import near the
top of the file. Methods in the MSVC modules can be invoked indirectly without
having to hard-code the method calls effectively decoupling the upstream module
with the downstream modules:
The reset method dispatches calls to all registered objects with a reset method
and/or a _reset method. The reset methods are used to restore data structures
to their initial state for testing purposes. Typically, this involves clearing
cached values.
The verify method dispatches calls to all registered objects with a verify
method and/or a _verify method. The verify methods are used to check that
initialized data structures distributed across multiple modules are internally
consistent. An exception is raised when a verification constraint violation
is detected. Typically, this verifies that initialized dictionaries support
all of the requisite keys as new versions are added.
"""
import sys
from ..common import (
debug,
)
_refs = []
def register_modulename(modname):
module = sys.modules[modname]
_refs.append(module)
def register_class(ref):
_refs.append(ref)
def reset():
debug('')
for ref in _refs:
for method in ['reset', '_reset']:
if not hasattr(ref, method) or not callable(getattr(ref, method, None)):
continue
debug('call %s.%s()', ref.__name__, method)
func = getattr(ref, method)
func()
def verify():
debug('')
for ref in _refs:
for method in ['verify', '_verify']:
if not hasattr(ref, method) or not callable(getattr(ref, method, None)):
continue
debug('call %s.%s()', ref.__name__, method)
func = getattr(ref, method)
func()

View File

@@ -0,0 +1,56 @@
# 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.
"""
Exceptions for Microsoft Visual C/C++.
"""
# reminder: add exceptions to MSCommon if necessary
class VisualCException(Exception):
pass
class MSVCInternalError(VisualCException):
pass
class MSVCUserError(VisualCException):
pass
class MSVCScriptExecutionError(VisualCException):
pass
class MSVCVersionNotFound(MSVCUserError):
pass
class MSVCSDKVersionNotFound(MSVCUserError):
pass
class MSVCToolsetVersionNotFound(MSVCUserError):
pass
class MSVCSpectreLibsNotFound(MSVCUserError):
pass
class MSVCArgumentError(MSVCUserError):
pass

View File

@@ -0,0 +1,668 @@
# 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.
"""
Version kind categorization for Microsoft Visual C/C++.
"""
import os
import re
from collections import (
namedtuple,
)
from ..common import (
debug,
)
from . import Registry
from . import Util
from . import Dispatcher
Dispatcher.register_modulename(__name__)
# use express install for non-express msvc_version if no other installations found
USE_EXPRESS_FOR_NONEXPRESS = True
# productdir kind
VCVER_KIND_UNKNOWN = 0 # undefined
VCVER_KIND_DEVELOP = 1 # devenv binary
VCVER_KIND_EXPRESS = 2 # express binary
VCVER_KIND_BTDISPATCH = 3 # no ide binaries (buildtools dispatch folder)
VCVER_KIND_VCFORPYTHON = 4 # no ide binaries (2008/9.0)
VCVER_KIND_EXPRESS_WIN = 5 # express for windows binary (VSWinExpress)
VCVER_KIND_EXPRESS_WEB = 6 # express for web binary (VWDExpress)
VCVER_KIND_SDK = 7 # no ide binaries
VCVER_KIND_CMDLINE = 8 # no ide binaries
VCVER_KIND_STR = {
VCVER_KIND_UNKNOWN: '<Unknown>',
VCVER_KIND_DEVELOP: 'Develop',
VCVER_KIND_EXPRESS: 'Express',
VCVER_KIND_BTDISPATCH: 'BTDispatch',
VCVER_KIND_VCFORPYTHON: 'VCForPython',
VCVER_KIND_EXPRESS_WIN: 'Express-Win',
VCVER_KIND_EXPRESS_WEB: 'Express-Web',
VCVER_KIND_SDK: 'SDK',
VCVER_KIND_CMDLINE: 'CmdLine',
}
BITFIELD_KIND_DEVELOP = 0b_1000
BITFIELD_KIND_EXPRESS = 0b_0100
BITFIELD_KIND_EXPRESS_WIN = 0b_0010
BITFIELD_KIND_EXPRESS_WEB = 0b_0001
VCVER_KIND_PROGRAM = namedtuple("VCVerKindProgram", [
'kind', # relpath from pdir to vsroot
'program', # ide binaries
'bitfield',
])
#
IDE_PROGRAM_DEVENV_COM = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_DEVELOP,
program='devenv.com',
bitfield=BITFIELD_KIND_DEVELOP,
)
IDE_PROGRAM_MSDEV_COM = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_DEVELOP,
program='msdev.com',
bitfield=BITFIELD_KIND_DEVELOP,
)
IDE_PROGRAM_WDEXPRESS_EXE = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_EXPRESS,
program='WDExpress.exe',
bitfield=BITFIELD_KIND_EXPRESS,
)
IDE_PROGRAM_VCEXPRESS_EXE = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_EXPRESS,
program='VCExpress.exe',
bitfield=BITFIELD_KIND_EXPRESS,
)
IDE_PROGRAM_VSWINEXPRESS_EXE = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_EXPRESS_WIN,
program='VSWinExpress.exe',
bitfield=BITFIELD_KIND_EXPRESS_WIN,
)
IDE_PROGRAM_VWDEXPRESS_EXE = VCVER_KIND_PROGRAM(
kind=VCVER_KIND_EXPRESS_WEB,
program='VWDExpress.exe',
bitfield=BITFIELD_KIND_EXPRESS_WEB,
)
# detection configuration
VCVER_KIND_DETECT = namedtuple("VCVerKindDetect", [
'root', # relpath from pdir to vsroot
'path', # vsroot to ide dir
'programs', # ide binaries
])
# detected binaries
VCVER_DETECT_BINARIES = namedtuple("VCVerDetectBinaries", [
'bitfields', # detect values
'have_dev', # develop ide binary
'have_exp', # express ide binary
'have_exp_win', # express windows ide binary
'have_exp_web', # express web ide binary
])
VCVER_DETECT_KIND = namedtuple("VCVerDetectKind", [
'skip', # skip vs root
'save', # save in case no other kind found
'kind', # internal kind
'binaries_t',
'extended',
])
# unknown value
_VCVER_DETECT_KIND_UNKNOWN = VCVER_DETECT_KIND(
skip=True,
save=False,
kind=VCVER_KIND_UNKNOWN,
binaries_t=VCVER_DETECT_BINARIES(
bitfields=0b0,
have_dev=False,
have_exp=False,
have_exp_win=False,
have_exp_web=False,
),
extended={},
)
#
_msvc_pdir_func = None
def register_msvc_version_pdir_func(func):
global _msvc_pdir_func
if func:
_msvc_pdir_func = func
_cache_vcver_kind_map = {}
def msvc_version_register_kind(msvc_version, kind_t):
global _cache_vcver_kind_map
if kind_t is None:
kind_t = _VCVER_DETECT_KIND_UNKNOWN
debug('msvc_version=%s, kind=%s', repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind]))
_cache_vcver_kind_map[msvc_version] = kind_t
def _msvc_version_kind_lookup(msvc_version, env=None):
global _cache_vcver_kind_map
global _msvc_pdir_func
if msvc_version not in _cache_vcver_kind_map:
_msvc_pdir_func(msvc_version, env)
kind_t = _cache_vcver_kind_map.get(msvc_version, _VCVER_DETECT_KIND_UNKNOWN)
debug(
'kind=%s, dev=%s, exp=%s, msvc_version=%s',
repr(VCVER_KIND_STR[kind_t.kind]),
kind_t.binaries_t.have_dev, kind_t.binaries_t.have_exp,
repr(msvc_version)
)
return kind_t
def msvc_version_is_btdispatch(msvc_version, env=None):
kind_t = _msvc_version_kind_lookup(msvc_version, env)
is_btdispatch = bool(kind_t.kind == VCVER_KIND_BTDISPATCH)
debug(
'is_btdispatch=%s, kind:%s, msvc_version=%s',
repr(is_btdispatch), repr(VCVER_KIND_STR[kind_t.kind]), repr(msvc_version)
)
return is_btdispatch
def msvc_version_is_express(msvc_version, env=None):
kind_t = _msvc_version_kind_lookup(msvc_version, env)
is_express = bool(kind_t.kind == VCVER_KIND_EXPRESS)
debug(
'is_express=%s, kind:%s, msvc_version=%s',
repr(is_express), repr(VCVER_KIND_STR[kind_t.kind]), repr(msvc_version)
)
return is_express
def msvc_version_is_vcforpython(msvc_version, env=None):
kind_t = _msvc_version_kind_lookup(msvc_version, env)
is_vcforpython = bool(kind_t.kind == VCVER_KIND_VCFORPYTHON)
debug(
'is_vcforpython=%s, kind:%s, msvc_version=%s',
repr(is_vcforpython), repr(VCVER_KIND_STR[kind_t.kind]), repr(msvc_version)
)
return is_vcforpython
def msvc_version_skip_uwp_target(env, msvc_version):
vernum = float(Util.get_msvc_version_prefix(msvc_version))
vernum_int = int(vernum * 10)
if vernum_int != 140:
return False
kind_t = _msvc_version_kind_lookup(msvc_version, env)
if kind_t.kind != VCVER_KIND_EXPRESS:
return False
target_arch = env.get('TARGET_ARCH')
uwp_is_supported = kind_t.extended.get('uwp_is_supported', {})
is_supported = uwp_is_supported.get(target_arch, True)
if is_supported:
return False
return True
def _pdir_detect_binaries(pdir, detect):
vs_root = os.path.join(pdir, detect.root)
ide_path = os.path.join(vs_root, detect.path)
bitfields = 0b_0000
for ide_program in detect.programs:
prog = os.path.join(ide_path, ide_program.program)
if not os.path.exists(prog):
continue
bitfields |= ide_program.bitfield
have_dev = bool(bitfields & BITFIELD_KIND_DEVELOP)
have_exp = bool(bitfields & BITFIELD_KIND_EXPRESS)
have_exp_win = bool(bitfields & BITFIELD_KIND_EXPRESS_WIN)
have_exp_web = bool(bitfields & BITFIELD_KIND_EXPRESS_WEB)
binaries_t = VCVER_DETECT_BINARIES(
bitfields=bitfields,
have_dev=have_dev,
have_exp=have_exp,
have_exp_win=have_exp_win,
have_exp_web=have_exp_web,
)
debug(
'vs_root=%s, dev=%s, exp=%s, exp_win=%s, exp_web=%s, pdir=%s',
repr(vs_root),
binaries_t.have_dev, binaries_t.have_exp,
binaries_t.have_exp_win, binaries_t.have_exp_web,
repr(pdir)
)
return vs_root, binaries_t
_cache_pdir_vswhere_kind = {}
def msvc_version_pdir_vswhere_kind(msvc_version, pdir, detect_t):
global _cache_pdir_vswhere_kind
vc_dir = os.path.normcase(os.path.normpath(pdir))
cache_key = (msvc_version, vc_dir)
rval = _cache_pdir_vswhere_kind.get(cache_key)
if rval is not None:
debug('cache=%s', repr(rval))
return rval
extended = {}
prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version)
vs_root, binaries_t = _pdir_detect_binaries(pdir, detect_t)
if binaries_t.have_dev:
kind = VCVER_KIND_DEVELOP
elif binaries_t.have_exp:
kind = VCVER_KIND_EXPRESS
else:
kind = VCVER_KIND_CMDLINE
skip = False
save = False
if suffix != 'Exp' and kind == VCVER_KIND_EXPRESS:
skip = True
save = USE_EXPRESS_FOR_NONEXPRESS
elif suffix == 'Exp' and kind != VCVER_KIND_EXPRESS:
skip = True
kind_t = VCVER_DETECT_KIND(
skip=skip,
save=save,
kind=kind,
binaries_t=binaries_t,
extended=extended,
)
debug(
'skip=%s, save=%s, kind=%s, msvc_version=%s, pdir=%s',
kind_t.skip, kind_t.save, repr(VCVER_KIND_STR[kind_t.kind]),
repr(msvc_version), repr(pdir)
)
_cache_pdir_vswhere_kind[cache_key] = kind_t
return kind_t
# VS2015 buildtools batch file call detection
# vs2015 buildtools do not support sdk_version or UWP arguments
_VS2015BT_PATH = r'..\Microsoft Visual C++ Build Tools\vcbuildtools.bat'
_VS2015BT_REGEX_STR = ''.join([
r'^\s*if\s+exist\s+',
re.escape(fr'"%~dp0..\{_VS2015BT_PATH}"'),
r'\s+goto\s+setup_buildsku\s*$',
])
_VS2015BT_VCVARS_BUILDTOOLS = re.compile(_VS2015BT_REGEX_STR, re.IGNORECASE)
_VS2015BT_VCVARS_STOP = re.compile(r'^\s*[:]Setup_VS\s*$', re.IGNORECASE)
def _vs_buildtools_2015_vcvars(vcvars_file):
have_buildtools_vcvars = False
with open(vcvars_file) as fh:
for line in fh:
if _VS2015BT_VCVARS_BUILDTOOLS.match(line):
have_buildtools_vcvars = True
break
if _VS2015BT_VCVARS_STOP.match(line):
break
return have_buildtools_vcvars
def _vs_buildtools_2015(vs_root, vc_dir):
is_btdispatch = False
do_once = True
while do_once:
do_once = False
buildtools_file = os.path.join(vs_root, _VS2015BT_PATH)
have_buildtools = os.path.exists(buildtools_file)
debug('have_buildtools=%s', have_buildtools)
if not have_buildtools:
break
vcvars_file = os.path.join(vc_dir, 'vcvarsall.bat')
have_vcvars = os.path.exists(vcvars_file)
debug('have_vcvars=%s', have_vcvars)
if not have_vcvars:
break
have_buildtools_vcvars = _vs_buildtools_2015_vcvars(vcvars_file)
debug('have_buildtools_vcvars=%s', have_buildtools_vcvars)
if not have_buildtools_vcvars:
break
is_btdispatch = True
debug('is_btdispatch=%s', is_btdispatch)
return is_btdispatch
_VS2015EXP_VCVARS_LIBPATH = re.compile(
''.join([
r'^\s*\@if\s+exist\s+\"\%VCINSTALLDIR\%LIB\\store\\(amd64|arm)"\s+',
r'set (LIB|LIBPATH)=\%VCINSTALLDIR\%LIB\\store\\(amd64|arm);.*\%(LIB|LIBPATH)\%\s*$'
]),
re.IGNORECASE
)
_VS2015EXP_VCVARS_STOP = re.compile(r'^\s*[:]GetVSCommonToolsDir\s*$', re.IGNORECASE)
def _vs_express_2015_vcvars(vcvars_file):
n_libpath = 0
with open(vcvars_file) as fh:
for line in fh:
if _VS2015EXP_VCVARS_LIBPATH.match(line):
n_libpath += 1
elif _VS2015EXP_VCVARS_STOP.match(line):
break
have_uwp_fix = n_libpath >= 2
return have_uwp_fix
def _vs_express_2015(pdir):
have_uwp_amd64 = False
have_uwp_arm = False
vcvars_file = os.path.join(pdir, r'vcvarsall.bat')
if os.path.exists(vcvars_file):
vcvars_file = os.path.join(pdir, r'bin\x86_amd64\vcvarsx86_amd64.bat')
if os.path.exists(vcvars_file):
have_uwp_fix = _vs_express_2015_vcvars(vcvars_file)
if have_uwp_fix:
have_uwp_amd64 = True
vcvars_file = os.path.join(pdir, r'bin\x86_arm\vcvarsx86_arm.bat')
if os.path.exists(vcvars_file):
have_uwp_fix = _vs_express_2015_vcvars(vcvars_file)
if have_uwp_fix:
have_uwp_arm = True
debug('have_uwp_amd64=%s, have_uwp_arm=%s', have_uwp_amd64, have_uwp_arm)
return have_uwp_amd64, have_uwp_arm
# winsdk installed 2010 [7.1], 2008 [7.0, 6.1] folders
_REGISTRY_WINSDK_VERSIONS = {'10.0', '9.0'}
_cache_pdir_registry_winsdk = {}
def _msvc_version_pdir_registry_winsdk(msvc_version, pdir):
global _cache_pdir_registry_winsdk
# detect winsdk-only installations
#
# registry keys:
# [prefix]\VisualStudio\SxS\VS7\10.0 <undefined>
# [prefix]\VisualStudio\SxS\VC7\10.0 product directory
# [prefix]\VisualStudio\SxS\VS7\9.0 <undefined>
# [prefix]\VisualStudio\SxS\VC7\9.0 product directory
#
# winsdk notes:
# - winsdk installs do not define the common tools env var
# - the product dir is detected but the vcvars batch files will fail
# - regular installations populate the VS7 registry keys
#
vc_dir = os.path.normcase(os.path.normpath(pdir))
cache_key = (msvc_version, vc_dir)
rval = _cache_pdir_registry_winsdk.get(cache_key)
if rval is not None:
debug('cache=%s', repr(rval))
return rval
if msvc_version not in _REGISTRY_WINSDK_VERSIONS:
is_sdk = False
debug('is_sdk=%s, msvc_version=%s', is_sdk, repr(msvc_version))
else:
vc_dir = os.path.normcase(os.path.normpath(pdir))
vc_suffix = Registry.vstudio_sxs_vc7(msvc_version)
vc_qresults = [record[0] for record in Registry.microsoft_query_paths(vc_suffix)]
vc_root = os.path.normcase(os.path.normpath(vc_qresults[0])) if vc_qresults else None
if vc_dir != vc_root:
# registry vc path is not the current pdir
is_sdk = False
debug(
'is_sdk=%s, msvc_version=%s, pdir=%s, vc_root=%s',
is_sdk, repr(msvc_version), repr(vc_dir), repr(vc_root)
)
else:
# registry vc path is the current pdir
vs_suffix = Registry.vstudio_sxs_vs7(msvc_version)
vs_qresults = [record[0] for record in Registry.microsoft_query_paths(vs_suffix)]
vs_root = vs_qresults[0] if vs_qresults else None
is_sdk = bool(not vs_root and vc_root)
debug(
'is_sdk=%s, msvc_version=%s, vs_root=%s, vc_root=%s',
is_sdk, repr(msvc_version), repr(vs_root), repr(vc_root)
)
_cache_pdir_registry_winsdk[cache_key] = is_sdk
return is_sdk
_cache_pdir_registry_kind = {}
def msvc_version_pdir_registry_kind(msvc_version, pdir, detect_t, is_vcforpython=False):
global _cache_pdir_registry_kind
vc_dir = os.path.normcase(os.path.normpath(pdir))
cache_key = (msvc_version, vc_dir)
rval = _cache_pdir_registry_kind.get(cache_key)
if rval is not None:
debug('cache=%s', repr(rval))
return rval
extended = {}
prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version)
vs_root, binaries_t = _pdir_detect_binaries(pdir, detect_t)
if binaries_t.have_dev:
kind = VCVER_KIND_DEVELOP
elif binaries_t.have_exp:
kind = VCVER_KIND_EXPRESS
elif msvc_version == '14.0' and _vs_buildtools_2015(vs_root, pdir):
kind = VCVER_KIND_BTDISPATCH
elif msvc_version == '9.0' and is_vcforpython:
kind = VCVER_KIND_VCFORPYTHON
elif binaries_t.have_exp_win:
kind = VCVER_KIND_EXPRESS_WIN
elif binaries_t.have_exp_web:
kind = VCVER_KIND_EXPRESS_WEB
elif _msvc_version_pdir_registry_winsdk(msvc_version, pdir):
kind = VCVER_KIND_SDK
else:
kind = VCVER_KIND_CMDLINE
skip = False
save = False
if kind in (VCVER_KIND_EXPRESS_WIN, VCVER_KIND_EXPRESS_WEB, VCVER_KIND_SDK):
skip = True
elif suffix != 'Exp' and kind == VCVER_KIND_EXPRESS:
skip = True
save = USE_EXPRESS_FOR_NONEXPRESS
elif suffix == 'Exp' and kind != VCVER_KIND_EXPRESS:
skip = True
if prefix == '14.0' and kind == VCVER_KIND_EXPRESS:
have_uwp_amd64, have_uwp_arm = _vs_express_2015(pdir)
uwp_is_supported = {
'x86': True,
'amd64': have_uwp_amd64,
'arm': have_uwp_arm,
}
extended['uwp_is_supported'] = uwp_is_supported
kind_t = VCVER_DETECT_KIND(
skip=skip,
save=save,
kind=kind,
binaries_t=binaries_t,
extended=extended,
)
debug(
'skip=%s, save=%s, kind=%s, msvc_version=%s, pdir=%s',
kind_t.skip, kind_t.save, repr(VCVER_KIND_STR[kind_t.kind]),
repr(msvc_version), repr(pdir)
)
_cache_pdir_registry_kind[cache_key] = kind_t
return kind_t
# queries
def get_msvc_version_kind(msvc_version, env=None):
kind_t = _msvc_version_kind_lookup(msvc_version, env)
kind_str = VCVER_KIND_STR[kind_t.kind]
debug(
'kind=%s, kind_str=%s, msvc_version=%s',
repr(kind_t.kind), repr(kind_str), repr(msvc_version)
)
return (kind_t.kind, kind_str)
def msvc_version_sdk_version_is_supported(msvc_version, env=None):
vernum = float(Util.get_msvc_version_prefix(msvc_version))
vernum_int = int(vernum * 10)
kind_t = _msvc_version_kind_lookup(msvc_version, env)
if vernum_int >= 141:
# VS2017 and later
is_supported = True
elif vernum_int == 140:
# VS2015:
# True: Develop, CmdLine
# False: Express, BTDispatch
is_supported = True
if kind_t.kind == VCVER_KIND_EXPRESS:
is_supported = False
elif kind_t.kind == VCVER_KIND_BTDISPATCH:
is_supported = False
else:
# VS2013 and earlier
is_supported = False
debug(
'is_supported=%s, msvc_version=%s, kind=%s',
is_supported, repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind])
)
return is_supported
def msvc_version_uwp_is_supported(msvc_version, target_arch=None, env=None):
vernum = float(Util.get_msvc_version_prefix(msvc_version))
vernum_int = int(vernum * 10)
kind_t = _msvc_version_kind_lookup(msvc_version, env)
is_target = False
if vernum_int >= 141:
# VS2017 and later
is_supported = True
elif vernum_int == 140:
# VS2015:
# True: Develop, CmdLine
# Maybe: Express
# False: BTDispatch
is_supported = True
if kind_t.kind == VCVER_KIND_EXPRESS:
uwp_is_supported = kind_t.extended.get('uwp_is_supported', {})
is_supported = uwp_is_supported.get(target_arch, True)
is_target = True
elif kind_t.kind == VCVER_KIND_BTDISPATCH:
is_supported = False
else:
# VS2013 and earlier
is_supported = False
debug(
'is_supported=%s, is_target=%s, msvc_version=%s, kind=%s, target_arch=%s',
is_supported, is_target, repr(msvc_version), repr(VCVER_KIND_STR[kind_t.kind]), repr(target_arch)
)
return is_supported, is_target
# reset cache
def reset():
global _cache_vcver_kind_map
global _cache_pdir_vswhere_kind
global _cache_pdir_registry_kind
global _cache_pdir_registry_winsdk
debug('')
_cache_vcver_kind_map = {}
_cache_pdir_vswhere_kind = {}
_cache_pdir_registry_kind = {}
_cache_pdir_registry_winsdk = {}

View File

@@ -0,0 +1,330 @@
# 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.
"""
Microsoft Visual C/C++ policy handlers.
Notes:
* The default msvc not found policy is that a warning is issued. This can be
changed globally via the function set_msvc_notfound_policy and/or through
the environment via the MSVC_NOTFOUND_POLICY construction variable.
* The default msvc script error policy is to suppress all msvc batch file
error messages. This can be changed globally via the function
set_msvc_scripterror_policy and/or through the environment via the
MSVC_SCRIPTERROR_POLICY construction variable.
"""
from collections import (
namedtuple,
)
from contextlib import (
contextmanager,
)
import SCons.Warnings
from ..common import (
debug,
)
from .Exceptions import (
MSVCArgumentError,
MSVCVersionNotFound,
MSVCScriptExecutionError,
)
from .Warnings import (
MSVCScriptExecutionWarning,
)
from . import Dispatcher
Dispatcher.register_modulename(__name__)
# MSVC_NOTFOUND_POLICY definition:
# error: raise exception
# warning: issue warning and continue
# ignore: continue
MSVC_NOTFOUND_POLICY_DEFINITION = namedtuple('MSVCNotFoundPolicyDefinition', [
'value',
'symbol',
])
MSVC_NOTFOUND_DEFINITION_LIST = []
MSVC_NOTFOUND_POLICY_INTERNAL = {}
MSVC_NOTFOUND_POLICY_EXTERNAL = {}
for policy_value, policy_symbol_list in [
(True, ['Error', 'Exception']),
(False, ['Warning', 'Warn']),
(None, ['Ignore', 'Suppress']),
]:
policy_symbol = policy_symbol_list[0].lower()
policy_def = MSVC_NOTFOUND_POLICY_DEFINITION(policy_value, policy_symbol)
MSVC_NOTFOUND_DEFINITION_LIST.append(policy_def)
MSVC_NOTFOUND_POLICY_INTERNAL[policy_symbol] = policy_def
for policy_symbol in policy_symbol_list:
MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.lower()] = policy_def
MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol] = policy_def
MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.upper()] = policy_def
# default definition
_MSVC_NOTFOUND_POLICY_DEF = MSVC_NOTFOUND_POLICY_INTERNAL['warning']
# MSVC_SCRIPTERROR_POLICY definition:
# error: raise exception
# warning: issue warning and continue
# ignore: continue
MSVC_SCRIPTERROR_POLICY_DEFINITION = namedtuple('MSVCBatchErrorPolicyDefinition', [
'value',
'symbol',
])
MSVC_SCRIPTERROR_DEFINITION_LIST = []
MSVC_SCRIPTERROR_POLICY_INTERNAL = {}
MSVC_SCRIPTERROR_POLICY_EXTERNAL = {}
for policy_value, policy_symbol_list in [
(True, ['Error', 'Exception']),
(False, ['Warning', 'Warn']),
(None, ['Ignore', 'Suppress']),
]:
policy_symbol = policy_symbol_list[0].lower()
policy_def = MSVC_SCRIPTERROR_POLICY_DEFINITION(policy_value, policy_symbol)
MSVC_SCRIPTERROR_DEFINITION_LIST.append(policy_def)
MSVC_SCRIPTERROR_POLICY_INTERNAL[policy_symbol] = policy_def
for policy_symbol in policy_symbol_list:
MSVC_SCRIPTERROR_POLICY_EXTERNAL[policy_symbol.lower()] = policy_def
MSVC_SCRIPTERROR_POLICY_EXTERNAL[policy_symbol] = policy_def
MSVC_SCRIPTERROR_POLICY_EXTERNAL[policy_symbol.upper()] = policy_def
# default definition
_MSVC_SCRIPTERROR_POLICY_DEF = MSVC_SCRIPTERROR_POLICY_INTERNAL['ignore']
def _msvc_notfound_policy_lookup(symbol):
try:
notfound_policy_def = MSVC_NOTFOUND_POLICY_EXTERNAL[symbol]
except KeyError:
err_msg = "Value specified for MSVC_NOTFOUND_POLICY is not supported: {}.\n" \
" Valid values are: {}".format(
repr(symbol),
', '.join([repr(s) for s in MSVC_NOTFOUND_POLICY_EXTERNAL.keys()])
)
raise MSVCArgumentError(err_msg)
return notfound_policy_def
def msvc_set_notfound_policy(MSVC_NOTFOUND_POLICY=None):
""" Set the default policy when MSVC is not found.
Args:
MSVC_NOTFOUND_POLICY:
string representing the policy behavior
when MSVC is not found or None
Returns:
The previous policy is returned when the MSVC_NOTFOUND_POLICY argument
is not None. The active policy is returned when the MSVC_NOTFOUND_POLICY
argument is None.
"""
global _MSVC_NOTFOUND_POLICY_DEF
prev_policy = _MSVC_NOTFOUND_POLICY_DEF.symbol
policy = MSVC_NOTFOUND_POLICY
if policy is not None:
_MSVC_NOTFOUND_POLICY_DEF = _msvc_notfound_policy_lookup(policy)
debug(
'prev_policy=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
repr(prev_policy), repr(policy),
repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value)
)
return prev_policy
def msvc_get_notfound_policy():
"""Return the active policy when MSVC is not found."""
debug(
'policy.symbol=%s, policy.value=%s',
repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value)
)
return _MSVC_NOTFOUND_POLICY_DEF.symbol
def msvc_notfound_handler(env, msg):
if env and 'MSVC_NOTFOUND_POLICY' in env:
# environment setting
notfound_policy_src = 'environment'
policy = env['MSVC_NOTFOUND_POLICY']
if policy is not None:
# user policy request
notfound_policy_def = _msvc_notfound_policy_lookup(policy)
else:
# active global setting
notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF
else:
# active global setting
notfound_policy_src = 'default'
policy = None
notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF
debug(
'source=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
notfound_policy_src, repr(policy), repr(notfound_policy_def.symbol), repr(notfound_policy_def.value)
)
if notfound_policy_def.value is None:
# ignore
pass
elif notfound_policy_def.value:
raise MSVCVersionNotFound(msg)
else:
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg)
@contextmanager
def msvc_notfound_policy_contextmanager(MSVC_NOTFOUND_POLICY=None):
""" Temporarily change the MSVC not found policy within a context.
Args:
MSVC_NOTFOUND_POLICY:
string representing the policy behavior
when MSVC is not found or None
"""
prev_policy = msvc_set_notfound_policy(MSVC_NOTFOUND_POLICY)
yield
msvc_set_notfound_policy(prev_policy)
def _msvc_scripterror_policy_lookup(symbol):
try:
scripterror_policy_def = MSVC_SCRIPTERROR_POLICY_EXTERNAL[symbol]
except KeyError:
err_msg = "Value specified for MSVC_SCRIPTERROR_POLICY is not supported: {}.\n" \
" Valid values are: {}".format(
repr(symbol),
', '.join([repr(s) for s in MSVC_SCRIPTERROR_POLICY_EXTERNAL.keys()])
)
raise MSVCArgumentError(err_msg)
return scripterror_policy_def
def msvc_set_scripterror_policy(MSVC_SCRIPTERROR_POLICY=None):
""" Set the default policy when msvc batch file execution errors are detected.
Args:
MSVC_SCRIPTERROR_POLICY:
string representing the policy behavior
when msvc batch file execution errors are detected or None
Returns:
The previous policy is returned when the MSVC_SCRIPTERROR_POLICY argument
is not None. The active policy is returned when the MSVC_SCRIPTERROR_POLICY
argument is None.
"""
global _MSVC_SCRIPTERROR_POLICY_DEF
prev_policy = _MSVC_SCRIPTERROR_POLICY_DEF.symbol
policy = MSVC_SCRIPTERROR_POLICY
if policy is not None:
_MSVC_SCRIPTERROR_POLICY_DEF = _msvc_scripterror_policy_lookup(policy)
debug(
'prev_policy=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
repr(prev_policy), repr(policy),
repr(_MSVC_SCRIPTERROR_POLICY_DEF.symbol), repr(_MSVC_SCRIPTERROR_POLICY_DEF.value)
)
return prev_policy
def msvc_get_scripterror_policy():
"""Return the active policy when msvc batch file execution errors are detected."""
debug(
'policy.symbol=%s, policy.value=%s',
repr(_MSVC_SCRIPTERROR_POLICY_DEF.symbol), repr(_MSVC_SCRIPTERROR_POLICY_DEF.value)
)
return _MSVC_SCRIPTERROR_POLICY_DEF.symbol
def msvc_scripterror_handler(env, msg):
if env and 'MSVC_SCRIPTERROR_POLICY' in env:
# environment setting
scripterror_policy_src = 'environment'
policy = env['MSVC_SCRIPTERROR_POLICY']
if policy is not None:
# user policy request
scripterror_policy_def = _msvc_scripterror_policy_lookup(policy)
else:
# active global setting
scripterror_policy_def = _MSVC_SCRIPTERROR_POLICY_DEF
else:
# active global setting
scripterror_policy_src = 'default'
policy = None
scripterror_policy_def = _MSVC_SCRIPTERROR_POLICY_DEF
debug(
'source=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
scripterror_policy_src, repr(policy), repr(scripterror_policy_def.symbol), repr(scripterror_policy_def.value)
)
if scripterror_policy_def.value is None:
# ignore
pass
elif scripterror_policy_def.value:
raise MSVCScriptExecutionError(msg)
else:
SCons.Warnings.warn(MSVCScriptExecutionWarning, msg)
@contextmanager
def msvc_scripterror_policy_contextmanager(MSVC_SCRIPTERROR_POLICY=None):
""" Temporarily change the msvc batch execution errors policy within a context.
Args:
MSVC_SCRIPTERROR_POLICY:
string representing the policy behavior
when msvc batch file execution errors are detected or None
"""
prev_policy = msvc_set_scripterror_policy(MSVC_SCRIPTERROR_POLICY)
yield
msvc_set_scripterror_policy(prev_policy)

View File

@@ -0,0 +1,121 @@
# 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.
"""
Windows registry functions for Microsoft Visual C/C++.
"""
import os
from SCons.Util import (
HKEY_LOCAL_MACHINE,
HKEY_CURRENT_USER,
RegGetValue,
)
from .. common import (
debug,
)
from . import Util
from . import Dispatcher
Dispatcher.register_modulename(__name__)
# A null-terminated string that contains unexpanded references to environment variables.
REG_EXPAND_SZ = 2
def read_value(hkey, subkey_valname, expand=True):
try:
rval_t = RegGetValue(hkey, subkey_valname)
except OSError:
debug('OSError: hkey=%s, subkey=%s', repr(hkey), repr(subkey_valname))
return None
rval, regtype = rval_t
if regtype == REG_EXPAND_SZ and expand:
rval = os.path.expandvars(rval)
debug('hkey=%s, subkey=%s, rval=%s', repr(hkey), repr(subkey_valname), repr(rval))
return rval
def registry_query_path(key, val, suffix, expand=True):
extval = val + '\\' + suffix if suffix else val
qpath = read_value(key, extval, expand=expand)
if qpath and os.path.exists(qpath):
qpath = Util.normalize_path(qpath)
else:
qpath = None
return (qpath, key, val, extval)
REG_SOFTWARE_MICROSOFT = [
(HKEY_LOCAL_MACHINE, r'Software\Wow6432Node\Microsoft'),
(HKEY_CURRENT_USER, r'Software\Wow6432Node\Microsoft'), # SDK queries
(HKEY_LOCAL_MACHINE, r'Software\Microsoft'),
(HKEY_CURRENT_USER, r'Software\Microsoft'),
]
def microsoft_query_paths(suffix, usrval=None, expand=True):
paths = []
records = []
for key, val in REG_SOFTWARE_MICROSOFT:
extval = val + '\\' + suffix if suffix else val
qpath = read_value(key, extval, expand=expand)
if qpath and os.path.exists(qpath):
qpath = Util.normalize_path(qpath)
if qpath not in paths:
paths.append(qpath)
records.append((qpath, key, val, extval, usrval))
return records
def microsoft_query_keys(suffix, usrval=None, expand=True):
records = []
for key, val in REG_SOFTWARE_MICROSOFT:
extval = val + '\\' + suffix if suffix else val
rval = read_value(key, extval, expand=expand)
if rval:
records.append((rval, key, val, extval, usrval))
return records
def microsoft_sdks(version):
return '\\'.join([r'Microsoft SDKs\Windows', 'v' + version, r'InstallationFolder'])
def sdk_query_paths(version):
q = microsoft_sdks(version)
return microsoft_query_paths(q)
def windows_kits(version):
return r'Windows Kits\Installed Roots\KitsRoot' + version
def windows_kit_query_paths(version):
q = windows_kits(version)
return microsoft_query_paths(q)
def vstudio_sxs_vs7(version):
return '\\'.join([r'VisualStudio\SxS\VS7', version])
def vstudio_sxs_vc7(version):
return '\\'.join([r'VisualStudio\SxS\VC7', version])
def devdiv_vs_servicing_component(version, component):
return '\\'.join([r'DevDiv\VS\Servicing', version, component, 'Install'])

View File

@@ -0,0 +1,236 @@
# 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.
"""
Determine if and/or when an error/warning should be issued when there
are no versions of msvc installed. If there is at least one version of
msvc installed, these routines do (almost) nothing.
Notes:
* When msvc is the default compiler because there are no compilers
installed, a build may fail due to the cl.exe command not being
recognized. Currently, there is no easy way to detect during
msvc initialization if the default environment will be used later
to build a program and/or library. There is no error/warning
as there are legitimate SCons uses that do not require a c compiler.
* An error is indicated by returning a non-empty tool list from the
function register_iserror.
"""
import re
from .. common import (
debug,
)
from . import Dispatcher
Dispatcher.register_modulename(__name__)
class _Data:
separator = r';'
need_init = True
@classmethod
def reset(cls):
debug('msvc default:init')
cls.n_setup = 0 # number of calls to msvc_setup_env_once
cls.default_ismsvc = False # is msvc the default compiler
cls.default_tools_re_list = [] # list of default tools regular expressions
cls.msvc_tools_init = set() # tools registered via msvc_exists
cls.msvc_tools = None # tools registered via msvc_setup_env_once
cls.msvc_installed = False # is msvc installed (vcs_installed > 0)
cls.msvc_nodefault = False # is there a default version of msvc
cls.need_init = True # reset initialization indicator
def _initialize(env, msvc_exists_func):
if _Data.need_init:
_Data.reset()
_Data.need_init = False
_Data.msvc_installed = msvc_exists_func(env)
debug('msvc default:msvc_installed=%s', _Data.msvc_installed)
def register_tool(env, tool, msvc_exists_func):
if _Data.need_init:
_initialize(env, msvc_exists_func)
if _Data.msvc_installed:
return None
if not tool:
return None
if _Data.n_setup == 0:
if tool not in _Data.msvc_tools_init:
_Data.msvc_tools_init.add(tool)
debug('msvc default:tool=%s, msvc_tools_init=%s', tool, _Data.msvc_tools_init)
return None
if tool not in _Data.msvc_tools:
_Data.msvc_tools.add(tool)
debug('msvc default:tool=%s, msvc_tools=%s', tool, _Data.msvc_tools)
def register_setup(env, msvc_exists_func):
if _Data.need_init:
_initialize(env, msvc_exists_func)
_Data.n_setup += 1
if not _Data.msvc_installed:
_Data.msvc_tools = set(_Data.msvc_tools_init)
if _Data.n_setup == 1:
tool_list = env.get('TOOLS', None)
if tool_list and tool_list[0] == 'default':
if len(tool_list) > 1 and tool_list[1] in _Data.msvc_tools:
# msvc tools are the default compiler
_Data.default_ismsvc = True
_Data.msvc_nodefault = False
debug(
'msvc default:n_setup=%d, msvc_installed=%s, default_ismsvc=%s',
_Data.n_setup, _Data.msvc_installed, _Data.default_ismsvc
)
def set_nodefault():
# default msvc version, msvc not installed
_Data.msvc_nodefault = True
debug('msvc default:msvc_nodefault=%s', _Data.msvc_nodefault)
def register_iserror(env, tool, msvc_exists_func):
register_tool(env, tool, msvc_exists_func)
if _Data.msvc_installed:
# msvc installed
return None
if not _Data.msvc_nodefault:
# msvc version specified
return None
tool_list = env.get('TOOLS', None)
if not tool_list:
# tool list is empty
return None
debug(
'msvc default:n_setup=%s, default_ismsvc=%s, msvc_tools=%s, tool_list=%s',
_Data.n_setup, _Data.default_ismsvc, _Data.msvc_tools, tool_list
)
if not _Data.default_ismsvc:
# Summary:
# * msvc is not installed
# * msvc version not specified (default)
# * msvc is not the default compiler
# construct tools set
tools_set = set(tool_list)
else:
if _Data.n_setup == 1:
# first setup and msvc is default compiler:
# build default tools regex for current tool state
tools = _Data.separator.join(tool_list)
tools_nchar = len(tools)
debug('msvc default:add regex:nchar=%d, tools=%s', tools_nchar, tools)
re_default_tools = re.compile(re.escape(tools))
_Data.default_tools_re_list.insert(0, (tools_nchar, re_default_tools))
# early exit: no error for default environment when msvc is not installed
return None
# Summary:
# * msvc is not installed
# * msvc version not specified (default)
# * environment tools list is not empty
# * default tools regex list constructed
# * msvc tools set constructed
#
# Algorithm using tools string and sets:
# * convert environment tools list to a string
# * iteratively remove default tools sequences via regex
# substition list built from longest sequence (first)
# to shortest sequence (last)
# * build environment tools set with remaining tools
# * compute intersection of environment tools and msvc tools sets
# * if the intersection is:
# empty - no error: default tools and/or no additional msvc tools
# not empty - error: user specified one or more msvc tool(s)
#
# This will not produce an error or warning when there are no
# msvc installed instances nor any other recognized compilers
# and the default environment is needed for a build. The msvc
# compiler is forcibly added to the environment tools list when
# there are no compilers installed on win32. In this case, cl.exe
# will not be found on the path resulting in a failed build.
# construct tools string
tools = _Data.separator.join(tool_list)
tools_nchar = len(tools)
debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools)
# iteratively remove default tool sequences (longest to shortest)
if not _Data.default_tools_re_list:
debug('default_tools_re_list=%s', _Data.default_tools_re_list)
else:
re_nchar_min, re_tools_min = _Data.default_tools_re_list[-1]
if tools_nchar >= re_nchar_min and re_tools_min.search(tools):
# minimum characters satisfied and minimum pattern exists
for re_nchar, re_default_tool in _Data.default_tools_re_list:
if tools_nchar < re_nchar:
# not enough characters for pattern
continue
tools = re_default_tool.sub('', tools).strip(_Data.separator)
tools_nchar = len(tools)
debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools)
if tools_nchar < re_nchar_min or not re_tools_min.search(tools):
# less than minimum characters or minimum pattern does not exist
break
# construct non-default list(s) tools set
tools_set = {msvc_tool for msvc_tool in tools.split(_Data.separator) if msvc_tool}
debug('msvc default:tools=%s', tools_set)
if not tools_set:
return None
# compute intersection of remaining tools set and msvc tools set
tools_found = _Data.msvc_tools.intersection(tools_set)
debug('msvc default:tools_exist=%s', tools_found)
if not tools_found:
return None
# construct in same order as tools list
tools_found_list = []
seen_tool = set()
for tool in tool_list:
if tool not in seen_tool:
seen_tool.add(tool)
if tool in tools_found:
tools_found_list.append(tool)
# return tool list in order presented
return tools_found_list
def reset():
debug('')
_Data.reset()

View File

@@ -0,0 +1,494 @@
# 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.
"""
Helper functions for Microsoft Visual C/C++.
"""
import os
import pathlib
import re
from collections import (
namedtuple,
)
from ..common import debug
from . import Config
# call _initialize method upon class definition completion
class AutoInitialize:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if hasattr(cls, '_initialize') and callable(getattr(cls, '_initialize', None)):
cls._initialize()
# path utilities
# windows drive specification (e.g., 'C:')
_RE_DRIVESPEC = re.compile(r'^[A-Za-z][:]$', re.IGNORECASE)
# windows path separators
_OS_PATH_SEPS = (os.path.sep, os.path.altsep) if os.path.altsep else (os.path.sep,)
def listdir_dirs(p):
"""
Return a list of tuples for each subdirectory of the given directory path.
Each tuple is comprised of the subdirectory name and the qualified subdirectory path.
Args:
p: str
directory path
Returns:
list[tuple[str,str]]: a list of tuples
"""
dirs = []
if p and os.path.exists(p) and os.path.isdir(p):
for dir_name in os.listdir(p):
dir_path = os.path.join(p, dir_name)
if os.path.isdir(dir_path):
dirs.append((dir_name, dir_path))
return dirs
def resolve_path(p, ignore_drivespec=True):
"""
Make path absolute resolving any symlinks
Args:
p: str
system path
ignore_drivespec: bool
ignore drive specifications when True
Returns:
str: absolute path with symlinks resolved
"""
if p:
if ignore_drivespec and _RE_DRIVESPEC.match(p):
# don't attempt to resolve drive specification (e.g., C:)
pass
else:
# both abspath and resolve necessary for an unqualified file name
# on a mapped network drive in order to return a mapped drive letter
# path rather than a UNC path.
p = os.path.abspath(p)
try:
p = str(pathlib.Path(p).resolve())
except OSError as e:
debug(
'caught exception: path=%s, exception=%s(%s)',
repr(p), type(e).__name__, repr(str(e))
)
return p
def normalize_path(
p,
strip=True,
preserve_trailing=False,
expand=False,
realpath=True,
ignore_drivespec=True,
):
"""
Normalize path
Args:
p: str
system path
strip: bool
remove leading and trailing whitespace when True
preserve_trailing: bool
preserve trailing path separator when True
expand: bool
apply expanduser and expandvars when True
realpath: bool
make the path absolute resolving any symlinks when True
ignore_drivespec: bool
ignore drive specifications for realpath when True
Returns:
str: normalized path
"""
if p and strip:
p = p.strip()
if p:
trailing = bool(preserve_trailing and p.endswith(_OS_PATH_SEPS))
if expand:
p = os.path.expanduser(p)
p = os.path.expandvars(p)
p = os.path.normpath(p)
if realpath:
p = resolve_path(p, ignore_drivespec=ignore_drivespec)
p = os.path.normcase(p)
if trailing:
p += os.path.sep
return p
# msvc version and msvc toolset version regexes
re_version_prefix = re.compile('^(?P<version>[0-9]+(?:[.][0-9]+)*)(?![.]).*$')
re_msvc_version_prefix = re.compile(r'^(?P<version>[1-9][0-9]?[.][0-9]).*$')
re_msvc_version = re.compile(r'^(?P<msvc_version>[1-9][0-9]?[.][0-9])(?P<suffix>[A-Z]+)*$', re.IGNORECASE)
re_extended_version = re.compile(r'''^
(?P<version>(?:
([1-9][0-9]?[.][0-9]{1,2})| # XX.Y - XX.YY
([1-9][0-9][.][0-9]{2}[.][0-9]{1,5})| # XX.YY.Z - XX.YY.ZZZZZ
([1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}) # XX.YY.AA.B - XX.YY.AA.BB
))
(?P<suffix>[A-Z]+)*
$''', re.IGNORECASE | re.VERBOSE)
re_toolset_full = re.compile(r'''^(?:
(?:[1-9][0-9][.][0-9]{1,2})| # XX.Y - XX.YY
(?:[1-9][0-9][.][0-9]{2}[.][0-9]{1,5}) # XX.YY.Z - XX.YY.ZZZZZ
)$''', re.VERBOSE)
re_toolset_140 = re.compile(r'''^(?:
(?:14[.]0{1,2})| # 14.0 - 14.00
(?:14[.]0{2}[.]0{1,5}) # 14.00.0 - 14.00.00000
)$''', re.VERBOSE)
re_toolset_sxs = re.compile(
r'^[1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}$' # MM.mm.VV.vv format
)
# msvc sdk version regexes
re_msvc_sdk_version = re.compile(r'''^
(?P<version>(?:
([1-9][0-9]?[.][0-9])| # XX.Y
([1-9][0-9][.][0-9]{1}[.][0-9]{5}[.][0-9]{1,2}) # XX.Y.ZZZZZ.A - XX.Y.ZZZZZ.AA
))
$''', re.IGNORECASE | re.VERBOSE)
# version prefix utilities
def get_version_prefix(version):
"""
Get the version number prefix from a string.
Args:
version: str
version specification
Returns:
str: the version number prefix
"""
rval = ''
if version:
m = re_version_prefix.match(version)
if m:
rval = m.group('version')
return rval
def get_msvc_version_prefix(version):
"""
Get the msvc version number prefix from a string.
Args:
version: str
version specification
Returns:
str: the msvc version number prefix
"""
rval = ''
if version:
m = re_msvc_version_prefix.match(version)
if m:
rval = m.group('version')
return rval
def get_msvc_version_prefix_suffix(version):
"""
Get the msvc version number prefix and suffix from a string.
Args:
version: str
version specification
Returns:
(str, str): the msvc version prefix and suffix
"""
prefix = suffix = ''
if version:
m = re_msvc_version.match(version)
if m:
prefix = m.group('msvc_version')
suffix = m.group('suffix') if m.group('suffix') else ''
return prefix, suffix
# toolset version query utilities
def is_toolset_full(toolset_version):
rval = False
if toolset_version:
if re_toolset_full.match(toolset_version):
rval = True
return rval
def is_toolset_140(toolset_version):
rval = False
if toolset_version:
if re_toolset_140.match(toolset_version):
rval = True
return rval
def is_toolset_sxs(toolset_version):
rval = False
if toolset_version:
if re_toolset_sxs.match(toolset_version):
rval = True
return rval
# msvc version and msvc toolset version decomposition utilties
_MSVC_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCVersionComponentsDefinition', [
'msvc_version', # msvc version (e.g., '14.1Exp')
'msvc_verstr', # msvc version numeric string (e.g., '14.1')
'msvc_suffix', # msvc version component type (e.g., 'Exp')
'msvc_vernum', # msvc version floating point number (e.g, 14.1)
'msvc_major', # msvc major version integer number (e.g., 14)
'msvc_minor', # msvc minor version integer number (e.g., 1)
'msvc_comps', # msvc version components tuple (e.g., ('14', '1'))
])
def msvc_version_components(vcver):
"""
Decompose an msvc version into components.
Tuple fields:
msvc_version: msvc version (e.g., '14.1Exp')
msvc_verstr: msvc version numeric string (e.g., '14.1')
msvc_suffix: msvc version component type (e.g., 'Exp')
msvc_vernum: msvc version floating point number (e.g., 14.1)
msvc_major: msvc major version integer number (e.g., 14)
msvc_minor: msvc minor version integer number (e.g., 1)
msvc_comps: msvc version components tuple (e.g., ('14', '1'))
Args:
vcver: str
msvc version specification
Returns:
None or MSVCVersionComponents namedtuple:
"""
if not vcver:
return None
m = re_msvc_version.match(vcver)
if not m:
return None
vs_def = Config.MSVC_VERSION_SUFFIX.get(vcver)
if not vs_def:
return None
msvc_version = vcver
msvc_verstr = m.group('msvc_version')
msvc_suffix = m.group('suffix') if m.group('suffix') else ''
msvc_vernum = float(msvc_verstr)
msvc_comps = tuple(msvc_verstr.split('.'))
msvc_major, msvc_minor = (int(x) for x in msvc_comps)
msvc_version_components_def = _MSVC_VERSION_COMPONENTS_DEFINITION(
msvc_version = msvc_version,
msvc_verstr = msvc_verstr,
msvc_suffix = msvc_suffix,
msvc_vernum = msvc_vernum,
msvc_major = msvc_major,
msvc_minor = msvc_minor,
msvc_comps = msvc_comps,
)
return msvc_version_components_def
_MSVC_EXTENDED_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCExtendedVersionComponentsDefinition', [
'msvc_version', # msvc version (e.g., '14.1Exp')
'msvc_verstr', # msvc version numeric string (e.g., '14.1')
'msvc_suffix', # msvc version component type (e.g., 'Exp')
'msvc_suffix_rank', # msvc version component rank (0, 1)
'msvc_vernum', # msvc version floating point number (e.g, 14.1)
'msvc_major', # msvc major version integer number (e.g., 14)
'msvc_minor', # msvc minor version integer number (e.g., 1)
'msvc_comps', # msvc version components tuple (e.g., ('14', '1'))
'msvc_buildtools', # msvc build tools
'msvc_buildtools_num', # msvc build tools integer number
'msvc_buildseries', # msvc build series
'msvc_buildseries_num', # msvc build series floating point number
'msvc_toolset_version', # msvc toolset version
'msvc_toolset_comps', # msvc toolset version components
'msvc_toolset_is_sxs', # msvc toolset version is sxs
'version', # msvc version or msvc toolset version
])
def msvc_extended_version_components(version):
"""
Decompose an msvc version or msvc toolset version into components.
Args:
version: str
version specification
Returns:
None or MSVCExtendedVersionComponents namedtuple:
"""
if not version:
return None
m = re_extended_version.match(version)
if not m:
return None
msvc_toolset_version = m.group('version')
msvc_toolset_comps = tuple(msvc_toolset_version.split('.'))
msvc_toolset_is_sxs = is_toolset_sxs(msvc_toolset_version)
vc_verstr = get_msvc_version_prefix(msvc_toolset_version)
if not vc_verstr:
return None
vc_buildseries_def = Config.MSVC_BUILDSERIES_EXTERNAL.get(vc_verstr)
if not vc_buildseries_def:
return None
vc_buildtools_def = Config.VC_BUILDTOOLS_MAP[vc_buildseries_def.vc_buildseries]
msvc_verstr = vc_buildtools_def.msvc_version
msvc_suffix = m.group('suffix') if m.group('suffix') else ''
msvc_version = msvc_verstr + msvc_suffix
vs_def = Config.MSVC_VERSION_SUFFIX.get(msvc_version)
if not vs_def:
return None
msvc_vernum = float(msvc_verstr)
msvc_comps = tuple(msvc_verstr.split('.'))
msvc_major, msvc_minor = (int(x) for x in msvc_comps)
msvc_extended_version_components_def = _MSVC_EXTENDED_VERSION_COMPONENTS_DEFINITION(
msvc_version = msvc_version,
msvc_verstr = msvc_verstr,
msvc_suffix = msvc_suffix,
msvc_suffix_rank = 0 if not msvc_suffix else 1,
msvc_vernum = msvc_vernum,
msvc_major = msvc_major,
msvc_minor = msvc_minor,
msvc_comps = msvc_comps,
msvc_buildtools = vc_buildtools_def.msvc_version,
msvc_buildtools_num = vc_buildtools_def.msvc_version_numeric,
msvc_buildseries = vc_buildseries_def.vc_version,
msvc_buildseries_num = vc_buildseries_def.vc_version_numeric,
msvc_toolset_version = msvc_toolset_version,
msvc_toolset_comps = msvc_toolset_comps,
msvc_toolset_is_sxs = msvc_toolset_is_sxs,
version = version,
)
return msvc_extended_version_components_def
# msvc sdk version decomposition utilties
_MSVC_SDK_VERSION_COMPONENTS_DEFINITION = namedtuple('MSVCSDKVersionComponentsDefinition', [
'sdk_version', # sdk version (e.g., '10.0.20348.0')
'sdk_verstr', # sdk version numeric string (e.g., '10.0')
'sdk_vernum', # sdk version floating point number (e.g, 10.0)
'sdk_major', # sdk major version integer number (e.g., 10)
'sdk_minor', # sdk minor version integer number (e.g., 0)
'sdk_comps', # sdk version components tuple (e.g., ('10', '0', '20348', '0'))
])
def msvc_sdk_version_components(version):
"""
Decompose an msvc sdk version into components.
Tuple fields:
sdk_version: sdk version (e.g., '10.0.20348.0')
sdk_verstr: sdk version numeric string (e.g., '10.0')
sdk_vernum: sdk version floating point number (e.g., 10.0)
sdk_major: sdk major version integer number (e.g., 10)
sdk_minor: sdk minor version integer number (e.g., 0)
sdk_comps: sdk version components tuple (e.g., ('10', '0', '20348', '0'))
Args:
version: str
sdk version specification
Returns:
None or MSVCSDKVersionComponents namedtuple:
"""
if not version:
return None
m = re_msvc_sdk_version.match(version)
if not m:
return None
sdk_version = version
sdk_comps = tuple(sdk_version.split('.'))
sdk_verstr = '.'.join(sdk_comps[:2])
sdk_vernum = float(sdk_verstr)
sdk_major, sdk_minor = (int(x) for x in sdk_comps[:2])
msvc_sdk_version_components_def = _MSVC_SDK_VERSION_COMPONENTS_DEFINITION(
sdk_version = sdk_version,
sdk_verstr = sdk_verstr,
sdk_vernum = sdk_vernum,
sdk_major = sdk_major,
sdk_minor = sdk_minor,
sdk_comps = sdk_comps,
)
return msvc_sdk_version_components_def

View File

@@ -0,0 +1,35 @@
# 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.
"""
Warnings for Microsoft Visual C/C++.
"""
import SCons.Warnings
class VisualCWarning(SCons.Warnings.WarningOnByDefault):
pass
class MSVCScriptExecutionWarning(VisualCWarning):
pass

View File

@@ -0,0 +1,264 @@
# 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.
"""
Windows SDK functions for Microsoft Visual C/C++.
"""
import os
from ..common import (
debug,
)
from . import Util
from . import Config
from . import Registry
from .Exceptions import (
MSVCInternalError,
)
from . import Dispatcher
Dispatcher.register_modulename(__name__)
_DESKTOP = Config.MSVC_PLATFORM_INTERNAL['Desktop']
_UWP = Config.MSVC_PLATFORM_INTERNAL['UWP']
def _new_sdk_map():
sdk_map = {
_DESKTOP.vc_platform: [],
_UWP.vc_platform: [],
}
return sdk_map
def _sdk_10_layout(version):
folder_prefix = version + '.'
sdk_map = _new_sdk_map()
sdk_roots = Registry.sdk_query_paths(version)
sdk_version_platform_seen = set()
sdk_roots_seen = set()
for sdk_t in sdk_roots:
sdk_root = sdk_t[0]
if sdk_root in sdk_roots_seen:
continue
sdk_roots_seen.add(sdk_root)
if not os.path.exists(sdk_root):
continue
sdk_include_path = os.path.join(sdk_root, 'include')
if not os.path.exists(sdk_include_path):
continue
for version_nbr, version_nbr_path in Util.listdir_dirs(sdk_include_path):
if not version_nbr.startswith(folder_prefix):
continue
sdk_inc_path = Util.normalize_path(os.path.join(version_nbr_path, 'um'))
if not os.path.exists(sdk_inc_path):
continue
for vc_platform, sdk_inc_file in [
(_DESKTOP.vc_platform, 'winsdkver.h'),
(_UWP.vc_platform, 'windows.h'),
]:
if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)):
continue
key = (version_nbr, vc_platform)
if key in sdk_version_platform_seen:
continue
sdk_version_platform_seen.add(key)
sdk_map[vc_platform].append(version_nbr)
for key, val in sdk_map.items():
val.sort(reverse=True)
return sdk_map
def _sdk_81_layout(version):
version_nbr = version
sdk_map = _new_sdk_map()
sdk_roots = Registry.sdk_query_paths(version)
sdk_version_platform_seen = set()
sdk_roots_seen = set()
for sdk_t in sdk_roots:
sdk_root = sdk_t[0]
if sdk_root in sdk_roots_seen:
continue
sdk_roots_seen.add(sdk_root)
# msvc does not check for existence of root or other files
sdk_inc_path = Util.normalize_path(os.path.join(sdk_root, r'include\um'))
if not os.path.exists(sdk_inc_path):
continue
for vc_platform, sdk_inc_file in [
(_DESKTOP.vc_platform, 'winsdkver.h'),
(_UWP.vc_platform, 'windows.h'),
]:
if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)):
continue
key = (version_nbr, vc_platform)
if key in sdk_version_platform_seen:
continue
sdk_version_platform_seen.add(key)
sdk_map[vc_platform].append(version_nbr)
for key, val in sdk_map.items():
val.sort(reverse=True)
return sdk_map
_sdk_map_cache = {}
_sdk_cache = {}
def _reset_sdk_cache():
global _sdk_map_cache
global _sdk_cache
debug('')
_sdk_map_cache = {}
_sdk_cache = {}
def _sdk_10(key, reg_version):
if key in _sdk_map_cache:
sdk_map = _sdk_map_cache[key]
else:
sdk_map = _sdk_10_layout(reg_version)
_sdk_map_cache[key] = sdk_map
return sdk_map
def _sdk_81(key, reg_version):
if key in _sdk_map_cache:
sdk_map = _sdk_map_cache[key]
else:
sdk_map = _sdk_81_layout(reg_version)
_sdk_map_cache[key] = sdk_map
return sdk_map
def _combine_sdk_map_list(sdk_map_list):
combined_sdk_map = _new_sdk_map()
for sdk_map in sdk_map_list:
for key, val in sdk_map.items():
combined_sdk_map[key].extend(val)
return combined_sdk_map
_sdk_dispatch_map = {
'10.0': (_sdk_10, '10.0'),
'8.1': (_sdk_81, '8.1'),
}
def _verify_sdk_dispatch_map():
debug('')
for sdk_version in Config.MSVC_SDK_VERSIONS:
if sdk_version in _sdk_dispatch_map:
continue
err_msg = f'sdk version {sdk_version} not in sdk_dispatch_map'
raise MSVCInternalError(err_msg)
return None
def _version_list_sdk_map(version_list):
sdk_map_list = []
for version in version_list:
func, reg_version = _sdk_dispatch_map[version]
sdk_map = func(version, reg_version)
sdk_map_list.append(sdk_map)
combined_sdk_map = _combine_sdk_map_list(sdk_map_list)
return combined_sdk_map
def _sdk_map(version_list):
key = tuple(version_list)
if key in _sdk_cache:
sdk_map = _sdk_cache[key]
else:
version_numlist = [float(v) for v in version_list]
version_numlist.sort(reverse=True)
key = tuple([str(v) for v in version_numlist])
sdk_map = _version_list_sdk_map(key)
_sdk_cache[key] = sdk_map
return sdk_map
def get_msvc_platform(is_uwp=False):
platform_def = _UWP if is_uwp else _DESKTOP
return platform_def
def get_sdk_version_list(vs_def, platform_def):
version_list = vs_def.vc_sdk_versions if vs_def.vc_sdk_versions is not None else []
sdk_map = _sdk_map(version_list)
sdk_list = sdk_map.get(platform_def.vc_platform, [])
return sdk_list
def get_msvc_sdk_version_list(msvc_version, msvc_uwp_app=False):
debug('msvc_version=%s, msvc_uwp_app=%s', repr(msvc_version), repr(msvc_uwp_app))
sdk_versions = []
verstr = Util.get_msvc_version_prefix(msvc_version)
if not verstr:
debug('msvc_version is not defined')
return sdk_versions
vs_def = Config.MSVC_VERSION_EXTERNAL.get(verstr, None)
if not vs_def:
debug('vs_def is not defined')
return sdk_versions
is_uwp = True if msvc_uwp_app in Config.BOOLEAN_SYMBOLS[True] else False
platform_def = get_msvc_platform(is_uwp)
sdk_list = get_sdk_version_list(vs_def, platform_def)
sdk_versions.extend(sdk_list)
debug('sdk_versions=%s', repr(sdk_versions))
return sdk_versions
def reset():
debug('')
_reset_sdk_cache()
def verify():
debug('')
_verify_sdk_dispatch_map()

View File

@@ -0,0 +1,56 @@
# 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.
"""
Functions for Microsoft Visual C/C++.
The _reset method is used to restore MSVC module data structures to their
initial state for testing purposes.
The _verify method is used as a sanity check that MSVC module data structures
are internally consistent.
Currently:
* _reset is invoked from reset_installed_vcs in the vc module.
* _verify is invoked from the last line in the vc module.
"""
from . import Exceptions # noqa: F401
from . import Config # noqa: F401
from . import Util # noqa: F401
from . import Registry # noqa: F401
from . import Kind # noqa: F401
from . import SetupEnvDefault # noqa: F401
from . import Policy # noqa: F401
from . import WinSDK # noqa: F401
from . import ScriptArguments # noqa: F401
from . import Dispatcher as _Dispatcher
def _reset():
_Dispatcher.reset()
def _verify():
_Dispatcher.verify()

View File

@@ -0,0 +1,101 @@
# 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.
"""
Common functions for Microsoft Visual Studio and Visual C/C++.
"""
import SCons.Errors
import SCons.Platform.win32
import SCons.Util # noqa: F401
from SCons.Tool.MSCommon.sdk import ( # noqa: F401
mssdk_exists,
mssdk_setup_env,
)
from SCons.Tool.MSCommon.vc import ( # noqa: F401
msvc_exists,
msvc_setup_env_tool,
msvc_setup_env_once,
msvc_version_to_maj_min,
msvc_find_vswhere,
msvc_sdk_versions,
msvc_toolset_versions,
msvc_toolset_versions_spectre,
msvc_query_version_toolset,
vswhere_register_executable,
vswhere_get_executable,
vswhere_freeze_executable,
)
from SCons.Tool.MSCommon.vs import ( # noqa: F401
get_default_version,
get_vs_by_version,
merge_default_version,
msvs_exists,
query_versions,
)
from .MSVC.Policy import ( # noqa: F401
msvc_set_notfound_policy,
msvc_get_notfound_policy,
msvc_set_scripterror_policy,
msvc_get_scripterror_policy,
msvc_notfound_policy_contextmanager,
msvc_scripterror_policy_contextmanager,
)
from .MSVC.Exceptions import ( # noqa: F401
VisualCException,
MSVCInternalError,
MSVCUserError,
MSVCScriptExecutionError,
MSVCVersionNotFound,
MSVCSDKVersionNotFound,
MSVCToolsetVersionNotFound,
MSVCSpectreLibsNotFound,
MSVCArgumentError,
)
from .vc import ( # noqa: F401
MSVCUnsupportedHostArch,
MSVCUnsupportedTargetArch,
MSVCScriptNotFound,
MSVCUseScriptError,
MSVCUseSettingsError,
VSWhereUserError,
)
from .MSVC.Util import ( # noqa: F401
msvc_version_components,
msvc_extended_version_components,
msvc_sdk_version_components,
)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,66 @@
# 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.
"""
MS compilers: Supported Windows chip architectures.
"""
class ArchDefinition:
"""
A class for defining architecture-specific settings and logic.
"""
def __init__(self, arch, synonyms=[]):
self.arch = arch
self.synonyms = synonyms
SupportedArchitectureList = [
ArchDefinition(
'x86',
['i386', 'i486', 'i586', 'i686'],
),
ArchDefinition(
'x86_64',
['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'],
),
ArchDefinition(
'ia64',
['IA64'],
),
ArchDefinition(
'arm',
['ARM'],
),
]
SupportedArchitectureMap = {}
for a in SupportedArchitectureList:
SupportedArchitectureMap[a.arch] = a
for s in a.synonyms:
SupportedArchitectureMap[s] = a
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,635 @@
# 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.
"""
Common helper functions for working with the Microsoft tool chain.
"""
import copy
import json
import os
import re
import sys
import time
from contextlib import suppress
from subprocess import DEVNULL, PIPE
from pathlib import Path
import SCons.Errors
import SCons.Util
import SCons.Warnings
# 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', # path to "shell"
'OS', # name of OS family: Windows_NT or undefined (95/98/ME)
'VS180COMNTOOLS', # path to common tools for given version
'VS170COMNTOOLS',
'VS160COMNTOOLS',
'VS150COMNTOOLS',
'VS140COMNTOOLS',
'VS120COMNTOOLS',
'VS110COMNTOOLS',
'VS100COMNTOOLS',
'VS90COMNTOOLS',
'VS80COMNTOOLS',
'VS71COMNTOOLS',
'VSCOMNTOOLS',
'MSDevDir',
'VSCMD_DEBUG', # enable logging and other debug aids
'VSCMD_SKIP_SENDTELEMETRY',
'windir', # windows directory (SystemRoot not available in 95/98/ME)
'VCPKG_DISABLE_METRICS',
'VCPKG_ROOT',
'POWERSHELL_TELEMETRY_OPTOUT',
'PSDisableModuleAnalysisCacheCleanup',
'PSModuleAnalysisCachePath',
]
class MSVCCacheInvalidWarning(SCons.Warnings.WarningOnByDefault):
pass
def _check_logfile(logfile):
if logfile and '"' in logfile:
err_msg = (
"SCONS_MSCOMMON_DEBUG value contains double quote character(s)\n"
f" SCONS_MSCOMMON_DEBUG={logfile}"
)
raise SCons.Errors.UserError(err_msg)
return logfile
# SCONS_MSCOMMON_DEBUG is internal-use so undocumented:
# set to '-' to print to console, else set to filename to log to
LOGFILE = _check_logfile(os.environ.get('SCONS_MSCOMMON_DEBUG'))
if LOGFILE:
import logging
class _Debug_Filter(logging.Filter):
# custom filter for module relative filename
modulelist = (
# root module and parent/root module
'MSCommon', 'Tool',
# python library and below: correct iff scons does not have a lib folder
'lib',
# scons modules
'SCons', 'test', 'scons'
)
def get_relative_filename(self, filename, module_list):
if not filename:
return filename
for module in module_list:
try:
ind = filename.rindex(module)
return filename[ind:]
except ValueError:
pass
return filename
def filter(self, record):
relfilename = self.get_relative_filename(record.pathname, self.modulelist)
relfilename = relfilename.replace('\\', '/')
record.relfilename = relfilename
return True
class _CustomFormatter(logging.Formatter):
# Log format looks like:
# 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [file]
# debug: 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [stdout]
log_format=(
'%(relativeCreated)05dms'
':%(relfilename)s'
':%(funcName)s'
'#%(lineno)s'
': %(message)s'
)
log_format_classname=(
'%(relativeCreated)05dms'
':%(relfilename)s'
':%(classname)s'
'.%(funcName)s'
'#%(lineno)s'
': %(message)s'
)
def __init__(self, log_prefix):
super().__init__()
if log_prefix:
self.log_format = log_prefix + self.log_format
self.log_format_classname = log_prefix + self.log_format_classname
log_record = logging.LogRecord(
'', # name (str)
0, # level (int)
'', # pathname (str)
0, # lineno (int)
None, # msg (Any)
{}, # args (tuple | dict[str, Any])
None # exc_info (tuple[type[BaseException], BaseException, types.TracebackType] | None)
)
self.default_attrs = set(log_record.__dict__.keys())
self.default_attrs.add('relfilename')
def format(self, record):
extras = set(record.__dict__.keys()) - self.default_attrs
if 'classname' in extras:
log_format = self.log_format_classname
else:
log_format = self.log_format
formatter = logging.Formatter(log_format)
return formatter.format(record)
if LOGFILE == '-':
log_prefix = 'debug: '
log_handler = logging.StreamHandler(sys.stdout)
else:
log_prefix = ''
try:
log_handler = logging.FileHandler(filename=LOGFILE)
except (OSError, FileNotFoundError) as e:
err_msg = (
"Could not create logfile, check SCONS_MSCOMMON_DEBUG\n"
f" SCONS_MSCOMMON_DEBUG={LOGFILE}\n"
f" {e.__class__.__name__}: {str(e)}"
)
raise SCons.Errors.UserError(err_msg)
log_formatter = _CustomFormatter(log_prefix)
log_handler.setFormatter(log_formatter)
logger = logging.getLogger(name=__name__)
logger.setLevel(level=logging.DEBUG)
logger.addHandler(log_handler)
logger.addFilter(_Debug_Filter())
debug = logger.debug
def debug_extra(cls=None):
if cls:
extra = {'classname': cls.__qualname__}
else:
extra = None
return extra
DEBUG_ENABLED = True
else:
def debug(x, *args, **kwargs):
return None
def debug_extra(*args, **kwargs):
return None
DEBUG_ENABLED = False
# SCONS_CACHE_MSVC_CONFIG is public, and is documented.
CONFIG_CACHE = os.environ.get('SCONS_CACHE_MSVC_CONFIG', '')
if CONFIG_CACHE in ('1', 'true', 'True'):
CONFIG_CACHE = os.path.join(os.path.expanduser('~'), 'scons_msvc_cache.json')
# SCONS_CACHE_MSVC_FORCE_DEFAULTS is internal-use so undocumented.
CONFIG_CACHE_FORCE_DEFAULT_ARGUMENTS = False
if CONFIG_CACHE:
if os.environ.get('SCONS_CACHE_MSVC_FORCE_DEFAULTS') in ('1', 'true', 'True'):
CONFIG_CACHE_FORCE_DEFAULT_ARGUMENTS = True
def read_script_env_cache():
""" fetch cached msvc env vars if requested, else return empty dict """
envcache = {}
p = Path(CONFIG_CACHE)
if not CONFIG_CACHE or not p.is_file():
return envcache
with SCons.Util.FileLock(CONFIG_CACHE, timeout=5, writer=False), p.open('r') as f:
# Convert the list of cache entry dictionaries read from
# json to the cache dictionary. Reconstruct the cache key
# tuple from the key list written to json.
# Note we need to take a write lock on the cachefile, as if there's
# an error and we try to remove it, that's "writing" on Windows.
try:
envcache_list = json.load(f)
except json.JSONDecodeError:
# If we couldn't decode it, it could be corrupt. Toss.
with suppress(FileNotFoundError):
p.unlink()
warn_msg = "Could not decode msvc cache file %s: dropping."
SCons.Warnings.warn(MSVCCacheInvalidWarning, warn_msg % CONFIG_CACHE)
debug(warn_msg, CONFIG_CACHE)
else:
if isinstance(envcache_list, list):
envcache = {tuple(d['key']): d['data'] for d in envcache_list}
else:
# don't fail if incompatible format, just proceed without it
warn_msg = "Incompatible format for msvc cache file %s: file may be overwritten."
SCons.Warnings.warn(MSVCCacheInvalidWarning, warn_msg % CONFIG_CACHE)
debug(warn_msg, CONFIG_CACHE)
return envcache
def write_script_env_cache(cache):
""" write out cache of msvc env vars if requested """
if not CONFIG_CACHE:
return
p = Path(CONFIG_CACHE)
try:
with SCons.Util.FileLock(CONFIG_CACHE, timeout=5, writer=True), p.open('w') as f:
# Convert the cache dictionary to a list of cache entry
# dictionaries. The cache key is converted from a tuple to
# a list for compatibility with json.
envcache_list = [
{'key': list(key), 'data': data} for key, data in cache.items()
]
json.dump(envcache_list, f, indent=2)
except TypeError:
# data can't serialize to json, don't leave partial file
with suppress(FileNotFoundError):
p.unlink()
except OSError:
# can't write the file, just skip
pass
return
_is_win64 = None
def is_win64():
"""Return true if running on windows 64 bits.
Works whether python itself runs in 64 bits or 32 bits."""
# Unfortunately, python does not provide a useful way to determine
# if the underlying Windows OS is 32-bit or 64-bit. Worse, whether
# the Python itself is 32-bit or 64-bit affects what it returns,
# so nothing in sys.* or os.* help.
# Apparently the best solution is to use env vars that Windows
# sets. If PROCESSOR_ARCHITECTURE is not x86, then the python
# process is running in 64 bit mode (on a 64-bit OS, 64-bit
# hardware, obviously).
# If this python is 32-bit but the OS is 64, Windows will set
# ProgramW6432 and PROCESSOR_ARCHITEW6432 to non-null.
# (Checking for HKLM\Software\Wow6432Node in the registry doesn't
# work, because some 32-bit installers create it.)
global _is_win64
if _is_win64 is None:
# I structured these tests to make it easy to add new ones or
# add exceptions in the future, because this is a bit fragile.
_is_win64 = False
if os.environ.get('PROCESSOR_ARCHITECTURE', 'x86') != 'x86':
_is_win64 = True
if os.environ.get('PROCESSOR_ARCHITEW6432'):
_is_win64 = True
if os.environ.get('ProgramW6432'):
_is_win64 = True
return _is_win64
def read_reg(value, hkroot=SCons.Util.HKEY_LOCAL_MACHINE):
return SCons.Util.RegGetValue(hkroot, value)[0]
def has_reg(value):
"""Return True if the given key exists in HKEY_LOCAL_MACHINE."""
try:
SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, value)
ret = True
except OSError:
ret = False
return ret
# Functions for fetching environment variable settings from batch files.
def _force_vscmd_skip_sendtelemetry(env):
if 'VSCMD_SKIP_SENDTELEMETRY' in env['ENV']:
return False
env['ENV']['VSCMD_SKIP_SENDTELEMETRY'] = '1'
debug("force env['ENV']['VSCMD_SKIP_SENDTELEMETRY']=%s", env['ENV']['VSCMD_SKIP_SENDTELEMETRY'])
return True
class _PathManager:
_PSEXECUTABLES = (
"pwsh.exe",
"powershell.exe",
)
_PSMODULEPATH_MAP = {os.path.normcase(os.path.abspath(p)): p for p in [
# os.path.expandvars(r"%USERPROFILE%\Documents\PowerShell\Modules"), # current user
os.path.expandvars(r"%ProgramFiles%\PowerShell\Modules"), # all users
os.path.expandvars(r"%ProgramFiles%\PowerShell\7\Modules"), # installation location
# os.path.expandvars(r"%USERPROFILE%\Documents\WindowsPowerShell\Modules"), # current user
os.path.expandvars(r"%ProgramFiles%\WindowsPowerShell\Modules"), # all users
os.path.expandvars(r"%windir%\System32\WindowsPowerShell\v1.0\Modules"), # installation location
]}
_cache_norm_path = {}
@classmethod
def _get_norm_path(cls, p):
norm_path = cls._cache_norm_path.get(p)
if norm_path is None:
norm_path = os.path.normcase(os.path.abspath(p))
cls._cache_norm_path[p] = norm_path
cls._cache_norm_path[norm_path] = norm_path
return norm_path
_cache_is_psmodulepath = {}
@classmethod
def _is_psmodulepath(cls, p):
is_psmodulepath = cls._cache_is_psmodulepath.get(p)
if is_psmodulepath is None:
norm_path = cls._get_norm_path(p)
is_psmodulepath = bool(norm_path in cls._PSMODULEPATH_MAP)
cls._cache_is_psmodulepath[p] = is_psmodulepath
cls._cache_is_psmodulepath[norm_path] = is_psmodulepath
return is_psmodulepath
_cache_psmodulepath_paths = {}
@classmethod
def get_psmodulepath_paths(cls, pathspec):
psmodulepath_paths = cls._cache_psmodulepath_paths.get(pathspec)
if psmodulepath_paths is None:
psmodulepath_paths = []
for p in pathspec.split(os.pathsep):
p = p.strip()
if not p:
continue
if not cls._is_psmodulepath(p):
continue
psmodulepath_paths.append(p)
psmodulepath_paths = tuple(psmodulepath_paths)
cls._cache_psmodulepath_paths[pathspec] = psmodulepath_paths
return psmodulepath_paths
_cache_psexe_paths = {}
@classmethod
def get_psexe_paths(cls, pathspec):
psexe_paths = cls._cache_psexe_paths.get(pathspec)
if psexe_paths is None:
psexe_set = set(cls._PSEXECUTABLES)
psexe_paths = []
for p in pathspec.split(os.pathsep):
p = p.strip()
if not p:
continue
for psexe in psexe_set:
psexe_path = os.path.join(p, psexe)
if not os.path.exists(psexe_path):
continue
psexe_paths.append(p)
psexe_set.remove(psexe)
break
if psexe_set:
continue
break
psexe_paths = tuple(psexe_paths)
cls._cache_psexe_paths[pathspec] = psexe_paths
return psexe_paths
_cache_minimal_pathspec = {}
@classmethod
def get_minimal_pathspec(cls, pathlist):
pathlist_t = tuple(pathlist)
minimal_pathspec = cls._cache_minimal_pathspec.get(pathlist_t)
if minimal_pathspec is None:
minimal_paths = []
seen = set()
for p in pathlist:
p = p.strip()
if not p:
continue
norm_path = cls._get_norm_path(p)
if norm_path in seen:
continue
seen.add(norm_path)
minimal_paths.append(p)
minimal_pathspec = os.pathsep.join(minimal_paths)
cls._cache_minimal_pathspec[pathlist_t] = minimal_pathspec
return minimal_pathspec
def normalize_env(env, keys, force=False):
"""Given a dictionary representing a shell environment, add the variables
from os.environ needed for the processing of .bat files; the keys are
controlled by the keys argument.
It also makes sure the environment values are correctly encoded.
If force=True, then all of the key values that exist are copied
into the returned dictionary. If force=false, values are only
copied if the key does not already exist in the copied dictionary.
Note: the environment is copied."""
normenv = {}
if env:
for k, v in env.items():
normenv[k] = copy.deepcopy(v)
for k in keys:
if k in os.environ and (force or k not in normenv):
normenv[k] = os.environ[k]
debug("keys: normenv[%s]=%s", k, normenv[k])
else:
debug("keys: skipped[%s]", k)
syspath_pathlist = normenv.get("PATH", "").split(os.pathsep)
# 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"
)
syspath_pathlist.append(sys32_dir)
# Without Wbem in PATH, vcvarsall.bat has a "'wmic' is not recognized"
# error starting with Visual Studio 2017, although the script still
# seems to work anyway.
sys32_wbem_dir = os.path.join(sys32_dir, 'Wbem')
syspath_pathlist.append(sys32_wbem_dir)
# 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.
# Find the powershell executable paths. Add the known powershell.exe
# path to the end of the shell system path (just in case).
# The VS vcpkg component prefers pwsh.exe if it's on the path.
sys32_ps_dir = os.path.join(sys32_dir, 'WindowsPowerShell', 'v1.0')
psexe_searchlist = os.pathsep.join([os.environ.get("PATH", ""), sys32_ps_dir])
psexe_pathlist = _PathManager.get_psexe_paths(psexe_searchlist)
# Add powershell executable paths in the order discovered.
syspath_pathlist.extend(psexe_pathlist)
normenv['PATH'] = _PathManager.get_minimal_pathspec(syspath_pathlist)
debug("PATH: %s", normenv['PATH'])
# Add psmodulepath paths in the order discovered.
psmodulepath_pathlist = _PathManager.get_psmodulepath_paths(os.environ.get("PSModulePath", ""))
if psmodulepath_pathlist:
normenv["PSModulePath"] = _PathManager.get_minimal_pathspec(psmodulepath_pathlist)
debug("PSModulePath: %s", normenv.get('PSModulePath',''))
return normenv
def get_output(vcbat, args=None, env=None, skip_sendtelemetry=False):
"""Parse the output of given bat file, with given args."""
if env is None:
# Create a blank environment, for use in launching the tools
env = SCons.Environment.Environment(tools=[])
env['ENV'] = normalize_env(env['ENV'], VS_VC_VARS, force=False)
if skip_sendtelemetry:
_force_vscmd_skip_sendtelemetry(env)
# debug("ENV=%r", env['ENV'])
if args:
debug("Calling '%s %s'", vcbat, args)
cmd_str = '"%s" %s & set' % (vcbat, args)
else:
debug("Calling '%s'", vcbat)
cmd_str = '"%s" & set' % vcbat
beg_time = time.time()
cp = SCons.Action.scons_subproc_run(
env, cmd_str, stdin=DEVNULL, stdout=PIPE, stderr=PIPE,
)
end_time = time.time()
debug("Elapsed %.2fs", end_time - beg_time)
# Extra debug logic, uncomment if necessary
# debug('stdout:%s', cp.stdout)
# debug('stderr:%s', cp.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.
OEM = "oem"
if cp.stderr:
# TODO: find something better to do with stderr;
# this at least prevents errors from getting swallowed.
sys.stderr.write(cp.stderr.decode(OEM))
if cp.returncode != 0:
raise OSError(cp.stderr.decode(OEM))
return cp.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
)
# Nuitka: Keep the Windows SDK version too
KEEPLIST += ("WindowsSDKVersion",)
def parse_output(output, keep=KEEPLIST):
"""
Parse output from running visual c++/studios vcvarsall.bat and running set
To capture the values listed in keep
"""
# dkeep is a dict associating key: path_list, where key is one item from
# keep, and path_list the associated list of paths
dkeep = {i: [] for i in keep}
# rdk will keep the regex to match the .bat file output line starts
rdk = {}
for i in keep:
rdk[i] = re.compile(r'%s=(.*)' % i, re.I)
def add_env(rmatch, key, dkeep=dkeep):
path_list = rmatch.group(1).split(os.pathsep)
for path in path_list:
# Do not add empty paths (when a var ends with ;)
if path:
# XXX: For some reason, VC98 .bat file adds "" around the PATH
# values, and it screws up the environment later, so we strip
# it.
path = path.strip('"')
dkeep[key].append(str(path))
debug("dkeep[%s].append(%r)", key, path)
for line in output.splitlines():
for k, value in rdk.items():
match = value.match(line)
if match:
add_env(match, k)
return dkeep
def get_pch_node(env, target, source):
"""
Get the actual PCH file node
"""
pch_subst = env.get('PCH', False) and env.subst('$PCH',target=target, source=source, conv=lambda x:x)
if not pch_subst:
return ""
if SCons.Util.is_String(pch_subst):
pch_subst = target[0].dir.File(pch_subst)
return pch_subst
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,82 @@
# 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.
"""
MS Compilers: .Net Framework support
"""
import os
import re
from .common import read_reg, debug
# Original value recorded by dcournapeau
_FRAMEWORKDIR_HKEY_ROOT = r'Software\Microsoft\.NETFramework\InstallRoot'
# On SGK's system
_FRAMEWORKDIR_HKEY_ROOT = r'Software\Microsoft\Microsoft SDKs\.NETFramework\v2.0\InstallationFolder'
def find_framework_root():
# XXX: find it from environment (FrameworkDir)
try:
froot = read_reg(_FRAMEWORKDIR_HKEY_ROOT)
debug("Found framework install root in registry: %s", froot)
except OSError:
debug("Could not read reg key %s", _FRAMEWORKDIR_HKEY_ROOT)
return None
if not os.path.exists(froot):
debug("%s not found on fs", froot)
return None
return froot
def query_versions():
froot = find_framework_root()
if froot:
contents = os.listdir(froot)
l = re.compile('v[0-9]+.*')
versions = [e for e in contents if l.match(e)]
def versrt(a,b):
# since version numbers aren't really floats...
aa = a[1:]
bb = b[1:]
aal = aa.split('.')
bbl = bb.split('.')
# sequence comparison in python is lexicographical
# which is exactly what we want.
# Note we sort backwards so the highest version is first.
return (aal > bbl) - (aal < bbl)
versions.sort(versrt)
else:
versions = []
return versions
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,412 @@
# 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.
"""
MS Compilers: detect the Platform/Windows SDK
PSDK 2003 R1 is the earliest version detected.
"""
import os
import SCons.Errors
import SCons.Util
from .common import debug, read_reg
# SDK Checks. This is of course a mess as everything else on MS platforms. Here
# is what we do to detect the SDK:
#
# For Windows SDK >= 6.0: just look into the registry entries:
# HKLM\Software\Microsoft\Microsoft SDKs\Windows
# All the keys in there are the available versions.
#
# For Platform SDK before 6.0 (2003 server R1 and R2, etc...), there does not
# seem to be any sane registry key, so the precise location is hardcoded.
#
# For versions below 2003R1, it seems the PSDK is included with Visual Studio?
# VC++ Professional comes with the SDK, VC++ Express does not.
#
# Of course, all this changed again after Express was phased out (2005).
# Location of the SDK (checked for 6.1 only)
_CURINSTALLED_SDK_HKEY_ROOT = \
r"Software\Microsoft\Microsoft SDKs\Windows\CurrentInstallFolder"
class SDKDefinition:
"""
An abstract base class for trying to find installed SDK directories.
"""
def __init__(self, version, **kw):
self.version = version
self.__dict__.update(kw)
def find_sdk_dir(self):
"""Try to find the MS SDK from the registry.
Return None if failed or the directory does not exist.
"""
if not SCons.Util.can_read_reg:
debug('find_sdk_dir(): can not read registry')
return None
hkey = self.HKEY_FMT % self.hkey_data
debug('find_sdk_dir(): checking registry: %s', hkey)
try:
sdk_dir = read_reg(hkey)
except OSError:
debug('find_sdk_dir(): no SDK registry key %s', hkey)
return None
debug('find_sdk_dir(): Trying SDK Dir: %s', sdk_dir)
if not os.path.exists(sdk_dir):
debug('find_sdk_dir(): %s not on file system', sdk_dir)
return None
ftc = os.path.join(sdk_dir, self.sanity_check_file)
if not os.path.exists(ftc):
debug("find_sdk_dir(): sanity check %s not found", ftc)
return None
return sdk_dir
def get_sdk_dir(self):
"""Return the MSSSDK given the version string."""
try:
return self._sdk_dir
except AttributeError:
sdk_dir = self.find_sdk_dir()
self._sdk_dir = sdk_dir
return sdk_dir
def get_sdk_vc_script(self,host_arch, target_arch):
""" Return the script to initialize the VC compiler installed by SDK
"""
if host_arch == 'amd64' and target_arch == 'x86':
# No cross tools needed compiling 32 bits on 64 bit machine
host_arch=target_arch
arch_string=target_arch
if host_arch != target_arch:
arch_string='%s_%s'%(host_arch,target_arch)
debug(
"get_sdk_vc_script():arch_string:%s host_arch:%s target_arch:%s",
arch_string,
host_arch,
target_arch,
)
file = self.vc_setup_scripts.get(arch_string, None)
debug("get_sdk_vc_script():file:%s", file)
return file
class WindowsSDK(SDKDefinition):
"""
A subclass for trying to find installed Windows SDK directories.
"""
HKEY_FMT = r'Software\Microsoft\Microsoft SDKs\Windows\v%s\InstallationFolder'
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.hkey_data = self.version
class PlatformSDK(SDKDefinition):
"""
A subclass for trying to find installed Platform SDK directories.
"""
HKEY_FMT = r'Software\Microsoft\MicrosoftSDK\InstalledSDKS\%s\Install Dir'
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.hkey_data = self.uuid
#
# The list of VC initialization scripts installed by the SDK
# These should be tried if the vcvarsall.bat TARGET_ARCH fails
preSDK61VCSetupScripts = { 'x86' : r'bin\vcvars32.bat',
'amd64' : r'bin\vcvarsamd64.bat',
'x86_amd64': r'bin\vcvarsx86_amd64.bat',
'x86_ia64' : r'bin\vcvarsx86_ia64.bat',
'ia64' : r'bin\vcvarsia64.bat'}
SDK61VCSetupScripts = {'x86' : r'bin\vcvars32.bat',
'amd64' : r'bin\amd64\vcvarsamd64.bat',
'x86_amd64': r'bin\x86_amd64\vcvarsx86_amd64.bat',
'x86_ia64' : r'bin\x86_ia64\vcvarsx86_ia64.bat',
'ia64' : r'bin\ia64\vcvarsia64.bat'}
SDK70VCSetupScripts = { 'x86' : r'bin\vcvars32.bat',
'amd64' : r'bin\vcvars64.bat',
'x86_amd64': r'bin\vcvarsx86_amd64.bat',
'x86_ia64' : r'bin\vcvarsx86_ia64.bat',
'ia64' : r'bin\vcvarsia64.bat'}
SDK100VCSetupScripts = {'x86' : r'bin\vcvars32.bat',
'amd64' : r'bin\vcvars64.bat',
'x86_amd64': r'bin\x86_amd64\vcvarsx86_amd64.bat',
'x86_arm' : r'bin\x86_arm\vcvarsx86_arm.bat'}
# The list of support SDKs which we know how to detect.
#
# The first SDK found in the list is the one used by default if there
# are multiple SDKs installed. Barring good reasons to the contrary,
# this means we should list SDKs from most recent to oldest.
#
# If you update this list, update the documentation in Tool/mssdk.xml.
SupportedSDKList = [
WindowsSDK('10.0A',
sanity_check_file=r'bin\SetEnv.Cmd',
include_subdir='include',
lib_subdir={
'x86' : ['lib'],
'x86_64' : [r'lib\x64'],
'ia64' : [r'lib\ia64'],
},
vc_setup_scripts = SDK70VCSetupScripts,
),
WindowsSDK('10.0',
sanity_check_file=r'bin\SetEnv.Cmd',
include_subdir='include',
lib_subdir={
'x86' : ['lib'],
'x86_64' : [r'lib\x64'],
'ia64' : [r'lib\ia64'],
},
vc_setup_scripts = SDK70VCSetupScripts,
),
WindowsSDK('7.1',
sanity_check_file=r'bin\SetEnv.Cmd',
include_subdir='include',
lib_subdir={
'x86' : ['lib'],
'x86_64' : [r'lib\x64'],
'ia64' : [r'lib\ia64'],
},
vc_setup_scripts = SDK70VCSetupScripts,
),
WindowsSDK('7.0A',
sanity_check_file=r'bin\SetEnv.Cmd',
include_subdir='include',
lib_subdir={
'x86' : ['lib'],
'x86_64' : [r'lib\x64'],
'ia64' : [r'lib\ia64'],
},
vc_setup_scripts = SDK70VCSetupScripts,
),
WindowsSDK('7.0',
sanity_check_file=r'bin\SetEnv.Cmd',
include_subdir='include',
lib_subdir={
'x86' : ['lib'],
'x86_64' : [r'lib\x64'],
'ia64' : [r'lib\ia64'],
},
vc_setup_scripts = SDK70VCSetupScripts,
),
WindowsSDK('6.1',
sanity_check_file=r'bin\SetEnv.Cmd',
include_subdir='include',
lib_subdir={
'x86' : ['lib'],
'x86_64' : [r'lib\x64'],
'ia64' : [r'lib\ia64'],
},
vc_setup_scripts = SDK61VCSetupScripts,
),
WindowsSDK('6.0A',
sanity_check_file=r'include\windows.h',
include_subdir='include',
lib_subdir={
'x86' : ['lib'],
'x86_64' : [r'lib\x64'],
'ia64' : [r'lib\ia64'],
},
vc_setup_scripts = preSDK61VCSetupScripts,
),
WindowsSDK('6.0',
sanity_check_file=r'bin\gacutil.exe',
include_subdir='include',
lib_subdir='lib',
vc_setup_scripts = preSDK61VCSetupScripts,
),
PlatformSDK('2003R2',
sanity_check_file=r'SetEnv.Cmd',
uuid="D2FF9F89-8AA2-4373-8A31-C838BF4DBBE1",
vc_setup_scripts = preSDK61VCSetupScripts,
),
PlatformSDK('2003R1',
sanity_check_file=r'SetEnv.Cmd',
uuid="8F9E5EF3-A9A5-491B-A889-C58EFFECE8B3",
vc_setup_scripts = preSDK61VCSetupScripts,
),
]
SupportedSDKMap = {}
for sdk in SupportedSDKList:
SupportedSDKMap[sdk.version] = sdk
# Finding installed SDKs isn't cheap, because it goes not only to the
# registry but also to the disk to sanity-check that there is, in fact,
# an SDK installed there and that the registry entry isn't just stale.
# Find this information once, when requested, and cache it.
InstalledSDKList = None
InstalledSDKMap = None
def get_installed_sdks():
global InstalledSDKList
global InstalledSDKMap
debug('get_installed_sdks()')
if InstalledSDKList is None:
InstalledSDKList = []
InstalledSDKMap = {}
for sdk in SupportedSDKList:
debug('trying to find SDK %s', sdk.version)
if sdk.get_sdk_dir():
debug('found SDK %s', sdk.version)
InstalledSDKList.append(sdk)
InstalledSDKMap[sdk.version] = sdk
return InstalledSDKList
# We may be asked to update multiple construction environments with
# SDK information. When doing this, we check on-disk for whether
# the SDK has 'mfc' and 'atl' subdirectories. Since going to disk
# is expensive, cache results by directory.
SDKEnvironmentUpdates = {}
def set_sdk_by_directory(env, sdk_dir):
global SDKEnvironmentUpdates
debug('set_sdk_by_directory: Using dir:%s', sdk_dir)
try:
env_tuple_list = SDKEnvironmentUpdates[sdk_dir]
except KeyError:
env_tuple_list = []
SDKEnvironmentUpdates[sdk_dir] = env_tuple_list
include_path = os.path.join(sdk_dir, 'include')
mfc_path = os.path.join(include_path, 'mfc')
atl_path = os.path.join(include_path, 'atl')
if os.path.exists(mfc_path):
env_tuple_list.append(('INCLUDE', mfc_path))
if os.path.exists(atl_path):
env_tuple_list.append(('INCLUDE', atl_path))
env_tuple_list.append(('INCLUDE', include_path))
env_tuple_list.append(('LIB', os.path.join(sdk_dir, 'lib')))
env_tuple_list.append(('LIBPATH', os.path.join(sdk_dir, 'lib')))
env_tuple_list.append(('PATH', os.path.join(sdk_dir, 'bin')))
for variable, directory in env_tuple_list:
env.PrependENVPath(variable, directory)
def get_sdk_by_version(mssdk):
if mssdk not in SupportedSDKMap:
raise SCons.Errors.UserError(f"SDK version {mssdk!r} is not supported")
get_installed_sdks()
return InstalledSDKMap.get(mssdk)
def get_default_sdk():
"""Set up the default Platform/Windows SDK."""
get_installed_sdks()
if not InstalledSDKList:
return None
return InstalledSDKList[0]
def mssdk_setup_env(env):
debug('mssdk_setup_env()')
if 'MSSDK_DIR' in env:
sdk_dir = env['MSSDK_DIR']
if sdk_dir is None:
return
sdk_dir = env.subst(sdk_dir)
debug('mssdk_setup_env: Using MSSDK_DIR:%s', sdk_dir)
elif 'MSSDK_VERSION' in env:
sdk_version = env['MSSDK_VERSION']
if sdk_version is None:
msg = "SDK version is specified as None"
raise SCons.Errors.UserError(msg)
sdk_version = env.subst(sdk_version)
mssdk = get_sdk_by_version(sdk_version)
if mssdk is None:
msg = "SDK version %s is not installed" % sdk_version
raise SCons.Errors.UserError(msg)
sdk_dir = mssdk.get_sdk_dir()
debug('mssdk_setup_env: Using MSSDK_VERSION:%s', sdk_dir)
elif 'MSVS_VERSION' in env:
msvs_version = env['MSVS_VERSION']
debug('mssdk_setup_env:Getting MSVS_VERSION from env:%s', msvs_version)
if msvs_version is None:
debug('mssdk_setup_env thinks msvs_version is None')
return
msvs_version = env.subst(msvs_version)
from . import vs
msvs = vs.get_vs_by_version(msvs_version, env)
debug('mssdk_setup_env:msvs is :%s', msvs)
if not msvs:
debug('mssdk_setup_env: no VS version detected, bailingout:%s', msvs)
return
sdk_version = msvs.sdk_version
debug('msvs.sdk_version is %s', sdk_version)
if not sdk_version:
return
mssdk = get_sdk_by_version(sdk_version)
if not mssdk:
mssdk = get_default_sdk()
if not mssdk:
return
sdk_dir = mssdk.get_sdk_dir()
debug('mssdk_setup_env: Using MSVS_VERSION:%s', sdk_dir)
else:
mssdk = get_default_sdk()
if not mssdk:
return
sdk_dir = mssdk.get_sdk_dir()
debug('mssdk_setup_env: not using any env values. sdk_dir:%s', sdk_dir)
set_sdk_by_directory(env, sdk_dir)
#print "No MSVS_VERSION: this is likely to be a bug"
def mssdk_exists(version=None):
sdks = get_installed_sdks()
if version is None:
return len(sdks) > 0
return version in sdks
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,647 @@
# 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.
"""
MS Compilers: detect Visual Studio and/or Visual C/C++
"""
import os
import SCons.Errors
import SCons.Util
from .common import (
debug,
get_output,
is_win64,
normalize_env,
parse_output,
read_reg,
)
from .vc import (
find_vc_pdir,
get_msvc_version_numeric,
reset_installed_vcs,
vswhere_freeze_env,
)
# Visual Studio express version policy when unqualified version is not installed:
# True: use express version for unqualified version (e.g., use 12.0Exp for 12.0)
# False: do not use express version for unqualified version
_VSEXPRESS_USE_VERSTR = True
class VisualStudio:
"""
An abstract base class for trying to find installed versions of
Visual Studio.
"""
def __init__(self, version, **kw):
self.version = version
self.verstr = get_msvc_version_numeric(version)
self.vernum = float(self.verstr)
self.is_express = True if self.verstr != self.version else False
kw['vc_version'] = kw.get('vc_version', version)
kw['sdk_version'] = kw.get('sdk_version', version)
self.__dict__.update(kw)
self._cache = {}
def find_batch_file(self):
vs_dir = self.get_vs_dir()
if not vs_dir:
debug('no vs_dir')
return None
batch_file = os.path.join(vs_dir, self.batch_file_path)
batch_file = os.path.normpath(batch_file)
if not os.path.isfile(batch_file):
debug('%s not on file system', batch_file)
return None
return batch_file
def find_vs_dir_by_vc(self, env):
dir = find_vc_pdir(self.vc_version, env)
if not dir:
debug('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, 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(env)
key = root + key
try:
comps = read_reg(key)
except OSError:
debug('no VS registry key %s', repr(key))
else:
debug('found VS in registry: %s', comps)
return comps
return None
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(env)
if not vs_dir:
vs_dir = self.find_vs_dir_by_vc(env)
debug('found VS in %s', str(vs_dir))
return vs_dir
def find_executable(self, env):
vs_dir = self.get_vs_dir(env)
if not vs_dir:
debug('no vs_dir (%s)', vs_dir)
return None
executable = os.path.join(vs_dir, self.executable_path)
executable = os.path.normpath(executable)
if not os.path.isfile(executable):
debug('%s not on file system', executable)
return None
return executable
def get_batch_file(self):
try:
return self._cache['batch_file']
except KeyError:
batch_file = self.find_batch_file()
self._cache['batch_file'] = batch_file
return batch_file
def get_executable(self, env=None):
try:
debug('using cache:%s', self._cache['executable'])
return self._cache['executable']
except KeyError:
executable = self.find_executable(env)
self._cache['executable'] = executable
debug('not in cache:%s', executable)
return executable
def get_vs_dir(self, env=None):
try:
return self._cache['vs_dir']
except KeyError:
vs_dir = self.find_vs_dir(env)
self._cache['vs_dir'] = vs_dir
return vs_dir
def get_supported_arch(self):
try:
return self._cache['supported_arch']
except KeyError:
# RDEVE: for the time being use hardcoded lists
# supported_arch = self.find_supported_arch()
self._cache['supported_arch'] = self.supported_arch
return self.supported_arch
def reset(self):
self._cache = {}
# The list of supported Visual Studio versions we know how to detect.
#
# How to look for .bat file ?
# - VS 2008 Express (x86):
# * from registry key productdir, gives the full path to vsvarsall.bat. In
# HKEY_LOCAL_MACHINE):
# Software\Microsoft\VCEpress\9.0\Setup\VC\productdir
# * from environmnent variable VS90COMNTOOLS: the path is then ..\..\VC
# relatively to the path given by the variable.
#
# - VS 2008 Express (WoW6432: 32 bits on windows x64):
# Software\Wow6432Node\Microsoft\VCEpress\9.0\Setup\VC\productdir
#
# - VS 2005 Express (x86):
# * from registry key productdir, gives the full path to vsvarsall.bat. In
# HKEY_LOCAL_MACHINE):
# Software\Microsoft\VCEpress\8.0\Setup\VC\productdir
# * from environmnent variable VS80COMNTOOLS: the path is then ..\..\VC
# relatively to the path given by the variable.
#
# - VS 2005 Express (WoW6432: 32 bits on windows x64): does not seem to have a
# productdir ?
#
# - VS 2003 .Net (pro edition ? x86):
# * from registry key productdir. The path is then ..\Common7\Tools\
# relatively to the key. The key is in HKEY_LOCAL_MACHINE):
# Software\Microsoft\VisualStudio\7.1\Setup\VC\productdir
# * from environmnent variable VS71COMNTOOLS: the path is the full path to
# vsvars32.bat
#
# - VS 98 (VS 6):
# * from registry key productdir. The path is then Bin
# relatively to the key. The key is in HKEY_LOCAL_MACHINE):
# Software\Microsoft\VisualStudio\6.0\Setup\VC98\productdir
#
# The first version found in the list is the one used by default if
# there are multiple versions installed. Barring good reasons to
# the contrary, this means we should list versions from most recent
# to oldest. Pro versions get listed before Express versions on the
# assumption that, by default, you'd rather use the version you paid
# good money for in preference to whatever Microsoft makes available
# for free.
#
# If you update this list, update _VCVER and _VCVER_TO_PRODUCT_DIR in
# Tool/MSCommon/vc.py, and the MSVC_VERSION documentation in Tool/msvc.xml.
SupportedVSList = [
# Visual Studio 2026
VisualStudio('14.5',
vc_version='14.5',
sdk_version='10.0A',
hkeys=[],
common_tools_var='VS180COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
# should be a fallback, prefer use vswhere installationPath
batch_file_path=r'Common7\Tools\VsDevCmd.bat',
supported_arch=['x86', 'amd64', "arm", 'arm64'],
),
# Visual Studio 2022
VisualStudio('14.3',
vc_version='14.3',
sdk_version='10.0A',
hkeys=[],
common_tools_var='VS170COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
# should be a fallback, prefer use vswhere installationPath
batch_file_path=r'Common7\Tools\VsDevCmd.bat',
supported_arch=['x86', 'amd64', "arm", 'arm64'],
),
# Visual Studio 2019
VisualStudio('14.2',
vc_version='14.2',
sdk_version='10.0A',
hkeys=[],
common_tools_var='VS160COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
# should be a fallback, prefer use vswhere installationPath
batch_file_path=r'Common7\Tools\VsDevCmd.bat',
supported_arch=['x86', 'amd64', "arm", 'arm64'],
),
# Visual Studio 2017
VisualStudio('14.1',
vc_version='14.1',
sdk_version='10.0A',
hkeys=[],
common_tools_var='VS150COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
# should be a fallback, prefer use vswhere installationPath
batch_file_path=r'Common7\Tools\VsDevCmd.bat',
supported_arch=['x86', 'amd64', "arm", 'arm64'],
),
# Visual C++ 2017 Express Edition (for Desktop)
VisualStudio('14.1Exp',
vc_version='14.1',
sdk_version='10.0A',
hkeys=[],
common_tools_var='VS150COMNTOOLS',
executable_path=r'Common7\IDE\WDExpress.exe',
# should be a fallback, prefer use vswhere installationPath
batch_file_path=r'Common7\Tools\VsDevCmd.bat',
supported_arch=['x86', 'amd64', "arm", 'arm64'],
),
# Visual Studio 2015
VisualStudio('14.0',
vc_version='14.0',
sdk_version='10.0',
hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'],
common_tools_var='VS140COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
batch_file_path=r'Common7\Tools\vsvars32.bat',
supported_arch=['x86', 'amd64', "arm"],
),
# Visual C++ 2015 Express Edition (for Desktop)
VisualStudio('14.0Exp',
vc_version='14.0',
sdk_version='10.0A',
hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'],
common_tools_var='VS140COMNTOOLS',
executable_path=r'Common7\IDE\WDExpress.exe',
batch_file_path=r'Common7\Tools\vsvars32.bat',
supported_arch=['x86', 'amd64', "arm"],
),
# Visual Studio 2013
VisualStudio('12.0',
vc_version='12.0',
sdk_version='8.1A',
hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'],
common_tools_var='VS120COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
batch_file_path=r'Common7\Tools\vsvars32.bat',
supported_arch=['x86', 'amd64'],
),
# Visual C++ 2013 Express Edition (for Desktop)
VisualStudio('12.0Exp',
vc_version='12.0',
sdk_version='8.1A',
hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'],
common_tools_var='VS120COMNTOOLS',
executable_path=r'Common7\IDE\WDExpress.exe',
batch_file_path=r'Common7\Tools\vsvars32.bat',
supported_arch=['x86', 'amd64'],
),
# Visual Studio 2012
VisualStudio('11.0',
sdk_version='8.0A',
hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'],
common_tools_var='VS110COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
batch_file_path=r'Common7\Tools\vsvars32.bat',
supported_arch=['x86', 'amd64'],
),
# Visual C++ 2012 Express Edition (for Desktop)
VisualStudio('11.0Exp',
vc_version='11.0',
sdk_version='8.0A',
hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'],
common_tools_var='VS110COMNTOOLS',
executable_path=r'Common7\IDE\WDExpress.exe',
batch_file_path=r'Common7\Tools\vsvars32.bat',
supported_arch=['x86', 'amd64'],
),
# Visual Studio 2010
VisualStudio('10.0',
sdk_version='7.0A',
hkeys=[r'Microsoft\VisualStudio\10.0\Setup\VS\ProductDir'],
common_tools_var='VS100COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
batch_file_path=r'Common7\Tools\vsvars32.bat',
supported_arch=['x86', 'amd64'],
),
# Visual C++ 2010 Express Edition
VisualStudio('10.0Exp',
vc_version='10.0',
sdk_version='7.0A',
hkeys=[r'Microsoft\VCExpress\10.0\Setup\VS\ProductDir'],
common_tools_var='VS100COMNTOOLS',
executable_path=r'Common7\IDE\VCExpress.exe',
batch_file_path=r'Common7\Tools\vsvars32.bat',
supported_arch=['x86'],
),
# Visual Studio 2008
VisualStudio('9.0',
sdk_version='6.0A',
hkeys=[r'Microsoft\VisualStudio\9.0\Setup\VS\ProductDir'],
common_tools_var='VS90COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
batch_file_path=r'Common7\Tools\vsvars32.bat',
supported_arch=['x86', 'amd64'],
),
# Visual C++ 2008 Express Edition
VisualStudio('9.0Exp',
vc_version='9.0',
sdk_version='6.0A',
hkeys=[r'Microsoft\VCExpress\9.0\Setup\VS\ProductDir'],
common_tools_var='VS90COMNTOOLS',
executable_path=r'Common7\IDE\VCExpress.exe',
batch_file_path=r'Common7\Tools\vsvars32.bat',
supported_arch=['x86'],
),
# Visual Studio 2005
VisualStudio('8.0',
sdk_version='6.0A',
hkeys=[r'Microsoft\VisualStudio\8.0\Setup\VS\ProductDir'],
common_tools_var='VS80COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
batch_file_path=r'Common7\Tools\vsvars32.bat',
default_dirname='Microsoft Visual Studio 8',
supported_arch=['x86', 'amd64'],
),
# Visual C++ 2005 Express Edition
VisualStudio('8.0Exp',
vc_version='8.0Exp',
sdk_version='6.0A',
hkeys=[r'Microsoft\VCExpress\8.0\Setup\VS\ProductDir'],
common_tools_var='VS80COMNTOOLS',
executable_path=r'Common7\IDE\VCExpress.exe',
batch_file_path=r'Common7\Tools\vsvars32.bat',
default_dirname='Microsoft Visual Studio 8',
supported_arch=['x86'],
),
# Visual Studio .NET 2003
VisualStudio('7.1',
sdk_version='6.0',
hkeys=[r'Microsoft\VisualStudio\7.1\Setup\VS\ProductDir'],
common_tools_var='VS71COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
batch_file_path=r'Common7\Tools\vsvars32.bat',
default_dirname='Microsoft Visual Studio .NET 2003',
supported_arch=['x86'],
),
# Visual Studio .NET
VisualStudio('7.0',
sdk_version='2003R2',
hkeys=[r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir'],
common_tools_var='VSCOMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
batch_file_path=r'Common7\Tools\vsvars32.bat',
default_dirname='Microsoft Visual Studio .NET',
supported_arch=['x86'],
),
# Visual Studio 6.0
VisualStudio('6.0',
sdk_version='2003R1',
hkeys=[r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Studio\ProductDir',
'use_dir'],
common_tools_var='MSDevDir',
executable_path=r'Common\MSDev98\Bin\MSDEV.COM',
batch_file_path=r'Common7\Tools\vsvars32.bat',
default_dirname='Microsoft Visual Studio',
supported_arch=['x86'],
),
]
SupportedVSMap = {}
for vs in SupportedVSList:
SupportedVSMap[vs.version] = vs
# Finding installed versions of Visual Studio isn't cheap, because it
# goes not only to the registry but also to the disk to sanity-check
# that there is, in fact, a Visual Studio directory there and that the
# registry entry isn't just stale. Find this information once, when
# requested, and cache it.
InstalledVSList = None
InstalledVSMap = None
def get_installed_visual_studios(env=None):
global InstalledVSList
global InstalledVSMap
vswhere_freeze_env(env)
if InstalledVSList is None:
InstalledVSList = []
InstalledVSMap = {}
for vs in SupportedVSList:
debug('trying to find VS %s', vs.version)
if vs.get_executable(env):
debug('found VS %s', vs.version)
InstalledVSList.append(vs)
if vs.is_express and vs.verstr not in InstalledVSMap:
if _VSEXPRESS_USE_VERSTR:
InstalledVSMap[vs.verstr] = vs
InstalledVSMap[vs.version] = vs
return InstalledVSList
def _get_installed_vss(env=None):
get_installed_visual_studios(env)
versions = list(InstalledVSMap.keys())
return versions
def reset_installed_visual_studios():
global InstalledVSList
global InstalledVSMap
debug('')
InstalledVSList = None
InstalledVSMap = None
for vs in SupportedVSList:
vs.reset()
# Need to clear installed VC's as well as they are used in finding
# installed VS's
reset_installed_vcs()
# We may be asked to update multiple construction environments with
# SDK information. When doing this, we check on-disk for whether
# the SDK has 'mfc' and 'atl' subdirectories. Since going to disk
# is expensive, cache results by directory.
#SDKEnvironmentUpdates = {}
#
#def set_sdk_by_directory(env, sdk_dir):
# global SDKEnvironmentUpdates
# try:
# env_tuple_list = SDKEnvironmentUpdates[sdk_dir]
# except KeyError:
# env_tuple_list = []
# SDKEnvironmentUpdates[sdk_dir] = env_tuple_list
#
# include_path = os.path.join(sdk_dir, 'include')
# mfc_path = os.path.join(include_path, 'mfc')
# atl_path = os.path.join(include_path, 'atl')
#
# if os.path.exists(mfc_path):
# env_tuple_list.append(('INCLUDE', mfc_path))
# if os.path.exists(atl_path):
# env_tuple_list.append(('INCLUDE', atl_path))
# env_tuple_list.append(('INCLUDE', include_path))
#
# env_tuple_list.append(('LIB', os.path.join(sdk_dir, 'lib')))
# env_tuple_list.append(('LIBPATH', os.path.join(sdk_dir, 'lib')))
# env_tuple_list.append(('PATH', os.path.join(sdk_dir, 'bin')))
#
# for variable, directory in env_tuple_list:
# env.PrependENVPath(variable, directory)
def msvs_exists(env=None):
return len(get_installed_visual_studios(env)) > 0
def get_vs_by_version(msvs, env=None):
global InstalledVSMap
global SupportedVSMap
debug('called')
if msvs not in SupportedVSMap:
msg = "Visual Studio version %s is not supported" % repr(msvs)
raise SCons.Errors.UserError(msg)
get_installed_visual_studios(env)
vs = InstalledVSMap.get(msvs)
debug('InstalledVSMap:%s', InstalledVSMap)
debug('found vs:%s', vs)
# Some check like this would let us provide a useful error message
# if they try to set a Visual Studio version that's not installed.
# However, we also want to be able to run tests (like the unit
# tests) on systems that don't, or won't ever, have it installed.
# It might be worth resurrecting this, with some configurable
# setting that the tests can use to bypass the check.
#if not vs:
# msg = "Visual Studio version %s is not installed" % repr(msvs)
# raise SCons.Errors.UserError, msg
return vs
def get_default_version(env):
"""Returns the default version string to use for MSVS.
If no version was requested by the user through the MSVS environment
variable, query all the available visual studios through
get_installed_visual_studios, and take the highest one.
Return
------
version: str
the default version.
"""
if 'MSVS' not in env or not SCons.Util.is_Dict(env['MSVS']):
# get all versions, and remember them for speed later
versions = _get_installed_vss(env)
env['MSVS'] = {'VERSIONS' : versions}
else:
versions = env['MSVS'].get('VERSIONS', [])
if 'MSVS_VERSION' not in env:
if versions:
env['MSVS_VERSION'] = versions[0] #use highest version by default
else:
debug('WARNING: no installed versions found, '
'using first in SupportedVSList (%s)',
SupportedVSList[0].version)
env['MSVS_VERSION'] = SupportedVSList[0].version
env['MSVS']['VERSION'] = env['MSVS_VERSION']
return env['MSVS_VERSION']
def get_default_arch(env):
"""Return the default arch to use for MSVS
if no version was requested by the user through the MSVS_ARCH environment
variable, select x86
Return
------
arch: str
"""
arch = env.get('MSVS_ARCH', 'x86')
msvs = InstalledVSMap.get(env['MSVS_VERSION'])
if not msvs:
arch = 'x86'
elif arch not in msvs.get_supported_arch():
fmt = "Visual Studio version %s does not support architecture %s"
raise SCons.Errors.UserError(fmt % (env['MSVS_VERSION'], arch))
return arch
def merge_default_version(env):
version = get_default_version(env)
arch = get_default_arch(env)
# TODO: refers to versions and arch which aren't defined; called nowhere. Drop?
def msvs_setup_env(env):
msvs = get_vs_by_version(version, env)
if msvs is None:
return
batfilename = msvs.get_batch_file()
# XXX: I think this is broken. This will silently set a bogus tool instead
# of failing, but there is no other way with the current scons tool
# framework
if batfilename is not None:
vars = ('LIB', 'LIBPATH', 'PATH', 'INCLUDE')
msvs_list = get_installed_visual_studios(env)
vscommonvarnames = [vs.common_tools_var for vs in msvs_list]
save_ENV = env['ENV']
nenv = normalize_env(env['ENV'],
['COMSPEC'] + vscommonvarnames,
force=True)
try:
output = get_output(batfilename, arch, env=nenv)
finally:
env['ENV'] = save_ENV
vars = parse_output(output, vars)
for k, v in vars.items():
env.PrependENVPath(k, v, delete_existing=1)
def query_versions(env=None):
"""Query the system to get available versions of VS. A version is
considered when a batfile is found."""
versions = _get_installed_vss(env)
return versions
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,116 @@
"""SCons.Tool.PharLapCommon
This module contains common code used by all Tools for the
Phar Lap ETS tool chain. Right now, this is linkloc and
386asm.
"""
#
# __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__"
import os
import os.path
import SCons.Errors
import SCons.Util
import re
def getPharLapPath():
"""Reads the registry to find the installed path of the Phar Lap ETS
development kit.
Raises UserError if no installed version of Phar Lap can
be found."""
if not SCons.Util.can_read_reg:
raise SCons.Errors.InternalError("No Windows registry module was found")
try:
k=SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
'SOFTWARE\\Pharlap\\ETS')
val, type = SCons.Util.RegQueryValueEx(k, 'BaseDir')
# The following is a hack...there is (not surprisingly)
# an odd issue in the Phar Lap plug in that inserts
# a bunch of junk data after the phar lap path in the
# registry. We must trim it.
idx=val.find('\0')
if idx >= 0:
val = val[:idx]
return os.path.normpath(val)
except SCons.Util.RegError:
raise SCons.Errors.UserError("Cannot find Phar Lap ETS path in the registry. Is it installed properly?")
REGEX_ETS_VER = re.compile(r'#define\s+ETS_VER\s+([0-9]+)')
def getPharLapVersion():
"""Returns the version of the installed ETS Tool Suite as a
decimal number. This version comes from the ETS_VER #define in
the embkern.h header. For example, '#define ETS_VER 1010' (which
is what Phar Lap 10.1 defines) would cause this method to return
1010. Phar Lap 9.1 does not have such a #define, but this method
will return 910 as a default.
Raises UserError if no installed version of Phar Lap can
be found."""
include_path = os.path.join(getPharLapPath(), os.path.normpath("include/embkern.h"))
if not os.path.exists(include_path):
raise SCons.Errors.UserError("Cannot find embkern.h in ETS include directory.\nIs Phar Lap ETS installed properly?")
with open(include_path) as f:
mo = REGEX_ETS_VER.search(f.read())
if mo:
return int(mo.group(1))
# Default return for Phar Lap 9.1
return 910
def addPharLapPaths(env):
"""This function adds the path to the Phar Lap binaries, includes,
and libraries, if they are not already there."""
ph_path = getPharLapPath()
try:
env_dict = env['ENV']
except KeyError:
env_dict = {}
env['ENV'] = env_dict
SCons.Util.AddPathIfNotExists(env_dict, 'PATH',
os.path.join(ph_path, 'bin'))
SCons.Util.AddPathIfNotExists(env_dict, 'INCLUDE',
os.path.join(ph_path, 'include'))
SCons.Util.AddPathIfNotExists(env_dict, 'LIB',
os.path.join(ph_path, 'lib'))
SCons.Util.AddPathIfNotExists(env_dict, 'LIB',
os.path.join(ph_path, os.path.normpath('lib/vclib')))
env['PHARLAP_PATH'] = getPharLapPath()
env['PHARLAP_VERSION'] = str(getPharLapVersion())
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,891 @@
# 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.
"""SCons tool selection.
Looks for modules that define a callable object that can modify a
construction environment as appropriate for a given tool (or tool chain).
Note that because this subsystem just *selects* a callable that can
modify a construction environment, it's possible for people to define
their own "tool specification" in an arbitrary callable function. No
one needs to use or tie in to this subsystem in order to roll their own
tool specifications.
"""
from __future__ import annotations
import sys
import os
import importlib.util
import SCons.Builder
import SCons.Errors
import SCons.Node.FS
import SCons.Scanner
import SCons.Scanner.C
# Nuitka: Avoid unused tools
# import SCons.Scanner.D
# import SCons.Scanner.Java
# import SCons.Scanner.LaTeX
import SCons.Scanner.Prog
# Nuitka: Avoid unused tools
# import SCons.Scanner.SWIG
from SCons.Tool.linkCommon import LibSymlinksActionFunction, LibSymlinksStrFun
DefaultToolpath = []
CScanner = SCons.Scanner.C.CScanner()
# Nuitka: Avoid unused tools
# DScanner = SCons.Scanner.D.DScanner()
# JavaScanner = SCons.Scanner.Java.JavaScanner()
# LaTeXScanner = SCons.Scanner.LaTeX.LaTeXScanner()
# PDFLaTeXScanner = SCons.Scanner.LaTeX.PDFLaTeXScanner()
ProgramScanner = SCons.Scanner.Prog.ProgramScanner()
SourceFileScanner = SCons.Scanner.ScannerBase({}, name='SourceFileScanner')
# Nuitka: Avoid unused tools
# SWIGScanner = SCons.Scanner.SWIG.SWIGScanner()
CSuffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
".h", ".H", ".hxx", ".hpp", ".hh",
".F", ".fpp", ".FPP",
".m", ".mm",
".S", ".spp", ".SPP", ".sx"]
DSuffixes = ['.d']
IDLSuffixes = [".idl", ".IDL"]
LaTeXSuffixes = [".tex", ".ltx", ".latex"]
SWIGSuffixes = ['.i']
for suffix in CSuffixes:
SourceFileScanner.add_scanner(suffix, CScanner)
# Nuitka: Avoid unused tools
# for suffix in DSuffixes:
# SourceFileScanner.add_scanner(suffix, DScanner)
# for suffix in SWIGSuffixes:
# SourceFileScanner.add_scanner(suffix, SWIGScanner)
# FIXME: what should be done here? Two scanners scan the same extensions,
# but look for different files, e.g., "picture.eps" vs. "picture.pdf".
# The builders for DVI and PDF explicitly reference their scanners
# I think that means this is not needed???
# Nuitka: Avoid unused tools
# for suffix in LaTeXSuffixes:
# SourceFileScanner.add_scanner(suffix, LaTeXScanner)
# SourceFileScanner.add_scanner(suffix, PDFLaTeXScanner)
# Tool aliases are needed for those tools whose module names also
# occur in the python standard library (This causes module shadowing and
# can break using python library functions under python3) or if the current tool/file names
# are not legal module names (violate python's identifier rules or are
# python language keywords).
TOOL_ALIASES = {
'gettext': 'gettext_tool',
'clang++': 'clangxx',
'as': 'asm',
'ninja' : 'ninja_tool'
}
class Tool:
def __init__(self, name, toolpath=None, **kwargs):
if toolpath is None:
toolpath = []
# Rename if there's a TOOL_ALIAS for this tool
self.name = TOOL_ALIASES.get(name, name)
self.toolpath = toolpath + DefaultToolpath
# remember these so we can merge them into the call
self.init_kw = kwargs
module = self._tool_module()
self.generate = module.generate
self.exists = module.exists
if hasattr(module, 'options'):
self.options = module.options
def _tool_module(self):
"""Try to load a tool module.
This will hunt in the toolpath for both a Python file (toolname.py)
and a Python module (toolname directory), then try the regular
import machinery, then fallback to try a zipfile.
"""
oldpythonpath = sys.path
sys.path = self.toolpath + sys.path
# These could be enabled under "if debug:"
# sys.stderr.write(f"Tool: {self.name}\n")
# sys.stderr.write(f"PATH: {sys.path}\n")
# sys.stderr.write(f"toolpath: {self.toolpath}\n")
# sys.stderr.write(f"SCONS.TOOL path: {sys.modules['SCons.Tool'].__path__}\n")
debug = False
spec = None
found_name = self.name
add_to_scons_tools_namespace = False
# Search for the tool module, but don't import it, yet.
#
# First look in the toolpath: these take priority.
# TODO: any reason to not just use find_spec here?
for path in self.toolpath:
sepname = self.name.replace('.', os.path.sep)
file_path = os.path.join(path, sepname + ".py")
file_package = os.path.join(path, sepname)
if debug: sys.stderr.write(f"Trying: {file_path} {file_package}\n")
if os.path.isfile(file_path):
spec = importlib.util.spec_from_file_location(self.name, file_path)
if debug: sys.stderr.write(f"file_Path: {file_path} FOUND\n")
break
elif os.path.isdir(file_package):
file_package = os.path.join(file_package, '__init__.py')
spec = importlib.util.spec_from_file_location(self.name, file_package)
if debug: sys.stderr.write(f"PACKAGE: {file_package} Found\n")
break
else:
continue
# Now look in the builtin tools (SCons.Tool package)
if spec is None:
if debug: sys.stderr.write(f"NO SPEC: {self.name}\n")
spec = importlib.util.find_spec("." + self.name, package='SCons.Tool')
if spec:
found_name = 'SCons.Tool.' + self.name
add_to_scons_tools_namespace = True
if debug: sys.stderr.write(f"Spec Found? .{self.name}: {spec}\n")
if spec is None:
# we are going to bail out here, format up stuff for the msg
sconstools = os.path.normpath(sys.modules['SCons.Tool'].__path__[0])
if self.toolpath:
sconstools = ", ".join(self.toolpath) + ", " + sconstools
msg = f"No tool module '{self.name}' found in {sconstools}"
raise SCons.Errors.UserError(msg)
# We have a module spec, so we're good to go.
module = importlib.util.module_from_spec(spec)
if module is None:
if debug: sys.stderr.write(f"MODULE IS NONE: {self.name}\n")
msg = f"Tool module '{self.name}' failed import"
raise SCons.Errors.SConsEnvironmentError(msg)
# Don't reload a tool we already loaded.
sys_modules_value = sys.modules.get(found_name, False)
found_module = None
if sys_modules_value and sys_modules_value.__file__ == spec.origin:
found_module = sys.modules[found_name]
else:
# Not sure what to do in the case that there already
# exists sys.modules[self.name] but the source file is
# different.. ?
sys.modules[found_name] = module
spec.loader.exec_module(module)
if add_to_scons_tools_namespace:
# If we found it in SCons.Tool, add it to the module
setattr(SCons.Tool, self.name, module)
found_module = module
if found_module is not None:
sys.path = oldpythonpath
return found_module
sys.path = oldpythonpath
# We try some other things here, but this is essentially dead code,
# because we bailed out above if we didn't find a module spec.
full_name = 'SCons.Tool.' + self.name
try:
return sys.modules[full_name]
except KeyError:
try:
# This support was added to enable running inside
# a py2exe bundle a long time ago - unclear if it's
# still needed. It is *not* intended to load individual
# tool modules stored in a zipfile.
import zipimport
tooldir = sys.modules['SCons.Tool'].__path__[0]
importer = zipimport.zipimporter(tooldir)
if not hasattr(importer, 'find_spec'):
# zipimport only added find_spec, exec_module in 3.10,
# unlike importlib, where they've been around since 3.4.
# If we don't have 'em, use the old way.
module = importer.load_module(full_name)
else:
spec = importer.find_spec(full_name)
module = importlib.util.module_from_spec(spec)
importer.exec_module(module)
sys.modules[full_name] = module
setattr(SCons.Tool, self.name, module)
return module
except zipimport.ZipImportError as e:
msg = "No tool named '{self.name}': {e}"
raise SCons.Errors.SConsEnvironmentError(msg)
def __call__(self, env, *args, **kw):
if self.init_kw is not None:
# Merge call kws into init kws;
# but don't bash self.init_kw.
if kw is not None:
call_kw = kw
kw = self.init_kw.copy()
kw.update(call_kw)
else:
kw = self.init_kw
env.AppendUnique(TOOLS=[self.name])
if hasattr(self, 'options'):
import SCons.Variables
if 'options' not in env:
from SCons.Script import ARGUMENTS
env['options'] = SCons.Variables.Variables(args=ARGUMENTS)
opts = env['options']
self.options(opts)
opts.Update(env)
self.generate(env, *args, **kw)
def __str__(self):
return self.name
LibSymlinksAction = SCons.Action.Action(LibSymlinksActionFunction, LibSymlinksStrFun)
##########################################################################
# Create common executable program / library / object builders
def createProgBuilder(env):
"""This is a utility function that creates the Program
Builder in an Environment if it is not there already.
If it is already there, we return the existing one.
"""
try:
program = env['BUILDERS']['Program']
except KeyError:
import SCons.Defaults
program = SCons.Builder.Builder(action=SCons.Defaults.LinkAction,
emitter='$PROGEMITTER',
prefix='$PROGPREFIX',
suffix='$PROGSUFFIX',
src_suffix='$OBJSUFFIX',
src_builder='Object',
target_scanner=ProgramScanner)
env['BUILDERS']['Program'] = program
return program
def createStaticLibBuilder(env):
"""This is a utility function that creates the StaticLibrary
Builder in an Environment if it is not there already.
If it is already there, we return the existing one.
"""
try:
static_lib = env['BUILDERS']['StaticLibrary']
except KeyError:
action_list = [SCons.Action.Action("$ARCOM", "$ARCOMSTR")]
if env.get('RANLIB', False) or env.Detect('ranlib'):
ranlib_action = SCons.Action.Action("$RANLIBCOM", "$RANLIBCOMSTR")
action_list.append(ranlib_action)
static_lib = SCons.Builder.Builder(action=action_list,
emitter='$LIBEMITTER',
prefix='$LIBPREFIX',
suffix='$LIBSUFFIX',
src_suffix='$OBJSUFFIX',
src_builder='StaticObject')
env['BUILDERS']['StaticLibrary'] = static_lib
env['BUILDERS']['Library'] = static_lib
return static_lib
def createSharedLibBuilder(env, shlib_suffix='$_SHLIBSUFFIX'):
"""This is a utility function that creates the SharedLibrary
Builder in an Environment if it is not there already.
If it is already there, we return the existing one.
Args:
shlib_suffix: The suffix specified for the shared library builder
"""
try:
shared_lib = env['BUILDERS']['SharedLibrary']
except KeyError:
import SCons.Defaults
action_list = [SCons.Defaults.SharedCheck,
SCons.Defaults.ShLinkAction,
LibSymlinksAction]
shared_lib = SCons.Builder.Builder(action=action_list,
emitter="$SHLIBEMITTER",
prefix="$SHLIBPREFIX",
suffix=shlib_suffix,
target_scanner=ProgramScanner,
src_suffix='$SHOBJSUFFIX',
src_builder='SharedObject')
env['BUILDERS']['SharedLibrary'] = shared_lib
return shared_lib
def createLoadableModuleBuilder(env, loadable_module_suffix='$_LDMODULESUFFIX'):
"""This is a utility function that creates the LoadableModule
Builder in an Environment if it is not there already.
If it is already there, we return the existing one.
Args:
loadable_module_suffix: The suffix specified for the loadable module builder
"""
try:
ld_module = env['BUILDERS']['LoadableModule']
except KeyError:
import SCons.Defaults
action_list = [SCons.Defaults.SharedCheck,
SCons.Defaults.LdModuleLinkAction,
LibSymlinksAction]
ld_module = SCons.Builder.Builder(action=action_list,
emitter="$LDMODULEEMITTER",
prefix="$LDMODULEPREFIX",
suffix=loadable_module_suffix,
target_scanner=ProgramScanner,
src_suffix='$SHOBJSUFFIX',
src_builder='SharedObject')
env['BUILDERS']['LoadableModule'] = ld_module
return ld_module
def createObjBuilders(env):
"""This is a utility function that creates the StaticObject
and SharedObject Builders in an Environment if they
are not there already.
If they are there already, we return the existing ones.
This is a separate function because soooo many Tools
use this functionality.
The return is a 2-tuple of (StaticObject, SharedObject)
"""
try:
static_obj = env['BUILDERS']['StaticObject']
except KeyError:
static_obj = SCons.Builder.Builder(action={},
emitter={},
prefix='$OBJPREFIX',
suffix='$OBJSUFFIX',
src_builder=['CFile', 'CXXFile'],
source_scanner=SourceFileScanner,
single_source=True)
env['BUILDERS']['StaticObject'] = static_obj
env['BUILDERS']['Object'] = static_obj
try:
shared_obj = env['BUILDERS']['SharedObject']
except KeyError:
shared_obj = SCons.Builder.Builder(action={},
emitter={},
prefix='$SHOBJPREFIX',
suffix='$SHOBJSUFFIX',
src_builder=['CFile', 'CXXFile'],
source_scanner=SourceFileScanner,
single_source=True)
env['BUILDERS']['SharedObject'] = shared_obj
return (static_obj, shared_obj)
def createCFileBuilders(env):
"""This is a utility function that creates the CFile/CXXFile
Builders in an Environment if they
are not there already.
If they are there already, we return the existing ones.
This is a separate function because soooo many Tools
use this functionality.
The return is a 2-tuple of (CFile, CXXFile)
"""
try:
c_file = env['BUILDERS']['CFile']
except KeyError:
c_file = SCons.Builder.Builder(action={},
emitter={},
suffix={None: '$CFILESUFFIX'})
env['BUILDERS']['CFile'] = c_file
env.SetDefault(CFILESUFFIX='.c')
try:
cxx_file = env['BUILDERS']['CXXFile']
except KeyError:
cxx_file = SCons.Builder.Builder(action={},
emitter={},
suffix={None: '$CXXFILESUFFIX'})
env['BUILDERS']['CXXFile'] = cxx_file
env.SetDefault(CXXFILESUFFIX='.cc')
return (c_file, cxx_file)
##########################################################################
# Create common Java builders
def CreateJarBuilder(env):
"""The Jar builder expects a list of class files
which it can package into a jar file.
The jar tool provides an interface for passing other types
of java files such as .java, directories or swig interfaces
and will build them to class files in which it can package
into the jar.
"""
try:
java_jar = env['BUILDERS']['JarFile']
except KeyError:
fs = SCons.Node.FS.get_default_fs()
jar_com = SCons.Action.Action('$JARCOM', '$JARCOMSTR')
java_jar = SCons.Builder.Builder(action=jar_com,
suffix='$JARSUFFIX',
src_suffix='$JAVACLASSSUFFIX',
src_builder='JavaClassFile',
source_factory=fs.Entry)
env['BUILDERS']['JarFile'] = java_jar
return java_jar
def CreateJavaHBuilder(env):
try:
java_javah = env['BUILDERS']['JavaH']
except KeyError:
fs = SCons.Node.FS.get_default_fs()
java_javah_com = SCons.Action.Action('$JAVAHCOM', '$JAVAHCOMSTR')
java_javah = SCons.Builder.Builder(action=java_javah_com,
src_suffix='$JAVACLASSSUFFIX',
target_factory=fs.Entry,
source_factory=fs.File,
src_builder='JavaClassFile')
env['BUILDERS']['JavaH'] = java_javah
return java_javah
def CreateJavaClassFileBuilder(env):
try:
java_class_file = env['BUILDERS']['JavaClassFile']
except KeyError:
fs = SCons.Node.FS.get_default_fs()
javac_com = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR')
java_class_file = SCons.Builder.Builder(action=javac_com,
emitter={},
# suffix = '$JAVACLASSSUFFIX',
src_suffix='$JAVASUFFIX',
src_builder=['JavaFile'],
target_factory=fs.Entry,
source_factory=fs.File,
target_scanner=JavaScanner)
env['BUILDERS']['JavaClassFile'] = java_class_file
return java_class_file
def CreateJavaClassDirBuilder(env):
try:
java_class_dir = env['BUILDERS']['JavaClassDir']
except KeyError:
fs = SCons.Node.FS.get_default_fs()
javac_com = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR')
java_class_dir = SCons.Builder.Builder(action=javac_com,
emitter={},
target_factory=fs.Dir,
source_factory=fs.Dir,
target_scanner=JavaScanner)
env['BUILDERS']['JavaClassDir'] = java_class_dir
return java_class_dir
def CreateJavaFileBuilder(env):
try:
java_file = env['BUILDERS']['JavaFile']
except KeyError:
java_file = SCons.Builder.Builder(action={},
emitter={},
suffix={None: '$JAVASUFFIX'})
env['BUILDERS']['JavaFile'] = java_file
env['JAVASUFFIX'] = '.java'
return java_file
class ToolInitializerMethod:
"""
This is added to a construction environment in place of a
method(s) normally called for a Builder (env.Object, env.StaticObject,
etc.). When called, it has its associated ToolInitializer
object search the specified list of tools and apply the first
one that exists to the construction environment. It then calls
whatever builder was (presumably) added to the construction
environment in place of this particular instance.
"""
def __init__(self, name, initializer):
"""
Note: we store the tool name as __name__ so it can be used by
the class that attaches this to a construction environment.
"""
self.__name__ = name
self.initializer = initializer
def get_builder(self, env):
"""
Returns the appropriate real Builder for this method name
after having the associated ToolInitializer object apply
the appropriate Tool module.
"""
builder = getattr(env, self.__name__)
self.initializer.apply_tools(env)
builder = getattr(env, self.__name__)
if builder is self:
# There was no Builder added, which means no valid Tool
# for this name was found (or possibly there's a mismatch
# between the name we were called by and the Builder name
# added by the Tool module).
return None
self.initializer.remove_methods(env)
return builder
def __call__(self, env, *args, **kw):
"""
"""
builder = self.get_builder(env)
if builder is None:
return [], []
return builder(*args, **kw)
class ToolInitializer:
"""
A class for delayed initialization of Tools modules.
Instances of this class associate a list of Tool modules with
a list of Builder method names that will be added by those Tool
modules. As part of instantiating this object for a particular
construction environment, we also add the appropriate
ToolInitializerMethod objects for the various Builder methods
that we want to use to delay Tool searches until necessary.
"""
def __init__(self, env, tools, names):
if not SCons.Util.is_List(tools):
tools = [tools]
if not SCons.Util.is_List(names):
names = [names]
self.env = env
self.tools = tools
self.names = names
self.methods = {}
for name in names:
method = ToolInitializerMethod(name, self)
self.methods[name] = method
env.AddMethod(method)
def remove_methods(self, env):
"""
Removes the methods that were added by the tool initialization
so we no longer copy and re-bind them when the construction
environment gets cloned.
"""
for method in self.methods.values():
env.RemoveMethod(method)
def apply_tools(self, env):
"""
Searches the list of associated Tool modules for one that
exists, and applies that to the construction environment.
"""
for t in self.tools:
tool = SCons.Tool.Tool(t)
if tool.exists(env):
env.Tool(tool)
return
# If we fall through here, there was no tool module found.
# This is where we can put an informative error message
# about the inability to find the tool. We'll start doing
# this as we cut over more pre-defined Builder+Tools to use
# the ToolInitializer class.
def Initializers(env):
ToolInitializer(env, ['install'], ['_InternalInstall', '_InternalInstallAs', '_InternalInstallVersionedLib'])
def Install(self, *args, **kw):
return self._InternalInstall(*args, **kw)
def InstallAs(self, *args, **kw):
return self._InternalInstallAs(*args, **kw)
def InstallVersionedLib(self, *args, **kw):
return self._InternalInstallVersionedLib(*args, **kw)
env.AddMethod(Install)
env.AddMethod(InstallAs)
env.AddMethod(InstallVersionedLib)
def FindTool(tools, env):
for tool in tools:
t = Tool(tool)
if t.exists(env):
return tool
return None
def FindAllTools(tools, env):
def ToolExists(tool, env=env):
return Tool(tool).exists(env)
return list(filter(ToolExists, tools))
def tool_list(platform, env):
other_plat_tools = []
# XXX this logic about what tool to prefer on which platform
# should be moved into either the platform files or
# the tool files themselves.
# The search orders here are described in the man page. If you
# change these search orders, update the man page as well.
if str(platform) == 'win32':
"prefer Microsoft tools on Windows"
linkers = ['mslink', 'gnulink', 'ilink', 'linkloc', 'ilink32']
c_compilers = ['msvc', 'mingw', 'gcc', 'clang', 'intelc', 'icl', 'icc', 'cc', 'bcc32']
cxx_compilers = ['msvc', 'intelc', 'icc', 'g++', 'clang++', 'cxx', 'bcc32']
assemblers = ['masm', 'nasm', 'gas', '386asm']
fortran_compilers = ['gfortran', 'g77', 'ifl', 'cvf', 'f95', 'f90', 'fortran']
ars = ['mslib', 'ar', 'tlib']
# Nuitka: Do not use "midl" or "wix" tool.
# other_plat_tools = ['msvs', 'midl', 'wix']
other_plat_tools = ['msvs']
elif str(platform) == 'os2':
"prefer IBM tools on OS/2"
linkers = ['ilink', 'gnulink', ] # 'mslink']
c_compilers = ['icc', 'gcc', ] # 'msvc', 'cc']
cxx_compilers = ['icc', 'g++', ] # 'msvc', 'cxx']
assemblers = ['nasm', ] # 'masm', 'gas']
fortran_compilers = ['ifl', 'g77']
ars = ['ar', ] # 'mslib']
elif str(platform) == 'irix':
"prefer MIPSPro on IRIX"
linkers = ['sgilink', 'gnulink']
c_compilers = ['sgicc', 'gcc', 'cc']
cxx_compilers = ['sgicxx', 'g++', 'cxx']
assemblers = ['as', 'gas']
fortran_compilers = ['f95', 'f90', 'f77', 'g77', 'fortran']
ars = ['sgiar']
elif str(platform) == 'sunos':
"prefer Forte tools on SunOS"
linkers = ['sunlink', 'gnulink']
c_compilers = ['suncc', 'gcc', 'cc']
cxx_compilers = ['suncxx', 'g++', 'cxx']
assemblers = ['as', 'gas']
fortran_compilers = ['sunf95', 'sunf90', 'sunf77', 'f95', 'f90', 'f77',
'gfortran', 'g77', 'fortran']
ars = ['sunar']
elif str(platform) == 'hpux':
"prefer aCC tools on HP-UX"
linkers = ['hplink', 'gnulink']
c_compilers = ['hpcc', 'gcc', 'cc']
cxx_compilers = ['hpcxx', 'g++', 'cxx']
assemblers = ['as', 'gas']
fortran_compilers = ['f95', 'f90', 'f77', 'g77', 'fortran']
ars = ['ar']
elif str(platform) == 'aix':
"prefer AIX Visual Age tools on AIX"
linkers = ['aixlink', 'gnulink']
c_compilers = ['aixcc', 'gcc', 'cc']
cxx_compilers = ['aixcxx', 'g++', 'cxx']
assemblers = ['as', 'gas']
fortran_compilers = ['f95', 'f90', 'aixf77', 'g77', 'fortran']
ars = ['ar']
elif str(platform) == 'darwin':
"prefer GNU tools on Mac OS X, except for some linkers and IBM tools"
linkers = ['applelink', 'gnulink']
c_compilers = ['gcc', 'cc']
cxx_compilers = ['g++', 'cxx']
assemblers = ['as']
fortran_compilers = ['gfortran', 'f95', 'f90', 'g77']
ars = ['ar']
elif str(platform) == 'cygwin':
"prefer GNU tools on Cygwin, except for a platform-specific linker"
linkers = ['cyglink', 'mslink', 'ilink']
c_compilers = ['gcc', 'msvc', 'intelc', 'icc', 'cc']
cxx_compilers = ['g++', 'msvc', 'intelc', 'icc', 'cxx']
assemblers = ['gas', 'nasm', 'masm']
fortran_compilers = ['gfortran', 'g77', 'ifort', 'ifl', 'f95', 'f90', 'f77']
ars = ['ar', 'mslib']
else:
"prefer GNU tools on all other platforms"
linkers = ['gnulink', 'ilink']
c_compilers = ['gcc', 'clang', 'intelc', 'icc', 'cc']
cxx_compilers = ['g++', 'clang++', 'intelc', 'icc', 'cxx']
assemblers = ['gas', 'nasm', 'masm']
fortran_compilers = ['gfortran', 'g77', 'ifort', 'ifl', 'f95', 'f90', 'f77']
ars = ['ar', ]
if not str(platform) == 'win32':
other_plat_tools += ['m4', 'rpm']
c_compiler = FindTool(c_compilers, env) or c_compilers[0]
# XXX this logic about what tool provides what should somehow be
# moved into the tool files themselves.
if c_compiler and c_compiler == 'mingw':
# MinGW contains a linker, C compiler, C++ compiler,
# Fortran compiler, archiver and assembler:
cxx_compiler = None
linker = None
assembler = None
fortran_compiler = None
ar = None
else:
# Don't use g++ if the C compiler has built-in C++ support:
if c_compiler in ('msvc', 'intelc', 'icc'):
cxx_compiler = None
else:
cxx_compiler = FindTool(cxx_compilers, env) or cxx_compilers[0]
linker = FindTool(linkers, env) or linkers[0]
# Nuitka: Avoid unused tools
# assembler = FindTool(assemblers, env) or assemblers[0]
assembler = None
# fortran_compiler = FindTool(fortran_compilers, env) or fortran_compilers[0]
fortran_compiler = None
# ar = FindTool(ars, env) or ars[0]
ar = None
d_compilers = ['dmd', 'ldc', 'gdc']
# Nuitka: Avoid unused tools
# d_compiler = FindTool(d_compilers, env) or d_compilers[0]
d_compiler = None
other_tools = FindAllTools(other_plat_tools + [
# Nuitka: Avoid unused tools
# TODO: merge 'install' into 'filesystem' and
# make 'filesystem' the default
# 'filesystem',
# Parser generators
# 'lex', 'yacc',
# Foreign function interface
# 'rpcgen', 'swig',
# Java
# 'jar', 'javac', 'javah', 'rmic',
# TeX
# 'dvipdf', 'dvips', 'gs',
# 'tex', 'latex', 'pdflatex', 'pdftex',
# Archivers
# 'tar', 'zip',
# File builders (text)
# 'textfile',
], env)
tools = [
linker,
c_compiler,
cxx_compiler,
fortran_compiler,
assembler,
ar,
d_compiler,
] + other_tools
return [x for x in tools if x]
def find_program_path(env, key_program, default_paths=None, add_path=False):
"""
Find the location of a tool using various means.
Mainly for windows where tools aren't all installed in /usr/bin, etc.
Args:
env: Current Construction Environment.
key_program: Tool to locate.
default_paths: List of additional paths this tool might be found in.
add_path: If true, add path found if it was from *default_paths*.
"""
# First search in the SCons path
path = env.WhereIs(key_program)
if path:
return path
# Then in the OS path
path = SCons.Util.WhereIs(key_program)
if path:
if add_path:
env.AppendENVPath('PATH', os.path.dirname(path))
return path
# Finally, add the defaults and check again.
if default_paths is None:
return path
save_path = env['ENV']['PATH']
for p in default_paths:
env.AppendENVPath('PATH', p)
path = env.WhereIs(key_program)
# By default, do not change ['ENV']['PATH'] permananetly
# leave that to the caller, unless add_path is true.
env['ENV']['PATH'] = save_path
if path and add_path:
env.AppendENVPath('PATH', os.path.dirname(path))
return path
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,43 @@
"""SCons.Tool.aixc++
Tool-specific initialization for IBM xlC / Visual Age C++ compiler.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
#forward proxy to the preffered cxx version
from SCons.Tool.aixcxx import *
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,74 @@
"""SCons.Tool.aixcc
Tool-specific initialization for IBM xlc / Visual Age C compiler.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
import os.path
import SCons.Platform.aix
from . import cc
packages = ['vac.C', 'ibmcxx.cmp']
def get_xlc(env):
xlc = env.get('CC', 'xlc')
return SCons.Platform.aix.get_xlc(env, xlc, packages)
def generate(env):
"""Add Builders and construction variables for xlc / Visual Age
suite to an Environment."""
path, _cc, version = get_xlc(env)
if path and _cc:
_cc = os.path.join(path, _cc)
if 'CC' not in env:
env['CC'] = _cc
cc.generate(env)
if version:
env['CCVERSION'] = version
def exists(env):
path, _cc, version = get_xlc(env)
if path and _cc:
xlc = os.path.join(path, _cc)
if os.path.exists(xlc):
return xlc
return None
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,77 @@
"""SCons.Tool.aixc++
Tool-specific initialization for IBM xlC / Visual Age C++ compiler.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
import os.path
import SCons.Platform.aix
import SCons.Tool.cxx
cplusplus = SCons.Tool.cxx
#cplusplus = __import__('cxx', globals(), locals(), [])
packages = ['vacpp.cmp.core', 'vacpp.cmp.batch', 'vacpp.cmp.C', 'ibmcxx.cmp']
def get_xlc(env):
xlc = env.get('CXX', 'xlC')
return SCons.Platform.aix.get_xlc(env, xlc, packages)
def generate(env):
"""Add Builders and construction variables for xlC / Visual Age
suite to an Environment."""
path, _cxx, version = get_xlc(env)
if path and _cxx:
_cxx = os.path.join(path, _cxx)
if 'CXX' not in env:
env['CXX'] = _cxx
cplusplus.generate(env)
if version:
env['CXXVERSION'] = version
def exists(env):
path, _cxx, version = get_xlc(env)
if path and _cxx:
xlc = os.path.join(path, _cxx)
if os.path.exists(xlc):
return xlc
return None
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,78 @@
"""SCons.Tool.aixlink
Tool-specific initialization for the IBM Visual Age linker.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# 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 os.path
import SCons.Util
from . import aixcc
from . import link
import SCons.Tool.cxx
cplusplus = SCons.Tool.cxx
def smart_linkflags(source, target, env, for_signature):
if cplusplus.iscplusplus(source):
build_dir = env.subst('$BUILDDIR', target=target, source=source)
if build_dir:
return '-qtempinc=' + os.path.join(build_dir, 'tempinc')
return ''
def generate(env):
"""
Add Builders and construction variables for Visual Age linker to
an Environment.
"""
link.generate(env)
env['SMARTLINKFLAGS'] = smart_linkflags
env['LINKFLAGS'] = SCons.Util.CLVar('$SMARTLINKFLAGS')
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -qmkshrobj -qsuppress=1501-218')
env['SHLIBSUFFIX'] = '.a'
def exists(env):
# TODO: sync with link.smart_link() to choose a linker
linkers = { 'CXX': ['aixc++'], 'CC': ['aixcc'] }
alltools = []
for langvar, linktools in linkers.items():
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)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,209 @@
"""SCons.Tool.applelink
Tool-specific initialization for Apple's gnu-like linker.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# MIT License
#
# Copyright 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.
#
# Even though the Mac is based on the GNU toolchain, it doesn't understand
# the -rpath option, so we use the "link" tool instead of "gnulink".
from SCons.Util import CLVar
from SCons.Errors import UserError
from . import link
# User programmatically describes how SHLIBVERSION maps to values for compat/current.
_APPLELIB_MAX_VERSION_VALUES = (65535, 255, 255)
class AppleLinkInvalidCurrentVersionException(Exception):
pass
class AppleLinkInvalidCompatibilityVersionException(Exception):
pass
def _applelib_check_valid_version(version_string):
"""
Check that the version # is valid.
X[.Y[.Z]]
where X 0-65535
where Y either not specified or 0-255
where Z either not specified or 0-255
:param version_string:
:return:
"""
parts = version_string.split('.')
if len(parts) > 3:
return False, "Version string has too many periods [%s]" % version_string
if len(parts) <= 0:
return False, "Version string unspecified [%s]" % version_string
for (i, p) in enumerate(parts):
try:
p_i = int(p)
except ValueError:
return False, "Version component %s (from %s) is not a number" % (p, version_string)
if p_i < 0 or p_i > _APPLELIB_MAX_VERSION_VALUES[i]:
return False, "Version component %s (from %s) is not valid value should be between 0 and %d" % (
p, version_string, _APPLELIB_MAX_VERSION_VALUES[i])
return True, ""
def _applelib_currentVersionFromSoVersion(source, target, env, for_signature):
"""
A generator function to create the -Wl,-current_version flag if needed.
If env['APPLELINK_NO_CURRENT_VERSION'] contains a true value no flag will be generated
Otherwise if APPLELINK_CURRENT_VERSION is not specified, env['SHLIBVERSION']
will be used.
:param source:
:param target:
:param env:
:param for_signature:
:return: A string providing the flag to specify the current_version of the shared library
"""
if env.get('APPLELINK_NO_CURRENT_VERSION', False):
return ""
elif env.get('APPLELINK_CURRENT_VERSION', False):
version_string = env['APPLELINK_CURRENT_VERSION']
elif env.get('SHLIBVERSION', False):
version_string = env['SHLIBVERSION']
else:
return ""
version_string = ".".join(version_string.split('.')[:3])
valid, reason = _applelib_check_valid_version(version_string)
if not valid:
raise AppleLinkInvalidCurrentVersionException(reason)
return "-Wl,-current_version,%s" % version_string
def _applelib_compatVersionFromSoVersion(source, target, env, for_signature):
"""
A generator function to create the -Wl,-compatibility_version flag if needed.
If env['APPLELINK_NO_COMPATIBILITY_VERSION'] contains a true value no flag will be generated
Otherwise if APPLELINK_COMPATIBILITY_VERSION is not specified
the first two parts of env['SHLIBVERSION'] will be used with a .0 appended.
:param source:
:param target:
:param env:
:param for_signature:
:return: A string providing the flag to specify the compatibility_version of the shared library
"""
if env.get('APPLELINK_NO_COMPATIBILITY_VERSION', False):
return ""
elif env.get('APPLELINK_COMPATIBILITY_VERSION', False):
version_string = env['APPLELINK_COMPATIBILITY_VERSION']
elif env.get('SHLIBVERSION', False):
version_string = ".".join(env['SHLIBVERSION'].split('.')[:2] + ['0'])
else:
return ""
if version_string is None:
return ""
valid, reason = _applelib_check_valid_version(version_string)
if not valid:
raise AppleLinkInvalidCompatibilityVersionException(reason)
return "-Wl,-compatibility_version,%s" % version_string
def _applelib_soname(target, source, env, for_signature):
"""
Override default _soname() function from SCons.Tools.linkCommon.SharedLibrary.
Apple's file naming for versioned shared libraries puts the version string before
the shared library suffix (.dylib), instead of after.
"""
if "SONAME" in env:
# Now verify that SOVERSION is not also set as that is not allowed
if "SOVERSION" in env:
raise UserError(
"Ambiguous library .so naming, both SONAME: %s and SOVERSION: %s are defined. "
"Only one can be defined for a target library."
% (env["SONAME"], env["SOVERSION"])
)
return "$SONAME"
else:
return "$SHLIBPREFIX$_get_shlib_stem$_SHLIBSOVERSION${SHLIBSUFFIX}"
def generate(env):
"""Add Builders and construction variables for applelink to an
Environment."""
link.generate(env)
env['FRAMEWORKPATHPREFIX'] = '-F'
env['_FRAMEWORKPATH'] = '${_concat(FRAMEWORKPATHPREFIX, FRAMEWORKPATH, "", __env__, RDirs)}'
env['_FRAMEWORKS'] = '${_concat("-framework ", FRAMEWORKS, "", __env__)}'
env['LINKCOM'] = env['LINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
env['SHLINKFLAGS'] = CLVar('$LINKFLAGS -dynamiclib')
env['SHLINKCOM'] = env['SHLINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
env['_APPLELINK_CURRENT_VERSION'] = _applelib_currentVersionFromSoVersion
env['_APPLELINK_COMPATIBILITY_VERSION'] = _applelib_compatVersionFromSoVersion
env['_SHLIBVERSIONFLAGS'] = '$_APPLELINK_CURRENT_VERSION $_APPLELINK_COMPATIBILITY_VERSION '
env['_LDMODULEVERSIONFLAGS'] = '$_APPLELINK_CURRENT_VERSION $_APPLELINK_COMPATIBILITY_VERSION '
# override the default for loadable modules, which are different
# on OS X than dynamic shared libs. echoing what XCode does for
# pre/suffixes:
env['LDMODULEPREFIX'] = ''
env['LDMODULESUFFIX'] = ''
env['LDMODULEFLAGS'] = CLVar('$LINKFLAGS -bundle')
env['LDMODULECOM'] = '$LDMODULE -o ${TARGET} $LDMODULEFLAGS' \
' $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
# New stuff
#
env['_SHLIBSUFFIX'] = '${_SHLIBVERSION}${SHLIBSUFFIX}'
env['__SHLIBVERSIONFLAGS'] = '${__lib_either_version_flag(__env__,' \
'"SHLIBVERSION","_APPLELINK_CURRENT_VERSION", "_SHLIBVERSIONFLAGS")}'
env['__LDMODULEVERSIONFLAGS'] = '${__lib_either_version_flag(__env__,' \
'"LDMODULEVERSION","_APPLELINK_CURRENT_VERSION", "_LDMODULEVERSIONFLAGS")}'
env["_SHLIBSONAME"] = _applelib_soname
def exists(env):
return env['PLATFORM'] == 'darwin'
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,63 @@
"""SCons.Tool.ar
Tool-specific initialization for ar (library archive).
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
import SCons.Defaults
import SCons.Tool
import SCons.Util
def generate(env):
"""Add Builders and construction variables for ar to an Environment."""
SCons.Tool.createStaticLibBuilder(env)
env['AR'] = 'ar'
env['ARFLAGS'] = SCons.Util.CLVar('rc')
env['ARCOM'] = '$AR $ARFLAGS $TARGET $SOURCES'
env['LIBPREFIX'] = 'lib'
env['LIBSUFFIX'] = '.a'
if env.get('RANLIB',env.Detect('ranlib')) :
env['RANLIB'] = env.get('RANLIB','ranlib')
env['RANLIBFLAGS'] = SCons.Util.CLVar('')
env['RANLIBCOM'] = '$RANLIB $RANLIBFLAGS $TARGET'
def exists(env):
return env.Detect('ar')
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,49 @@
"""SCons.Tool.as
Tool-specific initialization for generic assembler.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
#
# forward proxy to the preferred asm version
#
import SCons.Tool.asm
# Resolve FLAKE8 F401 (make sider happy)
generate = SCons.Tool.asm.generate
exists = SCons.Tool.asm.exists
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,78 @@
"""SCons.Tool.as
Tool-specific initialization for as, the generic Posix assembler.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
import SCons.Defaults
import SCons.Tool
import SCons.Util
assemblers = ['as']
ASSuffixes = ['.s', '.asm', '.ASM']
ASPPSuffixes = ['.spp', '.SPP', '.sx']
if SCons.Util.case_sensitive_suffixes('.s', '.S'):
ASPPSuffixes.extend(['.S'])
else:
ASSuffixes.extend(['.S'])
def generate(env):
"""Add Builders and construction variables for as to an Environment."""
static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
for suffix in ASSuffixes:
static_obj.add_action(suffix, SCons.Defaults.ASAction)
shared_obj.add_action(suffix, SCons.Defaults.ASAction)
static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)
for suffix in ASPPSuffixes:
static_obj.add_action(suffix, SCons.Defaults.ASPPAction)
shared_obj.add_action(suffix, SCons.Defaults.ASPPAction)
static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)
env['AS'] = env.Detect(assemblers) or 'as'
env['ASFLAGS'] = SCons.Util.CLVar('')
env['ASCOM'] = '$AS $ASFLAGS -o $TARGET $SOURCES'
env['ASPPFLAGS'] = '$ASFLAGS'
env['ASPPCOM'] = '$CC $ASPPFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES'
def exists(env):
return env.Detect(assemblers)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,81 @@
"""SCons.Tool.bcc32
XXX
"""
#
# __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__"
import os
import os.path
import SCons.Defaults
import SCons.Tool
import SCons.Util
def findIt(program, env):
# First search in the SCons path and then the OS path:
borwin = env.WhereIs(program) or SCons.Util.WhereIs(program)
if borwin:
dir = os.path.dirname(borwin)
env.PrependENVPath('PATH', dir)
return borwin
def generate(env):
findIt('bcc32', env)
"""Add Builders and construction variables for bcc to an
Environment."""
static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
for suffix in ['.c', '.cpp']:
static_obj.add_action(suffix, SCons.Defaults.CAction)
shared_obj.add_action(suffix, SCons.Defaults.ShCAction)
static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)
env['CC'] = 'bcc32'
env['CCFLAGS'] = SCons.Util.CLVar('')
env['CFLAGS'] = SCons.Util.CLVar('')
env['CCCOM'] = '$CC -q $CFLAGS $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o$TARGET $SOURCES'
env['SHCC'] = '$CC'
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')
env['SHCFLAGS'] = SCons.Util.CLVar('$CFLAGS')
env['SHCCCOM'] = '$SHCC -WD $SHCFLAGS $SHCCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o$TARGET $SOURCES'
env['CPPDEFPREFIX'] = '-D'
env['CPPDEFSUFFIX'] = ''
env['INCPREFIX'] = '-I'
env['INCSUFFIX'] = ''
env['SHOBJSUFFIX'] = '.dll'
env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 0
env['CFILESUFFIX'] = '.cpp'
def exists(env):
return findIt('bcc32', env)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,44 @@
"""SCons.Tool.c++
Tool-specific initialization for generic Posix C++ compilers.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
#forward proxy to the preffered cxx version
from SCons.Tool.cxx import *
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,105 @@
"""SCons.Tool.cc
Tool-specific initialization for generic Posix C compilers.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
import SCons.Tool
import SCons.Defaults
import SCons.Util
CSuffixes = ['.c', '.m']
if not SCons.Util.case_sensitive_suffixes('.c', '.C'):
CSuffixes.append('.C')
def add_common_cc_variables(env):
"""
Add underlying common "C compiler" variables that
are used by multiple tools (specifically, c++).
"""
if '_CCCOMCOM' not in env:
env['_CCCOMCOM'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS'
# It's a hack to test for darwin here, but the alternative
# of creating an applecc.py to contain this seems overkill.
# Maybe someday the Apple platform will require more setup and
# this logic will be moved.
env['FRAMEWORKS'] = SCons.Util.CLVar('')
env['FRAMEWORKPATH'] = SCons.Util.CLVar('')
if env['PLATFORM'] == 'darwin':
env['_CCCOMCOM'] = env['_CCCOMCOM'] + ' $_FRAMEWORKPATH'
if 'CCFLAGS' not in env:
env['CCFLAGS'] = SCons.Util.CLVar('')
if 'SHCCFLAGS' not in env:
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')
compilers = ['cc']
def generate(env):
"""
Add Builders and construction variables for C compilers to an Environment.
"""
static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
for suffix in CSuffixes:
static_obj.add_action(suffix, SCons.Defaults.CAction)
shared_obj.add_action(suffix, SCons.Defaults.ShCAction)
static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)
add_common_cc_variables(env)
if 'CC' not in env:
env['CC'] = env.Detect(compilers) or compilers[0]
env['CFLAGS'] = SCons.Util.CLVar('')
env['CCCOM'] = '$CC -o $TARGET -c $CFLAGS $CCFLAGS $_CCCOMCOM $SOURCES'
env['SHCC'] = '$CC'
env['SHCFLAGS'] = SCons.Util.CLVar('$CFLAGS')
env['SHCCCOM'] = '$SHCC -o $TARGET -c $SHCFLAGS $SHCCFLAGS $_CCCOMCOM $SOURCES'
env['CPPDEFPREFIX'] = '-D'
env['CPPDEFSUFFIX'] = ''
env['INCPREFIX'] = '-I'
env['INCSUFFIX'] = ''
env['SHOBJSUFFIX'] = '.os'
env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 0
env['CFILESUFFIX'] = '.c'
def exists(env):
return env.Detect(env.get('CC', compilers))
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,91 @@
# 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.
"""Tool-specific initialization for clang.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
# Based on SCons/Tool/gcc.py by Paweł Tomulik 2014 as a separate tool.
# Brought into the SCons mainline by Russel Winder 2017.
import os
import re
from subprocess import DEVNULL, PIPE
import SCons.Util
import SCons.Tool.cc
from SCons.Tool.clangCommon import get_clang_install_dirs
from SCons.Tool.MSCommon import msvc_setup_env_once
compilers = ['clang']
def generate(env):
"""Add Builders and construction variables for clang to an Environment."""
SCons.Tool.cc.generate(env)
if env['PLATFORM'] == 'win32':
# Ensure that we have a proper path for clang
clang = SCons.Tool.find_program_path(
env, compilers[0], default_paths=get_clang_install_dirs(env['PLATFORM'])
)
if clang:
clang_bin_dir = os.path.dirname(clang)
env.AppendENVPath("PATH", clang_bin_dir)
# Set-up ms tools paths
msvc_setup_env_once(env)
env['CC'] = env.Detect(compilers) or 'clang'
if env['PLATFORM'] in ['cygwin', 'win32']:
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')
else:
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC')
# determine compiler version
if env['CC']:
kw = {
'stdout': PIPE,
'stderr': DEVNULL,
'universal_newlines': True,
}
cp = SCons.Action.scons_subproc_run(env, [env['CC'], '-dumpversion'], **kw)
line = cp.stdout
if line:
env['CCVERSION'] = line
env['CCDEPFLAGS'] = '-MMD -MF ${TARGET}.d'
env["NINJA_DEPFILE_PARSE_FORMAT"] = 'clang'
def exists(env):
return env.Detect(compilers)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,18 @@
"""
Common routines and data for clang tools
"""
clang_win32_dirs = [
r'C:\Program Files\LLVM\bin',
r'C:\cygwin64\bin',
r'C:\msys64',
r'C:\msys64\mingw64\bin',
r'C:\cygwin\bin',
r'C:\msys',
]
def get_clang_install_dirs(platform):
if platform == 'win32':
return clang_win32_dirs
else:
return []

View File

@@ -0,0 +1,99 @@
# 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.
"""Tool-specific initialization for clang++.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
# Based on SCons/Tool/g++.py by Paweł Tomulik 2014 as a separate tool.
# Brought into the SCons mainline by Russel Winder 2017.
import os.path
import re
from subprocess import DEVNULL, PIPE
import SCons.Tool
import SCons.Util
import SCons.Tool.cxx
from SCons.Tool.clangCommon import get_clang_install_dirs
from SCons.Tool.MSCommon import msvc_setup_env_once
compilers = ['clang++']
def generate(env):
"""Add Builders and construction variables for clang++ to an Environment."""
static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
SCons.Tool.cxx.generate(env)
env['CXX'] = env.Detect(compilers) or 'clang++'
# platform specific settings
if env['PLATFORM'] == 'aix':
env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS -mminimal-toc')
env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
env['SHOBJSUFFIX'] = '$OBJSUFFIX'
elif env['PLATFORM'] == 'hpux':
env['SHOBJSUFFIX'] = '.pic.o'
elif env['PLATFORM'] == 'sunos':
env['SHOBJSUFFIX'] = '.pic.o'
elif env['PLATFORM'] == 'win32':
# Ensure that we have a proper path for clang++
clangxx = SCons.Tool.find_program_path(
env, compilers[0], default_paths=get_clang_install_dirs(env['PLATFORM'])
)
if clangxx:
clangxx_bin_dir = os.path.dirname(clangxx)
env.AppendENVPath('PATH', clangxx_bin_dir)
# Set-up ms tools paths
msvc_setup_env_once(env)
# determine compiler version
if env['CXX']:
kw = {
'stdout': PIPE,
'stderr': DEVNULL,
'universal_newlines': True,
}
cp = SCons.Action.scons_subproc_run(env, [env['CXX'], '-dumpversion'], **kw)
line = cp.stdout
if line:
env['CXXVERSION'] = line
env['CCDEPFLAGS'] = '-MMD -MF ${TARGET}.d'
env["NINJA_DEPFILE_PARSE_FORMAT"] = 'clang'
def exists(env):
return env.Detect(compilers)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,95 @@
# 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.
"""Tool-specific initialization for generic Posix C++ compilers.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
import os.path
import SCons.Defaults
import SCons.Util
compilers = ['CC', 'c++']
CXXSuffixes = ['.cpp', '.cc', '.cxx', '.c++', '.C++', '.mm']
if SCons.Util.case_sensitive_suffixes('.c', '.C'):
CXXSuffixes.append('.C')
def iscplusplus(source):
if not source:
# Source might be None for unusual cases like SConf.
return False
for s in source:
if s.sources:
ext = os.path.splitext(str(s.sources[0]))[1]
if ext in CXXSuffixes:
return True
return False
def generate(env):
"""
Add Builders and construction variables for Visual Age C++ compilers
to an Environment.
"""
import SCons.Tool
import SCons.Tool.cc
static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
for suffix in CXXSuffixes:
static_obj.add_action(suffix, SCons.Defaults.CXXAction)
shared_obj.add_action(suffix, SCons.Defaults.ShCXXAction)
static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)
SCons.Tool.cc.add_common_cc_variables(env)
if 'CXX' not in env:
env['CXX'] = env.Detect(compilers) or compilers[0]
env['CXXFLAGS'] = SCons.Util.CLVar('')
env['CXXCOM'] = '$CXX -o $TARGET -c $CXXFLAGS $CCFLAGS $_CCCOMCOM $SOURCES'
env['SHCXX'] = '$CXX'
env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS')
env['SHCXXCOM'] = '$SHCXX -o $TARGET -c $SHCXXFLAGS $SHCCFLAGS $_CCCOMCOM $SOURCES'
env['CPPDEFPREFIX'] = '-D'
env['CPPDEFSUFFIX'] = ''
env['INCPREFIX'] = '-I'
env['INCSUFFIX'] = ''
env['SHOBJSUFFIX'] = '.os'
env['OBJSUFFIX'] = '.o'
env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 0
env['CXXFILESUFFIX'] = '.cc'
def exists(env):
return env.Detect(env.get('CXX', compilers))
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,235 @@
# 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.
"""SCons.Tool.cyglink
Customization of gnulink for Cygwin (https://www.cygwin.com/)
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
from SCons.Tool.linkCommon import StringizeLibSymlinks, EmitLibSymlinks
from SCons.Util import CLVar, is_String
from . import gnulink
def cyglink_lib_emitter(target, source, env, **kw):
verbose = True
if 'variable_prefix' in kw:
var_prefix = kw['variable_prefix']
else:
var_prefix = 'SHLIB'
no_import_lib = env.get('no_import_lib', False)
if verbose:
print(f"cyglink_lib_emitter: target[0]={target[0].get_path()!r}")
if not no_import_lib:
# Specify import lib and add to targets
import_lib = env.subst('$%s_IMPLIBNAME' % var_prefix, target=target, source=source)
import_lib_target = env.fs.File(import_lib)
import_lib_target.attributes.shared = True
target.append(import_lib_target)
if verbose:
print(f"cyglink_lib_emitter: import_lib={import_lib}")
print("cyglink_lib_emitter: target=%s" % target)
for tgt in target:
if is_String(tgt):
tgt = env.File(tgt)
tgt.attributes.shared = True
return target, source
def cyglink_ldmodule_emitter(target, source, env, **kw):
return cyglink_lib_emitter(target, source, env, variable_prefix='LDMODULE')
def cyglink_shlib_symlink_emitter(target, source, env, **kw):
"""
On cygwin, we only create a symlink from the non-versioned implib to the versioned implib.
We don't version the shared library itself.
:param target:
:param source:
:param env:
:param kw:
:return:
"""
verbose = True
if 'variable_prefix' in kw:
var_prefix = kw['variable_prefix']
else:
var_prefix = 'SHLIB'
no_import_lib = env.get('no_import_lib', False)
if no_import_lib in ['1', 'True', 'true', True]:
if verbose:
print("cyglink_shlib_symlink_emitter: no_import_lib=%s" % no_import_lib)
return target, source
no_symlinks = env.subst('$%sNOVERSIONSYMLINKS' % var_prefix)
if no_symlinks in ['1', 'True', 'true', True]:
return target, source
shlibversion = env.subst('$%sVERSION' % var_prefix)
if shlibversion:
if verbose:
print("cyglink_shlib_symlink_emitter: %sVERSION=%s" % (var_prefix, shlibversion))
# The implib (added by the cyglink_lib_emitter)
imp_lib_node = target[1]
shlib_noversion_symlink = env.subst('$%s_NOVERSION_SYMLINK' % var_prefix, target=target[0], source=source)
if verbose:
print("cyglink_shlib_symlink_emitter: shlib_noversion_symlink :%s" % shlib_noversion_symlink)
print("cyglink_shlib_symlink_emitter: imp_lib_node :%s" % imp_lib_node)
symlinks = [(env.File(shlib_noversion_symlink), imp_lib_node)]
if verbose:
print("cyglink_shlib_symlink_emitter: symlinks={!r}".format(
', '.join(["%r->%r" % (k, v) for k, v in StringizeLibSymlinks(symlinks)])
))
if symlinks:
# This does the actual symlinking
EmitLibSymlinks(env, symlinks, target[0])
# This saves the information so if the versioned shared library is installed
# it can faithfully reproduce the correct symlinks
target[0].attributes.shliblinks = symlinks
return target, source
def cyglink_ldmod_symlink_emitter(target, source, env, **kw):
return cyglink_shlib_symlink_emitter(target, source, env, variable_prefix='LDMODULE')
def cyglink_shlibversion(target, source, env, for_signature):
var_prefix = 'SHLIB'
var = '%sVERSION' % var_prefix
if var not in env:
return ''
version = env.subst("$%s" % var, target=target, source=source)
version = version.replace('.', '-')
return "." + version
def cyglink_ldmodule_version(target, source, env, for_signature):
var_prefix = 'LDMODULE'
var = '%sVERSION' % var_prefix
if var not in env:
return ''
version = env.subst("$%s" % var, target=target, source=source)
version = version.replace('.', '-')
return "." + version
def _implib_pre_flags(target, source, env, for_signature):
no_import_lib = env.get('no_import_lib', False)
if no_import_lib in ['1', 'True', 'true', True]:
return ''
else:
return '-Wl,--out-implib=${TARGETS[1]} -Wl,--export-all-symbols -Wl,--enable-auto-import -Wl,--whole-archive'
def _implib_post_flags(target, source, env, for_signature):
no_import_lib = env.get('no_import_lib', False)
if no_import_lib in ['1', 'True', 'true', True]:
return ''
else:
return '-Wl,--no-whole-archive'
def generate(env):
"""Add Builders and construction variables for cyglink to an Environment."""
gnulink.generate(env)
env['LINKFLAGS'] = CLVar('-Wl,-no-undefined')
env['SHLIBPREFIX'] = 'cyg'
env['SHLIBSUFFIX'] = '.dll'
env['IMPLIBPREFIX'] = 'lib'
env['IMPLIBSUFFIX'] = '.dll.a'
# Variables used by versioned shared libraries
# SHLIBVERSIONFLAGS and LDMODULEVERSIONFLAGS are same as in gnulink...
env['_SHLIBVERSIONFLAGS'] = '$SHLIBVERSIONFLAGS'
env['_LDMODULEVERSIONFLAGS'] = '$LDMODULEVERSIONFLAGS'
env['_IMPLIB_PRE_SOURCES'] = _implib_pre_flags
env['_IMPLIB_POST_SOURCES'] = _implib_post_flags
env['SHLINKCOM'] = '$SHLINK -o $TARGET $SHLINKFLAGS $__SHLIBVERSIONFLAGS $__RPATH ' \
'$_IMPLIB_PRE_SOURCES $SOURCES $_IMPLIB_POST_SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
env['LDMODULECOM'] = '$LDMODULE -o $TARGET $SHLINKFLAGS $__LDMODULEVERSIONFLAGS $__RPATH ' \
'$_IMPLIB_PRE_SOURCES $SOURCES $_IMPLIB_POST_SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
# Overwrite emitters. Cyglink does things differently when creating symlinks
env['SHLIBEMITTER'] = [cyglink_lib_emitter, cyglink_shlib_symlink_emitter]
env['LDMODULEEMITTER'] = [cyglink_ldmodule_emitter, cyglink_ldmod_symlink_emitter]
# This is the non versioned shlib filename
# If SHLIBVERSION is defined then this will symlink to $SHLIBNAME
env['SHLIB_NOVERSION_SYMLINK'] = '${IMPLIBPREFIX}$_get_shlib_stem${IMPLIBSUFFIX}'
env['LDMODULE_NOVERSION_SYMLINK'] = '${IMPLIBPREFIX}$_get_ldmodule_stem${IMPLIBSUFFIX}'
env['SHLIB_IMPLIBNAME'] = '${IMPLIBPREFIX}$_get_shlib_stem${_SHLIB_IMPLIBSUFFIX}'
env['LDMODULE_IMPLIBNAME'] = '${IMPLIBPREFIX}$_get_ldmodule_stem${_LDMODULE_IMPLIBSUFFIX}'
env['_cyglink_shlibversion'] = cyglink_shlibversion
env['_SHLIB_IMPLIBSUFFIX'] = '${_cyglink_shlibversion}${IMPLIBSUFFIX}'
env['_SHLIBSUFFIX'] = '${_cyglink_shlibversion}${SHLIBSUFFIX}'
env['_cyglink_ldmodule_version'] = cyglink_ldmodule_version
env['_LDMODULESUFFIX'] = '${_cyglink_ldmodule_version}${LDMODULESUFFIX}'
env['_LDMODULE_IMPLIBSUFFIX'] = '${_cyglink_ldmodule_version}${IMPLIBSUFFIX}'
# Remove variables set by default initialization which aren't needed/used by cyglink
# these variables were set by gnulink but are not used in cyglink
for rv in ['_SHLIBSONAME', '_LDMODULESONAME']:
if rv in env:
del env[rv]
def exists(env):
return gnulink.exists(env)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,45 @@
# 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.
"""Initialization with a default tool list.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
import SCons.Tool
def generate(env):
"""Add default tools."""
for t in SCons.Tool.tool_list(env['PLATFORM'], env):
SCons.Tool.Tool(t)(env)
def exists(env):
return True
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,94 @@
# 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.
"""Tool-specific initialization for the filesystem tools.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
import SCons
from SCons.Tool.install import copyFunc
copyToBuilder, copyAsBuilder = None, None
def copyto_emitter(target, source, env):
""" changes the path of the source to be under the target (which
are assumed to be directories.
"""
n_target = []
for t in target:
n_target = n_target + [t.File( str( s ) ) for s in source]
return (n_target, source)
def copy_action_func(target, source, env):
assert( len(target) == len(source) ), "\ntarget: %s\nsource: %s" %(list(map(str, target)),list(map(str, source)))
for t, s in zip(target, source):
if copyFunc(t.get_path(), s.get_path(), env):
return 1
return 0
def copy_action_str(target, source, env):
return env.subst_target_source(env['COPYSTR'], 0, target, source)
copy_action = SCons.Action.Action( copy_action_func, copy_action_str )
def generate(env):
try:
env['BUILDERS']['CopyTo']
env['BUILDERS']['CopyAs']
except KeyError as e:
global copyToBuilder
if copyToBuilder is None:
copyToBuilder = SCons.Builder.Builder(
action = copy_action,
target_factory = env.fs.Dir,
source_factory = env.fs.Entry,
multi = 1,
emitter = [ copyto_emitter, ] )
global copyAsBuilder
if copyAsBuilder is None:
copyAsBuilder = SCons.Builder.Builder(
action = copy_action,
target_factory = env.fs.Entry,
source_factory = env.fs.Entry )
env['BUILDERS']['CopyTo'] = copyToBuilder
env['BUILDERS']['CopyAs'] = copyAsBuilder
env['COPYSTR'] = 'Copy file(s): "$SOURCES" to "$TARGETS"'
def exists(env):
return True
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,45 @@
"""SCons.Tool.g++
Tool-specific initialization for g++.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
#forward proxy to the preffered cxx version
from SCons.Tool.gxx import *
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,56 @@
"""SCons.Tool.gas
Tool-specific initialization for as, the Gnu assembler.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
try:
as_module = __import__('as', globals(), locals(), [])
except:
as_module = __import__(__package__+'.as', globals(), locals(), ['*'])
assemblers = ['as', 'gas']
def generate(env):
"""Add Builders and construction variables for as to an Environment."""
as_module.generate(env)
env['AS'] = env.Detect(assemblers) or 'as'
def exists(env):
return env.Detect(assemblers)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,108 @@
# 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.
"""SCons.Tool.gcc
Tool-specific initialization for gcc.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
from . import cc
import re
from subprocess import PIPE
import SCons.Util
compilers = ['gcc', 'cc']
def generate(env):
"""Add Builders and construction variables for gcc to an Environment."""
if 'CC' not in env:
env['CC'] = env.Detect(compilers) or compilers[0]
cc.generate(env)
if env['PLATFORM'] in ['cygwin', 'win32']:
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')
else:
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC')
# determine compiler version
version = detect_version(env, env['CC'])
if version:
env['CCVERSION'] = version
env['CCDEPFLAGS'] = '-MMD -MF ${TARGET}.d'
env["NINJA_DEPFILE_PARSE_FORMAT"] = 'gcc'
def exists(env):
# is executable, and is a GNU compiler (or accepts '--version' at least)
return detect_version(env, env.Detect(env.get('CC', compilers)))
def detect_version(env, cc):
"""Return the version of the GNU compiler, or None if it is not a GNU compiler."""
version = None
cc = env.subst(cc)
if not cc:
return version
# -dumpversion was added in GCC 3.0. As long as we're supporting
# GCC versions older than that, we should use --version and a
# regular expression.
# pipe = SCons.Action.scons_subproc_run(env, SCons.Util.CLVar(cc) + ['-dumpversion'],
cp = SCons.Action.scons_subproc_run(
env, SCons.Util.CLVar(cc) + ['--version'], stdout=PIPE
)
if cp.returncode:
return version
# -dumpversion variant:
# line = cp.stdout.strip()
# --version variant:
try:
line = SCons.Util.to_str(cp.stdout.splitlines()[0])
except IndexError:
return version
# -dumpversion variant:
# version = line
# --version variant:
match = re.search(r'[0-9]+(\.[0-9]+)+', line)
if match:
version = match.group(0)
return version
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,73 @@
#
# 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.
#
"""SCons.Tool.gnulink
Tool-specific initialization for the gnu linker.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
import SCons.Tool.linkCommon
import SCons.Util
import SCons.Tool
from . import link
def generate(env):
"""Add Builders and construction variables for gnulink to an Environment."""
link.generate(env)
if env['PLATFORM'] == 'hpux':
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared -fPIC')
# __RPATH is set to $_RPATH in the platform specification if that
# platform supports it.
env['RPATHPREFIX'] = '-Wl,-rpath='
env['RPATHSUFFIX'] = ''
env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}'
env['LIBLITERALPREFIX'] = ':'
def exists(env):
# TODO: sync with link.smart_link() to choose a linker
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
return SCons.Tool.FindTool(linktools, env)
alltools.extend(linktools)
return SCons.Tool.FindTool(alltools, env) # find CXX or CC
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,80 @@
# 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.
"""SCons.Tool.g++
Tool-specific initialization for g++.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
import SCons.Tool
import SCons.Util
from . import gcc
from . import cxx
compilers = ['g++']
def generate(env):
"""Add Builders and construction variables for g++ to an Environment."""
static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
if 'CXX' not in env:
env['CXX'] = env.Detect(compilers) or compilers[0]
cxx.generate(env)
# platform specific settings
if env['PLATFORM'] == 'aix':
env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS -mminimal-toc')
env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
env['SHOBJSUFFIX'] = '$OBJSUFFIX'
elif env['PLATFORM'] == 'hpux':
env['SHOBJSUFFIX'] = '.pic.o'
elif env['PLATFORM'] == 'sunos':
env['SHOBJSUFFIX'] = '.pic.o'
# determine compiler version
version = gcc.detect_version(env, env['CXX'])
if version:
env['CXXVERSION'] = version
env['CCDEPFLAGS'] = '-MMD -MF ${TARGET}.d'
env["NINJA_DEPFILE_PARSE_FORMAT"] = 'gcc'
def exists(env):
# is executable, and is a GNU compiler (or accepts '--version' at least)
return gcc.detect_version(env, env.Detect(env.get('CXX', compilers)))
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,45 @@
"""SCons.Tool.hpc++
Tool-specific initialization for c++ on HP/UX.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
#forward proxy to the preffered cxx version
from SCons.Tool.hpcxx import *
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,53 @@
"""SCons.Tool.hpcc
Tool-specific initialization for HP aCC and cc.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
import SCons.Util
from . import cc
def generate(env):
"""Add Builders and construction variables for aCC & cc to an Environment."""
cc.generate(env)
env['CXX'] = 'aCC'
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS +Z')
def exists(env):
return env.Detect('aCC')
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,85 @@
# 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.
"""SCons.Tool.hpc++
Tool-specific initialization for c++ on HP/UX.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
import os.path
import SCons.Util
import SCons.Tool.cxx
cplusplus = SCons.Tool.cxx
#cplusplus = __import__('cxx', globals(), locals(), [])
acc = None
# search for the acc compiler and linker front end
try:
dirs = os.listdir('/opt')
except OSError:
# Not being able to read the directory because it doesn't exist
# (IOError) or isn't readable (OSError) is okay.
dirs = []
for dir in dirs:
cc = '/opt/' + dir + '/bin/aCC'
if os.path.exists(cc):
acc = cc
break
def generate(env):
"""Add Builders and construction variables for g++ to an Environment."""
cplusplus.generate(env)
if acc:
env['CXX'] = acc or 'aCC'
env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS +Z')
# determine version of aCC
with os.popen(acc + ' -V 2>&1') as p:
line = p.readline().rstrip()
if line.startswith('aCC: HP ANSI C++'):
env['CXXVERSION'] = line.split()[-1]
if env['PLATFORM'] == 'cygwin':
env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS')
else:
env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS +Z')
def exists(env):
return acc
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,72 @@
"""SCons.Tool.hplink
Tool-specific initialization for the HP linker.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# 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.path
import SCons.Util
from . import link
ccLinker = None
# search for the acc compiler and linker front end
try:
dirs = os.listdir('/opt')
except OSError:
# Not being able to read the directory because it doesn't exist
# (IOError) or isn't readable (OSError) is okay.
dirs = []
for dir in dirs:
linker = '/opt/' + dir + '/bin/aCC'
if os.path.exists(linker):
ccLinker = linker
break
def generate(env):
"""
Add Builders and construction variables for Visual Age linker to
an Environment.
"""
link.generate(env)
env['LINKFLAGS'] = SCons.Util.CLVar('-Wl,+s -Wl,+vnocompatwarnings')
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -b')
env['SHLIBSUFFIX'] = '.sl'
def exists(env):
return ccLinker
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,59 @@
"""SCons.Tool.icc
Tool-specific initialization for the OS/2 icc compiler.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
from . import cc
def generate(env):
"""Add Builders and construction variables for the OS/2 to an Environment."""
cc.generate(env)
env['CC'] = 'icc'
env['CCCOM'] = '$CC $CFLAGS $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET'
env['CXXCOM'] = '$CXX $CXXFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET'
env['CPPDEFPREFIX'] = '/D'
env['CPPDEFSUFFIX'] = ''
env['INCPREFIX'] = '/I'
env['INCSUFFIX'] = ''
env['CFILESUFFIX'] = '.c'
env['CXXFILESUFFIX'] = '.cc'
def exists(env):
return env.Detect('icc')
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,52 @@
"""SCons.Tool.icl
Tool-specific initialization for the Intel C/C++ compiler.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# __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__"
import SCons.Tool.intelc
# This has been completely superseded by intelc.py, which can
# handle both Windows and Linux versions.
def generate(*args, **kw):
"""Add Builders and construction variables for icl to an Environment."""
return SCons.Tool.intelc.generate(*args, **kw)
def exists(*args, **kw):
return SCons.Tool.intelc.exists(*args, **kw)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,60 @@
"""SCons.Tool.ilink32
XXX
"""
#
# __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__"
import SCons.Tool
import SCons.Tool.bcc32
import SCons.Util
def generate(env):
"""Add Builders and construction variables for Borland ilink to an
Environment."""
SCons.Tool.createSharedLibBuilder(env)
SCons.Tool.createProgBuilder(env)
env['LINK'] = '$CC'
env['LINKFLAGS'] = SCons.Util.CLVar('')
env['LINKCOM'] = '$LINK -q $LINKFLAGS -e$TARGET $SOURCES $LIBS'
env['LIBDIRPREFIX']=''
env['LIBDIRSUFFIX']=''
env['LIBLINKPREFIX']=''
env['LIBLINKSUFFIX']='$LIBSUFFIX'
def exists(env):
# Uses bcc32 to do linking as it generally knows where the standard
# LIBS are and set up the linking correctly
return SCons.Tool.bcc32.findIt('bcc32', env)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,510 @@
# 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.
""" Tool-specific initialization for the install tool.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
import os
import stat
from shutil import copy2, copystat
import SCons.Action
import SCons.Tool
import SCons.Util
from SCons.Subst import SUBST_RAW
from SCons.Tool.linkCommon import (
StringizeLibSymlinks,
CreateLibSymlinks,
EmitLibSymlinks,
)
# We keep track of *all* installed files.
_INSTALLED_FILES = []
_UNIQUE_INSTALLED_FILES = None
class CopytreeError(OSError):
pass
def scons_copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
ignore_dangling_symlinks=False, dirs_exist_ok=False):
"""Recursively copy a directory tree, SCons version.
This is a modified copy of the Python 3.7 shutil.copytree function.
SCons update: dirs_exist_ok dictates whether to raise an
exception in case dst or any missing parent directory already
exists. Implementation depends on os.makedirs having a similar
flag, which it has since Python 3.2. This version also raises an
SCons-defined exception rather than the one defined locally to shtuil.
This version uses a change from Python 3.8.
TODO: we can remove this forked copy once the minimum Py version is 3.8.
If exception(s) occur, an Error is raised with a list of reasons.
If the optional symlinks flag is true, symbolic links in the
source tree result in symbolic links in the destination tree; if
it is false, the contents of the files pointed to by symbolic
links are copied. If the file pointed by the symlink doesn't
exist, an exception will be added in the list of errors raised in
an Error exception at the end of the copy process.
You can set the optional ignore_dangling_symlinks flag to true if you
want to silence this exception. Notice that this has no effect on
platforms that don't support os.symlink.
The optional ignore argument is a callable. If given, it
is called with the `src` parameter, which is the directory
being visited by copytree(), and `names` which is the list of
`src` contents, as returned by os.listdir():
callable(src, names) -> ignored_names
Since copytree() is called recursively, the callable will be
called once for each directory that is copied. It returns a
list of names relative to the `src` directory that should
not be copied.
The optional copy_function argument is a callable that will be used
to copy each file. It will be called with the source path and the
destination path as arguments. By default, copy2() is used, but any
function that supports the same signature (like copy()) can be used.
"""
names = os.listdir(src)
if ignore is not None:
ignored_names = ignore(src, names)
else:
ignored_names = set()
os.makedirs(dst, exist_ok=dirs_exist_ok)
errors = []
for name in names:
if name in ignored_names:
continue
srcname = os.path.join(src, name)
dstname = os.path.join(dst, name)
try:
if os.path.islink(srcname):
linkto = os.readlink(srcname)
if symlinks:
# We can't just leave it to `copy_function` because legacy
# code with a custom `copy_function` may rely on copytree
# doing the right thing.
os.symlink(linkto, dstname)
copystat(srcname, dstname, follow_symlinks=not symlinks)
else:
# ignore dangling symlink if the flag is on
if not os.path.exists(linkto) and ignore_dangling_symlinks:
continue
# otherwise let the copy occurs. copy2 will raise an error
if os.path.isdir(srcname):
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=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)
# catch the Error from the recursive copytree so that we can
# continue with other files
except CopytreeError as err: # SCons change
errors.extend(err.args[0])
except OSError as why:
errors.append((srcname, dstname, str(why)))
try:
copystat(src, dst)
except OSError as why:
# Copying file access times may fail on Windows
if getattr(why, 'winerror', None) is None:
errors.append((src, dst, str(why)))
if errors:
raise CopytreeError(errors) # SCons change
return dst
#
# Functions doing the actual work of the Install Builder.
#
def copyFunc(dest, source, env):
"""Install a source file or directory into a destination by copying.
Mode/permissions bits will be copied as well, except that the target
will be made writable.
Returns:
POSIX-style error code - 0 for success, non-zero for fail
"""
if os.path.isdir(source):
if os.path.exists(dest):
if not os.path.isdir(dest):
raise SCons.Errors.UserError("cannot overwrite non-directory `%s' with a directory `%s'" % (str(dest), str(source)))
else:
parent = os.path.split(dest)[0]
if not os.path.exists(parent):
os.makedirs(parent)
scons_copytree(source, dest, dirs_exist_ok=True)
else:
copy2(source, dest)
st = os.stat(source)
os.chmod(dest, stat.S_IMODE(st.st_mode) | stat.S_IWRITE)
return 0
#
# Functions doing the actual work of the InstallVersionedLib Builder.
#
def copyFuncVersionedLib(dest, source, env):
"""Install a versioned library into a destination by copying.
Any required symbolic links for other library names are created.
Mode/permissions bits will be copied as well, except that the target
will be made writable.
Returns:
POSIX-style error code - 0 for success, non-zero for fail
"""
if os.path.isdir(source):
raise SCons.Errors.UserError("cannot install directory `%s' as a version library" % str(source) )
else:
# remove the link if it is already there
try:
os.remove(dest)
except:
pass
copy2(source, dest)
st = os.stat(source)
os.chmod(dest, stat.S_IMODE(st.st_mode) | stat.S_IWRITE)
installShlibLinks(dest, source, env)
return 0
def listShlibLinksToInstall(dest, source, env):
install_links = []
source = env.arg2nodes(source)
dest = env.fs.File(dest)
install_dir = dest.get_dir()
for src in source:
symlinks = getattr(getattr(src, 'attributes', None), 'shliblinks', None)
if symlinks:
for link, linktgt in symlinks:
link_base = os.path.basename(link.get_path())
linktgt_base = os.path.basename(linktgt.get_path())
install_link = env.fs.File(link_base, install_dir)
install_linktgt = env.fs.File(linktgt_base, install_dir)
install_links.append((install_link, install_linktgt))
return install_links
def installShlibLinks(dest, source, env):
"""If we are installing a versioned shared library create the required links."""
Verbose = False
symlinks = listShlibLinksToInstall(dest, source, env)
if Verbose:
print(f'installShlibLinks: symlinks={StringizeLibSymlinks(symlinks)!r}')
if symlinks:
CreateLibSymlinks(env, symlinks)
return
def installFunc(target, source, env):
"""Install a source file into a target.
Uses the function specified in the INSTALL construction variable.
Returns:
POSIX-style error code - 0 for success, non-zero for fail
"""
try:
install = env['INSTALL']
except KeyError:
raise SCons.Errors.UserError('Missing INSTALL construction variable.')
assert len(target) == len(source), (
"Installing source %s into target %s: "
"target and source lists must have same length."
% (list(map(str, source)), list(map(str, target)))
)
for t, s in zip(target, source):
if install(t.get_path(), s.get_path(), env):
return 1
return 0
def installFuncVersionedLib(target, source, env):
"""Install a versioned library into a target.
Uses the function specified in the INSTALL construction variable.
Returns:
POSIX-style error code - 0 for success, non-zero for fail
"""
try:
install = env['INSTALLVERSIONEDLIB']
except KeyError:
raise SCons.Errors.UserError(
'Missing INSTALLVERSIONEDLIB construction variable.'
)
assert len(target) == len(source), (
"Installing source %s into target %s: "
"target and source lists must have same length."
% (list(map(str, source)), list(map(str, target)))
)
for t, s in zip(target, source):
if hasattr(t.attributes, 'shlibname'):
tpath = os.path.join(t.get_dir(), t.attributes.shlibname)
else:
tpath = t.get_path()
if install(tpath, s.get_path(), env):
return 1
return 0
def stringFunc(target, source, env):
installstr = env.get('INSTALLSTR')
if installstr:
return env.subst_target_source(installstr, SUBST_RAW, target, source)
target = str(target[0])
source = str(source[0])
if os.path.isdir(source):
type = 'directory'
else:
type = 'file'
return 'Install %s: "%s" as "%s"' % (type, source, target)
#
# Emitter functions
#
def add_targets_to_INSTALLED_FILES(target, source, env):
""" An emitter that adds all target files to the list stored in the
_INSTALLED_FILES global variable. This way all installed files of one
scons call will be collected.
"""
global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
_INSTALLED_FILES.extend(target)
_UNIQUE_INSTALLED_FILES = None
return (target, source)
def add_versioned_targets_to_INSTALLED_FILES(target, source, env):
""" An emitter that adds all target files to the list stored in the
_INSTALLED_FILES global variable. This way all installed files of one
scons call will be collected.
"""
global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
Verbose = False
_INSTALLED_FILES.extend(target)
if Verbose:
print(f"add_versioned_targets_to_INSTALLED_FILES: target={list(map(str, target))!r}")
symlinks = listShlibLinksToInstall(target[0], source, env)
if symlinks:
EmitLibSymlinks(env, symlinks, target[0])
_UNIQUE_INSTALLED_FILES = None
return (target, source)
class DESTDIR_factory:
""" A node factory, where all files will be relative to the dir supplied
in the constructor.
"""
def __init__(self, env, dir):
self.env = env
self.dir = env.arg2nodes( dir, env.fs.Dir )[0]
def Entry(self, name):
name = SCons.Util.make_path_relative(name)
return self.dir.Entry(name)
def Dir(self, name):
name = SCons.Util.make_path_relative(name)
return self.dir.Dir(name)
#
# The Builder Definition
#
install_action = SCons.Action.Action(installFunc, stringFunc)
installas_action = SCons.Action.Action(installFunc, stringFunc)
installVerLib_action = SCons.Action.Action(installFuncVersionedLib, stringFunc)
BaseInstallBuilder = None
def InstallBuilderWrapper(env, target=None, source=None, dir=None, **kw):
if target and dir:
import SCons.Errors
raise SCons.Errors.UserError("Both target and dir defined for Install(), only one may be defined.")
if not dir:
dir=target
import SCons.Script
install_sandbox = SCons.Script.GetOption('install_sandbox')
if install_sandbox:
target_factory = DESTDIR_factory(env, install_sandbox)
else:
target_factory = env.fs
try:
dnodes = env.arg2nodes(dir, target_factory.Dir)
except TypeError:
raise SCons.Errors.UserError("Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?" % str(dir))
sources = env.arg2nodes(source, env.fs.Entry)
tgt = []
for dnode in dnodes:
for src in sources:
# Prepend './' so the lookup doesn't interpret an initial
# '#' on the file name portion as meaning the Node should
# be relative to the top-level SConstruct directory.
target = env.fs.Entry('.'+os.sep+src.name, dnode)
tgt.extend(BaseInstallBuilder(env, target, src, **kw))
return tgt
def InstallAsBuilderWrapper(env, target=None, source=None, **kw):
result = []
for src, tgt in map(lambda x, y: (x, y), source, target):
result.extend(BaseInstallBuilder(env, tgt, src, **kw))
return result
BaseVersionedInstallBuilder = None
def InstallVersionedBuilderWrapper(env, target=None, source=None, dir=None, **kw):
if target and dir:
import SCons.Errors
raise SCons.Errors.UserError("Both target and dir defined for Install(), only one may be defined.")
if not dir:
dir=target
import SCons.Script
install_sandbox = SCons.Script.GetOption('install_sandbox')
if install_sandbox:
target_factory = DESTDIR_factory(env, install_sandbox)
else:
target_factory = env.fs
try:
dnodes = env.arg2nodes(dir, target_factory.Dir)
except TypeError:
raise SCons.Errors.UserError("Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?" % str(dir))
sources = env.arg2nodes(source, env.fs.Entry)
tgt = []
for dnode in dnodes:
for src in sources:
# Prepend './' so the lookup doesn't interpret an initial
# '#' on the file name portion as meaning the Node should
# be relative to the top-level SConstruct directory.
target = env.fs.Entry('.'+os.sep+src.name, dnode)
tgt.extend(BaseVersionedInstallBuilder(env, target, src, **kw))
return tgt
added = None
def generate(env):
from SCons.Script import AddOption, GetOption
global added
if not added:
added = 1
AddOption('--install-sandbox',
dest='install_sandbox',
type="string",
action="store",
help='A directory under which all installed files will be placed.')
global BaseInstallBuilder
if BaseInstallBuilder is None:
install_sandbox = GetOption('install_sandbox')
if install_sandbox:
target_factory = DESTDIR_factory(env, install_sandbox)
else:
target_factory = env.fs
BaseInstallBuilder = SCons.Builder.Builder(
action = install_action,
target_factory = target_factory.Entry,
source_factory = env.fs.Entry,
multi = True,
emitter = [ add_targets_to_INSTALLED_FILES, ],
source_scanner = SCons.Scanner.ScannerBase({}, name='Install', recursive=False),
name = 'InstallBuilder')
global BaseVersionedInstallBuilder
if BaseVersionedInstallBuilder is None:
install_sandbox = GetOption('install_sandbox')
if install_sandbox:
target_factory = DESTDIR_factory(env, install_sandbox)
else:
target_factory = env.fs
BaseVersionedInstallBuilder = SCons.Builder.Builder(
action = installVerLib_action,
target_factory = target_factory.Entry,
source_factory = env.fs.Entry,
multi = True,
emitter = [ add_versioned_targets_to_INSTALLED_FILES, ],
name = 'InstallVersionedBuilder')
env['BUILDERS']['_InternalInstall'] = InstallBuilderWrapper
env['BUILDERS']['_InternalInstallAs'] = InstallAsBuilderWrapper
env['BUILDERS']['_InternalInstallVersionedLib'] = InstallVersionedBuilderWrapper
# We'd like to initialize this doing something like the following,
# but there isn't yet support for a ${SOURCE.type} expansion that
# will print "file" or "directory" depending on what's being
# installed. For now we punt by not initializing it, and letting
# the stringFunc() that we put in the action fall back to the
# hand-crafted default string if it's not set.
#
#try:
# env['INSTALLSTR']
#except KeyError:
# env['INSTALLSTR'] = 'Install ${SOURCE.type}: "$SOURCES" as "$TARGETS"'
try:
env['INSTALL']
except KeyError:
env['INSTALL'] = copyFunc
try:
env['INSTALLVERSIONEDLIB']
except KeyError:
env['INSTALLVERSIONEDLIB'] = copyFuncVersionedLib
def exists(env):
return True
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,617 @@
# 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.
"""Tool-specific initialization for the Intel C/C++ compiler.
Supports Linux and Windows compilers, v7 and up.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
import glob
import math
import os.path
import re
import sys
is_windows = sys.platform == 'win32'
is_win64 = is_windows and (os.environ['PROCESSOR_ARCHITECTURE'] == 'AMD64' or
('PROCESSOR_ARCHITEW6432' in os.environ and
os.environ['PROCESSOR_ARCHITEW6432'] == 'AMD64'))
is_linux = sys.platform.startswith('linux')
is_mac = sys.platform == 'darwin'
if is_windows:
import SCons.Tool.msvc
elif is_linux:
import SCons.Tool.gcc
elif is_mac:
import SCons.Tool.gcc
import SCons.Util
import SCons.Warnings
# Exceptions for this tool
class IntelCError(SCons.Errors.InternalError):
pass
class MissingRegistryError(IntelCError): # missing registry entry
pass
class MissingDirError(IntelCError): # dir not found
pass
class NoRegistryModuleError(IntelCError): # can't read registry at all
pass
def linux_ver_normalize(vstr):
"""Normalize a Linux compiler version number.
Intel changed from "80" to "9.0" in 2005, so we assume if the number
is greater than 60 it's an old-style number and otherwise new-style.
Always returns an old-style float like 80 or 90 for compatibility with Windows.
Shades of Y2K!"""
# Check for version number like 9.1.026: return 91.026
# XXX needs to be updated for 2011+ versions (like 2011.11.344 which is compiler v12.1.5)
m = re.match(r'([0-9]+)\.([0-9]+)\.([0-9]+)', vstr)
if m:
vmaj,vmin,build = m.groups()
return float(vmaj) * 10. + float(vmin) + float(build) / 1000.
else:
f = float(vstr)
if is_windows:
return f
else:
if f < 60: return f * 10.0
else: return f
def check_abi(abi):
"""Check for valid ABI (application binary interface) name,
and map into canonical one"""
if not abi:
return None
abi = abi.lower()
# valid_abis maps input name to canonical name
if is_windows:
valid_abis = {'ia32' : 'ia32',
'x86' : 'ia32',
'ia64' : 'ia64',
'em64t' : 'em64t',
'amd64' : 'em64t'}
if is_linux:
valid_abis = {'ia32' : 'ia32',
'x86' : 'ia32',
'x86_64' : 'x86_64',
'em64t' : 'x86_64',
'amd64' : 'x86_64'}
if is_mac:
valid_abis = {'ia32' : 'ia32',
'x86' : 'ia32',
'x86_64' : 'x86_64',
'em64t' : 'x86_64'}
try:
abi = valid_abis[abi]
except KeyError:
raise SCons.Errors.UserError("Intel compiler: Invalid ABI %s, valid values are %s"% \
(abi, list(valid_abis.keys())))
return abi
def get_version_from_list(v, vlist):
"""See if we can match v (string) in vlist (list of strings)
Linux has to match in a fuzzy way."""
if is_windows:
# Simple case, just find it in the list
if v in vlist: return v
else: return None
else:
# Fuzzy match: normalize version number first, but still return
# original non-normalized form.
fuzz = 0.001
for vi in vlist:
if math.fabs(linux_ver_normalize(vi) - linux_ver_normalize(v)) < fuzz:
return vi
# Not found
return None
def get_intel_registry_value(valuename, version=None, abi=None):
"""
Return a value from the Intel compiler registry tree. (Windows only)
"""
# Open the key:
if is_win64:
K = 'Software\\Wow6432Node\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
else:
K = 'Software\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
try:
k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)
except SCons.Util.RegError:
# For version 13 and later, check UUID subkeys for valuename
if is_win64:
K = 'Software\\Wow6432Node\\Intel\\Suites\\' + version + "\\Defaults\\C++\\" + abi.upper()
else:
K = 'Software\\Intel\\Suites\\' + version + "\\Defaults\\C++\\" + abi.upper()
try:
k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)
uuid = SCons.Util.RegQueryValueEx(k, 'SubKey')[0]
if is_win64:
K = 'Software\\Wow6432Node\\Intel\\Suites\\' + version + "\\" + uuid + "\\C++"
else:
K = 'Software\\Intel\\Suites\\' + version + "\\" + uuid + "\\C++"
k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)
try:
v = SCons.Util.RegQueryValueEx(k, valuename)[0]
return v # or v.encode('iso-8859-1', 'replace') to remove unicode?
except SCons.Util.RegError:
if abi.upper() == 'EM64T':
abi = 'em64t_native'
if is_win64:
K = 'Software\\Wow6432Node\\Intel\\Suites\\' + version + "\\" + uuid + "\\C++\\" + abi.upper()
else:
K = 'Software\\Intel\\Suites\\' + version + "\\" + uuid + "\\C++\\" + abi.upper()
k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)
try:
v = SCons.Util.RegQueryValueEx(k, valuename)[0]
return v # or v.encode('iso-8859-1', 'replace') to remove unicode?
except SCons.Util.RegError:
raise MissingRegistryError("%s was not found in the registry, for Intel compiler version %s, abi='%s'"%(K, version,abi))
except (SCons.Util.RegError, OSError):
raise MissingRegistryError("%s was not found in the registry, for Intel compiler version %s, abi='%s'"%(K, version,abi))
# Get the value:
try:
v = SCons.Util.RegQueryValueEx(k, valuename)[0]
return v # or v.encode('iso-8859-1', 'replace') to remove unicode?
except SCons.Util.RegError:
raise MissingRegistryError("%s\\%s was not found in the registry."%(K, valuename))
def get_all_compiler_versions():
"""Returns a sorted list of strings, like "70" or "80" or "9.0"
with most recent compiler version first.
"""
versions=[]
if is_windows:
if is_win64:
keyname = 'Software\\WoW6432Node\\Intel\\Compilers\\C++'
else:
keyname = 'Software\\Intel\\Compilers\\C++'
try:
k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
keyname)
except OSError:
# For version 13 or later, check for default instance UUID
if is_win64:
keyname = 'Software\\WoW6432Node\\Intel\\Suites'
else:
keyname = 'Software\\Intel\\Suites'
try:
k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
keyname)
except OSError:
return []
i = 0
versions = []
try:
while i < 100:
subkey = SCons.Util.RegEnumKey(k, i) # raises SConsEnvironmentError
# Check that this refers to an existing dir.
# This is not 100% perfect but should catch common
# installation issues like when the compiler was installed
# and then the install directory deleted or moved (rather
# than uninstalling properly), so the registry values
# are still there.
if subkey == 'Defaults': # Ignore default instances
i = i + 1
continue
ok = False
for try_abi in ('IA32', 'IA32e', 'IA64', 'EM64T'):
try:
d = get_intel_registry_value('ProductDir', subkey, try_abi)
except MissingRegistryError:
continue # not found in reg, keep going
if os.path.exists(d): ok = True
if ok:
versions.append(subkey)
else:
try:
# Registry points to nonexistent dir. Ignore this
# version.
value = get_intel_registry_value('ProductDir', subkey, 'IA32')
except MissingRegistryError as e:
# Registry key is left dangling (potentially
# after uninstalling).
print("scons: *** Ignoring the registry key for the Intel compiler version %s.\n" \
"scons: *** It seems that the compiler was uninstalled and that the registry\n" \
"scons: *** was not cleaned up properly.\n" % subkey)
else:
print("scons: *** Ignoring "+str(value))
i = i + 1
except OSError:
# no more subkeys
pass
elif is_linux or is_mac:
for d in glob.glob('/opt/intel_cc_*'):
# Typical dir here is /opt/intel_cc_80.
m = re.search(r'cc_(.*)$', d)
if m:
versions.append(m.group(1))
for d in glob.glob('/opt/intel/cc*/*'):
# Typical dir here is /opt/intel/cc/9.0 for IA32,
# /opt/intel/cce/9.0 for EMT64 (AMD64)
m = re.search(r'([0-9][0-9.]*)$', d)
if m:
versions.append(m.group(1))
for d in glob.glob('/opt/intel/Compiler/*'):
# Typical dir here is /opt/intel/Compiler/11.1
m = re.search(r'([0-9][0-9.]*)$', d)
if m:
versions.append(m.group(1))
for d in glob.glob('/opt/intel/composerxe-*'):
# Typical dir here is /opt/intel/composerxe-2011.4.184
m = re.search(r'([0-9][0-9.]*)$', d)
if m:
versions.append(m.group(1))
for d in glob.glob('/opt/intel/composer_xe_*'):
# Typical dir here is /opt/intel/composer_xe_2011_sp1.11.344
# The _sp1 is useless, the installers are named 2011.9.x, 2011.10.x, 2011.11.x
m = re.search(r'([0-9]{0,4})(?:_sp\d*)?\.([0-9][0-9.]*)$', d)
if m:
versions.append("%s.%s"%(m.group(1), m.group(2)))
for d in glob.glob('/opt/intel/compilers_and_libraries_*'):
# JPA: For the new version of Intel compiler 2016.1.
m = re.search(r'([0-9]{0,4})(?:_sp\d*)?\.([0-9][0-9.]*)$', d)
if m:
versions.append("%s.%s"%(m.group(1), m.group(2)))
def keyfunc(str):
"""Given a dot-separated version string, return a tuple of ints representing it."""
return [int(x) for x in str.split('.')]
# split into ints, sort, then remove dups
return sorted(SCons.Util.unique(versions), key=keyfunc, reverse=True)
def get_intel_compiler_top(version, abi):
"""
Return the main path to the top-level dir of the Intel compiler,
using the given version.
The compiler will be in <top>/bin/icl.exe (icc on linux),
the include dir is <top>/include, etc.
"""
if is_windows:
if not SCons.Util.can_read_reg:
raise NoRegistryModuleError("No Windows registry module was found")
top = get_intel_registry_value('ProductDir', version, abi)
archdir={'x86_64': 'intel64',
'amd64' : 'intel64',
'em64t' : 'intel64',
'x86' : 'ia32',
'i386' : 'ia32',
'ia32' : 'ia32'
}[abi] # for v11 and greater
# pre-11, icl was in Bin. 11 and later, it's in Bin/<abi> apparently.
if not os.path.exists(os.path.join(top, "Bin", "icl.exe")) \
and not os.path.exists(os.path.join(top, "Bin", abi, "icl.exe")) \
and not os.path.exists(os.path.join(top, "Bin", archdir, "icl.exe")):
raise MissingDirError("Can't find Intel compiler in %s" % top)
elif is_mac or is_linux:
def find_in_2008style_dir(version):
# first dir is new (>=9.0) style, second is old (8.0) style.
dirs=('/opt/intel/cc/%s', '/opt/intel_cc_%s')
if abi == 'x86_64':
dirs=('/opt/intel/cce/%s',) # 'e' stands for 'em64t', aka x86_64 aka amd64
top=None
for d in dirs:
if os.path.exists(os.path.join(d%version, "bin", "icc")):
top = d%version
break
return top
def find_in_2010style_dir(version):
dirs=('/opt/intel/Compiler/%s/*'%version)
# typically /opt/intel/Compiler/11.1/064 (then bin/intel64/icc)
dirs=glob.glob(dirs)
# find highest sub-version number by reverse sorting and picking first existing one.
dirs.sort()
dirs.reverse()
top=None
for d in dirs:
if (os.path.exists(os.path.join(d, "bin", "ia32", "icc")) or
os.path.exists(os.path.join(d, "bin", "intel64", "icc"))):
top = d
break
return top
def find_in_2011style_dir(version):
# The 2011 (compiler v12) dirs are inconsistent, so just redo the search from
# get_all_compiler_versions and look for a match (search the newest form first)
top=None
for d in glob.glob('/opt/intel/composer_xe_*'):
# Typical dir here is /opt/intel/composer_xe_2011_sp1.11.344
# The _sp1 is useless, the installers are named 2011.9.x, 2011.10.x, 2011.11.x
m = re.search(r'([0-9]{0,4})(?:_sp\d*)?\.([0-9][0-9.]*)$', d)
if m:
cur_ver = "%s.%s"%(m.group(1), m.group(2))
if cur_ver == version and \
(os.path.exists(os.path.join(d, "bin", "ia32", "icc")) or
os.path.exists(os.path.join(d, "bin", "intel64", "icc"))):
top = d
break
if not top:
for d in glob.glob('/opt/intel/composerxe-*'):
# Typical dir here is /opt/intel/composerxe-2011.4.184
m = re.search(r'([0-9][0-9.]*)$', d)
if m and m.group(1) == version and \
(os.path.exists(os.path.join(d, "bin", "ia32", "icc")) or
os.path.exists(os.path.join(d, "bin", "intel64", "icc"))):
top = d
break
return top
def find_in_2016style_dir(version):
# The 2016 (compiler v16) dirs are inconsistent from previous.
top = None
for d in glob.glob('/opt/intel/compilers_and_libraries_%s/linux'%version):
if os.path.exists(os.path.join(d, "bin", "ia32", "icc")) or os.path.exists(os.path.join(d, "bin", "intel64", "icc")):
top = d
break
return top
top = find_in_2016style_dir(version) or find_in_2011style_dir(version) or find_in_2010style_dir(version) or find_in_2008style_dir(version)
# print "INTELC: top=",top
if not top:
raise MissingDirError("Can't find version %s Intel compiler in %s (abi='%s')"%(version,top, abi))
return top
def generate(env, version=None, abi=None, topdir=None, verbose=0):
r"""Add Builders and construction variables for Intel C/C++ compiler
to an Environment.
Args:
version (str): compiler version to use, like "80"
abi (str): 'win32' or whatever Itanium version wants
topdir (str): directory containing compiler tree, e.g.
"c:\\Program Files\\Intel\\Compiler70".
If `topdir` is used, `version` and `abi` are ignored.
verbose: if >0, prints compiler version used.
"""
if not (is_mac or is_linux or is_windows):
# can't handle this platform
return
if is_windows:
SCons.Tool.msvc.generate(env)
elif is_linux:
SCons.Tool.gcc.generate(env)
elif is_mac:
SCons.Tool.gcc.generate(env)
# if version is unspecified, use latest
vlist = get_all_compiler_versions()
if not version:
if vlist:
version = vlist[0]
else:
# User may have specified '90' but we need to get actual dirname '9.0'.
# get_version_from_list does that mapping.
v = get_version_from_list(version, vlist)
if not v:
raise SCons.Errors.UserError("Invalid Intel compiler version %s: "%version + \
"installed versions are %s"%(', '.join(vlist)))
version = v
# if abi is unspecified, use ia32
# alternatives are ia64 for Itanium, or amd64 or em64t or x86_64 (all synonyms here)
abi = check_abi(abi)
if abi is None:
if is_mac or is_linux:
# Check if we are on 64-bit linux, default to 64 then.
uname_m = os.uname()[4]
if uname_m == 'x86_64':
abi = 'x86_64'
else:
abi = 'ia32'
else:
if is_win64:
abi = 'em64t'
else:
abi = 'ia32'
if version and not topdir:
try:
topdir = get_intel_compiler_top(version, abi)
except (SCons.Util.RegError, IntelCError):
topdir = None
if not topdir:
# Normally this is an error, but it might not be if the compiler is
# on $PATH and the user is importing their env.
class ICLTopDirWarning(SCons.Warnings.SConsWarning):
pass
if (
((is_mac or is_linux) and not env.Detect('icc'))
or (is_windows and not env.Detect('icl'))
):
SCons.Warnings.enableWarningClass(ICLTopDirWarning)
SCons.Warnings.warn(
ICLTopDirWarning,
"Failed to find Intel compiler for version='%s', abi='%s'"
% (str(version), str(abi)),
)
else:
# should be cleaned up to say what this other version is
# since in this case we have some other Intel compiler installed
SCons.Warnings.enableWarningClass(ICLTopDirWarning)
SCons.Warnings.warn(
ICLTopDirWarning,
"Can't find Intel compiler top dir for version='%s', abi='%s'"
% (str(version), str(abi)),
)
if topdir:
archdir={'x86_64': 'intel64',
'amd64' : 'intel64',
'em64t' : 'intel64',
'x86' : 'ia32',
'i386' : 'ia32',
'ia32' : 'ia32'
}[abi] # for v11 and greater
if os.path.exists(os.path.join(topdir, 'bin', archdir)):
bindir="bin/%s"%archdir
libdir="lib/%s"%archdir
else:
bindir="bin"
libdir="lib"
if verbose:
print("Intel C compiler: using version %s (%g), abi %s, in '%s/%s'"%\
(repr(version), linux_ver_normalize(version),abi,topdir,bindir))
if is_linux:
# Show the actual compiler version by running the compiler.
os.system('%s/%s/icc --version'%(topdir,bindir))
if is_mac:
# Show the actual compiler version by running the compiler.
os.system('%s/%s/icc --version'%(topdir,bindir))
env['INTEL_C_COMPILER_TOP'] = topdir
if is_linux:
paths={'INCLUDE' : 'include',
'LIB' : libdir,
'PATH' : bindir,
'LD_LIBRARY_PATH' : libdir}
for p, v in paths.items():
env.PrependENVPath(p, os.path.join(topdir, v))
if is_mac:
paths={'INCLUDE' : 'include',
'LIB' : libdir,
'PATH' : bindir,
'LD_LIBRARY_PATH' : libdir}
for p, v in paths.items():
env.PrependENVPath(p, os.path.join(topdir, v))
if is_windows:
# env key reg valname default subdir of top
paths=(('INCLUDE', 'IncludeDir', 'Include'),
('LIB' , 'LibDir', 'Lib'),
('PATH' , 'BinDir', 'Bin'))
# We are supposed to ignore version if topdir is set, so set
# it to the emptry string if it's not already set.
if version is None:
version = ''
# Each path has a registry entry, use that or default to subdir
for p in paths:
try:
path=get_intel_registry_value(p[1], version, abi)
# These paths may have $(ICInstallDir)
# which needs to be substituted with the topdir.
path=path.replace('$(ICInstallDir)', topdir + os.sep)
except IntelCError:
# Couldn't get it from registry: use default subdir of topdir
env.PrependENVPath(p[0], os.path.join(topdir, p[2]))
else:
env.PrependENVPath(p[0], path.split(os.pathsep))
# print "ICL %s: %s, final=%s"%(p[0], path, str(env['ENV'][p[0]]))
if is_windows:
env['CC'] = 'icl'
env['CXX'] = 'icl'
env['LINK'] = 'xilink'
else:
env['CC'] = 'icc'
env['CXX'] = 'icpc'
# Don't reset LINK here;
# use smart_link which should already be here from link.py.
#env['LINK'] = '$CC'
env['AR'] = 'xiar'
env['LD'] = 'xild' # not used by default
# This is not the exact (detailed) compiler version,
# just the major version as determined above or specified
# by the user. It is a float like 80 or 90, in normalized form for Linux
# (i.e. even for Linux 9.0 compiler, still returns 90 rather than 9.0)
if version:
env['INTEL_C_COMPILER_VERSION']=linux_ver_normalize(version)
if is_windows:
# Look for license file dir
# in system environment, registry, and default location.
envlicdir = os.environ.get("INTEL_LICENSE_FILE", '')
K = r'SOFTWARE\Intel\Licenses'
try:
k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)
reglicdir = SCons.Util.RegQueryValueEx(k, "w_cpp")[0]
except (AttributeError, SCons.Util.RegError):
reglicdir = ""
defaultlicdir = r'C:\Program Files\Common Files\Intel\Licenses'
licdir = None
for ld in [envlicdir, reglicdir]:
# If the string contains an '@', then assume it's a network
# license (port@system) and good by definition.
if ld and ('@' in ld or os.path.exists(ld)):
licdir = ld
break
if not licdir:
licdir = defaultlicdir
if not os.path.exists(licdir):
class ICLLicenseDirWarning(SCons.Warnings.SConsWarning):
pass
SCons.Warnings.enableWarningClass(ICLLicenseDirWarning)
SCons.Warnings.warn(
ICLLicenseDirWarning,
"Intel license dir was not found. "
"Tried using the INTEL_LICENSE_FILE environment variable "
"(%s), the registry (%s) and the default path (%s). "
"Using the default path as a last resort."
% (envlicdir, reglicdir, defaultlicdir)
)
env['ENV']['INTEL_LICENSE_FILE'] = licdir
def exists(env):
if not (is_mac or is_linux or is_windows):
# can't handle this platform
return 0
try:
versions = get_all_compiler_versions()
except (SCons.Util.RegError, IntelCError):
versions = None
detected = versions is not None and len(versions) > 0
if not detected:
# try env.Detect, maybe that will work
if is_windows:
return env.Detect('icl')
elif is_linux:
return env.Detect('icc')
elif is_mac:
return env.Detect('icc')
return detected
# end of file
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,72 @@
#
# 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.
"""
Tool-specific initialization for the generic POSIX linker.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
import SCons.Tool
import SCons.Util
import SCons.Warnings
from SCons.Tool import createProgBuilder
from SCons.Tool.linkCommon import smart_link
from SCons.Tool.linkCommon.LoadableModule import setup_loadable_module_logic
from SCons.Tool.linkCommon.SharedLibrary import setup_shared_lib_logic
def generate(env):
"""Add Builders and construction variables for gnulink to an Environment."""
createProgBuilder(env)
setup_shared_lib_logic(env)
setup_loadable_module_logic(env)
env['SMARTLINK'] = smart_link
env['LINK'] = "$SMARTLINK"
env['LINKFLAGS'] = SCons.Util.CLVar('')
# __RPATH is only set to something ($_RPATH typically) on platforms that support it.
env['LINKCOM'] = '$LINK -o $TARGET $LINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
env['LIBDIRPREFIX'] = '-L'
env['LIBDIRSUFFIX'] = ''
env['_LIBFLAGS'] = '${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__, LIBLITERALPREFIX)}'
env['LIBLINKPREFIX'] = '-l'
env['LIBLINKSUFFIX'] = ''
def exists(env):
# This module isn't really a Tool on its own, it's common logic for
# other linkers.
return None
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,131 @@
# 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 SCons.Tool import createLoadableModuleBuilder
from .SharedLibrary import shlib_symlink_emitter
from . import lib_emitter
def ldmod_symlink_emitter(target, source, env, **kw):
return shlib_symlink_emitter(target, source, env, variable_prefix='LDMODULE')
def _get_ldmodule_stem(target, source, env, for_signature):
"""
Get the basename for a library (so for libxyz.so, return xyz)
:param target:
:param source:
:param env:
:param for_signature:
:return:
"""
target_name = str(target)
ldmodule_prefix = env.subst('$LDMODULEPREFIX')
ldmodule_suffix = env.subst("$_LDMODULESUFFIX")
if target_name.startswith(ldmodule_prefix):
target_name = target_name[len(ldmodule_prefix):]
if target_name.endswith(ldmodule_suffix):
target_name = target_name[:-len(ldmodule_suffix)]
return target_name
def _ldmodule_soversion(target, source, env, for_signature):
"""Function to determine what to use for SOVERSION"""
if 'SOVERSION' in env:
return '.$SOVERSION'
elif 'LDMODULEVERSION' in env:
ldmod_version = env.subst('$LDMODULEVERSION')
# We use only the most significant digit of LDMODULEVERSION
return '.' + ldmod_version.split('.')[0]
else:
return ''
def _ldmodule_soname(target, source, env, for_signature):
if 'SONAME' in env:
return '$SONAME'
else:
return "$LDMODULEPREFIX$_get_ldmodule_stem${LDMODULESUFFIX}$_LDMODULESOVERSION"
def _LDMODULEVERSION(target, source, env, for_signature):
"""
Return "." + version if it's set, otherwise just a blank
"""
value = env.subst('$LDMODULEVERSION', target=target, source=source)
# print("_has_LDMODULEVERSION:%s"%value)
if value:
return "."+value
else:
return ""
def setup_loadable_module_logic(env):
"""
Just the logic for loadable modules
For most platforms, a loadable module is the same as a shared
library. Platforms which are different can override these, but
setting them the same means that LoadableModule works everywhere.
:param env:
:return:
"""
createLoadableModuleBuilder(env)
env['_get_ldmodule_stem'] = _get_ldmodule_stem
env['_LDMODULESOVERSION'] = _ldmodule_soversion
env['_LDMODULESONAME'] = _ldmodule_soname
env['LDMODULENAME'] = '${LDMODULEPREFIX}$_get_ldmodule_stem${_LDMODULESUFFIX}'
# This is the non versioned LDMODULE filename
# If LDMODULEVERSION is defined then this will symlink to $LDMODULENAME
env['LDMODULE_NOVERSION_SYMLINK'] = '$_get_shlib_dir${LDMODULEPREFIX}$_get_ldmodule_stem${LDMODULESUFFIX}'
# This is the sonamed file name
# If LDMODULEVERSION is defined then this will symlink to $LDMODULENAME
env['LDMODULE_SONAME_SYMLINK'] = '$_get_shlib_dir$_LDMODULESONAME'
env['_LDMODULEVERSION'] = _LDMODULEVERSION
env['_LDMODULEVERSIONFLAGS'] = '$LDMODULEVERSIONFLAGS -Wl,-soname=$_LDMODULESONAME'
env['LDMODULEEMITTER'] = [lib_emitter, ldmod_symlink_emitter]
env['LDMODULEPREFIX'] = '$SHLIBPREFIX'
env['_LDMODULESUFFIX'] = '${LDMODULESUFFIX}${_LDMODULEVERSION}'
env['LDMODULESUFFIX'] = '$SHLIBSUFFIX'
env['LDMODULE'] = '$SHLINK'
env['LDMODULEFLAGS'] = '$SHLINKFLAGS'
env['LDMODULECOM'] = '$LDMODULE -o $TARGET $LDMODULEFLAGS $__LDMODULEVERSIONFLAGS $__RPATH $SOURCES ' \
'$_LIBDIRFLAGS $_LIBFLAGS '
env['LDMODULEVERSION'] = '$SHLIBVERSION'
env['LDMODULENOVERSIONSYMLINKS'] = '$SHLIBNOVERSIONSYMLINKS'

Some files were not shown because too many files have changed in this diff Show More