Compare commits

...

9 commits

Author SHA1 Message Date
Artem Pavlenko
9f31153229 Fix TypeError in SConstruct (OSX) 2020-11-20 14:45:54 +00:00
Artem Pavlenko
03527f2d8c scons - use 'auto' (exprerimental) 2020-11-20 14:18:29 +00:00
Artem Pavlenko
d3f81be01b Merge branch 'master' into scons-4.0.1 2020-11-20 12:34:17 +00:00
Artem Pavlenko
e260186226 travis - bump xcode to 12.2 2020-11-20 09:28:16 +00:00
Artem Pavlenko
ad4ad235a7 Upgrade to postgis-2.4 2020-11-19 11:32:58 +00:00
Artem Pavlenko
aa228eb4f4 travis - upgrade to xcode11 2020-11-19 10:57:14 +00:00
Artem Pavlenko
c5330bac32 use default travis distro (16.04 LTS) + python3 2020-11-19 10:44:59 +00:00
Artem Pavlenko
d1a1bf27e9 Actually remove scons 3.0.1 2020-11-19 10:14:48 +00:00
Artem Pavlenko
e74be337b0 Upgrade to Scons 4.0.1
- remove SourceCode (depricated in >= 4.x)
 - remove 'FAST' option as SetCacheMode('force') broken in >= 3.0.5
2020-11-19 10:04:04 +00:00
1513 changed files with 414113 additions and 5691 deletions

View file

@ -9,13 +9,14 @@ env:
- CCACHE_TEMPDIR=/tmp/.ccache-temp
- CCACHE_COMPRESS=1
- PREFIX=/tmp/mapnik
- PYTHON=python3
- secure: "F6ivqDNMBQQnrDGA9+7IX+GDswuIqQQd7YPJdQqa2Ked9jddAQDeJClb05ig3JlwfOlYLGZOd43ZX0pKuMtI2Gbkwz211agGP9S3YunwlRg8iWtJlO5kYFUdKCmJNhjg4icfkGELCgwXn+zuEWFSLpkPcjqAFKFlQrIJeAJJgKM="
cache:
directories:
- $HOME/.ccache
dist: trusty
dist: xenial
sudo: false
matrix:
@ -29,7 +30,7 @@ matrix:
postgresql: "9.5"
apt:
sources: [ 'ubuntu-toolchain-r-test']
packages: [ 'libstdc++-6-dev', 'g++-6', 'xutils-dev', 'postgresql-9.5-postgis-2.3' ]
packages: [ 'libstdc++-6-dev', 'g++-6', 'xutils-dev', 'postgresql-9.5-postgis-2.4' ]
- os: linux
name: Linux clang-3.9
env: >-
@ -42,7 +43,7 @@ matrix:
postgresql: "9.5"
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'clang-3.9', 'libstdc++-4.9-dev', 'xutils-dev', 'postgresql-9.5-postgis-2.3' ]
packages: [ 'clang-3.9', 'libstdc++-4.9-dev', 'xutils-dev', 'postgresql-9.5-postgis-2.4' ]
- os: linux
name: Linux clang-3.9 + coverage
env: >-
@ -57,11 +58,10 @@ matrix:
postgresql: "9.5"
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'clang-3.9', 'llvm-3.9', 'libstdc++-4.9-dev', 'xutils-dev', 'postgresql-9.5-postgis-2.3' ]
packages: [ 'clang-3.9', 'llvm-3.9', 'libstdc++-4.9-dev', 'xutils-dev', 'postgresql-9.5-postgis-2.4' ]
- os: osx
name: OSX clang
# https://docs.travis-ci.com/user/languages/objective-c/#Supported-OS-X-iOS-SDK-versions
osx_image: xcode7.3 # upgrades clang from 6 -> 7
osx_image: xcode12.2
env: >-
CXX="ccache clang++ -Qunused-arguments"

View file

@ -11,7 +11,7 @@ endif
all: mapnik
install:
$(PYTHON) scons/scons.py -j$(JOBS) --config=cache --implicit-cache --max-drift=1 install
$(PYTHON) scons/scons.py -j$(JOBS) --config=auto --implicit-cache --max-drift=1 install
release:
./scripts/publish_release.sh
@ -22,7 +22,7 @@ test-release:
src/json/libmapnik-json.a:
# we first build memory intensive files with -j$(HEAVY_JOBS)
$(PYTHON) scons/scons.py -j$(HEAVY_JOBS) \
--config=cache --implicit-cache --max-drift=1 \
--config=auto --implicit-cache --max-drift=1 \
src/renderer_common/render_group_symbolizer.os \
src/renderer_common/render_markers_symbolizer.os \
src/renderer_common/render_thunk_extractor.os \
@ -45,10 +45,10 @@ src/json/libmapnik-json.a:
mapnik: src/json/libmapnik-json.a
# then install the rest with -j$(JOBS)
$(PYTHON) scons/scons.py -j$(JOBS) --config=cache --implicit-cache --max-drift=1
$(PYTHON) scons/scons.py -j$(JOBS) --config=auto --implicit-cache --max-drift=1
clean:
@$(PYTHON) scons/scons.py -j$(JOBS) -c --config=cache --implicit-cache --max-drift=1
@$(PYTHON) scons/scons.py -j$(JOBS) -c --config=auto --implicit-cache --max-drift=1
@if test -e ".sconsign.dblite"; then rm ".sconsign.dblite"; fi
@if test -e "config.log"; then rm "config.log"; fi
@if test -e "config.cache"; then rm "config.cache"; fi
@ -71,7 +71,7 @@ rebuild:
make uninstall && make clean && time make && make install
uninstall:
@$(PYTHON) scons/scons.py -j$(JOBS) --config=cache --implicit-cache --max-drift=1 uninstall
@$(PYTHON) scons/scons.py -j$(JOBS) --config=auto --implicit-cache --max-drift=1 uninstall
test/data-visual:
./scripts/ensure_test_data.sh

View file

@ -25,7 +25,6 @@ import platform
from glob import glob
from copy import copy
from subprocess import Popen, PIPE
from SCons.SConf import SetCacheMode
import pickle
try:
@ -142,7 +141,6 @@ PLUGINS = { # plugins with external dependencies
def init_environment(env):
env.Decider('MD5-timestamp')
env.SourceCode(".", None)
env['ORIGIN'] = Literal('$ORIGIN')
env['ENV']['ORIGIN'] = '$ORIGIN'
if os.environ.get('RANLIB'):
@ -380,7 +378,6 @@ opts.AddVariables(
BoolVariable('ENABLE_GLIBC_WORKAROUND', "Workaround known GLIBC symbol exports to allow building against libstdc++-4.8 without binaries needing throw_out_of_range_fmt", 'False'),
# http://www.scons.org/wiki/GoFastButton
# http://stackoverflow.com/questions/1318863/how-to-optimize-an-scons-script
BoolVariable('FAST', "Make SCons faster at the cost of less precise dependency tracking", 'False'),
BoolVariable('PRIORITIZE_LINKING', 'Sort list of lib and inc directories to ensure preferential compiling and linking (useful when duplicate libs)', 'True'),
('LINK_PRIORITY','Priority list in which to sort library and include paths (default order is internal, other, frameworks, user, then system - see source of `sort_paths` function for more detail)',','.join(DEFAULT_LINK_PRIORITY)),
@ -694,7 +691,7 @@ def parse_config(context, config, checks='--libs --cflags'):
# and thus breaks knowledge below that gdal worked
# TODO - upgrade our scons logic to support Framework linking
if env['PLATFORM'] == 'Darwin':
if value and b'-framework GDAL' in value:
if value and '-framework GDAL' in value:
env['LIBS'].append('gdal')
if os.path.exists('/Library/Frameworks/GDAL.framework/unix/lib'):
env['LIBPATH'].insert(0,'/Library/Frameworks/GDAL.framework/unix/lib')
@ -1291,12 +1288,7 @@ def GetMapnikLibVersion():
return version_string
if not preconfigured:
color_print(4,'Configuring build environment...')
if not env['FAST']:
SetCacheMode('force')
if env['USE_CONFIG']:
if not env['CONFIG'].endswith('.py'):
color_print(1,'SCons CONFIG file specified is not a python file, will not be read...')
@ -2172,13 +2164,6 @@ if not HELP_REQUESTED:
Export('plugin_base')
if env['FAST']:
# caching is 'auto' by default in SCons
# But let's also cache implicit deps...
EnsureSConsVersion(0,98)
SetOption('implicit_cache', 1)
SetOption('max_drift', 1)
# Build agg first, doesn't need anything special
if env['RUNTIME_LINK'] == 'shared':
SConscript('deps/agg/build.py')

View file

@ -3,7 +3,9 @@
This copyright and license do not apply to any other software
with which this software may have been included.
Copyright (c) 2001 - 2017 The SCons Foundation
MIT License
Copyright (c) 2001 - 2020 The SCons Foundation
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View file

@ -1,4 +1,4 @@
# Copyright (c) 2001 - 2017 The SCons Foundation
# Copyright (c) 2001 - 2020 The SCons Foundation
SCons - a software construction tool
@ -38,19 +38,20 @@ LATEST VERSION
Before going further, you can check for the latest version of the
scons-local package, or any SCons package, at the SCons download page:
http://www.scons.org/download.html
https://scons.org/pages/download.html
EXECUTION REQUIREMENTS
======================
Running SCons requires Python version 2.4 or later. There should be
no other dependencies or requirements to run SCons.
Running SCons requires Python 3.5 or higher.
There should be no other dependencies or requirements to run SCons.
The default SCons configuration assumes use of the Microsoft Visual C++
compiler suite on WIN32 systems, and assumes a C compiler named 'cc',
a C++ compiler named 'c++', and a Fortran compiler named 'g77' (such
as found in the GNU C compiler suite) on any other type of system.
compiler suite on WIN32 systems (either through the Visual Studio
product, or through the separate Build Tools), and assumes a C compiler
named 'cc', a C++ compiler named 'c++', and a Fortran compiler named 'g77'
(such as found in the GNU Compiler Collection) on any other type of system.
You may, of course, override these default values by appropriate
configuration of Environment construction variables.
@ -157,14 +158,23 @@ available at:
REPORTING BUGS
==============
You can report bugs either by following the "Tracker - Bugs" link
on the SCons project page:
The SCons project welcomes bug reports and feature requests.
http://sourceforge.net/projects/scons/
Please make sure you send email with the problem or feature request to
the SCons users mailing list, which you can join via the link below:
or by sending mail to the SCons developers mailing list:
http://two.pairlist.net/mailman/listinfo/scons-users
Once you have discussed your issue on the users mailing list and the
community has confirmed that it is either a new bug or a duplicate of an
existing bug, then please follow the instructions the community provides
to file a new bug or to add yourself to the CC list for an existing bug
You can explore the list of existing bugs, which may include workarounds
for the problem you've run into, on GitHub:
https://github.com/SCons/scons/issues
scons-devel@lists.sourceforge.net
MAILING LISTS
@ -173,11 +183,29 @@ MAILING LISTS
A mailing list for users of SCons is available. You may send questions
or comments to the list at:
scons-users@lists.sourceforge.net
scons-users@scons.org
You may subscribe to the scons-users mailing list at:
http://lists.sourceforge.net/lists/listinfo/scons-users
http://two.pairlist.net/mailman/listinfo/scons-users
An active mailing list for developers of SCons is available. You may
send questions or comments to the list at:
scons-dev@scons.org
You may subscribe to the developer's mailing list using form on this page:
http://two.pairlist.net/mailman/listinfo/scons-dev
Subscription to the developer's mailing list is by approval. In practice, no
one is refused list membership, but we reserve the right to limit membership
in the future and/or weed out lurkers.
There are other mailing lists available for SCons users, for notification of
SCons code changes, and for notification of updated bug reports and project
documents. Please see our mailing lists page for details.
FOR MORE INFORMATION
@ -188,18 +216,29 @@ Check the SCons web site at:
http://www.scons.org/
AUTHOR INFO
Author Info
===========
Steven Knight
knight at baldmt dot com
http://www.baldmt.com/~knight/
With plenty of help from the SCons Development team:
Chad Austin
Charles Crain
Steve Leblanc
Anthony Roach
Terrel Shumway
SCons was originally written by Steven Knight, knight at baldmt dot com.
Since around 2010 it has been maintained by the SCons
development team, co-managed by Bill Deegan and Gary Oberbrunner, with
many contributors, including but not at all limited to:
- Chad Austin
- Dirk Baechle
- Charles Crain
- William Deegan
- Steve Leblanc
- Rob Managan
- Greg Noel
- Gary Oberbrunner
- Anthony Roach
- Greg Spencer
- Tom Tanner
- Anatoly Techtonik
- Christoph Wiedemann
- Mats Wichmann
- Russel Winder
- Mats Wichmann
\... and many others.

View file

@ -1,50 +0,0 @@
#
# Copyright (c) 2001 - 2017 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.
#
__revision__ = "src/engine/SCons/Options/BoolOption.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__doc__ = """Place-holder for the old SCons.Options module hierarchy
This is for backwards compatibility. The new equivalent is the Variables/
class hierarchy. These will have deprecation warnings added (some day),
and will then be removed entirely (some day).
"""
import SCons.Variables
import SCons.Warnings
warned = False
def BoolOption(*args, **kw):
global warned
if not warned:
msg = "The BoolOption() function is deprecated; use the BoolVariable() function instead."
SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg)
warned = True
return SCons.Variables.BoolVariable(*args, **kw)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View file

@ -1,50 +0,0 @@
#
# Copyright (c) 2001 - 2017 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.
#
__revision__ = "src/engine/SCons/Options/PackageOption.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__doc__ = """Place-holder for the old SCons.Options module hierarchy
This is for backwards compatibility. The new equivalent is the Variables/
class hierarchy. These will have deprecation warnings added (some day),
and will then be removed entirely (some day).
"""
import SCons.Variables
import SCons.Warnings
warned = False
def PackageOption(*args, **kw):
global warned
if not warned:
msg = "The PackageOption() function is deprecated; use the PackageVariable() function instead."
SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg)
warned = True
return SCons.Variables.PackageVariable(*args, **kw)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View file

@ -1,76 +0,0 @@
#
# Copyright (c) 2001 - 2017 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.
#
__revision__ = "src/engine/SCons/Options/PathOption.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__doc__ = """Place-holder for the old SCons.Options module hierarchy
This is for backwards compatibility. The new equivalent is the Variables/
class hierarchy. These will have deprecation warnings added (some day),
and will then be removed entirely (some day).
"""
import SCons.Variables
import SCons.Warnings
warned = False
class _PathOptionClass(object):
def warn(self):
global warned
if not warned:
msg = "The PathOption() function is deprecated; use the PathVariable() function instead."
SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg)
warned = True
def __call__(self, *args, **kw):
self.warn()
return SCons.Variables.PathVariable(*args, **kw)
def PathAccept(self, *args, **kw):
self.warn()
return SCons.Variables.PathVariable.PathAccept(*args, **kw)
def PathIsDir(self, *args, **kw):
self.warn()
return SCons.Variables.PathVariable.PathIsDir(*args, **kw)
def PathIsDirCreate(self, *args, **kw):
self.warn()
return SCons.Variables.PathVariable.PathIsDirCreate(*args, **kw)
def PathIsFile(self, *args, **kw):
self.warn()
return SCons.Variables.PathVariable.PathIsFile(*args, **kw)
def PathExists(self, *args, **kw):
self.warn()
return SCons.Variables.PathVariable.PathExists(*args, **kw)
PathOption = _PathOptionClass()
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View file

@ -1,67 +0,0 @@
#
# Copyright (c) 2001 - 2017 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.
#
__revision__ = "src/engine/SCons/Options/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__doc__ = """Place-holder for the old SCons.Options module hierarchy
This is for backwards compatibility. The new equivalent is the Variables/
class hierarchy. These will have deprecation warnings added (some day),
and will then be removed entirely (some day).
"""
import SCons.Variables
import SCons.Warnings
from .BoolOption import BoolOption # okay
from .EnumOption import EnumOption # okay
from .ListOption import ListOption # naja
from .PackageOption import PackageOption # naja
from .PathOption import PathOption # okay
warned = False
class Options(SCons.Variables.Variables):
def __init__(self, *args, **kw):
global warned
if not warned:
msg = "The Options class is deprecated; use the Variables class instead."
SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg)
warned = True
SCons.Variables.Variables.__init__(self, *args, **kw)
def AddOptions(self, *args, **kw):
return SCons.Variables.Variables.AddVariables(self, *args, **kw)
def UnknownOptions(self, *args, **kw):
return SCons.Variables.Variables.UnknownVariables(self, *args, **kw)
def FormatOptionHelpText(self, *args, **kw):
return SCons.Variables.Variables.FormatVariableHelpText(self, *args,
**kw)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View file

@ -1,573 +0,0 @@
#
# Copyright (c) 2001 - 2017 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.
#
# TODO:
# * supported arch for versions: for old versions of batch file without
# argument, giving bogus argument cannot be detected, so we have to hardcode
# this here
# * print warning when msvc version specified but not found
# * find out why warning do not print
# * test on 64 bits XP + VS 2005 (and VS 6 if possible)
# * SDK
# * Assembly
__revision__ = "src/engine/SCons/Tool/MSCommon/vc.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__doc__ = """Module for Visual C/C++ detection and configuration.
"""
import SCons.compat
import SCons.Util
import subprocess
import os
import platform
from string import digits as string_digits
import SCons.Warnings
from . import common
debug = common.debug
from . import sdk
get_installed_sdks = sdk.get_installed_sdks
class VisualCException(Exception):
pass
class UnsupportedVersion(VisualCException):
pass
class UnsupportedArch(VisualCException):
pass
class MissingConfiguration(VisualCException):
pass
class NoVersionFound(VisualCException):
pass
class BatchFileExecutionError(VisualCException):
pass
# Dict to 'canonalize' the arch
_ARCH_TO_CANONICAL = {
"amd64" : "amd64",
"emt64" : "amd64",
"i386" : "x86",
"i486" : "x86",
"i586" : "x86",
"i686" : "x86",
"ia64" : "ia64",
"itanium" : "ia64",
"x86" : "x86",
"x86_64" : "amd64",
"x86_amd64" : "x86_amd64", # Cross compile to 64 bit from 32bits
}
# Given a (host, target) tuple, return the argument for the bat file. Both host
# and targets should be canonalized.
_HOST_TARGET_ARCH_TO_BAT_ARCH = {
("x86", "x86"): "x86",
("x86", "amd64"): "x86_amd64",
("x86", "x86_amd64"): "x86_amd64",
("amd64", "x86_amd64"): "x86_amd64", # This is present in (at least) VS2012 express
("amd64", "amd64"): "amd64",
("amd64", "x86"): "x86",
("x86", "ia64"): "x86_ia64"
}
def get_host_target(env):
debug('vc.py:get_host_target()')
host_platform = env.get('HOST_ARCH')
if not host_platform:
host_platform = platform.machine()
# TODO(2.5): the native Python platform.machine() function returns
# '' on all Python versions before 2.6, after which it also uses
# PROCESSOR_ARCHITECTURE.
if not host_platform:
host_platform = os.environ.get('PROCESSOR_ARCHITECTURE', '')
# Retain user requested TARGET_ARCH
req_target_platform = env.get('TARGET_ARCH')
debug('vc.py:get_host_target() req_target_platform:%s'%req_target_platform)
if req_target_platform:
# If user requested a specific platform then only try that one.
target_platform = req_target_platform
else:
target_platform = host_platform
try:
host = _ARCH_TO_CANONICAL[host_platform.lower()]
except KeyError as e:
msg = "Unrecognized host architecture %s"
raise ValueError(msg % repr(host_platform))
try:
target = _ARCH_TO_CANONICAL[target_platform.lower()]
except KeyError as e:
all_archs = str(list(_ARCH_TO_CANONICAL.keys()))
raise ValueError("Unrecognized target architecture %s\n\tValid architectures: %s" % (target_platform, all_archs))
return (host, target,req_target_platform)
# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the
# MSVC_VERSION documentation in Tool/msvc.xml.
_VCVER = ["14.1", "14.0", "14.0Exp", "12.0", "12.0Exp", "11.0", "11.0Exp", "10.0", "10.0Exp", "9.0", "9.0Exp","8.0", "8.0Exp","7.1", "7.0", "6.0"]
_VCVER_TO_PRODUCT_DIR = {
'14.1' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'')], # Visual Studio 2017 doesn't set this registry key anymore
'14.0' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')],
'14.0Exp' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir')],
'12.0' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'),
],
'12.0Exp' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'),
],
'11.0': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'),
],
'11.0Exp' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'),
],
'10.0': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'),
],
'10.0Exp' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'),
],
'9.0': [
(SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',),
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir',),
],
'9.0Exp' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'),
],
'8.0': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'),
],
'8.0Exp': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'),
],
'7.1': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'),
],
'7.0': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'),
],
'6.0': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'),
]
}
def msvc_version_to_maj_min(msvc_version):
msvc_version_numeric = ''.join([x for x in msvc_version if x in string_digits + '.'])
t = msvc_version_numeric.split(".")
if not len(t) == 2:
raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric))
try:
maj = int(t[0])
min = int(t[1])
return maj, min
except ValueError as e:
raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric))
def is_host_target_supported(host_target, msvc_version):
"""Return True if the given (host, target) tuple is supported given the
msvc version.
Parameters
----------
host_target: tuple
tuple of (canonalized) host-target, e.g. ("x86", "amd64") for cross
compilation from 32 bits windows to 64 bits.
msvc_version: str
msvc version (major.minor, e.g. 10.0)
Note
----
This only check whether a given version *may* support the given (host,
target), not that the toolchain is actually present on the machine.
"""
# We assume that any Visual Studio version supports x86 as a target
if host_target[1] != "x86":
maj, min = msvc_version_to_maj_min(msvc_version)
if maj < 8:
return False
return True
def find_vc_pdir_vswhere(msvc_version):
"""
Find the MSVC product directory using vswhere.exe .
Run it asking for specified version and get MSVS install location
:param msvc_version:
:return: MSVC install dir
"""
vswhere_path = os.path.join(
'C:\\',
'Program Files (x86)',
'Microsoft Visual Studio',
'Installer',
'vswhere.exe'
)
vswhere_cmd = [vswhere_path, '-version', msvc_version, '-property', 'installationPath']
if os.path.exists(vswhere_path):
sp = subprocess.Popen(vswhere_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
vsdir, err = sp.communicate()
vsdir = vsdir.decode("mbcs")
vsdir = vsdir.rstrip()
vc_pdir = os.path.join(vsdir, 'VC')
return vc_pdir
else:
# No vswhere on system, no install info available
return None
def find_vc_pdir(msvc_version):
"""Try to find the product directory for the given
version.
Note
----
If for some reason the requested version could not be found, an
exception which inherits from VisualCException will be raised."""
root = 'Software\\'
try:
hkeys = _VCVER_TO_PRODUCT_DIR[msvc_version]
except KeyError:
debug("Unknown version of MSVC: %s" % msvc_version)
raise UnsupportedVersion("Unknown version %s" % msvc_version)
for hkroot, key in hkeys:
try:
comps = None
if not key:
comps = find_vc_pdir_vswhere(msvc_version)
if not comps:
debug('find_vc_dir(): no VC found via vswhere for version {}'.format(repr(key)))
raise SCons.Util.WinError
else:
if common.is_win64():
try:
# ordinally at win64, try Wow6432Node first.
comps = common.read_reg(root + 'Wow6432Node\\' + key, hkroot)
except SCons.Util.WinError as e:
# at Microsoft Visual Studio for Python 2.7, value is not in Wow6432Node
pass
if not comps:
# not Win64, or Microsoft Visual Studio for Python 2.7
comps = common.read_reg(root + key, hkroot)
except SCons.Util.WinError as e:
debug('find_vc_dir(): no VC registry key {}'.format(repr(key)))
else:
debug('find_vc_dir(): found VC in registry: {}'.format(comps))
if os.path.exists(comps):
return comps
else:
debug('find_vc_dir(): reg says dir is {}, but it does not exist. (ignoring)'.format(comps))
raise MissingConfiguration("registry dir {} not found on the filesystem".format(comps))
return None
def find_batch_file(env,msvc_version,host_arch,target_arch):
"""
Find the location of the batch script which should set up the compiler
for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress
"""
pdir = find_vc_pdir(msvc_version)
if pdir is None:
raise NoVersionFound("No version of Visual Studio found")
debug('vc.py: find_batch_file() pdir:{}'.format(pdir))
# filter out e.g. "Exp" from the version name
msvc_ver_numeric = ''.join([x for x in msvc_version if x in string_digits + "."])
vernum = float(msvc_ver_numeric)
if 7 <= vernum < 8:
pdir = os.path.join(pdir, os.pardir, "Common7", "Tools")
batfilename = os.path.join(pdir, "vsvars32.bat")
elif vernum < 7:
pdir = os.path.join(pdir, "Bin")
batfilename = os.path.join(pdir, "vcvars32.bat")
elif 8 <= vernum <= 14:
batfilename = os.path.join(pdir, "vcvarsall.bat")
else: # vernum >= 14.1 VS2017 and above
batfilename = os.path.join(pdir, "Auxiliary", "Build", "vcvarsall.bat")
if not os.path.exists(batfilename):
debug("Not found: %s" % batfilename)
batfilename = None
installed_sdks=get_installed_sdks()
for _sdk in installed_sdks:
sdk_bat_file = _sdk.get_sdk_vc_script(host_arch,target_arch)
if not sdk_bat_file:
debug("vc.py:find_batch_file() not found:%s"%_sdk)
else:
sdk_bat_file_path = os.path.join(pdir,sdk_bat_file)
if os.path.exists(sdk_bat_file_path):
debug('vc.py:find_batch_file() sdk_bat_file_path:%s'%sdk_bat_file_path)
return (batfilename,sdk_bat_file_path)
return (batfilename,None)
__INSTALLED_VCS_RUN = None
def cached_get_installed_vcs():
global __INSTALLED_VCS_RUN
if __INSTALLED_VCS_RUN is None:
ret = get_installed_vcs()
__INSTALLED_VCS_RUN = ret
return __INSTALLED_VCS_RUN
def get_installed_vcs():
installed_versions = []
for ver in _VCVER:
debug('trying to find VC %s' % ver)
try:
if find_vc_pdir(ver):
debug('found VC %s' % ver)
installed_versions.append(ver)
else:
debug('find_vc_pdir return None for ver %s' % ver)
except VisualCException as e:
debug('did not find VC %s: caught exception %s' % (ver, str(e)))
return installed_versions
def reset_installed_vcs():
"""Make it try again to find VC. This is just for the tests."""
__INSTALLED_VCS_RUN = None
# Running these batch files isn't cheap: most of the time spent in
# msvs.generate() is due to vcvars*.bat. In a build that uses "tools='msvs'"
# in multiple environments, for example:
# env1 = Environment(tools='msvs')
# env2 = Environment(tools='msvs')
# we can greatly improve the speed of the second and subsequent Environment
# (or Clone) calls by memoizing the environment variables set by vcvars*.bat.
script_env_stdout_cache = {}
def script_env(script, args=None):
cache_key = (script, args)
stdout = script_env_stdout_cache.get(cache_key, None)
if stdout is None:
stdout = common.get_output(script, args)
script_env_stdout_cache[cache_key] = stdout
# Stupid batch files do not set return code: we take a look at the
# beginning of the output for an error message instead
olines = stdout.splitlines()
if olines[0].startswith("The specified configuration type is missing"):
raise BatchFileExecutionError("\n".join(olines[:2]))
return common.parse_output(stdout)
def get_default_version(env):
debug('get_default_version()')
msvc_version = env.get('MSVC_VERSION')
msvs_version = env.get('MSVS_VERSION')
debug('get_default_version(): msvc_version:%s msvs_version:%s'%(msvc_version,msvs_version))
if msvs_version and not msvc_version:
SCons.Warnings.warn(
SCons.Warnings.DeprecatedWarning,
"MSVS_VERSION is deprecated: please use MSVC_VERSION instead ")
return msvs_version
elif msvc_version and msvs_version:
if not msvc_version == msvs_version:
SCons.Warnings.warn(
SCons.Warnings.VisualVersionMismatch,
"Requested msvc version (%s) and msvs version (%s) do " \
"not match: please use MSVC_VERSION only to request a " \
"visual studio version, MSVS_VERSION is deprecated" \
% (msvc_version, msvs_version))
return msvs_version
if not msvc_version:
installed_vcs = cached_get_installed_vcs()
debug('installed_vcs:%s' % installed_vcs)
if not installed_vcs:
#msg = 'No installed VCs'
#debug('msv %s\n' % repr(msg))
#SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg)
debug('msvc_setup_env: No installed VCs')
return None
msvc_version = installed_vcs[0]
debug('msvc_setup_env: using default installed MSVC version %s\n' % repr(msvc_version))
return msvc_version
def msvc_setup_env_once(env):
try:
has_run = env["MSVC_SETUP_RUN"]
except KeyError:
has_run = False
if not has_run:
msvc_setup_env(env)
env["MSVC_SETUP_RUN"] = True
def msvc_find_valid_batch_script(env,version):
debug('vc.py:msvc_find_valid_batch_script()')
# Find the host platform, target platform, and if present the requested
# target platform
(host_platform, target_platform,req_target_platform) = get_host_target(env)
try_target_archs = [target_platform]
debug("msvs_find_valid_batch_script(): req_target_platform %s target_platform:%s"%(req_target_platform,target_platform))
# VS2012 has a "cross compile" environment to build 64 bit
# with x86_amd64 as the argument to the batch setup script
if req_target_platform in ('amd64','x86_64'):
try_target_archs.append('x86_amd64')
elif not req_target_platform and target_platform in ['amd64','x86_64']:
# There may not be "native" amd64, but maybe "cross" x86_amd64 tools
try_target_archs.append('x86_amd64')
# If the user hasn't specifically requested a TARGET_ARCH, and
# The TARGET_ARCH is amd64 then also try 32 bits if there are no viable
# 64 bit tools installed
try_target_archs.append('x86')
debug("msvs_find_valid_batch_script(): host_platform: %s try_target_archs:%s"%(host_platform, try_target_archs))
d = None
for tp in try_target_archs:
# Set to current arch.
env['TARGET_ARCH']=tp
debug("vc.py:msvc_find_valid_batch_script() trying target_platform:%s"%tp)
host_target = (host_platform, tp)
if not is_host_target_supported(host_target, version):
warn_msg = "host, target = %s not supported for MSVC version %s" % \
(host_target, version)
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
arg = _HOST_TARGET_ARCH_TO_BAT_ARCH[host_target]
# Get just version numbers
maj, min = msvc_version_to_maj_min(version)
# VS2015+
if maj >= 14:
if env.get('MSVC_UWP_APP') == '1':
# Initialize environment variables with store/universal paths
arg += ' store'
# Try to locate a batch file for this host/target platform combo
try:
(vc_script,sdk_script) = find_batch_file(env,version,host_platform,tp)
debug('vc.py:msvc_find_valid_batch_script() vc_script:%s sdk_script:%s'%(vc_script,sdk_script))
except VisualCException as e:
msg = str(e)
debug('Caught exception while looking for batch file (%s)' % msg)
warn_msg = "VC version %s not installed. " + \
"C/C++ compilers are most likely not set correctly.\n" + \
" Installed versions are: %s"
warn_msg = warn_msg % (version, cached_get_installed_vcs())
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
continue
# Try to use the located batch file for this host/target platform combo
debug('vc.py:msvc_find_valid_batch_script() use_script 2 %s, args:%s\n' % (repr(vc_script), arg))
if vc_script:
try:
d = script_env(vc_script, args=arg)
except BatchFileExecutionError as e:
debug('vc.py:msvc_find_valid_batch_script() use_script 3: failed running VC script %s: %s: Error:%s'%(repr(vc_script),arg,e))
vc_script=None
continue
if not vc_script and sdk_script:
debug('vc.py:msvc_find_valid_batch_script() use_script 4: trying sdk script: %s'%(sdk_script))
try:
d = script_env(sdk_script)
except BatchFileExecutionError as e:
debug('vc.py:msvc_find_valid_batch_script() use_script 5: failed running SDK script %s: Error:%s'%(repr(sdk_script),e))
continue
elif not vc_script and not sdk_script:
debug('vc.py:msvc_find_valid_batch_script() use_script 6: Neither VC script nor SDK script found')
continue
debug("vc.py:msvc_find_valid_batch_script() Found a working script/target: %s %s"%(repr(sdk_script),arg))
break # We've found a working target_platform, so stop looking
# If we cannot find a viable installed compiler, reset the TARGET_ARCH
# To it's initial value
if not d:
env['TARGET_ARCH']=req_target_platform
return d
def msvc_setup_env(env):
debug('msvc_setup_env()')
version = get_default_version(env)
if version is None:
warn_msg = "No version of Visual Studio compiler found - C/C++ " \
"compilers most likely not set correctly"
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
return None
debug('msvc_setup_env: using specified MSVC version %s\n' % repr(version))
# XXX: we set-up both MSVS version for backward
# compatibility with the msvs tool
env['MSVC_VERSION'] = version
env['MSVS_VERSION'] = version
env['MSVS'] = {}
use_script = env.get('MSVC_USE_SCRIPT', True)
if SCons.Util.is_String(use_script):
debug('vc.py:msvc_setup_env() use_script 1 %s\n' % repr(use_script))
d = script_env(use_script)
elif use_script:
d = msvc_find_valid_batch_script(env,version)
debug('vc.py:msvc_setup_env() use_script 2 %s\n' % d)
if not d:
return d
else:
debug('MSVC_USE_SCRIPT set to False')
warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment " \
"set correctly."
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
return None
for k, v in d.items():
debug('vc.py:msvc_setup_env() env:%s -> %s'%(k,v))
env.PrependENVPath(k, v, delete_existing=True)
def msvc_exists(version=None):
vcs = cached_get_installed_vcs()
if version is None:
return len(vcs) > 0
return version in vcs

View file

@ -1,79 +0,0 @@
"""SCons.Tool.applelink
Tool-specific initialization for the Apple 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.
"""
#
# Copyright (c) 2001 - 2017 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.
#
__revision__ = "src/engine/SCons/Tool/applelink.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import SCons.Util
# 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 . import link
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__)}'
env['_FRAMEWORKS'] = '${_concat("-framework ", FRAMEWORKS, "", __env__)}'
env['LINKCOM'] = env['LINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -dynamiclib')
env['SHLINKCOM'] = env['SHLINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
# TODO: Work needed to generate versioned shared libraries
# Leaving this commented out, and also going to disable versioned library checking for now
# see: http://docstore.mik.ua/orelly/unix3/mac/ch05_04.htm for proper naming
#link._setup_versioned_lib_variables(env, tool = 'applelink')#, use_soname = use_soname)
#env['LINKCALLBACKS'] = link._versioned_lib_callbacks()
# 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'] = SCons.Util.CLVar('$LINKFLAGS -bundle')
env['LDMODULECOM'] = '$LDMODULE -o ${TARGET} $LDMODULEFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
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

@ -1,13 +0,0 @@
Metadata-Version: 1.0
Name: scons
Version: 3.0.1
Summary: Open Source next-generation build tool.
Home-page: http://www.scons.org/
Author: Steven Knight
Author-email: knight@baldmt.com
License: UNKNOWN
Description: Open Source next-generation build tool.
Improved, cross-platform substitute for the classic Make
utility. In short, SCons is an easier, more reliable
and faster way to build software.
Platform: UNKNOWN

View file

@ -77,7 +77,7 @@ way for wrapping up the functions.
"""
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -98,15 +98,17 @@ way for wrapping up the functions.
# 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__ = "src/engine/SCons/Action.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import pickle
import re
import sys
import subprocess
from subprocess import DEVNULL
import itertools
import inspect
from collections import OrderedDict
import SCons.Debug
from SCons.Debug import logInstanceCreation
@ -115,10 +117,9 @@ import SCons.Util
import SCons.Subst
# we use these a lot, so try to optimize them
is_String = SCons.Util.is_String
is_List = SCons.Util.is_List
from SCons.Util import is_String, is_List
class _null(object):
class _null:
pass
print_actions = 1
@ -211,7 +212,7 @@ def _object_contents(obj):
def _code_contents(code, docstring=None):
"""Return the signature contents of a code object.
r"""Return the signature contents of a code object.
By providing direct access to the code object of the
function, Python makes this extremely easy. Hooray!
@ -258,8 +259,7 @@ def _code_contents(code, docstring=None):
# function. Note that we have to call _object_contents on each
# constants because the code object of nested functions can
# show-up among the constants.
z = [_object_contents(cc) for cc in code.co_consts[1:]]
z = [_object_contents(cc) for cc in code.co_consts if cc != docstring]
contents.extend(b',(')
contents.extend(bytearray(',', 'utf-8').join(z))
contents.extend(b')')
@ -515,7 +515,7 @@ def Action(act, *args, **kw):
return _do_create_action(act, kw)
class ActionBase(object):
class ActionBase:
"""Base class for all types of action objects that can be held by
other objects (Builders, Executors, etc.) This provides the
common methods for manipulating and combining those actions."""
@ -535,7 +535,7 @@ class ActionBase(object):
result = self.get_presig(target, source, env)
if not isinstance(result,(bytes, bytearray)):
result = bytearray("",'utf-8').join([ SCons.Util.to_bytes(r) for r in result ])
result = bytearray(result, 'utf-8')
else:
# Make a copy and put in bytearray, without this the contents returned by get_presig
# can be changed by the logic below, appending with each call and causing very
@ -629,16 +629,8 @@ class _ActionAction(ActionBase):
"""
In python 3, and in some of our tests, sys.stdout is
a String io object, and it takes unicode strings only
In other cases it's a regular Python 2.x file object
which takes strings (bytes), and if you pass those a
unicode object they try to decode with 'ascii' codec
which fails if the cmd line has any hi-bit-set chars.
This code assumes s is a regular string, but should
work if it's unicode too.
This code assumes s is a regular string.
"""
try:
sys.stdout.write(s + u"\n")
except UnicodeDecodeError:
sys.stdout.write(s + "\n")
def __call__(self, target, source, env,
@ -657,10 +649,14 @@ class _ActionAction(ActionBase):
presub = self.presub
if presub is _null:
presub = print_actions_presub
if exitstatfunc is _null: exitstatfunc = self.exitstatfunc
if show is _null: show = print_actions
if execute is _null: execute = execute_actions
if chdir is _null: chdir = self.chdir
if exitstatfunc is _null:
exitstatfunc = self.exitstatfunc
if show is _null:
show = print_actions
if execute is _null:
execute = execute_actions
if chdir is _null:
chdir = self.chdir
save_cwd = None
if chdir:
save_cwd = os.getcwd()
@ -678,7 +674,7 @@ class _ActionAction(ActionBase):
source = executor.get_all_sources()
t = ' and '.join(map(str, target))
l = '\n '.join(self.presub_lines(env))
out = u"Building %s with action:\n %s\n" % (t, l)
out = "Building %s with action:\n %s\n" % (t, l)
sys.stdout.write(out)
cmd = None
if show and self.strfunction:
@ -760,24 +756,20 @@ def get_default_ENV(env):
return default_ENV
def _subproc(scons_env, cmd, error = 'ignore', **kw):
"""Do common setup for a subprocess.Popen() call
def _subproc(scons_env, cmd, error='ignore', **kw):
"""Wrapper for subprocess which pulls from construction env.
This function is still in draft mode. We're going to need something like
it in the long run as more and more places use subprocess, but I'm sure
it'll have to be tweaked to get the full desired functionality.
one special arg (so far?), 'error', to tell what to do with exceptions.
Use for calls to subprocess which need to interpolate values from
an SCons construction environment into the environment passed to
subprocess. Adds an an error-handling argument. Adds ability
to specify std{in,out,err} with "'devnull'" tag.
"""
# allow std{in,out,err} to be "'devnull'"
io = kw.get('stdin')
# TODO: just uses subprocess.DEVNULL now, we can drop the "devnull"
# string now - it is a holdover from Py2, which didn't have DEVNULL.
for stream in 'stdin', 'stdout', 'stderr':
io = kw.get(stream)
if is_String(io) and io == 'devnull':
kw['stdin'] = open(os.devnull)
io = kw.get('stdout')
if is_String(io) and io == 'devnull':
kw['stdout'] = open(os.devnull, 'w')
io = kw.get('stderr')
if is_String(io) and io == 'devnull':
kw['stderr'] = open(os.devnull, 'w')
kw[stream] = DEVNULL
# Figure out what shell environment to use
ENV = kw.get('env', None)
@ -803,21 +795,28 @@ def _subproc(scons_env, cmd, error = 'ignore', **kw):
kw['env'] = new_env
try:
return subprocess.Popen(cmd, **kw)
pobj = subprocess.Popen(cmd, **kw)
except EnvironmentError as e:
if error == 'raise': raise
# return a dummy Popen instance that only returns error
class dummyPopen(object):
class dummyPopen:
def __init__(self, e): self.exception = e
def communicate(self, input=None): return ('', '')
def wait(self): return -self.exception.errno
stdin = None
class f(object):
class f:
def read(self): return ''
def readline(self): return ''
def __iter__(self): return iter(())
stdout = stderr = f()
return dummyPopen(e)
pobj = dummyPopen(e)
finally:
# clean up open file handles stored in parent's kw
for k, v in kw.items():
if inspect.ismethod(getattr(v, 'close', None)):
v.close()
return pobj
class CommandAction(_ActionAction):
@ -837,7 +836,7 @@ class CommandAction(_ActionAction):
_ActionAction.__init__(self, **kw)
if is_List(cmd):
if [c for c in cmd if is_List(c)]:
raise TypeError("CommandAction should be given only " \
raise TypeError("CommandAction should be given only "
"a single command")
self.cmd_list = cmd
@ -964,11 +963,33 @@ class CommandAction(_ActionAction):
return env.subst_target_source(cmd, SUBST_SIG, target, source)
def get_implicit_deps(self, target, source, env, executor=None):
"""Return the implicit dependencies of this action's command line."""
icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
if is_String(icd) and icd[:1] == '$':
icd = env.subst(icd)
if not icd or icd in ('0', 'None'):
if not icd or str(icd).lower in ('0', 'none', 'false', 'no', 'off'):
return []
try:
icd_int = int(icd)
except ValueError:
icd_int = None
if (icd_int and icd_int > 1) or icd == 'all':
# An integer value greater than 1 specifies the number of entries
# to scan. "all" means to scan all.
return self._get_implicit_deps_heavyweight(target, source, env, executor, icd_int)
else:
# Everything else (usually 1 or True) means that we want
# lightweight dependency scanning.
return self._get_implicit_deps_lightweight(target, source, env, executor)
def _get_implicit_deps_lightweight(self, target, source, env, executor):
"""
Lightweight dependency scanning involves only scanning the first entry
in an action string, even if it contains &&.
"""
from SCons.Subst import SUBST_SIG
if executor:
cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=executor)
@ -986,6 +1007,65 @@ class CommandAction(_ActionAction):
res.append(env.fs.File(d))
return res
def _get_implicit_deps_heavyweight(self, target, source, env, executor,
icd_int):
"""
Heavyweight dependency scanning involves scanning more than just the
first entry in an action string. The exact behavior depends on the
value of icd_int. Only files are taken as implicit dependencies;
directories are ignored.
If icd_int is an integer value, it specifies the number of entries to
scan for implicit dependencies. Action strings are also scanned after
a &&. So for example, if icd_int=2 and the action string is
"cd <some_dir> && $PYTHON $SCRIPT_PATH <another_path>", the implicit
dependencies would be the path to the python binary and the path to the
script.
If icd_int is None, all entries are scanned for implicit dependencies.
"""
# Avoid circular and duplicate dependencies by not providing source,
# target, or executor to subst_list. This causes references to
# $SOURCES, $TARGETS, and all related variables to disappear.
from SCons.Subst import SUBST_SIG
cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, conv=lambda x: x)
res = []
for cmd_line in cmd_list:
if cmd_line:
entry_count = 0
for entry in cmd_line:
d = str(entry)
if ((icd_int is None or entry_count < icd_int) and
not d.startswith(('&', '-', '/') if os.name == 'nt'
else ('&', '-'))):
m = strip_quotes.match(d)
if m:
d = m.group(1)
if d:
# Resolve the first entry in the command string using
# PATH, which env.WhereIs() looks in.
# For now, only match files, not directories.
p = os.path.abspath(d) if os.path.isfile(d) else None
if not p and entry_count == 0:
p = env.WhereIs(d)
if p:
res.append(env.fs.File(p))
entry_count = entry_count + 1
else:
entry_count = 0 if d == '&&' else entry_count + 1
# Despite not providing source and target to env.subst() above, we
# can still end up with sources in this list. For example, files in
# LIBS will still resolve in env.subst(). This won't result in
# circular dependencies, but it causes problems with cache signatures
# changing between full and incremental builds.
return [r for r in res if r not in target and r not in source]
class CommandGeneratorAction(ActionBase):
"""Class for command-generator actions."""
@ -1034,11 +1114,14 @@ class CommandGeneratorAction(ActionBase):
show=_null, execute=_null, chdir=_null, executor=None):
act = self._generate(target, source, env, 0, executor)
if act is None:
raise SCons.Errors.UserError("While building `%s': "
raise SCons.Errors.UserError(
"While building `%s': "
"Cannot deduce file extension from source files: %s"
% (repr(list(map(str, target))), repr(list(map(str, source)))))
return act(target, source, env, exitstatfunc, presub,
show, execute, chdir, executor)
% (repr(list(map(str, target))), repr(list(map(str, source))))
)
return act(
target, source, env, exitstatfunc, presub, show, execute, chdir, executor
)
def get_presig(self, target, source, env, executor=None):
"""Return the signature contents of this action's command line.
@ -1086,7 +1169,7 @@ class LazyAction(CommandGeneratorAction, CommandAction):
def get_parent_class(self, env):
c = env.get(self.var)
if is_String(c) and not '\n' in c:
if is_String(c) and '\n' not in c:
return CommandAction
return CommandGeneratorAction
@ -1289,14 +1372,14 @@ class ListAction(ActionBase):
return result
def get_varlist(self, target, source, env, executor=None):
result = SCons.Util.OrderedDict()
result = OrderedDict()
for act in self.list:
for var in act.get_varlist(target, source, env, executor):
result[var] = True
return list(result.keys())
class ActionCaller(object):
class ActionCaller:
"""A class for delaying calling an Action function with specific
(positional and keyword) arguments until the Action is actually
executed.
@ -1367,7 +1450,7 @@ class ActionCaller(object):
return self.parent.strfunc(*self.args, **self.kw)
class ActionFactory(object):
class ActionFactory:
"""A factory class that will wrap up an arbitrary function
as an SCons-executable Action object.

View file

@ -77,7 +77,7 @@ There are the following methods for internal use within this module:
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -98,9 +98,9 @@ There are the following methods for internal use within this module:
# 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__ = "src/engine/SCons/Builder.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import collections
from collections import UserDict, UserList
import SCons.Action
import SCons.Debug
@ -111,7 +111,7 @@ import SCons.Memoize
import SCons.Util
import SCons.Warnings
class _Null(object):
class _Null:
pass
_null = _Null
@ -197,7 +197,7 @@ class DictEmitter(SCons.Util.Selector):
target, source = emitter(target, source, env)
return (target, source)
class ListEmitter(collections.UserList):
class ListEmitter(UserList):
"""A callable list of emitters that calls each in sequence,
returning the result.
"""
@ -215,7 +215,7 @@ misleading_keywords = {
'sources' : 'source',
}
class OverrideWarner(collections.UserDict):
class OverrideWarner(UserDict):
"""A class for warning about keyword arguments that we use as
overrides in a Builder call.
@ -224,13 +224,13 @@ class OverrideWarner(collections.UserDict):
warnings once, no matter how many Builders are invoked.
"""
def __init__(self, dict):
collections.UserDict.__init__(self, dict)
UserDict.__init__(self, dict)
if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.OverrideWarner')
self.already_warned = None
def warn(self):
if self.already_warned:
return
for k in list(self.keys()):
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)
@ -274,7 +274,7 @@ def Builder(**kw):
result = BuilderBase(**kw)
if not composite is None:
if composite is not None:
result = CompositeBuilder(result, composite)
return result
@ -293,7 +293,7 @@ def _node_errors(builder, env, tlist, slist):
if t.has_explicit_builder():
# Check for errors when the environments are different
# No error if environments are the same Environment instance
if (not t.env is None and not t.env is env and
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
@ -309,7 +309,7 @@ def _node_errors(builder, env, tlist, slist):
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 as e:
except UnicodeDecodeError:
msg = "Two environments with different actions were specified for the same target: %s"%t
raise UserError(msg)
if builder.multi:
@ -328,7 +328,7 @@ def _node_errors(builder, env, tlist, slist):
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(object):
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
@ -361,7 +361,7 @@ class EmitterProxy(object):
def __lt__(self, other):
return self.var < other.var
class BuilderBase(object):
class BuilderBase:
"""Base class for Builders, objects that create output
nodes (files) from input nodes (files).
"""
@ -396,16 +396,13 @@ class BuilderBase(object):
self.env = env
self.single_source = single_source
if 'overrides' in overrides:
SCons.Warnings.warn(SCons.Warnings.DeprecatedBuilderKeywordsWarning,
"The \"overrides\" keyword to Builder() creation has been deprecated;\n" +\
"\tspecify the items as keyword arguments to the Builder() call instead.")
overrides.update(overrides['overrides'])
del overrides['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:
SCons.Warnings.warn(SCons.Warnings.DeprecatedBuilderKeywordsWarning,
"The \"scanner\" keyword to Builder() creation has been deprecated;\n"
"\tuse: source_scanner or target_scanner as appropriate.")
del overrides['scanner']
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)
@ -424,7 +421,7 @@ class BuilderBase(object):
if name:
self.name = name
self.executor_kw = {}
if not chdir is _null:
if chdir is not _null:
self.executor_kw['chdir'] = chdir
self.is_explicit = is_explicit
@ -554,8 +551,10 @@ class BuilderBase(object):
result = []
if target is None: target = [None]*len(source)
for tgt, src in zip(target, source):
if not tgt is None: tgt = [tgt]
if not src is None: src = [src]
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)
@ -563,6 +562,13 @@ class BuilderBase(object):
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)
@ -641,6 +647,8 @@ class BuilderBase(object):
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)
@ -744,7 +752,7 @@ class BuilderBase(object):
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:
if not match_suffix and '.' not in s:
src_suf = self.get_src_suffix(env)
s = self._adjustixes(s, None, src_suf)[0]
else:

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -21,18 +21,20 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/CacheDir.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """
CacheDir support
"""
import atexit
import json
import os
import stat
import sys
import SCons.Action
import SCons.Errors
import SCons.Warnings
cache_enabled = True
@ -45,16 +47,22 @@ 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:
env.copy_from_cache(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[stat.ST_MODE]) | stat.S_IWRITE)
return 0
@ -106,7 +114,7 @@ def CachePushFunc(target, source, env):
# has beaten us creating the directory.
if not fs.isdir(cachedir):
msg = errfmt % (str(target), cachefile)
raise SCons.Errors.EnvironmentError(msg)
raise SCons.Errors.SConsEnvironmentError(msg)
try:
if fs.islink(t.get_internal_path()):
@ -127,74 +135,67 @@ def CachePushFunc(target, source, env):
CachePush = SCons.Action.Action(CachePushFunc, None)
# Nasty hack to cut down to one warning for each cachedir path that needs
# upgrading.
warned = dict()
class CacheDir(object):
class CacheDir:
def __init__(self, path):
try:
import hashlib
except ImportError:
msg = "No hashlib or MD5 module available, CacheDir() not supported"
SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
path = None
"""
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 = dict()
if path is None:
return
# See if there's a config file in the cache directory. If there is,
# use it. If there isn't, and the directory exists and isn't empty,
# produce a warning. If the directory doesn't exist or is empty,
# write a config file.
config_file = os.path.join(path, 'config')
if not os.path.exists(config_file):
# A note: There is a race hazard here, if two processes start and
# attempt to create the cache directory at the same time. However,
# python doesn't really give you the option to do exclusive file
# creation (it doesn't even give you the option to error on opening
# an existing file for writing...). The ordering of events here
# as an attempt to alleviate this, on the basis that it's a pretty
# unlikely occurence (it'd require two builds with a brand new cache
# directory)
if os.path.isdir(path) and len(os.listdir(path)) != 0:
self.config['prefix_len'] = 1
# When building the project I was testing this on, the warning
# was output over 20 times. That seems excessive
global warned
if self.path not in warned:
msg = "Please upgrade your cache by running " +\
" scons-configure-cache.py " + self.path
SCons.Warnings.warn(SCons.Warnings.CacheVersionWarning, msg)
warned[self.path] = True
else:
if not os.path.isdir(path):
try:
os.makedirs(path)
except OSError:
# If someone else is trying to create the directory at
# the same time as me, bad things will happen
msg = "Failed to create cache directory " + path
raise SCons.Errors.EnvironmentError(msg)
self.config['prefix_len'] = 2
if not os.path.exists(config_file):
self._readconfig(path)
def _readconfig(self, path):
"""
Read the cache config.
If directory or config file do not exist, create. Take advantage
of Py3 capability in os.makedirs() and in file open(): just try
the operation and handle failure appropriately.
Omit the check for old cache format, assume that's old enough
there will be none of those left to worry about.
:param path: path to the cache directory
"""
config_file = os.path.join(path, 'config')
try:
os.makedirs(path, exist_ok=True)
except FileExistsError:
pass
except OSError:
msg = "Failed to create cache directory " + path
raise SCons.Errors.SConsEnvironmentError(msg)
try:
with open(config_file, 'x') as config:
self.config['prefix_len'] = 2
try:
with open(config_file, 'w') as config:
json.dump(self.config, config)
except:
except Exception:
msg = "Failed to write cache configuration for " + path
raise SCons.Errors.EnvironmentError(msg)
else:
raise SCons.Errors.SConsEnvironmentError(msg)
except FileExistsError:
try:
with open(config_file) as config:
self.config = json.load(config)
except ValueError:
msg = "Failed to read cache configuration for " + path
raise SCons.Errors.EnvironmentError(msg)
raise SCons.Errors.SConsEnvironmentError(msg)
def CacheDebug(self, fmt, target, cachefile):
@ -202,15 +203,29 @@ class CacheDir(object):
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))
@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 not self.path is None
return cache_enabled and self.path is not None
def is_readonly(self):
return cache_readonly
@ -222,7 +237,9 @@ class CacheDir(object):
return None, None
sig = node.get_cachedir_bsig()
subdir = sig[:self.config['prefix_len']].upper()
dir = os.path.join(self.path, subdir)
return dir, os.path.join(dir, sig)

View file

@ -136,7 +136,7 @@ def CheckBuilder(context, text = None, language = None):
if not text:
text = """
int main() {
int main(void) {
return 0;
}
"""
@ -157,7 +157,7 @@ def CheckCC(context):
"""
context.Display("Checking whether the C compiler works... ")
text = """
int main()
int main(void)
{
return 0;
}
@ -177,7 +177,7 @@ def CheckSHCC(context):
"""
context.Display("Checking whether the (shared) C compiler works... ")
text = """
int foo()
int foo(void)
{
return 0;
}
@ -197,7 +197,7 @@ def CheckCXX(context):
"""
context.Display("Checking whether the C++ compiler works... ")
text = """
int main()
int main(void)
{
return 0;
}
@ -217,7 +217,7 @@ def CheckSHCXX(context):
"""
context.Display("Checking whether the (shared) C++ compiler works... ")
text = """
int main()
int main(void)
{
return 0;
}
@ -290,7 +290,11 @@ char %s();""" % function_name
#include <assert.h>
%(hdr)s
int main() {
#if _MSC_VER && !__INTEL_COMPILER
#pragma function(%(name)s)
#endif
int main(void) {
#if defined (__stub_%(name)s) || defined (__stub___%(name)s)
fail fail fail
#else
@ -311,8 +315,8 @@ int main() {
return ret
def CheckHeader(context, header_name, header = None, language = None,
include_quotes = None):
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
@ -400,7 +404,7 @@ def CheckType(context, type_name, fallback = None,
%(include)s
%(header)s
int main() {
int main(void) {
if ((%(name)s *) 0)
return 0;
if (sizeof (%(name)s))
@ -455,7 +459,7 @@ def CheckTypeSize(context, type_name, header = None, language = None, expect = N
return msg
src = includetext + header
if not expect is None:
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))
@ -465,7 +469,7 @@ def CheckTypeSize(context, type_name, header = None, language = None, expect = N
src = src + r"""
typedef %s scons_check_type;
int main()
int main(void)
{
static int test_array[1 - 2 * !(((long int) (sizeof(scons_check_type))) == %d)];
test_array[0] = 0;
@ -498,7 +502,7 @@ int main()
src = src + """
#include <stdlib.h>
#include <stdio.h>
int main() {
int main(void) {
printf("%d", (int)sizeof(""" + type_name + """));
return 0;
}
@ -560,7 +564,7 @@ def CheckDeclaration(context, symbol, includes = None, language = None):
context.Display('Checking whether %s is declared... ' % symbol)
src = src + r"""
int main()
int main(void)
{
#ifndef %s
(void) %s;
@ -704,7 +708,7 @@ def CheckProg(context, prog_name):
#
def _YesNoResult(context, ret, key, text, comment = None):
"""
r"""
Handle the result of a test with a "yes" or "no" result.
:Parameters:
@ -723,7 +727,7 @@ def _YesNoResult(context, ret, key, text, comment = None):
def _Have(context, key, have, comment = None):
"""
r"""
Store result of a test in context.havedict and context.headerfilename.
:Parameters:

View file

@ -9,7 +9,7 @@ caller_trace()
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -31,8 +31,9 @@ caller_trace()
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Debug.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import atexit
import os
import sys
import time
@ -201,22 +202,39 @@ if sys.platform == 'win32':
TraceDefault = 'con'
else:
TraceDefault = '/dev/tty'
TimeStampDefault = None
TimeStampDefault = False
StartTime = time.time()
PreviousTime = StartTime
def Trace(msg, file=None, mode='w', tstamp=None):
"""Write a trace message to a file. Whenever a file is specified,
it becomes the default for the next call to Trace()."""
def Trace(msg, filename=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 file argument persists
across calls unless overridden.
Args:
filename: 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 file is None:
file = TraceDefault
else:
TraceDefault = file
if tstamp is None:
if not tstamp:
tstamp = TimeStampDefault
else:
TimeStampDefault = tstamp
@ -225,6 +243,7 @@ def Trace(msg, file=None, mode='w', tstamp=None):
except KeyError:
try:
fp = TraceFP[file] = open(file, mode)
atexit.register(trace_cleanup, fp)
except TypeError:
# Assume we were passed an open file pointer.
fp = file
@ -234,7 +253,6 @@ def Trace(msg, file=None, mode='w', tstamp=None):
PreviousTime = now
fp.write(msg)
fp.flush()
fp.close()
# Local Variables:
# tab-width:4

View file

@ -10,7 +10,7 @@ from distutils.msvccompiler.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -31,9 +31,7 @@ from distutils.msvccompiler.
# 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 __future__ import division
__revision__ = "src/engine/SCons/Defaults.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
@ -193,7 +191,7 @@ def chmod_func(dest, mode):
SCons.Node.FS.invalidate_node_memos(dest)
if not SCons.Util.is_List(dest):
dest = [dest]
if SCons.Util.is_String(mode) and not 0 in [i in digits for i in mode]:
if SCons.Util.is_String(mode) and 0 not in [i in digits for i in mode]:
mode = int(mode, 8)
if not SCons.Util.is_String(mode):
for element in dest:
@ -210,7 +208,7 @@ def chmod_func(dest, mode):
else:
raise SyntaxError("Could not find +, - or =")
operation_list = operation.split(operator)
if len(operation_list) is not 2:
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()
@ -333,7 +331,7 @@ def touch_func(dest):
if os.path.exists(file):
atime = os.path.getatime(file)
else:
open(file, 'w')
with open(file, 'w'):
atime = mtime
os.utime(file, (atime, mtime))
@ -342,6 +340,7 @@ Touch = ActionFactory(touch_func,
# Internal utility functions
def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None):
"""
Creates a new list from 'list' by first interpolating each element
@ -358,6 +357,7 @@ def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None):
return _concat_ixes(prefix, list, suffix, env)
def _concat_ixes(prefix, list, suffix, env):
"""
Creates a new list from 'list' by concatenating the 'prefix' and
@ -395,6 +395,7 @@ def _concat_ixes(prefix, list, suffix, env):
return result
def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
"""
This is a wrapper around _concat()/_concat_ixes() that checks for
@ -497,7 +498,7 @@ def _defines(prefix, defs, suffix, env, c=_concat_ixes):
return c(prefix, env.subst_path(processDefines(defs)), suffix, env)
class NullCmdGenerator(object):
class NullCmdGenerator:
"""This is a callable class that can be used in place of other
command generators if you don't want them to do anything.
@ -516,7 +517,7 @@ class NullCmdGenerator(object):
return self.cmd
class Variable_Method_Caller(object):
class Variable_Method_Caller:
"""A class for finding a construction variable on the stack and
calling one of its methods.
@ -565,7 +566,6 @@ ConstructionEnvironment = {
'DSUFFIXES' : SCons.Tool.DSuffixes,
'ENV' : {},
'IDLSUFFIXES' : SCons.Tool.IDLSuffixes,
# 'LATEXSUFFIXES' : SCons.Tool.LaTeXSuffixes, # moved to the TeX tools generate functions
'_concat' : _concat,
'_defines' : _defines,
'_stripixes' : _stripixes,
@ -580,6 +580,7 @@ ConstructionEnvironment = {
'__DSHLIBVERSIONFLAGS' : '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}',
'TEMPFILE' : NullCmdGenerator,
'TEMPFILEARGJOIN': ' ',
'Dir' : Variable_Method_Caller('TARGET', 'Dir'),
'Dirs' : Variable_Method_Caller('TARGET', 'Dirs'),
'File' : Variable_Method_Caller('TARGET', 'File'),

View file

@ -10,7 +10,7 @@ Environment
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -31,7 +31,7 @@ Environment
# 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__ = "src/engine/SCons/Environment.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import copy
@ -46,7 +46,7 @@ import SCons.Builder
import SCons.Debug
from SCons.Debug import logInstanceCreation
import SCons.Defaults
import SCons.Errors
from SCons.Errors import UserError, BuildError
import SCons.Memoize
import SCons.Node
import SCons.Node.Alias
@ -60,7 +60,7 @@ import SCons.Tool
import SCons.Util
import SCons.Warnings
class _Null(object):
class _Null:
pass
_null = _Null
@ -75,11 +75,6 @@ CalculatorArgs = {}
semi_deepcopy = SCons.Util.semi_deepcopy
semi_deepcopy_dict = SCons.Util.semi_deepcopy_dict
# Pull UserError into the global name space for the benefit of
# Environment().SourceSignatures(), which has some import statements
# which seem to mess up its ability to reference SCons directly.
UserError = SCons.Errors.UserError
def alias_builder(env, target, source):
pass
@ -128,7 +123,7 @@ future_reserved_construction_var_names = [
def copy_non_reserved_keywords(dict):
result = semi_deepcopy(dict)
for k in list(result.keys()):
for k in result.copy().keys():
if k in reserved_construction_var_names:
msg = "Ignoring attempt to set reserved variable `$%s'"
SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % k)
@ -147,14 +142,14 @@ def _set_future_reserved(env, key, value):
def _set_BUILDERS(env, key, value):
try:
bd = env._dict[key]
for k in list(bd.keys()):
for k in bd.copy().keys():
del bd[k]
except KeyError:
bd = BuilderDict(kwbd, env)
bd = BuilderDict(bd, env)
env._dict[key] = bd
for k, v in value.items():
if not SCons.Builder.is_a_Builder(v):
raise SCons.Errors.UserError('%s is not a Builder.' % repr(v))
raise UserError('%s is not a Builder.' % repr(v))
bd.update(value)
def _del_SCANNERS(env, key):
@ -196,7 +191,7 @@ def _delete_duplicates(l, keep_last):
# BuilderWrapper a subclass that overrides __call__() to enforce specific
# Builder calling conventions, simplified some of our higher-layer code.
class MethodWrapper(object):
class MethodWrapper:
"""
A generic Wrapper class that associates a method (which can
actually be any callable) with an object. As part of creating this
@ -339,7 +334,7 @@ def is_valid_construction_var(varstr):
class SubstitutionEnvironment(object):
class SubstitutionEnvironment:
"""Base class for different flavors of construction environments.
This class contains a minimal set of methods that handle construction
@ -431,7 +426,7 @@ class SubstitutionEnvironment(object):
# efficient than calling another function or a method.
if key not in self._dict \
and not _is_valid_var.match(key):
raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
raise UserError("Illegal construction variable `%s'" % key)
self._dict[key] = value
def get(self, key, default=None):
@ -439,13 +434,23 @@ class SubstitutionEnvironment(object):
return self._dict.get(key, default)
def has_key(self, key):
"""Emulates the has_key() method of dictionaries."""
return key in self._dict
def __contains__(self, key):
return self._dict.__contains__(key)
def keys(self):
"""Emulates the keys() method of dictionaries."""
return self._dict.keys()
def values(self):
"""Emulates the values() method of dictionaries."""
return self._dict.values()
def items(self):
return list(self._dict.items())
"""Emulates the items() method of dictionaries."""
return self._dict.items()
def arg2nodes(self, args, node_factory=_null, lookup_list=_null, **kw):
if node_factory is _null:
@ -589,7 +594,7 @@ class SubstitutionEnvironment(object):
out,err = p.communicate()
status = p.wait()
if err:
sys.stderr.write(u"" + err)
sys.stderr.write("" + err)
if status:
raise OSError("'%s' exited %d" % (command, status))
return out
@ -608,7 +613,7 @@ class SubstitutionEnvironment(object):
Removes the specified function's MethodWrapper from the
added_methods list, so we don't re-bind it when making a clone.
"""
self.added_methods = [dm for dm in self.added_methods if not dm.method is function]
self.added_methods = [dm for dm in self.added_methods if dm.method is not function]
def Override(self, overrides):
"""
@ -712,6 +717,9 @@ class SubstitutionEnvironment(object):
elif append_next_arg_to == '-include':
t = ('-include', self.fs.File(arg))
dict['CCFLAGS'].append(t)
elif append_next_arg_to == '-imacros':
t = ('-imacros', self.fs.File(arg))
dict['CCFLAGS'].append(t)
elif append_next_arg_to == '-isysroot':
t = ('-isysroot', arg)
dict['CCFLAGS'].append(t)
@ -719,6 +727,12 @@ class SubstitutionEnvironment(object):
elif append_next_arg_to == '-isystem':
t = ('-isystem', arg)
dict['CCFLAGS'].append(t)
elif append_next_arg_to == '-iquote':
t = ('-iquote', arg)
dict['CCFLAGS'].append(t)
elif append_next_arg_to == '-idirafter':
t = ('-idirafter', arg)
dict['CCFLAGS'].append(t)
elif append_next_arg_to == '-arch':
t = ('-arch', arg)
dict['CCFLAGS'].append(t)
@ -777,13 +791,14 @@ class SubstitutionEnvironment(object):
elif arg in ['-mno-cygwin',
'-pthread',
'-openmp',
'-fmerge-all-constants',
'-fopenmp']:
dict['CCFLAGS'].append(arg)
dict['LINKFLAGS'].append(arg)
elif arg == '-mwindows':
dict['LINKFLAGS'].append(arg)
elif arg[:5] == '-std=':
if arg[5:].find('++')!=-1:
if '++' in arg[5:]:
key='CXXFLAGS'
else:
key='CFLAGS'
@ -791,7 +806,7 @@ class SubstitutionEnvironment(object):
elif arg[0] == '+':
dict['CCFLAGS'].append(arg)
dict['LINKFLAGS'].append(arg)
elif arg in ['-include', '-isysroot', '-isystem', '-arch']:
elif arg in ['-include', '-imacros', '-isysroot', '-isystem', '-iquote', '-idirafter', '-arch']:
append_next_arg_to = arg
else:
dict['CCFLAGS'].append(arg)
@ -850,26 +865,28 @@ class SubstitutionEnvironment(object):
t.append(v)
else:
### keep right-most occurence
orig.reverse()
for v in orig:
for v in orig[::-1]:
if v not in t:
t.insert(0, v)
self[key] = t
return self
def default_decide_source(dependency, target, prev_ni):
def default_decide_source(dependency, target, prev_ni, repo_node=None):
f = SCons.Defaults.DefaultEnvironment().decide_source
return f(dependency, target, prev_ni)
return f(dependency, target, prev_ni, repo_node)
def default_decide_target(dependency, target, prev_ni):
def default_decide_target(dependency, target, prev_ni, repo_node=None):
f = SCons.Defaults.DefaultEnvironment().decide_target
return f(dependency, target, prev_ni)
return f(dependency, target, prev_ni, repo_node)
def default_copy_from_cache(src, dst):
f = SCons.Defaults.DefaultEnvironment().copy_from_cache
return f(src, dst)
class Base(SubstitutionEnvironment):
"""Base class for "real" construction Environments. These are the
primary objects used to communicate dependency and construction
@ -1217,7 +1234,7 @@ class Base(SubstitutionEnvironment):
return path
def AppendENVPath(self, name, newpath, envname = 'ENV',
sep = os.pathsep, delete_existing=1):
sep = os.pathsep, delete_existing=0):
"""Append path elements to the path 'name' in the 'ENV'
dictionary for this environment. Will only add any particular
path once, and will normpath and normcase all paths to help
@ -1342,7 +1359,7 @@ class Base(SubstitutionEnvironment):
dk = list(filter(lambda x, val=val: x not in val, dk))
self._dict[key] = dk + [val]
else:
if not val in dk:
if val not in dk:
self._dict[key] = dk + [val]
else:
if key == 'CPPDEFINES':
@ -1420,38 +1437,30 @@ class Base(SubstitutionEnvironment):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.EnvironmentClone')
return clone
def Copy(self, *args, **kw):
global _warn_copy_deprecated
if _warn_copy_deprecated:
msg = "The env.Copy() method is deprecated; use the env.Clone() method instead."
SCons.Warnings.warn(SCons.Warnings.DeprecatedCopyWarning, msg)
_warn_copy_deprecated = False
return self.Clone(*args, **kw)
def _changed_build(self, dependency, target, prev_ni):
if dependency.changed_state(target, prev_ni):
def _changed_build(self, dependency, target, prev_ni, repo_node=None):
if dependency.changed_state(target, prev_ni, repo_node):
return 1
return self.decide_source(dependency, target, prev_ni)
return self.decide_source(dependency, target, prev_ni, repo_node)
def _changed_content(self, dependency, target, prev_ni):
return dependency.changed_content(target, prev_ni)
def _changed_content(self, dependency, target, prev_ni, repo_node=None):
return dependency.changed_content(target, prev_ni, repo_node)
def _changed_source(self, dependency, target, prev_ni):
def _changed_source(self, dependency, target, prev_ni, repo_node=None):
target_env = dependency.get_build_env()
type = target_env.get_tgt_sig_type()
if type == 'source':
return target_env.decide_source(dependency, target, prev_ni)
return target_env.decide_source(dependency, target, prev_ni, repo_node)
else:
return target_env.decide_target(dependency, target, prev_ni)
return target_env.decide_target(dependency, target, prev_ni, repo_node)
def _changed_timestamp_then_content(self, dependency, target, prev_ni):
return dependency.changed_timestamp_then_content(target, prev_ni)
def _changed_timestamp_then_content(self, dependency, target, prev_ni, repo_node=None):
return dependency.changed_timestamp_then_content(target, prev_ni, repo_node)
def _changed_timestamp_newer(self, dependency, target, prev_ni):
return dependency.changed_timestamp_newer(target, prev_ni)
def _changed_timestamp_newer(self, dependency, target, prev_ni, repo_node=None):
return dependency.changed_timestamp_newer(target, prev_ni, repo_node)
def _changed_timestamp_match(self, dependency, target, prev_ni):
return dependency.changed_timestamp_match(target, prev_ni)
def _changed_timestamp_match(self, dependency, target, prev_ni, repo_node=None):
return dependency.changed_timestamp_match(target, prev_ni, repo_node)
def _copy_from_cache(self, src, dst):
return self.fs.copy(src, dst)
@ -1483,8 +1492,14 @@ class Base(SubstitutionEnvironment):
self.copy_from_cache = copy_function
def Detect(self, progs):
"""Return the first available program in progs.
:param progs: one or more command names to check for
:type progs: str or list
:returns str: first name from progs that can be found.
"""
if not SCons.Util.is_List(progs):
progs = [ progs ]
@ -1493,7 +1508,22 @@ class Base(SubstitutionEnvironment):
if path: return prog
return None
def Dictionary(self, *args):
"""Return construction variables from an environment.
Args:
\*args (optional): variable names to look up
Returns:
If `args` omitted, the dictionary of all construction variables.
If one arg, the corresponding value is returned.
If more than one arg, a list of values is returned.
Raises:
KeyError: if any of `args` is not in the construction environment.
"""
if not args:
return self._dict
dlist = [self._dict[x] for x in args]
@ -1501,31 +1531,56 @@ class Base(SubstitutionEnvironment):
dlist = dlist[0]
return dlist
def Dump(self, key = None):
"""
Using the standard Python pretty printer, return the contents of the
scons build environment as a string.
If the key passed in is anything other than None, then that will
be used as an index into the build environment dictionary and
whatever is found there will be fed into the pretty printer. Note
that this key is case sensitive.
def Dump(self, key=None, format='pretty'):
""" Return construction variables serialized to a string.
Args:
key (optional): if None, format the whole dict of variables.
Else format the value of `key` (Default value = None)
format (optional): specify the format to serialize to.
`"pretty"` generates a pretty-printed string,
`"json"` a JSON-formatted string.
(Default value = None, equivalent to `"pretty"`)
"""
if key:
cvars = self.Dictionary(key)
else:
cvars = self.Dictionary()
fmt = format.lower()
if fmt == 'pretty':
import pprint
pp = pprint.PrettyPrinter(indent=2)
if key:
dict = self.Dictionary(key)
# TODO: pprint doesn't do a nice job on path-style values
# if the paths contain spaces (i.e. Windows), because the
# algorithm tries to break lines on spaces, while breaking
# on the path-separator would be more "natural". Is there
# a better way to format those?
return pp.pformat(cvars)
elif fmt == 'json':
import json
def non_serializable(obj):
return str(type(obj).__qualname__)
return json.dumps(cvars, indent=4, default=non_serializable)
else:
dict = self.Dictionary()
return pp.pformat(dict)
raise ValueError("Unsupported serialization format: %s." % fmt)
def FindIxes(self, paths, prefix, suffix):
"""
Search a list of paths for something that matches the prefix and suffix.
"""Search a list of paths for something that matches the prefix and suffix.
Args:
paths: the list of paths or nodes.
prefix: construction variable for the prefix.
suffix: construction variable for the suffix.
Returns: the matched path or None
paths - the list of paths or nodes.
prefix - construction variable for the prefix.
suffix - construction variable for the suffix.
"""
suffix = self.subst('$'+suffix)
@ -1568,12 +1623,12 @@ class Base(SubstitutionEnvironment):
"""
filename = self.subst(filename)
try:
fp = open(filename, 'r')
with open(filename, 'r') as fp:
lines = SCons.Util.LogicalLines(fp).readlines()
except IOError:
if must_exist:
raise
return
lines = SCons.Util.LogicalLines(fp).readlines()
lines = [l for l in lines if l[0] != '#']
tdlist = []
for line in lines:
@ -1590,7 +1645,7 @@ class Base(SubstitutionEnvironment):
for td in tdlist:
targets.extend(td[0])
if len(targets) > 1:
raise SCons.Errors.UserError(
raise UserError(
"More than one dependency target found in `%s': %s"
% (filename, targets))
for target, depends in tdlist:
@ -1722,7 +1777,7 @@ class Base(SubstitutionEnvironment):
dk = [x for x in dk if x not in val]
self._dict[key] = [val] + dk
else:
if not val in dk:
if val not in dk:
self._dict[key] = [val] + dk
else:
if delete_existing:
@ -1833,7 +1888,7 @@ class Base(SubstitutionEnvironment):
uniq = {}
for executor in [n.get_executor() for n in nodes]:
uniq[executor] = 1
for executor in list(uniq.keys()):
for executor in uniq.keys():
executor.add_pre_action(action)
return nodes
@ -1843,7 +1898,7 @@ class Base(SubstitutionEnvironment):
uniq = {}
for executor in [n.get_executor() for n in nodes]:
uniq[executor] = 1
for executor in list(uniq.keys()):
for executor in uniq.keys():
executor.add_post_action(action)
return nodes
@ -1908,14 +1963,6 @@ class Base(SubstitutionEnvironment):
t.set_always_build()
return tlist
def BuildDir(self, *args, **kw):
msg = """BuildDir() and the build_dir keyword have been deprecated;\n\tuse VariantDir() and the variant_dir keyword instead."""
SCons.Warnings.warn(SCons.Warnings.DeprecatedBuildDirWarning, msg)
if 'build_dir' in kw:
kw['variant_dir'] = kw['build_dir']
del kw['build_dir']
return self.VariantDir(*args, **kw)
def Builder(self, **kw):
nkw = self.subst_kw(kw)
return SCons.Builder.Builder(**nkw)
@ -1954,13 +2001,42 @@ class Base(SubstitutionEnvironment):
be any type that the Builder constructor will accept
for an action."""
bkw = {
'action' : action,
'target_factory' : self.fs.Entry,
'source_factory' : self.fs.Entry,
'action': action,
'target_factory': self.fs.Entry,
'source_factory': self.fs.Entry,
}
try: bkw['source_scanner'] = kw['source_scanner']
except KeyError: pass
else: del kw['source_scanner']
# source scanner
try:
bkw['source_scanner'] = kw['source_scanner']
except KeyError:
pass
else:
del kw['source_scanner']
# target scanner
try:
bkw['target_scanner'] = kw['target_scanner']
except KeyError:
pass
else:
del kw['target_scanner']
# source factory
try:
bkw['source_factory'] = kw['source_factory']
except KeyError:
pass
else:
del kw['source_factory']
# target factory
try:
bkw['target_factory'] = kw['target_factory']
except KeyError:
pass
else:
del kw['target_factory']
bld = SCons.Builder.Builder(**bkw)
return bld(self, target, source, **kw)
@ -2029,7 +2105,7 @@ class Base(SubstitutionEnvironment):
"""
action = self.Action(action, *args, **kw)
result = action([], [], self)
if isinstance(result, SCons.Errors.BuildError):
if isinstance(result, BuildError):
errstr = result.errstr
if result.filename:
errstr = result.filename + ': ' + errstr
@ -2149,7 +2225,7 @@ class Base(SubstitutionEnvironment):
for side_effect in side_effects:
if side_effect.multiple_side_effect_has_builder():
raise SCons.Errors.UserError("Multiple ways to build the same target were specified for: %s" % str(side_effect))
raise UserError("Multiple ways to build the same target were specified for: %s" % str(side_effect))
side_effect.add_source(targets)
side_effect.side_effect = 1
self.Precious(side_effect)
@ -2157,34 +2233,6 @@ class Base(SubstitutionEnvironment):
target.side_effects.append(side_effect)
return side_effects
def SourceCode(self, entry, builder):
"""Arrange for a source code builder for (part of) a tree."""
msg = """SourceCode() has been deprecated and there is no replacement.
\tIf you need this function, please contact scons-dev@scons.org"""
SCons.Warnings.warn(SCons.Warnings.DeprecatedSourceCodeWarning, msg)
entries = self.arg2nodes(entry, self.fs.Entry)
for entry in entries:
entry.set_src_builder(builder)
return entries
def SourceSignatures(self, type):
global _warn_source_signatures_deprecated
if _warn_source_signatures_deprecated:
msg = "The env.SourceSignatures() method is deprecated;\n" + \
"\tconvert your build to use the env.Decider() method instead."
SCons.Warnings.warn(SCons.Warnings.DeprecatedSourceSignaturesWarning, msg)
_warn_source_signatures_deprecated = False
type = self.subst(type)
self.src_sig_type = type
if type == 'MD5':
if not SCons.Util.md5:
raise UserError("MD5 signatures are not available in this version of Python.")
self.decide_source = self._changed_content
elif type == 'timestamp':
self.decide_source = self._changed_timestamp_match
else:
raise UserError("Unknown source signature type '%s'" % type)
def Split(self, arg):
"""This function converts a string or list into a list of strings
or Nodes. This makes things easier for users by allowing files to
@ -2206,32 +2254,10 @@ class Base(SubstitutionEnvironment):
else:
return [self.subst(arg)]
def TargetSignatures(self, type):
global _warn_target_signatures_deprecated
if _warn_target_signatures_deprecated:
msg = "The env.TargetSignatures() method is deprecated;\n" + \
"\tconvert your build to use the env.Decider() method instead."
SCons.Warnings.warn(SCons.Warnings.DeprecatedTargetSignaturesWarning, msg)
_warn_target_signatures_deprecated = False
type = self.subst(type)
self.tgt_sig_type = type
if type in ('MD5', 'content'):
if not SCons.Util.md5:
raise UserError("MD5 signatures are not available in this version of Python.")
self.decide_target = self._changed_content
elif type == 'timestamp':
self.decide_target = self._changed_timestamp_match
elif type == 'build':
self.decide_target = self._changed_build
elif type == 'source':
self.decide_target = self._changed_source
else:
raise UserError("Unknown target signature type '%s'"%type)
def Value(self, value, built_value=None):
def Value(self, value, built_value=None, name=None):
"""
"""
return SCons.Node.Python.Value(value, built_value)
return SCons.Node.Python.ValueWithMemo(value, built_value, name)
def VariantDir(self, variant_dir, src_dir, duplicate=1):
variant_dir = self.arg2nodes(variant_dir, self.fs.Dir)[0]
@ -2255,10 +2281,10 @@ class Base(SubstitutionEnvironment):
build_source(node.all_children())
def final_source(node):
while (node != node.srcnode()):
while node != node.srcnode():
node = node.srcnode()
return node
sources = list(map( final_source, sources ));
sources = list(map(final_source, sources))
# remove duplicates
return list(set(sources))
@ -2299,7 +2325,20 @@ class OverrideEnvironment(Base):
# Methods that make this class act like a proxy.
def __getattr__(self, name):
return getattr(self.__dict__['__subject'], name)
attr = getattr(self.__dict__['__subject'], name)
# Here we check if attr is one of the Wrapper classes. For
# example when a pseudo-builder is being called from an
# OverrideEnvironment.
#
# These wrappers when they're constructed capture the
# Environment they are being constructed with and so will not
# have access to overrided values. So we rebuild them with the
# OverrideEnvironment so they have access to overrided values.
if isinstance(attr, (MethodWrapper, BuilderWrapper)):
return attr.clone(self)
else:
return attr
def __setattr__(self, name, value):
setattr(self.__dict__['__subject'], name, value)
@ -2309,10 +2348,12 @@ class OverrideEnvironment(Base):
return self.__dict__['overrides'][key]
except KeyError:
return self.__dict__['__subject'].__getitem__(key)
def __setitem__(self, key, value):
if not is_valid_construction_var(key):
raise SCons.Errors.UserError("Illegal construction variable `%s'" % key)
raise UserError("Illegal construction variable `%s'" % key)
self.__dict__['overrides'][key] = value
def __delitem__(self, key):
try:
del self.__dict__['overrides'][key]
@ -2327,30 +2368,43 @@ class OverrideEnvironment(Base):
raise
result = None
return result
def get(self, key, default=None):
"""Emulates the get() method of dictionaries."""
try:
return self.__dict__['overrides'][key]
except KeyError:
return self.__dict__['__subject'].get(key, default)
def has_key(self, key):
"""Emulates the has_key() method of dictionaries."""
try:
self.__dict__['overrides'][key]
return 1
except KeyError:
return key in self.__dict__['__subject']
def __contains__(self, key):
if self.__dict__['overrides'].__contains__(key):
return 1
return self.__dict__['__subject'].__contains__(key)
def Dictionary(self):
"""Emulates the items() method of dictionaries."""
d = self.__dict__['__subject'].Dictionary().copy()
d.update(self.__dict__['overrides'])
return d
def items(self):
"""Emulates the items() method of dictionaries."""
return list(self.Dictionary().items())
return self.Dictionary().items()
def keys(self):
"""Emulates the keys() method of dictionaries."""
return self.Dictionary().keys()
def values(self):
"""Emulates the values() method of dictionaries."""
return self.Dictionary().values()
# Overridden private construction environment methods.
def _update(self, dict):
@ -2372,6 +2426,7 @@ class OverrideEnvironment(Base):
kw = copy_non_reserved_keywords(kw)
self.__dict__['overrides'].update(semi_deepcopy(kw))
# The entry point that will be used by the external world
# to refer to a construction environment. This allows the wrapper
# interface to extend a construction environment for its own purposes

View file

@ -0,0 +1,97 @@
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)
pass
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,16 @@
import unittest
from SCons.EnvironmentValues import EnvironmentValues
class MyTestCase(unittest.TestCase):
def test_simple_environmentValues(self):
"""Test comparing SubstitutionEnvironments
"""
env1 = EnvironmentValues(XXX='x')
env2 = EnvironmentValues(XXX='x',XX="$X", X1="${X}", X2="$($X$)")
if __name__ == '__main__':
unittest.main()

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -28,28 +28,25 @@ and user errors in SCons.
"""
__revision__ = "src/engine/SCons/Errors.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import shutil
import SCons.Util
class BuildError(Exception):
""" Errors occurring while building.
"""SCons Errors that can occur while building.
BuildError have the following attributes:
=========================================
Attributes:
Information about the cause of the build error :
Information about the cause of the build error:
-----------------------------------------------
errstr: a description of the error message
errstr : a description of the error message
status : the return code of the action that caused the build error.
status: 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 : SCons exit status due to this build error.
exitstatus: SCons exit status due to this build error.
Must be nonzero unless due to an explicit Exit()
call. Not always the same as status, since
actions return a status code that should be
@ -57,7 +54,7 @@ class BuildError(Exception):
irrespective of the return value of the failed
action.
filename : The name of the file or directory that caused the
filename: 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
@ -65,27 +62,26 @@ class BuildError(Exception):
can be None if the error is not due to a particular
filename.
exc_info : Info about exception that caused the build
exc_info: Info about exception that caused the build
error. Set to (None, None, None) if this build
error is not due to an exception.
Information about the what caused the build error :
Information about the cause of the location of the error:
---------------------------------------------------------
node: the error occurred while building this target node(s)
node : the error occured while building this target node(s)
executor : the executor that caused the build to fail (might
executor: the executor that caused the build to fail (might
be None if the build failures is not due to the
executor failing)
action : the action that caused the build to fail (might be
action: the action that caused the build to fail (might be
None if the build failures is not due to the an
action failure)
command : the command line for the action that caused the
command: 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)
"""
def __init__(self,
@ -95,7 +91,7 @@ class BuildError(Exception):
# py3: errstr should be string and not bytes.
self.errstr = SCons.Util.to_str(errstr)
self.errstr = SCons.Util.to_String(errstr)
self.status = status
self.exitstatus = exitstatus
self.filename = filename
@ -124,7 +120,7 @@ class UserError(Exception):
class StopError(Exception):
pass
class EnvironmentError(Exception):
class SConsEnvironmentError(Exception):
pass
class MSVCError(IOError):
@ -138,14 +134,15 @@ class ExplicitExit(Exception):
Exception.__init__(self, *args)
def convert_to_BuildError(status, exc_info=None):
"""
Convert any return code a BuildError Exception.
"""Convert a return code to a BuildError Exception.
:Parameters:
- `status`: can either be a return code or an Exception.
The buildError.status we set here will normally be
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):
@ -184,20 +181,19 @@ def convert_to_BuildError(status, exc_info=None):
filename=filename,
exc_info=exc_info)
elif isinstance(status, (EnvironmentError, OSError, IOError)):
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).
try:
filename = status.filename
except AttributeError:
filename = None
filename = getattr(status, 'filename', None)
strerror = getattr(status, 'strerror', str(status))
errno = getattr(status, 'errno', 2)
buildError = BuildError(
errstr=status.strerror,
status=status.errno,
errstr=strerror,
status=errno,
exitstatus=2,
filename=filename,
exc_info=exc_info)

View file

@ -6,7 +6,7 @@ Nodes.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -26,9 +26,7 @@ Nodes.
# 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 __future__ import print_function
__revision__ = "src/engine/SCons/Executor.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import collections
@ -36,9 +34,10 @@ import SCons.Debug
from SCons.Debug import logInstanceCreation
import SCons.Errors
import SCons.Memoize
from SCons.compat import with_metaclass, NoSlotsPyPy
import SCons.Util
from SCons.compat import NoSlotsPyPy
class Batch(object):
class Batch:
"""Remembers exact association between targets
and sources of executor."""
@ -71,7 +70,7 @@ class TSList(collections.UserList):
return nl[i]
def __getslice__(self, i, j):
nl = self.func()
i = max(i, 0); j = max(j, 0)
i, j = max(i, 0), max(j, 0)
return nl[i:j]
def __str__(self):
nl = self.func()
@ -80,7 +79,7 @@ class TSList(collections.UserList):
nl = self.func()
return repr(nl)
class TSObject(object):
class TSObject:
"""A class that implements $TARGET or $SOURCE expansions by wrapping
an Executor method.
"""
@ -127,7 +126,7 @@ def execute_action_list(obj, target, kw):
status = act(*args, **kw)
if isinstance(status, SCons.Errors.BuildError):
status.executor = obj
raise status
raise status # TODO pylint E0702: raising int not allowed
elif status:
msg = "Error %s" % status
raise SCons.Errors.BuildError(
@ -155,7 +154,7 @@ _execute_str_map = {0 : execute_null_str,
1 : execute_actions_str}
class Executor(object, with_metaclass(NoSlotsPyPy)):
class Executor(object, metaclass=NoSlotsPyPy):
"""A class for controlling instances of executing an action.
This largely exists to hold a single association of an action,
@ -450,6 +449,8 @@ class Executor(object, with_metaclass(NoSlotsPyPy)):
"""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']
@ -570,7 +571,6 @@ def AddBatchExecutor(key, executor):
nullenv = None
import SCons.Util
class NullEnvironment(SCons.Util.Null):
import SCons.CacheDir
_CacheDir_path = None
@ -587,7 +587,7 @@ def get_NullEnvironment():
nullenv = NullEnvironment()
return nullenv
class Null(object, with_metaclass(NoSlotsPyPy)):
class Null(object, metaclass=NoSlotsPyPy):
"""A null Executor, with a null build Environment, that does
nothing when the rest of the methods call it.
@ -613,7 +613,8 @@ class Null(object, with_metaclass(NoSlotsPyPy)):
'_execute_str')
def __init__(self, *args, **kw):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Executor.Null')
if SCons.Debug.track_instances:
logInstanceCreation(self, 'Executor.Null')
self.batches = [Batch(kw['targets'][:], [])]
def get_build_env(self):
return get_NullEnvironment()

View file

@ -7,7 +7,7 @@ stop, and wait on jobs.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -29,7 +29,7 @@ stop, and wait on jobs.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Job.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.compat
@ -52,7 +52,7 @@ default_stack_size = 256
interrupt_msg = 'Build interrupted.'
class InterruptState(object):
class InterruptState:
def __init__(self):
self.interrupted = False
@ -63,7 +63,7 @@ class InterruptState(object):
return self.interrupted
class Jobs(object):
class Jobs:
"""An instance of this class initializes N jobs, and provides
methods for starting, stopping, and waiting on all N jobs.
"""
@ -163,7 +163,7 @@ class Jobs(object):
except AttributeError:
pass
class Serial(object):
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.
@ -199,7 +199,7 @@ class Serial(object):
task.prepare()
if task.needs_execute():
task.execute()
except:
except Exception:
if self.interrupted():
try:
raise SCons.Errors.BuildError(
@ -264,7 +264,7 @@ else:
self.resultsQueue.put((task, ok))
class ThreadPool(object):
class ThreadPool:
"""This class is responsible for spawning and managing worker threads."""
def __init__(self, num, stack_size, interrupted):
@ -281,7 +281,7 @@ else:
except AttributeError as e:
# Only print a warning if the stack size has been
# explicitly set.
if not explicit_stack_size is None:
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)
@ -338,7 +338,7 @@ else:
worker.join(1.0)
self.workers = []
class Parallel(object):
class Parallel:
"""This class is used to execute tasks in parallel, and is somewhat
less efficient than Serial, but is appropriate for parallel builds.

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -20,9 +20,7 @@
# 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 __future__ import print_function
__revision__ = "src/engine/SCons/Memoize.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """Memoizer
@ -106,7 +104,7 @@ use_memoizer = None
# Global list of counter objects
CounterList = {}
class Counter(object):
class Counter:
"""
Base class for counting memoization hits and misses.

View file

@ -8,7 +8,7 @@ This creates a hash of global Aliases (dummy targets).
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,13 +30,14 @@ This creates a hash of global Aliases (dummy targets).
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Node/Alias.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import collections
import SCons.Errors
import SCons.Node
import SCons.Util
from SCons.Util import MD5signature
class AliasNameSpace(collections.UserDict):
def Alias(self, name, **kw):
@ -166,7 +167,7 @@ class Alias(SCons.Node.Node):
pass
contents = self.get_contents()
csig = SCons.Util.MD5signature(contents)
csig = MD5signature(contents)
self.get_ninfo().csig = csig
return csig

View file

@ -11,7 +11,7 @@ that can be used by scripts or modules looking for the canonical default.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -31,9 +31,7 @@ that can be used by scripts or modules looking for the canonical default.
# 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 __future__ import print_function
__revision__ = "src/engine/SCons/Node/FS.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import fnmatch
import os
@ -43,6 +41,8 @@ import stat
import sys
import time
import codecs
from itertools import chain
import importlib.util
import SCons.Action
import SCons.Debug
@ -53,12 +53,15 @@ import SCons.Node
import SCons.Node.Alias
import SCons.Subst
import SCons.Util
from SCons.Util import MD5signature, MD5filesignature, MD5collect
import SCons.Warnings
from SCons.Debug import Trace
print_duplicate = 0
MD5_TIMESTAMP_DEBUG = False
def sconsign_none(node):
raise NotImplementedError
@ -74,6 +77,9 @@ def sconsign_dir(node):
_sconsign_map = {0 : sconsign_none,
1 : sconsign_dir}
class FileBuildInfoFileToCsigMappingError(Exception):
pass
class EntryProxyAttributeError(AttributeError):
"""
An AttributeError subclass for recording and displaying the name
@ -132,7 +138,10 @@ def initialize_do_splitdrive():
global do_splitdrive
global has_unc
drive, path = os.path.splitdrive('X:/foo')
has_unc = hasattr(os.path, 'splitunc')
# splitunc is removed from python 3.7 and newer
# so we can also just test if splitdrive works with UNC
has_unc = (hasattr(os.path, 'splitunc')
or os.path.splitdrive(r'\\split\drive\test')[0] == r'\\split\drive')
do_splitdrive = not not drive or has_unc
@ -272,7 +281,7 @@ def set_duplicate(duplicate):
'copy' : _copy_func
}
if not duplicate in Valid_Duplicates:
if duplicate not in Valid_Duplicates:
raise SCons.Errors.InternalError("The argument of set_duplicate "
"should be in Valid_Duplicates")
global Link_Funcs
@ -282,11 +291,13 @@ def set_duplicate(duplicate):
Link_Funcs.append(link_dict[func])
def LinkFunc(target, source, env):
# Relative paths cause problems with symbolic links, so
# we use absolute paths, which may be a problem for people
# who want to move their soft-linked src-trees around. Those
# people should use the 'hard-copy' mode, softlinks cannot be
# used for that; at least I have no idea how ...
"""
Relative paths cause problems with symbolic links, so
we use absolute paths, which may be a problem for people
who want to move their soft-linked src-trees around. Those
people should use the 'hard-copy' mode, softlinks cannot be
used for that; at least I have no idea how ...
"""
src = source[0].get_abspath()
dest = target[0].get_abspath()
dir, file = os.path.split(dest)
@ -328,7 +339,12 @@ Unlink = SCons.Action.Action(UnlinkFunc, None)
def MkdirFunc(target, source, env):
t = target[0]
if not t.exists():
# This os.path.exists test looks redundant, but it's possible
# when using Install() to install multiple dirs outside the
# source tree to get a case where t.exists() is true but
# the path does already exist, so this prevents spurious
# build failures in that case. See test/Install/multi-dir.
if not t.exists() and not os.path.exists(t.get_abspath()):
t.fs.mkdir(t.get_abspath())
return 0
@ -351,7 +367,7 @@ def get_MkdirBuilder():
name = "MkdirBuilder")
return MkdirBuilder
class _Null(object):
class _Null:
pass
_null = _Null()
@ -367,7 +383,7 @@ else:
class DiskChecker(object):
class DiskChecker:
def __init__(self, type, do, ignore):
self.type = type
self.do = do
@ -463,7 +479,7 @@ class EntryProxy(SCons.Util.Proxy):
return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
def __get_windows_path(self):
"""Return the path with \ as the path separator,
r"""Return the path with \ as the path separator,
regardless of platform."""
if OS_SEP == '\\':
return self
@ -514,7 +530,7 @@ class EntryProxy(SCons.Util.Proxy):
except KeyError:
try:
attr = SCons.Util.Proxy.__getattr__(self, name)
except AttributeError as e:
except AttributeError:
# Raise our own AttributeError subclass with an
# overridden __str__() method that identifies the
# name of the entry that caused the exception.
@ -682,13 +698,32 @@ class Base(SCons.Node.Node):
@SCons.Memoize.CountMethodCall
def stat(self):
try: return self._memo['stat']
except KeyError: pass
try: result = self.fs.stat(self.get_abspath())
except os.error: result = None
try:
return self._memo['stat']
except KeyError:
pass
try:
result = self.fs.stat(self.get_abspath())
except os.error:
result = None
self._memo['stat'] = result
return result
@SCons.Memoize.CountMethodCall
def lstat(self):
try:
return self._memo['lstat']
except KeyError:
pass
try:
result = self.fs.lstat(self.get_abspath())
except os.error:
result = None
self._memo['lstat'] = result
return result
def exists(self):
return SCons.Node._exists_map[self._func_exists](self)
@ -696,14 +731,26 @@ class Base(SCons.Node.Node):
return SCons.Node._rexists_map[self._func_rexists](self)
def getmtime(self):
if self.islink():
st = self.lstat()
else:
st = self.stat()
if st: return st[stat.ST_MTIME]
else: return None
if st:
return st[stat.ST_MTIME]
else:
return None
def getsize(self):
if self.islink():
st = self.lstat()
else:
st = self.stat()
if st: return st[stat.ST_SIZE]
else: return None
if st:
return st[stat.ST_SIZE]
else:
return None
def isdir(self):
st = self.stat()
@ -939,13 +986,13 @@ class Entry(Base):
def disambiguate(self, must_exist=None):
"""
"""
if self.isdir():
self.__class__ = Dir
self._morph()
elif self.isfile():
if self.isfile():
self.__class__ = File
self._morph()
self.clear()
elif self.isdir():
self.__class__ = Dir
self._morph()
else:
# There was nothing on-disk at this location, so look in
# the src directory.
@ -1047,22 +1094,23 @@ class Entry(Base):
_classEntry = Entry
class LocalFS(object):
class LocalFS:
"""
This class implements an abstraction layer for operations involving
a local file system. Essentially, this wraps any function in
the os, os.path or shutil modules that we use to actually go do
anything with or to the local file system.
# This class implements an abstraction layer for operations involving
# a local file system. Essentially, this wraps any function in
# the os, os.path or shutil modules that we use to actually go do
# anything with or to the local file system.
#
# Note that there's a very good chance we'll refactor this part of
# the architecture in some way as we really implement the interface(s)
# for remote file system Nodes. For example, the right architecture
# might be to have this be a subclass instead of a base class.
# Nevertheless, we're using this as a first step in that direction.
#
# We're not using chdir() yet because the calling subclass method
# needs to use os.chdir() directly to avoid recursion. Will we
# really need this one?
Note that there's a very good chance we'll refactor this part of
the architecture in some way as we really implement the interface(s)
for remote file system Nodes. For example, the right architecture
might be to have this be a subclass instead of a base class.
Nevertheless, we're using this as a first step in that direction.
We're not using chdir() yet because the calling subclass method
needs to use os.chdir() directly to avoid recursion. Will we
really need this one?
"""
#def chdir(self, path):
# return os.chdir(path)
def chmod(self, path, mode):
@ -1391,7 +1439,7 @@ class FS(LocalFS):
self.Top.addRepository(d)
def PyPackageDir(self, modulename):
"""Locate the directory of a given python module name
r"""Locate the directory of a given python module name
For example scons might resolve to
Windows: C:\Python27\Lib\site-packages\scons-2.5.1
@ -1400,20 +1448,8 @@ class FS(LocalFS):
This can be useful when we want to determine a toolpath based on a python module name"""
dirpath = ''
if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] in (0,1,2,3,4)):
# Python2 Code
import imp
splitname = modulename.split('.')
srchpths = sys.path
for item in splitname:
file, path, desc = imp.find_module(item, srchpths)
if file is not None:
path = os.path.dirname(path)
srchpths = [path]
dirpath = path
else:
# Python3 Code
import importlib.util
modspec = importlib.util.find_spec(modulename)
dirpath = os.path.dirname(modspec.origin)
return self._lookup(dirpath, None, Dir, True)
@ -1522,9 +1558,7 @@ class Dir(Base):
self.repositories = []
self.srcdir = None
self.entries = {}
self.entries['.'] = self
self.entries['..'] = self.dir
self.entries = {'.': self, '..': self.dir}
self.cwd = self
self.searched = 0
self._sconsign = None
@ -1585,7 +1619,7 @@ class Dir(Base):
This clears any cached information that is invalidated by changing
the repository."""
for node in list(self.entries.values()):
for node in self.entries.values():
if node != self.dir:
if node != self and isinstance(node, Dir):
node.__clearRepositoryCache(duplicate)
@ -1596,7 +1630,7 @@ class Dir(Base):
except AttributeError:
pass
if duplicate is not None:
node.duplicate=duplicate
node.duplicate = duplicate
def __resetDuplicate(self, node):
if node != self:
@ -1662,7 +1696,7 @@ class Dir(Base):
return result
def addRepository(self, dir):
if dir != self and not dir in self.repositories:
if dir != self and dir not in self.repositories:
self.repositories.append(dir)
dir._tpath = '.'
self.__clearRepositoryCache()
@ -1702,7 +1736,7 @@ class Dir(Base):
if self is other:
result = '.'
elif not other in self._path_elements:
elif other not in self._path_elements:
try:
other_dir = other.get_dir()
except AttributeError:
@ -1836,7 +1870,7 @@ class Dir(Base):
node is called which has a child directory, the child
directory should return the hash of its contents."""
contents = self.get_contents()
return SCons.Util.MD5signature(contents)
return MD5signature(contents)
def do_duplicate(self, src):
pass
@ -2234,7 +2268,7 @@ class RootDir(Dir):
this directory.
"""
__slots__ = ['_lookupDict']
__slots__ = ('_lookupDict', 'abspath', 'path')
def __init__(self, drive, fs):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir')
@ -2276,13 +2310,17 @@ class RootDir(Dir):
self._tpath = dirname
self.dirname = dirname
# EntryProxy interferes with this class and turns drive paths on
# Windows such as "C:" into "C:\C:". Avoid this problem by setting
# commonly-accessed attributes directly.
self.abspath = self._abspath
self.path = self._path
self._morph()
self.duplicate = 0
self._lookupDict = {}
self._lookupDict = {'': self, '/': self}
self._lookupDict[''] = self
self._lookupDict['/'] = self
self.root = self
# The // entry is necessary because os.path.normpath()
# preserves double slashes at the beginning of a path on Posix
@ -2302,9 +2340,7 @@ class RootDir(Dir):
self.repositories = []
self.srcdir = None
self.entries = {}
self.entries['.'] = self
self.entries['..'] = self.dir
self.entries = {'.': self, '..': self.dir}
self.cwd = self
self.searched = 0
self._sconsign = None
@ -2440,7 +2476,7 @@ class FileNodeInfo(SCons.Node.NodeInfoBase):
"""
state = getattr(self, '__dict__', {}).copy()
for obj in type(self).mro():
for name in getattr(obj,'__slots__',()):
for name in getattr(obj, '__slots__', ()):
if hasattr(self, name):
state[name] = getattr(self, name)
@ -2462,11 +2498,42 @@ class FileNodeInfo(SCons.Node.NodeInfoBase):
if key not in ('__weakref__',):
setattr(self, key, value)
def __eq__(self, other):
return self.csig == other.csig and self.timestamp == other.timestamp and self.size == other.size
def __ne__(self, other):
return not self.__eq__(other)
class FileBuildInfo(SCons.Node.BuildInfoBase):
__slots__ = ()
"""
This is info loaded from sconsign.
Attributes unique to FileBuildInfo:
dependency_map : Caches file->csig mapping
for all dependencies. Currently this is only used when using
MD5-timestamp decider.
It's used to ensure that we copy the correct csig from the
previous build to be written to .sconsign when current build
is done. Previously the matching of csig to file was strictly
by order they appeared in bdepends, bsources, or bimplicit,
and so a change in order or count of any of these could
yield writing wrong csig, and then false positive rebuilds
"""
__slots__ = ['dependency_map', ]
current_version_id = 2
def __setattr__(self, key, value):
# If any attributes are changed in FileBuildInfo, we need to
# invalidate the cached map of file name to content signature
# heald in dependency_map. Currently only used with
# MD5-timestamp decider
if key != 'dependency_map' and hasattr(self, 'dependency_map'):
del self.dependency_map
return super(FileBuildInfo, self).__setattr__(key, value)
def convert_to_sconsign(self):
"""
Converts this FileBuildInfo object for writing to a .sconsign file
@ -2666,11 +2733,10 @@ class File(Base):
Compute and return the MD5 hash for this file.
"""
if not self.rexists():
return SCons.Util.MD5signature('')
return MD5signature('')
fname = self.rfile().get_abspath()
try:
cs = SCons.Util.MD5filesignature(fname,
chunksize=SCons.Node.FS.File.md5_chunksize*1024)
cs = MD5filesignature(fname, chunksize=File.md5_chunksize * 1024)
except EnvironmentError as e:
if not e.filename:
e.filename = fname
@ -2971,7 +3037,7 @@ class File(Base):
@see: built() and Node.release_target_info()
"""
if (self.released_target_info or SCons.Node.interactive):
if self.released_target_info or SCons.Node.interactive:
return
if not hasattr(self.attributes, 'keep_targetinfo'):
@ -3059,7 +3125,10 @@ class File(Base):
SCons.Node.Node.prepare(self)
if self.get_state() != SCons.Node.up_to_date:
if self.exists():
# Exists will report False for dangling symlinks so if it
# exists or is a link (which would mean it's a dangling
# link) then we should remove it as appropriate.
if self.exists() or self.islink():
if self.is_derived() and not self.precious:
self._rmv_existing()
else:
@ -3153,7 +3222,7 @@ class File(Base):
if csig is None:
try:
if self.get_size() < SCons.Node.FS.File.md5_chunksize:
if self.get_size() < File.md5_chunksize:
contents = self.get_contents()
else:
csig = self.get_content_hash()
@ -3225,46 +3294,232 @@ class File(Base):
self._memo['changed'] = has_changed
return has_changed
def changed_content(self, target, prev_ni):
def changed_content(self, target, prev_ni, repo_node=None):
cur_csig = self.get_csig()
try:
return cur_csig != prev_ni.csig
except AttributeError:
return 1
def changed_state(self, target, prev_ni):
def changed_state(self, target, prev_ni, repo_node=None):
return self.state != SCons.Node.up_to_date
def changed_timestamp_then_content(self, target, prev_ni):
if not self.changed_timestamp_match(target, prev_ni):
# Caching node -> string mapping for the below method
__dmap_cache = {}
__dmap_sig_cache = {}
def _build_dependency_map(self, binfo):
"""
Build mapping from file -> signature
Args:
self - self
binfo - buildinfo from node being considered
Returns:
dictionary of file->signature mappings
"""
# For an "empty" binfo properties like bsources
# do not exist: check this to avoid exception.
if (len(binfo.bsourcesigs) + len(binfo.bdependsigs) +
len(binfo.bimplicitsigs)) == 0:
return {}
binfo.dependency_map = { child:signature for child, signature in zip(chain(binfo.bsources, binfo.bdepends, binfo.bimplicit),
chain(binfo.bsourcesigs, binfo.bdependsigs, binfo.bimplicitsigs))}
return binfo.dependency_map
# @profile
def _add_strings_to_dependency_map(self, dmap):
"""
In the case comparing node objects isn't sufficient, we'll add the strings for the nodes to the dependency map
:return:
"""
first_string = str(next(iter(dmap)))
# print("DMAP:%s"%id(dmap))
if first_string not in dmap:
string_dict = {str(child): signature for child, signature in dmap.items()}
dmap.update(string_dict)
return dmap
def _get_previous_signatures(self, dmap):
"""
Return a list of corresponding csigs from previous
build in order of the node/files in children.
Args:
self - self
dmap - Dictionary of file -> csig
Returns:
List of csigs for provided list of children
"""
prev = []
# MD5_TIMESTAMP_DEBUG = False
if len(dmap) == 0:
if MD5_TIMESTAMP_DEBUG: print("Nothing dmap shortcutting")
return None
elif MD5_TIMESTAMP_DEBUG: print("len(dmap):%d"%len(dmap))
# First try retrieving via Node
if MD5_TIMESTAMP_DEBUG: print("Checking if self is in map:%s id:%s type:%s"%(str(self), id(self), type(self)))
df = dmap.get(self, False)
if df:
return df
# Now check if self's repository file is in map.
rf = self.rfile()
if MD5_TIMESTAMP_DEBUG: print("Checking if self.rfile is in map:%s id:%s type:%s"%(str(rf), id(rf), type(rf)))
rfm = dmap.get(rf, False)
if rfm:
return rfm
# get default string for node and then also string swapping os.altsep for os.sep (/ for \)
c_strs = [str(self)]
if os.altsep:
c_strs.append(c_strs[0].replace(os.sep, os.altsep))
# In some cases the dependency_maps' keys are already strings check.
# Check if either string is now in dmap.
for s in c_strs:
if MD5_TIMESTAMP_DEBUG: print("Checking if str(self) is in map :%s" % s)
df = dmap.get(s, False)
if df:
return df
# Strings don't exist in map, add them and try again
# If there are no strings in this dmap, then add them.
# This may not be necessary, we could walk the nodes in the dmap and check each string
# rather than adding ALL the strings to dmap. In theory that would be n/2 vs 2n str() calls on node
# if not dmap.has_strings:
dmap = self._add_strings_to_dependency_map(dmap)
# In some cases the dependency_maps' keys are already strings check.
# Check if either string is now in dmap.
for s in c_strs:
if MD5_TIMESTAMP_DEBUG: print("Checking if str(self) is in map (now with strings) :%s" % s)
df = dmap.get(s, False)
if df:
return df
# Lastly use nodes get_path() to generate string and see if that's in dmap
if not df:
try:
self.get_ninfo().csig = prev_ni.csig
# this should yield a path which matches what's in the sconsign
c_str = self.get_path()
if os.altsep:
c_str = c_str.replace(os.sep, os.altsep)
if MD5_TIMESTAMP_DEBUG: print("Checking if self.get_path is in map (now with strings) :%s" % s)
df = dmap.get(c_str, None)
except AttributeError as e:
raise FileBuildInfoFileToCsigMappingError("No mapping from file name to content signature for :%s"%c_str)
return df
def changed_timestamp_then_content(self, target, prev_ni, node=None):
"""
Used when decider for file is Timestamp-MD5
NOTE: If the timestamp hasn't changed this will skip md5'ing the
file and just copy the prev_ni provided. If the prev_ni
is wrong. It will propagate it.
See: https://github.com/SCons/scons/issues/2980
Args:
self - dependency
target - target
prev_ni - The NodeInfo object loaded from previous builds .sconsign
node - Node instance. Check this node for file existence/timestamp
if specified.
Returns:
Boolean - Indicates if node(File) has changed.
"""
if node is None:
node = self
# Now get sconsign name -> csig map and then get proper prev_ni if possible
bi = node.get_stored_info().binfo
rebuilt = False
try:
dependency_map = bi.dependency_map
except AttributeError as e:
dependency_map = self._build_dependency_map(bi)
rebuilt = True
if len(dependency_map) == 0:
# If there's no dependency map, there's no need to find the
# prev_ni as there aren't any
# shortcut the rest of the logic
if MD5_TIMESTAMP_DEBUG: print("Skipping checks len(dmap)=0")
# We still need to get the current file's csig
# This should be slightly faster than calling self.changed_content(target, new_prev_ni)
self.get_csig()
return True
new_prev_ni = self._get_previous_signatures(dependency_map)
new = self.changed_timestamp_match(target, new_prev_ni)
if MD5_TIMESTAMP_DEBUG:
old = self.changed_timestamp_match(target, prev_ni)
if old != new:
print("Mismatch self.changed_timestamp_match(%s, prev_ni) old:%s new:%s"%(str(target), old, new))
new_prev_ni = self._get_previous_signatures(dependency_map)
if not new:
try:
# NOTE: We're modifying the current node's csig in a query.
self.get_ninfo().csig = new_prev_ni.csig
except AttributeError:
pass
return False
return self.changed_content(target, prev_ni)
return self.changed_content(target, new_prev_ni)
def changed_timestamp_newer(self, target, prev_ni):
def changed_timestamp_newer(self, target, prev_ni, repo_node=None):
try:
return self.get_timestamp() > target.get_timestamp()
except AttributeError:
return 1
def changed_timestamp_match(self, target, prev_ni):
def changed_timestamp_match(self, target, prev_ni, repo_node=None):
"""
Return True if the timestamps don't match or if there is no previous timestamp
:param target:
:param prev_ni: Information about the node from the previous build
:return:
"""
try:
return self.get_timestamp() != prev_ni.timestamp
except AttributeError:
return 1
def is_up_to_date(self):
"""Check for whether the Node is current
In all cases self is the target we're checking to see if it's up to date
"""
T = 0
if T: Trace('is_up_to_date(%s):' % self)
if not self.exists():
if T: Trace(' not self.exists():')
# The file doesn't exist locally...
# The file (always a target) doesn't exist locally...
r = self.rfile()
if r != self:
# ...but there is one in a Repository...
# ...but there is one (always a target) in a Repository...
if not self.changed(r):
if T: Trace(' changed(%s):' % r)
# ...and it's even up-to-date...
@ -3272,7 +3527,9 @@ class File(Base):
# ...and they'd like a local copy.
e = LocalCopy(self, r, None)
if isinstance(e, SCons.Errors.BuildError):
raise
# Likely this should be re-raising exception e
# (which would be BuildError)
raise e
SCons.Node.store_info_map[self.store_info](self)
if T: Trace(' 1\n')
return 1
@ -3293,11 +3550,14 @@ class File(Base):
result = self
if not self.exists():
norm_name = _my_normcase(self.name)
for dir in self.dir.get_all_rdirs():
try: node = dir.entries[norm_name]
except KeyError: node = dir.file_on_disk(self.name)
for repo_dir in self.dir.get_all_rdirs():
try:
node = repo_dir.entries[norm_name]
except KeyError:
node = repo_dir.file_on_disk(self.name)
if node and node.exists() and \
(isinstance(node, File) or isinstance(node, Entry) \
(isinstance(node, File) or isinstance(node, Entry)
or not node.is_derived()):
result = node
# Copy over our local attributes to the repository
@ -3317,6 +3577,28 @@ class File(Base):
self._memo['rfile'] = result
return result
def find_repo_file(self):
"""
For this node, find if there exists a corresponding file in one or more repositories
:return: list of corresponding files in repositories
"""
retvals = []
norm_name = _my_normcase(self.name)
for repo_dir in self.dir.get_all_rdirs():
try:
node = repo_dir.entries[norm_name]
except KeyError:
node = repo_dir.file_on_disk(self.name)
if node and node.exists() and \
(isinstance(node, File) or isinstance(node, Entry)
or not node.is_derived()):
retvals.append(node)
return retvals
def rstr(self):
return str(self.rfile())
@ -3341,8 +3623,7 @@ class File(Base):
cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
if not self.exists() and cachefile and os.path.exists(cachefile):
self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \
SCons.Node.FS.File.md5_chunksize * 1024)
self.cachedir_csig = MD5filesignature(cachefile, File.md5_chunksize * 1024)
else:
self.cachedir_csig = self.get_csig()
return self.cachedir_csig
@ -3362,7 +3643,7 @@ class File(Base):
executor = self.get_executor()
result = self.contentsig = SCons.Util.MD5signature(executor.get_contents())
result = self.contentsig = MD5signature(executor.get_contents())
return result
def get_cachedir_bsig(self):
@ -3374,6 +3655,8 @@ class File(Base):
because multiple targets built by the same action will all
have the same build signature, and we have to differentiate
them somehow.
Signature should normally be string of hex digits.
"""
try:
return self.cachesig
@ -3383,12 +3666,15 @@ class File(Base):
# Collect signatures for all children
children = self.children()
sigs = [n.get_cachedir_csig() for n in children]
# Append this node's signature...
sigs.append(self.get_contents_sig())
# ...and it's path
sigs.append(self.get_internal_path())
# Merge this all into a single signature
result = self.cachesig = SCons.Util.MD5collect(sigs)
result = self.cachesig = MD5collect(sigs)
return result
default_fs = None
@ -3399,7 +3685,7 @@ def get_default_fs():
default_fs = FS()
return default_fs
class FileFinder(object):
class FileFinder:
"""
"""
@ -3472,7 +3758,7 @@ class FileFinder(object):
if verbose and not callable(verbose):
if not SCons.Util.is_String(verbose):
verbose = "find_file"
_verbose = u' %s: ' % verbose
_verbose = ' %s: ' % verbose
verbose = lambda s: sys.stdout.write(_verbose + s)
filedir, filename = os.path.split(filename)

View file

@ -5,7 +5,7 @@ Python nodes.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -27,10 +27,13 @@ Python nodes.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Node/Python.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Node
_memo_lookup_map = {}
class ValueNodeInfo(SCons.Node.NodeInfoBase):
__slots__ = ('csig',)
current_version_id = 2
@ -38,18 +41,18 @@ class ValueNodeInfo(SCons.Node.NodeInfoBase):
field_list = ['csig']
def str_to_node(self, s):
return Value(s)
return ValueWithMemo(s)
def __getstate__(self):
"""
Return all fields that shall be pickled. Walk the slots in the class
hierarchy and add those to the state dictionary. If a '__dict__' slot is
available, copy all entries to the dictionary. Also include the version
id, which is fixed for all instances of a class.
hierarchy and add those to the state dictionary. If a '__dict__' slot
is available, copy all entries to the dictionary. Also include the
version id, which is fixed for all instances of a class.
"""
state = getattr(self, '__dict__', {}).copy()
for obj in type(self).mro():
for name in getattr(obj,'__slots__',()):
for name in getattr(obj, '__slots__', ()):
if hasattr(self, name):
state[name] = getattr(self, name)
@ -76,6 +79,7 @@ class ValueBuildInfo(SCons.Node.BuildInfoBase):
__slots__ = ()
current_version_id = 2
class Value(SCons.Node.Node):
"""A class for Python variables, typically passed on the command line
or generated by a script, but not from a file or some other source.
@ -84,7 +88,7 @@ class Value(SCons.Node.Node):
NodeInfo = ValueNodeInfo
BuildInfo = ValueBuildInfo
def __init__(self, value, built_value=None):
def __init__(self, value, built_value=None, name=None):
SCons.Node.Node.__init__(self)
self.value = value
self.changed_since_last_build = 6
@ -92,6 +96,13 @@ class Value(SCons.Node.Node):
if built_value is not None:
self.built_value = built_value
# Set a name so it can be a child of a node and not break
# its parent's implementation of Node.get_contents.
if name:
self.name = name
else:
self.name = str(value)
def str_for_display(self):
return repr(self.value)
@ -137,6 +148,10 @@ class Value(SCons.Node.Node):
return contents
def get_contents(self):
"""
Get contents for signature calculations.
:return: bytes
"""
text_contents = self.get_text_contents()
try:
return text_contents.encode()
@ -144,7 +159,6 @@ class Value(SCons.Node.Node):
# Already encoded as python2 str are bytes
return text_contents
def changed_since_last_build(self, target, prev_ni):
cur_csig = self.get_csig()
try:
@ -155,15 +169,45 @@ class Value(SCons.Node.Node):
def get_csig(self, calc=None):
"""Because we're a Python value node and don't have a real
timestamp, we get to ignore the calculator and just use the
value contents."""
value contents.
Returns string. Ideally string of hex digits. (Not bytes)
"""
try:
return self.ninfo.csig
except AttributeError:
pass
contents = self.get_contents()
contents = self.get_text_contents()
self.get_ninfo().csig = contents
return contents
def ValueWithMemo(value, built_value=None, name=None):
"""
Memoized Value() node factory.
"""
global _memo_lookup_map
# No current support for memoizing a value that needs to be built.
if built_value:
return Value(value, built_value, name=name)
try:
memo_lookup_key = hash((value, name))
except TypeError:
# Non-primitive types will hit this codepath.
return Value(value, name=name)
try:
return _memo_lookup_map[memo_lookup_key]
except KeyError:
v = Value(value, built_value, name)
_memo_lookup_map[memo_lookup_key] = v
return v
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil

View file

@ -19,10 +19,8 @@ be able to depend on any other type of "thing."
"""
from __future__ import print_function
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -43,21 +41,28 @@ from __future__ import print_function
# 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__ = "src/engine/SCons/Node/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import collections
import copy
from itertools import chain
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest
import SCons.Debug
from SCons.Debug import logInstanceCreation
import SCons.Executor
import SCons.Memoize
import SCons.Util
from SCons.Util import MD5signature
from SCons.Debug import Trace
from SCons.compat import with_metaclass, NoSlotsPyPy
from SCons.compat import NoSlotsPyPy
print_duplicate = 0
@ -102,9 +107,12 @@ implicit_deps_changed = 0
# A variable that can be set to an interface-specific function be called
# to annotate a Node with information about its creation.
def do_nothing(node): pass
def do_nothing_node(node): pass
Annotate = do_nothing
Annotate = do_nothing_node
# global set for recording all processed SContruct/SConscript nodes
SConscriptNodes = set()
# Gets set to 'True' if we're running in interactive mode. Is
# currently used to release parts of a target's info during
@ -139,6 +147,7 @@ def exists_entry(node):
node.disambiguate()
return _exists_map[node._func_exists](node)
def exists_file(node):
# Duplicate from source path if we are set up to do this.
if node.duplicate and not node.is_derived() and not node.linked:
@ -155,7 +164,7 @@ def exists_file(node):
# The source file does not exist. Make sure no old
# copy remains in the variant directory.
if print_duplicate:
print("dup: no src for %s, unlinking old variant copy"%self)
print("dup: no src for %s, unlinking old variant copy" % node)
if exists_base(node) or node.islink():
node.fs.unlink(node.get_internal_path())
# Return None explicitly because the Base.exists() call
@ -249,7 +258,7 @@ _target_from_source_map = {0 : target_from_source_none,
#
# First, the single decider functions
#
def changed_since_last_build_node(node, target, prev_ni):
def changed_since_last_build_node(node, target, prev_ni, repo_node=None):
"""
Must be overridden in a specific subclass to return True if this
@ -269,27 +278,33 @@ def changed_since_last_build_node(node, target, prev_ni):
"""
raise NotImplementedError
def changed_since_last_build_alias(node, target, prev_ni):
def changed_since_last_build_alias(node, target, prev_ni, repo_node=None):
cur_csig = node.get_csig()
try:
return cur_csig != prev_ni.csig
except AttributeError:
return 1
def changed_since_last_build_entry(node, target, prev_ni):
def changed_since_last_build_entry(node, target, prev_ni, repo_node=None):
node.disambiguate()
return _decider_map[node.changed_since_last_build](node, target, prev_ni)
return _decider_map[node.changed_since_last_build](node, target, prev_ni, repo_node)
def changed_since_last_build_state_changed(node, target, prev_ni):
return (node.state != SCons.Node.up_to_date)
def decide_source(node, target, prev_ni):
return target.get_build_env().decide_source(node, target, prev_ni)
def changed_since_last_build_state_changed(node, target, prev_ni, repo_node=None):
return node.state != SCons.Node.up_to_date
def decide_target(node, target, prev_ni):
return target.get_build_env().decide_target(node, target, prev_ni)
def changed_since_last_build_python(node, target, prev_ni):
def decide_source(node, target, prev_ni, repo_node=None):
return target.get_build_env().decide_source(node, target, prev_ni, repo_node)
def decide_target(node, target, prev_ni, repo_node=None):
return target.get_build_env().decide_target(node, target, prev_ni, repo_node)
def changed_since_last_build_python(node, target, prev_ni, repo_node=None):
cur_csig = node.get_csig()
try:
return cur_csig != prev_ni.csig
@ -341,7 +356,7 @@ store_info_map = {0 : store_info_pass,
# Classes for signature info for Nodes.
class NodeInfoBase(object):
class NodeInfoBase:
"""
The generic base class for signature information for a Node.
@ -380,6 +395,7 @@ class NodeInfoBase(object):
"""
state = other.__getstate__()
self.__setstate__(state)
def format(self, field_list=None, names=0):
if field_list is None:
try:
@ -435,7 +451,7 @@ class NodeInfoBase(object):
setattr(self, key, value)
class BuildInfoBase(object):
class BuildInfoBase:
"""
The generic base class for build information for a Node.
@ -498,13 +514,14 @@ class BuildInfoBase(object):
setattr(self, key, value)
class Node(object, with_metaclass(NoSlotsPyPy)):
class Node(object, metaclass=NoSlotsPyPy):
"""The base Node class, for entities that we know how to
build, or use to build other Nodes.
"""
__slots__ = ['sources',
'sources_set',
'target_peers',
'_specific_sources',
'depends',
'depends_set',
@ -545,7 +562,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
'_func_get_contents',
'_func_target_from_source']
class Attrs(object):
class Attrs:
__slots__ = ('shared', '__dict__')
@ -599,6 +616,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
self._func_rexists = 1
self._func_get_contents = 0
self._func_target_from_source = 0
self.ninfo = None
self.clear_memoized_values()
@ -665,7 +683,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
executor.cleanup()
def reset_executor(self):
"Remove cached executor; forces recompute when needed."
"""Remove cached executor; forces recompute when needed."""
try:
delattr(self, 'executor')
except AttributeError:
@ -760,6 +778,25 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
for parent in self.waiting_parents:
parent.implicit = None
# Handle issue where builder emits more than one target and
# the source file for the builder is generated.
# in that case only the first target was getting it's .implicit
# cleared when the source file is built (second scan).
# leaving only partial implicits from scan before source file is generated
# typically the compiler only. Then scanned files are appended
# This is persisted to sconsign and rebuild causes false rebuilds
# because the ordering of the implicit list then changes to what it
# should have been.
# This is at least the following bugs
# https://github.com/SCons/scons/issues/2811
# https://jira.mongodb.org/browse/SERVER-33111
try:
for peer in parent.target_peers:
peer.implicit = None
except AttributeError:
pass
self.clear()
if self.pseudo:
@ -912,6 +949,10 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
"""
return _is_derived_map[self._func_is_derived](self)
def is_sconscript(self):
""" Returns true if this node is an sconscript """
return self in SConscriptNodes
def alter_targets(self):
"""Return a list of alternate targets for this Node.
"""
@ -1097,9 +1138,8 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
return ninfo
def get_ninfo(self):
try:
if self.ninfo is not None:
return self.ninfo
except AttributeError:
self.ninfo = self.new_ninfo()
return self.ninfo
@ -1133,10 +1173,10 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
if self.has_builder():
binfo.bact = str(executor)
binfo.bactsig = SCons.Util.MD5signature(executor.get_contents())
binfo.bactsig = MD5signature(executor.get_contents())
if self._specific_sources:
sources = [ s for s in self.sources if not s in ignore_set]
sources = [s for s in self.sources if s not in ignore_set]
else:
sources = executor.get_unignored_sources(self, self.ignore)
@ -1145,13 +1185,17 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
binfo.bsources = [s for s in sources if s not in seen and not seen.add(s)]
binfo.bsourcesigs = [s.get_ninfo() for s in binfo.bsources]
binfo.bdepends = [d for d in self.depends if d not in ignore_set]
binfo.bdependsigs = [d.get_ninfo() for d in self.depends]
binfo.bdepends = self.depends
binfo.bdependsigs = [d.get_ninfo() for d in self.depends if d not in ignore_set]
binfo.bimplicit = self.implicit or []
binfo.bimplicitsigs = [i.get_ninfo() for i in binfo.bimplicit if i not in ignore_set]
# Because self.implicit is initialized to None (and not empty list [])
# we have to handle this case
if not self.implicit:
binfo.bimplicit = []
binfo.bimplicitsigs = []
else:
binfo.bimplicit = [i for i in self.implicit if i not in ignore_set]
binfo.bimplicitsigs = [i.get_ninfo() for i in binfo.bimplicit]
return binfo
@ -1167,7 +1211,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
return self.ninfo.csig
except AttributeError:
ninfo = self.get_ninfo()
ninfo.csig = SCons.Util.MD5signature(self.get_contents())
ninfo.csig = MD5signature(self.get_contents())
return self.ninfo.csig
def get_cachedir_csig(self):
@ -1213,7 +1257,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
return _exists_map[self._func_exists](self)
def rexists(self):
"""Does this node exist locally or in a repositiory?"""
"""Does this node exist locally or in a repository?"""
# There are no repositories by default:
return _rexists_map[self._func_rexists](self)
@ -1452,14 +1496,13 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
result = True
for child, prev_ni in zip(children, then):
if _decider_map[child.changed_since_last_build](child, self, prev_ni):
if _decider_map[child.changed_since_last_build](child, self, prev_ni, node):
if t: Trace(': %s changed' % child)
result = True
contents = self.get_executor().get_contents()
if self.has_builder():
import SCons.Util
newsig = SCons.Util.MD5signature(contents)
contents = self.get_executor().get_contents()
newsig = MD5signature(contents)
if bi.bactsig != newsig:
if t: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig))
result = True
@ -1607,29 +1650,39 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
# so we only print them after running them through this lambda
# to turn them into the right relative Node and then return
# its string.
def stringify( s, E=self.dir.Entry ) :
def stringify( s, E=self.dir.Entry):
if hasattr( s, 'dir' ) :
return str(E(s))
return str(s)
lines = []
removed = [x for x in old_bkids if not x in new_bkids]
removed = [x for x in old_bkids if x not in new_bkids]
if removed:
removed = list(map(stringify, removed))
removed = [stringify(r) for r in removed]
fmt = "`%s' is no longer a dependency\n"
lines.extend([fmt % s for s in removed])
for k in new_bkids:
if not k in old_bkids:
if k not in old_bkids:
lines.append("`%s' is a new dependency\n" % stringify(k))
elif _decider_map[k.changed_since_last_build](k, self, osig[k]):
else:
changed = _decider_map[k.changed_since_last_build](k, self, osig[k])
if changed:
lines.append("`%s' changed\n" % stringify(k))
if len(lines) == 0 and old_bkids != new_bkids:
lines.append("the dependency order changed:\n" +
"%sold: %s\n" % (' '*15, list(map(stringify, old_bkids))) +
"%snew: %s\n" % (' '*15, list(map(stringify, new_bkids))))
lines.append("the dependency order changed:\n")
lines.append("->Sources\n")
for (o,n) in zip_longest(old.bsources, new.bsources, fillvalue=None):
lines.append("Old:%s\tNew:%s\n"%(o,n))
lines.append("->Depends\n")
for (o,n) in zip_longest(old.bdepends, new.bdepends, fillvalue=None):
lines.append("Old:%s\tNew:%s\n"%(o,n))
lines.append("->Implicit\n")
for (o,n) in zip_longest(old.bimplicit, new.bimplicit, fillvalue=None):
lines.append("Old:%s\tNew:%s\n"%(o,n))
if len(lines) == 0:
def fmt_with_title(title, strlines):
@ -1666,13 +1719,12 @@ def get_children(node, parent): return node.children()
def ignore_cycle(node, stack): pass
def do_nothing(node, parent): pass
class Walker(object):
class Walker:
"""An iterator for walking a Node tree.
This is depth-first, children are visited before the parent.
The Walker object can be initialized with any node, and
returns the next node on the descent with each get_next() call.
'kids_func' is an optional function that will be called to
get the children of a node instead of calling 'children'.
'cycle_func' is an optional function that will be called
when a cycle is detected.

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -21,7 +21,7 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/PathList.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """SCons.PathList
@ -66,7 +66,7 @@ def node_conv(obj):
result = get()
return result
class _PathList(object):
class _PathList:
"""
An actual PathList object.
"""
@ -143,7 +143,7 @@ class _PathList(object):
return tuple(result)
class PathListCache(object):
class PathListCache:
"""
A class to handle caching of PathList lookups.

View file

@ -20,7 +20,7 @@ their own platform definition.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -41,13 +41,11 @@ their own platform definition.
# 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 __future__ import print_function
__revision__ = "src/engine/SCons/Platform/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.compat
import imp
import importlib
import os
import sys
import tempfile
@ -60,8 +58,8 @@ import SCons.Tool
def platform_default():
"""Return the platform string for our execution environment.
The returned value should map to one of the SCons/Platform/*.py
files. Since we're architecture independent, though, we don't
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
@ -87,6 +85,7 @@ def platform_default():
else:
return sys.platform
def platform_module(name = platform_default()):
"""Return the imported module for the platform.
@ -100,13 +99,8 @@ def platform_module(name = platform_default()):
eval(full_name)
else:
try:
file, path, desc = imp.find_module(name,
sys.modules['SCons.Platform'].__path__)
try:
mod = imp.load_module(full_name, file, path, desc)
finally:
if file:
file.close()
# the specific platform module is a relative import
mod = importlib.import_module("." + name, __name__)
except ImportError:
try:
import zipimport
@ -117,12 +111,14 @@ def platform_module(name = platform_default()):
setattr(SCons.Platform, name, mod)
return sys.modules[full_name]
def DefaultToolList(platform, env):
"""Select a default tool list for the specified platform.
"""
return SCons.Tool.tool_list(platform, env)
class PlatformSpec(object):
class PlatformSpec:
def __init__(self, name, generate):
self.name = name
self.generate = generate
@ -133,22 +129,35 @@ class PlatformSpec(object):
def __str__(self):
return self.name
class TempFileMunge(object):
"""A callable class. You can set an Environment variable to this,
then call it with a string argument, then it will perform temporary
file substitution on it. This is used to circumvent the long command
line limitation.
Example usage:
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 configred for other tool chains by
setting '$TEMPFILEPREFIX'.
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.
"""
def __init__(self, cmd, cmdstr = None):
self.cmd = cmd
@ -185,21 +194,27 @@ class TempFileMunge(object):
node = target[0] if SCons.Util.is_List(target) else target
cmdlist = getattr(node.attributes, 'tempfile_cmdlist', None) \
if node is not None else None
if cmdlist is not None :
if cmdlist is not None:
return cmdlist
# We do a normpath because mktemp() has what appears to be
# a bug in Windows that will use a forward slash as a path
# delimiter. Windows's link mistakes that for a command line
# switch and barfs.
#
# We use the .lnk suffix for the benefit of the Phar Lap
# 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.
(fd, tmp) = tempfile.mkstemp('.lnk', text=True)
native_tmp = SCons.Util.get_native_path(os.path.normpath(tmp))
if 'TEMPFILESUFFIX' in env:
suffix = env.subst('$TEMPFILESUFFIX')
else:
suffix = '.lnk'
if env.get('SHELL',None) == 'sh':
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, text=True)
native_tmp = SCons.Util.get_native_path(tmp)
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'\\\\')
@ -217,8 +232,10 @@ class TempFileMunge(object):
prefix = '@'
args = list(map(SCons.Subst.quote_spaces, cmd[1:]))
os.write(fd, bytearray(" ".join(args) + "\n",'utf-8'))
join_char = env.get('TEMPFILEARGJOIN',' ')
os.write(fd, bytearray(join_char.join(args) + "\n",'utf-8'))
os.close(fd)
# 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
@ -239,8 +256,9 @@ class TempFileMunge(object):
source) if self.cmdstr is not None else ''
# Print our message only if XXXCOMSTR returns an empty string
if len(cmdstr) == 0 :
print("Using tempfile "+native_tmp+" for command line:\n"+
cmdstr = ("Using tempfile "+native_tmp+" for command line:\n"+
str(cmd[0]) + " " + " ".join(args))
self._print_cmd_str(target, source, env, cmdstr)
# Store the temporary file command list into the target Node.attributes
# to avoid creating two temporary files one for print and one for execute.
@ -252,6 +270,23 @@ class TempFileMunge(object):
pass
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.

View file

@ -1,4 +1,4 @@
"""engine.SCons.Platform.aix
"""SCons.Platform.aix
Platform-specific initialization for IBM AIX systems.
@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Platform/aix.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import subprocess
@ -55,6 +55,7 @@ def get_xlc(env, xlc=None, packages=[]):
pipe = SCons.Action._subproc(env, ['lslpp', '-fc', package],
stdin = 'devnull',
stderr = 'devnull',
universal_newlines=True,
stdout = subprocess.PIPE)
# output of lslpp is something like this:
# #Path:Fileset:File

View file

@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,11 +30,20 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Platform/cygwin.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
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)

View file

@ -1,4 +1,4 @@
"""engine.SCons.Platform.darwin
"""SCons.Platform.darwin
Platform-specific initialization for Mac OS X systems.
@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Platform/darwin.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
from . import posix
import os
@ -56,12 +56,11 @@ def generate(env):
for file in filelist:
if os.path.isfile(file):
f = open(file, 'r')
with open(file, 'r') as f:
lines = f.readlines()
for line in lines:
if line:
env.AppendENVPath('PATHOSX', line.strip('\n'))
f.close()
# 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):

View file

@ -1,4 +1,4 @@
"""engine.SCons.Platform.hpux
"""SCons.Platform.hpux
Platform-specific initialization for HP-UX systems.
@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Platform/hpux.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
from . import posix

View file

@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Platform/irix.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
from . import posix

View file

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

View file

@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Platform/os2.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
from . import win32
def generate(env):

View file

@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Platform/posix.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import errno
import os
@ -41,6 +41,8 @@ import select
import SCons.Util
from SCons.Platform import TempFileMunge
from SCons.Platform.virtualenv import ImportVirtualenv
from SCons.Platform.virtualenv import ignore_virtualenv, enable_virtualenv
exitvalmap = {
2 : 127,
@ -48,7 +50,7 @@ exitvalmap = {
}
def escape(arg):
"escape shell special characters"
"""escape shell special characters"""
slash = '\\'
special = '"$'
@ -119,6 +121,9 @@ def generate(env):
# 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

View file

@ -1,4 +1,4 @@
"""engine.SCons.Platform.sunos
"""SCons.Platform.sunos
Platform-specific initialization for Sun systems.
@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Platform/sunos.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
from . import posix

View file

@ -0,0 +1,120 @@
"""SCons.Platform.virtualenv
Support for virtualenv.
"""
#
# __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 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()
virtualenv_variables = ['VIRTUAL_ENV', 'PIPENV_ACTIVE']
def _running_in_virtualenv():
"""Returns True, if scons is executed within a virtualenv"""
# see https://stackoverflow.com/a/42580137
return (hasattr(sys, 'real_prefix') or
(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix))
def _is_path_in(path, base):
"""Returns true, if **path** is located under the **base** directory."""
if not path or not base: # empty path may happen, base too
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):
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):
"""Modify environment such that SCons will take into account its virtualenv
when running external tools."""
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):
"""Returns a list of paths from **path_list** which are under virtualenv's
home directory."""
if SCons.Util.is_String(path_list):
path_list = path_list.split(os.path.pathsep)
# Find in path_list the paths under the virtualenv's home
return [path for path in path_list if IsInVirtualenv(path)]
def ImportVirtualenv(env):
"""Copies virtualenv-related environment variables from OS environment
to ``env['ENV']`` and prepends virtualenv's PATH to ``env['ENV']['PATH']``.
"""
_inject_venv_variables(env)
_inject_venv_path(env)
def Virtualenv():
"""Returns path to the virtualenv home if scons is executing within a
virtualenv or None, if not."""
if _running_in_virtualenv():
return sys.prefix
return None
def IsInVirtualenv(path):
"""Returns True, if **path** is under virtualenv's home directory. If not,
or if we don't use virtualenv, returns False."""
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

@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Platform/win32.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import os.path
@ -39,16 +39,18 @@ 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'
]
try:
import msvcrt
import win32api
import win32con
msvcrt.get_osfhandle
win32api.SetHandleInformation
win32con.HANDLE_FLAG_INHERIT
except ImportError:
parallel_msg = \
"you do not seem to have the pywin32 extensions installed;\n" + \
@ -60,38 +62,6 @@ except AttributeError:
else:
parallel_msg = None
_builtin_open = open
def _scons_open(*args, **kw):
fp = _builtin_open(*args, **kw)
win32api.SetHandleInformation(msvcrt.get_osfhandle(fp.fileno()),
win32con.HANDLE_FLAG_INHERIT,
0)
return fp
open = _scons_open
if sys.version_info.major == 2:
_builtin_file = file
class _scons_file(_builtin_file):
def __init__(self, *args, **kw):
_builtin_file.__init__(self, *args, **kw)
win32api.SetHandleInformation(msvcrt.get_osfhandle(self.fileno()),
win32con.HANDLE_FLAG_INHERIT, 0)
file = _scons_file
else:
import io
for io_class in ['BufferedReader', 'BufferedWriter', 'BufferedRWPair',
'BufferedRandom', 'TextIOWrapper']:
_builtin_file = getattr(io, io_class)
class _scons_file(_builtin_file):
def __init__(self, *args, **kw):
_builtin_file.__init__(self, *args, **kw)
win32api.SetHandleInformation(msvcrt.get_osfhandle(self.fileno()),
win32con.HANDLE_FLAG_INHERIT, 0)
setattr(io, io_class, _scons_file)
if False:
# Now swap out shutil.filecopy and filecopy2 for win32 api native CopyFile
@ -132,7 +102,7 @@ try:
# Without this, python can randomly crash while using -jN.
# See the python bug at http://bugs.python.org/issue6476
# and SCons issue at
# http://scons.tigris.org/issues/show_bug.cgi?id=2449
# https://github.com/SCons/scons/issues/2449
def spawnve(mode, file, args, env):
spawn_lock.acquire()
try:
@ -168,58 +138,67 @@ def piped_spawn(sh, escape, cmd, args, env, stdout, stderr):
# 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
else:
# one temporary file for stdout and stderr
tmpFileStdout = os.path.normpath(tempfile.mktemp())
tmpFileStderr = os.path.normpath(tempfile.mktemp())
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 = 0
stderrRedirected = 0
stdoutRedirected = False
stderrRedirected = False
for arg in args:
# are there more possibilities to redirect stdout ?
if arg.find( ">", 0, 1 ) != -1 or arg.find( "1>", 0, 2 ) != -1:
stdoutRedirected = 1
if arg.find(">", 0, 1) != -1 or arg.find("1>", 0, 2) != -1:
stdoutRedirected = True
# are there more possibilities to redirect stderr ?
if arg.find( "2>", 0, 2 ) != -1:
stderrRedirected = 1
if arg.find("2>", 0, 2) != -1:
stderrRedirected = True
# redirect output of non-redirected streams to our tempfiles
if stdoutRedirected == 0:
args.append(">" + str(tmpFileStdout))
if stderrRedirected == 0:
args.append("2>" + str(tmpFileStderr))
if not stdoutRedirected:
args.append(">" + tmpFileStdoutName)
if not stderrRedirected:
args.append("2>" + tmpFileStderrName)
# actually do the spawn
try:
args = [sh, '/C', escape(' '.join(args)) ]
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[0]]
ret = exitvalmap[e.errno]
except KeyError:
sys.stderr.write("scons: unknown OSError exception code %d - %s: %s\n" % (e[0], cmd, e[1]))
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[1]))
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 stdoutRedirected == 0:
if stdout is not None and not stdoutRedirected:
try:
stdout.write(open( tmpFileStdout, "r" ).read())
os.remove( tmpFileStdout )
with open(tmpFileStdoutName, "r") as tmpFileStdout:
stdout.write(tmpFileStdout.read())
os.remove(tmpFileStdoutName)
except (IOError, OSError):
pass
if stderr is not None and stderrRedirected == 0:
if stderr is not None and not stderrRedirected:
try:
stderr.write(open( tmpFileStderr, "r" ).read())
os.remove( tmpFileStderr )
with open(tmpFileStderrName, "r") as tmpFileStderr:
stderr.write(tmpFileStderr.read())
os.remove(tmpFileStderrName)
except (IOError, OSError):
pass
return ret
@ -288,9 +267,6 @@ def get_system_root():
except:
pass
# Ensure system root is a string and not unicode
# (This only matters for py27 were unicode in env passed to POpen fails)
val = str(val)
_system_root = val
return val
@ -322,7 +298,7 @@ def get_program_files_dir():
return val
class ArchDefinition(object):
class ArchDefinition:
"""
Determine which windows CPU were running on.
A class for defining architecture-specific settings and logic.
@ -436,7 +412,7 @@ def generate(env):
if v:
env['ENV']['COMSPEC'] = v
env.AppendENVPath('PATH', get_system_root() + '\System32')
env.AppendENVPath('PATH', get_system_root() + '\\System32')
env['ENV']['PATHEXT'] = '.COM;.EXE;.BAT;.CMD'
env['OBJPREFIX'] = ''
@ -462,6 +438,9 @@ def generate(env):
env['HOST_OS'] = 'win32'
env['HOST_ARCH'] = get_architecture().arch
if enable_virtualenv and not ignore_virtualenv:
ImportVirtualenv(env)
# Local Variables:
# tab-width:4

View file

@ -12,7 +12,7 @@ libraries are installed, if some command line options are supported etc.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -33,12 +33,11 @@ libraries are installed, if some command line options are supported etc.
# 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 __future__ import print_function
__revision__ = "src/engine/SCons/SConf.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.compat
import atexit
import io
import os
import re
@ -56,6 +55,7 @@ import SCons.Warnings
import SCons.Conftest
from SCons.Debug import Trace
from collections import defaultdict
# Turn off the Conftest error logging
SCons.Conftest.LogInputFiles = 0
@ -65,9 +65,9 @@ SCons.Conftest.LogErrorMessages = 0
build_type = None
build_types = ['clean', 'help']
def SetBuildType(type):
def SetBuildType(buildtype):
global build_type
build_type = type
build_type = buildtype
# to be set, if we are in dry-run mode
dryrun = 0
@ -98,7 +98,7 @@ def SetProgressDisplay(display):
SConfFS = None
_ac_build_counter = 0 # incremented, whenever TryBuild is called
_ac_build_counter = defaultdict(int)
_ac_config_logs = {} # all config.log files created in this build
_ac_config_hs = {} # all config.h files created in this build
sconf_global = None # current sconf object
@ -132,8 +132,8 @@ def CreateConfigHBuilder(env):
_stringConfigH)
sconfigHBld = SCons.Builder.Builder(action=action)
env.Append( BUILDERS={'SConfigHBuilder':sconfigHBld} )
for k in list(_ac_config_hs.keys()):
env.SConfigHBuilder(k, env.Value(_ac_config_hs[k]))
for k, v in _ac_config_hs.items():
env.SConfigHBuilder(k, env.Value(v))
class SConfWarning(SCons.Warnings.Warning):
@ -161,11 +161,14 @@ class ConfigureCacheError(SConfError):
def __init__(self,target):
SConfError.__init__(self, '"%s" is not yet built and cache is forced.' % str(target))
# define actions for building text files
def _createSource( target, source, env ):
def _createSource(target, source, env):
fd = open(str(target[0]), "w")
fd.write(source[0].get_contents().decode())
fd.close()
def _stringSource( target, source, env ):
return (str(target[0]) + ' <-\n |' +
source[0].get_contents().decode().replace( '\n', "\n |" ) )
@ -187,7 +190,7 @@ class SConfBuildInfo(SCons.Node.FS.FileBuildInfo):
self.string = string
class Streamer(object):
class Streamer:
"""
'Sniffer' for a file-like writable object. Similar to the unix tool tee.
"""
@ -246,6 +249,7 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
# ConfigureCacheError and if yes, reraise the exception
exc_type = self.exc_info()[0]
if issubclass(exc_type, SConfError):
# TODO pylint E0704: bare raise not inside except
raise
elif issubclass(exc_type, SCons.Errors.BuildError):
# we ignore Build Errors (occurs, when a test doesn't pass)
@ -269,7 +273,7 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
cached_error = False
cachable = True
for t in self.targets:
if T: Trace('%s' % (t))
if T: Trace('%s' % t)
bi = t.get_stored_info().binfo
if isinstance(bi, SConfBuildInfo):
if T: Trace(': SConfBuildInfo')
@ -279,7 +283,7 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
else:
if T: Trace(': get_state() %s' % t.get_state())
if T: Trace(': changed() %s' % t.changed())
if (t.get_state() != SCons.Node.up_to_date and t.changed()):
if t.get_state() != SCons.Node.up_to_date and t.changed():
changed = True
if T: Trace(': changed %s' % changed)
cached_error = cached_error or bi.result
@ -323,18 +327,6 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
s = sys.stdout = sys.stderr = Streamer(sys.stdout)
try:
env = self.targets[0].get_build_env()
if cache_mode == FORCE:
# Set up the Decider() to force rebuilds by saying
# that every source has changed. Note that we still
# call the environment's underlying source decider so
# that the correct .sconsign info will get calculated
# and keep the build state consistent.
def force_build(dependency, target, prev_ni,
env_decider=env.decide_source):
env_decider(dependency, target, prev_ni)
return True
if env.decide_source.__code__ is not force_build.__code__:
env.Decider(force_build)
env['PSTDOUT'] = env['PSTDERR'] = s
try:
sconf.cached = 0
@ -383,7 +375,7 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
sconsign.set_entry(t.name, sconsign_entry)
sconsign.merge()
class SConfBase(object):
class SConfBase:
"""This is simply a class to represent a configure context. After
creating a SConf object, you can call any tests. After finished with your
tests, be sure to call the Finish() method, which returns the modified
@ -405,12 +397,41 @@ class SConfBase(object):
build tests in the VariantDir, not in the SourceDir)
"""
global SConfFS
# Now create isolated override so setting source_decider doesn't affect parent Environment
if cache_mode == FORCE:
self.original_env = env
self.env = env.Clone()
# Set up the Decider() to force rebuilds by saying
# that every source has changed. Note that we still
# call the environment's underlying source decider so
# that the correct .sconsign info will get calculated
# and keep the build state consistent.
def force_build(dependency, target, prev_ni,
repo_node=None,
env_decider=env.decide_source):
try:
env_decider(dependency, target, prev_ni, repo_node)
except Exception as e:
raise e
return True
if self.env.decide_source.__code__ is not force_build.__code__:
self.env.Decider(force_build)
else:
self.env = env
# print("Override env:%s"%env)
if not SConfFS:
SConfFS = SCons.Node.FS.default_fs or \
SCons.Node.FS.FS(env.fs.pathTop)
if sconf_global is not None:
raise SCons.Errors.UserError
self.env = env
raise SCons.Errors.UserError("""Configure() called while another Configure() exists.
Please call .Finish() before creating and second Configure() context""")
if log_file is not None:
log_file = SConfFS.File(env.subst(log_file))
self.logfile = log_file
@ -449,6 +470,7 @@ class SConfBase(object):
env = sconf.Finish()
"""
self._shutdown()
return self.env
def Define(self, name, value = None, comment = None):
@ -503,6 +525,20 @@ class SConfBase(object):
n.attributes = SCons.Node.Node.Attrs()
n.attributes.keep_targetinfo = 1
if True:
# Some checkers have intermediate files (for example anything that compiles a c file into a program to run
# Those files need to be set to not release their target info, otherwise taskmaster will throw a
# Nonetype not callable
for c in n.children(scan=False):
# Keep debug code here.
# print("Checking [%s] for builders and then setting keep_targetinfo"%c)
if c.has_builder():
n.store_info = 0
if not hasattr(c, 'attributes'):
c.attributes = SCons.Node.Node.Attrs()
c.attributes.keep_targetinfo = 1
# pass
ret = 1
try:
@ -541,8 +577,7 @@ class SConfBase(object):
"""
return self.pspawn(sh, escape, cmd, args, env, self.logstream, self.logstream)
def TryBuild(self, builder, text = None, extension = ""):
def TryBuild(self, builder, text=None, extension=""):
"""Low level TryBuild implementation. Normally you don't need to
call that - you can use TryCompile / TryLink / TryRun instead
"""
@ -560,8 +595,30 @@ class SConfBase(object):
raise SCons.Errors.UserError('Missing SPAWN construction variable.')
nodesToBeBuilt = []
sourcetext = self.env.Value(text)
f = "conftest"
if text is not None:
textSig = SCons.Util.MD5signature(sourcetext)
textSigCounter = str(_ac_build_counter[textSig])
_ac_build_counter[textSig] += 1
f = "_".join([f, textSig, textSigCounter])
textFile = self.confdir.File(f + extension)
textFileNode = self.env.SConfSourceBuilder(target=textFile,
source=sourcetext)
nodesToBeBuilt.extend(textFileNode)
source = textFile
target = textFile.File(f + "SConfActionsContentDummyTarget")
else:
source = None
target = None
action = builder.builder.action.get_contents(target=target, source=[source], env=self.env)
actionsig = SCons.Util.MD5signature(action)
f = "_".join([f, actionsig])
f = "conftest_" + str(_ac_build_counter)
pref = self.env.subst( builder.builder.prefix )
suff = self.env.subst( builder.builder.suffix )
target = self.confdir.File(pref + f + suff)
@ -570,16 +627,6 @@ class SConfBase(object):
# Slide our wrapper into the construction environment as
# the SPAWN function.
self.env['SPAWN'] = self.pspawn_wrapper
sourcetext = self.env.Value(text)
if text is not None:
textFile = self.confdir.File(f + extension)
textFileNode = self.env.SConfSourceBuilder(target=textFile,
source=sourcetext)
nodesToBeBuilt.extend(textFileNode)
source = textFileNode
else:
source = None
nodes = builder(target = target, source = source)
if not SCons.Util.is_List(nodes):
@ -590,7 +637,6 @@ class SConfBase(object):
finally:
self.env['SPAWN'] = save_spawn
_ac_build_counter = _ac_build_counter + 1
if result:
self.lastTarget = nodes[0]
else:
@ -609,7 +655,7 @@ class SConfBase(object):
ok = self.TryBuild(self.env.SConfActionBuilder, text, extension)
del self.env['BUILDERS']['SConfActionBuilder']
if ok:
outputStr = self.lastTarget.get_contents().decode()
outputStr = self.lastTarget.get_text_contents()
return (1, outputStr)
return (0, "")
@ -636,7 +682,7 @@ class SConfBase(object):
is saved in self.lastTarget (for further processing).
"""
ok = self.TryLink(text, extension)
if( ok ):
if ok:
prog = self.lastTarget
pname = prog.get_internal_path()
output = self.confdir.File(os.path.basename(pname)+'.out')
@ -647,7 +693,7 @@ class SConfBase(object):
return( 1, outputStr)
return (0, "")
class TestWrapper(object):
class TestWrapper:
"""A wrapper around Tests (to ensure sanity)"""
def __init__(self, test, sconf):
self.test = test
@ -671,7 +717,7 @@ class SConfBase(object):
"""Adds all the tests given in the tests dictionary to this SConf
instance
"""
for name in list(tests.keys()):
for name in tests.keys():
self.AddTest(name, tests[name])
def _createDir( self, node ):
@ -705,11 +751,16 @@ class SConfBase(object):
_ac_config_logs[self.logfile] = None
log_mode = "w"
fp = open(str(self.logfile), log_mode)
def conflog_cleanup(logf):
logf.close()
atexit.register(conflog_cleanup, fp)
self.logstream = SCons.Util.Unbuffered(fp)
# logfile may stay in a build directory, so we tell
# the build system not to override it with a eventually
# the build system not to override it with an eventually
# existing file with the same name in the source directory
self.logfile.dir.add_ignore( [self.logfile] )
self.logfile.dir.add_ignore([self.logfile])
tb = traceback.extract_stack()[-3-self.depth]
old_fs_dir = SConfFS.getcwd()
@ -739,17 +790,25 @@ class SConfBase(object):
self.logstream.write("\n")
self.logstream.close()
self.logstream = None
# Now reset the decider if we changed it due to --config=force
# We saved original Environment passed in and cloned it to isolate
# it from being changed.
if cache_mode == FORCE:
self.env.Decider(self.original_env.decide_source)
# remove the SConfSourceBuilder from the environment
blds = self.env['BUILDERS']
del blds['SConfSourceBuilder']
self.env.Replace( BUILDERS=blds )
self.active = 0
sconf_global = None
if not self.config_h is None:
if self.config_h is not None:
_ac_config_hs[self.config_h] = self.config_h_text
self.env.fs = self.lastEnvFs
class CheckContext(object):
class CheckContext:
"""Provides a context for configure tests. Defines how a test writes to the
screen and log file.
@ -826,9 +885,9 @@ class CheckContext(object):
return self.sconf.TryRun(*args, **kw)
def __getattr__( self, attr ):
if( attr == 'env' ):
if attr == 'env':
return self.sconf.env
elif( attr == 'lastTarget' ):
elif attr == 'lastTarget':
return self.sconf.lastTarget
else:
raise AttributeError("CheckContext instance has no attribute '%s'" % attr)
@ -1000,7 +1059,7 @@ def CheckLib(context, library = None, symbol = "main",
compiles without flags.
"""
if library == []:
if not library:
library = [None]
if not SCons.Util.is_List(library):
@ -1027,7 +1086,7 @@ def CheckLibWithHeader(context, libs, header, language,
"""
prog_prefix, dummy = \
createIncludesFromHeaders(header, 0)
if libs == []:
if not libs:
libs = [None]
if not SCons.Util.is_List(libs):

View file

@ -5,7 +5,7 @@ Writing and reading information to the .sconsign file or files.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -26,10 +26,7 @@ Writing and reading information to the .sconsign file or files.
# 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 __future__ import print_function
__revision__ = "src/engine/SCons/SConsign.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.compat
@ -76,7 +73,8 @@ def Get_DataBase(dir):
except KeyError:
path = d.entry_abspath(DB_Name)
try: db = DataBase[d] = DB_Module.open(path, mode)
except (IOError, OSError): pass
except (IOError, OSError):
pass
else:
if mode != "r":
DB_sync_list.append(db)
@ -122,7 +120,7 @@ def write():
closemethod()
class SConsignEntry(object):
class SConsignEntry:
"""
Wrapper class for the generic entry in a .sconsign file.
The Node subclass populates it with attributes as it pleases.
@ -165,7 +163,7 @@ class SConsignEntry(object):
setattr(self, key, value)
class Base(object):
class Base:
"""
This is the controlling class for the signatures for the collection of
entries associated with a specific directory. The actual directory
@ -334,10 +332,15 @@ class DirFile(Dir):
Dir.__init__(self, fp, dir)
except KeyboardInterrupt:
raise
except:
except Exception:
SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
"Ignoring corrupt .sconsign file: %s"%self.sconsign)
try:
fp.close()
except AttributeError:
pass
global sig_files
sig_files.append(self)
@ -394,7 +397,8 @@ class DirFile(Dir):
# here, or in any of the following calls, would get
# raised, indicating something like a potentially
# serious disk or network issue.
open(self.sconsign, 'wb').write(open(fname, 'rb').read())
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)
@ -416,7 +420,7 @@ def File(name, dbm_module=None):
else:
ForDirectory = DB
DB_Name = name
if not dbm_module is None:
if dbm_module is not None:
DB_Module = dbm_module
# Local Variables:

View file

@ -5,7 +5,7 @@ This module implements the dependency scanner for C/C++ code.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -27,7 +27,7 @@ This module implements the dependency scanner for C/C++ code.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Scanner/C.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Node.FS
import SCons.Scanner
@ -80,7 +80,7 @@ def dictify_CPPDEFINES(env):
return {cppdefines : None}
return cppdefines
class SConsCPPScannerWrapper(object):
class SConsCPPScannerWrapper:
"""
The SCons wrapper around a cpp.py scanner.
@ -124,6 +124,100 @@ def CScanner():
'^[ \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, **kw):
SCons.cpp.PreProcessor.__init__(self, *args, **kw)
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:
with open(str(file.rfile())) as fp:
return fp.read()
except EnvironmentError:
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 = SCons.Scanner.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

View file

@ -8,7 +8,7 @@ Coded by Andy Friesen
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ Coded by Andy Friesen
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Scanner/D.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Scanner
@ -46,7 +46,7 @@ class D(SCons.Scanner.Classic):
name = "DScanner",
suffixes = '$DSUFFIXES',
path_variable = 'DPATH',
regex = '(?:import\s+)([\w\s=,.]+)(?:\s*:[\s\w,=]+)?(?:;)'
regex = r'(?:import\s+)([\w\s=,.]+)(?:\s*:[\s\w,=]+)?(?:;)'
)
def find_include(self, include, source_dir, path):

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -20,7 +20,7 @@
# 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__ = "src/engine/SCons/Scanner/Dir.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Node.FS
import SCons.Scanner

View file

@ -5,7 +5,7 @@ This module implements the dependency scanner for Fortran code.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -26,7 +26,7 @@ This module implements the dependency scanner for Fortran code.
# 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__ = "src/engine/SCons/Scanner/Fortran.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import re
@ -78,7 +78,7 @@ class F90Scanner(SCons.Scanner.Classic):
def scan(self, node, env, path=()):
# cache the includes list in node so we only scan it once:
if node.includes != None:
if node.includes is not None:
mods_and_includes = node.includes
else:
# retrieve all included filenames
@ -187,7 +187,7 @@ def FortranScan(path_variable="FORTRANPATH"):
# (\w+) : match the module name that is being USE'd
#
#
use_regex = "(?i)(?:^|;)\s*USE(?:\s+|(?:(?:\s*,\s*(?:NON_)?INTRINSIC)?\s*::))\s*(\w+)"
use_regex = r"(?i)(?:^|;)\s*USE(?:\s+|(?:(?:\s*,\s*(?:NON_)?INTRINSIC)?\s*::))\s*(\w+)"
# The INCLUDE statement regex matches the following:
@ -275,7 +275,7 @@ def FortranScan(path_variable="FORTRANPATH"):
# set of semicolon-separated INCLUDE statements
# (as allowed by the F2003 standard)
include_regex = """(?i)(?:^|['">]\s*;)\s*INCLUDE\s+(?:\w+_)?[<"'](.+?)(?=["'>])"""
include_regex = r"""(?i)(?:^|['">]\s*;)\s*INCLUDE\s+(?:\w+_)?[<"'](.+?)(?=["'>])"""
# The MODULE statement regex finds module definitions by matching
# the following:
@ -285,21 +285,29 @@ def FortranScan(path_variable="FORTRANPATH"):
# but *not* the following:
#
# MODULE PROCEDURE procedure_name
# MODULE SUBROUTINE subroutine_name
# MODULE FUNCTION function_name
# MODULE PURE SUBROUTINE|FUNCTION subroutine_name|function_name
# MODULE ELEMENTAL SUBROUTINE|FUNCTION subroutine_name|function_name
#
# Here is a breakdown of the regex:
#
# (?i) : regex is case insensitive
# ^\s* : any amount of white space
# MODULE : match the string MODULE, case insensitive
# \s+ : match one or more white space characters
# (?!PROCEDURE) : but *don't* match if the next word matches
# PROCEDURE (negative lookahead assertion),
# case insensitive
# (\w+) : match one or more alphanumeric characters
# that make up the defined module name and
# save it in a group
# MODULE : match the string MODULE, case
# insensitive
# \s+ : match one or more white space
# characters
# (?!PROCEDURE|SUBROUTINE|FUNCTION|PURE|ELEMENTAL)
# : but *don't* match if the next word
# matches PROCEDURE, SUBROUTINE,
# FUNCTION, PURE or ELEMENTAL (negative
# lookahead assertion), case insensitive
# (\w+) : match one or more alphanumeric
# characters that make up the defined
# module name and save it in a group
def_regex = """(?i)^\s*MODULE\s+(?!PROCEDURE)(\w+)"""
def_regex = r"""(?i)^\s*MODULE\s+(?!PROCEDURE|SUBROUTINE|FUNCTION|PURE|ELEMENTAL)(\w+)"""
scanner = F90Scanner("FortranScan",
"$FORTRANSUFFIXES",

View file

@ -6,7 +6,7 @@ Definition Language) files.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -28,7 +28,7 @@ Definition Language) files.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Scanner/IDL.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Node.FS
import SCons.Scanner

View file

@ -5,7 +5,7 @@ This module implements the dependency scanner for LaTeX code.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -27,7 +27,7 @@ This module implements the dependency scanner for LaTeX code.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Scanner/LaTeX.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os.path
import re
@ -42,7 +42,7 @@ LatexGraphics = [ '.png', '.jpg', '.gif', '.tif']
# Used as a return value of modify_env_var if the variable is not set.
class _Null(object):
class _Null:
pass
_null = _Null
@ -77,7 +77,7 @@ def modify_env_var(env, var, abspath):
return save
class FindENVPathDirs(object):
class FindENVPathDirs:
"""
A class to bind a specific E{*}PATH variable name to a function that
will return all of the E{*}path directories.
@ -179,15 +179,7 @@ class LaTeX(SCons.Scanner.Base):
'inputfrom', 'subinputfrom']
def __init__(self, name, suffixes, graphics_extensions, *args, **kw):
# We have to include \n with the % we exclude from the first part
# part of the regex because the expression is compiled with re.M.
# Without the \n, the ^ could match the beginning of a *previous*
# line followed by one or more newline characters (i.e. blank
# lines), interfering with a match on the next line.
# add option for whitespace before the '[options]' or the '{filename}'
regex = r'''
^[^%\n]*
\\(
include
| includegraphics(?:\s*\[[^\]]+\])?
@ -219,7 +211,7 @@ class LaTeX(SCons.Scanner.Base):
return []
return self.scan_recurse(node, path)
class FindMultiPathDirs(object):
class FindMultiPathDirs:
"""The stock FindPathDirs function has the wrong granularity:
it is called once per target, while we need the path that depends
on what kind of included files is being searched. This wrapper
@ -247,7 +239,7 @@ class LaTeX(SCons.Scanner.Base):
# To prevent "dict is not hashable error"
return tuple(di.items())
class LaTeXScanCheck(object):
class LaTeXScanCheck:
"""Skip all but LaTeX source files, i.e., do not scan *.eps,
*.pdf, *.jpg, etc.
"""
@ -333,7 +325,7 @@ class LaTeX(SCons.Scanner.Base):
line_continues_a_comment = False
for line in text.splitlines():
line,comment = self.comment_re.findall(line)[0]
if line_continues_a_comment == True:
if line_continues_a_comment:
out[-1] = out[-1] + line.lstrip()
else:
out.append(line)
@ -348,8 +340,8 @@ class LaTeX(SCons.Scanner.Base):
# Cache the includes list in node so we only scan it once:
# path_dict = dict(list(path))
# add option for whitespace (\s) before the '['
noopt_cre = re.compile('\s*\[.*$')
if node.includes != None:
noopt_cre = re.compile(r'\s*\[.*$')
if node.includes is not None:
includes = node.includes
else:
text = self.canonical_text(node.get_text_contents())
@ -372,9 +364,9 @@ class LaTeX(SCons.Scanner.Base):
inc_list = include[2].split(',')
else:
inc_list = include[1].split(',')
for j in range(len(inc_list)):
split_includes.append( (inc_type, inc_subdir, inc_list[j]) )
#
for inc in inc_list:
split_includes.append((inc_type, inc_subdir, inc))
includes = split_includes
node.includes = includes

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -21,7 +21,7 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Scanner/Prog.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Node
import SCons.Node.FS

View file

@ -0,0 +1,171 @@
"""SCons.Scanner.Python
This module implements the dependency scanner for Python code.
One important note about the design is that this does not take any dependencies
upon packages or binaries in the Python installation unless they are listed in
PYTHONPATH. To do otherwise would have required code to determine where the
Python installation is, which is outside of the scope of a scanner like this.
If consumers want to pick up dependencies upon these packages, they must put
those directories in PYTHONPATH.
"""
#
# __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 itertools
import os
import re
import SCons.Scanner
# Capture python "from a import b" and "import a" statements.
from_cre = re.compile(r'^\s*from\s+([^\s]+)\s+import\s+(.*)', re.M)
import_cre = re.compile(r'^\s*import\s+([^\s]+)', re.M)
def path_function(env, dir=None, target=None, source=None, argument=None):
"""Retrieves a tuple with all search paths."""
paths = env['ENV'].get('PYTHONPATH', '').split(os.pathsep)
if source:
paths.append(source[0].dir.abspath)
return tuple(paths)
def find_include_names(node):
"""
Scans the node for all imports.
Returns a list of tuples. Each tuple has two elements:
1. The main import (e.g. module, module.file, module.module2)
2. Additional optional imports that could be functions or files
in the case of a "from X import Y" statement. In the case of a
normal "import" statement, this is None.
"""
text = node.get_text_contents()
all_matches = []
matches = from_cre.findall(text)
if matches:
for match in matches:
imports = [i.strip() for i in match[1].split(',')]
# Add some custom logic to strip out "as" because the regex
# includes it.
last_import_split = imports[-1].split()
if len(last_import_split) > 1:
imports[-1] = last_import_split[0]
all_matches.append((match[0], imports))
matches = import_cre.findall(text)
if matches:
for match in matches:
all_matches.append((match, None))
return all_matches
def scan(node, env, path=()):
# cache the includes list in node so we only scan it once:
if node.includes is not None:
includes = node.includes
else:
includes = 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))
# XXX TODO: Sort?
nodes = []
if callable(path):
path = path()
for module, imports in includes:
is_relative = module.startswith('.')
if is_relative:
# This is a relative include, so we must ignore PYTHONPATH.
module_lstripped = module.lstrip('.')
# One dot is current directory, two is parent, three is
# grandparent, etc.
num_parents = len(module) - len(module_lstripped) - 1
current_dir = node.get_dir()
for i in itertools.repeat(None, num_parents):
current_dir = current_dir.up()
search_paths = [current_dir.abspath]
search_string = module_lstripped
else:
search_paths = path
search_string = module
module_components = search_string.split('.')
for search_path in search_paths:
candidate_path = os.path.join(search_path, *module_components)
# The import stored in "module" could refer to a directory or file.
import_dirs = []
if os.path.isdir(candidate_path):
import_dirs = module_components
# Because this resolved to a directory, there is a chance that
# additional imports (e.g. from module import A, B) could refer
# to files to import.
if imports:
for imp in imports:
file = os.path.join(candidate_path, imp + '.py')
if os.path.isfile(file):
nodes.append(file)
elif os.path.isfile(candidate_path + '.py'):
nodes.append(candidate_path + '.py')
import_dirs = module_components[:-1]
# We can ignore imports because this resolved to a file. Any
# additional imports (e.g. from module.file import A, B) would
# only refer to functions in this file.
# Take a dependency on all __init__.py files from all imported
# packages unless it's a relative import. If it's a relative
# import, we don't need to take the dependency because Python
# requires that all referenced packages have already been imported,
# which means that the dependency has already been established.
if import_dirs and not is_relative:
for i in range(len(import_dirs)):
init_components = module_components[:i+1] + ['__init__.py']
init_path = os.path.join(search_path, *(init_components))
if os.path.isfile(init_path):
nodes.append(init_path)
break
return sorted(nodes)
PythonSuffixes = ['.py']
PythonScanner = SCons.Scanner.Base(scan, name='PythonScanner',
skeys=PythonSuffixes,
path_function=path_function, recursive=1)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View file

@ -6,7 +6,7 @@ Definition Language) files.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -28,7 +28,7 @@ Definition Language) files.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Scanner/RC.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import re
@ -48,9 +48,9 @@ def RCScan():
"""Return a prototype Scanner instance for scanning RC source files"""
res_re= r'^(?:\s*#\s*(?:include)|' \
'.*?\s+(?:ICON|BITMAP|CURSOR|HTML|FONT|MESSAGETABLE|TYPELIB|REGISTRY|D3DFX)' \
'\s*.*?)' \
'\s*(<|"| )([^>"\s]+)(?:[>"\s])*$'
r'.*?\s+(?:ICON|BITMAP|CURSOR|HTML|FONT|MESSAGETABLE|TYPELIB|REGISTRY|D3DFX)' \
r'\s*.*?)' \
r'\s*(<|"| )([^>"\s]+)(?:[>"\s])*$'
resScanner = SCons.Scanner.ClassicCPP("ResourceScanner",
"$RCSUFFIXES",
"CPPPATH",

View file

@ -5,7 +5,7 @@ This module implements the dependency scanner for SWIG code.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -27,14 +27,14 @@ This module implements the dependency scanner for SWIG code.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Scanner/SWIG.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Scanner
SWIGSuffixes = [ '.i' ]
def SWIGScanner():
expr = '^[ \t]*%[ \t]*(?:include|import|extern)[ \t]*(<|"?)([^>\s"]+)(?:>|"?)'
expr = r'^[ \t]*%[ \t]*(?:include|import|extern)[ \t]*(<|"?)([^>\s"]+)(?:>|"?)'
scanner = SCons.Scanner.ClassicCPP("SWIGScanner", ".i", "SWIGPATH", expr)
return scanner

View file

@ -5,7 +5,7 @@ The Scanner package for the SCons software construction utility.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -27,7 +27,7 @@ The Scanner package for the SCons software construction utility.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Scanner/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import re
@ -35,7 +35,7 @@ import SCons.Node.FS
import SCons.Util
class _Null(object):
class _Null:
pass
# This is used instead of None as a default argument value so None can be
@ -61,7 +61,7 @@ def Scanner(function, *args, **kw):
class FindPathDirs(object):
class FindPathDirs:
"""
A class to bind a specific E{*}PATH variable name to a function that
will return all of the E{*}path directories.
@ -81,7 +81,7 @@ class FindPathDirs(object):
class Base(object):
class Base:
"""
The base class for dependency scanners. This implements
straightforward, single-pass scanning of a single file.
@ -207,7 +207,7 @@ class Base(object):
self = self.select(node)
if not self.argument is _null:
if self.argument is not _null:
node_list = self.function(node, env, path, self.argument)
else:
node_list = self.function(node, env, path)

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -19,9 +19,7 @@
# 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 __future__ import print_function
__revision__ = "src/engine/SCons/Script/Interactive.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """
SCons interactive mode
@ -247,7 +245,7 @@ version Prints SCons version information.
while n:
n = walker.get_next()
for node in list(seen_nodes.keys()):
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

View file

@ -10,14 +10,11 @@ some other module. If it's specific to the "scons" script invocation,
it goes here.
"""
from __future__ import print_function
unsupported_python_version = (3, 4, 0)
deprecated_python_version = (3, 4, 0)
unsupported_python_version = (2, 6, 0)
deprecated_python_version = (2, 7, 0)
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -38,16 +35,20 @@ deprecated_python_version = (2, 7, 0)
# 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__ = "src/engine/SCons/Script/Main.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.compat
import atexit
import importlib.util
import os
import re
import sys
import time
import traceback
import sysconfig
import platform
import SCons.CacheDir
import SCons.Debug
@ -58,14 +59,29 @@ import SCons.Job
import SCons.Node
import SCons.Node.FS
import SCons.Platform
import SCons.Platform.virtualenv
import SCons.SConf
import SCons.Script
import SCons.Taskmaster
import SCons.Util
import SCons.Warnings
import SCons.Script.Interactive
# Global variables
first_command_start = None
last_command_end = None
print_objects = 0
print_memoizer = 0
print_stacktrace = 0
print_time = 0
print_action_timestamps = 0
sconscript_time = 0
cumulative_command_time = 0
exit_status = 0 # final exit status, assume success by default
this_build_status = 0 # "exit status" of an individual build
num_jobs = None
delayed_warnings = []
def fetch_win32_parallel_msg():
# A subsidiary function that exists solely to isolate this import
@ -85,17 +101,16 @@ def revert_io():
sys.stderr = sys.__stderr__
sys.stdout = sys.__stdout__
class SConsPrintHelpException(Exception):
pass
display = SCons.Util.display
progress_display = SCons.Util.DisplayEngine()
first_command_start = None
last_command_end = None
class Progressor(object):
class Progressor:
prev = ''
count = 0
target_string = '$TARGET'
@ -194,6 +209,9 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask):
finish_time = time.time()
last_command_end = finish_time
cumulative_command_time = cumulative_command_time+finish_time-start_time
if print_action_timestamps:
sys.stdout.write("Command execution start timestamp: %s: %f\n"%(str(self.node), start_time))
sys.stdout.write("Command execution end timestamp: %s: %f\n"%(str(self.node), finish_time))
sys.stdout.write("Command execution time: %s: %f seconds\n"%(str(self.node), finish_time-start_time))
def do_failed(self, status=2):
@ -336,7 +354,7 @@ class CleanTask(SCons.Taskmaster.AlwaysTask):
display("Removed directory " + pathstr)
else:
errstr = "Path '%s' exists but isn't a file or directory."
raise SCons.Errors.UserError(errstr % (pathstr))
raise SCons.Errors.UserError(errstr % pathstr)
except SCons.Errors.UserError as e:
print(e)
except (IOError, OSError) as e:
@ -412,11 +430,12 @@ class QuestionTask(SCons.Taskmaster.AlwaysTask):
pass
class TreePrinter(object):
def __init__(self, derived=False, prune=False, status=False):
class TreePrinter:
def __init__(self, derived=False, prune=False, status=False, sLineDraw=False):
self.derived = derived
self.prune = prune
self.status = status
self.sLineDraw = sLineDraw
def get_all_children(self, node):
return node.all_children()
def get_derived_children(self, node):
@ -428,7 +447,7 @@ class TreePrinter(object):
else:
func = self.get_all_children
s = self.status and 2 or 0
SCons.Util.print_tree(t, func, prune=self.prune, showtags=s)
SCons.Util.print_tree(t, func, prune=self.prune, showtags=s, lastChild=True, singleLineDraw=self.sLineDraw)
def python_version_string():
@ -441,20 +460,7 @@ def python_version_deprecated(version=sys.version_info):
return version < deprecated_python_version
# Global variables
print_objects = 0
print_memoizer = 0
print_stacktrace = 0
print_time = 0
sconscript_time = 0
cumulative_command_time = 0
exit_status = 0 # final exit status, assume success by default
this_build_status = 0 # "exit status" of an individual build
num_jobs = None
delayed_warnings = []
class FakeOptionParser(object):
class FakeOptionParser:
"""
A do-nothing option parser, used for the initial OptionsParser variable.
@ -466,7 +472,7 @@ class FakeOptionParser(object):
without blowing up.
"""
class FakeOptionValues(object):
class FakeOptionValues:
def __getattr__(self, attr):
return None
values = FakeOptionValues()
@ -490,7 +496,7 @@ def SetOption(name, value):
def PrintHelp(file=None):
OptionsParser.print_help(file=file)
class Stats(object):
class Stats:
def __init__(self):
self.stats = []
self.labels = []
@ -622,7 +628,7 @@ def _SConstruct_exists(dirname='', repositories=[], filelist=None):
current directory.
"""
if not filelist:
filelist = ['SConstruct', 'Sconstruct', 'sconstruct']
filelist = ['SConstruct', 'Sconstruct', 'sconstruct', 'SConstruct.py', 'Sconstruct.py', 'sconstruct.py']
for file in filelist:
sfile = os.path.join(dirname, file)
if os.path.isfile(sfile):
@ -634,7 +640,7 @@ def _SConstruct_exists(dirname='', repositories=[], filelist=None):
return None
def _set_debug_values(options):
global print_memoizer, print_objects, print_stacktrace, print_time
global print_memoizer, print_objects, print_stacktrace, print_time, print_action_timestamps
debug_values = options.debug
@ -672,6 +678,9 @@ def _set_debug_values(options):
options.tree_printers.append(TreePrinter(status=True))
if "time" in debug_values:
print_time = 1
if "action-timestamps" in debug_values:
print_time = 1
print_action_timestamps = 1
if "tree" in debug_values:
options.tree_printers.append(TreePrinter())
if "prepare" in debug_values:
@ -689,79 +698,86 @@ def _create_path(plist):
return path
def _load_site_scons_dir(topdir, site_dir_name=None):
"""Load the site_scons dir under topdir.
Prepends site_scons to sys.path, imports site_scons/site_init.py,
and prepends site_scons/site_tools to default toolpath."""
"""Load the site directory under topdir.
If a site dir name is supplied use it, else use default "site_scons"
Prepend site dir to sys.path.
If a "site_tools" subdir exists, prepend to toolpath.
Import "site_init.py" from site dir if it exists.
"""
if site_dir_name:
err_if_not_found = True # user specified: err if missing
else:
site_dir_name = "site_scons"
err_if_not_found = False
err_if_not_found = False # scons default: okay to be missing
site_dir = os.path.join(topdir, site_dir_name)
if not os.path.exists(site_dir):
if err_if_not_found:
raise SCons.Errors.UserError("site dir %s not found."%site_dir)
raise SCons.Errors.UserError("site dir %s not found." % site_dir)
return
sys.path.insert(0, os.path.abspath(site_dir))
site_init_filename = "site_init.py"
site_init_modname = "site_init"
site_tools_dirname = "site_tools"
# prepend to sys.path
sys.path = [os.path.abspath(site_dir)] + sys.path
site_init_file = os.path.join(site_dir, site_init_filename)
site_tools_dir = os.path.join(site_dir, site_tools_dirname)
if os.path.exists(site_init_file):
import imp, re
if os.path.exists(site_tools_dir):
SCons.Tool.DefaultToolpath.insert(0, os.path.abspath(site_tools_dir))
if not os.path.exists(site_init_file):
return
# "import" the site_init.py file into the SCons.Script namespace.
# This is a variant on the basic Python import flow in that the globals
# dict for the compile step is prepopulated from the SCons.Script
# module object; on success the SCons.Script globals are refilled
# from the site_init globals so it all appears in SCons.Script
# instead of as a separate module.
try:
try:
fp, pathname, description = imp.find_module(site_init_modname,
[site_dir])
# Load the file into SCons.Script namespace. This is
# opaque and clever; m is the module object for the
# SCons.Script module, and the exec ... in call executes a
# file (or string containing code) in the context of the
# module's dictionary, so anything that code defines ends
# up adding to that module. This is really short, but all
# the error checking makes it longer.
try:
m = sys.modules['SCons.Script']
except Exception as e:
fmt = 'cannot import site_init.py: missing SCons.Script module %s'
raise SCons.Errors.InternalError(fmt % repr(e))
try:
sfx = description[0]
modname = os.path.basename(pathname)[:-len(sfx)]
site_m = {"__file__": pathname, "__name__": modname, "__doc__": None}
re_special = re.compile("__[^_]+__")
for k in list(m.__dict__.keys()):
if not re_special.match(k):
site_m[k] = m.__dict__[k]
except KeyError:
fmt = 'cannot import {}: missing SCons.Script module'
raise SCons.Errors.InternalError(fmt.format(site_init_file))
# This is the magic.
exec(compile(fp.read(), fp.name, 'exec'), site_m)
spec = importlib.util.spec_from_file_location(site_init_modname, site_init_file)
site_m = {
"__file__": spec.origin,
"__name__": spec.name,
"__doc__": None,
}
re_dunder = re.compile(r"__[^_]+__")
# update site dict with all but magic (dunder) methods
for k, v in m.__dict__.items():
if not re_dunder.match(k):
site_m[k] = v
with open(spec.origin, 'r') as f:
code = f.read()
try:
codeobj = compile(code, spec.name, "exec")
exec(codeobj, site_m)
except KeyboardInterrupt:
raise
except Exception as e:
fmt = '*** Error loading site_init file %s:\n'
sys.stderr.write(fmt % repr(site_init_file))
except Exception:
fmt = "*** Error loading site_init file {}:\n"
sys.stderr.write(fmt.format(site_init_file))
raise
else:
for k in site_m:
if not re_special.match(k):
m.__dict__[k] = site_m[k]
# now refill globals with site_init's symbols
for k, v in site_m.items():
if not re_dunder.match(k):
m.__dict__[k] = v
except KeyboardInterrupt:
raise
except ImportError as e:
fmt = '*** cannot import site init file %s:\n'
sys.stderr.write(fmt % repr(site_init_file))
except Exception:
fmt = "*** cannot import site init file {}:\n"
sys.stderr.write(fmt.format(site_init_file))
raise
finally:
if fp:
fp.close()
if os.path.exists(site_tools_dir):
# prepend to DefaultToolpath
SCons.Tool.DefaultToolpath.insert(0, os.path.abspath(site_tools_dir))
def _load_all_site_scons_dirs(topdir, verbose=None):
"""Load all of the predefined site_scons dir.
@ -801,7 +817,7 @@ def _load_all_site_scons_dirs(topdir, verbose=None):
sysdirs=['/usr/share/scons',
homedir('.scons')]
dirs=sysdirs + [topdir]
dirs = sysdirs + [topdir]
for d in dirs:
if verbose: # this is used by unit tests.
print("Loading site dir ", d)
@ -862,6 +878,13 @@ def _main(parser):
for warning_type, message in delayed_warnings:
SCons.Warnings.warn(warning_type, message)
if not SCons.Platform.virtualenv.virtualenv_enabled_by_default:
if options.enable_virtualenv:
SCons.Platform.virtualenv.enable_virtualenv = True
if options.ignore_virtualenv:
SCons.Platform.virtualenv.ignore_virtualenv = True
if options.diskcheck:
SCons.Node.FS.set_diskcheck(options.diskcheck)
@ -1161,7 +1184,7 @@ def _build_targets(fs, options, targets, target_top):
# -U, local SConscript Default() targets
target_top = fs.Dir(target_top)
def check_dir(x, target_top=target_top):
if hasattr(x, 'cwd') and not x.cwd is None:
if hasattr(x, 'cwd') and x.cwd is not None:
cwd = x.cwd.srcnode()
return cwd == target_top
else:
@ -1240,10 +1263,14 @@ def _build_targets(fs, options, targets, target_top):
"""Leave the order of dependencies alone."""
return dependencies
def tmtrace_cleanup(tfile):
tfile.close()
if options.taskmastertrace_file == '-':
tmtrace = sys.stdout
elif options.taskmastertrace_file:
tmtrace = open(options.taskmastertrace_file, 'w')
atexit.register(tmtrace_cleanup, tmtrace)
else:
tmtrace = None
taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
@ -1253,7 +1280,11 @@ def _build_targets(fs, options, targets, target_top):
BuildTask.options = options
python_has_threads = sysconfig.get_config_var('WITH_THREAD')
is_pypy = platform.python_implementation() == 'PyPy'
# As of 3.7, python removed support for threadless platforms.
# See https://www.python.org/dev/peps/pep-0011/
is_37_or_later = sys.version_info >= (3, 7)
python_has_threads = sysconfig.get_config_var('WITH_THREAD') or is_pypy or is_37_or_later
# to check if python configured with threads.
global num_jobs
num_jobs = options.num_jobs
@ -1339,15 +1370,14 @@ def main():
parts = ["SCons by Steven Knight et al.:\n"]
try:
import __main__
parts.append(version_string("script", __main__))
import SCons
parts.append(version_string("SCons", SCons))
except (ImportError, AttributeError):
# On Windows there is no scons.py, so there is no
# __main__.__version__, hence there is no script version.
pass
parts.append(version_string("engine", SCons))
parts.append(path_string("engine", SCons))
parts.append("Copyright (c) 2001 - 2017 The SCons Foundation")
parts.append(path_string("SCons", SCons))
parts.append(SCons.__copyright__)
version = ''.join(parts)
from . import SConsOptions
@ -1363,7 +1393,7 @@ def main():
revert_io()
except SystemExit as s:
if s:
exit_status = s
exit_status = s.code
except KeyboardInterrupt:
print("scons: Build interrupted.")
sys.exit(2)

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -21,14 +21,14 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Script/SConsOptions.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import optparse
import re
import sys
import textwrap
no_hyphen_re = re.compile(r'(\s+|(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))')
no_hyphen_re = re.compile(r'(\s+|(?<=[\w!\"\'&.,?])-{2,}(?=\w))')
try:
from gettext import gettext
@ -38,6 +38,7 @@ except ImportError:
_ = gettext
import SCons.Node.FS
import SCons.Platform.virtualenv
import SCons.Warnings
OptionValueError = optparse.OptionValueError
@ -139,14 +140,15 @@ class SConsValues(optparse.Values):
'random',
'stack_size',
'warn',
'silent'
'silent',
'no_progress'
]
def set_option(self, name, value):
"""
Sets an option from an SConscript file.
"""
if not name in self.settable:
if name not in self.settable:
raise SCons.Errors.UserError("This option is not settable from a SConscript file: %s"%name)
if name == 'num_jobs':
@ -166,7 +168,7 @@ class SConsValues(optparse.Values):
value = str(value)
except ValueError:
raise SCons.Errors.UserError("A string is required: %s"%repr(value))
if not value in SCons.Node.FS.Valid_Duplicates:
if value not in SCons.Node.FS.Valid_Duplicates:
raise SCons.Errors.UserError("Not a valid duplication style: %s" % value)
# Set the duplicate style right away so it can affect linking
# of SConscript files.
@ -196,6 +198,9 @@ class SConsValues(optparse.Values):
value = [value]
value = self.__SConscript_settings__.get(name, []) + value
SCons.Warnings.process_warn_strings(value)
elif name == 'no_progress':
SCons.Script.Main.progress_display.set_mode(False)
self.__SConscript_settings__[name] = value
@ -225,39 +230,8 @@ class SConsOption(optparse.Option):
fmt = "option %s: nargs='?' is incompatible with short options"
raise SCons.Errors.UserError(fmt % self._short_opts[0])
try:
_orig_CONST_ACTIONS = optparse.Option.CONST_ACTIONS
_orig_CHECK_METHODS = optparse.Option.CHECK_METHODS
except AttributeError:
# optparse.Option had no CONST_ACTIONS before Python 2.5.
_orig_CONST_ACTIONS = ("store_const",)
def _check_const(self):
if self.action not in self.CONST_ACTIONS and self.const is not None:
raise OptionError(
"'const' must not be supplied for action %r" % self.action,
self)
# optparse.Option collects its list of unbound check functions
# up front. This sucks because it means we can't just override
# the _check_const() function like a normal method, we have to
# actually replace it in the list. This seems to be the most
# straightforward way to do that.
_orig_CHECK_METHODS = [optparse.Option._check_action,
optparse.Option._check_type,
optparse.Option._check_choice,
optparse.Option._check_dest,
_check_const,
optparse.Option._check_nargs,
optparse.Option._check_callback]
CHECK_METHODS = _orig_CHECK_METHODS + [_check_nargs_optional]
CONST_ACTIONS = _orig_CONST_ACTIONS + optparse.Option.TYPED_ACTIONS
CHECK_METHODS = optparse.Option.CHECK_METHODS + [_check_nargs_optional]
CONST_ACTIONS = optparse.Option.CONST_ACTIONS + optparse.Option.TYPED_ACTIONS
class SConsOptionGroup(optparse.OptionGroup):
"""
@ -358,29 +332,27 @@ class SConsOptionParser(optparse.OptionParser):
option.process(opt, value, values, self)
def reparse_local_options(self):
"""
Re-parse the leftover command-line options stored
in self.largs, so that any value overridden on the
command line is immediately available if the user turns
around and does a GetOption() right away.
""" Re-parse the leftover command-line options.
Parse options stored in `self.largs`, so that any value
overridden on the command line is immediately available
if the user turns around and does a :func:`GetOption` right away.
We mimic the processing of the single args
in the original OptionParser._process_args(), but here we
allow exact matches for long-opts only (no partial
argument names!).
Else, this would lead to problems in add_local_option()
in the original OptionParser :func:`_process_args`, but here we
allow exact matches for long-opts only (no partial argument names!).
Otherwise there could be problems in :func:`add_local_option`
below. When called from there, we try to reparse the
command-line arguments that
1. haven't been processed so far (self.largs), but
1. haven't been processed so far (`self.largs`), but
2. are possibly not added to the list of options yet.
So, when we only have a value for "--myargument" yet,
a command-line argument of "--myarg=test" would set it.
Responsible for this behaviour is the method
_match_long_opt(), which allows for partial matches of
the option name, as long as the common prefix appears to
be unique.
So, when we only have a value for "--myargument" so far,
a command-line argument of "--myarg=test" would set it,
per the behaviour of :func:`_match_long_opt`,
which allows for partial matches of the option name,
as long as the common prefix appears to be unique.
This would lead to further confusion, because we might want
to add another option "--myarg" later on (see issue #2929).
@ -614,9 +586,15 @@ def Parser(version):
help="Print build actions for files from CacheDir.")
def opt_invalid(group, value, options):
"""report an invalid option from a group"""
errmsg = "`%s' is not a valid %s option type, try:\n" % (value, group)
return errmsg + " %s" % ", ".join(options)
def opt_invalid_rm(group, value, msg):
"""report an invalid option from a group: recognized but removed"""
errmsg = "`%s' is not a valid %s option type " % (value, group)
return errmsg + msg
config_options = ["auto", "force" ,"cache"]
opt_config_help = "Controls Configure subsystem: %s." \
@ -634,9 +612,11 @@ def Parser(version):
help="Search up directory tree for SConstruct, "
"build all Default() targets.")
deprecated_debug_options = {
deprecated_debug_options = {}
removed_debug_options = {
"dtree" : '; please use --tree=derived instead',
"nomemoizer" : ' and has no effect',
"nomemoizer" : '; there is no replacement',
"stree" : '; please use --tree=all,status instead',
"tree" : '; please use --tree=all instead',
}
@ -644,15 +624,16 @@ def Parser(version):
debug_options = ["count", "duplicate", "explain", "findlibs",
"includes", "memoizer", "memory", "objects",
"pdb", "prepare", "presub", "stacktrace",
"time"]
"time", "action-timestamps"]
def opt_debug(option, opt, value__, parser,
debug_options=debug_options,
deprecated_debug_options=deprecated_debug_options):
deprecated_debug_options=deprecated_debug_options,
removed_debug_options=removed_debug_options):
for value in value__.split(','):
if value in debug_options:
parser.values.debug.append(value)
elif value in list(deprecated_debug_options.keys()):
elif value in deprecated_debug_options:
parser.values.debug.append(value)
try:
parser.values.delayed_warnings
@ -662,6 +643,9 @@ def Parser(version):
w = "The --debug=%s option is deprecated%s." % (value, msg)
t = (SCons.Warnings.DeprecatedDebugOptionsWarning, w)
parser.values.delayed_warnings.append(t)
elif value in removed_debug_options:
msg = removed_debug_options[value]
raise OptionValueError(opt_invalid_rm('debug', value, msg))
else:
raise OptionValueError(opt_invalid('debug', value, debug_options))
@ -689,7 +673,7 @@ def Parser(version):
metavar="TYPE")
def opt_duplicate(option, opt, value, parser):
if not value in SCons.Node.FS.Valid_Duplicates:
if value not in SCons.Node.FS.Valid_Duplicates:
raise OptionValueError(opt_invalid('duplication', value,
SCons.Node.FS.Valid_Duplicates))
setattr(parser.values, option.dest, value)
@ -706,6 +690,12 @@ def Parser(version):
action="callback", callback=opt_duplicate,
help=opt_duplicate_help)
if not SCons.Platform.virtualenv.virtualenv_enabled_by_default:
op.add_option('--enable-virtualenv',
dest="enable_virtualenv",
action="store_true",
help="Import certain virtualenv variables to SCons")
op.add_option('-f', '--file', '--makefile', '--sconstruct',
nargs=1, type="string",
dest="file", default=[],
@ -733,6 +723,11 @@ def Parser(version):
help="Search DIR for imported Python modules.",
metavar="DIR")
op.add_option('--ignore-virtualenv',
dest="ignore_virtualenv",
action="store_true",
help="Do not import virtualenv variables to SCons")
op.add_option('--implicit-cache',
dest='implicit_cache', default=False,
action="store_true",
@ -841,7 +836,7 @@ def Parser(version):
help="Trace Node evaluation to FILE.",
metavar="FILE")
tree_options = ["all", "derived", "prune", "status"]
tree_options = ["all", "derived", "prune", "status", "linedraw"]
def opt_tree(option, opt, value, parser, tree_options=tree_options):
from . import Main
@ -855,6 +850,8 @@ def Parser(version):
tp.prune = True
elif o == 'status':
tp.status = True
elif o == 'linedraw':
tp.sLineDraw = True
else:
raise OptionValueError(opt_invalid('--tree', o, tree_options))
parser.values.tree_printers.append(tp)
@ -906,6 +903,7 @@ def Parser(version):
action="append",
help="Search REPOSITORY for source and target files.")
# Options from Make and Cons classic that we do not yet support,
# but which we may support someday and whose (potential) meanings
# we don't want to change. These all get a "the -X option is not
@ -978,7 +976,6 @@ def Parser(version):
action="callback", callback=opt_not_yet,
# help="Warn when an undefined variable is referenced."
help=SUPPRESS_HELP)
return op
# Local Variables:

View file

@ -6,7 +6,7 @@ files.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -27,7 +27,7 @@ files.
# 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__ = "src/engine/SCons/Script/SConscript.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons
import SCons.Action
@ -42,8 +42,8 @@ import SCons.Platform
import SCons.SConf
import SCons.Script.Main
import SCons.Tool
import SCons.Util
from SCons.Util import is_List, is_String, is_Dict, flatten
from SCons.Node import SConscriptNodes
from . import Main
import collections
@ -98,7 +98,7 @@ def compute_exports(exports):
retval = {}
try:
for export in exports:
if SCons.Util.is_Dict(export):
if is_Dict(export):
retval.update(export)
else:
try:
@ -110,7 +110,7 @@ def compute_exports(exports):
return retval
class Frame(object):
class Frame:
"""A frame on the SConstruct/SConscript call stack"""
def __init__(self, fs, exports, sconscript):
self.globals = BuildDefaultGlobals()
@ -133,7 +133,7 @@ call_stack = []
def Return(*vars, **kw):
retval = []
try:
fvars = SCons.Util.flatten(vars)
fvars = flatten(vars)
for var in fvars:
for v in var.split():
retval.append(call_stack[-1].globals[v])
@ -153,6 +153,35 @@ def Return(*vars, **kw):
stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
def handle_missing_SConscript(f, must_exist=None):
"""Take appropriate action on missing file in SConscript() call.
Print a warning or raise an exception on missing file.
On first warning, print a deprecation message.
Args:
f (str): path of missing configuration file
must_exist (bool): raise exception if file does not exist
Raises:
UserError if 'must_exist' is True or if global
SCons.Script._no_missing_sconscript is True.
"""
if must_exist or (SCons.Script._no_missing_sconscript and must_exist is not False):
msg = "Fatal: missing SConscript '%s'" % f.get_internal_path()
raise SCons.Errors.UserError(msg)
if SCons.Script._warn_missing_sconscript_deprecated:
msg = "Calling missing SConscript without error is deprecated.\n" + \
"Transition by adding must_exist=0 to SConscript calls.\n" + \
"Missing SConscript '%s'" % f.get_internal_path()
SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning, msg)
SCons.Script._warn_missing_sconscript_deprecated = False
else:
msg = "Ignoring missing SConscript '%s'" % f.get_internal_path()
SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning, msg)
def _SConscript(fs, *files, **kw):
top = fs.Top
sd = fs.SConstruct_dir.rdir()
@ -173,6 +202,7 @@ def _SConscript(fs, *files, **kw):
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
@ -249,11 +279,12 @@ def _SConscript(fs, *files, **kw):
pass
try:
try:
# _file_ = SCons.Util.to_str(_file_)
if Main.print_time:
time1 = time.time()
exec(compile(_file_.read(), _file_.name, 'exec'),
call_stack[-1].globals)
scriptdata = _file_.read()
scriptname = _file_.name
_file_.close()
exec(compile(scriptdata, scriptname, 'exec'), call_stack[-1].globals)
except SConscriptReturn:
pass
finally:
@ -264,8 +295,7 @@ def _SConscript(fs, *files, **kw):
if old_file is not None:
call_stack[-1].globals.update({__file__:old_file})
else:
SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
"Ignoring missing SConscript '%s'" % f.get_internal_path())
handle_missing_SConscript(f, kw.get('must_exist', None))
finally:
SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
@ -369,9 +399,9 @@ class SConsEnvironment(SCons.Environment.Base):
something like 3.2b1."""
version = version_string.split(' ')[0].split('.')
v_major = int(version[0])
v_minor = int(re.match('\d+', version[1]).group())
v_minor = int(re.match(r'\d+', version[1]).group())
if len(version) >= 3:
v_revision = int(re.match('\d+', version[2]).group())
v_revision = int(re.match(r'\d+', version[2]).group())
else:
v_revision = 0
return v_major, v_minor, v_revision
@ -391,7 +421,7 @@ class SConsEnvironment(SCons.Environment.Base):
except KeyError:
raise SCons.Errors.UserError("Invalid SConscript usage - no parameters")
if not SCons.Util.is_List(dirs):
if not is_List(dirs):
dirs = [ dirs ]
dirs = list(map(str, dirs))
@ -412,13 +442,13 @@ class SConsEnvironment(SCons.Environment.Base):
raise SCons.Errors.UserError("Invalid SConscript() usage - too many arguments")
if not SCons.Util.is_List(files):
if not is_List(files):
files = [ files ]
if kw.get('exports'):
exports.extend(self.Split(kw['exports']))
variant_dir = kw.get('variant_dir') or kw.get('build_dir')
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")
@ -523,9 +553,31 @@ class SConsEnvironment(SCons.Environment.Base):
raise SCons.Errors.UserError("Import of non-existent variable '%s'"%x)
def SConscript(self, *ls, **kw):
if 'build_dir' in kw:
msg = """The build_dir keyword has been deprecated; use the variant_dir keyword instead."""
SCons.Warnings.warn(SCons.Warnings.DeprecatedBuildDirWarning, msg)
"""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))
@ -535,15 +587,10 @@ class SConsEnvironment(SCons.Environment.Base):
ls = list(map(subst_element, ls))
subst_kw = {}
for key, val in kw.items():
if SCons.Util.is_String(val):
if is_String(val):
val = self.subst(val)
elif SCons.Util.is_List(val):
result = []
for v in val:
if SCons.Util.is_String(v):
v = self.subst(v)
result.append(v)
val = result
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)
@ -593,7 +640,7 @@ def get_DefaultEnvironmentProxy():
_DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env)
return _DefaultEnvironmentProxy
class DefaultEnvironmentCall(object):
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

View file

@ -12,7 +12,7 @@ it goes here.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -34,7 +34,7 @@ it goes here.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Script/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import time
start_time = time.time()
@ -81,8 +81,8 @@ import SCons.Action
import SCons.Builder
import SCons.Environment
import SCons.Node.FS
import SCons.Options
import SCons.Platform
import SCons.Platform.virtualenv
import SCons.Scanner
import SCons.SConf
import SCons.Subst
@ -150,6 +150,7 @@ 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.Base
Tool = SCons.Tool.Tool
@ -162,12 +163,6 @@ ListVariable = SCons.Variables.ListVariable
PackageVariable = SCons.Variables.PackageVariable
PathVariable = SCons.Variables.PathVariable
# Deprecated names that will go away some day.
BoolOption = SCons.Options.BoolOption
EnumOption = SCons.Options.EnumOption
ListOption = SCons.Options.ListOption
PackageOption = SCons.Options.PackageOption
PathOption = SCons.Options.PathOption
# Action factories.
Chmod = SCons.Defaults.Chmod
@ -283,12 +278,20 @@ def HelpFunction(text, append=False):
# Will be non-zero if we are reading an SConscript file.
sconscript_reading = 0
#
def Variables(files=[], args=ARGUMENTS):
_no_missing_sconscript = False
_warn_missing_sconscript_deprecated = True
def set_missing_sconscript_error(flag=1):
"""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)
def Options(files=[], args=ARGUMENTS):
return SCons.Options.Options(files, args)
# The list of global functions to add to the SConscript name space
# that end up calling corresponding methods or Builders in the
@ -311,7 +314,6 @@ GlobalDefaultEnvironmentFunctions = [
'AddPreAction',
'Alias',
'AlwaysBuild',
'BuildDir',
'CacheDir',
'Clean',
#The Command() method is handled separately, below.
@ -342,11 +344,8 @@ GlobalDefaultEnvironmentFunctions = [
'Requires',
'SConsignFile',
'SideEffect',
'SourceCode',
'SourceSignatures',
'Split',
'Tag',
'TargetSignatures',
'Value',
'VariantDir',
]
@ -374,7 +373,9 @@ GlobalDefaultBuilders = [
'SharedObject',
'StaticLibrary',
'StaticObject',
'Substfile',
'Tar',
'Textfile',
'TypeLibrary',
'Zip',
'Package',

View file

@ -5,7 +5,7 @@ SCons string substitution.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -26,11 +26,11 @@ SCons string substitution.
# 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__ = "src/engine/SCons/Subst.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import collections
import re
from inspect import signature
import SCons.Errors
from SCons.Util import is_String, is_Sequence
@ -58,7 +58,7 @@ def raise_exception(exception, target, s):
class Literal(object):
class Literal:
"""A wrapper for a string. If you use this object wrapped
around a string, then it will be interpreted as literal.
When passed to the command interpreter, all special
@ -86,7 +86,10 @@ class Literal(object):
def __neq__(self, other):
return not self.__eq__(other)
class SpecialAttrWrapper(object):
def __hash__(self):
return hash(self.lstr)
class SpecialAttrWrapper:
"""This is a wrapper for what we call a 'Node special attribute.'
This is any of the attributes of a Node that we can reference from
Environment variable substitution, such as $TARGET.abspath or
@ -168,7 +171,7 @@ def escape_list(mylist, escape_func):
return e(escape_func)
return list(map(escape, mylist))
class NLWrapper(object):
class NLWrapper:
"""A wrapper class that delays turning a list of sources or targets
into a NodeList until it's needed. The specified function supplied
when the object is initialized is responsible for turning raw nodes
@ -229,7 +232,7 @@ class Targets_or_Sources(collections.UserList):
nl = self.nl._create_nodelist()
return repr(nl)
class Target_or_Source(object):
class Target_or_Source:
"""A class that implements $TARGET or $SOURCE expansions by in turn
wrapping a NLWrapper. This class handles the different methods used
to access an individual proxy Node, calling the NLWrapper to create
@ -328,88 +331,8 @@ def subst_dict(target, source):
return dict
# Constants for the "mode" parameter to scons_subst_list() and
# scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD
# gives a command line suitable for passing to a shell. SUBST_SIG
# gives a command line appropriate for calculating the signature
# of a command line...if this changes, we should rebuild.
SUBST_CMD = 0
SUBST_RAW = 1
SUBST_SIG = 2
_rm = re.compile(r'\$[()]')
# Note the pattern below only matches $( or $) when there is no
# preceeding $. (Thus the (?<!\$))
_rm_split = re.compile(r'(?<!\$)(\$[()])')
# Indexed by the SUBST_* constants above.
_regex_remove = [ _rm, None, _rm_split ]
def _rm_list(list):
return [l for l in list if not l in ('$(', '$)')]
def _remove_list(list):
result = []
depth = 0
for l in list:
if l == '$(':
depth += 1
elif l == '$)':
depth -= 1
if depth < 0:
break
elif depth == 0:
result.append(l)
if depth != 0:
return None
return result
# Indexed by the SUBST_* constants above.
_list_remove = [ _rm_list, None, _remove_list ]
# 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 ]+(?![^{]*})')
def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
"""Expand a string or list containing construction variable
substitutions.
This is the work-horse function for substitutions in file names
and the like. The companion scons_subst_list() function (below)
handles separating command lines into lists of arguments, so see
that function if that's what you're looking for.
"""
if isinstance(strSubst, str) and strSubst.find('$') < 0:
return strSubst
class StringSubber(object):
class StringSubber:
"""A class to construct the results of a scons_subst() call.
This binds a specific construction environment, mode, target and
@ -452,6 +375,16 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
if key[0] == '{' or '.' in key:
if key[0] == '{':
key = key[1:-1]
# Store for error messages if we fail to expand the
# value
old_s = s
s = None
if key in lvars:
s = lvars[key]
elif key in self.gvars:
s = self.gvars[key]
else:
try:
s = eval(key, self.gvars, lvars)
except KeyboardInterrupt:
@ -459,15 +392,11 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
except Exception as e:
if e.__class__ in AllowableExceptions:
return ''
raise_exception(e, lvars['TARGETS'], s)
else:
if key in lvars:
s = lvars[key]
elif key in self.gvars:
s = self.gvars[key]
elif not NameError in AllowableExceptions:
raise_exception(NameError(key), lvars['TARGETS'], s)
else:
raise_exception(e, lvars['TARGETS'], old_s)
if s is None and NameError not in AllowableExceptions:
raise_exception(NameError(key), lvars['TARGETS'], old_s)
elif s is None:
return ''
# Before re-expanding the result, handle
@ -491,12 +420,16 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
return conv(substitute(l, lvars))
return list(map(func, s))
elif callable(s):
try:
# SCons has the unusual Null class where any __getattr__ call returns it's self,
# which does not work the signature module, and the Null class returns an empty
# string if called on, so we make an exception in this condition for Null class
if (isinstance(s, SCons.Util.Null) or
set(signature(s).parameters.keys()) == set(['target', 'source', 'env', 'for_signature'])):
s = s(target=lvars['TARGETS'],
source=lvars['SOURCES'],
env=self.env,
for_signature=(self.mode != SUBST_CMD))
except TypeError:
else:
# This probably indicates that it's a callable
# object that doesn't match our calling arguments
# (like an Action).
@ -539,81 +472,8 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
else:
return self.expand(args, lvars)
if conv is None:
conv = _strconv[mode]
# Doing this every time is a bit of a waste, since the Executor
# has typically already populated the OverrideEnvironment with
# $TARGET/$SOURCE variables. We're keeping this (for now), though,
# because it supports existing behavior that allows us to call
# an Action directly with an arbitrary target+source pair, which
# we use in Tool/tex.py to handle calling $BIBTEX when necessary.
# If we dropped that behavior (or found another way to cover it),
# we could get rid of this call completely and just rely on the
# Executor setting the variables.
if 'TARGET' not in lvars:
d = subst_dict(target, source)
if d:
lvars = lvars.copy()
lvars.update(d)
# We're (most likely) going to eval() things. If Python doesn't
# find a __builtins__ value in the global dictionary used for eval(),
# it copies the current global values for you. Avoid this by
# setting it explicitly and then deleting, so we don't pollute the
# construction environment Dictionary(ies) that are typically used
# for expansion.
gvars['__builtins__'] = __builtins__
ss = StringSubber(env, mode, conv, gvars)
result = ss.substitute(strSubst, lvars)
try:
del gvars['__builtins__']
except KeyError:
pass
res = result
if is_String(result):
# Remove $(-$) pairs and any stuff in between,
# if that's appropriate.
remove = _regex_remove[mode]
if remove:
if mode == SUBST_SIG:
result = _list_remove[mode](remove.split(result))
if result is None:
raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res)
result = ' '.join(result)
else:
result = remove.sub('', result)
if mode != SUBST_RAW:
# Compress strings of white space characters into
# a single space.
result = _space_sep.sub(' ', result).strip()
# Now replace escaped $'s currently "$$"
# This is needed because we now retain $$ instead of
# replacing them during substition to avoid
# improperly trying to escape "$$(" as being "$("
result = result.replace('$$','$')
elif is_Sequence(result):
remove = _list_remove[mode]
if remove:
result = remove(result)
if result is None:
raise SCons.Errors.UserError("Unbalanced $(/$) in: " + str(res))
return result
def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
"""Substitute construction variables in a string (or list or other
object) and separate the arguments into a command list.
The companion scons_subst() function (above) handles basic
substitutions within strings, so see that function instead
if that's what you're looking for.
"""
class ListSubber(collections.UserList):
class ListSubber(collections.UserList):
"""A class to construct the results of a scons_subst_list() call.
Like StringSubber, this class binds a specific construction
@ -643,6 +503,21 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
self.in_strip = None
self.next_line()
def expanded(self, s):
"""Determines if the string s requires further expansion.
Due to the implementation of ListSubber expand will call
itself 2 additional times for an already expanded string. This
method is used to determine if a string is already fully
expanded and if so exit the loop early to prevent these
recursive calls.
"""
if not is_String(s) or isinstance(s, CmdStringHolder):
return False
s = str(s) # in case it's a UserString
return _separate_args.findall(s) is None
def expand(self, s, lvars, within_list):
"""Expand a single "token" as necessary, appending the
expansion to the current result.
@ -674,6 +549,16 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
if key[0] == '{' or key.find('.') >= 0:
if key[0] == '{':
key = key[1:-1]
# Store for error messages if we fail to expand the
# value
old_s = s
s = None
if key in lvars:
s = lvars[key]
elif key in self.gvars:
s = self.gvars[key]
else:
try:
s = eval(key, self.gvars, lvars)
except KeyboardInterrupt:
@ -681,15 +566,17 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
except Exception as e:
if e.__class__ in AllowableExceptions:
return
raise_exception(e, lvars['TARGETS'], s)
else:
if key in lvars:
s = lvars[key]
elif key in self.gvars:
s = self.gvars[key]
elif not NameError in AllowableExceptions:
raise_exception(NameError(), lvars['TARGETS'], s)
else:
raise_exception(e, lvars['TARGETS'], old_s)
if s is None and NameError not in AllowableExceptions:
raise_exception(NameError(), lvars['TARGETS'], old_s)
elif s is None:
return
# If the string is already full expanded there's no
# need to continue recursion.
if self.expanded(s):
self.append(s)
return
# Before re-expanding the result, handle
@ -707,12 +594,16 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
self.substitute(a, lvars, 1)
self.next_word()
elif callable(s):
try:
# SCons has the unusual Null class where any __getattr__ call returns it's self,
# which does not work the signature module, and the Null class returns an empty
# string if called on, so we make an exception in this condition for Null class
if (isinstance(s, SCons.Util.Null) or
set(signature(s).parameters.keys()) == set(['target', 'source', 'env', 'for_signature'])):
s = s(target=lvars['TARGETS'],
source=lvars['SOURCES'],
env=self.env,
for_signature=(self.mode != SUBST_CMD))
except TypeError:
else:
# This probably indicates that it's a callable
# object that doesn't match our calling arguments
# (like an Action).
@ -840,6 +731,162 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
self.add_strip(x)
self.in_strip = None
# Constants for the "mode" parameter to scons_subst_list() and
# scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD
# gives a command line suitable for passing to a shell. SUBST_SIG
# gives a command line appropriate for calculating the signature
# of a command line...if this changes, we should rebuild.
SUBST_CMD = 0
SUBST_RAW = 1
SUBST_SIG = 2
_rm = re.compile(r'\$[()]')
# Note the pattern below only matches $( or $) when there is no
# preceeding $. (Thus the (?<!\$))
_rm_split = re.compile(r'(?<!\$)(\$[()])')
# Indexed by the SUBST_* constants above.
_regex_remove = [ _rm, None, _rm_split ]
def _rm_list(list):
return [l for l in list if l not in ('$(', '$)')]
def _remove_list(list):
result = []
depth = 0
for l in list:
if l == '$(':
depth += 1
elif l == '$)':
depth -= 1
if depth < 0:
break
elif depth == 0:
result.append(l)
if depth != 0:
return None
return result
# Indexed by the SUBST_* constants above.
_list_remove = [ _rm_list, None, _remove_list ]
# 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 ]+(?![^{]*})')
def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
"""Expand a string or list containing construction variable
substitutions.
This is the work-horse function for substitutions in file names
and the like. The companion scons_subst_list() function (below)
handles separating command lines into lists of arguments, so see
that function if that's what you're looking for.
"""
if (isinstance(strSubst, str) and '$' not in strSubst) or isinstance(strSubst, CmdStringHolder):
return strSubst
if conv is None:
conv = _strconv[mode]
# Doing this every time is a bit of a waste, since the Executor
# has typically already populated the OverrideEnvironment with
# $TARGET/$SOURCE variables. We're keeping this (for now), though,
# because it supports existing behavior that allows us to call
# an Action directly with an arbitrary target+source pair, which
# we use in Tool/tex.py to handle calling $BIBTEX when necessary.
# If we dropped that behavior (or found another way to cover it),
# we could get rid of this call completely and just rely on the
# Executor setting the variables.
if 'TARGET' not in lvars:
d = subst_dict(target, source)
if d:
lvars = lvars.copy()
lvars.update(d)
# We're (most likely) going to eval() things. If Python doesn't
# find a __builtins__ value in the global dictionary used for eval(),
# it copies the current global values for you. Avoid this by
# setting it explicitly and then deleting, so we don't pollute the
# construction environment Dictionary(ies) that are typically used
# for expansion.
gvars['__builtins__'] = __builtins__
ss = StringSubber(env, mode, conv, gvars)
result = ss.substitute(strSubst, lvars)
try:
del gvars['__builtins__']
except KeyError:
pass
res = result
if is_String(result):
# Remove $(-$) pairs and any stuff in between,
# if that's appropriate.
remove = _regex_remove[mode]
if remove:
if mode == SUBST_SIG:
result = _list_remove[mode](remove.split(result))
if result is None:
raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res)
result = ' '.join(result)
else:
result = remove.sub('', result)
if mode != SUBST_RAW:
# Compress strings of white space characters into
# a single space.
result = _space_sep.sub(' ', result).strip()
# Now replace escaped $'s currently "$$"
# This is needed because we now retain $$ instead of
# replacing them during substition to avoid
# improperly trying to escape "$$(" as being "$("
result = result.replace('$$','$')
elif is_Sequence(result):
remove = _list_remove[mode]
if remove:
result = remove(result)
if result is None:
raise SCons.Errors.UserError("Unbalanced $(/$) in: " + str(res))
return result
def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None):
"""Substitute construction variables in a string (or list or other
object) and separate the arguments into a command list.
The companion scons_subst() function (above) handles basic
substitutions within strings, so see that function instead
if that's what you're looking for.
"""
if conv is None:
conv = _strconv[mode]

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -20,8 +20,6 @@
# 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 __future__ import print_function
import sys
__doc__ = """
@ -54,12 +52,13 @@ __doc__ = """
target(s) that it decides need to be evaluated and/or built.
"""
__revision__ = "src/engine/SCons/Taskmaster.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
from itertools import chain
import operator
import sys
import traceback
from abc import ABC, abstractmethod
from itertools import chain
import SCons.Errors
import SCons.Node
@ -81,7 +80,7 @@ print_prepare = 0 # set by option --debug=prepare
CollectStats = None
class Stats(object):
class Stats:
"""
A simple class for holding statistics about the disposition of a
Node by the Taskmaster. If we're collecting statistics, each Node
@ -117,10 +116,8 @@ def dump_stats():
print((fmt % n.attributes.stats.__dict__) + str(n))
class Task(object):
"""
Default SCons build engine task.
class Task(ABC):
""" SCons build engine abstract task class.
This controls the interaction of the actual building of node
and the rest of the engine.
@ -171,7 +168,7 @@ class Task(object):
"""
global print_prepare
T = self.tm.trace
if T: T.write(self.trace_message(u'Task.prepare()', self.node))
if T: T.write(self.trace_message('Task.prepare()', self.node))
# Now that it's the appropriate time, give the TaskMaster a
# chance to raise any exceptions it encountered while preparing
@ -212,17 +209,9 @@ class Task(object):
"""
return self.node
@abstractmethod
def needs_execute(self):
# TODO(deprecate): "return True" is the old default behavior;
# change it to NotImplementedError (after running through the
# Deprecation Cycle) so the desired behavior is explicitly
# determined by which concrete subclass is used.
#raise NotImplementedError
msg = ('Taskmaster.Task is an abstract base class; instead of\n'
'\tusing it directly, '
'derive from it and override the abstract methods.')
SCons.Warnings.warn(SCons.Warnings.TaskmasterNeedsExecuteWarning, msg)
return True
return
def execute(self):
"""
@ -233,7 +222,7 @@ class Task(object):
prepare(), executed() or failed().
"""
T = self.tm.trace
if T: T.write(self.trace_message(u'Task.execute()', self.node))
if T: T.write(self.trace_message('Task.execute()', self.node))
try:
cached_targets = []
@ -399,7 +388,7 @@ class Task(object):
"""
global print_prepare
T = self.tm.trace
if T: T.write(self.trace_message(u'Task.make_ready_current()',
if T: T.write(self.trace_message('Task.make_ready_current()',
self.node))
self.out_of_date = []
@ -447,7 +436,7 @@ class Task(object):
that can be put back on the candidates list.
"""
T = self.tm.trace
if T: T.write(self.trace_message(u'Task.postprocess()', self.node))
if T: T.write(self.trace_message('Task.postprocess()', self.node))
# We may have built multiple targets, some of which may have
# common parents waiting for this build. Count up how many
@ -464,27 +453,36 @@ class Task(object):
# A node can only be in the pending_children set if it has
# some waiting_parents.
if t.waiting_parents:
if T: T.write(self.trace_message(u'Task.postprocess()',
if T: T.write(self.trace_message('Task.postprocess()',
t,
'removing'))
pending_children.discard(t)
for p in t.waiting_parents:
parents[p] = parents.get(p, 0) + 1
t.waiting_parents = set()
for t in targets:
if t.side_effects is not None:
for s in t.side_effects:
if s.get_state() == NODE_EXECUTING:
s.set_state(NODE_NO_STATE)
# The side-effects may have been transferred to
# NODE_NO_STATE by executed_with{,out}_callbacks, but was
# not taken out of the waiting parents/pending children
# data structures. Check for that now.
if s.get_state() == NODE_NO_STATE and s.waiting_parents:
pending_children.discard(s)
for p in s.waiting_parents:
parents[p] = parents.get(p, 0) + 1
s.waiting_parents = set()
for p in s.waiting_s_e:
if p.ref_count == 0:
self.tm.candidates.append(p)
for p, subtract in parents.items():
p.ref_count = p.ref_count - subtract
if T: T.write(self.trace_message(u'Task.postprocess()',
if T: T.write(self.trace_message('Task.postprocess()',
p,
'adjusted parent ref count'))
if p.ref_count == 0:
@ -542,19 +540,16 @@ class Task(object):
try:
exc_type, exc_value, exc_traceback = exc
except ValueError:
exc_type, exc_value = exc
exc_type, exc_value = exc # pylint: disable=unbalanced-tuple-unpacking
exc_traceback = None
# raise exc_type(exc_value).with_traceback(exc_traceback)
if sys.version_info[0] == 2:
exec("raise exc_type, exc_value, exc_traceback")
else: # sys.version_info[0] == 3:
if isinstance(exc_value, Exception): #hasattr(exc_value, 'with_traceback'):
# If exc_value is an exception, then just reraise
exec("raise exc_value.with_traceback(exc_traceback)")
raise exc_value.with_traceback(exc_traceback)
else:
# else we'll create an exception using the value and raise that
exec("raise exc_type(exc_value).with_traceback(exc_traceback)")
raise exc_type(exc_value).with_traceback(exc_traceback)
# raise e.__class__, e.__class__(e), sys.exc_info()[2]
@ -573,7 +568,7 @@ class AlwaysTask(Task):
dependencies) can use this as follows:
class MyTaskSubclass(SCons.Taskmaster.Task):
needs_execute = SCons.Taskmaster.Task.execute_always
needs_execute = SCons.Taskmaster.AlwaysTask.needs_execute
"""
return True
@ -601,7 +596,7 @@ def find_cycle(stack, visited):
return None
class Taskmaster(object):
class Taskmaster:
"""
The Taskmaster for walking the dependency DAG.
"""
@ -783,12 +778,12 @@ class Taskmaster(object):
self.ready_exc = None
T = self.trace
if T: T.write(SCons.Util.UnicodeType('\n') + self.trace_message('Looking for a node to evaluate'))
if T: T.write('\n' + self.trace_message('Looking for a node to evaluate'))
while True:
node = self.next_candidate()
if node is None:
if T: T.write(self.trace_message('No candidate anymore.') + u'\n')
if T: T.write(self.trace_message('No candidate anymore.') + '\n')
return None
node = node.disambiguate()
@ -811,7 +806,7 @@ class Taskmaster(object):
else:
S = None
if T: T.write(self.trace_message(u' Considering node %s and its children:' % self.trace_node(node)))
if T: T.write(self.trace_message(' Considering node %s and its children:' % self.trace_node(node)))
if state == NODE_NO_STATE:
# Mark this node as being on the execution stack:
@ -819,7 +814,7 @@ class Taskmaster(object):
elif state > NODE_PENDING:
# Skip this node if it has already been evaluated:
if S: S.already_handled = S.already_handled + 1
if T: T.write(self.trace_message(u' already handled (executed)'))
if T: T.write(self.trace_message(' already handled (executed)'))
continue
executor = node.get_executor()
@ -850,7 +845,7 @@ class Taskmaster(object):
for child in chain(executor.get_all_prerequisites(), children):
childstate = child.get_state()
if T: T.write(self.trace_message(u' ' + self.trace_node(child)))
if T: T.write(self.trace_message(' ' + self.trace_node(child)))
if childstate == NODE_NO_STATE:
children_not_visited.append(child)
@ -865,6 +860,8 @@ class Taskmaster(object):
# These nodes have not even been visited yet. Add
# them to the list so that on some next pass we can
# take a stab at evaluating them (or their children).
if children_not_visited:
if len(children_not_visited) > 1:
children_not_visited.reverse()
self.candidates.extend(self.order(children_not_visited))
@ -909,7 +906,7 @@ class Taskmaster(object):
# count so we can be put back on the list for
# re-evaluation when they've all finished.
node.ref_count = node.ref_count + child.add_to_waiting_parents(node)
if T: T.write(self.trace_message(u' adjusted ref count: %s, child %s' %
if T: T.write(self.trace_message(' adjusted ref count: %s, child %s' %
(self.trace_node(node), repr(str(child)))))
if T:
@ -935,7 +932,7 @@ class Taskmaster(object):
# The default when we've gotten through all of the checks above:
# this node is ready to be built.
if S: S.build = S.build + 1
if T: T.write(self.trace_message(u'Evaluating %s\n' %
if T: T.write(self.trace_message('Evaluating %s\n' %
self.trace_node(node)))
# For debugging only:

View file

@ -10,7 +10,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -32,7 +32,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/386asm.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
from SCons.Tool.PharLapCommon import addPharLapPaths
import SCons.Util

View file

@ -1,5 +1,3 @@
from __future__ import print_function
"""SCons.Tool.DCommon
Common code for the various D tools.
@ -9,7 +7,7 @@ Coded by Russel Winder (russel@winder.org.uk)
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -31,7 +29,7 @@ Coded by Russel Winder (russel@winder.org.uk)
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/DCommon.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os.path

View file

@ -5,7 +5,7 @@ Stuff for processing Fortran, common to all fortran dialects.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -26,9 +26,7 @@ Stuff for processing Fortran, common to all fortran dialects.
# 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 __future__ import print_function
__revision__ = "src/engine/SCons/Tool/FortranCommon.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import re
import os.path
@ -64,7 +62,8 @@ def _fortranEmitter(target, source, env):
if not node.exists() and not node.is_derived():
print("Could not locate " + str(node.name))
return ([], [])
mod_regex = """(?i)^\s*MODULE\s+(?!PROCEDURE)(\w+)"""
# This has to match the def_regex in the Fortran scanner
mod_regex = r"""(?i)^\s*MODULE\s+(?!PROCEDURE|SUBROUTINE|FUNCTION|PURE|ELEMENTAL)(\w+)"""
cre = re.compile(mod_regex,re.M)
# Retrieve all USE'd module names
modules = cre.findall(node.get_text_contents())

View file

@ -3,7 +3,7 @@
Used by several tools of `gettext` toolset.
"""
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -24,7 +24,7 @@ Used by several tools of `gettext` toolset.
# 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__ = "src/engine/SCons/Tool/GettextCommon.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Warnings
import re
@ -69,7 +69,7 @@ SCons.Warnings.enableWarningClass(MsgfmtNotFound)
#############################################################################
#############################################################################
class _POTargetFactory(object):
class _POTargetFactory:
""" A factory of `PO` target files.
Factory defaults differ from these of `SCons.Node.FS.FS`. We set `precious`
@ -198,19 +198,19 @@ class _POFileBuilder(BuilderBase):
# 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 not 'suffix' in kw:
if 'suffix' not in kw:
kw['suffix'] = '$POSUFFIX'
if not 'src_suffix' in kw:
if 'src_suffix' not in kw:
kw['src_suffix'] = '$POTSUFFIX'
if not 'src_builder' in kw:
if 'src_builder' not in kw:
kw['src_builder'] = '_POTUpdateBuilder'
if not 'single_source' in kw:
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 not 'target_factory' in kw:
if 'target_factory' not in kw:
kw['target_factory'] = _POTargetFactory(env, alias=alias).File
BuilderBase.__init__(self, **kw)
@ -269,7 +269,7 @@ def _translate(env, target=None, source=SCons.Environment._null, *args, **kw):
#############################################################################
#############################################################################
class RPaths(object):
class RPaths:
""" Callable object, which returns pathnames relative to SCons current
working directory.
@ -390,7 +390,7 @@ def _detect_xgettext(env):
""" Detects *xgettext(1)* binary """
if 'XGETTEXT' in env:
return env['XGETTEXT']
xgettext = env.Detect('xgettext');
xgettext = env.Detect('xgettext')
if xgettext:
return xgettext
raise SCons.Errors.StopError(XgettextNotFound, "Could not detect xgettext")
@ -409,7 +409,7 @@ def _detect_msginit(env):
""" Detects *msginit(1)* program. """
if 'MSGINIT' in env:
return env['MSGINIT']
msginit = env.Detect('msginit');
msginit = env.Detect('msginit')
if msginit:
return msginit
raise SCons.Errors.StopError(MsginitNotFound, "Could not detect msginit")
@ -428,7 +428,7 @@ def _detect_msgmerge(env):
""" Detects *msgmerge(1)* program. """
if 'MSGMERGE' in env:
return env['MSGMERGE']
msgmerge = env.Detect('msgmerge');
msgmerge = env.Detect('msgmerge')
if msgmerge:
return msgmerge
raise SCons.Errors.StopError(MsgmergeNotFound, "Could not detect msgmerge")
@ -447,7 +447,7 @@ def _detect_msgfmt(env):
""" Detects *msgmfmt(1)* program. """
if 'MSGFMT' in env:
return env['MSGFMT']
msgfmt = env.Detect('msgfmt');
msgfmt = env.Detect('msgfmt')
if msgfmt:
return msgfmt
raise SCons.Errors.StopError(MsgfmtNotFound, "Could not detect msgfmt")

View file

@ -5,7 +5,7 @@ Stuff for processing Java.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -27,16 +27,48 @@ Stuff for processing Java.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/JavaCommon.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import os.path
import re
import glob
java_parsing = 1
default_java_version = '1.4'
# a switch for which jdk versions to use the Scope state for smarter
# anonymous inner class parsing.
scopeStateVersions = ('1.8',)
# Glob patterns for use in finding where the JDK is.
# These are pairs, *dir_glob used in the general case,
# *version_dir_glob if matching only a specific version.
# For now only used for Windows.
java_win32_dir_glob = 'C:/Program Files*/Java/jdk*/bin'
# On windows, since Java 9, there is a dash between 'jdk' and the version
# string that wasn't there before. this glob should catch either way.
java_win32_version_dir_glob = 'C:/Program Files*/Java/jdk*%s*/bin'
# Glob patterns for use in finding where the JDK headers are.
# These are pairs, *dir_glob used in the general case,
# *version_dir_glob if matching only a specific version.
java_macos_include_dir_glob = '/System/Library/Frameworks/JavaVM.framework/Headers/'
java_macos_version_include_dir_glob = '/System/Library/Frameworks/JavaVM.framework/Versions/%s*/Headers/'
java_linux_include_dirs_glob = [
'/usr/lib/jvm/default-java/include',
'/usr/lib/jvm/java-*/include'
]
# Need to match path like below (from Centos 7)
# /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-0.el7_5.x86_64/include/
java_linux_version_include_dirs_glob = [
'/usr/lib/jvm/java-*-sun-%s*/include',
'/usr/lib/jvm/java-%s*-openjdk*/include',
'/usr/java/jdk%s*/include'
]
if java_parsing:
# Parse Java files for class names.
#
@ -55,17 +87,19 @@ if java_parsing:
# any alphanumeric token surrounded by angle brackets (generics);
# the multi-line comment begin and end tokens /* and */;
# array declarations "[]".
_reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"\{\}\;\.\(\)]|' +
r'\d*\.\d*|[A-Za-z_][\w\$\.]*|<[A-Za-z_]\w+>|' +
_reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"{\};.()]|' +
r'\d*\.\d*|[A-Za-z_][\w$.]*|<[A-Za-z_]\w+>|' +
r'/\*|\*/|\[\])')
class OuterState(object):
class OuterState:
"""The initial state for parsing a Java file for classes,
interfaces, and anonymous inner classes."""
def __init__(self, version=default_java_version):
if not version in ('1.1', '1.2', '1.3','1.4', '1.5', '1.6', '1.7',
'1.8', '5', '6'):
if version not in ('1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7',
'1.8', '5', '6', '9.0', '10.0', '11.0', '12.0'):
msg = "Java version %s not supported" % version
raise NotImplementedError(msg)
@ -116,7 +150,7 @@ if java_parsing:
self.skipState = ret
return ret
def __getAnonStack(self):
def _getAnonStack(self):
return self.anonStacksStack[-1]
def openBracket(self):
@ -132,8 +166,9 @@ if java_parsing:
self.anonStacksStack.pop()
self.stackBrackets.pop()
if len(self.stackAnonClassBrackets) and \
self.brackets == self.stackAnonClassBrackets[-1]:
self.__getAnonStack().pop()
self.brackets == self.stackAnonClassBrackets[-1] and \
self.version not in scopeStateVersions:
self._getAnonStack().pop()
self.stackAnonClassBrackets.pop()
def parseToken(self, token):
@ -145,7 +180,7 @@ if java_parsing:
self.openBracket()
elif token == '}':
self.closeBracket()
elif token in [ '"', "'" ]:
elif token in ['"', "'"]:
return IgnoreState(token, self)
elif token == "new":
# anonymous inner class
@ -171,28 +206,99 @@ if java_parsing:
if self.version in ('1.1', '1.2', '1.3', '1.4'):
clazz = self.listClasses[0]
self.listOutputs.append('%s$%d' % (clazz, self.nextAnon))
elif self.version in ('1.5', '1.6', '1.7', '1.8', '5', '6'):
elif self.version in ('1.5', '1.6', '1.7', '1.8', '5', '6', '9.0', '10.0', '11.0', '12.0'):
self.stackAnonClassBrackets.append(self.brackets)
className = []
className.extend(self.listClasses)
self.__getAnonStack()[-1] = self.__getAnonStack()[-1] + 1
for anon in self.__getAnonStack():
self._getAnonStack()[-1] = self._getAnonStack()[-1] + 1
for anon in self._getAnonStack():
className.append(str(anon))
self.listOutputs.append('$'.join(className))
self.nextAnon = self.nextAnon + 1
self.__getAnonStack().append(0)
self._getAnonStack().append(0)
def setPackage(self, package):
self.package = package
class AnonClassState(object):
class ScopeState:
"""
A state that parses code within a scope normally,
within the confines of a scope.
"""
def __init__(self, old_state):
self.outer_state = old_state.outer_state
self.old_state = old_state
self.brackets = 0
def __getClassState(self):
try:
return self.classState
except AttributeError:
ret = ClassState(self)
self.classState = ret
return ret
def __getAnonClassState(self):
try:
return self.anonState
except AttributeError:
ret = SkipState(1, AnonClassState(self))
self.anonState = ret
return ret
def __getSkipState(self):
try:
return self.skipState
except AttributeError:
ret = SkipState(1, self)
self.skipState = ret
return ret
def openBracket(self):
self.brackets = self.brackets + 1
def closeBracket(self):
self.brackets = self.brackets - 1
def parseToken(self, token):
# if self.brackets == 0:
# return self.old_state.parseToken(token)
if token[:2] == '//':
return IgnoreState('\n', self)
elif token == '/*':
return IgnoreState('*/', self)
elif token == '{':
self.openBracket()
elif token == '}':
self.closeBracket()
if self.brackets == 0:
self.outer_state._getAnonStack().pop()
return self.old_state
elif token in ['"', "'"]:
return IgnoreState(token, self)
elif token == "new":
# anonymous inner class
return self.__getAnonClassState()
elif token == '.':
# Skip the attribute, it might be named "class", in which
# case we don't want to treat the following token as
# an inner class name...
return self.__getSkipState()
return self
class AnonClassState:
"""A state that looks for anonymous inner classes."""
def __init__(self, old_state):
# outer_state is always an instance of OuterState
self.outer_state = old_state.outer_state
self.old_state = old_state
self.brace_level = 0
def parseToken(self, token):
# This is an anonymous class if and only if the next
# non-whitespace token is a bracket. Everything between
@ -212,32 +318,40 @@ if java_parsing:
if token == 'new':
# look further for anonymous inner class
return SkipState(1, AnonClassState(self))
elif token in [ '"', "'" ]:
elif token in ['"', "'"]:
return IgnoreState(token, self)
elif token == ')':
self.brace_level = self.brace_level - 1
return self
if token == '{':
self.outer_state.addAnonClass()
if self.outer_state.version in scopeStateVersions:
return ScopeState(old_state=self.old_state).parseToken(token)
return self.old_state.parseToken(token)
class SkipState(object):
class SkipState:
"""A state that will skip a specified number of tokens before
reverting to the previous state."""
def __init__(self, tokens_to_skip, old_state):
self.tokens_to_skip = tokens_to_skip
self.old_state = old_state
def parseToken(self, token):
self.tokens_to_skip = self.tokens_to_skip - 1
if self.tokens_to_skip < 1:
return self.old_state
return self
class ClassState(object):
class ClassState:
"""A state we go into when we hit a class or interface keyword."""
def __init__(self, outer_state):
# outer_state is always an instance of OuterState
self.outer_state = outer_state
def parseToken(self, token):
# the next non-whitespace token should be the name of the class
if token == '\n':
@ -245,14 +359,14 @@ if java_parsing:
# If that's an inner class which is declared in a method, it
# requires an index prepended to the class-name, e.g.
# 'Foo$1Inner'
# http://scons.tigris.org/issues/show_bug.cgi?id=2087
# https://github.com/SCons/scons/issues/2087
if self.outer_state.localClasses and \
self.outer_state.stackBrackets[-1] > \
self.outer_state.stackBrackets[-2]+1:
self.outer_state.stackBrackets[-2] + 1:
locals = self.outer_state.localClasses[-1]
try:
idx = locals[token]
locals[token] = locals[token]+1
locals[token] = locals[token] + 1
except KeyError:
locals[token] = 1
token = str(locals[token]) + token
@ -261,29 +375,39 @@ if java_parsing:
self.outer_state.anonStacksStack.append([0])
return self.outer_state
class IgnoreState(object):
class IgnoreState:
"""A state that will ignore all tokens until it gets to a
specified token."""
def __init__(self, ignore_until, old_state):
self.ignore_until = ignore_until
self.old_state = old_state
def parseToken(self, token):
if self.ignore_until == token:
return self.old_state
return self
class PackageState(object):
class PackageState:
"""The state we enter when we encounter the package keyword.
We assume the next token will be the package name."""
def __init__(self, outer_state):
# outer_state is always an instance of OuterState
self.outer_state = outer_state
def parseToken(self, token):
self.outer_state.setPackage(token)
return self.outer_state
def parse_java_file(fn, version=default_java_version):
return parse_java(open(fn, 'r').read(), version)
with open(fn, 'r', encoding='utf-8') as f:
data = f.read()
return parse_java(data, version)
def parse_java(contents, version=default_java_version, trace=None):
"""Parse a .java file and return a double of package directory,
@ -315,7 +439,71 @@ else:
is that the file name matches the public class name, and that
the path to the file is the same as the package name.
"""
return os.path.split(file)
return os.path.split(fn)
def get_java_install_dirs(platform, version=None):
"""
Find the java jdk installation directories.
This list is intended to supply as "default paths" for use when looking
up actual java binaries.
:param platform: selector for search algorithm.
:param version: If specified, only look for java sdk's of this version
:return: list of default paths for java.
"""
paths = []
if platform == 'win32':
if version:
paths = glob.glob(java_win32_version_dir_glob % version)
else:
paths = glob.glob(java_win32_dir_glob)
else:
# other platforms, do nothing for now
pass
return sorted(paths)
def get_java_include_paths(env, javac, version):
"""
Find java include paths for JNI building.
:param env: construction environment, used to extract platform.
:param javac: path to detected javac.
:return: list of paths.
"""
paths = []
if not javac:
# there are no paths if we've not detected javac.
pass
elif env['PLATFORM'] == 'win32':
# on Windows, we have the right path to javac, so look locally
javac_bin_dir = os.path.dirname(javac)
java_inc_dir = os.path.normpath(os.path.join(javac_bin_dir, '..', 'include'))
paths = [java_inc_dir, os.path.join(java_inc_dir, 'win32')]
elif env['PLATFORM'] == 'darwin':
if not version:
paths = [java_macos_include_dir_glob]
else:
paths = sorted(glob.glob(java_macos_version_include_dir_glob % version))
else:
base_paths = []
if not version:
for p in java_linux_include_dirs_glob:
base_paths.extend(glob.glob(p))
else:
for p in java_linux_version_include_dirs_glob:
base_paths.extend(glob.glob(p % version))
for p in base_paths:
paths.extend([p, os.path.join(p, 'linux')])
# print("PATHS:%s"%paths)
return paths
# Local Variables:
# tab-width:4

View file

@ -0,0 +1,107 @@
This is the flow of the compiler detection logic:
External to MSCommon:
The Tool init modules, in their exists() routines, call -> msvc_exists(env)
At the moment, those modules are:
SCons/Tool/midl.py
SCons/Tool/mslib.py
SCons/Tool/mslink.py
SCons/Tool/msvc.py
SCons/Tool/msvs.py
env may contain a version request in MSVC_VERSION, but this is not used
in the detection that follows from msvc_exists(), only in the later
batch that starts with a call to msvc_setup_env().
Internal to MSCommon/vc.py:
+ MSCommon/vc.py:msvc_exists:
| vcs = cached_get_installed_vcs(env)
| returns True if vcs > 0
|
+-> MSCommon/vc.py:cached_get_installed_vcs:
| checks global if we've run previously, if so return it
| populate the global from -> get_installed_vcs(env)
|
+-> MSCommon/vc.py:get_installed_vcs:
| loop through "known" versions of msvc, granularity is maj.min
| check for product dir -> find_vc_pdir(env, ver)
|
+-> MSCommon/vc.py:find_vc_pdir:
| From the msvc-version to pdir mapping dict, get reg key base and value
| If value is none -> find_vc_pdir_vswhere(ver, env)
|
+-> MSCommon/vc.py:find_vc_pdir_vswhere:
| From the vc-version to VS-version mapping table get string
| Figure out where vswhere is -> msvc_find_vswhere()
| Use subprocess to call vswhere, return first line of match
/
| else get product directory from registry (<= 14.0)
/
| if we found one -> _check_cl_exists_in_vc_dir(env, pdir, ver)
|
+-> MSCommon/vc.py:_check_cl_exists_in_vc_dir:
| Figure out host/target pair
| if version > 14.0 get specific version by looking in
| pdir + Auxiliary/Build/Microsoft/VCToolsVersion/default.txt
| look for pdir + Tools/MSVC/{specver}/bin/host/target/cl.exe
| if 14.0 or less, "do older stuff"
All of this just got us a yes-no answer on whether /some/ msvc version
exists, but does populate __INSTALLED_VCS_RUN with all of the top-level
versions as noted for get_installed_vcs
Externally:
Once a module's exists() has been called (or, in the case of
clang/clangxx, after the compiler has been detected by other means -
those still expect the rest of the msvc chain but not cl.exe)
the module's generate() function calls -> msvc_setup_env_once(env)
Internally:
+ MSCommon/vc.py:msvc_setup_env_once:
| checks for environment flag MSVC_SETUP_RUN
| if not, -> msvc_setup_env(env) and set flag
|
+-+ MSCommon/vc.py:msvc_setup_env:
| set ver from -> get_default_version(env)
|
+-+ MSCommon/vc.py:get_default_version:
| if no version specified in env.MSVC_VERSION:
| return first entry from -> cached_get_installed_vcs(env)
| else return requested version
/
| get script from MSVC_USE_SCRIPT if set to a filename
| -> script_env(script)
|
+-+ MSCommon/vc.py:script_env:
| return (possibly cached) script variables matching script arg
/
| else -> msvc_find_valid_batch_script(env, version)
|
+-+ MSCommon/vc.py:msvc_find_valid_batch_script:
| Build a list of plausible target values, and loop through
| look for host + target -> find_batch_file(env, ver, host, target)
|
+-+ MSCommon/vc.py:find_batch_file:
| call -> find_vc_pdir (see above)
| use the return to construct a version-biased batfile path, check
/
| if not found, try sdk scripts (unknown if this is still useful)
Problems:
- For VS >= 2017, VS and VS are not 1:1, there can be many VC for one VS
- For vswhere-ready versions, detection does not proceed beyond the
product level ("2019") into individual "features" (individual msvc)
- As documented for MSVC_VERSION, compilers can only be requested if versions
are from the set in _VCVER, so 14.1 but not 14.16 or 14.16.27023
- Information found in the first pass (msvs_exists) isn't really
available anywhere except the cached version list, since we just
return true/false.
- Since msvc_exists chain of calls does not look at version, we
can proceed to compiler setup if *any* msvc was found, even if the
one requested wasn't found.

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -21,7 +21,7 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/MSCommon/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """
Common functions for Microsoft Visual Studio and Visual C/C++.
@ -42,7 +42,8 @@ from SCons.Tool.MSCommon.sdk import mssdk_exists, \
from SCons.Tool.MSCommon.vc import msvc_exists, \
msvc_setup_env, \
msvc_setup_env_once, \
msvc_version_to_maj_min
msvc_version_to_maj_min, \
msvc_find_vswhere
from SCons.Tool.MSCommon.vs import get_default_version, \
get_vs_by_version, \

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -21,14 +21,14 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/MSCommon/arch.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """Module to define supported Windows chip architectures.
"""
import os
class ArchDefinition(object):
class ArchDefinition:
"""
A class for defining architecture-specific settings and logic.
"""
@ -37,22 +37,22 @@ class ArchDefinition(object):
self.synonyms = synonyms
SupportedArchitectureList = [
ArchitectureDefinition(
ArchDefinition(
'x86',
['i386', 'i486', 'i586', 'i686'],
),
ArchitectureDefinition(
ArchDefinition(
'x86_64',
['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'],
),
ArchitectureDefinition(
ArchDefinition(
'ia64',
['IA64'],
),
ArchitectureDefinition(
ArchDefinition(
'arm',
['ARM'],
),

View file

@ -2,7 +2,7 @@
Common helper functions for working with the Microsoft tool chain.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -23,36 +23,78 @@ Common helper functions for working with the Microsoft tool chain.
# 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 __future__ import print_function
__revision__ = "src/engine/SCons/Tool/MSCommon/common.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import copy
import json
import os
import subprocess
import re
import subprocess
import sys
import SCons.Util
# SCONS_MSCOMMON_DEBUG is internal-use so undocumented:
# set to '-' to print to console, else set to filename to log to
LOGFILE = os.environ.get('SCONS_MSCOMMON_DEBUG')
if LOGFILE == '-':
def debug(message):
print(message)
elif LOGFILE:
try:
import logging
except ImportError:
debug = lambda message: open(LOGFILE, 'a').write(message + '\n')
else:
logging.basicConfig(filename=LOGFILE, level=logging.DEBUG)
debug = logging.debug
logging.basicConfig(
# This looks like:
# 00109ms:MSCommon/vc.py:find_vc_pdir#447:
format=(
'%(relativeCreated)05dms'
':MSCommon/%(filename)s'
':%(funcName)s'
'#%(lineno)s'
':%(message)s: '
),
filename=LOGFILE,
level=logging.DEBUG)
debug = logging.getLogger(name=__name__).debug
else:
debug = lambda x: None
def debug(x): return None
# 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')
def read_script_env_cache():
""" fetch cached msvc env vars if requested, else return empty dict """
envcache = {}
if CONFIG_CACHE:
try:
with open(CONFIG_CACHE, 'r') as f:
envcache = json.load(f)
except FileNotFoundError:
# don't fail if no cache file, just proceed without it
pass
return envcache
def write_script_env_cache(cache):
""" write out cache of msvc env vars if requested """
if CONFIG_CACHE:
try:
with open(CONFIG_CACHE, 'w') as f:
json.dump(cache, f, indent=2)
except TypeError:
# data can't serialize to json, don't leave partial file
os.remove(CONFIG_CACHE)
except IOError:
# can't write the file, just skip
pass
_is_win64 = None
def is_win64():
"""Return true if running on windows 64 bits.
@ -87,6 +129,7 @@ def 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, False
otherwise."""
@ -99,6 +142,7 @@ def has_reg(value):
# Functions for fetching environment variable settings from batch files.
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
@ -113,22 +157,21 @@ def normalize_env(env, keys, force=False):
Note: the environment is copied."""
normenv = {}
if env:
for k in list(env.keys()):
normenv[k] = copy.deepcopy(env[k])
for k, v in env.items():
normenv[k] = copy.deepcopy(v)
for k in keys:
if k in os.environ and (force or not k in normenv):
if k in os.environ and (force or k not in normenv):
normenv[k] = os.environ[k]
# This shouldn't be necessary, since the default environment should include system32,
# but keep this here to be safe, since it's needed to find reg.exe which the MSVC
# bat scripts use.
sys32_dir = os.path.join(os.environ.get("SystemRoot",
os.environ.get("windir", r"C:\Windows\system32")),
"System32")
if sys32_dir not in normenv['PATH']:
normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_dir
# add some things to PATH to prevent problems:
# Shouldn't be necessary to add system32, since the default environment
# should include it, but keep this here to be safe (needed for reg.exe)
sys32_dir = os.path.join(
os.environ.get("SystemRoot", os.environ.get("windir", r"C:\Windows")), "System32"
)
if sys32_dir not in normenv["PATH"]:
normenv["PATH"] = normenv["PATH"] + os.pathsep + sys32_dir
# Without Wbem in PATH, vcvarsall.bat has a "'wmic' is not recognized"
# error starting with Visual Studio 2017, although the script still
@ -137,27 +180,39 @@ def normalize_env(env, keys, force=False):
if sys32_wbem_dir not in normenv['PATH']:
normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_wbem_dir
debug("PATH: %s"%normenv['PATH'])
# Without Powershell in PATH, an internal call to a telemetry
# function (starting with a VS2019 update) can fail
# Note can also set VSCMD_SKIP_SENDTELEMETRY to avoid this.
sys32_ps_dir = os.path.join(sys32_dir, r'WindowsPowerShell\v1.0')
if sys32_ps_dir not in normenv['PATH']:
normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_ps_dir
debug("PATH: %s" % normenv['PATH'])
return normenv
def get_output(vcbat, args = None, env = None):
def get_output(vcbat, args=None, env=None):
"""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=[])
# TODO: This is a hard-coded list of the variables that (may) need
# to be imported from os.environ[] for v[sc]*vars*.bat file
# execution to work. This list should really be either directly
# controlled by vc.py, or else derived from the common_tools_var
# settings in vs.py.
# TODO: Hard-coded list of the variables that (may) need to be
# imported from os.environ[] for the chain of development batch
# files to execute correctly. One call to vcvars*.bat may
# end up running a dozen or more scripts, changes not only with
# each release but with what is installed at the time. We think
# in modern installations most are set along the way and don't
# need to be picked from the env, but include these for safety's sake.
# Any VSCMD variables definitely are picked from the env and
# control execution in interesting ways.
# Note these really should be unified - either controlled by vs.py,
# or synced with the the common_tools_var # settings in vs.py.
vs_vc_vars = [
'COMSPEC',
# VS100 and VS110: Still set, but modern MSVC setup scripts will
# discard these if registry has values. However Intel compiler setup
# script still requires these as of 2013/2014.
'COMSPEC', # path to "shell"
'VS160COMNTOOLS', # path to common tools for given version
'VS150COMNTOOLS',
'VS140COMNTOOLS',
'VS120COMNTOOLS',
'VS110COMNTOOLS',
@ -167,6 +222,8 @@ def get_output(vcbat, args = None, env = None):
'VS71COMNTOOLS',
'VS70COMNTOOLS',
'VS60COMNTOOLS',
'VSCMD_DEBUG', # enable logging and other debug aids
'VSCMD_SKIP_SENDTELEMETRY',
]
env['ENV'] = normalize_env(env['ENV'], vs_vc_vars, force=False)
@ -188,32 +245,54 @@ def get_output(vcbat, args = None, env = None):
# Use the .stdout and .stderr attributes directly because the
# .communicate() method uses the threading module on Windows
# and won't work under Pythons not built with threading.
with popen.stdout:
stdout = popen.stdout.read()
with popen.stderr:
stderr = popen.stderr.read()
# Extra debug logic, uncomment if necessary
# debug('get_output():stdout:%s'%stdout)
# debug('get_output():stderr:%s'%stderr)
# debug('stdout:%s' % stdout)
# debug('stderr:%s' % stderr)
# Ongoing problems getting non-corrupted text led to this
# changing to "oem" from "mbcs" - the scripts run presumably
# attached to a console, so some particular rules apply.
# Unfortunately, "oem" not defined in Python 3.5, so get another way
if sys.version_info.major == 3 and sys.version_info.minor < 6:
from ctypes import windll
OEM = "cp{}".format(windll.kernel32.GetConsoleOutputCP())
else:
OEM = "oem"
if stderr:
# TODO: find something better to do with stderr;
# this at least prevents errors from getting swallowed.
import sys
sys.stderr.write(stderr)
sys.stderr.write(stderr.decode(OEM))
if popen.wait() != 0:
raise IOError(stderr.decode("mbcs"))
raise IOError(stderr.decode(OEM))
output = stdout.decode("mbcs")
return output
return stdout.decode(OEM)
def parse_output(output, keep=("INCLUDE", "LIB", "LIBPATH", "PATH")):
KEEPLIST = (
"INCLUDE",
"LIB",
"LIBPATH",
"PATH",
"VSCMD_ARG_app_plat",
"VCINSTALLDIR", # needed by clang -VS 2017 and newer
"VCToolsInstallDir", # needed by clang - VS 2015 and older
)
def parse_output(output, keep=KEEPLIST):
"""
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 pat_list the associated list of paths
# keep, and path_list the associated list of paths
dkeep = dict([(i, []) for i in keep])
# rdk will keep the regex to match the .bat file output line starts

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -20,7 +20,7 @@
# 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__ = "src/engine/SCons/Tool/MSCommon/netframework.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """
"""
@ -68,7 +68,7 @@ def query_versions():
# sequence comparison in python is lexicographical
# which is exactly what we want.
# Note we sort backwards so the highest version is first.
return cmp(bbl,aal)
return (aal > bbl) - (aal < bbl)
versions.sort(versrt)
else:

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -21,7 +21,7 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__revision__ = "src/engine/SCons/Tool/MSCommon/sdk.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """Module to detect the Platform/Windows SDK
@ -33,9 +33,7 @@ import os
import SCons.Errors
import SCons.Util
from . import common
debug = common.debug
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:
@ -58,7 +56,7 @@ _CURINSTALLED_SDK_HKEY_ROOT = \
r"Software\Microsoft\Microsoft SDKs\Windows\CurrentInstallFolder"
class SDKDefinition(object):
class SDKDefinition:
"""
An abstract base class for trying to find installed SDK directories.
"""
@ -79,7 +77,7 @@ class SDKDefinition(object):
debug('find_sdk_dir(): checking registry:{}'.format(hkey))
try:
sdk_dir = common.read_reg(hkey)
sdk_dir = read_reg(hkey)
except SCons.Util.WinError as e:
debug('find_sdk_dir(): no SDK registry key {}'.format(repr(hkey)))
return None
@ -110,19 +108,19 @@ class SDKDefinition(object):
""" Return the script to initialize the VC compiler installed by SDK
"""
if (host_arch == 'amd64' and target_arch == 'x86'):
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):
if host_arch != target_arch:
arch_string='%s_%s'%(host_arch,target_arch)
debug("sdk.py: get_sdk_vc_script():arch_string:%s host_arch:%s target_arch:%s"%(arch_string,
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("sdk.py: get_sdk_vc_script():file:%s"%file)
debug("get_sdk_vc_script():file:%s"%file)
return file
class WindowsSDK(SDKDefinition):
@ -178,6 +176,16 @@ SDK100VCSetupScripts = {'x86' : r'bin\vcvars32.bat',
#
# 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',
@ -276,14 +284,14 @@ InstalledSDKMap = None
def get_installed_sdks():
global InstalledSDKList
global InstalledSDKMap
debug('sdk.py:get_installed_sdks()')
debug('get_installed_sdks()')
if InstalledSDKList is None:
InstalledSDKList = []
InstalledSDKMap = {}
for sdk in SupportedSDKList:
debug('MSCommon/sdk.py: trying to find SDK %s' % sdk.version)
debug('trying to find SDK %s' % sdk.version)
if sdk.get_sdk_dir():
debug('MSCommon/sdk.py:found SDK %s' % sdk.version)
debug('found SDK %s' % sdk.version)
InstalledSDKList.append(sdk)
InstalledSDKMap[sdk.version] = sdk
return InstalledSDKList
@ -336,13 +344,13 @@ def get_default_sdk():
return InstalledSDKList[0]
def mssdk_setup_env(env):
debug('sdk.py:mssdk_setup_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('sdk.py:mssdk_setup_env: Using MSSDK_DIR:{}'.format(sdk_dir))
debug('mssdk_setup_env: Using MSSDK_DIR:{}'.format(sdk_dir))
elif 'MSSDK_VERSION' in env:
sdk_version = env['MSSDK_VERSION']
if sdk_version is None:
@ -354,22 +362,22 @@ def mssdk_setup_env(env):
msg = "SDK version %s is not installed" % sdk_version
raise SCons.Errors.UserError(msg)
sdk_dir = mssdk.get_sdk_dir()
debug('sdk.py:mssdk_setup_env: Using MSSDK_VERSION:%s'%sdk_dir)
debug('mssdk_setup_env: Using MSSDK_VERSION:%s'%sdk_dir)
elif 'MSVS_VERSION' in env:
msvs_version = env['MSVS_VERSION']
debug('sdk.py:mssdk_setup_env:Getting MSVS_VERSION from env:%s'%msvs_version)
debug('mssdk_setup_env:Getting MSVS_VERSION from env:%s'%msvs_version)
if msvs_version is None:
debug('sdk.py:mssdk_setup_env thinks 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)
debug('sdk.py:mssdk_setup_env:msvs is :%s'%msvs)
debug('mssdk_setup_env:msvs is :%s'%msvs)
if not msvs:
debug('sdk.py:mssdk_setup_env: no VS version detected, bailingout:%s'%msvs)
debug('mssdk_setup_env: no VS version detected, bailingout:%s'%msvs)
return
sdk_version = msvs.sdk_version
debug('sdk.py:msvs.sdk_version is %s'%sdk_version)
debug('msvs.sdk_version is %s'%sdk_version)
if not sdk_version:
return
mssdk = get_sdk_by_version(sdk_version)
@ -378,13 +386,13 @@ def mssdk_setup_env(env):
if not mssdk:
return
sdk_dir = mssdk.get_sdk_dir()
debug('sdk.py:mssdk_setup_env: Using MSVS_VERSION:%s'%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('sdk.py:mssdk_setup_env: not using any env values. sdk_dir:%s'%sdk_dir)
debug('mssdk_setup_env: not using any env values. sdk_dir:%s'%sdk_dir)
set_sdk_by_directory(env, sdk_dir)

View file

@ -0,0 +1,954 @@
#
# __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.
#
# TODO:
# * gather all the information from a single vswhere call instead
# of calling repeatedly (use json format?)
# * support passing/setting location for vswhere in env.
# * supported arch for versions: for old versions of batch file without
# argument, giving bogus argument cannot be detected, so we have to hardcode
# this here
# * print warning when msvc version specified but not found
# * find out why warning do not print
# * test on 64 bits XP + VS 2005 (and VS 6 if possible)
# * SDK
# * Assembly
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """Module for Visual C/C++ detection and configuration.
"""
import SCons.compat
import SCons.Util
import subprocess
import os
import platform
import sys
from string import digits as string_digits
from subprocess import PIPE
import SCons.Warnings
from SCons.Tool import find_program_path
from . import common
from .common import CONFIG_CACHE, debug
from .sdk import get_installed_sdks
class VisualCException(Exception):
pass
class UnsupportedVersion(VisualCException):
pass
class MSVCUnsupportedHostArch(VisualCException):
pass
class MSVCUnsupportedTargetArch(VisualCException):
pass
class MissingConfiguration(VisualCException):
pass
class NoVersionFound(VisualCException):
pass
class BatchFileExecutionError(VisualCException):
pass
# Dict to 'canonalize' the arch
_ARCH_TO_CANONICAL = {
"amd64" : "amd64",
"emt64" : "amd64",
"i386" : "x86",
"i486" : "x86",
"i586" : "x86",
"i686" : "x86",
"ia64" : "ia64", # deprecated
"itanium" : "ia64", # deprecated
"x86" : "x86",
"x86_64" : "amd64",
"arm" : "arm",
"arm64" : "arm64",
"aarch64" : "arm64",
}
# Starting with 14.1 (aka VS2017), the tools are organized by host directory.
# subdirs for each target. They are now in .../VC/Auxuiliary/Build.
# Note 2017 Express uses Hostx86 even if it's on 64-bit Windows,
# not reflected in this table.
_HOST_TARGET_TO_CL_DIR_GREATER_THAN_14 = {
("amd64","amd64") : ("Hostx64","x64"),
("amd64","x86") : ("Hostx64","x86"),
("amd64","arm") : ("Hostx64","arm"),
("amd64","arm64") : ("Hostx64","arm64"),
("x86","amd64") : ("Hostx86","x64"),
("x86","x86") : ("Hostx86","x86"),
("x86","arm") : ("Hostx86","arm"),
("x86","arm64") : ("Hostx86","arm64"),
}
# before 14.1 (VS2017): the original x86 tools are in the tools dir,
# any others are in a subdir named by the host/target pair,
# or just a single word if host==target
_HOST_TARGET_TO_CL_DIR = {
("amd64","amd64") : "amd64",
("amd64","x86") : "amd64_x86",
("amd64","arm") : "amd64_arm",
("amd64","arm64") : "amd64_arm64",
("x86","amd64") : "x86_amd64",
("x86","x86") : "",
("x86","arm") : "x86_arm",
("x86","arm64") : "x86_arm64",
("arm","arm") : "arm",
}
# 14.1 (VS2017) and later:
# Given a (host, target) tuple, return the batch file to look for.
# We can't rely on returning an arg to use for vcvarsall.bat,
# because that script will run even if given a pair that isn't installed.
# Targets that already look like a pair are pseudo targets that
# effectively mean to skip whatever the host was specified as.
_HOST_TARGET_TO_BAT_ARCH_GT14 = {
("amd64", "amd64"): "vcvars64.bat",
("amd64", "x86"): "vcvarsamd64_x86.bat",
("amd64", "x86_amd64"): "vcvarsx86_amd64.bat",
("amd64", "x86_x86"): "vcvars32.bat",
("amd64", "arm"): "vcvarsamd64_arm.bat",
("amd64", "x86_arm"): "vcvarsx86_arm.bat",
("amd64", "arm64"): "vcvarsamd64_arm64.bat",
("amd64", "x86_arm64"): "vcvarsx86_arm64.bat",
("x86", "x86"): "vcvars32.bat",
("x86", "amd64"): "vcvarsx86_amd64.bat",
("x86", "x86_amd64"): "vcvarsx86_amd64.bat",
("x86", "arm"): "vcvarsx86_arm.bat",
("x86", "x86_arm"): "vcvarsx86_arm.bat",
("x86", "arm64"): "vcvarsx86_arm64.bat",
("x86", "x86_arm64"): "vcvarsx86_arm64.bat",
}
# before 14.1 (VS2017):
# Given a (host, target) tuple, return the argument for the bat file;
# Both host and target should be canoncalized.
# If the target already looks like a pair, return it - these are
# pseudo targets (mainly used by Express versions)
_HOST_TARGET_ARCH_TO_BAT_ARCH = {
("x86", "x86"): "x86",
("x86", "amd64"): "x86_amd64",
("x86", "x86_amd64"): "x86_amd64",
("amd64", "x86_amd64"): "x86_amd64", # This is present in (at least) VS2012 express
("amd64", "amd64"): "amd64",
("amd64", "x86"): "x86",
("amd64", "x86_x86"): "x86",
("x86", "ia64"): "x86_ia64", # gone since 14.0
("x86", "arm"): "x86_arm", # since 14.0
("x86", "arm64"): "x86_arm64", # since 14.1
("amd64", "arm"): "amd64_arm", # since 14.0
("amd64", "arm64"): "amd64_arm64", # since 14.1
("x86", "x86_arm"): "x86_arm", # since 14.0
("x86", "x86_arm64"): "x86_arm64", # since 14.1
("amd64", "x86_arm"): "x86_arm", # since 14.0
("amd64", "x86_arm64"): "x86_arm64", # since 14.1
}
_CL_EXE_NAME = 'cl.exe'
def get_msvc_version_numeric(msvc_version):
"""Get the raw version numbers from a MSVC_VERSION string, so it
could be cast to float or other numeric values. For example, '14.0Exp'
would get converted to '14.0'.
Args:
msvc_version: str
string representing the version number, could contain non
digit characters
Returns:
str: the value converted to a numeric only string
"""
return ''.join([x for x in msvc_version if x in string_digits + '.'])
def get_host_target(env):
host_platform = env.get('HOST_ARCH')
debug("HOST_ARCH:" + str(host_platform))
if not host_platform:
host_platform = platform.machine()
# Solaris returns i86pc for both 32 and 64 bit architectures
if host_platform == "i86pc":
if platform.architecture()[0] == "64bit":
host_platform = "amd64"
else:
host_platform = "x86"
# Retain user requested TARGET_ARCH
req_target_platform = env.get('TARGET_ARCH')
debug("HOST_ARCH:" + str(req_target_platform))
if req_target_platform:
# If user requested a specific platform then only try that one.
target_platform = req_target_platform
else:
target_platform = host_platform
try:
host = _ARCH_TO_CANONICAL[host_platform.lower()]
except KeyError:
msg = "Unrecognized host architecture %s"
raise MSVCUnsupportedHostArch(msg % repr(host_platform))
try:
target = _ARCH_TO_CANONICAL[target_platform.lower()]
except KeyError:
all_archs = str(list(_ARCH_TO_CANONICAL.keys()))
raise MSVCUnsupportedTargetArch("Unrecognized target architecture %s\n\tValid architectures: %s" % (target_platform, all_archs))
return (host, target,req_target_platform)
# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the
# MSVC_VERSION documentation in Tool/msvc.xml.
_VCVER = ["14.2", "14.1", "14.1Exp", "14.0", "14.0Exp", "12.0", "12.0Exp", "11.0", "11.0Exp", "10.0", "10.0Exp", "9.0", "9.0Exp","8.0", "8.0Exp","7.1", "7.0", "6.0"]
# if using vswhere, configure command line arguments to probe for installed VC editions
_VCVER_TO_VSWHERE_VER = {
'14.2': [
["-version", "[16.0, 17.0)", ], # default: Enterprise, Professional, Community (order unpredictable?)
["-version", "[16.0, 17.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"], # BuildTools
],
'14.1': [
["-version", "[15.0, 16.0)", ], # default: Enterprise, Professional, Community (order unpredictable?)
["-version", "[15.0, 16.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"], # BuildTools
],
'14.1Exp': [
["-version", "[15.0, 16.0)", "-products", "Microsoft.VisualStudio.Product.WDExpress"], # Express
],
}
_VCVER_TO_PRODUCT_DIR = {
'14.2': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version
'14.1': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version
'14.1Exp': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version
'14.0' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')],
'14.0Exp' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir')],
'12.0' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'),
],
'12.0Exp' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'),
],
'11.0': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'),
],
'11.0Exp' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'),
],
'10.0': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'),
],
'10.0Exp' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'),
],
'9.0': [
(SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',),
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir',),
],
'9.0Exp' : [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'),
],
'8.0': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'),
],
'8.0Exp': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'),
],
'7.1': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'),
],
'7.0': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'),
],
'6.0': [
(SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'),
]
}
def msvc_version_to_maj_min(msvc_version):
msvc_version_numeric = get_msvc_version_numeric(msvc_version)
t = msvc_version_numeric.split(".")
if not len(t) == 2:
raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric))
try:
maj = int(t[0])
min = int(t[1])
return maj, min
except ValueError as e:
raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric))
def is_host_target_supported(host_target, msvc_version):
"""Check if (host, target) pair is supported for a VC version.
Only checks whether a given version *may* support the given
(host, target) pair, not that the toolchain is actually on the machine.
Args:
host_target: canonalized host-target pair, e.g.
("x86", "amd64") for cross compilation from 32- to 64-bit Windows.
msvc_version: Visual C++ version (major.minor), e.g. "10.0"
Returns:
True or False
"""
# We assume that any Visual Studio version supports x86 as a target
if host_target[1] != "x86":
maj, min = msvc_version_to_maj_min(msvc_version)
if maj < 8:
return False
return True
VSWHERE_PATHS = [os.path.join(p,'vswhere.exe') for p in [
os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"),
os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"),
os.path.expandvars(r"%ChocolateyInstall%\bin"),
]]
def msvc_find_vswhere():
""" Find the location of vswhere """
# For bug 3333: support default location of vswhere for both
# 64 and 32 bit windows installs.
# For bug 3542: also accommodate not being on C: drive.
# NB: this gets called from testsuite on non-Windows platforms.
# Whether that makes sense or not, don't break it for those.
vswhere_path = None
for pf in VSWHERE_PATHS:
if os.path.exists(pf):
vswhere_path = pf
break
return vswhere_path
def find_vc_pdir_vswhere(msvc_version, env=None):
""" Find the MSVC product directory using the vswhere program.
Args:
msvc_version: MSVC version to search for
env: optional to look up VSWHERE variable
Returns:
MSVC install dir or None
Raises:
UnsupportedVersion: if the version is not known by this file
"""
try:
vswhere_version = _VCVER_TO_VSWHERE_VER[msvc_version]
except KeyError:
debug("Unknown version of MSVC: %s" % msvc_version)
raise UnsupportedVersion("Unknown version %s" % msvc_version)
if env is None or not env.get('VSWHERE'):
vswhere_path = msvc_find_vswhere()
else:
vswhere_path = env.subst('$VSWHERE')
if vswhere_path is None:
return None
debug('VSWHERE: %s' % vswhere_path)
for vswhere_version_args in vswhere_version:
vswhere_cmd = [vswhere_path] + vswhere_version_args + ["-property", "installationPath"]
debug("running: %s" % vswhere_cmd)
#cp = subprocess.run(vswhere_cmd, capture_output=True) # 3.7+ only
cp = subprocess.run(vswhere_cmd, stdout=PIPE, stderr=PIPE)
if cp.stdout:
# vswhere could return multiple lines, e.g. if Build Tools
# and {Community,Professional,Enterprise} are both installed.
# We could define a way to pick the one we prefer, but since
# this data is currently only used to make a check for existence,
# returning the first hit should be good enough.
lines = cp.stdout.decode("mbcs").splitlines()
return os.path.join(lines[0], 'VC')
else:
# We found vswhere, but no install info available for this version
pass
return None
def find_vc_pdir(env, msvc_version):
"""Find the MSVC product directory for the given version.
Tries to look up the path using a registry key from the table
_VCVER_TO_PRODUCT_DIR; if there is no key, calls find_vc_pdir_wshere
for help instead.
Args:
msvc_version: str
msvc version (major.minor, e.g. 10.0)
Returns:
str: Path found in registry, or None
Raises:
UnsupportedVersion: if the version is not known by this file.
MissingConfiguration: found version but the directory is missing.
Both exceptions inherit from VisualCException.
"""
root = 'Software\\'
try:
hkeys = _VCVER_TO_PRODUCT_DIR[msvc_version]
except KeyError:
debug("Unknown version of MSVC: %s" % msvc_version)
raise UnsupportedVersion("Unknown version %s" % msvc_version)
for hkroot, key in hkeys:
try:
comps = None
if not key:
comps = find_vc_pdir_vswhere(msvc_version, env)
if not comps:
debug('no VC found for version {}'.format(repr(msvc_version)))
raise SCons.Util.WinError
debug('VC found: {}'.format(repr(msvc_version)))
return comps
else:
if common.is_win64():
try:
# ordinarily at win64, try Wow6432Node first.
comps = common.read_reg(root + 'Wow6432Node\\' + key, hkroot)
except SCons.Util.WinError as e:
# at Microsoft Visual Studio for Python 2.7, value is not in Wow6432Node
pass
if not comps:
# not Win64, or Microsoft Visual Studio for Python 2.7
comps = common.read_reg(root + key, hkroot)
except SCons.Util.WinError as e:
debug('no VC registry key {}'.format(repr(key)))
else:
debug('found VC in registry: {}'.format(comps))
if os.path.exists(comps):
return comps
else:
debug('reg says dir is {}, but it does not exist. (ignoring)'.format(comps))
raise MissingConfiguration("registry dir {} not found on the filesystem".format(comps))
return None
def find_batch_file(env,msvc_version,host_arch,target_arch):
"""
Find the location of the batch script which should set up the compiler
for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress
In newer (2017+) compilers, make use of the fact there are vcvars
scripts named with a host_target pair that calls vcvarsall.bat properly,
so use that and return an indication we don't need the argument
we would have computed to run vcvarsall.bat.
"""
pdir = find_vc_pdir(env, msvc_version)
if pdir is None:
raise NoVersionFound("No version of Visual Studio found")
debug('looking in {}'.format(pdir))
# filter out e.g. "Exp" from the version name
msvc_ver_numeric = get_msvc_version_numeric(msvc_version)
use_arg = True
vernum = float(msvc_ver_numeric)
if 7 <= vernum < 8:
pdir = os.path.join(pdir, os.pardir, "Common7", "Tools")
batfilename = os.path.join(pdir, "vsvars32.bat")
elif vernum < 7:
pdir = os.path.join(pdir, "Bin")
batfilename = os.path.join(pdir, "vcvars32.bat")
elif 8 <= vernum <= 14:
batfilename = os.path.join(pdir, "vcvarsall.bat")
else: # vernum >= 14.1 VS2017 and above
batfiledir = os.path.join(pdir, "Auxiliary", "Build")
targ = _HOST_TARGET_TO_BAT_ARCH_GT14[(host_arch, target_arch)]
batfilename = os.path.join(batfiledir, targ)
use_arg = False
if not os.path.exists(batfilename):
debug("Not found: %s" % batfilename)
batfilename = None
installed_sdks = get_installed_sdks()
for _sdk in installed_sdks:
sdk_bat_file = _sdk.get_sdk_vc_script(host_arch,target_arch)
if not sdk_bat_file:
debug("batch file not found:%s" % _sdk)
else:
sdk_bat_file_path = os.path.join(pdir,sdk_bat_file)
if os.path.exists(sdk_bat_file_path):
debug('sdk_bat_file_path:%s' % sdk_bat_file_path)
return (batfilename, use_arg, sdk_bat_file_path)
return (batfilename, use_arg, None)
__INSTALLED_VCS_RUN = None
_VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.default.txt']
_VC_TOOLS_VERSION_FILE = os.sep.join(_VC_TOOLS_VERSION_FILE_PATH)
def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version):
"""Return status of finding a cl.exe to use.
Locates cl in the vc_dir depending on TARGET_ARCH, HOST_ARCH and the
msvc version. TARGET_ARCH and HOST_ARCH can be extracted from the
passed env, unless it is None, in which case the native platform is
assumed for both host and target.
Args:
env: Environment
a construction environment, usually if this is passed its
because there is a desired TARGET_ARCH to be used when searching
for a cl.exe
vc_dir: str
the path to the VC dir in the MSVC installation
msvc_version: str
msvc version (major.minor, e.g. 10.0)
Returns:
bool:
"""
# determine if there is a specific target platform we want to build for and
# use that to find a list of valid VCs, default is host platform == target platform
# and same for if no env is specified to extract target platform from
if env:
(host_platform, target_platform, req_target_platform) = get_host_target(env)
else:
host_platform = platform.machine().lower()
target_platform = host_platform
host_platform = _ARCH_TO_CANONICAL[host_platform]
target_platform = _ARCH_TO_CANONICAL[target_platform]
debug('host platform %s, target platform %s for version %s' % (host_platform, target_platform, msvc_version))
ver_num = float(get_msvc_version_numeric(msvc_version))
# make sure the cl.exe exists meaning the tool is installed
if ver_num > 14:
# 2017 and newer allowed multiple versions of the VC toolset to be
# installed at the same time. This changes the layout.
# Just get the default tool version for now
#TODO: support setting a specific minor VC version
default_toolset_file = os.path.join(vc_dir, _VC_TOOLS_VERSION_FILE)
try:
with open(default_toolset_file) as f:
vc_specific_version = f.readlines()[0].strip()
except IOError:
debug('failed to read ' + default_toolset_file)
return False
except IndexError:
debug('failed to find MSVC version in ' + default_toolset_file)
return False
host_trgt_dir = _HOST_TARGET_TO_CL_DIR_GREATER_THAN_14.get((host_platform, target_platform), None)
if host_trgt_dir is None:
debug('unsupported host/target platform combo: (%s,%s)'%(host_platform, target_platform))
return False
cl_path = os.path.join(vc_dir, 'Tools','MSVC', vc_specific_version, 'bin', host_trgt_dir[0], host_trgt_dir[1], _CL_EXE_NAME)
debug('checking for ' + _CL_EXE_NAME + ' at ' + cl_path)
if os.path.exists(cl_path):
debug('found ' + _CL_EXE_NAME + '!')
return True
elif host_platform == "amd64" and host_trgt_dir[0] == "Hostx64":
# Special case: fallback to Hostx86 if Hostx64 was tried
# and failed. This is because VS 2017 Express running on amd64
# will look to our probe like the host dir should be Hostx64,
# but Express uses Hostx86 anyway.
# We should key this off the "x86_amd64" and related pseudo
# targets, but we don't see those in this function.
host_trgt_dir = ("Hostx86", host_trgt_dir[1])
cl_path = os.path.join(vc_dir, 'Tools','MSVC', vc_specific_version, 'bin', host_trgt_dir[0], host_trgt_dir[1], _CL_EXE_NAME)
debug('checking for ' + _CL_EXE_NAME + ' at ' + cl_path)
if os.path.exists(cl_path):
debug('found ' + _CL_EXE_NAME + '!')
return True
elif 14 >= ver_num >= 8:
# Set default value to be -1 as "", which is the value for x86/x86,
# yields true when tested if not host_trgt_dir
host_trgt_dir = _HOST_TARGET_TO_CL_DIR.get((host_platform, target_platform), None)
if host_trgt_dir is None:
debug('unsupported host/target platform combo')
return False
cl_path = os.path.join(vc_dir, 'bin', host_trgt_dir, _CL_EXE_NAME)
debug('checking for ' + _CL_EXE_NAME + ' at ' + cl_path)
cl_path_exists = os.path.exists(cl_path)
if not cl_path_exists and host_platform == 'amd64':
# older versions of visual studio only had x86 binaries,
# so if the host platform is amd64, we need to check cross
# compile options (x86 binary compiles some other target on a 64 bit os)
# Set default value to be -1 as "" which is the value for x86/x86 yields true when tested
# if not host_trgt_dir
host_trgt_dir = _HOST_TARGET_TO_CL_DIR.get(('x86', target_platform), None)
if host_trgt_dir is None:
return False
cl_path = os.path.join(vc_dir, 'bin', host_trgt_dir, _CL_EXE_NAME)
debug('checking for ' + _CL_EXE_NAME + ' at ' + cl_path)
cl_path_exists = os.path.exists(cl_path)
if cl_path_exists:
debug('found ' + _CL_EXE_NAME + '!')
return True
elif 8 > ver_num >= 6:
# quick check for vc_dir/bin and vc_dir/ before walk
# need to check root as the walk only considers subdirectories
for cl_dir in ('bin', ''):
cl_path = os.path.join(vc_dir, cl_dir, _CL_EXE_NAME)
if os.path.exists(cl_path):
debug(_CL_EXE_NAME + ' found %s' % cl_path)
return True
# not in bin or root: must be in a subdirectory
for cl_root, cl_dirs, _ in os.walk(vc_dir):
for cl_dir in cl_dirs:
cl_path = os.path.join(cl_root, cl_dir, _CL_EXE_NAME)
if os.path.exists(cl_path):
debug(_CL_EXE_NAME + ' found %s' % cl_path)
return True
return False
else:
# version not support return false
debug('unsupported MSVC version: ' + str(ver_num))
return False
def cached_get_installed_vcs(env=None):
global __INSTALLED_VCS_RUN
if __INSTALLED_VCS_RUN is None:
ret = get_installed_vcs(env)
__INSTALLED_VCS_RUN = ret
return __INSTALLED_VCS_RUN
def get_installed_vcs(env=None):
installed_versions = []
for ver in _VCVER:
debug('trying to find VC %s' % ver)
try:
VC_DIR = find_vc_pdir(env, ver)
if VC_DIR:
debug('found VC %s' % ver)
if _check_cl_exists_in_vc_dir(env, VC_DIR, ver):
installed_versions.append(ver)
else:
debug('no compiler found %s' % ver)
else:
debug('return None for ver %s' % ver)
except (MSVCUnsupportedTargetArch, MSVCUnsupportedHostArch):
# Allow this exception to propagate further as it should cause
# SCons to exit with an error code
raise
except VisualCException as e:
debug('did not find VC %s: caught exception %s' % (ver, str(e)))
return installed_versions
def reset_installed_vcs():
"""Make it try again to find VC. This is just for the tests."""
global __INSTALLED_VCS_RUN
__INSTALLED_VCS_RUN = None
# Running these batch files isn't cheap: most of the time spent in
# msvs.generate() is due to vcvars*.bat. In a build that uses "tools='msvs'"
# in multiple environments, for example:
# env1 = Environment(tools='msvs')
# env2 = Environment(tools='msvs')
# we can greatly improve the speed of the second and subsequent Environment
# (or Clone) calls by memoizing the environment variables set by vcvars*.bat.
#
# Updated: by 2018, vcvarsall.bat had gotten so expensive (vs2017 era)
# it was breaking CI builds because the test suite starts scons so many
# times and the existing memo logic only helped with repeated calls
# within the same scons run. Windows builds on the CI system were split
# into chunks to get around single-build time limits.
# With VS2019 it got even slower and an optional persistent cache file
# was introduced. The cache now also stores only the parsed vars,
# not the entire output of running the batch file - saves a bit
# of time not parsing every time.
script_env_cache = None
def script_env(script, args=None):
global script_env_cache
if script_env_cache is None:
script_env_cache = common.read_script_env_cache()
cache_key = "{}--{}".format(script, args)
cache_data = script_env_cache.get(cache_key, None)
if cache_data is None:
stdout = common.get_output(script, args)
# Stupid batch files do not set return code: we take a look at the
# beginning of the output for an error message instead
olines = stdout.splitlines()
if olines[0].startswith("The specified configuration type is missing"):
raise BatchFileExecutionError("\n".join(olines[:2]))
cache_data = common.parse_output(stdout)
script_env_cache[cache_key] = cache_data
# once we updated cache, give a chance to write out if user wanted
common.write_script_env_cache(script_env_cache)
return cache_data
def get_default_version(env):
msvc_version = env.get('MSVC_VERSION')
msvs_version = env.get('MSVS_VERSION')
debug('msvc_version:%s msvs_version:%s' % (msvc_version, msvs_version))
if msvs_version and not msvc_version:
SCons.Warnings.warn(
SCons.Warnings.DeprecatedWarning,
"MSVS_VERSION is deprecated: please use MSVC_VERSION instead ")
return msvs_version
elif msvc_version and msvs_version:
if not msvc_version == msvs_version:
SCons.Warnings.warn(
SCons.Warnings.VisualVersionMismatch,
"Requested msvc version (%s) and msvs version (%s) do " \
"not match: please use MSVC_VERSION only to request a " \
"visual studio version, MSVS_VERSION is deprecated" \
% (msvc_version, msvs_version))
return msvs_version
if not msvc_version:
installed_vcs = cached_get_installed_vcs(env)
debug('installed_vcs:%s' % installed_vcs)
if not installed_vcs:
#msg = 'No installed VCs'
#debug('msv %s' % repr(msg))
#SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg)
debug('No installed VCs')
return None
msvc_version = installed_vcs[0]
debug('using default installed MSVC version %s' % repr(msvc_version))
else:
debug('using specified MSVC version %s' % repr(msvc_version))
return msvc_version
def msvc_setup_env_once(env):
try:
has_run = env["MSVC_SETUP_RUN"]
except KeyError:
has_run = False
if not has_run:
msvc_setup_env(env)
env["MSVC_SETUP_RUN"] = True
def msvc_find_valid_batch_script(env, version):
"""Find and execute appropriate batch script to set up build env.
The MSVC build environment depends heavily on having the shell
environment set. SCons does not inherit that, and does not count
on that being set up correctly anyway, so it tries to find the right
MSVC batch script, or the right arguments to the generic batch script
vcvarsall.bat, and run that, so we have a valid environment to build in.
There are dragons here: the batch scripts don't fail (see comments
elsewhere), they just leave you with a bad setup, so try hard to
get it right.
"""
# Find the host, target, and if present the requested target:
platforms = get_host_target(env)
debug("host_platform %s, target_platform %s req_target_platform %s" % platforms)
host_platform, target_platform, req_target_platform = platforms
# Most combinations of host + target are straightforward.
# While all MSVC / Visual Studio tools are pysically 32-bit, they
# make it look like there are 64-bit tools if the host is 64-bit,
# so you can invoke the environment batch script to set up to build,
# say, amd64 host -> x86 target. Express versions are an exception:
# they always look 32-bit, so the batch scripts with 64-bit
# host parts are absent. We try to fix that up in a couple of ways.
# One is here: we make a table of "targets" to try, with the extra
# targets being tags that tell us to try a different "host" instead
# of the deduced host.
try_target_archs = [target_platform]
if req_target_platform in ('amd64', 'x86_64'):
try_target_archs.append('x86_amd64')
elif req_target_platform in ('x86',):
try_target_archs.append('x86_x86')
elif req_target_platform in ('arm',):
try_target_archs.append('x86_arm')
elif req_target_platform in ('arm64',):
try_target_archs.append('x86_arm64')
elif not req_target_platform:
if target_platform in ('amd64', 'x86_64'):
try_target_archs.append('x86_amd64')
# If the user hasn't specifically requested a TARGET_ARCH,
# and the TARGET_ARCH is amd64 then also try 32 bits
# if there are no viable 64 bit tools installed
try_target_archs.append('x86')
debug("host_platform: %s, try_target_archs: %s"%(host_platform, try_target_archs))
d = None
for tp in try_target_archs:
# Set to current arch.
env['TARGET_ARCH'] = tp
debug("trying target_platform:%s" % tp)
host_target = (host_platform, tp)
if not is_host_target_supported(host_target, version):
warn_msg = "host, target = %s not supported for MSVC version %s" % \
(host_target, version)
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
arg = _HOST_TARGET_ARCH_TO_BAT_ARCH[host_target]
# Try to locate a batch file for this host/target platform combo
try:
(vc_script, use_arg, sdk_script) = find_batch_file(env, version, host_platform, tp)
debug('vc_script:%s sdk_script:%s'%(vc_script,sdk_script))
except VisualCException as e:
msg = str(e)
debug('Caught exception while looking for batch file (%s)' % msg)
warn_msg = "VC version %s not installed. " + \
"C/C++ compilers are most likely not set correctly.\n" + \
" Installed versions are: %s"
warn_msg = warn_msg % (version, cached_get_installed_vcs(env))
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
continue
# Try to use the located batch file for this host/target platform combo
debug('use_script 2 %s, args:%s' % (repr(vc_script), arg))
found = None
if vc_script:
if not use_arg:
arg = '' # bat file will supply platform type
# Get just version numbers
maj, min = msvc_version_to_maj_min(version)
# VS2015+
if maj >= 14:
if env.get('MSVC_UWP_APP') == '1':
# Initialize environment variables with store/UWP paths
arg = (arg + ' store').lstrip()
try:
d = script_env(vc_script, args=arg)
found = vc_script
except BatchFileExecutionError as e:
debug('use_script 3: failed running VC script %s: %s: Error:%s'%(repr(vc_script),arg,e))
vc_script=None
continue
if not vc_script and sdk_script:
debug('use_script 4: trying sdk script: %s' % sdk_script)
try:
d = script_env(sdk_script)
found = sdk_script
except BatchFileExecutionError as e:
debug('use_script 5: failed running SDK script %s: Error:%s'%(repr(sdk_script), e))
continue
elif not vc_script and not sdk_script:
debug('use_script 6: Neither VC script nor SDK script found')
continue
debug("Found a working script/target: %s/%s"%(repr(found),arg))
break # We've found a working target_platform, so stop looking
# If we cannot find a viable installed compiler, reset the TARGET_ARCH
# To it's initial value
if not d:
env['TARGET_ARCH']=req_target_platform
return d
def msvc_setup_env(env):
debug('called')
version = get_default_version(env)
if version is None:
warn_msg = "No version of Visual Studio compiler found - C/C++ " \
"compilers most likely not set correctly"
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
return None
# XXX: we set-up both MSVS version for backward
# compatibility with the msvs tool
env['MSVC_VERSION'] = version
env['MSVS_VERSION'] = version
env['MSVS'] = {}
use_script = env.get('MSVC_USE_SCRIPT', True)
if SCons.Util.is_String(use_script):
debug('use_script 1 %s' % repr(use_script))
d = script_env(use_script)
elif use_script:
d = msvc_find_valid_batch_script(env,version)
debug('use_script 2 %s' % d)
if not d:
return d
else:
debug('MSVC_USE_SCRIPT set to False')
warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment " \
"set correctly."
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
return None
for k, v in d.items():
env.PrependENVPath(k, v, delete_existing=True)
debug("env['ENV']['%s'] = %s" % (k, env['ENV'][k]))
# final check to issue a warning if the compiler is not present
if not find_program_path(env, 'cl'):
debug("did not find " + _CL_EXE_NAME)
if CONFIG_CACHE:
propose = "SCONS_CACHE_MSVC_CONFIG caching enabled, remove cache file {} if out of date.".format(CONFIG_CACHE)
else:
propose = "It may need to be installed separately with Visual Studio."
warn_msg = "Could not find MSVC compiler 'cl'. {}".format(propose)
SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
def msvc_exists(env=None, version=None):
vcs = cached_get_installed_vcs(env)
if version is None:
return len(vcs) > 0
return version in vcs

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -21,7 +21,7 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/MSCommon/vs.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """Module to detect Visual Studio and/or Visual C/C++
"""
@ -40,7 +40,7 @@ from .common import debug, \
import SCons.Tool.MSCommon.vc
class VisualStudio(object):
class VisualStudio:
"""
An abstract base class for trying to find installed versions of
Visual Studio.
@ -55,62 +55,61 @@ class VisualStudio(object):
def find_batch_file(self):
vs_dir = self.get_vs_dir()
if not vs_dir:
debug('find_executable(): no 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('find_batch_file(): %s not on file system' % batch_file)
debug('%s not on file system' % batch_file)
return None
return batch_file
def find_vs_dir_by_vc(self):
SCons.Tool.MSCommon.vc.get_installed_vcs()
dir = SCons.Tool.MSCommon.vc.find_vc_pdir(self.vc_version)
def find_vs_dir_by_vc(self, env):
SCons.Tool.MSCommon.vc.get_installed_vcs(env)
dir = SCons.Tool.MSCommon.vc.find_vc_pdir(env, self.vc_version)
if not dir:
debug('find_vs_dir(): no installed VC %s' % self.vc_version)
debug('no installed VC %s' % self.vc_version)
return None
return dir
return os.path.abspath(os.path.join(dir, os.pardir))
def find_vs_dir_by_reg(self):
def find_vs_dir_by_reg(self, env):
root = 'Software\\'
if is_win64():
root = root + 'Wow6432Node\\'
for key in self.hkeys:
if key=='use_dir':
return self.find_vs_dir_by_vc()
return self.find_vs_dir_by_vc(env)
key = root + key
try:
comps = read_reg(key)
except SCons.Util.WinError as e:
debug('find_vs_dir_by_reg(): no VS registry key {}'.format(repr(key)))
debug('no VS registry key {}'.format(repr(key)))
else:
debug('find_vs_dir_by_reg(): found VS in registry: {}'.format(comps))
debug('found VS in registry: {}'.format(comps))
return comps
return None
def find_vs_dir(self):
def find_vs_dir(self, env):
""" Can use registry or location of VC to find vs dir
First try to find by registry, and if that fails find via VC dir
"""
if True:
vs_dir=self.find_vs_dir_by_reg()
return vs_dir
else:
return self.find_vs_dir_by_vc()
def find_executable(self):
vs_dir = self.get_vs_dir()
vs_dir=self.find_vs_dir_by_reg(env)
if not vs_dir:
debug('find_executable(): no vs_dir ({})'.format(vs_dir))
vs_dir = self.find_vs_dir_by_vc(env)
debug('found VS in ' + 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 ({})'.format(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('find_executable(): {} not on file system'.format(executable))
debug('{} not on file system'.format(executable))
return None
return executable
@ -122,21 +121,21 @@ class VisualStudio(object):
self._cache['batch_file'] = batch_file
return batch_file
def get_executable(self):
def get_executable(self, env=None):
try:
debug('get_executable using cache:%s'%self._cache['executable'])
debug('using cache:%s'%self._cache['executable'])
return self._cache['executable']
except KeyError:
executable = self.find_executable()
executable = self.find_executable(env)
self._cache['executable'] = executable
debug('get_executable not in cache:%s'%executable)
debug('not in cache:%s'%executable)
return executable
def get_vs_dir(self):
def get_vs_dir(self, env):
try:
return self._cache['vs_dir']
except KeyError:
vs_dir = self.find_vs_dir()
vs_dir = self.find_vs_dir(env)
self._cache['vs_dir'] = vs_dir
return vs_dir
@ -199,6 +198,18 @@ class VisualStudio(object):
# Tool/MSCommon/vc.py, and the MSVC_VERSION documentation in Tool/msvc.xml.
SupportedVSList = [
# 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"],
),
# Visual Studio 2017
VisualStudio('14.1',
vc_version='14.1',
@ -206,7 +217,20 @@ SupportedVSList = [
hkeys=[],
common_tools_var='VS150COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com',
batch_file_path=r'VC\Auxiliary\Build\vsvars32.bat',
# should be a fallback, prefer use vswhere installationPath
batch_file_path=r'Common7\Tools\VsDevCmd.bat',
supported_arch=['x86', 'amd64', "arm"],
),
# 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"],
),
@ -356,7 +380,7 @@ SupportedVSList = [
sdk_version='2003R2',
hkeys=[r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir'],
common_tools_var='VS70COMNTOOLS',
executable_path=r'IDE\devenv.com',
executable_path=r'Common7\IDE\devenv.com',
batch_file_path=r'Common7\Tools\vsvars32.bat',
default_dirname='Microsoft Visual Studio .NET',
supported_arch=['x86'],
@ -389,7 +413,7 @@ for vs in SupportedVSList:
InstalledVSList = None
InstalledVSMap = None
def get_installed_visual_studios():
def get_installed_visual_studios(env=None):
global InstalledVSList
global InstalledVSMap
if InstalledVSList is None:
@ -397,7 +421,7 @@ def get_installed_visual_studios():
InstalledVSMap = {}
for vs in SupportedVSList:
debug('trying to find VS %s' % vs.version)
if vs.get_executable():
if vs.get_executable(env):
debug('found VS %s' % vs.version)
InstalledVSList.append(vs)
InstalledVSMap[vs.version] = vs
@ -448,21 +472,21 @@ def reset_installed_visual_studios():
# for variable, directory in env_tuple_list:
# env.PrependENVPath(variable, directory)
def msvs_exists():
return (len(get_installed_visual_studios()) > 0)
def msvs_exists(env=None):
return (len(get_installed_visual_studios(env)) > 0)
def get_vs_by_version(msvs):
global InstalledVSMap
global SupportedVSMap
debug('vs.py:get_vs_by_version()')
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()
vs = InstalledVSMap.get(msvs)
debug('InstalledVSMap:%s'%InstalledVSMap)
debug('vs.py:get_vs_by_version: found vs:%s'%vs)
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
@ -497,8 +521,8 @@ def get_default_version(env):
if versions:
env['MSVS_VERSION'] = versions[0] #use highest version by default
else:
debug('get_default_version: WARNING: no installed versions found, '
'using first in SupportedVSList (%s)'%SupportedVSList[0].version)
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']
@ -521,7 +545,7 @@ def get_default_arch(env):
if not msvs:
arch = 'x86'
elif not arch in msvs.get_supported_arch():
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))

View file

@ -7,7 +7,7 @@ Phar Lap ETS tool chain. Right now, this is linkloc and
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -29,7 +29,7 @@ Phar Lap ETS tool chain. Right now, this is linkloc and
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/PharLapCommon.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import os.path
@ -79,7 +79,8 @@ def getPharLapVersion():
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?")
mo = REGEX_ETS_VER.search(open(include_path, 'r').read())
with open(include_path, 'r') as f:
mo = REGEX_ETS_VER.search(f.read())
if mo:
return int(mo.group(1))
# Default return for Phar Lap 9.1

View file

@ -9,7 +9,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -31,7 +31,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/aixc++.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
#forward proxy to the preffered cxx version
from SCons.Tool.aixcxx import *

View file

@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/aixcc.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os.path

View file

@ -9,7 +9,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -31,7 +31,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/aixcxx.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os.path

View file

@ -1,4 +1,4 @@
"""engine.SCons.Tool.aixf77
"""SCons.Tool.aixf77
Tool-specific initialization for IBM Visual Age f77 Fortran compiler.
@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/aixf77.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os.path

View file

@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/aixlink.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import os.path

View file

@ -0,0 +1,224 @@
"""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.
"""
#
# __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
# 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 . import link
from SCons.Tool import ShLibSonameGenerator
class AppleLinkInvalidCurrentVersionException(Exception):
pass
class AppleLinkInvalidCompatibilityVersionException(Exception):
pass
def _applelib_versioned_lib_suffix(env, suffix, version):
"""For suffix='.dylib' and version='0.1.2' it returns '.0.1.2.dylib'"""
Verbose = False
if Verbose:
print("_applelib_versioned_lib_suffix: suffix={!r}".format(suffix))
print("_applelib_versioned_lib_suffix: version={!r}".format(version))
if version not in suffix:
suffix = "." + version + suffix
if Verbose:
print("_applelib_versioned_lib_suffix: return suffix={!r}".format(suffix))
return suffix
def _applelib_versioned_lib_soname(env, libnode, version, prefix, suffix, name_func):
"""For libnode='/optional/dir/libfoo.X.Y.Z.dylib' it returns 'libfoo.X.dylib'"""
Verbose = False
if Verbose:
print("_applelib_versioned_lib_soname: version={!r}".format(version))
name = name_func(env, libnode, version, prefix, suffix)
if Verbose:
print("_applelib_versioned_lib_soname: name={!r}".format(name))
major = version.split('.')[0]
(libname,_suffix) = name.split('.')
# if a desired SONAME was supplied, use that, otherwise create
# a default from the major version
if env.get('SONAME'):
soname = ShLibSonameGenerator(env, libnode)
else:
soname = '.'.join([libname, major, _suffix])
if Verbose:
print("_applelib_versioned_lib_soname: soname={!r}".format(soname))
return soname
def _applelib_versioned_shlib_soname(env, libnode, version, prefix, suffix):
return _applelib_versioned_lib_soname(env, libnode, version, prefix, suffix, link._versioned_shlib_name)
# User programmatically describes how SHLIBVERSION maps to values for compat/current.
_applelib_max_version_values = (65535, 255, 255)
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 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'] = SCons.Util.CLVar('$LINKFLAGS -dynamiclib')
env['SHLINKCOM'] = env['SHLINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
# see: http://docstore.mik.ua/orelly/unix3/mac/ch05_04.htm for proper naming
link._setup_versioned_lib_variables(env, tool = 'applelink')#, use_soname = use_soname)
env['LINKCALLBACKS'] = link._versioned_lib_callbacks()
env['LINKCALLBACKS']['VersionedShLibSuffix'] = _applelib_versioned_lib_suffix
env['LINKCALLBACKS']['VersionedShLibSoname'] = _applelib_versioned_shlib_soname
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'] = SCons.Util.CLVar('$LINKFLAGS -bundle')
env['LDMODULECOM'] = '$LDMODULE -o ${TARGET} $LDMODULEFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
env['__SHLIBVERSIONFLAGS'] = '${__libversionflags(__env__,"SHLIBVERSION","_SHLIBVERSIONFLAGS")}'
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

@ -9,7 +9,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -31,7 +31,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/ar.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Defaults
import SCons.Tool

View file

@ -1,5 +1,14 @@
"""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 (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -21,27 +30,17 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Options/EnumOption.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """Place-holder for the old SCons.Options module hierarchy
#
# forward proxy to the preferred asm version
#
import SCons.Tool.asm
This is for backwards compatibility. The new equivalent is the Variables/
class hierarchy. These will have deprecation warnings added (some day),
and will then be removed entirely (some day).
"""
# Resolve FLAKE8 F401 (make sider happy)
generate = SCons.Tool.asm.generate
exists = SCons.Tool.asm.exists
import SCons.Variables
import SCons.Warnings
warned = False
def EnumOption(*args, **kw):
global warned
if not warned:
msg = "The EnumOption() function is deprecated; use the EnumVariable() function instead."
SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg)
warned = True
return SCons.Variables.EnumVariable(*args, **kw)
# Local Variables:
# tab-width:4

View file

@ -9,7 +9,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -31,7 +31,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/as.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Defaults
import SCons.Tool

View file

@ -5,7 +5,7 @@ XXX
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -27,7 +27,7 @@ XXX
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/bcc32.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import os.path

View file

@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/c++.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
#forward proxy to the preffered cxx version

View file

@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/cc.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Tool
import SCons.Defaults

View file

@ -11,7 +11,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -33,7 +33,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# __revision__ = "src/engine/SCons/Tool/clang.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
# __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
# Based on SCons/Tool/gcc.py by Paweł Tomulik 2014 as a separate tool.
# Brought into the SCons mainline by Russel Winder 2017.
@ -45,6 +45,9 @@ import sys
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']
@ -52,11 +55,24 @@ 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']:
#pipe = SCons.Action._subproc(env, [env['CC'], '-dumpversion'],
@ -66,6 +82,7 @@ def generate(env):
stdout=subprocess.PIPE)
if pipe.wait() != 0: return
# clang -dumpversion is of no use
with pipe.stdout:
line = pipe.stdout.readline()
if sys.version_info[0] > 2:
line = line.decode()

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

@ -11,7 +11,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -33,7 +33,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# __revision__ = "src/engine/SCons/Tool/clangxx.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
# __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
# Based on SCons/Tool/g++.py by Paweł Tomulik 2014 as a separate tool.
# Brought into the SCons mainline by Russel Winder 2017.
@ -46,6 +46,9 @@ import sys
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++']
@ -66,14 +69,28 @@ def generate(env):
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']:
pipe = SCons.Action._subproc(env, [env['CXX'], '--version'],
stdin='devnull',
stderr='devnull',
stdout=subprocess.PIPE)
if pipe.wait() != 0: return
if pipe.wait() != 0:
return
# clang -dumpversion is of no use
with pipe.stdout:
line = pipe.stdout.readline()
if sys.version_info[0] > 2:
line = line.decode()

View file

@ -0,0 +1,250 @@
"""
Implements the ability for SCons to emit a compilation database for the MongoDB project. See
http://clang.llvm.org/docs/JSONCompilationDatabase.html for details on what a compilation
database is, and why you might want one. The only user visible entry point here is
'env.CompilationDatabase'. This method takes an optional 'target' to name the file that
should hold the compilation database, otherwise, the file defaults to compile_commands.json,
which is the name that most clang tools search for by default.
"""
# Copyright 2020 MongoDB Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
import json
import itertools
import SCons
from .cxx import CXXSuffixes
from .cc import CSuffixes
from .asm import ASSuffixes, ASPPSuffixes
# TODO: Is there a better way to do this than this global? Right now this exists so that the
# emitter we add can record all of the things it emits, so that the scanner for the top level
# compilation database can access the complete list, and also so that the writer has easy
# access to write all of the files. But it seems clunky. How can the emitter and the scanner
# communicate more gracefully?
__COMPILATION_DB_ENTRIES = []
# We make no effort to avoid rebuilding the entries. Someday, perhaps we could and even
# integrate with the cache, but there doesn't seem to be much call for it.
class __CompilationDbNode(SCons.Node.Python.Value):
def __init__(self, value):
SCons.Node.Python.Value.__init__(self, value)
self.Decider(changed_since_last_build_node)
def changed_since_last_build_node(child, target, prev_ni, node):
""" Dummy decider to force always building"""
return True
def make_emit_compilation_DB_entry(comstr):
"""
Effectively this creates a lambda function to capture:
* command line
* source
* target
:param comstr: unevaluated command line
:return: an emitter which has captured the above
"""
user_action = SCons.Action.Action(comstr)
def emit_compilation_db_entry(target, source, env):
"""
This emitter will be added to each c/c++ object build to capture the info needed
for clang tools
:param target: target node(s)
:param source: source node(s)
:param env: Environment for use building this node
:return: target(s), source(s)
"""
dbtarget = __CompilationDbNode(source)
entry = env.__COMPILATIONDB_Entry(
target=dbtarget,
source=[],
__COMPILATIONDB_UOUTPUT=target,
__COMPILATIONDB_USOURCE=source,
__COMPILATIONDB_UACTION=user_action,
__COMPILATIONDB_ENV=env,
)
# TODO: Technically, these next two lines should not be required: it should be fine to
# cache the entries. However, they don't seem to update properly. Since they are quick
# to re-generate disable caching and sidestep this problem.
env.AlwaysBuild(entry)
env.NoCache(entry)
__COMPILATION_DB_ENTRIES.append(dbtarget)
return target, source
return emit_compilation_db_entry
def compilation_db_entry_action(target, source, env, **kw):
"""
Create a dictionary with evaluated command line, target, source
and store that info as an attribute on the target
(Which has been stored in __COMPILATION_DB_ENTRIES array
:param target: target node(s)
:param source: source node(s)
:param env: Environment for use building this node
:param kw:
:return: None
"""
command = env["__COMPILATIONDB_UACTION"].strfunction(
target=env["__COMPILATIONDB_UOUTPUT"],
source=env["__COMPILATIONDB_USOURCE"],
env=env["__COMPILATIONDB_ENV"],
)
entry = {
"directory": env.Dir("#").abspath,
"command": command,
"file": env["__COMPILATIONDB_USOURCE"][0],
"output": env['__COMPILATIONDB_UOUTPUT'][0]
}
target[0].write(entry)
def write_compilation_db(target, source, env):
entries = []
use_abspath = env['COMPILATIONDB_USE_ABSPATH'] in [True, 1, 'True', 'true']
for s in __COMPILATION_DB_ENTRIES:
entry = s.read()
source_file = entry['file']
output_file = entry['output']
if use_abspath:
source_file = source_file.srcnode().abspath
output_file = output_file.abspath
else:
source_file = source_file.srcnode().path
output_file = output_file.path
path_entry = {'directory': entry['directory'],
'command': entry['command'],
'file': source_file,
'output': output_file}
entries.append(path_entry)
with open(target[0].path, "w") as output_file:
json.dump(
entries, output_file, sort_keys=True, indent=4, separators=(",", ": ")
)
def scan_compilation_db(node, env, path):
return __COMPILATION_DB_ENTRIES
def compilation_db_emitter(target, source, env):
""" fix up the source/targets """
# Someone called env.CompilationDatabase('my_targetname.json')
if not target and len(source) == 1:
target = source
# Default target name is compilation_db.json
if not target:
target = ['compile_commands.json', ]
# No source should have been passed. Drop it.
if source:
source = []
return target, source
def generate(env, **kwargs):
static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
env["COMPILATIONDB_COMSTR"] = kwargs.get(
"COMPILATIONDB_COMSTR", "Building compilation database $TARGET"
)
components_by_suffix = itertools.chain(
itertools.product(
CSuffixes,
[
(static_obj, SCons.Defaults.StaticObjectEmitter, "$CCCOM"),
(shared_obj, SCons.Defaults.SharedObjectEmitter, "$SHCCCOM"),
],
),
itertools.product(
CXXSuffixes,
[
(static_obj, SCons.Defaults.StaticObjectEmitter, "$CXXCOM"),
(shared_obj, SCons.Defaults.SharedObjectEmitter, "$SHCXXCOM"),
],
),
itertools.product(
ASSuffixes,
[(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASCOM")],
[(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASCOM")],
),
itertools.product(
ASPPSuffixes,
[(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASPPCOM")],
[(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASPPCOM")],
),
)
for entry in components_by_suffix:
suffix = entry[0]
builder, base_emitter, command = entry[1]
# Assumes a dictionary emitter
emitter = builder.emitter.get(suffix, False)
if emitter:
# We may not have tools installed which initialize all or any of
# cxx, cc, or assembly. If not skip resetting the respective emitter.
builder.emitter[suffix] = SCons.Builder.ListEmitter(
[emitter, make_emit_compilation_DB_entry(command), ]
)
env["BUILDERS"]["__COMPILATIONDB_Entry"] = SCons.Builder.Builder(
action=SCons.Action.Action(compilation_db_entry_action, None),
)
env["BUILDERS"]["CompilationDatabase"] = SCons.Builder.Builder(
action=SCons.Action.Action(write_compilation_db, "$COMPILATIONDB_COMSTR"),
target_scanner=SCons.Scanner.Scanner(
function=scan_compilation_db, node_class=None
),
emitter=compilation_db_emitter,
suffix='json',
)
env['COMPILATIONDB_USE_ABSPATH'] = False
def exists(env):
return True

View file

@ -1,11 +1,11 @@
"""engine.SCons.Tool.cvf
"""SCons.Tool.cvf
Tool-specific initialization for the Compaq Visual Fortran compiler.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -27,7 +27,7 @@ Tool-specific initialization for the Compaq Visual Fortran compiler.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/cvf.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
from . import fortran

View file

@ -8,7 +8,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -30,7 +30,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/cxx.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os.path

View file

@ -8,8 +8,6 @@ selection method.
"""
from __future__ import absolute_import, print_function
import re
import os
@ -133,7 +131,7 @@ def _versioned_lib_suffix(env, suffix, version):
if Verbose:
print("_versioned_lib_suffix: suffix= ", suffix)
print("_versioned_lib_suffix: version= ", version)
cygversion = re.sub('\.', '-', version)
cygversion = re.sub(r'\.', '-', version)
if not suffix.startswith('-' + cygversion):
suffix = '-' + cygversion + suffix
if Verbose:

View file

@ -9,7 +9,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -31,7 +31,7 @@ selection method.
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/default.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Tool

View file

@ -1,5 +1,3 @@
from __future__ import print_function
"""SCons.Tool.dmd
Tool-specific initialization for the Digital Mars D compiler.
@ -12,26 +10,47 @@ Evolved by Russel Winder (russel@winder.org.uk)
2010-02-07 onwards
Compiler variables:
DC - The name of the D compiler to use. Defaults to dmd or gdmd,
whichever is found.
DPATH - List of paths to search for import modules.
DVERSIONS - List of version tags to enable when compiling.
DDEBUG - List of debug tags to enable when compiling.
DC
The name of the D compiler to use.
Defaults to dmd or gdmd, whichever is found.
DPATH
List of paths to search for import modules.
DVERSIONS
List of version tags to enable when compiling.
DDEBUG
List of debug tags to enable when compiling.
Linker related variables:
LIBS - List of library files to link in.
DLINK - Name of the linker to use. Defaults to dmd or gdmd,
whichever is found.
DLINKFLAGS - List of linker flags.
LIBS
List of library files to link in.
DLINK
Name of the linker to use.
Defaults to dmd or gdmd, whichever is found.
DLINKFLAGS
List of linker flags.
Lib tool variables:
DLIB - Name of the lib tool to use. Defaults to lib.
DLIBFLAGS - List of flags to pass to the lib tool.
LIBS - Same as for the linker. (libraries to pull into the .lib)
DLIB
Name of the lib tool to use. Defaults to lib.
DLIBFLAGS
List of flags to pass to the lib tool.
LIBS
Same as for the linker. (libraries to pull into the .lib)
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -53,7 +72,7 @@ Lib tool variables:
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "src/engine/SCons/Tool/dmd.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
import subprocess

View file

@ -10,7 +10,7 @@ selection method.
"""
#
# Copyright (c) 2001 - 2017 The SCons Foundation
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@ -51,20 +51,14 @@ scriptpath = os.path.dirname(os.path.realpath(__file__))
# Local folder for the collection of DocBook XSLs
db_xsl_folder = 'docbook-xsl-1.76.1'
# Do we have libxml2/libxslt/lxml?
has_libxml2 = True
# Do we have lxml?
has_lxml = True
try:
import libxml2
import libxslt
except:
has_libxml2 = False
try:
import lxml
except:
except Exception:
has_lxml = False
# Set this to True, to prefer xsltproc over libxml2 and lxml
# Set this to True, to prefer xsltproc over lxml
prefer_xsltproc = False
# Regexs for parsing Docbook XML sources of MAN pages
@ -95,20 +89,12 @@ def __init_xsl_stylesheet(kw, env, user_xsl_var, default_path):
xsl_style = os.path.join(*path_args)
kw['DOCBOOK_XSL'] = xsl_style
def __select_builder(lxml_builder, libxml2_builder, cmdline_builder):
def __select_builder(lxml_builder, cmdline_builder):
""" Selects a builder, based on which Python modules are present. """
if prefer_xsltproc:
return cmdline_builder
if not has_libxml2:
# At the moment we prefer libxml2 over lxml, the latter can lead
# to conflicts when installed together with libxml2.
if has_lxml:
if has_lxml and not prefer_xsltproc:
return lxml_builder
else:
return cmdline_builder
return libxml2_builder
return cmdline_builder
def __ensure_suffix(t, suffix):
""" Ensure that the target t has the given suffix. """
@ -135,7 +121,7 @@ def __get_xml_text(root):
""" Return the text for the given root node (xml.dom.minidom). """
txt = ""
for e in root.childNodes:
if (e.nodeType == e.TEXT_NODE):
if e.nodeType == e.TEXT_NODE:
txt += e.data
return txt
@ -166,6 +152,8 @@ xsltproc_com_priority = ['xsltproc', 'saxon', 'saxon-xslt', 'xalan']
# see: http://saxon.sourceforge.net/
xsltproc_com = {'xsltproc' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE',
'saxon' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE $DOCBOOK_XSLTPROCPARAMS',
# Note if saxon-xslt is version 5.5 the proper arguments are: (swap order of docbook_xsl and source)
# 'saxon-xslt' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $SOURCE $DOCBOOK_XSL $DOCBOOK_XSLTPROCPARAMS',
'saxon-xslt' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE $DOCBOOK_XSLTPROCPARAMS',
'xalan' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -q -out $TARGET -xsl $DOCBOOK_XSL -in $SOURCE'}
xmllint_com = {'xmllint' : '$DOCBOOK_XMLLINT $DOCBOOK_XMLLINTFLAGS --xinclude $SOURCE > $TARGET'}
@ -205,7 +193,7 @@ def _detect(env):
if env.get('DOCBOOK_PREFER_XSLTPROC',''):
prefer_xsltproc = True
if ((not has_libxml2 and not has_lxml) or (prefer_xsltproc)):
if (not has_lxml) or prefer_xsltproc:
# Try to find the XSLT processors
__detect_cl_tool(env, 'DOCBOOK_XSLTPROC', xsltproc_com, xsltproc_com_priority)
__detect_cl_tool(env, 'DOCBOOK_XMLLINT', xmllint_com)
@ -231,18 +219,7 @@ def __xml_scan(node, env, path, arg):
return sentity_re.findall(contents)
xsl_file = os.path.join(scriptpath,'utils','xmldepend.xsl')
if not has_libxml2 or prefer_xsltproc:
if has_lxml and not prefer_xsltproc:
from lxml import etree
xsl_tree = etree.parse(xsl_file)
doc = etree.parse(str(node))
result = doc.xslt(xsl_tree)
depfiles = [x.strip() for x in str(result).splitlines() if x.strip() != "" and not x.startswith("<?xml ")]
return depfiles
else:
if not has_lxml or prefer_xsltproc:
# Try to call xsltproc
xsltproc = env.subst("$DOCBOOK_XSLTPROC")
if xsltproc and xsltproc.endswith('xsltproc'):
@ -255,20 +232,13 @@ def __xml_scan(node, env, path, arg):
contents = node.get_text_contents()
return include_re.findall(contents)
styledoc = libxml2.parseFile(xsl_file)
style = libxslt.parseStylesheetDoc(styledoc)
doc = libxml2.readFile(str(node), None, libxml2.XML_PARSE_NOENT)
result = style.applyStylesheet(doc, None)
from lxml import etree
depfiles = []
for x in str(result).splitlines():
if x.strip() != "" and not x.startswith("<?xml "):
depfiles.extend(x.strip().split())
style.freeStylesheet()
doc.freeDoc()
result.freeDoc()
xsl_tree = etree.parse(xsl_file)
doc = etree.parse(str(node))
result = doc.xslt(xsl_tree)
depfiles = [x.strip() for x in str(result).splitlines() if x.strip() != "" and not x.startswith("<?xml ")]
return depfiles
# Creating the instance of our XML dependency scanner
@ -285,7 +255,16 @@ def __generate_xsltproc_action(source, target, env, for_signature):
base_dir = env.subst('$base_dir')
if base_dir:
# Yes, so replace target path by its filename
return cmd.replace('$TARGET','${TARGET.file}')
return cmd.replace('$TARGET', os.path.join(base_dir, '${TARGET.file}'))
return cmd
def __generate_xsltproc_nobase_action(source, target, env, for_signature):
cmd = env['DOCBOOK_XSLTPROCCOM']
# Does the environment have a base_dir defined?
base_dir = env.subst('$base_dir')
if base_dir:
# Yes, so replace target path by its filename
return cmd.replace('$TARGET', '${TARGET.file}')
return cmd
@ -306,27 +285,6 @@ def __emit_xsl_basedir(target, source, env):
#
# Builders
#
def __build_libxml2(target, source, env):
"""
General XSLT builder (HTML/FO), using the libxml2 module.
"""
xsl_style = env.subst('$DOCBOOK_XSL')
styledoc = libxml2.parseFile(xsl_style)
style = libxslt.parseStylesheetDoc(styledoc)
doc = libxml2.readFile(str(source[0]),None,libxml2.XML_PARSE_NOENT)
# Support for additional parameters
parampass = {}
if parampass:
result = style.applyStylesheet(doc, parampass)
else:
result = style.applyStylesheet(doc, None)
style.saveResultToFilename(str(target[0]), result, 0)
style.freeStylesheet()
doc.freeDoc()
result.freeDoc()
return None
def __build_lxml(target, source, env):
"""
General XSLT builder (HTML/FO), using the lxml module.
@ -350,25 +308,40 @@ def __build_lxml(target, source, env):
result = transform(doc)
try:
of = open(str(target[0]), "wb")
of.write(of.write(etree.tostring(result, pretty_print=True)))
of.close()
except:
pass
with open(str(target[0]), "wb") as of:
of.write(etree.tostring(result, encoding="utf-8", pretty_print=True))
except Exception as e:
print("ERROR: Failed to write {}".format(str(target[0])))
print(e)
return None
def __xinclude_libxml2(target, source, env):
def __build_lxml_noresult(target, source, env):
"""
Resolving XIncludes, using the libxml2 module.
Specialized XSLT builder for transformations without a direct result where the Docbook
stylesheet itself creates the target file, using the lxml module.
"""
doc = libxml2.readFile(str(source[0]), None, libxml2.XML_PARSE_NOENT)
doc.xincludeProcessFlags(libxml2.XML_PARSE_NOENT)
doc.saveFile(str(target[0]))
doc.freeDoc()
from lxml import etree
xslt_ac = etree.XSLTAccessControl(read_file=True,
write_file=True,
create_dir=True,
read_network=False,
write_network=False)
xsl_style = env.subst('$DOCBOOK_XSL')
xsl_tree = etree.parse(xsl_style)
transform = etree.XSLT(xsl_tree, access_control=xslt_ac)
doc = etree.parse(str(source[0]))
# Support for additional parameters
parampass = {}
if parampass:
result = transform(doc, **parampass)
else:
result = transform(doc)
return None
def __xinclude_lxml(target, source, env):
"""
Resolving XIncludes, using the lxml module.
@ -380,27 +353,24 @@ def __xinclude_lxml(target, source, env):
try:
doc.write(str(target[0]), xml_declaration=True,
encoding="UTF-8", pretty_print=True)
except:
pass
except Exception as e:
print("ERROR: Failed to write {}".format(str(target[0])))
print(e)
return None
__libxml2_builder = SCons.Builder.Builder(
action = __build_libxml2,
src_suffix = '.xml',
source_scanner = docbook_xml_scanner,
emitter = __emit_xsl_basedir)
__lxml_builder = SCons.Builder.Builder(
action = __build_lxml,
src_suffix = '.xml',
source_scanner = docbook_xml_scanner,
emitter = __emit_xsl_basedir)
__xinclude_libxml2_builder = SCons.Builder.Builder(
action = __xinclude_libxml2,
suffix = '.xml',
__lxml_noresult_builder = SCons.Builder.Builder(
action = __build_lxml_noresult,
src_suffix = '.xml',
source_scanner = docbook_xml_scanner)
source_scanner = docbook_xml_scanner,
emitter = __emit_xsl_basedir)
__xinclude_lxml_builder = SCons.Builder.Builder(
action = __xinclude_lxml,
suffix = '.xml',
@ -413,6 +383,12 @@ __xsltproc_builder = SCons.Builder.Builder(
src_suffix = '.xml',
source_scanner = docbook_xml_scanner,
emitter = __emit_xsl_basedir)
__xsltproc_nobase_builder = SCons.Builder.Builder(
action = SCons.Action.CommandGeneratorAction(__generate_xsltproc_nobase_action,
{'cmdstr' : '$DOCBOOK_XSLTPROCCOMSTR'}),
src_suffix = '.xml',
source_scanner = docbook_xml_scanner,
emitter = __emit_xsl_basedir)
__xmllint_builder = SCons.Builder.Builder(
action = SCons.Action.Action('$DOCBOOK_XMLLINTCOM','$DOCBOOK_XMLLINTCOMSTR'),
suffix = '.xml',
@ -438,10 +414,9 @@ def DocbookEpub(env, target, source=None, *args, **kw):
function could be replaced by a call to the SCons Zip builder if support
was added for different compression formats for separate source nodes.
"""
zf = zipfile.ZipFile(str(target[0]), 'w')
mime_file = open('mimetype', 'w')
with zipfile.ZipFile(str(target[0]), 'w') as zf:
with open('mimetype', 'w') as mime_file:
mime_file.write('application/epub+zip')
mime_file.close()
zf.write(mime_file.name, compress_type = zipfile.ZIP_STORED)
for s in source:
if os.path.isfile(str(s)):
@ -455,7 +430,6 @@ def DocbookEpub(env, target, source=None, *args, **kw):
if os.path.isfile(path):
zf.write(path, os.path.relpath(path, str(env.get('ZIPROOT', ''))),
zipfile.ZIP_DEFLATED)
zf.close()
def add_resources(target, source, env):
"""Add missing resources to the OEBPS directory
@ -468,33 +442,7 @@ def DocbookEpub(env, target, source=None, *args, **kw):
return
hrefs = []
if has_libxml2:
nsmap = {'opf' : 'http://www.idpf.org/2007/opf'}
# Read file and resolve entities
doc = libxml2.readFile(content_file, None, 0)
opf = doc.getRootElement()
# Create xpath context
xpath_context = doc.xpathNewContext()
# Register namespaces
for key, val in nsmap.items():
xpath_context.xpathRegisterNs(key, val)
if hasattr(opf, 'xpathEval') and xpath_context:
# Use the xpath context
xpath_context.setContextNode(opf)
items = xpath_context.xpathEval(".//opf:item")
else:
items = opf.findall(".//{'http://www.idpf.org/2007/opf'}item")
for item in items:
if hasattr(item, 'prop'):
hrefs.append(item.prop('href'))
else:
hrefs.append(item.attrib['href'])
doc.freeDoc()
xpath_context.xpathFreeContext()
elif has_lxml:
if has_lxml:
from lxml import etree
opf = etree.parse(content_file)
@ -517,7 +465,7 @@ def DocbookEpub(env, target, source=None, *args, **kw):
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_EPUB', ['epub','docbook.xsl'])
# Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
__builder = __select_builder(__lxml_noresult_builder, __xsltproc_nobase_builder)
# Create targets
result = []
@ -558,7 +506,7 @@ def DocbookHtml(env, target, source=None, *args, **kw):
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTML', ['html','docbook.xsl'])
# Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
__builder = __select_builder(__lxml_builder, __xsltproc_builder)
# Create targets
result = []
@ -586,7 +534,7 @@ def DocbookHtmlChunked(env, target, source=None, *args, **kw):
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTMLCHUNKED', ['html','chunkfast.xsl'])
# Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
__builder = __select_builder(__lxml_noresult_builder, __xsltproc_nobase_builder)
# Detect base dir
base_dir = kw.get('base_dir', '')
@ -621,7 +569,7 @@ def DocbookHtmlhelp(env, target, source=None, *args, **kw):
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTMLHELP', ['htmlhelp','htmlhelp.xsl'])
# Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
__builder = __select_builder(__lxml_noresult_builder, __xsltproc_nobase_builder)
# Detect base dir
base_dir = kw.get('base_dir', '')
@ -650,7 +598,7 @@ def DocbookPdf(env, target, source=None, *args, **kw):
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_PDF', ['fo','docbook.xsl'])
# Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
__builder = __select_builder(__lxml_builder, __xsltproc_builder)
# Create targets
result = []
@ -674,7 +622,7 @@ def DocbookMan(env, target, source=None, *args, **kw):
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_MAN', ['manpages','docbook.xsl'])
# Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
__builder = __select_builder(__lxml_noresult_builder, __xsltproc_builder)
# Create targets
result = []
@ -697,11 +645,10 @@ def DocbookMan(env, target, source=None, *args, **kw):
for ref in node.getElementsByTagName('refname'):
outfiles.append(__get_xml_text(ref)+'.'+volnum)
except:
except Exception:
# Use simple regex parsing
f = open(__ensure_suffix(str(s),'.xml'), 'r')
with open(__ensure_suffix(str(s),'.xml'), 'r') as f:
content = f.read()
f.close()
for m in re_manvolnum.finditer(content):
volnum = m.group(1)
@ -741,7 +688,7 @@ def DocbookSlidesPdf(env, target, source=None, *args, **kw):
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_SLIDESPDF', ['slides','fo','plain.xsl'])
# Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
__builder = __select_builder(__lxml_builder, __xsltproc_builder)
# Create targets
result = []
@ -768,10 +715,10 @@ def DocbookSlidesHtml(env, target, source=None, *args, **kw):
source = [source]
# Init XSL stylesheet
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_SLIDESHTML', ['slides','html','plain.xsl'])
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_SLIDESHTML', ['slides','xhtml','plain.xsl'])
# Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
__builder = __select_builder(__lxml_builder, __xsltproc_builder)
# Detect base dir
base_dir = kw.get('base_dir', '')
@ -797,7 +744,7 @@ def DocbookXInclude(env, target, source, *args, **kw):
target, source = __extend_targets_sources(target, source)
# Setup builder
__builder = __select_builder(__xinclude_lxml_builder,__xinclude_libxml2_builder,__xmllint_builder)
__builder = __select_builder(__xinclude_lxml_builder,__xmllint_builder)
# Create targets
result = []
@ -817,7 +764,7 @@ def DocbookXslt(env, target, source=None, *args, **kw):
kw['DOCBOOK_XSL'] = kw.get('xsl', 'transform.xsl')
# Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
__builder = __select_builder(__lxml_builder, __xsltproc_builder)
# Create targets
result = []

View file

@ -0,0 +1,605 @@
<?xml version="1.0"?>
<!--
__COPYRIGHT__
This file is processed by the bin/SConsDoc.py module.
See its __doc__ string for a discussion of the format.
-->
<!DOCTYPE sconsdoc [
<!ENTITY % scons SYSTEM '../../../doc/scons.mod'>
%scons;
<!ENTITY % builders-mod SYSTEM '../../../doc/generated/builders.mod'>
%builders-mod;
<!ENTITY % functions-mod SYSTEM '../../../doc/generated/functions.mod'>
%functions-mod;
<!ENTITY % tools-mod SYSTEM '../../../doc/generated/tools.mod'>
%tools-mod;
<!ENTITY % variables-mod SYSTEM '../../../doc/generated/variables.mod'>
%variables-mod;
]>
<sconsdoc xmlns="http://www.scons.org/dbxsd/v1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd">
<tool name="docbook">
<summary>
<para>This tool tries to make working with Docbook in SCons a little easier.
It provides several toolchains for creating different output formats,
like HTML or PDF. Contained in the package is
a distribution of the Docbook XSL stylesheets as of version 1.76.1.
As long as you don't specify your own stylesheets for customization,
these official versions are picked as default...which should reduce
the inevitable setup hassles for you.
</para>
<para>Implicit dependencies to images and XIncludes are detected automatically
if you meet the HTML requirements. The additional
stylesheet <filename>utils/xmldepend.xsl</filename> by Paul DuBois is used for this purpose.
</para>
<para>Note, that there is no support for XML catalog resolving offered! This tool calls
the XSLT processors and PDF renderers with the stylesheets you specified, that's it.
The rest lies in your hands and you still have to know what you're doing when
resolving names via a catalog.
</para>
<para>For activating the tool "docbook", you have to add its name to the Environment constructor,
like this
</para>
<screen>env = Environment(tools=['docbook'])
</screen>
<para>On its startup, the Docbook tool tries to find a required <literal>xsltproc</literal> processor, and
a PDF renderer, e.g. <literal>fop</literal>. So make sure that these are added to your system's environment
<literal>PATH</literal> and can be called directly, without specifying their full path.
</para>
<para>For the most basic processing of Docbook to HTML, you need to have installed
</para>
<itemizedlist><listitem><para>the Python <literal>lxml</literal> binding to <literal>libxml2</literal>, or
</para>
</listitem>
<listitem><para>a standalone XSLT processor, currently detected are <literal>xsltproc</literal>, <literal>saxon</literal>, <literal>saxon-xslt</literal>
and <literal>xalan</literal>.
</para>
</listitem>
</itemizedlist>
<para>Rendering to PDF requires you to have one of the applications
<literal>fop</literal> or <literal>xep</literal> installed.
</para>
<para>Creating a HTML or PDF document is very simple and straightforward. Say
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookHtml('manual.html', 'manual.xml')
env.DocbookPdf('manual.pdf', 'manual.xml')
</screen>
<para>to get both outputs from your XML source <filename>manual.xml</filename>. As a shortcut, you can
give the stem of the filenames alone, like this:
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookHtml('manual')
env.DocbookPdf('manual')
</screen>
<para>and get the same result. Target and source lists are also supported:
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookHtml(['manual.html','reference.html'], ['manual.xml','reference.xml'])
</screen>
<para>or even
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookHtml(['manual','reference'])
</screen>
<important><para>Whenever you leave out the list of sources, you may not specify a file extension! The
Tool uses the given names as file stems, and adds the suffixes for target and source files
accordingly.
</para>
</important>
<para>The rules given above are valid for the Builders &b-link-DocbookHtml;,
&b-link-DocbookPdf;, &b-link-DocbookEpub;, &b-link-DocbookSlidesPdf; and &b-link-DocbookXInclude;. For the
&b-link-DocbookMan; transformation you
can specify a target name, but the actual output names are automatically
set from the <literal>refname</literal> entries in your XML source.
</para>
<para>The Builders &b-link-DocbookHtmlChunked;, &b-link-DocbookHtmlhelp; and
&b-link-DocbookSlidesHtml; are special, in that:
</para>
<orderedlist><listitem><para>they create a large set of files, where the exact names and their number depend
on the content of the source file, and
</para>
</listitem>
<listitem><para>the main target is always named <filename>index.html</filename>, i.e. the output name for the
XSL transformation is not picked up by the stylesheets.
</para>
</listitem>
</orderedlist>
<para>As a result, there is simply no use in specifying a target HTML name.
So the basic syntax for these builders is always:
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookHtmlhelp('manual')
</screen>
<para>If you want to use a specific XSL file, you can set the
additional <literal>xsl</literal> parameter to your
Builder call as follows:
</para>
<screen>env.DocbookHtml('other.html', 'manual.xml', xsl='html.xsl')
</screen>
<para>Since this may get tedious if you always use the same local naming for your customized XSL files,
e.g. <filename>html.xsl</filename> for HTML and <filename>pdf.xsl</filename> for PDF output, a set of
variables for setting the default XSL name is provided. These are:
</para>
<screen>DOCBOOK_DEFAULT_XSL_HTML
DOCBOOK_DEFAULT_XSL_HTMLCHUNKED
DOCBOOK_DEFAULT_XSL_HTMLHELP
DOCBOOK_DEFAULT_XSL_PDF
DOCBOOK_DEFAULT_XSL_EPUB
DOCBOOK_DEFAULT_XSL_MAN
DOCBOOK_DEFAULT_XSL_SLIDESPDF
DOCBOOK_DEFAULT_XSL_SLIDESHTML
</screen>
<para>and you can set them when constructing your environment:
</para>
<screen>env = Environment(tools=['docbook'],
DOCBOOK_DEFAULT_XSL_HTML='html.xsl',
DOCBOOK_DEFAULT_XSL_PDF='pdf.xsl')
env.DocbookHtml('manual') # now uses html.xsl
</screen>
</summary>
<sets>
<item>DOCBOOK_DEFAULT_XSL_HTML</item>
<item>DOCBOOK_DEFAULT_XSL_HTMLCHUNKED</item>
<item>DOCBOOK_DEFAULT_XSL_HTMLHELP</item>
<item>DOCBOOK_DEFAULT_XSL_PDF</item>
<item>DOCBOOK_DEFAULT_XSL_EPUB</item>
<item>DOCBOOK_DEFAULT_XSL_MAN</item>
<item>DOCBOOK_DEFAULT_XSL_SLIDESPDF</item>
<item>DOCBOOK_DEFAULT_XSL_SLIDESHTML</item>
<item>DOCBOOK_XSLTPROC</item>
<item>DOCBOOK_XMLLINT</item>
<item>DOCBOOK_FOP</item>
<item>DOCBOOK_XSLTPROCFLAGS</item>
<item>DOCBOOK_XMLLINTFLAGS</item>
<item>DOCBOOK_FOPFLAGS</item>
<item>DOCBOOK_XSLTPROCPARAMS</item>
<item>DOCBOOK_XSLTPROCCOM</item>
<item>DOCBOOK_XMLLINTCOM</item>
<item>DOCBOOK_FOPCOM</item>
</sets>
<uses>
<item>DOCBOOK_XSLTPROCCOMSTR</item>
<item>DOCBOOK_XMLLINTCOMSTR</item>
<item>DOCBOOK_FOPCOMSTR</item>
</uses>
</tool>
<cvar name="DOCBOOK_DEFAULT_XSL_HTML">
<summary>
<para>
The default XSLT file for the &b-link-DocbookHtml; builder within the
current environment, if no other XSLT gets specified via keyword.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_DEFAULT_XSL_HTMLCHUNKED">
<summary>
<para>
The default XSLT file for the &b-link-DocbookHtmlChunked; builder within the
current environment, if no other XSLT gets specified via keyword.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_DEFAULT_XSL_HTMLHELP">
<summary>
<para>
The default XSLT file for the &b-link-DocbookHtmlhelp; builder within the
current environment, if no other XSLT gets specified via keyword.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_DEFAULT_XSL_PDF">
<summary>
<para>
The default XSLT file for the &b-link-DocbookPdf; builder within the
current environment, if no other XSLT gets specified via keyword.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_DEFAULT_XSL_EPUB">
<summary>
<para>
The default XSLT file for the &b-link-DocbookEpub; builder within the
current environment, if no other XSLT gets specified via keyword.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_DEFAULT_XSL_MAN">
<summary>
<para>
The default XSLT file for the &b-link-DocbookMan; builder within the
current environment, if no other XSLT gets specified via keyword.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_DEFAULT_XSL_SLIDESPDF">
<summary>
<para>
The default XSLT file for the &b-link-DocbookSlidesPdf; builder within the
current environment, if no other XSLT gets specified via keyword.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_DEFAULT_XSL_SLIDESHTML">
<summary>
<para>
The default XSLT file for the &b-link-DocbookSlidesHtml; builder within the
current environment, if no other XSLT gets specified via keyword.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_XSLTPROC">
<summary>
<para>
The path to the external executable <literal>xsltproc</literal>
(or <literal>saxon</literal>, <literal>xalan</literal>), if one of them
is installed.
Note, that this is only used as last fallback for XSL transformations, if
no lxml Python binding can be imported in the current system.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_XMLLINT">
<summary>
<para>
The path to the external executable <literal>xmllint</literal>, if it's installed.
Note, that this is only used as last fallback for resolving
XIncludes, if no lxml Python binding can be imported
in the current system.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_FOP">
<summary>
<para>
The path to the PDF renderer <literal>fop</literal> or <literal>xep</literal>,
if one of them is installed (<literal>fop</literal> gets checked first).
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_XSLTPROCFLAGS">
<summary>
<para>
Additonal command-line flags for the external executable
<literal>xsltproc</literal> (or <literal>saxon</literal>,
<literal>xalan</literal>).
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_XMLLINTFLAGS">
<summary>
<para>
Additonal command-line flags for the external executable
<literal>xmllint</literal>.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_FOPFLAGS">
<summary>
<para>
Additonal command-line flags for the
PDF renderer <literal>fop</literal> or <literal>xep</literal>.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_XSLTPROCPARAMS">
<summary>
<para>
Additonal parameters that are not intended for the XSLT processor executable, but
the XSL processing itself. By default, they get appended at the end of the command line
for <literal>saxon</literal> and <literal>saxon-xslt</literal>, respectively.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_XSLTPROCCOM">
<summary>
<para>
The full command-line for the external executable
<literal>xsltproc</literal> (or <literal>saxon</literal>,
<literal>xalan</literal>).
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_XMLLINTCOM">
<summary>
<para>
The full command-line for the external executable
<literal>xmllint</literal>.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_FOPCOM">
<summary>
<para>
The full command-line for the
PDF renderer <literal>fop</literal> or <literal>xep</literal>.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_XSLTPROCCOMSTR">
<summary>
<para>
The string displayed when <literal>xsltproc</literal> is used to transform
an XML file via a given XSLT stylesheet.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_XMLLINTCOMSTR">
<summary>
<para>
The string displayed when <literal>xmllint</literal> is used to resolve
XIncludes for a given XML file.
</para>
</summary>
</cvar>
<cvar name="DOCBOOK_FOPCOMSTR">
<summary>
<para>
The string displayed when a renderer like <literal>fop</literal> or
<literal>xep</literal> is used to create PDF output from an XML file.
</para>
</summary>
</cvar>
<builder name="DocbookHtml">
<summary>
<para>
A pseudo-Builder, providing a Docbook toolchain for HTML output.
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookHtml('manual.html', 'manual.xml')
</example_commands>
<para>
or simply
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookHtml('manual')
</example_commands>
</summary>
</builder>
<builder name="DocbookHtmlChunked">
<summary>
<para>
A pseudo-Builder, providing a Docbook toolchain for chunked HTML output.
It supports the <literal>base.dir</literal> parameter. The
<filename>chunkfast.xsl</filename> file (requires &quot;EXSLT&quot;) is used as the
default stylesheet. Basic syntax:
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookHtmlChunked('manual')
</example_commands>
<para>
where <filename>manual.xml</filename> is the input file.
</para>
<para>If you use the <literal>root.filename</literal>
parameter in your own stylesheets you have to specify the new target name.
This ensures that the dependencies get correct, especially for the cleanup via <quote><literal>scons -c</literal></quote>:
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookHtmlChunked('mymanual.html', 'manual', xsl='htmlchunk.xsl')
</screen>
<para>Some basic support for the <literal>base.dir</literal> is provided. You
can add the <literal>base_dir</literal> keyword to your Builder
call, and the given prefix gets prepended to all the created filenames:
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookHtmlChunked('manual', xsl='htmlchunk.xsl', base_dir='output/')
</screen>
<para>Make sure that you don't forget the trailing slash for the base folder, else
your files get renamed only!
</para>
</summary>
</builder>
<builder name="DocbookHtmlhelp">
<summary>
<para>
A pseudo-Builder, providing a Docbook toolchain for HTMLHELP output.
Its basic syntax is:
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookHtmlhelp('manual')
</example_commands>
<para>
where <filename>manual.xml</filename> is the input file.
</para>
<para>If you use the <literal>root.filename</literal>
parameter in your own stylesheets you have to specify the new target name.
This ensures that the dependencies get correct, especially for the cleanup via <quote><literal>scons -c</literal></quote>:
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookHtmlhelp('mymanual.html', 'manual', xsl='htmlhelp.xsl')
</screen>
<para>Some basic support for the <literal>base.dir</literal> parameter
is provided. You can add the <literal>base_dir</literal> keyword to
your Builder call, and the given prefix gets prepended to all the
created filenames:
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookHtmlhelp('manual', xsl='htmlhelp.xsl', base_dir='output/')
</screen>
<para>Make sure that you don't forget the trailing slash for the base folder, else
your files get renamed only!
</para>
</summary>
</builder>
<builder name="DocbookPdf">
<summary>
<para>
A pseudo-Builder, providing a Docbook toolchain for PDF output.
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookPdf('manual.pdf', 'manual.xml')
</example_commands>
<para>
or simply
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookPdf('manual')
</example_commands>
</summary>
</builder>
<builder name="DocbookEpub">
<summary>
<para>
A pseudo-Builder, providing a Docbook toolchain for EPUB output.
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookEpub('manual.epub', 'manual.xml')
</example_commands>
<para>
or simply
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookEpub('manual')
</example_commands>
</summary>
</builder>
<builder name="DocbookMan">
<summary>
<para>
A pseudo-Builder, providing a Docbook toolchain for Man page output.
Its basic syntax is:
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookMan('manual')
</example_commands>
<para>
where <filename>manual.xml</filename> is the input file. Note, that
you can specify a target name, but the actual output names are automatically
set from the <literal>refname</literal> entries in your XML source.
</para>
</summary>
</builder>
<builder name="DocbookSlidesPdf">
<summary>
<para>
A pseudo-Builder, providing a Docbook toolchain for PDF slides output.
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookSlidesPdf('manual.pdf', 'manual.xml')
</example_commands>
<para>
or simply
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookSlidesPdf('manual')
</example_commands>
</summary>
</builder>
<builder name="DocbookSlidesHtml">
<summary>
<para>
A pseudo-Builder, providing a Docbook toolchain for HTML slides output.
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookSlidesHtml('manual')
</example_commands>
<para>If you use the <literal>titlefoil.html</literal> parameter in
your own stylesheets you have to give the new target name. This ensures
that the dependencies get correct, especially for the cleanup via
<quote><literal>scons -c</literal></quote>:
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookSlidesHtml('mymanual.html','manual', xsl='slideshtml.xsl')
</screen>
<para>Some basic support for the <literal>base.dir</literal> parameter
is provided. You
can add the <literal>base_dir</literal> keyword to your Builder
call, and the given prefix gets prepended to all the created filenames:
</para>
<screen>env = Environment(tools=['docbook'])
env.DocbookSlidesHtml('manual', xsl='slideshtml.xsl', base_dir='output/')
</screen>
<para>Make sure that you don't forget the trailing slash for the base folder, else
your files get renamed only!
</para>
</summary>
</builder>
<builder name="DocbookXInclude">
<summary>
<para>
A pseudo-Builder, for resolving XIncludes in a separate processing step.
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookXInclude('manual_xincluded.xml', 'manual.xml')
</example_commands>
</summary>
</builder>
<builder name="DocbookXslt">
<summary>
<para>
A pseudo-Builder, applying a given XSL transformation to the input file.
</para>
<example_commands>env = Environment(tools=['docbook'])
env.DocbookXslt('manual_transformed.xml', 'manual.xml', xsl='transform.xslt')
</example_commands>
<para>Note, that this builder requires the <literal>xsl</literal> parameter
to be set.
</para>
</summary>
</builder>
</sconsdoc>

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