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

View file

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

View file

@ -25,7 +25,6 @@ import platform
from glob import glob from glob import glob
from copy import copy from copy import copy
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from SCons.SConf import SetCacheMode
import pickle import pickle
try: try:
@ -142,7 +141,6 @@ PLUGINS = { # plugins with external dependencies
def init_environment(env): def init_environment(env):
env.Decider('MD5-timestamp') env.Decider('MD5-timestamp')
env.SourceCode(".", None)
env['ORIGIN'] = Literal('$ORIGIN') env['ORIGIN'] = Literal('$ORIGIN')
env['ENV']['ORIGIN'] = '$ORIGIN' env['ENV']['ORIGIN'] = '$ORIGIN'
if os.environ.get('RANLIB'): 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'), 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://www.scons.org/wiki/GoFastButton
# http://stackoverflow.com/questions/1318863/how-to-optimize-an-scons-script # 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'), 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)), ('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 # and thus breaks knowledge below that gdal worked
# TODO - upgrade our scons logic to support Framework linking # TODO - upgrade our scons logic to support Framework linking
if env['PLATFORM'] == 'Darwin': if env['PLATFORM'] == 'Darwin':
if value and b'-framework GDAL' in value: if value and '-framework GDAL' in value:
env['LIBS'].append('gdal') env['LIBS'].append('gdal')
if os.path.exists('/Library/Frameworks/GDAL.framework/unix/lib'): if os.path.exists('/Library/Frameworks/GDAL.framework/unix/lib'):
env['LIBPATH'].insert(0,'/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 return version_string
if not preconfigured: if not preconfigured:
color_print(4,'Configuring build environment...') color_print(4,'Configuring build environment...')
if not env['FAST']:
SetCacheMode('force')
if env['USE_CONFIG']: if env['USE_CONFIG']:
if not env['CONFIG'].endswith('.py'): if not env['CONFIG'].endswith('.py'):
color_print(1,'SCons CONFIG file specified is not a python file, will not be read...') 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') 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 # Build agg first, doesn't need anything special
if env['RUNTIME_LINK'] == 'shared': if env['RUNTIME_LINK'] == 'shared':
SConscript('deps/agg/build.py') SConscript('deps/agg/build.py')

View file

@ -3,7 +3,9 @@
This copyright and license do not apply to any other software This copyright and license do not apply to any other software
with which this software may have been included. 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 Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the 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 SCons - a software construction tool
@ -38,19 +38,20 @@ LATEST VERSION
Before going further, you can check for the latest version of the Before going further, you can check for the latest version of the
scons-local package, or any SCons package, at the SCons download page: 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 EXECUTION REQUIREMENTS
====================== ======================
Running SCons requires Python version 2.4 or later. There should be Running SCons requires Python 3.5 or higher.
no other dependencies or requirements to run SCons. There should be no other dependencies or requirements to run SCons.
The default SCons configuration assumes use of the Microsoft Visual C++ The default SCons configuration assumes use of the Microsoft Visual C++
compiler suite on WIN32 systems, and assumes a C compiler named 'cc', compiler suite on WIN32 systems (either through the Visual Studio
a C++ compiler named 'c++', and a Fortran compiler named 'g77' (such product, or through the separate Build Tools), and assumes a C compiler
as found in the GNU C compiler suite) on any other type of system. 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 You may, of course, override these default values by appropriate
configuration of Environment construction variables. configuration of Environment construction variables.
@ -157,14 +158,23 @@ available at:
REPORTING BUGS REPORTING BUGS
============== ==============
You can report bugs either by following the "Tracker - Bugs" link The SCons project welcomes bug reports and feature requests.
on the SCons project page:
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 MAILING LISTS
@ -173,11 +183,29 @@ MAILING LISTS
A mailing list for users of SCons is available. You may send questions A mailing list for users of SCons is available. You may send questions
or comments to the list at: 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: 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 FOR MORE INFORMATION
@ -188,18 +216,29 @@ Check the SCons web site at:
http://www.scons.org/ http://www.scons.org/
AUTHOR INFO Author Info
=========== ===========
Steven Knight SCons was originally written by Steven Knight, knight at baldmt dot com.
knight at baldmt dot com Since around 2010 it has been maintained by the SCons
http://www.baldmt.com/~knight/ development team, co-managed by Bill Deegan and Gary Oberbrunner, with
many contributors, including but not at all limited to:
With plenty of help from the SCons Development team:
Chad Austin
Charles Crain
Steve Leblanc
Anthony Roach
Terrel Shumway
- 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__revision__ = "src/engine/SCons/Action.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os import os
import pickle import pickle
import re import re
import sys import sys
import subprocess import subprocess
from subprocess import DEVNULL
import itertools import itertools
import inspect import inspect
from collections import OrderedDict
import SCons.Debug import SCons.Debug
from SCons.Debug import logInstanceCreation from SCons.Debug import logInstanceCreation
@ -115,10 +117,9 @@ import SCons.Util
import SCons.Subst import SCons.Subst
# we use these a lot, so try to optimize them # we use these a lot, so try to optimize them
is_String = SCons.Util.is_String from SCons.Util import is_String, is_List
is_List = SCons.Util.is_List
class _null(object): class _null:
pass pass
print_actions = 1 print_actions = 1
@ -211,7 +212,7 @@ def _object_contents(obj):
def _code_contents(code, docstring=None): 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 By providing direct access to the code object of the
function, Python makes this extremely easy. Hooray! 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 # function. Note that we have to call _object_contents on each
# constants because the code object of nested functions can # constants because the code object of nested functions can
# show-up among the constants. # show-up among the constants.
z = [_object_contents(cc) for cc in code.co_consts if cc != docstring]
z = [_object_contents(cc) for cc in code.co_consts[1:]]
contents.extend(b',(') contents.extend(b',(')
contents.extend(bytearray(',', 'utf-8').join(z)) contents.extend(bytearray(',', 'utf-8').join(z))
contents.extend(b')') contents.extend(b')')
@ -515,7 +515,7 @@ def Action(act, *args, **kw):
return _do_create_action(act, 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 """Base class for all types of action objects that can be held by
other objects (Builders, Executors, etc.) This provides the other objects (Builders, Executors, etc.) This provides the
common methods for manipulating and combining those actions.""" common methods for manipulating and combining those actions."""
@ -535,7 +535,7 @@ class ActionBase(object):
result = self.get_presig(target, source, env) result = self.get_presig(target, source, env)
if not isinstance(result,(bytes, bytearray)): 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: else:
# Make a copy and put in bytearray, without this the contents returned by get_presig # 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 # can be changed by the logic below, appending with each call and causing very
@ -629,17 +629,9 @@ class _ActionAction(ActionBase):
""" """
In python 3, and in some of our tests, sys.stdout is In python 3, and in some of our tests, sys.stdout is
a String io object, and it takes unicode strings only a String io object, and it takes unicode strings only
In other cases it's a regular Python 2.x file object This code assumes s is a regular string.
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.
""" """
try: sys.stdout.write(s + "\n")
sys.stdout.write(s + u"\n")
except UnicodeDecodeError:
sys.stdout.write(s + "\n")
def __call__(self, target, source, env, def __call__(self, target, source, env,
exitstatfunc=_null, exitstatfunc=_null,
@ -657,10 +649,14 @@ class _ActionAction(ActionBase):
presub = self.presub presub = self.presub
if presub is _null: if presub is _null:
presub = print_actions_presub presub = print_actions_presub
if exitstatfunc is _null: exitstatfunc = self.exitstatfunc if exitstatfunc is _null:
if show is _null: show = print_actions exitstatfunc = self.exitstatfunc
if execute is _null: execute = execute_actions if show is _null:
if chdir is _null: chdir = self.chdir show = print_actions
if execute is _null:
execute = execute_actions
if chdir is _null:
chdir = self.chdir
save_cwd = None save_cwd = None
if chdir: if chdir:
save_cwd = os.getcwd() save_cwd = os.getcwd()
@ -678,7 +674,7 @@ class _ActionAction(ActionBase):
source = executor.get_all_sources() source = executor.get_all_sources()
t = ' and '.join(map(str, target)) t = ' and '.join(map(str, target))
l = '\n '.join(self.presub_lines(env)) 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) sys.stdout.write(out)
cmd = None cmd = None
if show and self.strfunction: if show and self.strfunction:
@ -760,24 +756,20 @@ def get_default_ENV(env):
return default_ENV return default_ENV
def _subproc(scons_env, cmd, error = 'ignore', **kw): def _subproc(scons_env, cmd, error='ignore', **kw):
"""Do common setup for a subprocess.Popen() call """Wrapper for subprocess which pulls from construction env.
This function is still in draft mode. We're going to need something like Use for calls to subprocess which need to interpolate values from
it in the long run as more and more places use subprocess, but I'm sure an SCons construction environment into the environment passed to
it'll have to be tweaked to get the full desired functionality. subprocess. Adds an an error-handling argument. Adds ability
one special arg (so far?), 'error', to tell what to do with exceptions. to specify std{in,out,err} with "'devnull'" tag.
""" """
# allow std{in,out,err} to be "'devnull'" # TODO: just uses subprocess.DEVNULL now, we can drop the "devnull"
io = kw.get('stdin') # string now - it is a holdover from Py2, which didn't have DEVNULL.
if is_String(io) and io == 'devnull': for stream in 'stdin', 'stdout', 'stderr':
kw['stdin'] = open(os.devnull) io = kw.get(stream)
io = kw.get('stdout') if is_String(io) and io == 'devnull':
if is_String(io) and io == 'devnull': kw[stream] = DEVNULL
kw['stdout'] = open(os.devnull, 'w')
io = kw.get('stderr')
if is_String(io) and io == 'devnull':
kw['stderr'] = open(os.devnull, 'w')
# Figure out what shell environment to use # Figure out what shell environment to use
ENV = kw.get('env', None) ENV = kw.get('env', None)
@ -803,21 +795,28 @@ def _subproc(scons_env, cmd, error = 'ignore', **kw):
kw['env'] = new_env kw['env'] = new_env
try: try:
return subprocess.Popen(cmd, **kw) pobj = subprocess.Popen(cmd, **kw)
except EnvironmentError as e: except EnvironmentError as e:
if error == 'raise': raise if error == 'raise': raise
# return a dummy Popen instance that only returns error # return a dummy Popen instance that only returns error
class dummyPopen(object): class dummyPopen:
def __init__(self, e): self.exception = e def __init__(self, e): self.exception = e
def communicate(self, input=None): return ('', '') def communicate(self, input=None): return ('', '')
def wait(self): return -self.exception.errno def wait(self): return -self.exception.errno
stdin = None stdin = None
class f(object): class f:
def read(self): return '' def read(self): return ''
def readline(self): return '' def readline(self): return ''
def __iter__(self): return iter(()) def __iter__(self): return iter(())
stdout = stderr = f() 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): class CommandAction(_ActionAction):
@ -837,8 +836,8 @@ class CommandAction(_ActionAction):
_ActionAction.__init__(self, **kw) _ActionAction.__init__(self, **kw)
if is_List(cmd): if is_List(cmd):
if [c for c in cmd if is_List(c)]: 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") "a single command")
self.cmd_list = cmd self.cmd_list = cmd
def __str__(self): def __str__(self):
@ -964,11 +963,33 @@ class CommandAction(_ActionAction):
return env.subst_target_source(cmd, SUBST_SIG, target, source) return env.subst_target_source(cmd, SUBST_SIG, target, source)
def get_implicit_deps(self, target, source, env, executor=None): 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) icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True)
if is_String(icd) and icd[:1] == '$': if is_String(icd) and icd[:1] == '$':
icd = env.subst(icd) 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 [] 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 from SCons.Subst import SUBST_SIG
if executor: if executor:
cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=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)) res.append(env.fs.File(d))
return res 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 CommandGeneratorAction(ActionBase):
"""Class for command-generator actions.""" """Class for command-generator actions."""
@ -1034,11 +1114,14 @@ class CommandGeneratorAction(ActionBase):
show=_null, execute=_null, chdir=_null, executor=None): show=_null, execute=_null, chdir=_null, executor=None):
act = self._generate(target, source, env, 0, executor) act = self._generate(target, source, env, 0, executor)
if act is None: if act is None:
raise SCons.Errors.UserError("While building `%s': " raise SCons.Errors.UserError(
"Cannot deduce file extension from source files: %s" "While building `%s': "
% (repr(list(map(str, target))), repr(list(map(str, source))))) "Cannot deduce file extension from source files: %s"
return act(target, source, env, exitstatfunc, presub, % (repr(list(map(str, target))), repr(list(map(str, source))))
show, execute, chdir, executor) )
return act(
target, source, env, exitstatfunc, presub, show, execute, chdir, executor
)
def get_presig(self, target, source, env, executor=None): def get_presig(self, target, source, env, executor=None):
"""Return the signature contents of this action's command line. """Return the signature contents of this action's command line.
@ -1086,7 +1169,7 @@ class LazyAction(CommandGeneratorAction, CommandAction):
def get_parent_class(self, env): def get_parent_class(self, env):
c = env.get(self.var) 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 CommandAction
return CommandGeneratorAction return CommandGeneratorAction
@ -1289,14 +1372,14 @@ class ListAction(ActionBase):
return result return result
def get_varlist(self, target, source, env, executor=None): def get_varlist(self, target, source, env, executor=None):
result = SCons.Util.OrderedDict() result = OrderedDict()
for act in self.list: for act in self.list:
for var in act.get_varlist(target, source, env, executor): for var in act.get_varlist(target, source, env, executor):
result[var] = True result[var] = True
return list(result.keys()) return list(result.keys())
class ActionCaller(object): class ActionCaller:
"""A class for delaying calling an Action function with specific """A class for delaying calling an Action function with specific
(positional and keyword) arguments until the Action is actually (positional and keyword) arguments until the Action is actually
executed. executed.
@ -1367,7 +1450,7 @@ class ActionCaller(object):
return self.parent.strfunc(*self.args, **self.kw) return self.parent.strfunc(*self.args, **self.kw)
class ActionFactory(object): class ActionFactory:
"""A factory class that will wrap up an arbitrary function """A factory class that will wrap up an arbitrary function
as an SCons-executable Action object. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__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.Action
import SCons.Debug import SCons.Debug
@ -111,7 +111,7 @@ import SCons.Memoize
import SCons.Util import SCons.Util
import SCons.Warnings import SCons.Warnings
class _Null(object): class _Null:
pass pass
_null = _Null _null = _Null
@ -197,7 +197,7 @@ class DictEmitter(SCons.Util.Selector):
target, source = emitter(target, source, env) target, source = emitter(target, source, env)
return (target, source) return (target, source)
class ListEmitter(collections.UserList): class ListEmitter(UserList):
"""A callable list of emitters that calls each in sequence, """A callable list of emitters that calls each in sequence,
returning the result. returning the result.
""" """
@ -215,7 +215,7 @@ misleading_keywords = {
'sources' : 'source', 'sources' : 'source',
} }
class OverrideWarner(collections.UserDict): class OverrideWarner(UserDict):
"""A class for warning about keyword arguments that we use as """A class for warning about keyword arguments that we use as
overrides in a Builder call. overrides in a Builder call.
@ -224,13 +224,13 @@ class OverrideWarner(collections.UserDict):
warnings once, no matter how many Builders are invoked. warnings once, no matter how many Builders are invoked.
""" """
def __init__(self, dict): def __init__(self, dict):
collections.UserDict.__init__(self, dict) UserDict.__init__(self, dict)
if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.OverrideWarner') if SCons.Debug.track_instances: logInstanceCreation(self, 'Builder.OverrideWarner')
self.already_warned = None self.already_warned = None
def warn(self): def warn(self):
if self.already_warned: if self.already_warned:
return return
for k in list(self.keys()): for k in self.keys():
if k in misleading_keywords: if k in misleading_keywords:
alt = misleading_keywords[k] alt = misleading_keywords[k]
msg = "Did you mean to use `%s' instead of `%s'?" % (alt, k) msg = "Did you mean to use `%s' instead of `%s'?" % (alt, k)
@ -274,7 +274,7 @@ def Builder(**kw):
result = BuilderBase(**kw) result = BuilderBase(**kw)
if not composite is None: if composite is not None:
result = CompositeBuilder(result, composite) result = CompositeBuilder(result, composite)
return result return result
@ -293,7 +293,7 @@ def _node_errors(builder, env, tlist, slist):
if t.has_explicit_builder(): if t.has_explicit_builder():
# Check for errors when the environments are different # Check for errors when the environments are different
# No error if environments are the same Environment instance # 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 # Check OverrideEnvironment case - no error if wrapped Environments
# are the same instance, and overrides lists match # are the same instance, and overrides lists match
not (getattr(t.env, '__subject', 0) is getattr(env, '__subject', 1) and not (getattr(t.env, '__subject', 0) is getattr(env, '__subject', 1) and
@ -309,7 +309,7 @@ def _node_errors(builder, env, tlist, slist):
else: else:
try: 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')) 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 msg = "Two environments with different actions were specified for the same target: %s"%t
raise UserError(msg) raise UserError(msg)
if builder.multi: if builder.multi:
@ -328,7 +328,7 @@ def _node_errors(builder, env, tlist, slist):
if len(slist) > 1: 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)))) 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 """This is a callable class that can act as a
Builder emitter. It holds on to a string that Builder emitter. It holds on to a string that
is a key into an Environment dictionary, and will is a key into an Environment dictionary, and will
@ -361,7 +361,7 @@ class EmitterProxy(object):
def __lt__(self, other): def __lt__(self, other):
return self.var < other.var return self.var < other.var
class BuilderBase(object): class BuilderBase:
"""Base class for Builders, objects that create output """Base class for Builders, objects that create output
nodes (files) from input nodes (files). nodes (files) from input nodes (files).
""" """
@ -396,16 +396,13 @@ class BuilderBase(object):
self.env = env self.env = env
self.single_source = single_source self.single_source = single_source
if 'overrides' in overrides: if 'overrides' in overrides:
SCons.Warnings.warn(SCons.Warnings.DeprecatedBuilderKeywordsWarning, msg = "The \"overrides\" keyword to Builder() creation has been removed;\n" +\
"The \"overrides\" keyword to Builder() creation has been deprecated;\n" +\ "\tspecify the items as keyword arguments to the Builder() call instead."
"\tspecify the items as keyword arguments to the Builder() call instead.") raise TypeError(msg)
overrides.update(overrides['overrides'])
del overrides['overrides']
if 'scanner' in overrides: if 'scanner' in overrides:
SCons.Warnings.warn(SCons.Warnings.DeprecatedBuilderKeywordsWarning, msg = "The \"scanner\" keyword to Builder() creation has been removed;\n" +\
"The \"scanner\" keyword to Builder() creation has been deprecated;\n" "\tuse: source_scanner or target_scanner as appropriate."
"\tuse: source_scanner or target_scanner as appropriate.") raise TypeError(msg)
del overrides['scanner']
self.overrides = overrides self.overrides = overrides
self.set_suffix(suffix) self.set_suffix(suffix)
@ -424,7 +421,7 @@ class BuilderBase(object):
if name: if name:
self.name = name self.name = name
self.executor_kw = {} self.executor_kw = {}
if not chdir is _null: if chdir is not _null:
self.executor_kw['chdir'] = chdir self.executor_kw['chdir'] = chdir
self.is_explicit = is_explicit self.is_explicit = is_explicit
@ -554,8 +551,10 @@ class BuilderBase(object):
result = [] result = []
if target is None: target = [None]*len(source) if target is None: target = [None]*len(source)
for tgt, src in zip(target, source): for tgt, src in zip(target, source):
if not tgt is None: tgt = [tgt] if tgt is not None:
if not src is None: src = [src] tgt = [tgt]
if src is not None:
src = [src]
result.extend(self._execute(env, tgt, src, overwarn)) result.extend(self._execute(env, tgt, src, overwarn))
return SCons.Node.NodeList(result) return SCons.Node.NodeList(result)
@ -563,6 +562,13 @@ class BuilderBase(object):
tlist, slist = self._create_nodes(env, target, source) 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. # Check for errors with the specified target/source lists.
_node_errors(self, env, tlist, slist) _node_errors(self, env, tlist, slist)
@ -641,6 +647,8 @@ class BuilderBase(object):
env_kw = kw env_kw = kw
else: else:
env_kw = self.overrides 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) env = env.Override(env_kw)
return self._execute(env, target, source, OverrideWarner(kw), ekw) return self._execute(env, target, source, OverrideWarner(kw), ekw)
@ -744,7 +752,7 @@ class BuilderBase(object):
for s in SCons.Util.flatten(source): for s in SCons.Util.flatten(source):
if SCons.Util.is_String(s): if SCons.Util.is_String(s):
match_suffix = match_src_suffix(env.subst(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) src_suf = self.get_src_suffix(env)
s = self._adjustixes(s, None, src_suf)[0] s = self._adjustixes(s, None, src_suf)[0]
else: 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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__ = """ __doc__ = """
CacheDir support CacheDir support
""" """
import atexit
import json import json
import os import os
import stat import stat
import sys import sys
import SCons.Action import SCons.Action
import SCons.Errors
import SCons.Warnings import SCons.Warnings
cache_enabled = True cache_enabled = True
@ -45,16 +47,22 @@ def CacheRetrieveFunc(target, source, env):
t = target[0] t = target[0]
fs = t.fs fs = t.fs
cd = env.get_CacheDir() cd = env.get_CacheDir()
cd.requests += 1
cachedir, cachefile = cd.cachepath(t) cachedir, cachefile = cd.cachepath(t)
if not fs.exists(cachefile): if not fs.exists(cachefile):
cd.CacheDebug('CacheRetrieve(%s): %s not in cache\n', t, cachefile) cd.CacheDebug('CacheRetrieve(%s): %s not in cache\n', t, cachefile)
return 1 return 1
cd.hits += 1
cd.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile) cd.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile)
if SCons.Action.execute_actions: if SCons.Action.execute_actions:
if fs.islink(cachefile): if fs.islink(cachefile):
fs.symlink(fs.readlink(cachefile), t.get_internal_path()) fs.symlink(fs.readlink(cachefile), t.get_internal_path())
else: else:
env.copy_from_cache(cachefile, t.get_internal_path()) env.copy_from_cache(cachefile, t.get_internal_path())
try:
os.utime(cachefile, None)
except OSError:
pass
st = fs.stat(cachefile) st = fs.stat(cachefile)
fs.chmod(t.get_internal_path(), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) fs.chmod(t.get_internal_path(), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
return 0 return 0
@ -106,7 +114,7 @@ def CachePushFunc(target, source, env):
# has beaten us creating the directory. # has beaten us creating the directory.
if not fs.isdir(cachedir): if not fs.isdir(cachedir):
msg = errfmt % (str(target), cachefile) msg = errfmt % (str(target), cachefile)
raise SCons.Errors.EnvironmentError(msg) raise SCons.Errors.SConsEnvironmentError(msg)
try: try:
if fs.islink(t.get_internal_path()): if fs.islink(t.get_internal_path()):
@ -127,90 +135,97 @@ def CachePushFunc(target, source, env):
CachePush = SCons.Action.Action(CachePushFunc, None) 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): def __init__(self, path):
try: """
import hashlib Initialize a CacheDir object.
except ImportError:
msg = "No hashlib or MD5 module available, CacheDir() not supported" The cache configuration is stored in the object. It
SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg) is read from the config file in the supplied path if
path = None 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.path = path
self.current_cache_debug = None self.current_cache_debug = None
self.debugFP = None self.debugFP = None
self.config = dict() self.config = dict()
if path is None: if path is None:
return 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, self._readconfig(path)
# produce a warning. If the directory doesn't exist or is empty,
# write a config file.
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') config_file = os.path.join(path, 'config')
if not os.path.exists(config_file): try:
# A note: There is a race hazard here, if two processes start and os.makedirs(path, exist_ok=True)
# attempt to create the cache directory at the same time. However, except FileExistsError:
# python doesn't really give you the option to do exclusive file pass
# creation (it doesn't even give you the option to error on opening except OSError:
# an existing file for writing...). The ordering of events here msg = "Failed to create cache directory " + path
# as an attempt to alleviate this, on the basis that it's a pretty raise SCons.Errors.SConsEnvironmentError(msg)
# unlikely occurence (it'd require two builds with a brand new cache
# directory) try:
if os.path.isdir(path) and len(os.listdir(path)) != 0: with open(config_file, 'x') as config:
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 self.config['prefix_len'] = 2
if not os.path.exists(config_file): try:
try: json.dump(self.config, config)
with open(config_file, 'w') as config: except Exception:
json.dump(self.config, config) msg = "Failed to write cache configuration for " + path
except: raise SCons.Errors.SConsEnvironmentError(msg)
msg = "Failed to write cache configuration for " + path except FileExistsError:
raise SCons.Errors.EnvironmentError(msg)
else:
try: try:
with open(config_file) as config: with open(config_file) as config:
self.config = json.load(config) self.config = json.load(config)
except ValueError: except ValueError:
msg = "Failed to read cache configuration for " + path msg = "Failed to read cache configuration for " + path
raise SCons.Errors.EnvironmentError(msg) raise SCons.Errors.SConsEnvironmentError(msg)
def CacheDebug(self, fmt, target, cachefile): def CacheDebug(self, fmt, target, cachefile):
if cache_debug != self.current_cache_debug: if cache_debug != self.current_cache_debug:
if cache_debug == '-': if cache_debug == '-':
self.debugFP = sys.stdout self.debugFP = sys.stdout
elif cache_debug: elif cache_debug:
def debug_cleanup(debugFP):
debugFP.close()
self.debugFP = open(cache_debug, 'w') self.debugFP = open(cache_debug, 'w')
atexit.register(debug_cleanup, self.debugFP)
else: else:
self.debugFP = None self.debugFP = None
self.current_cache_debug = cache_debug self.current_cache_debug = cache_debug
if self.debugFP: if self.debugFP:
self.debugFP.write(fmt % (target, os.path.split(cachefile)[1])) 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): 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): def is_readonly(self):
return cache_readonly return cache_readonly
@ -222,7 +237,9 @@ class CacheDir(object):
return None, None return None, None
sig = node.get_cachedir_bsig() sig = node.get_cachedir_bsig()
subdir = sig[:self.config['prefix_len']].upper() subdir = sig[:self.config['prefix_len']].upper()
dir = os.path.join(self.path, subdir) dir = os.path.join(self.path, subdir)
return dir, os.path.join(dir, sig) return dir, os.path.join(dir, sig)

View file

@ -136,7 +136,7 @@ def CheckBuilder(context, text = None, language = None):
if not text: if not text:
text = """ text = """
int main() { int main(void) {
return 0; return 0;
} }
""" """
@ -157,7 +157,7 @@ def CheckCC(context):
""" """
context.Display("Checking whether the C compiler works... ") context.Display("Checking whether the C compiler works... ")
text = """ text = """
int main() int main(void)
{ {
return 0; return 0;
} }
@ -177,7 +177,7 @@ def CheckSHCC(context):
""" """
context.Display("Checking whether the (shared) C compiler works... ") context.Display("Checking whether the (shared) C compiler works... ")
text = """ text = """
int foo() int foo(void)
{ {
return 0; return 0;
} }
@ -197,7 +197,7 @@ def CheckCXX(context):
""" """
context.Display("Checking whether the C++ compiler works... ") context.Display("Checking whether the C++ compiler works... ")
text = """ text = """
int main() int main(void)
{ {
return 0; return 0;
} }
@ -217,7 +217,7 @@ def CheckSHCXX(context):
""" """
context.Display("Checking whether the (shared) C++ compiler works... ") context.Display("Checking whether the (shared) C++ compiler works... ")
text = """ text = """
int main() int main(void)
{ {
return 0; return 0;
} }
@ -290,7 +290,11 @@ char %s();""" % function_name
#include <assert.h> #include <assert.h>
%(hdr)s %(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) #if defined (__stub_%(name)s) || defined (__stub___%(name)s)
fail fail fail fail fail fail
#else #else
@ -311,8 +315,8 @@ int main() {
return ret return ret
def CheckHeader(context, header_name, header = None, language = None, def CheckHeader(context, header_name, header=None, language=None,
include_quotes = None): include_quotes=None):
""" """
Configure check for a C or C++ header file "header_name". Configure check for a C or C++ header file "header_name".
Optional "header" can be defined to do something before including the Optional "header" can be defined to do something before including the
@ -354,7 +358,7 @@ def CheckHeader(context, header_name, header = None, language = None,
context.Display("Checking for %s header file %s... " % (lang, header_name)) context.Display("Checking for %s header file %s... " % (lang, header_name))
ret = context.CompileProg(text, suffix) ret = context.CompileProg(text, suffix)
_YesNoResult(context, ret, "HAVE_" + header_name, text, _YesNoResult(context, ret, "HAVE_" + header_name, text,
"Define to 1 if you have the <%s> header file." % header_name) "Define to 1 if you have the <%s> header file." % header_name)
return ret return ret
@ -400,7 +404,7 @@ def CheckType(context, type_name, fallback = None,
%(include)s %(include)s
%(header)s %(header)s
int main() { int main(void) {
if ((%(name)s *) 0) if ((%(name)s *) 0)
return 0; return 0;
if (sizeof (%(name)s)) if (sizeof (%(name)s))
@ -439,7 +443,7 @@ def CheckTypeSize(context, type_name, header = None, language = None, expect = N
Returns: Returns:
status : int status : int
0 if the check failed, or the found size of the type if the check succeeded.""" 0 if the check failed, or the found size of the type if the check succeeded."""
# Include "confdefs.h" first, so that the header can use HAVE_HEADER_H. # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
if context.headerfilename: if context.headerfilename:
includetext = '#include "%s"' % context.headerfilename includetext = '#include "%s"' % context.headerfilename
@ -454,8 +458,8 @@ def CheckTypeSize(context, type_name, header = None, language = None, expect = N
context.Display("Cannot check for %s type: %s\n" % (type_name, msg)) context.Display("Cannot check for %s type: %s\n" % (type_name, msg))
return msg return msg
src = includetext + header src = includetext + header
if not expect is None: if expect is not None:
# Only check if the given size is the right one # Only check if the given size is the right one
context.Display('Checking %s is %d bytes... ' % (type_name, expect)) 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""" src = src + r"""
typedef %s scons_check_type; typedef %s scons_check_type;
int main() int main(void)
{ {
static int test_array[1 - 2 * !(((long int) (sizeof(scons_check_type))) == %d)]; static int test_array[1 - 2 * !(((long int) (sizeof(scons_check_type))) == %d)];
test_array[0] = 0; test_array[0] = 0;
@ -477,7 +481,7 @@ int main()
st = context.CompileProg(src % (type_name, expect), suffix) st = context.CompileProg(src % (type_name, expect), suffix)
if not st: if not st:
context.Display("yes\n") context.Display("yes\n")
_Have(context, "SIZEOF_%s" % type_name, expect, _Have(context, "SIZEOF_%s" % type_name, expect,
"The size of `%s', as computed by sizeof." % type_name) "The size of `%s', as computed by sizeof." % type_name)
return expect return expect
else: else:
@ -498,7 +502,7 @@ int main()
src = src + """ src = src + """
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
int main() { int main(void) {
printf("%d", (int)sizeof(""" + type_name + """)); printf("%d", (int)sizeof(""" + type_name + """));
return 0; return 0;
} }
@ -541,7 +545,7 @@ def CheckDeclaration(context, symbol, includes = None, language = None):
Returns: Returns:
status : bool status : bool
True if the check failed, False if succeeded.""" True if the check failed, False if succeeded."""
# Include "confdefs.h" first, so that the header can use HAVE_HEADER_H. # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
if context.headerfilename: if context.headerfilename:
includetext = '#include "%s"' % context.headerfilename includetext = '#include "%s"' % context.headerfilename
@ -556,11 +560,11 @@ def CheckDeclaration(context, symbol, includes = None, language = None):
context.Display("Cannot check for declaration %s: %s\n" % (symbol, msg)) context.Display("Cannot check for declaration %s: %s\n" % (symbol, msg))
return msg return msg
src = includetext + includes src = includetext + includes
context.Display('Checking whether %s is declared... ' % symbol) context.Display('Checking whether %s is declared... ' % symbol)
src = src + r""" src = src + r"""
int main() int main(void)
{ {
#ifndef %s #ifndef %s
(void) %s; (void) %s;
@ -677,7 +681,7 @@ return 0;
"Define to 1 if you have the `%s' library." % lib_name) "Define to 1 if you have the `%s' library." % lib_name)
if oldLIBS != -1 and (ret or not autoadd): if oldLIBS != -1 and (ret or not autoadd):
context.SetLIBS(oldLIBS) context.SetLIBS(oldLIBS)
if not ret: if not ret:
return ret return ret
@ -704,7 +708,7 @@ def CheckProg(context, prog_name):
# #
def _YesNoResult(context, ret, key, text, comment = None): def _YesNoResult(context, ret, key, text, comment = None):
""" r"""
Handle the result of a test with a "yes" or "no" result. Handle the result of a test with a "yes" or "no" result.
:Parameters: :Parameters:
@ -723,7 +727,7 @@ def _YesNoResult(context, ret, key, text, comment = None):
def _Have(context, key, have, comment = None): def _Have(context, key, have, comment = None):
""" r"""
Store result of a test in context.havedict and context.headerfilename. Store result of a test in context.havedict and context.headerfilename.
:Parameters: :Parameters:
@ -751,7 +755,7 @@ def _Have(context, key, have, comment = None):
line = "#define %s %d\n" % (key_up, have) line = "#define %s %d\n" % (key_up, have)
else: else:
line = "#define %s %s\n" % (key_up, str(have)) line = "#define %s %s\n" % (key_up, str(have))
if comment is not None: if comment is not None:
lines = "\n/* %s */\n" % comment + line lines = "\n/* %s */\n" % comment + line
else: else:

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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 os
import sys import sys
import time import time
@ -66,7 +67,7 @@ def string_to_classes(s):
def fetchLoggedInstances(classes="*"): def fetchLoggedInstances(classes="*"):
classnames = string_to_classes(classes) classnames = string_to_classes(classes)
return [(cn, len(tracked_classes[cn])) for cn in classnames] return [(cn, len(tracked_classes[cn])) for cn in classnames]
def countLoggedInstances(classes, file=sys.stdout): def countLoggedInstances(classes, file=sys.stdout):
for classname in string_to_classes(classes): for classname in string_to_classes(classes):
file.write("%s: %d\n" % (classname, len(tracked_classes[classname]))) file.write("%s: %d\n" % (classname, len(tracked_classes[classname])))
@ -201,22 +202,39 @@ if sys.platform == 'win32':
TraceDefault = 'con' TraceDefault = 'con'
else: else:
TraceDefault = '/dev/tty' TraceDefault = '/dev/tty'
TimeStampDefault = False
TimeStampDefault = None
StartTime = time.time() StartTime = time.time()
PreviousTime = StartTime PreviousTime = StartTime
def Trace(msg, file=None, mode='w', tstamp=None): def Trace(msg, filename=None, mode='w', tstamp=False):
"""Write a trace message to a file. Whenever a file is specified, """Write a trace message.
it becomes the default for the next call to Trace()."""
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 TraceDefault
global TimeStampDefault global TimeStampDefault
global PreviousTime global PreviousTime
def trace_cleanup(traceFP):
traceFP.close()
if file is None: if file is None:
file = TraceDefault file = TraceDefault
else: else:
TraceDefault = file TraceDefault = file
if tstamp is None: if not tstamp:
tstamp = TimeStampDefault tstamp = TimeStampDefault
else: else:
TimeStampDefault = tstamp TimeStampDefault = tstamp
@ -225,6 +243,7 @@ def Trace(msg, file=None, mode='w', tstamp=None):
except KeyError: except KeyError:
try: try:
fp = TraceFP[file] = open(file, mode) fp = TraceFP[file] = open(file, mode)
atexit.register(trace_cleanup, fp)
except TypeError: except TypeError:
# Assume we were passed an open file pointer. # Assume we were passed an open file pointer.
fp = file fp = file
@ -234,7 +253,6 @@ def Trace(msg, file=None, mode='w', tstamp=None):
PreviousTime = now PreviousTime = now
fp.write(msg) fp.write(msg)
fp.flush() fp.flush()
fp.close()
# Local Variables: # Local Variables:
# tab-width:4 # 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# #
from __future__ import division __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__revision__ = "src/engine/SCons/Defaults.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import os import os
@ -193,7 +191,7 @@ def chmod_func(dest, mode):
SCons.Node.FS.invalidate_node_memos(dest) SCons.Node.FS.invalidate_node_memos(dest)
if not SCons.Util.is_List(dest): if not SCons.Util.is_List(dest):
dest = [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) mode = int(mode, 8)
if not SCons.Util.is_String(mode): if not SCons.Util.is_String(mode):
for element in dest: for element in dest:
@ -210,7 +208,7 @@ def chmod_func(dest, mode):
else: else:
raise SyntaxError("Could not find +, - or =") raise SyntaxError("Could not find +, - or =")
operation_list = operation.split(operator) operation_list = operation.split(operator)
if len(operation_list) is not 2: if len(operation_list) != 2:
raise SyntaxError("More than one operator found") raise SyntaxError("More than one operator found")
user = operation_list[0].strip().replace("a", "ugo") user = operation_list[0].strip().replace("a", "ugo")
permission = operation_list[1].strip() permission = operation_list[1].strip()
@ -333,8 +331,8 @@ def touch_func(dest):
if os.path.exists(file): if os.path.exists(file):
atime = os.path.getatime(file) atime = os.path.getatime(file)
else: else:
open(file, 'w') with open(file, 'w'):
atime = mtime atime = mtime
os.utime(file, (atime, mtime)) os.utime(file, (atime, mtime))
Touch = ActionFactory(touch_func, Touch = ActionFactory(touch_func,
@ -342,6 +340,7 @@ Touch = ActionFactory(touch_func,
# Internal utility functions # Internal utility functions
def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None): 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 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) return _concat_ixes(prefix, list, suffix, env)
def _concat_ixes(prefix, list, suffix, env): def _concat_ixes(prefix, list, suffix, env):
""" """
Creates a new list from 'list' by concatenating the 'prefix' and Creates a new list from 'list' by concatenating the 'prefix' and
@ -395,6 +395,7 @@ def _concat_ixes(prefix, list, suffix, env):
return result return result
def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None): def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
""" """
This is a wrapper around _concat()/_concat_ixes() that checks for 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) 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 """This is a callable class that can be used in place of other
command generators if you don't want them to do anything. command generators if you don't want them to do anything.
@ -516,7 +517,7 @@ class NullCmdGenerator(object):
return self.cmd return self.cmd
class Variable_Method_Caller(object): class Variable_Method_Caller:
"""A class for finding a construction variable on the stack and """A class for finding a construction variable on the stack and
calling one of its methods. calling one of its methods.
@ -565,7 +566,6 @@ ConstructionEnvironment = {
'DSUFFIXES' : SCons.Tool.DSuffixes, 'DSUFFIXES' : SCons.Tool.DSuffixes,
'ENV' : {}, 'ENV' : {},
'IDLSUFFIXES' : SCons.Tool.IDLSuffixes, 'IDLSUFFIXES' : SCons.Tool.IDLSuffixes,
# 'LATEXSUFFIXES' : SCons.Tool.LaTeXSuffixes, # moved to the TeX tools generate functions
'_concat' : _concat, '_concat' : _concat,
'_defines' : _defines, '_defines' : _defines,
'_stripixes' : _stripixes, '_stripixes' : _stripixes,
@ -580,6 +580,7 @@ ConstructionEnvironment = {
'__DSHLIBVERSIONFLAGS' : '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}', '__DSHLIBVERSIONFLAGS' : '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}',
'TEMPFILE' : NullCmdGenerator, 'TEMPFILE' : NullCmdGenerator,
'TEMPFILEARGJOIN': ' ',
'Dir' : Variable_Method_Caller('TARGET', 'Dir'), 'Dir' : Variable_Method_Caller('TARGET', 'Dir'),
'Dirs' : Variable_Method_Caller('TARGET', 'Dirs'), 'Dirs' : Variable_Method_Caller('TARGET', 'Dirs'),
'File' : Variable_Method_Caller('TARGET', 'File'), '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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__revision__ = "src/engine/SCons/Environment.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import copy import copy
@ -46,7 +46,7 @@ import SCons.Builder
import SCons.Debug import SCons.Debug
from SCons.Debug import logInstanceCreation from SCons.Debug import logInstanceCreation
import SCons.Defaults import SCons.Defaults
import SCons.Errors from SCons.Errors import UserError, BuildError
import SCons.Memoize import SCons.Memoize
import SCons.Node import SCons.Node
import SCons.Node.Alias import SCons.Node.Alias
@ -60,7 +60,7 @@ import SCons.Tool
import SCons.Util import SCons.Util
import SCons.Warnings import SCons.Warnings
class _Null(object): class _Null:
pass pass
_null = _Null _null = _Null
@ -75,11 +75,6 @@ CalculatorArgs = {}
semi_deepcopy = SCons.Util.semi_deepcopy semi_deepcopy = SCons.Util.semi_deepcopy
semi_deepcopy_dict = SCons.Util.semi_deepcopy_dict 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): def alias_builder(env, target, source):
pass pass
@ -128,7 +123,7 @@ future_reserved_construction_var_names = [
def copy_non_reserved_keywords(dict): def copy_non_reserved_keywords(dict):
result = semi_deepcopy(dict) result = semi_deepcopy(dict)
for k in list(result.keys()): for k in result.copy().keys():
if k in reserved_construction_var_names: if k in reserved_construction_var_names:
msg = "Ignoring attempt to set reserved variable `$%s'" msg = "Ignoring attempt to set reserved variable `$%s'"
SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % k) 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): def _set_BUILDERS(env, key, value):
try: try:
bd = env._dict[key] bd = env._dict[key]
for k in list(bd.keys()): for k in bd.copy().keys():
del bd[k] del bd[k]
except KeyError: except KeyError:
bd = BuilderDict(kwbd, env) bd = BuilderDict(bd, env)
env._dict[key] = bd env._dict[key] = bd
for k, v in value.items(): for k, v in value.items():
if not SCons.Builder.is_a_Builder(v): 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) bd.update(value)
def _del_SCANNERS(env, key): def _del_SCANNERS(env, key):
@ -196,7 +191,7 @@ def _delete_duplicates(l, keep_last):
# BuilderWrapper a subclass that overrides __call__() to enforce specific # BuilderWrapper a subclass that overrides __call__() to enforce specific
# Builder calling conventions, simplified some of our higher-layer code. # 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 A generic Wrapper class that associates a method (which can
actually be any callable) with an object. As part of creating this 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. """Base class for different flavors of construction environments.
This class contains a minimal set of methods that handle construction 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. # efficient than calling another function or a method.
if key not in self._dict \ if key not in self._dict \
and not _is_valid_var.match(key): 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 self._dict[key] = value
def get(self, key, default=None): def get(self, key, default=None):
@ -439,13 +434,23 @@ class SubstitutionEnvironment(object):
return self._dict.get(key, default) return self._dict.get(key, default)
def has_key(self, key): def has_key(self, key):
"""Emulates the has_key() method of dictionaries."""
return key in self._dict return key in self._dict
def __contains__(self, key): def __contains__(self, key):
return self._dict.__contains__(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): 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): def arg2nodes(self, args, node_factory=_null, lookup_list=_null, **kw):
if node_factory is _null: if node_factory is _null:
@ -589,7 +594,7 @@ class SubstitutionEnvironment(object):
out,err = p.communicate() out,err = p.communicate()
status = p.wait() status = p.wait()
if err: if err:
sys.stderr.write(u"" + err) sys.stderr.write("" + err)
if status: if status:
raise OSError("'%s' exited %d" % (command, status)) raise OSError("'%s' exited %d" % (command, status))
return out return out
@ -608,7 +613,7 @@ class SubstitutionEnvironment(object):
Removes the specified function's MethodWrapper from the Removes the specified function's MethodWrapper from the
added_methods list, so we don't re-bind it when making a clone. 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): def Override(self, overrides):
""" """
@ -707,25 +712,34 @@ class SubstitutionEnvironment(object):
append_next_arg_to = None # for multi-word args append_next_arg_to = None # for multi-word args
for arg in params: for arg in params:
if append_next_arg_to: if append_next_arg_to:
if append_next_arg_to == 'CPPDEFINES': if append_next_arg_to == 'CPPDEFINES':
append_define(arg) append_define(arg)
elif append_next_arg_to == '-include': elif append_next_arg_to == '-include':
t = ('-include', self.fs.File(arg)) t = ('-include', self.fs.File(arg))
dict['CCFLAGS'].append(t) dict['CCFLAGS'].append(t)
elif append_next_arg_to == '-isysroot': elif append_next_arg_to == '-imacros':
t = ('-isysroot', arg) t = ('-imacros', self.fs.File(arg))
dict['CCFLAGS'].append(t) dict['CCFLAGS'].append(t)
dict['LINKFLAGS'].append(t) elif append_next_arg_to == '-isysroot':
elif append_next_arg_to == '-isystem': t = ('-isysroot', arg)
t = ('-isystem', arg) dict['CCFLAGS'].append(t)
dict['CCFLAGS'].append(t) dict['LINKFLAGS'].append(t)
elif append_next_arg_to == '-arch': elif append_next_arg_to == '-isystem':
t = ('-arch', arg) t = ('-isystem', arg)
dict['CCFLAGS'].append(t) dict['CCFLAGS'].append(t)
dict['LINKFLAGS'].append(t) elif append_next_arg_to == '-iquote':
else: t = ('-iquote', arg)
dict[append_next_arg_to].append(arg) dict['CCFLAGS'].append(t)
append_next_arg_to = None 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)
dict['LINKFLAGS'].append(t)
else:
dict[append_next_arg_to].append(arg)
append_next_arg_to = None
elif not arg[0] in ['-', '+']: elif not arg[0] in ['-', '+']:
dict['LIBS'].append(self.fs.File(arg)) dict['LIBS'].append(self.fs.File(arg))
elif arg == '-dylib_file': elif arg == '-dylib_file':
@ -777,13 +791,14 @@ class SubstitutionEnvironment(object):
elif arg in ['-mno-cygwin', elif arg in ['-mno-cygwin',
'-pthread', '-pthread',
'-openmp', '-openmp',
'-fmerge-all-constants',
'-fopenmp']: '-fopenmp']:
dict['CCFLAGS'].append(arg) dict['CCFLAGS'].append(arg)
dict['LINKFLAGS'].append(arg) dict['LINKFLAGS'].append(arg)
elif arg == '-mwindows': elif arg == '-mwindows':
dict['LINKFLAGS'].append(arg) dict['LINKFLAGS'].append(arg)
elif arg[:5] == '-std=': elif arg[:5] == '-std=':
if arg[5:].find('++')!=-1: if '++' in arg[5:]:
key='CXXFLAGS' key='CXXFLAGS'
else: else:
key='CFLAGS' key='CFLAGS'
@ -791,7 +806,7 @@ class SubstitutionEnvironment(object):
elif arg[0] == '+': elif arg[0] == '+':
dict['CCFLAGS'].append(arg) dict['CCFLAGS'].append(arg)
dict['LINKFLAGS'].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 append_next_arg_to = arg
else: else:
dict['CCFLAGS'].append(arg) dict['CCFLAGS'].append(arg)
@ -850,26 +865,28 @@ class SubstitutionEnvironment(object):
t.append(v) t.append(v)
else: else:
### keep right-most occurence ### keep right-most occurence
orig.reverse() for v in orig[::-1]:
for v in orig:
if v not in t: if v not in t:
t.insert(0, v) t.insert(0, v)
self[key] = t self[key] = t
return self 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 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 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): def default_copy_from_cache(src, dst):
f = SCons.Defaults.DefaultEnvironment().copy_from_cache f = SCons.Defaults.DefaultEnvironment().copy_from_cache
return f(src, dst) return f(src, dst)
class Base(SubstitutionEnvironment): class Base(SubstitutionEnvironment):
"""Base class for "real" construction Environments. These are the """Base class for "real" construction Environments. These are the
primary objects used to communicate dependency and construction primary objects used to communicate dependency and construction
@ -1217,7 +1234,7 @@ class Base(SubstitutionEnvironment):
return path return path
def AppendENVPath(self, name, newpath, envname = 'ENV', 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' """Append path elements to the path 'name' in the 'ENV'
dictionary for this environment. Will only add any particular dictionary for this environment. Will only add any particular
path once, and will normpath and normcase all paths to help 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)) dk = list(filter(lambda x, val=val: x not in val, dk))
self._dict[key] = dk + [val] self._dict[key] = dk + [val]
else: else:
if not val in dk: if val not in dk:
self._dict[key] = dk + [val] self._dict[key] = dk + [val]
else: else:
if key == 'CPPDEFINES': if key == 'CPPDEFINES':
@ -1420,38 +1437,30 @@ class Base(SubstitutionEnvironment):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.EnvironmentClone') if SCons.Debug.track_instances: logInstanceCreation(self, 'Environment.EnvironmentClone')
return clone return clone
def Copy(self, *args, **kw): def _changed_build(self, dependency, target, prev_ni, repo_node=None):
global _warn_copy_deprecated if dependency.changed_state(target, prev_ni, repo_node):
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):
return 1 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): def _changed_content(self, dependency, target, prev_ni, repo_node=None):
return dependency.changed_content(target, prev_ni) 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() target_env = dependency.get_build_env()
type = target_env.get_tgt_sig_type() type = target_env.get_tgt_sig_type()
if type == 'source': if type == 'source':
return target_env.decide_source(dependency, target, prev_ni) return target_env.decide_source(dependency, target, prev_ni, repo_node)
else: 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): def _changed_timestamp_then_content(self, dependency, target, prev_ni, repo_node=None):
return dependency.changed_timestamp_then_content(target, prev_ni) return dependency.changed_timestamp_then_content(target, prev_ni, repo_node)
def _changed_timestamp_newer(self, dependency, target, prev_ni): def _changed_timestamp_newer(self, dependency, target, prev_ni, repo_node=None):
return dependency.changed_timestamp_newer(target, prev_ni) return dependency.changed_timestamp_newer(target, prev_ni, repo_node)
def _changed_timestamp_match(self, dependency, target, prev_ni): def _changed_timestamp_match(self, dependency, target, prev_ni, repo_node=None):
return dependency.changed_timestamp_match(target, prev_ni) return dependency.changed_timestamp_match(target, prev_ni, repo_node)
def _copy_from_cache(self, src, dst): def _copy_from_cache(self, src, dst):
return self.fs.copy(src, dst) return self.fs.copy(src, dst)
@ -1483,8 +1492,14 @@ class Base(SubstitutionEnvironment):
self.copy_from_cache = copy_function self.copy_from_cache = copy_function
def Detect(self, progs): def Detect(self, progs):
"""Return the first available program in 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): if not SCons.Util.is_List(progs):
progs = [ progs ] progs = [ progs ]
@ -1493,7 +1508,22 @@ class Base(SubstitutionEnvironment):
if path: return prog if path: return prog
return None return None
def Dictionary(self, *args): 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: if not args:
return self._dict return self._dict
dlist = [self._dict[x] for x in args] dlist = [self._dict[x] for x in args]
@ -1501,31 +1531,56 @@ class Base(SubstitutionEnvironment):
dlist = dlist[0] dlist = dlist[0]
return dlist 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 def Dump(self, key=None, format='pretty'):
be used as an index into the build environment dictionary and """ Return construction variables serialized to a string.
whatever is found there will be fed into the pretty printer. Note
that this key is case sensitive. 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"`)
""" """
import pprint
pp = pprint.PrettyPrinter(indent=2)
if key: if key:
dict = self.Dictionary(key) cvars = self.Dictionary(key)
else: else:
dict = self.Dictionary() cvars = self.Dictionary()
return pp.pformat(dict)
fmt = format.lower()
if fmt == 'pretty':
import pprint
pp = pprint.PrettyPrinter(indent=2)
# 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:
raise ValueError("Unsupported serialization format: %s." % fmt)
def FindIxes(self, paths, prefix, suffix): 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) suffix = self.subst('$'+suffix)
@ -1568,12 +1623,12 @@ class Base(SubstitutionEnvironment):
""" """
filename = self.subst(filename) filename = self.subst(filename)
try: try:
fp = open(filename, 'r') with open(filename, 'r') as fp:
lines = SCons.Util.LogicalLines(fp).readlines()
except IOError: except IOError:
if must_exist: if must_exist:
raise raise
return return
lines = SCons.Util.LogicalLines(fp).readlines()
lines = [l for l in lines if l[0] != '#'] lines = [l for l in lines if l[0] != '#']
tdlist = [] tdlist = []
for line in lines: for line in lines:
@ -1590,7 +1645,7 @@ class Base(SubstitutionEnvironment):
for td in tdlist: for td in tdlist:
targets.extend(td[0]) targets.extend(td[0])
if len(targets) > 1: if len(targets) > 1:
raise SCons.Errors.UserError( raise UserError(
"More than one dependency target found in `%s': %s" "More than one dependency target found in `%s': %s"
% (filename, targets)) % (filename, targets))
for target, depends in tdlist: for target, depends in tdlist:
@ -1722,7 +1777,7 @@ class Base(SubstitutionEnvironment):
dk = [x for x in dk if x not in val] dk = [x for x in dk if x not in val]
self._dict[key] = [val] + dk self._dict[key] = [val] + dk
else: else:
if not val in dk: if val not in dk:
self._dict[key] = [val] + dk self._dict[key] = [val] + dk
else: else:
if delete_existing: if delete_existing:
@ -1833,7 +1888,7 @@ class Base(SubstitutionEnvironment):
uniq = {} uniq = {}
for executor in [n.get_executor() for n in nodes]: for executor in [n.get_executor() for n in nodes]:
uniq[executor] = 1 uniq[executor] = 1
for executor in list(uniq.keys()): for executor in uniq.keys():
executor.add_pre_action(action) executor.add_pre_action(action)
return nodes return nodes
@ -1843,7 +1898,7 @@ class Base(SubstitutionEnvironment):
uniq = {} uniq = {}
for executor in [n.get_executor() for n in nodes]: for executor in [n.get_executor() for n in nodes]:
uniq[executor] = 1 uniq[executor] = 1
for executor in list(uniq.keys()): for executor in uniq.keys():
executor.add_post_action(action) executor.add_post_action(action)
return nodes return nodes
@ -1908,14 +1963,6 @@ class Base(SubstitutionEnvironment):
t.set_always_build() t.set_always_build()
return tlist 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): def Builder(self, **kw):
nkw = self.subst_kw(kw) nkw = self.subst_kw(kw)
return SCons.Builder.Builder(**nkw) return SCons.Builder.Builder(**nkw)
@ -1954,13 +2001,42 @@ class Base(SubstitutionEnvironment):
be any type that the Builder constructor will accept be any type that the Builder constructor will accept
for an action.""" for an action."""
bkw = { bkw = {
'action' : action, 'action': action,
'target_factory' : self.fs.Entry, 'target_factory': self.fs.Entry,
'source_factory' : self.fs.Entry, 'source_factory': self.fs.Entry,
} }
try: bkw['source_scanner'] = kw['source_scanner'] # source scanner
except KeyError: pass try:
else: del kw['source_scanner'] 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) bld = SCons.Builder.Builder(**bkw)
return bld(self, target, source, **kw) return bld(self, target, source, **kw)
@ -2029,7 +2105,7 @@ class Base(SubstitutionEnvironment):
""" """
action = self.Action(action, *args, **kw) action = self.Action(action, *args, **kw)
result = action([], [], self) result = action([], [], self)
if isinstance(result, SCons.Errors.BuildError): if isinstance(result, BuildError):
errstr = result.errstr errstr = result.errstr
if result.filename: if result.filename:
errstr = result.filename + ': ' + errstr errstr = result.filename + ': ' + errstr
@ -2149,7 +2225,7 @@ class Base(SubstitutionEnvironment):
for side_effect in side_effects: for side_effect in side_effects:
if side_effect.multiple_side_effect_has_builder(): 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.add_source(targets)
side_effect.side_effect = 1 side_effect.side_effect = 1
self.Precious(side_effect) self.Precious(side_effect)
@ -2157,34 +2233,6 @@ class Base(SubstitutionEnvironment):
target.side_effects.append(side_effect) target.side_effects.append(side_effect)
return side_effects 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): def Split(self, arg):
"""This function converts a string or list into a list of strings """This function converts a string or list into a list of strings
or Nodes. This makes things easier for users by allowing files to or Nodes. This makes things easier for users by allowing files to
@ -2206,32 +2254,10 @@ class Base(SubstitutionEnvironment):
else: else:
return [self.subst(arg)] return [self.subst(arg)]
def TargetSignatures(self, type): def Value(self, value, built_value=None, name=None):
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):
""" """
""" """
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): def VariantDir(self, variant_dir, src_dir, duplicate=1):
variant_dir = self.arg2nodes(variant_dir, self.fs.Dir)[0] variant_dir = self.arg2nodes(variant_dir, self.fs.Dir)[0]
@ -2255,10 +2281,10 @@ class Base(SubstitutionEnvironment):
build_source(node.all_children()) build_source(node.all_children())
def final_source(node): def final_source(node):
while (node != node.srcnode()): while node != node.srcnode():
node = node.srcnode() node = node.srcnode()
return node return node
sources = list(map( final_source, sources )); sources = list(map(final_source, sources))
# remove duplicates # remove duplicates
return list(set(sources)) return list(set(sources))
@ -2299,7 +2325,20 @@ class OverrideEnvironment(Base):
# Methods that make this class act like a proxy. # Methods that make this class act like a proxy.
def __getattr__(self, name): 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): def __setattr__(self, name, value):
setattr(self.__dict__['__subject'], name, value) setattr(self.__dict__['__subject'], name, value)
@ -2309,10 +2348,12 @@ class OverrideEnvironment(Base):
return self.__dict__['overrides'][key] return self.__dict__['overrides'][key]
except KeyError: except KeyError:
return self.__dict__['__subject'].__getitem__(key) return self.__dict__['__subject'].__getitem__(key)
def __setitem__(self, key, value): def __setitem__(self, key, value):
if not is_valid_construction_var(key): 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 self.__dict__['overrides'][key] = value
def __delitem__(self, key): def __delitem__(self, key):
try: try:
del self.__dict__['overrides'][key] del self.__dict__['overrides'][key]
@ -2327,30 +2368,43 @@ class OverrideEnvironment(Base):
raise raise
result = None result = None
return result return result
def get(self, key, default=None): def get(self, key, default=None):
"""Emulates the get() method of dictionaries.""" """Emulates the get() method of dictionaries."""
try: try:
return self.__dict__['overrides'][key] return self.__dict__['overrides'][key]
except KeyError: except KeyError:
return self.__dict__['__subject'].get(key, default) return self.__dict__['__subject'].get(key, default)
def has_key(self, key): def has_key(self, key):
"""Emulates the has_key() method of dictionaries."""
try: try:
self.__dict__['overrides'][key] self.__dict__['overrides'][key]
return 1 return 1
except KeyError: except KeyError:
return key in self.__dict__['__subject'] return key in self.__dict__['__subject']
def __contains__(self, key): def __contains__(self, key):
if self.__dict__['overrides'].__contains__(key): if self.__dict__['overrides'].__contains__(key):
return 1 return 1
return self.__dict__['__subject'].__contains__(key) return self.__dict__['__subject'].__contains__(key)
def Dictionary(self): def Dictionary(self):
"""Emulates the items() method of dictionaries."""
d = self.__dict__['__subject'].Dictionary().copy() d = self.__dict__['__subject'].Dictionary().copy()
d.update(self.__dict__['overrides']) d.update(self.__dict__['overrides'])
return d return d
def items(self): def items(self):
"""Emulates the items() method of dictionaries.""" """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. # Overridden private construction environment methods.
def _update(self, dict): def _update(self, dict):
@ -2372,6 +2426,7 @@ class OverrideEnvironment(Base):
kw = copy_non_reserved_keywords(kw) kw = copy_non_reserved_keywords(kw)
self.__dict__['overrides'].update(semi_deepcopy(kw)) self.__dict__['overrides'].update(semi_deepcopy(kw))
# The entry point that will be used by the external world # The entry point that will be used by the external world
# to refer to a construction environment. This allows the wrapper # to refer to a construction environment. This allows the wrapper
# interface to extend a construction environment for its own purposes # 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # a copy of this software and associated documentation files (the
@ -28,64 +28,60 @@ 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 shutil
import SCons.Util import SCons.Util
class BuildError(Exception): 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.
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.
status : the return code of the action that caused the build error. exitstatus: SCons exit status due to this build error.
Must be set to a non-zero value even if the build error is not due Must be nonzero unless due to an explicit Exit()
to an action returning a non-zero returned code. call. Not always the same as status, since
actions return a status code that should be
respected, but SCons typically exits with 2
irrespective of the return value of the failed
action.
exitstatus : SCons exit status due to this build error. filename: The name of the file or directory that caused the
Must be nonzero unless due to an explicit Exit() build error. Set to None if no files are associated with
call. Not always the same as status, since this error. This might be different from the target
actions return a status code that should be being built. For example, failure to create the
respected, but SCons typically exits with 2 directory in which the target file will appear. It
irrespective of the return value of the failed can be None if the error is not due to a particular
action. filename.
filename : The name of the file or directory that caused the exc_info: Info about exception that caused the build
build error. Set to None if no files are associated with error. Set to (None, None, None) if this build
this error. This might be different from the target error is not due to an exception.
being built. For example, failure to create the
directory in which the target file will appear. It
can be None if the error is not due to a particular
filename.
exc_info : Info about exception that caused the build Information about the what caused the build error :
error. Set to (None, None, None) if this build
error is not due to an exception.
node: the error occurred while building this target node(s)
Information about the cause of the location of the error: executor: the executor that caused the build to fail (might
--------------------------------------------------------- be None if the build failures is not due to the
executor failing)
node : the error occured while building this target node(s) action: the action that caused the build to fail (might be
None if the build failures is not due to the an
action failure)
executor : the executor that caused the build to fail (might command: the command line for the action that caused the
be None if the build failures is not due to the build to fail (might be None if the build failures
executor failing) is not due to the an action failure)
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
build to fail (might be None if the build failures
is not due to the an action failure)
""" """
def __init__(self, def __init__(self,
@ -95,7 +91,7 @@ class BuildError(Exception):
# py3: errstr should be string and not bytes. # 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.status = status
self.exitstatus = exitstatus self.exitstatus = exitstatus
self.filename = filename self.filename = filename
@ -124,7 +120,7 @@ class UserError(Exception):
class StopError(Exception): class StopError(Exception):
pass pass
class EnvironmentError(Exception): class SConsEnvironmentError(Exception):
pass pass
class MSVCError(IOError): class MSVCError(IOError):
@ -138,14 +134,15 @@ class ExplicitExit(Exception):
Exception.__init__(self, *args) Exception.__init__(self, *args)
def convert_to_BuildError(status, exc_info=None): def convert_to_BuildError(status, exc_info=None):
""" """Convert a return code to a BuildError Exception.
Convert any return code a BuildError Exception.
:Parameters: The `buildError.status` we set here will normally be
- `status`: can either be a return code or an Exception.
The buildError.status we set here will normally be
used as the exit status of the "scons" process. 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): if not exc_info and isinstance(status, Exception):
@ -176,28 +173,27 @@ def convert_to_BuildError(status, exc_info=None):
filename = status.filename filename = status.filename
except AttributeError: except AttributeError:
filename = None filename = None
buildError = BuildError( buildError = BuildError(
errstr=status.args[0], errstr=status.args[0],
status=status.errno, status=status.errno,
exitstatus=2, exitstatus=2,
filename=filename, filename=filename,
exc_info=exc_info) exc_info=exc_info)
elif isinstance(status, (EnvironmentError, OSError, IOError)): elif isinstance(status, (SConsEnvironmentError, OSError, IOError)):
# If an IOError/OSError happens, raise a BuildError. # If an IOError/OSError happens, raise a BuildError.
# Report the name of the file or directory that caused the # Report the name of the file or directory that caused the
# error, which might be different from the target being built # error, which might be different from the target being built
# (for example, failure to create the directory in which the # (for example, failure to create the directory in which the
# target file will appear). # target file will appear).
try: filename = getattr(status, 'filename', None)
filename = status.filename strerror = getattr(status, 'strerror', str(status))
except AttributeError: errno = getattr(status, 'errno', 2)
filename = None
buildError = BuildError( buildError = BuildError(
errstr=status.strerror, errstr=strerror,
status=status.errno, status=errno,
exitstatus=2, exitstatus=2,
filename=filename, filename=filename,
exc_info=exc_info) exc_info=exc_info)
@ -217,7 +213,7 @@ def convert_to_BuildError(status, exc_info=None):
errstr="Error %s" % status, errstr="Error %s" % status,
status=status, status=status,
exitstatus=2) exitstatus=2)
#import sys #import sys
#sys.stderr.write("convert_to_BuildError: status %s => (errstr %s, status %s)\n"%(status,buildError.errstr, buildError.status)) #sys.stderr.write("convert_to_BuildError: status %s => (errstr %s, status %s)\n"%(status,buildError.errstr, buildError.status))
return buildError return buildError

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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__revision__ = "src/engine/SCons/Executor.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import collections import collections
@ -36,15 +34,16 @@ import SCons.Debug
from SCons.Debug import logInstanceCreation from SCons.Debug import logInstanceCreation
import SCons.Errors import SCons.Errors
import SCons.Memoize 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 """Remembers exact association between targets
and sources of executor.""" and sources of executor."""
__slots__ = ('targets', __slots__ = ('targets',
'sources') 'sources')
def __init__(self, targets=[], sources=[]): def __init__(self, targets=[], sources=[]):
self.targets = targets self.targets = targets
self.sources = sources self.sources = sources
@ -71,7 +70,7 @@ class TSList(collections.UserList):
return nl[i] return nl[i]
def __getslice__(self, i, j): def __getslice__(self, i, j):
nl = self.func() nl = self.func()
i = max(i, 0); j = max(j, 0) i, j = max(i, 0), max(j, 0)
return nl[i:j] return nl[i:j]
def __str__(self): def __str__(self):
nl = self.func() nl = self.func()
@ -80,7 +79,7 @@ class TSList(collections.UserList):
nl = self.func() nl = self.func()
return repr(nl) return repr(nl)
class TSObject(object): class TSObject:
"""A class that implements $TARGET or $SOURCE expansions by wrapping """A class that implements $TARGET or $SOURCE expansions by wrapping
an Executor method. an Executor method.
""" """
@ -127,13 +126,13 @@ def execute_action_list(obj, target, kw):
status = act(*args, **kw) status = act(*args, **kw)
if isinstance(status, SCons.Errors.BuildError): if isinstance(status, SCons.Errors.BuildError):
status.executor = obj status.executor = obj
raise status raise status # TODO pylint E0702: raising int not allowed
elif status: elif status:
msg = "Error %s" % status msg = "Error %s" % status
raise SCons.Errors.BuildError( raise SCons.Errors.BuildError(
errstr=msg, errstr=msg,
node=obj.batches[0].targets, node=obj.batches[0].targets,
executor=obj, executor=obj,
action=act) action=act)
return status return status
@ -155,7 +154,7 @@ _execute_str_map = {0 : execute_null_str,
1 : execute_actions_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. """A class for controlling instances of executing an action.
This largely exists to hold a single association of 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 """Fetch the signature contents. This is the main reason this
class exists, so we can compute this once and cache it regardless class exists, so we can compute this once and cache it regardless
of how many target or source Nodes there are. of how many target or source Nodes there are.
Returns bytes
""" """
try: try:
return self._memo['get_contents'] return self._memo['get_contents']
@ -570,7 +571,6 @@ def AddBatchExecutor(key, executor):
nullenv = None nullenv = None
import SCons.Util
class NullEnvironment(SCons.Util.Null): class NullEnvironment(SCons.Util.Null):
import SCons.CacheDir import SCons.CacheDir
_CacheDir_path = None _CacheDir_path = None
@ -587,7 +587,7 @@ def get_NullEnvironment():
nullenv = NullEnvironment() nullenv = NullEnvironment()
return nullenv return nullenv
class Null(object, with_metaclass(NoSlotsPyPy)): class Null(object, metaclass=NoSlotsPyPy):
"""A null Executor, with a null build Environment, that does """A null Executor, with a null build Environment, that does
nothing when the rest of the methods call it. nothing when the rest of the methods call it.
@ -595,7 +595,7 @@ class Null(object, with_metaclass(NoSlotsPyPy)):
disassociate Builders from Nodes entirely, so we're not disassociate Builders from Nodes entirely, so we're not
going to worry about unit tests for this--at least for now. going to worry about unit tests for this--at least for now.
""" """
__slots__ = ('pre_actions', __slots__ = ('pre_actions',
'post_actions', 'post_actions',
'env', 'env',
@ -611,9 +611,10 @@ class Null(object, with_metaclass(NoSlotsPyPy)):
'action_list', 'action_list',
'_do_execute', '_do_execute',
'_execute_str') '_execute_str')
def __init__(self, *args, **kw): 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'][:], [])] self.batches = [Batch(kw['targets'][:], [])]
def get_build_env(self): def get_build_env(self):
return get_NullEnvironment() return get_NullEnvironment()
@ -647,7 +648,7 @@ class Null(object, with_metaclass(NoSlotsPyPy)):
"""Morph this Null executor to a real Executor object.""" """Morph this Null executor to a real Executor object."""
batches = self.batches batches = self.batches
self.__class__ = Executor self.__class__ = Executor
self.__init__([]) self.__init__([])
self.batches = batches self.batches = batches
# The following methods require morphing this Null Executor to a # The following methods require morphing this Null Executor to a

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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 import SCons.compat
@ -52,18 +52,18 @@ default_stack_size = 256
interrupt_msg = 'Build interrupted.' interrupt_msg = 'Build interrupted.'
class InterruptState(object): class InterruptState:
def __init__(self): def __init__(self):
self.interrupted = False self.interrupted = False
def set(self): def set(self):
self.interrupted = True self.interrupted = True
def __call__(self): def __call__(self):
return self.interrupted return self.interrupted
class Jobs(object): class Jobs:
"""An instance of this class initializes N jobs, and provides """An instance of this class initializes N jobs, and provides
methods for starting, stopping, and waiting on all N jobs. methods for starting, stopping, and waiting on all N jobs.
""" """
@ -87,7 +87,7 @@ class Jobs(object):
stack_size = explicit_stack_size stack_size = explicit_stack_size
if stack_size is None: if stack_size is None:
stack_size = default_stack_size stack_size = default_stack_size
try: try:
self.job = Parallel(taskmaster, num, stack_size) self.job = Parallel(taskmaster, num, stack_size)
self.num_jobs = num self.num_jobs = num
@ -163,7 +163,7 @@ class Jobs(object):
except AttributeError: except AttributeError:
pass pass
class Serial(object): class Serial:
"""This class is used to execute tasks in series, and is more efficient """This class is used to execute tasks in series, and is more efficient
than Parallel, but is only appropriate for non-parallel builds. Only than Parallel, but is only appropriate for non-parallel builds. Only
one instance of this class should be in existence at a time. one instance of this class should be in existence at a time.
@ -172,14 +172,14 @@ class Serial(object):
""" """
def __init__(self, taskmaster): def __init__(self, taskmaster):
"""Create a new serial job given a taskmaster. """Create a new serial job given a taskmaster.
The taskmaster's next_task() method should return the next task The taskmaster's next_task() method should return the next task
that needs to be executed, or None if there are no more tasks. The that needs to be executed, or None if there are no more tasks. The
taskmaster's executed() method will be called for each task when it taskmaster's executed() method will be called for each task when it
is successfully executed, or failed() will be called if it failed to is successfully executed, or failed() will be called if it failed to
execute (e.g. execute() raised an exception).""" execute (e.g. execute() raised an exception)."""
self.taskmaster = taskmaster self.taskmaster = taskmaster
self.interrupted = InterruptState() self.interrupted = InterruptState()
@ -188,7 +188,7 @@ class Serial(object):
and executing them, and return when there are no more tasks. If a task and executing them, and return when there are no more tasks. If a task
fails to execute (i.e. execute() raises an exception), then the job will fails to execute (i.e. execute() raises an exception), then the job will
stop.""" stop."""
while True: while True:
task = self.taskmaster.next_task() task = self.taskmaster.next_task()
@ -199,7 +199,7 @@ class Serial(object):
task.prepare() task.prepare()
if task.needs_execute(): if task.needs_execute():
task.execute() task.execute()
except: except Exception:
if self.interrupted(): if self.interrupted():
try: try:
raise SCons.Errors.BuildError( raise SCons.Errors.BuildError(
@ -264,12 +264,12 @@ else:
self.resultsQueue.put((task, ok)) self.resultsQueue.put((task, ok))
class ThreadPool(object): class ThreadPool:
"""This class is responsible for spawning and managing worker threads.""" """This class is responsible for spawning and managing worker threads."""
def __init__(self, num, stack_size, interrupted): def __init__(self, num, stack_size, interrupted):
"""Create the request and reply queues, and 'num' worker threads. """Create the request and reply queues, and 'num' worker threads.
One must specify the stack size of the worker threads. The One must specify the stack size of the worker threads. The
stack size is specified in kilobytes. stack size is specified in kilobytes.
""" """
@ -277,11 +277,11 @@ else:
self.resultsQueue = queue.Queue(0) self.resultsQueue = queue.Queue(0)
try: try:
prev_size = threading.stack_size(stack_size*1024) prev_size = threading.stack_size(stack_size*1024)
except AttributeError as e: except AttributeError as e:
# Only print a warning if the stack size has been # Only print a warning if the stack size has been
# explicitly set. # 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 " + \ msg = "Setting stack size is unsupported by this version of Python:\n " + \
e.args[0] e.args[0]
SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg) SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg)
@ -322,7 +322,7 @@ else:
self.requestQueue.put(None) self.requestQueue.put(None)
# Wait for all of the workers to terminate. # Wait for all of the workers to terminate.
# #
# If we don't do this, later Python versions (2.4, 2.5) often # If we don't do this, later Python versions (2.4, 2.5) often
# seem to raise exceptions during shutdown. This happens # seem to raise exceptions during shutdown. This happens
# in requestQueue.get(), as an assertion failure that # in requestQueue.get(), as an assertion failure that
@ -338,8 +338,8 @@ else:
worker.join(1.0) worker.join(1.0)
self.workers = [] self.workers = []
class Parallel(object): class Parallel:
"""This class is used to execute tasks in parallel, and is somewhat """This class is used to execute tasks in parallel, and is somewhat
less efficient than Serial, but is appropriate for parallel builds. less efficient than Serial, but is appropriate for parallel builds.
This class is thread safe. This class is thread safe.
@ -373,7 +373,7 @@ else:
an exception), then the job will stop.""" an exception), then the job will stop."""
jobs = 0 jobs = 0
while True: while True:
# Start up as many available tasks as we're # Start up as many available tasks as we're
# allowed to. # allowed to.

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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# #
from __future__ import print_function __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__revision__ = "src/engine/SCons/Memoize.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__doc__ = """Memoizer __doc__ = """Memoizer
@ -106,7 +104,7 @@ use_memoizer = None
# Global list of counter objects # Global list of counter objects
CounterList = {} CounterList = {}
class Counter(object): class Counter:
""" """
Base class for counting memoization hits and misses. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 collections
import SCons.Errors import SCons.Errors
import SCons.Node import SCons.Node
import SCons.Util import SCons.Util
from SCons.Util import MD5signature
class AliasNameSpace(collections.UserDict): class AliasNameSpace(collections.UserDict):
def Alias(self, name, **kw): def Alias(self, name, **kw):
@ -166,7 +167,7 @@ class Alias(SCons.Node.Node):
pass pass
contents = self.get_contents() contents = self.get_contents()
csig = SCons.Util.MD5signature(contents) csig = MD5signature(contents)
self.get_ninfo().csig = csig self.get_ninfo().csig = csig
return 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__revision__ = "src/engine/SCons/Node/FS.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import fnmatch import fnmatch
import os import os
@ -43,6 +41,8 @@ import stat
import sys import sys
import time import time
import codecs import codecs
from itertools import chain
import importlib.util
import SCons.Action import SCons.Action
import SCons.Debug import SCons.Debug
@ -53,12 +53,15 @@ import SCons.Node
import SCons.Node.Alias import SCons.Node.Alias
import SCons.Subst import SCons.Subst
import SCons.Util import SCons.Util
from SCons.Util import MD5signature, MD5filesignature, MD5collect
import SCons.Warnings import SCons.Warnings
from SCons.Debug import Trace from SCons.Debug import Trace
print_duplicate = 0 print_duplicate = 0
MD5_TIMESTAMP_DEBUG = False
def sconsign_none(node): def sconsign_none(node):
raise NotImplementedError raise NotImplementedError
@ -74,6 +77,9 @@ def sconsign_dir(node):
_sconsign_map = {0 : sconsign_none, _sconsign_map = {0 : sconsign_none,
1 : sconsign_dir} 1 : sconsign_dir}
class FileBuildInfoFileToCsigMappingError(Exception):
pass
class EntryProxyAttributeError(AttributeError): class EntryProxyAttributeError(AttributeError):
""" """
An AttributeError subclass for recording and displaying the name An AttributeError subclass for recording and displaying the name
@ -132,7 +138,10 @@ def initialize_do_splitdrive():
global do_splitdrive global do_splitdrive
global has_unc global has_unc
drive, path = os.path.splitdrive('X:/foo') 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 do_splitdrive = not not drive or has_unc
@ -272,7 +281,7 @@ def set_duplicate(duplicate):
'copy' : _copy_func 'copy' : _copy_func
} }
if not duplicate in Valid_Duplicates: if duplicate not in Valid_Duplicates:
raise SCons.Errors.InternalError("The argument of set_duplicate " raise SCons.Errors.InternalError("The argument of set_duplicate "
"should be in Valid_Duplicates") "should be in Valid_Duplicates")
global Link_Funcs global Link_Funcs
@ -282,11 +291,13 @@ def set_duplicate(duplicate):
Link_Funcs.append(link_dict[func]) Link_Funcs.append(link_dict[func])
def LinkFunc(target, source, env): def LinkFunc(target, source, env):
# Relative paths cause problems with symbolic links, so """
# we use absolute paths, which may be a problem for people Relative paths cause problems with symbolic links, so
# who want to move their soft-linked src-trees around. Those we use absolute paths, which may be a problem for people
# people should use the 'hard-copy' mode, softlinks cannot be who want to move their soft-linked src-trees around. Those
# used for that; at least I have no idea how ... 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() src = source[0].get_abspath()
dest = target[0].get_abspath() dest = target[0].get_abspath()
dir, file = os.path.split(dest) dir, file = os.path.split(dest)
@ -328,7 +339,12 @@ Unlink = SCons.Action.Action(UnlinkFunc, None)
def MkdirFunc(target, source, env): def MkdirFunc(target, source, env):
t = target[0] 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()) t.fs.mkdir(t.get_abspath())
return 0 return 0
@ -351,7 +367,7 @@ def get_MkdirBuilder():
name = "MkdirBuilder") name = "MkdirBuilder")
return MkdirBuilder return MkdirBuilder
class _Null(object): class _Null:
pass pass
_null = _Null() _null = _Null()
@ -367,7 +383,7 @@ else:
class DiskChecker(object): class DiskChecker:
def __init__(self, type, do, ignore): def __init__(self, type, do, ignore):
self.type = type self.type = type
self.do = do self.do = do
@ -463,7 +479,7 @@ class EntryProxy(SCons.Util.Proxy):
return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix") return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix")
def __get_windows_path(self): 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.""" regardless of platform."""
if OS_SEP == '\\': if OS_SEP == '\\':
return self return self
@ -514,7 +530,7 @@ class EntryProxy(SCons.Util.Proxy):
except KeyError: except KeyError:
try: try:
attr = SCons.Util.Proxy.__getattr__(self, name) attr = SCons.Util.Proxy.__getattr__(self, name)
except AttributeError as e: except AttributeError:
# Raise our own AttributeError subclass with an # Raise our own AttributeError subclass with an
# overridden __str__() method that identifies the # overridden __str__() method that identifies the
# name of the entry that caused the exception. # name of the entry that caused the exception.
@ -682,13 +698,32 @@ class Base(SCons.Node.Node):
@SCons.Memoize.CountMethodCall @SCons.Memoize.CountMethodCall
def stat(self): def stat(self):
try: return self._memo['stat'] try:
except KeyError: pass return self._memo['stat']
try: result = self.fs.stat(self.get_abspath()) except KeyError:
except os.error: result = None pass
try:
result = self.fs.stat(self.get_abspath())
except os.error:
result = None
self._memo['stat'] = result self._memo['stat'] = result
return 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): def exists(self):
return SCons.Node._exists_map[self._func_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) return SCons.Node._rexists_map[self._func_rexists](self)
def getmtime(self): def getmtime(self):
st = self.stat() if self.islink():
if st: return st[stat.ST_MTIME] st = self.lstat()
else: return None else:
st = self.stat()
if st:
return st[stat.ST_MTIME]
else:
return None
def getsize(self): def getsize(self):
st = self.stat() if self.islink():
if st: return st[stat.ST_SIZE] st = self.lstat()
else: return None else:
st = self.stat()
if st:
return st[stat.ST_SIZE]
else:
return None
def isdir(self): def isdir(self):
st = self.stat() st = self.stat()
@ -938,14 +985,14 @@ class Entry(Base):
def disambiguate(self, must_exist=None): def disambiguate(self, must_exist=None):
""" """
""" """
if self.isdir(): if self.isfile():
self.__class__ = Dir
self._morph()
elif self.isfile():
self.__class__ = File self.__class__ = File
self._morph() self._morph()
self.clear() self.clear()
elif self.isdir():
self.__class__ = Dir
self._morph()
else: else:
# There was nothing on-disk at this location, so look in # There was nothing on-disk at this location, so look in
# the src directory. # the src directory.
@ -1047,22 +1094,23 @@ class Entry(Base):
_classEntry = Entry _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 Note that there's a very good chance we'll refactor this part of
# a local file system. Essentially, this wraps any function in the architecture in some way as we really implement the interface(s)
# the os, os.path or shutil modules that we use to actually go do for remote file system Nodes. For example, the right architecture
# anything with or to the local file system. 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.
# 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) We're not using chdir() yet because the calling subclass method
# for remote file system Nodes. For example, the right architecture needs to use os.chdir() directly to avoid recursion. Will we
# might be to have this be a subclass instead of a base class. really need this one?
# 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): #def chdir(self, path):
# return os.chdir(path) # return os.chdir(path)
def chmod(self, path, mode): def chmod(self, path, mode):
@ -1389,10 +1437,10 @@ class FS(LocalFS):
if not isinstance(d, SCons.Node.Node): if not isinstance(d, SCons.Node.Node):
d = self.Dir(d) d = self.Dir(d)
self.Top.addRepository(d) self.Top.addRepository(d)
def PyPackageDir(self, modulename): 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 For example scons might resolve to
Windows: C:\Python27\Lib\site-packages\scons-2.5.1 Windows: C:\Python27\Lib\site-packages\scons-2.5.1
Linux: /usr/lib/scons Linux: /usr/lib/scons
@ -1400,22 +1448,10 @@ class FS(LocalFS):
This can be useful when we want to determine a toolpath based on a python module name""" This can be useful when we want to determine a toolpath based on a python module name"""
dirpath = '' 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 # Python3 Code
import imp modspec = importlib.util.find_spec(modulename)
splitname = modulename.split('.') dirpath = os.path.dirname(modspec.origin)
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) return self._lookup(dirpath, None, Dir, True)
@ -1522,9 +1558,7 @@ class Dir(Base):
self.repositories = [] self.repositories = []
self.srcdir = None self.srcdir = None
self.entries = {} self.entries = {'.': self, '..': self.dir}
self.entries['.'] = self
self.entries['..'] = self.dir
self.cwd = self self.cwd = self
self.searched = 0 self.searched = 0
self._sconsign = None self._sconsign = None
@ -1585,7 +1619,7 @@ class Dir(Base):
This clears any cached information that is invalidated by changing This clears any cached information that is invalidated by changing
the repository.""" the repository."""
for node in list(self.entries.values()): for node in self.entries.values():
if node != self.dir: if node != self.dir:
if node != self and isinstance(node, Dir): if node != self and isinstance(node, Dir):
node.__clearRepositoryCache(duplicate) node.__clearRepositoryCache(duplicate)
@ -1596,7 +1630,7 @@ class Dir(Base):
except AttributeError: except AttributeError:
pass pass
if duplicate is not None: if duplicate is not None:
node.duplicate=duplicate node.duplicate = duplicate
def __resetDuplicate(self, node): def __resetDuplicate(self, node):
if node != self: if node != self:
@ -1662,7 +1696,7 @@ class Dir(Base):
return result return result
def addRepository(self, dir): 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) self.repositories.append(dir)
dir._tpath = '.' dir._tpath = '.'
self.__clearRepositoryCache() self.__clearRepositoryCache()
@ -1702,7 +1736,7 @@ class Dir(Base):
if self is other: if self is other:
result = '.' result = '.'
elif not other in self._path_elements: elif other not in self._path_elements:
try: try:
other_dir = other.get_dir() other_dir = other.get_dir()
except AttributeError: except AttributeError:
@ -1836,7 +1870,7 @@ class Dir(Base):
node is called which has a child directory, the child node is called which has a child directory, the child
directory should return the hash of its contents.""" directory should return the hash of its contents."""
contents = self.get_contents() contents = self.get_contents()
return SCons.Util.MD5signature(contents) return MD5signature(contents)
def do_duplicate(self, src): def do_duplicate(self, src):
pass pass
@ -2234,7 +2268,7 @@ class RootDir(Dir):
this directory. this directory.
""" """
__slots__ = ['_lookupDict'] __slots__ = ('_lookupDict', 'abspath', 'path')
def __init__(self, drive, fs): def __init__(self, drive, fs):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir') if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir')
@ -2276,13 +2310,17 @@ class RootDir(Dir):
self._tpath = dirname self._tpath = dirname
self.dirname = 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._morph()
self.duplicate = 0 self.duplicate = 0
self._lookupDict = {} self._lookupDict = {'': self, '/': self}
self._lookupDict[''] = self
self._lookupDict['/'] = self
self.root = self self.root = self
# The // entry is necessary because os.path.normpath() # The // entry is necessary because os.path.normpath()
# preserves double slashes at the beginning of a path on Posix # preserves double slashes at the beginning of a path on Posix
@ -2302,9 +2340,7 @@ class RootDir(Dir):
self.repositories = [] self.repositories = []
self.srcdir = None self.srcdir = None
self.entries = {} self.entries = {'.': self, '..': self.dir}
self.entries['.'] = self
self.entries['..'] = self.dir
self.cwd = self self.cwd = self
self.searched = 0 self.searched = 0
self._sconsign = None self._sconsign = None
@ -2440,7 +2476,7 @@ class FileNodeInfo(SCons.Node.NodeInfoBase):
""" """
state = getattr(self, '__dict__', {}).copy() state = getattr(self, '__dict__', {}).copy()
for obj in type(self).mro(): for obj in type(self).mro():
for name in getattr(obj,'__slots__',()): for name in getattr(obj, '__slots__', ()):
if hasattr(self, name): if hasattr(self, name):
state[name] = getattr(self, name) state[name] = getattr(self, name)
@ -2462,11 +2498,42 @@ class FileNodeInfo(SCons.Node.NodeInfoBase):
if key not in ('__weakref__',): if key not in ('__weakref__',):
setattr(self, key, value) 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): 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 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): def convert_to_sconsign(self):
""" """
Converts this FileBuildInfo object for writing to a .sconsign file 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. Compute and return the MD5 hash for this file.
""" """
if not self.rexists(): if not self.rexists():
return SCons.Util.MD5signature('') return MD5signature('')
fname = self.rfile().get_abspath() fname = self.rfile().get_abspath()
try: try:
cs = SCons.Util.MD5filesignature(fname, cs = MD5filesignature(fname, chunksize=File.md5_chunksize * 1024)
chunksize=SCons.Node.FS.File.md5_chunksize*1024)
except EnvironmentError as e: except EnvironmentError as e:
if not e.filename: if not e.filename:
e.filename = fname e.filename = fname
@ -2971,7 +3037,7 @@ class File(Base):
@see: built() and Node.release_target_info() @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 return
if not hasattr(self.attributes, 'keep_targetinfo'): if not hasattr(self.attributes, 'keep_targetinfo'):
@ -3059,7 +3125,10 @@ class File(Base):
SCons.Node.Node.prepare(self) SCons.Node.Node.prepare(self)
if self.get_state() != SCons.Node.up_to_date: 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: if self.is_derived() and not self.precious:
self._rmv_existing() self._rmv_existing()
else: else:
@ -3153,7 +3222,7 @@ class File(Base):
if csig is None: if csig is None:
try: try:
if self.get_size() < SCons.Node.FS.File.md5_chunksize: if self.get_size() < File.md5_chunksize:
contents = self.get_contents() contents = self.get_contents()
else: else:
csig = self.get_content_hash() csig = self.get_content_hash()
@ -3225,46 +3294,232 @@ class File(Base):
self._memo['changed'] = has_changed self._memo['changed'] = has_changed
return 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() cur_csig = self.get_csig()
try: try:
return cur_csig != prev_ni.csig return cur_csig != prev_ni.csig
except AttributeError: except AttributeError:
return 1 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 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: 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: except AttributeError:
pass pass
return False 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: try:
return self.get_timestamp() > target.get_timestamp() return self.get_timestamp() > target.get_timestamp()
except AttributeError: except AttributeError:
return 1 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: try:
return self.get_timestamp() != prev_ni.timestamp return self.get_timestamp() != prev_ni.timestamp
except AttributeError: except AttributeError:
return 1 return 1
def is_up_to_date(self): 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 T = 0
if T: Trace('is_up_to_date(%s):' % self) if T: Trace('is_up_to_date(%s):' % self)
if not self.exists(): if not self.exists():
if T: Trace(' 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() r = self.rfile()
if r != self: 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 not self.changed(r):
if T: Trace(' changed(%s):' % r) if T: Trace(' changed(%s):' % r)
# ...and it's even up-to-date... # ...and it's even up-to-date...
@ -3272,7 +3527,9 @@ class File(Base):
# ...and they'd like a local copy. # ...and they'd like a local copy.
e = LocalCopy(self, r, None) e = LocalCopy(self, r, None)
if isinstance(e, SCons.Errors.BuildError): 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) SCons.Node.store_info_map[self.store_info](self)
if T: Trace(' 1\n') if T: Trace(' 1\n')
return 1 return 1
@ -3293,11 +3550,14 @@ class File(Base):
result = self result = self
if not self.exists(): if not self.exists():
norm_name = _my_normcase(self.name) norm_name = _my_normcase(self.name)
for dir in self.dir.get_all_rdirs(): for repo_dir in self.dir.get_all_rdirs():
try: node = dir.entries[norm_name] try:
except KeyError: node = dir.file_on_disk(self.name) node = repo_dir.entries[norm_name]
except KeyError:
node = repo_dir.file_on_disk(self.name)
if node and node.exists() and \ 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()): or not node.is_derived()):
result = node result = node
# Copy over our local attributes to the repository # Copy over our local attributes to the repository
@ -3317,6 +3577,28 @@ class File(Base):
self._memo['rfile'] = result self._memo['rfile'] = result
return 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): def rstr(self):
return str(self.rfile()) return str(self.rfile())
@ -3341,8 +3623,7 @@ class File(Base):
cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self) cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self)
if not self.exists() and cachefile and os.path.exists(cachefile): if not self.exists() and cachefile and os.path.exists(cachefile):
self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \ self.cachedir_csig = MD5filesignature(cachefile, File.md5_chunksize * 1024)
SCons.Node.FS.File.md5_chunksize * 1024)
else: else:
self.cachedir_csig = self.get_csig() self.cachedir_csig = self.get_csig()
return self.cachedir_csig return self.cachedir_csig
@ -3362,7 +3643,7 @@ class File(Base):
executor = self.get_executor() executor = self.get_executor()
result = self.contentsig = SCons.Util.MD5signature(executor.get_contents()) result = self.contentsig = MD5signature(executor.get_contents())
return result return result
def get_cachedir_bsig(self): def get_cachedir_bsig(self):
@ -3374,6 +3655,8 @@ class File(Base):
because multiple targets built by the same action will all because multiple targets built by the same action will all
have the same build signature, and we have to differentiate have the same build signature, and we have to differentiate
them somehow. them somehow.
Signature should normally be string of hex digits.
""" """
try: try:
return self.cachesig return self.cachesig
@ -3383,12 +3666,15 @@ class File(Base):
# Collect signatures for all children # Collect signatures for all children
children = self.children() children = self.children()
sigs = [n.get_cachedir_csig() for n in children] sigs = [n.get_cachedir_csig() for n in children]
# Append this node's signature... # Append this node's signature...
sigs.append(self.get_contents_sig()) sigs.append(self.get_contents_sig())
# ...and it's path # ...and it's path
sigs.append(self.get_internal_path()) sigs.append(self.get_internal_path())
# Merge this all into a single signature # Merge this all into a single signature
result = self.cachesig = SCons.Util.MD5collect(sigs) result = self.cachesig = MD5collect(sigs)
return result return result
default_fs = None default_fs = None
@ -3399,7 +3685,7 @@ def get_default_fs():
default_fs = FS() default_fs = FS()
return default_fs return default_fs
class FileFinder(object): class FileFinder:
""" """
""" """
@ -3472,7 +3758,7 @@ class FileFinder(object):
if verbose and not callable(verbose): if verbose and not callable(verbose):
if not SCons.Util.is_String(verbose): if not SCons.Util.is_String(verbose):
verbose = "find_file" verbose = "find_file"
_verbose = u' %s: ' % verbose _verbose = ' %s: ' % verbose
verbose = lambda s: sys.stdout.write(_verbose + s) verbose = lambda s: sys.stdout.write(_verbose + s)
filedir, filename = os.path.split(filename) 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 import SCons.Node
_memo_lookup_map = {}
class ValueNodeInfo(SCons.Node.NodeInfoBase): class ValueNodeInfo(SCons.Node.NodeInfoBase):
__slots__ = ('csig',) __slots__ = ('csig',)
current_version_id = 2 current_version_id = 2
@ -38,18 +41,18 @@ class ValueNodeInfo(SCons.Node.NodeInfoBase):
field_list = ['csig'] field_list = ['csig']
def str_to_node(self, s): def str_to_node(self, s):
return Value(s) return ValueWithMemo(s)
def __getstate__(self): def __getstate__(self):
""" """
Return all fields that shall be pickled. Walk the slots in the class 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 hierarchy and add those to the state dictionary. If a '__dict__' slot
available, copy all entries to the dictionary. Also include the version is available, copy all entries to the dictionary. Also include the
id, which is fixed for all instances of a class. version id, which is fixed for all instances of a class.
""" """
state = getattr(self, '__dict__', {}).copy() state = getattr(self, '__dict__', {}).copy()
for obj in type(self).mro(): for obj in type(self).mro():
for name in getattr(obj,'__slots__',()): for name in getattr(obj, '__slots__', ()):
if hasattr(self, name): if hasattr(self, name):
state[name] = getattr(self, name) state[name] = getattr(self, name)
@ -76,6 +79,7 @@ class ValueBuildInfo(SCons.Node.BuildInfoBase):
__slots__ = () __slots__ = ()
current_version_id = 2 current_version_id = 2
class Value(SCons.Node.Node): class Value(SCons.Node.Node):
"""A class for Python variables, typically passed on the command line """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. 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 NodeInfo = ValueNodeInfo
BuildInfo = ValueBuildInfo BuildInfo = ValueBuildInfo
def __init__(self, value, built_value=None): def __init__(self, value, built_value=None, name=None):
SCons.Node.Node.__init__(self) SCons.Node.Node.__init__(self)
self.value = value self.value = value
self.changed_since_last_build = 6 self.changed_since_last_build = 6
@ -92,6 +96,13 @@ class Value(SCons.Node.Node):
if built_value is not None: if built_value is not None:
self.built_value = built_value 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): def str_for_display(self):
return repr(self.value) return repr(self.value)
@ -137,6 +148,10 @@ class Value(SCons.Node.Node):
return contents return contents
def get_contents(self): def get_contents(self):
"""
Get contents for signature calculations.
:return: bytes
"""
text_contents = self.get_text_contents() text_contents = self.get_text_contents()
try: try:
return text_contents.encode() return text_contents.encode()
@ -144,7 +159,6 @@ class Value(SCons.Node.Node):
# Already encoded as python2 str are bytes # Already encoded as python2 str are bytes
return text_contents return text_contents
def changed_since_last_build(self, target, prev_ni): def changed_since_last_build(self, target, prev_ni):
cur_csig = self.get_csig() cur_csig = self.get_csig()
try: try:
@ -155,15 +169,45 @@ class Value(SCons.Node.Node):
def get_csig(self, calc=None): def get_csig(self, calc=None):
"""Because we're a Python value node and don't have a real """Because we're a Python value node and don't have a real
timestamp, we get to ignore the calculator and just use the 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: try:
return self.ninfo.csig return self.ninfo.csig
except AttributeError: except AttributeError:
pass pass
contents = self.get_contents()
contents = self.get_text_contents()
self.get_ninfo().csig = contents self.get_ninfo().csig = contents
return 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: # Local Variables:
# tab-width:4 # tab-width:4
# indent-tabs-mode:nil # 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__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 collections
import copy import copy
from itertools import chain from itertools import chain
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest
import SCons.Debug import SCons.Debug
from SCons.Debug import logInstanceCreation from SCons.Debug import logInstanceCreation
import SCons.Executor import SCons.Executor
import SCons.Memoize import SCons.Memoize
import SCons.Util import SCons.Util
from SCons.Util import MD5signature
from SCons.Debug import Trace from SCons.Debug import Trace
from SCons.compat import with_metaclass, NoSlotsPyPy from SCons.compat import NoSlotsPyPy
print_duplicate = 0 print_duplicate = 0
@ -102,9 +107,12 @@ implicit_deps_changed = 0
# A variable that can be set to an interface-specific function be called # A variable that can be set to an interface-specific function be called
# to annotate a Node with information about its creation. # 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 # Gets set to 'True' if we're running in interactive mode. Is
# currently used to release parts of a target's info during # currently used to release parts of a target's info during
@ -139,6 +147,7 @@ def exists_entry(node):
node.disambiguate() node.disambiguate()
return _exists_map[node._func_exists](node) return _exists_map[node._func_exists](node)
def exists_file(node): def exists_file(node):
# Duplicate from source path if we are set up to do this. # Duplicate from source path if we are set up to do this.
if node.duplicate and not node.is_derived() and not node.linked: 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 # The source file does not exist. Make sure no old
# copy remains in the variant directory. # copy remains in the variant directory.
if print_duplicate: 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(): if exists_base(node) or node.islink():
node.fs.unlink(node.get_internal_path()) node.fs.unlink(node.get_internal_path())
# Return None explicitly because the Base.exists() call # 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 # 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 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 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() cur_csig = node.get_csig()
try: try:
return cur_csig != prev_ni.csig return cur_csig != prev_ni.csig
except AttributeError: except AttributeError:
return 1 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() 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): def changed_since_last_build_state_changed(node, target, prev_ni, repo_node=None):
return target.get_build_env().decide_source(node, target, prev_ni) 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() cur_csig = node.get_csig()
try: try:
return cur_csig != prev_ni.csig return cur_csig != prev_ni.csig
@ -341,7 +356,7 @@ store_info_map = {0 : store_info_pass,
# Classes for signature info for Nodes. # Classes for signature info for Nodes.
class NodeInfoBase(object): class NodeInfoBase:
""" """
The generic base class for signature information for a Node. The generic base class for signature information for a Node.
@ -380,6 +395,7 @@ class NodeInfoBase(object):
""" """
state = other.__getstate__() state = other.__getstate__()
self.__setstate__(state) self.__setstate__(state)
def format(self, field_list=None, names=0): def format(self, field_list=None, names=0):
if field_list is None: if field_list is None:
try: try:
@ -435,7 +451,7 @@ class NodeInfoBase(object):
setattr(self, key, value) setattr(self, key, value)
class BuildInfoBase(object): class BuildInfoBase:
""" """
The generic base class for build information for a Node. The generic base class for build information for a Node.
@ -498,13 +514,14 @@ class BuildInfoBase(object):
setattr(self, key, value) 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 """The base Node class, for entities that we know how to
build, or use to build other Nodes. build, or use to build other Nodes.
""" """
__slots__ = ['sources', __slots__ = ['sources',
'sources_set', 'sources_set',
'target_peers',
'_specific_sources', '_specific_sources',
'depends', 'depends',
'depends_set', 'depends_set',
@ -545,7 +562,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
'_func_get_contents', '_func_get_contents',
'_func_target_from_source'] '_func_target_from_source']
class Attrs(object): class Attrs:
__slots__ = ('shared', '__dict__') __slots__ = ('shared', '__dict__')
@ -599,6 +616,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
self._func_rexists = 1 self._func_rexists = 1
self._func_get_contents = 0 self._func_get_contents = 0
self._func_target_from_source = 0 self._func_target_from_source = 0
self.ninfo = None
self.clear_memoized_values() self.clear_memoized_values()
@ -665,7 +683,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
executor.cleanup() executor.cleanup()
def reset_executor(self): def reset_executor(self):
"Remove cached executor; forces recompute when needed." """Remove cached executor; forces recompute when needed."""
try: try:
delattr(self, 'executor') delattr(self, 'executor')
except AttributeError: except AttributeError:
@ -760,6 +778,25 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
for parent in self.waiting_parents: for parent in self.waiting_parents:
parent.implicit = None 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() self.clear()
if self.pseudo: if self.pseudo:
@ -912,6 +949,10 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
""" """
return _is_derived_map[self._func_is_derived](self) 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): def alter_targets(self):
"""Return a list of alternate targets for this Node. """Return a list of alternate targets for this Node.
""" """
@ -1097,11 +1138,10 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
return ninfo return ninfo
def get_ninfo(self): def get_ninfo(self):
try: if self.ninfo is not None:
return self.ninfo
except AttributeError:
self.ninfo = self.new_ninfo()
return self.ninfo return self.ninfo
self.ninfo = self.new_ninfo()
return self.ninfo
def new_binfo(self): def new_binfo(self):
binfo = self.BuildInfo() binfo = self.BuildInfo()
@ -1133,10 +1173,10 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
if self.has_builder(): if self.has_builder():
binfo.bact = str(executor) binfo.bact = str(executor)
binfo.bactsig = SCons.Util.MD5signature(executor.get_contents()) binfo.bactsig = MD5signature(executor.get_contents())
if self._specific_sources: 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: else:
sources = executor.get_unignored_sources(self, self.ignore) 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.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.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 # Because self.implicit is initialized to None (and not empty list [])
binfo.bdependsigs = [d.get_ninfo() for d in self.depends if d not in ignore_set] # we have to handle this case
if not self.implicit:
binfo.bimplicit = self.implicit or [] binfo.bimplicit = []
binfo.bimplicitsigs = [i.get_ninfo() for i in binfo.bimplicit if i not in ignore_set] 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 return binfo
@ -1167,7 +1211,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
return self.ninfo.csig return self.ninfo.csig
except AttributeError: except AttributeError:
ninfo = self.get_ninfo() ninfo = self.get_ninfo()
ninfo.csig = SCons.Util.MD5signature(self.get_contents()) ninfo.csig = MD5signature(self.get_contents())
return self.ninfo.csig return self.ninfo.csig
def get_cachedir_csig(self): def get_cachedir_csig(self):
@ -1213,7 +1257,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
return _exists_map[self._func_exists](self) return _exists_map[self._func_exists](self)
def rexists(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: # There are no repositories by default:
return _rexists_map[self._func_rexists](self) return _rexists_map[self._func_rexists](self)
@ -1452,14 +1496,13 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
result = True result = True
for child, prev_ni in zip(children, then): 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) if t: Trace(': %s changed' % child)
result = True result = True
contents = self.get_executor().get_contents()
if self.has_builder(): if self.has_builder():
import SCons.Util contents = self.get_executor().get_contents()
newsig = SCons.Util.MD5signature(contents) newsig = MD5signature(contents)
if bi.bactsig != newsig: if bi.bactsig != newsig:
if t: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig)) if t: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig))
result = True result = True
@ -1607,29 +1650,39 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
# so we only print them after running them through this lambda # so we only print them after running them through this lambda
# to turn them into the right relative Node and then return # to turn them into the right relative Node and then return
# its string. # its string.
def stringify( s, E=self.dir.Entry ) : def stringify( s, E=self.dir.Entry):
if hasattr( s, 'dir' ) : if hasattr( s, 'dir' ) :
return str(E(s)) return str(E(s))
return str(s) return str(s)
lines = [] 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: if removed:
removed = list(map(stringify, removed)) removed = [stringify(r) for r in removed]
fmt = "`%s' is no longer a dependency\n" fmt = "`%s' is no longer a dependency\n"
lines.extend([fmt % s for s in removed]) lines.extend([fmt % s for s in removed])
for k in new_bkids: 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)) lines.append("`%s' is a new dependency\n" % stringify(k))
elif _decider_map[k.changed_since_last_build](k, self, osig[k]): else:
lines.append("`%s' changed\n" % stringify(k)) 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: if len(lines) == 0 and old_bkids != new_bkids:
lines.append("the dependency order changed:\n" + lines.append("the dependency order changed:\n")
"%sold: %s\n" % (' '*15, list(map(stringify, old_bkids))) + lines.append("->Sources\n")
"%snew: %s\n" % (' '*15, list(map(stringify, new_bkids)))) 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: if len(lines) == 0:
def fmt_with_title(title, strlines): 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 ignore_cycle(node, stack): pass
def do_nothing(node, parent): pass def do_nothing(node, parent): pass
class Walker(object): class Walker:
"""An iterator for walking a Node tree. """An iterator for walking a Node tree.
This is depth-first, children are visited before the parent. This is depth-first, children are visited before the parent.
The Walker object can be initialized with any node, and The Walker object can be initialized with any node, and
returns the next node on the descent with each get_next() call. 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'. get the children of a node instead of calling 'children'.
'cycle_func' is an optional function that will be called 'cycle_func' is an optional function that will be called
when a cycle is detected. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 __doc__ = """SCons.PathList
@ -66,7 +66,7 @@ def node_conv(obj):
result = get() result = get()
return result return result
class _PathList(object): class _PathList:
""" """
An actual PathList object. An actual PathList object.
""" """
@ -143,7 +143,7 @@ class _PathList(object):
return tuple(result) return tuple(result)
class PathListCache(object): class PathListCache:
""" """
A class to handle caching of PathList lookups. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# #
from __future__ import print_function __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__revision__ = "src/engine/SCons/Platform/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import SCons.compat import SCons.compat
import imp import importlib
import os import os
import sys import sys
import tempfile import tempfile
@ -60,8 +58,8 @@ import SCons.Tool
def platform_default(): def platform_default():
"""Return the platform string for our execution environment. """Return the platform string for our execution environment.
The returned value should map to one of the SCons/Platform/*.py The returned value should map to one of the SCons/Platform/\*.py
files. Since we're architecture independent, though, we don't files. Since scons is architecture independent, though, we don't
care about the machine architecture. care about the machine architecture.
""" """
osname = os.name osname = os.name
@ -87,6 +85,7 @@ def platform_default():
else: else:
return sys.platform return sys.platform
def platform_module(name = platform_default()): def platform_module(name = platform_default()):
"""Return the imported module for the platform. """Return the imported module for the platform.
@ -100,13 +99,8 @@ def platform_module(name = platform_default()):
eval(full_name) eval(full_name)
else: else:
try: try:
file, path, desc = imp.find_module(name, # the specific platform module is a relative import
sys.modules['SCons.Platform'].__path__) mod = importlib.import_module("." + name, __name__)
try:
mod = imp.load_module(full_name, file, path, desc)
finally:
if file:
file.close()
except ImportError: except ImportError:
try: try:
import zipimport import zipimport
@ -117,12 +111,14 @@ def platform_module(name = platform_default()):
setattr(SCons.Platform, name, mod) setattr(SCons.Platform, name, mod)
return sys.modules[full_name] return sys.modules[full_name]
def DefaultToolList(platform, env): def DefaultToolList(platform, env):
"""Select a default tool list for the specified platform. """Select a default tool list for the specified platform.
""" """
return SCons.Tool.tool_list(platform, env) return SCons.Tool.tool_list(platform, env)
class PlatformSpec(object):
class PlatformSpec:
def __init__(self, name, generate): def __init__(self, name, generate):
self.name = name self.name = name
self.generate = generate self.generate = generate
@ -133,22 +129,35 @@ class PlatformSpec(object):
def __str__(self): def __str__(self):
return self.name 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:
env["TEMPFILE"] = TempFileMunge """Convert long command lines to use a temporary file.
env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES','$LINKCOMSTR')}"
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 By default, the name of the temporary file used begins with a
prefix of '@'. This may be configred for other tool chains by prefix of '@'. This may be configured for other tool chains by
setting '$TEMPFILEPREFIX'. 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.
env["TEMPFILEPREFIX"] = '-@' # diab compiler
env["TEMPFILEPREFIX"] = '-via' # arm tool chain
""" """
def __init__(self, cmd, cmdstr = None): def __init__(self, cmd, cmdstr = None):
self.cmd = cmd self.cmd = cmd
@ -185,21 +194,27 @@ class TempFileMunge(object):
node = target[0] if SCons.Util.is_List(target) else target node = target[0] if SCons.Util.is_List(target) else target
cmdlist = getattr(node.attributes, 'tempfile_cmdlist', None) \ cmdlist = getattr(node.attributes, 'tempfile_cmdlist', None) \
if node is not None else None if node is not None else None
if cmdlist is not None : if cmdlist is not None:
return cmdlist return cmdlist
# We do a normpath because mktemp() has what appears to be # Default to the .lnk suffix for the benefit of the Phar Lap
# 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
# linkloc linker, which likes to append an .lnk suffix if # linkloc linker, which likes to append an .lnk suffix if
# none is given. # none is given.
(fd, tmp) = tempfile.mkstemp('.lnk', text=True) if 'TEMPFILESUFFIX' in env:
native_tmp = SCons.Util.get_native_path(os.path.normpath(tmp)) 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 # The sh shell will try to escape the backslashes in the
# path, so unescape them. # path, so unescape them.
native_tmp = native_tmp.replace('\\', r'\\\\') native_tmp = native_tmp.replace('\\', r'\\\\')
@ -217,8 +232,10 @@ class TempFileMunge(object):
prefix = '@' prefix = '@'
args = list(map(SCons.Subst.quote_spaces, cmd[1:])) 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) os.close(fd)
# XXX Using the SCons.Action.print_actions value directly # XXX Using the SCons.Action.print_actions value directly
# like this is bogus, but expedient. This class should # like this is bogus, but expedient. This class should
# really be rewritten as an Action that defines the # really be rewritten as an Action that defines the
@ -239,8 +256,9 @@ class TempFileMunge(object):
source) if self.cmdstr is not None else '' source) if self.cmdstr is not None else ''
# Print our message only if XXXCOMSTR returns an empty string # Print our message only if XXXCOMSTR returns an empty string
if len(cmdstr) == 0 : 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)) str(cmd[0]) + " " + " ".join(args))
self._print_cmd_str(target, source, env, cmdstr)
# Store the temporary file command list into the target Node.attributes # Store the temporary file command list into the target Node.attributes
# to avoid creating two temporary files one for print and one for execute. # to avoid creating two temporary files one for print and one for execute.
@ -252,6 +270,23 @@ class TempFileMunge(object):
pass pass
return cmdlist return cmdlist
def _print_cmd_str(self, target, source, env, cmdstr):
# check if the user has specified a cmd line print function
print_func = None
try:
get = env.get
except AttributeError:
pass
else:
print_func = get('PRINT_CMD_LINE_FUNC')
# use the default action cmd line print if user did not supply one
if not print_func:
action = SCons.Action._ActionAction()
action.print_cmd_line(cmdstr, target, source, env)
else:
print_func(cmdstr, target, source, env)
def Platform(name = platform_default()): def Platform(name = platform_default()):
"""Select a canned Platform specification. """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. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 os
import subprocess import subprocess
@ -55,6 +55,7 @@ def get_xlc(env, xlc=None, packages=[]):
pipe = SCons.Action._subproc(env, ['lslpp', '-fc', package], pipe = SCons.Action._subproc(env, ['lslpp', '-fc', package],
stdin = 'devnull', stdin = 'devnull',
stderr = 'devnull', stderr = 'devnull',
universal_newlines=True,
stdout = subprocess.PIPE) stdout = subprocess.PIPE)
# output of lslpp is something like this: # output of lslpp is something like this:
# #Path:Fileset:File # #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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 . import posix
from SCons.Platform import TempFileMunge 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): def generate(env):
posix.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. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 from . import posix
import os import os
@ -56,12 +56,11 @@ def generate(env):
for file in filelist: for file in filelist:
if os.path.isfile(file): if os.path.isfile(file):
f = open(file, 'r') with open(file, 'r') as f:
lines = f.readlines() lines = f.readlines()
for line in lines: for line in lines:
if line: if line:
env.AppendENVPath('PATHOSX', line.strip('\n')) env.AppendENVPath('PATHOSX', line.strip('\n'))
f.close()
# Not sure why this wasn't the case all along? # 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): 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. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 from . import win32
def generate(env): 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 errno
import os import os
@ -41,6 +41,8 @@ import select
import SCons.Util import SCons.Util
from SCons.Platform import TempFileMunge from SCons.Platform import TempFileMunge
from SCons.Platform.virtualenv import ImportVirtualenv
from SCons.Platform.virtualenv import ignore_virtualenv, enable_virtualenv
exitvalmap = { exitvalmap = {
2 : 127, 2 : 127,
@ -48,7 +50,7 @@ exitvalmap = {
} }
def escape(arg): def escape(arg):
"escape shell special characters" """escape shell special characters"""
slash = '\\' slash = '\\'
special = '"$' special = '"$'
@ -119,6 +121,9 @@ def generate(env):
# Must be able to have GCC and DMD work in the same build, so: # Must be able to have GCC and DMD work in the same build, so:
env['__DRPATH'] = '$_DRPATH' env['__DRPATH'] = '$_DRPATH'
if enable_virtualenv and not ignore_virtualenv:
ImportVirtualenv(env)
# Local Variables: # Local Variables:
# tab-width:4 # tab-width:4
# indent-tabs-mode:nil # indent-tabs-mode:nil

View file

@ -1,4 +1,4 @@
"""engine.SCons.Platform.sunos """SCons.Platform.sunos
Platform-specific initialization for Sun systems. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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
import os.path import os.path
@ -39,16 +39,18 @@ import tempfile
from SCons.Platform.posix import exitvalmap from SCons.Platform.posix import exitvalmap
from SCons.Platform import TempFileMunge from SCons.Platform import TempFileMunge
from SCons.Platform.virtualenv import ImportVirtualenv
from SCons.Platform.virtualenv import ignore_virtualenv, enable_virtualenv
import SCons.Util import SCons.Util
CHOCO_DEFAULT_PATH = [
r'C:\ProgramData\chocolatey\bin'
]
try: try:
import msvcrt import msvcrt
import win32api import win32api
import win32con import win32con
msvcrt.get_osfhandle
win32api.SetHandleInformation
win32con.HANDLE_FLAG_INHERIT
except ImportError: except ImportError:
parallel_msg = \ parallel_msg = \
"you do not seem to have the pywin32 extensions installed;\n" + \ "you do not seem to have the pywin32 extensions installed;\n" + \
@ -60,38 +62,6 @@ except AttributeError:
else: else:
parallel_msg = None 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: if False:
# Now swap out shutil.filecopy and filecopy2 for win32 api native CopyFile # 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. # Without this, python can randomly crash while using -jN.
# See the python bug at http://bugs.python.org/issue6476 # See the python bug at http://bugs.python.org/issue6476
# and SCons issue at # 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): def spawnve(mode, file, args, env):
spawn_lock.acquire() spawn_lock.acquire()
try: try:
@ -168,59 +138,68 @@ def piped_spawn(sh, escape, cmd, args, env, stdout, stderr):
# we redirect it into a temporary file tmpFileStdout # we redirect it into a temporary file tmpFileStdout
# (tmpFileStderr) and copy the contents of this file # (tmpFileStderr) and copy the contents of this file
# to stdout (stderr) given in the argument # 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: if not sh:
sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n") sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
return 127 return 127
else:
# one temporary file for stdout and stderr
tmpFileStdout = os.path.normpath(tempfile.mktemp())
tmpFileStderr = os.path.normpath(tempfile.mktemp())
# check if output is redirected # one temporary file for stdout and stderr
stdoutRedirected = 0 tmpFileStdout, tmpFileStdoutName = tempfile.mkstemp(text=True)
stderrRedirected = 0 os.close(tmpFileStdout) # don't need open until the subproc is done
for arg in args: tmpFileStderr, tmpFileStderrName = tempfile.mkstemp(text=True)
# are there more possibilities to redirect stdout ? os.close(tmpFileStderr)
if arg.find( ">", 0, 1 ) != -1 or arg.find( "1>", 0, 2 ) != -1:
stdoutRedirected = 1
# are there more possibilities to redirect stderr ?
if arg.find( "2>", 0, 2 ) != -1:
stderrRedirected = 1
# redirect output of non-redirected streams to our tempfiles # check if output is redirected
if stdoutRedirected == 0: stdoutRedirected = False
args.append(">" + str(tmpFileStdout)) stderrRedirected = False
if stderrRedirected == 0: for arg in args:
args.append("2>" + str(tmpFileStderr)) # are there more possibilities to redirect stdout ?
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 = True
# actually do the spawn # redirect output of non-redirected streams to our tempfiles
if not stdoutRedirected:
args.append(">" + tmpFileStdoutName)
if not stderrRedirected:
args.append("2>" + tmpFileStderrName)
# actually do the spawn
try:
args = [sh, '/C', escape(' '.join(args))]
ret = spawnve(os.P_WAIT, sh, args, env)
except OSError as e:
# catch any error
try: try:
args = [sh, '/C', escape(' '.join(args)) ] ret = exitvalmap[e.errno]
ret = spawnve(os.P_WAIT, sh, args, env) except KeyError:
except OSError as e: sys.stderr.write("scons: unknown OSError exception code %d - %s: %s\n" % (e.errno, cmd, e.strerror))
# catch any error if stderr is not None:
try: stderr.write("scons: %s: %s\n" % (cmd, e.strerror))
ret = exitvalmap[e[0]]
except KeyError:
sys.stderr.write("scons: unknown OSError exception code %d - %s: %s\n" % (e[0], cmd, e[1]))
if stderr is not None:
stderr.write("scons: %s: %s\n" % (cmd, e[1]))
# copy child output from tempfiles to our streams
# and do clean up stuff
if stdout is not None and stdoutRedirected == 0:
try:
stdout.write(open( tmpFileStdout, "r" ).read())
os.remove( tmpFileStdout )
except (IOError, OSError):
pass
if stderr is not None and stderrRedirected == 0: # copy child output from tempfiles to our streams
try: # and do clean up stuff
stderr.write(open( tmpFileStderr, "r" ).read()) if stdout is not None and not stdoutRedirected:
os.remove( tmpFileStderr ) try:
except (IOError, OSError): with open(tmpFileStdoutName, "r") as tmpFileStdout:
pass stdout.write(tmpFileStdout.read())
return ret os.remove(tmpFileStdoutName)
except (IOError, OSError):
pass
if stderr is not None and not stderrRedirected:
try:
with open(tmpFileStderrName, "r") as tmpFileStderr:
stderr.write(tmpFileStderr.read())
os.remove(tmpFileStderrName)
except (IOError, OSError):
pass
return ret
def exec_spawn(l, env): def exec_spawn(l, env):
@ -288,9 +267,6 @@ def get_system_root():
except: except:
pass 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 _system_root = val
return val return val
@ -322,7 +298,7 @@ def get_program_files_dir():
return val return val
class ArchDefinition(object): class ArchDefinition:
""" """
Determine which windows CPU were running on. Determine which windows CPU were running on.
A class for defining architecture-specific settings and logic. A class for defining architecture-specific settings and logic.
@ -436,7 +412,7 @@ def generate(env):
if v: if v:
env['ENV']['COMSPEC'] = 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['ENV']['PATHEXT'] = '.COM;.EXE;.BAT;.CMD'
env['OBJPREFIX'] = '' env['OBJPREFIX'] = ''
@ -462,6 +438,9 @@ def generate(env):
env['HOST_OS'] = 'win32' env['HOST_OS'] = 'win32'
env['HOST_ARCH'] = get_architecture().arch env['HOST_ARCH'] = get_architecture().arch
if enable_virtualenv and not ignore_virtualenv:
ImportVirtualenv(env)
# Local Variables: # Local Variables:
# tab-width:4 # 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# #
from __future__ import print_function __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__revision__ = "src/engine/SCons/SConf.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import SCons.compat import SCons.compat
import atexit
import io import io
import os import os
import re import re
@ -56,6 +55,7 @@ import SCons.Warnings
import SCons.Conftest import SCons.Conftest
from SCons.Debug import Trace from SCons.Debug import Trace
from collections import defaultdict
# Turn off the Conftest error logging # Turn off the Conftest error logging
SCons.Conftest.LogInputFiles = 0 SCons.Conftest.LogInputFiles = 0
@ -65,9 +65,9 @@ SCons.Conftest.LogErrorMessages = 0
build_type = None build_type = None
build_types = ['clean', 'help'] build_types = ['clean', 'help']
def SetBuildType(type): def SetBuildType(buildtype):
global build_type global build_type
build_type = type build_type = buildtype
# to be set, if we are in dry-run mode # to be set, if we are in dry-run mode
dryrun = 0 dryrun = 0
@ -98,7 +98,7 @@ def SetProgressDisplay(display):
SConfFS = None 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_logs = {} # all config.log files created in this build
_ac_config_hs = {} # all config.h files created in this build _ac_config_hs = {} # all config.h files created in this build
sconf_global = None # current sconf object sconf_global = None # current sconf object
@ -132,8 +132,8 @@ def CreateConfigHBuilder(env):
_stringConfigH) _stringConfigH)
sconfigHBld = SCons.Builder.Builder(action=action) sconfigHBld = SCons.Builder.Builder(action=action)
env.Append( BUILDERS={'SConfigHBuilder':sconfigHBld} ) env.Append( BUILDERS={'SConfigHBuilder':sconfigHBld} )
for k in list(_ac_config_hs.keys()): for k, v in _ac_config_hs.items():
env.SConfigHBuilder(k, env.Value(_ac_config_hs[k])) env.SConfigHBuilder(k, env.Value(v))
class SConfWarning(SCons.Warnings.Warning): class SConfWarning(SCons.Warnings.Warning):
@ -161,11 +161,14 @@ class ConfigureCacheError(SConfError):
def __init__(self,target): def __init__(self,target):
SConfError.__init__(self, '"%s" is not yet built and cache is forced.' % str(target)) SConfError.__init__(self, '"%s" is not yet built and cache is forced.' % str(target))
# define actions for building text files # define actions for building text files
def _createSource( target, source, env ): def _createSource(target, source, env):
fd = open(str(target[0]), "w") fd = open(str(target[0]), "w")
fd.write(source[0].get_contents().decode()) fd.write(source[0].get_contents().decode())
fd.close() fd.close()
def _stringSource( target, source, env ): def _stringSource( target, source, env ):
return (str(target[0]) + ' <-\n |' + return (str(target[0]) + ' <-\n |' +
source[0].get_contents().decode().replace( '\n', "\n |" ) ) source[0].get_contents().decode().replace( '\n', "\n |" ) )
@ -187,7 +190,7 @@ class SConfBuildInfo(SCons.Node.FS.FileBuildInfo):
self.string = string self.string = string
class Streamer(object): class Streamer:
""" """
'Sniffer' for a file-like writable object. Similar to the unix tool tee. '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 # ConfigureCacheError and if yes, reraise the exception
exc_type = self.exc_info()[0] exc_type = self.exc_info()[0]
if issubclass(exc_type, SConfError): if issubclass(exc_type, SConfError):
# TODO pylint E0704: bare raise not inside except
raise raise
elif issubclass(exc_type, SCons.Errors.BuildError): elif issubclass(exc_type, SCons.Errors.BuildError):
# we ignore Build Errors (occurs, when a test doesn't pass) # we ignore Build Errors (occurs, when a test doesn't pass)
@ -269,7 +273,7 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
cached_error = False cached_error = False
cachable = True cachable = True
for t in self.targets: for t in self.targets:
if T: Trace('%s' % (t)) if T: Trace('%s' % t)
bi = t.get_stored_info().binfo bi = t.get_stored_info().binfo
if isinstance(bi, SConfBuildInfo): if isinstance(bi, SConfBuildInfo):
if T: Trace(': SConfBuildInfo') if T: Trace(': SConfBuildInfo')
@ -279,7 +283,7 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
else: else:
if T: Trace(': get_state() %s' % t.get_state()) if T: Trace(': get_state() %s' % t.get_state())
if T: Trace(': changed() %s' % t.changed()) 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 changed = True
if T: Trace(': changed %s' % changed) if T: Trace(': changed %s' % changed)
cached_error = cached_error or bi.result cached_error = cached_error or bi.result
@ -323,18 +327,6 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
s = sys.stdout = sys.stderr = Streamer(sys.stdout) s = sys.stdout = sys.stderr = Streamer(sys.stdout)
try: try:
env = self.targets[0].get_build_env() 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 env['PSTDOUT'] = env['PSTDERR'] = s
try: try:
sconf.cached = 0 sconf.cached = 0
@ -383,7 +375,7 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
sconsign.set_entry(t.name, sconsign_entry) sconsign.set_entry(t.name, sconsign_entry)
sconsign.merge() sconsign.merge()
class SConfBase(object): class SConfBase:
"""This is simply a class to represent a configure context. After """This is simply a class to represent a configure context. After
creating a SConf object, you can call any tests. After finished with your 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 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) build tests in the VariantDir, not in the SourceDir)
""" """
global SConfFS 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: if not SConfFS:
SConfFS = SCons.Node.FS.default_fs or \ SConfFS = SCons.Node.FS.default_fs or \
SCons.Node.FS.FS(env.fs.pathTop) SCons.Node.FS.FS(env.fs.pathTop)
if sconf_global is not None: if sconf_global is not None:
raise SCons.Errors.UserError raise SCons.Errors.UserError("""Configure() called while another Configure() exists.
self.env = env Please call .Finish() before creating and second Configure() context""")
if log_file is not None: if log_file is not None:
log_file = SConfFS.File(env.subst(log_file)) log_file = SConfFS.File(env.subst(log_file))
self.logfile = log_file self.logfile = log_file
@ -449,6 +470,7 @@ class SConfBase(object):
env = sconf.Finish() env = sconf.Finish()
""" """
self._shutdown() self._shutdown()
return self.env return self.env
def Define(self, name, value = None, comment = None): def Define(self, name, value = None, comment = None):
@ -503,6 +525,20 @@ class SConfBase(object):
n.attributes = SCons.Node.Node.Attrs() n.attributes = SCons.Node.Node.Attrs()
n.attributes.keep_targetinfo = 1 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 ret = 1
try: try:
@ -541,8 +577,7 @@ class SConfBase(object):
""" """
return self.pspawn(sh, escape, cmd, args, env, self.logstream, self.logstream) 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 """Low level TryBuild implementation. Normally you don't need to
call that - you can use TryCompile / TryLink / TryRun instead call that - you can use TryCompile / TryLink / TryRun instead
""" """
@ -560,8 +595,30 @@ class SConfBase(object):
raise SCons.Errors.UserError('Missing SPAWN construction variable.') raise SCons.Errors.UserError('Missing SPAWN construction variable.')
nodesToBeBuilt = [] 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 ) pref = self.env.subst( builder.builder.prefix )
suff = self.env.subst( builder.builder.suffix ) suff = self.env.subst( builder.builder.suffix )
target = self.confdir.File(pref + f + suff) target = self.confdir.File(pref + f + suff)
@ -570,16 +627,6 @@ class SConfBase(object):
# Slide our wrapper into the construction environment as # Slide our wrapper into the construction environment as
# the SPAWN function. # the SPAWN function.
self.env['SPAWN'] = self.pspawn_wrapper 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) nodes = builder(target = target, source = source)
if not SCons.Util.is_List(nodes): if not SCons.Util.is_List(nodes):
@ -590,7 +637,6 @@ class SConfBase(object):
finally: finally:
self.env['SPAWN'] = save_spawn self.env['SPAWN'] = save_spawn
_ac_build_counter = _ac_build_counter + 1
if result: if result:
self.lastTarget = nodes[0] self.lastTarget = nodes[0]
else: else:
@ -609,7 +655,7 @@ class SConfBase(object):
ok = self.TryBuild(self.env.SConfActionBuilder, text, extension) ok = self.TryBuild(self.env.SConfActionBuilder, text, extension)
del self.env['BUILDERS']['SConfActionBuilder'] del self.env['BUILDERS']['SConfActionBuilder']
if ok: if ok:
outputStr = self.lastTarget.get_contents().decode() outputStr = self.lastTarget.get_text_contents()
return (1, outputStr) return (1, outputStr)
return (0, "") return (0, "")
@ -636,7 +682,7 @@ class SConfBase(object):
is saved in self.lastTarget (for further processing). is saved in self.lastTarget (for further processing).
""" """
ok = self.TryLink(text, extension) ok = self.TryLink(text, extension)
if( ok ): if ok:
prog = self.lastTarget prog = self.lastTarget
pname = prog.get_internal_path() pname = prog.get_internal_path()
output = self.confdir.File(os.path.basename(pname)+'.out') output = self.confdir.File(os.path.basename(pname)+'.out')
@ -647,7 +693,7 @@ class SConfBase(object):
return( 1, outputStr) return( 1, outputStr)
return (0, "") return (0, "")
class TestWrapper(object): class TestWrapper:
"""A wrapper around Tests (to ensure sanity)""" """A wrapper around Tests (to ensure sanity)"""
def __init__(self, test, sconf): def __init__(self, test, sconf):
self.test = test self.test = test
@ -671,7 +717,7 @@ class SConfBase(object):
"""Adds all the tests given in the tests dictionary to this SConf """Adds all the tests given in the tests dictionary to this SConf
instance instance
""" """
for name in list(tests.keys()): for name in tests.keys():
self.AddTest(name, tests[name]) self.AddTest(name, tests[name])
def _createDir( self, node ): def _createDir( self, node ):
@ -705,11 +751,16 @@ class SConfBase(object):
_ac_config_logs[self.logfile] = None _ac_config_logs[self.logfile] = None
log_mode = "w" log_mode = "w"
fp = open(str(self.logfile), log_mode) fp = open(str(self.logfile), log_mode)
def conflog_cleanup(logf):
logf.close()
atexit.register(conflog_cleanup, fp)
self.logstream = SCons.Util.Unbuffered(fp) self.logstream = SCons.Util.Unbuffered(fp)
# logfile may stay in a build directory, so we tell # 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 # 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] tb = traceback.extract_stack()[-3-self.depth]
old_fs_dir = SConfFS.getcwd() old_fs_dir = SConfFS.getcwd()
@ -739,17 +790,25 @@ class SConfBase(object):
self.logstream.write("\n") self.logstream.write("\n")
self.logstream.close() self.logstream.close()
self.logstream = None self.logstream = None
# remove the SConfSourceBuilder from the environment
blds = self.env['BUILDERS'] # Now reset the decider if we changed it due to --config=force
del blds['SConfSourceBuilder'] # We saved original Environment passed in and cloned it to isolate
self.env.Replace( BUILDERS=blds ) # 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 self.active = 0
sconf_global = None 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 _ac_config_hs[self.config_h] = self.config_h_text
self.env.fs = self.lastEnvFs self.env.fs = self.lastEnvFs
class CheckContext(object): class CheckContext:
"""Provides a context for configure tests. Defines how a test writes to the """Provides a context for configure tests. Defines how a test writes to the
screen and log file. screen and log file.
@ -826,9 +885,9 @@ class CheckContext(object):
return self.sconf.TryRun(*args, **kw) return self.sconf.TryRun(*args, **kw)
def __getattr__( self, attr ): def __getattr__( self, attr ):
if( attr == 'env' ): if attr == 'env':
return self.sconf.env return self.sconf.env
elif( attr == 'lastTarget' ): elif attr == 'lastTarget':
return self.sconf.lastTarget return self.sconf.lastTarget
else: else:
raise AttributeError("CheckContext instance has no attribute '%s'" % attr) raise AttributeError("CheckContext instance has no attribute '%s'" % attr)
@ -1000,7 +1059,7 @@ def CheckLib(context, library = None, symbol = "main",
compiles without flags. compiles without flags.
""" """
if library == []: if not library:
library = [None] library = [None]
if not SCons.Util.is_List(library): if not SCons.Util.is_List(library):
@ -1027,7 +1086,7 @@ def CheckLibWithHeader(context, libs, header, language,
""" """
prog_prefix, dummy = \ prog_prefix, dummy = \
createIncludesFromHeaders(header, 0) createIncludesFromHeaders(header, 0)
if libs == []: if not libs:
libs = [None] libs = [None]
if not SCons.Util.is_List(libs): 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# #
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
from __future__ import print_function
__revision__ = "src/engine/SCons/SConsign.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import SCons.compat import SCons.compat
@ -76,7 +73,8 @@ def Get_DataBase(dir):
except KeyError: except KeyError:
path = d.entry_abspath(DB_Name) path = d.entry_abspath(DB_Name)
try: db = DataBase[d] = DB_Module.open(path, mode) try: db = DataBase[d] = DB_Module.open(path, mode)
except (IOError, OSError): pass except (IOError, OSError):
pass
else: else:
if mode != "r": if mode != "r":
DB_sync_list.append(db) DB_sync_list.append(db)
@ -122,7 +120,7 @@ def write():
closemethod() closemethod()
class SConsignEntry(object): class SConsignEntry:
""" """
Wrapper class for the generic entry in a .sconsign file. Wrapper class for the generic entry in a .sconsign file.
The Node subclass populates it with attributes as it pleases. The Node subclass populates it with attributes as it pleases.
@ -165,7 +163,7 @@ class SConsignEntry(object):
setattr(self, key, value) setattr(self, key, value)
class Base(object): class Base:
""" """
This is the controlling class for the signatures for the collection of This is the controlling class for the signatures for the collection of
entries associated with a specific directory. The actual directory entries associated with a specific directory. The actual directory
@ -334,10 +332,15 @@ class DirFile(Dir):
Dir.__init__(self, fp, dir) Dir.__init__(self, fp, dir)
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except: except Exception:
SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
"Ignoring corrupt .sconsign file: %s"%self.sconsign) "Ignoring corrupt .sconsign file: %s"%self.sconsign)
try:
fp.close()
except AttributeError:
pass
global sig_files global sig_files
sig_files.append(self) sig_files.append(self)
@ -394,7 +397,8 @@ class DirFile(Dir):
# here, or in any of the following calls, would get # here, or in any of the following calls, would get
# raised, indicating something like a potentially # raised, indicating something like a potentially
# serious disk or network issue. # 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) os.chmod(self.sconsign, mode)
try: try:
os.unlink(temp) os.unlink(temp)
@ -416,7 +420,7 @@ def File(name, dbm_module=None):
else: else:
ForDirectory = DB ForDirectory = DB
DB_Name = name DB_Name = name
if not dbm_module is None: if dbm_module is not None:
DB_Module = dbm_module DB_Module = dbm_module
# Local Variables: # Local Variables:

View file

@ -1,11 +1,11 @@
"""SCons.Scanner.C """SCons.Scanner.C
This module implements the dependency scanner for C/C++ code. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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.Node.FS
import SCons.Scanner import SCons.Scanner
@ -80,7 +80,7 @@ def dictify_CPPDEFINES(env):
return {cppdefines : None} return {cppdefines : None}
return cppdefines return cppdefines
class SConsCPPScannerWrapper(object): class SConsCPPScannerWrapper:
""" """
The SCons wrapper around a cpp.py scanner. The SCons wrapper around a cpp.py scanner.
@ -124,6 +124,100 @@ def CScanner():
'^[ \t]*#[ \t]*(?:include|import)[ \t]*(<|")([^>"]+)(>|")') '^[ \t]*#[ \t]*(?:include|import)[ \t]*(<|")([^>"]+)(>|")')
return cs 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: # Local Variables:
# tab-width:4 # tab-width:4
# indent-tabs-mode:nil # 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 import SCons.Scanner
@ -46,7 +46,7 @@ class D(SCons.Scanner.Classic):
name = "DScanner", name = "DScanner",
suffixes = '$DSUFFIXES', suffixes = '$DSUFFIXES',
path_variable = 'DPATH', 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): 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__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.Node.FS
import SCons.Scanner 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__revision__ = "src/engine/SCons/Scanner/Fortran.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import re import re
@ -78,7 +78,7 @@ class F90Scanner(SCons.Scanner.Classic):
def scan(self, node, env, path=()): def scan(self, node, env, path=()):
# cache the includes list in node so we only scan it once: # 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 mods_and_includes = node.includes
else: else:
# retrieve all included filenames # retrieve all included filenames
@ -187,7 +187,7 @@ def FortranScan(path_variable="FORTRANPATH"):
# (\w+) : match the module name that is being USE'd # (\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: # The INCLUDE statement regex matches the following:
@ -275,7 +275,7 @@ def FortranScan(path_variable="FORTRANPATH"):
# set of semicolon-separated INCLUDE statements # set of semicolon-separated INCLUDE statements
# (as allowed by the F2003 standard) # (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 MODULE statement regex finds module definitions by matching
# the following: # the following:
@ -285,21 +285,29 @@ def FortranScan(path_variable="FORTRANPATH"):
# but *not* the following: # but *not* the following:
# #
# MODULE PROCEDURE procedure_name # 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: # Here is a breakdown of the regex:
# #
# (?i) : regex is case insensitive # (?i) : regex is case insensitive
# ^\s* : any amount of white space # ^\s* : any amount of white space
# MODULE : match the string MODULE, case insensitive # MODULE : match the string MODULE, case
# \s+ : match one or more white space characters # insensitive
# (?!PROCEDURE) : but *don't* match if the next word matches # \s+ : match one or more white space
# PROCEDURE (negative lookahead assertion), # characters
# case insensitive # (?!PROCEDURE|SUBROUTINE|FUNCTION|PURE|ELEMENTAL)
# (\w+) : match one or more alphanumeric characters # : but *don't* match if the next word
# that make up the defined module name and # matches PROCEDURE, SUBROUTINE,
# save it in a group # 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", scanner = F90Scanner("FortranScan",
"$FORTRANSUFFIXES", "$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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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.Node.FS
import SCons.Scanner 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 os.path
import re 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. # Used as a return value of modify_env_var if the variable is not set.
class _Null(object): class _Null:
pass pass
_null = _Null _null = _Null
@ -77,7 +77,7 @@ def modify_env_var(env, var, abspath):
return save return save
class FindENVPathDirs(object): class FindENVPathDirs:
""" """
A class to bind a specific E{*}PATH variable name to a function that A class to bind a specific E{*}PATH variable name to a function that
will return all of the E{*}path directories. will return all of the E{*}path directories.
@ -128,8 +128,8 @@ class LaTeX(SCons.Scanner.Base):
Unlike most scanners, which use regular expressions that just Unlike most scanners, which use regular expressions that just
return the included file name, this returns a tuple consisting return the included file name, this returns a tuple consisting
of the keyword for the inclusion ("include", "includegraphics", of the keyword for the inclusion ("include", "includegraphics",
"input", or "bibliography"), and then the file name itself. "input", or "bibliography"), and then the file name itself.
Based on a quick look at LaTeX documentation, it seems that we Based on a quick look at LaTeX documentation, it seems that we
should append .tex suffix for the "include" keywords, append .tex if should append .tex suffix for the "include" keywords, append .tex if
there is no extension for the "input" keyword, and need to add .bib there is no extension for the "input" keyword, and need to add .bib
for the "bibliography" keyword that does not accept extensions by itself. for the "bibliography" keyword that does not accept extensions by itself.
@ -137,7 +137,7 @@ class LaTeX(SCons.Scanner.Base):
Finally, if there is no extension for an "includegraphics" keyword Finally, if there is no extension for an "includegraphics" keyword
latex will append .ps or .eps to find the file, while pdftex may use .pdf, latex will append .ps or .eps to find the file, while pdftex may use .pdf,
.jpg, .tif, .mps, or .png. .jpg, .tif, .mps, or .png.
The actual subset and search order may be altered by The actual subset and search order may be altered by
DeclareGraphicsExtensions command. This complication is ignored. DeclareGraphicsExtensions command. This complication is ignored.
The default order corresponds to experimentation with teTeX:: The default order corresponds to experimentation with teTeX::
@ -179,15 +179,7 @@ class LaTeX(SCons.Scanner.Base):
'inputfrom', 'subinputfrom'] 'inputfrom', 'subinputfrom']
def __init__(self, name, suffixes, graphics_extensions, *args, **kw): 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''' regex = r'''
^[^%\n]*
\\( \\(
include include
| includegraphics(?:\s*\[[^\]]+\])? | includegraphics(?:\s*\[[^\]]+\])?
@ -219,7 +211,7 @@ class LaTeX(SCons.Scanner.Base):
return [] return []
return self.scan_recurse(node, path) return self.scan_recurse(node, path)
class FindMultiPathDirs(object): class FindMultiPathDirs:
"""The stock FindPathDirs function has the wrong granularity: """The stock FindPathDirs function has the wrong granularity:
it is called once per target, while we need the path that depends it is called once per target, while we need the path that depends
on what kind of included files is being searched. This wrapper 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" # To prevent "dict is not hashable error"
return tuple(di.items()) return tuple(di.items())
class LaTeXScanCheck(object): class LaTeXScanCheck:
"""Skip all but LaTeX source files, i.e., do not scan *.eps, """Skip all but LaTeX source files, i.e., do not scan *.eps,
*.pdf, *.jpg, etc. *.pdf, *.jpg, etc.
""" """
@ -333,7 +325,7 @@ class LaTeX(SCons.Scanner.Base):
line_continues_a_comment = False line_continues_a_comment = False
for line in text.splitlines(): for line in text.splitlines():
line,comment = self.comment_re.findall(line)[0] 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() out[-1] = out[-1] + line.lstrip()
else: else:
out.append(line) out.append(line)
@ -348,8 +340,8 @@ class LaTeX(SCons.Scanner.Base):
# Cache the includes list in node so we only scan it once: # Cache the includes list in node so we only scan it once:
# path_dict = dict(list(path)) # path_dict = dict(list(path))
# add option for whitespace (\s) before the '[' # add option for whitespace (\s) before the '['
noopt_cre = re.compile('\s*\[.*$') noopt_cre = re.compile(r'\s*\[.*$')
if node.includes != None: if node.includes is not None:
includes = node.includes includes = node.includes
else: else:
text = self.canonical_text(node.get_text_contents()) text = self.canonical_text(node.get_text_contents())
@ -372,9 +364,9 @@ class LaTeX(SCons.Scanner.Base):
inc_list = include[2].split(',') inc_list = include[2].split(',')
else: else:
inc_list = include[1].split(',') inc_list = include[1].split(',')
for j in range(len(inc_list)): for inc in inc_list:
split_includes.append( (inc_type, inc_subdir, inc_list[j]) ) split_includes.append((inc_type, inc_subdir, inc))
#
includes = split_includes includes = split_includes
node.includes = includes node.includes = includes
@ -386,8 +378,8 @@ class LaTeX(SCons.Scanner.Base):
directory of the main file just as latex does""" directory of the main file just as latex does"""
path_dict = dict(list(path)) path_dict = dict(list(path))
queue = [] queue = []
queue.extend( self.scan(node) ) queue.extend( self.scan(node) )
seen = {} seen = {}
@ -402,7 +394,7 @@ class LaTeX(SCons.Scanner.Base):
source_dir = node.get_dir() source_dir = node.get_dir()
#for include in includes: #for include in includes:
while queue: while queue:
include = queue.pop() include = queue.pop()
inc_type, inc_subdir, inc_filename = include inc_type, inc_subdir, inc_filename = include

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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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
import SCons.Node.FS 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 import re
@ -48,9 +48,9 @@ def RCScan():
"""Return a prototype Scanner instance for scanning RC source files""" """Return a prototype Scanner instance for scanning RC source files"""
res_re= r'^(?:\s*#\s*(?:include)|' \ res_re= r'^(?:\s*#\s*(?:include)|' \
'.*?\s+(?:ICON|BITMAP|CURSOR|HTML|FONT|MESSAGETABLE|TYPELIB|REGISTRY|D3DFX)' \ r'.*?\s+(?:ICON|BITMAP|CURSOR|HTML|FONT|MESSAGETABLE|TYPELIB|REGISTRY|D3DFX)' \
'\s*.*?)' \ r'\s*.*?)' \
'\s*(<|"| )([^>"\s]+)(?:[>"\s])*$' r'\s*(<|"| )([^>"\s]+)(?:[>"\s])*$'
resScanner = SCons.Scanner.ClassicCPP("ResourceScanner", resScanner = SCons.Scanner.ClassicCPP("ResourceScanner",
"$RCSUFFIXES", "$RCSUFFIXES",
"CPPPATH", "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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 import SCons.Scanner
SWIGSuffixes = [ '.i' ] SWIGSuffixes = [ '.i' ]
def SWIGScanner(): 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) scanner = SCons.Scanner.ClassicCPP("SWIGScanner", ".i", "SWIGPATH", expr)
return scanner 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 import re
@ -35,7 +35,7 @@ import SCons.Node.FS
import SCons.Util import SCons.Util
class _Null(object): class _Null:
pass pass
# This is used instead of None as a default argument value so None can be # 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 A class to bind a specific E{*}PATH variable name to a function that
will return all of the E{*}path directories. 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 The base class for dependency scanners. This implements
straightforward, single-pass scanning of a single file. straightforward, single-pass scanning of a single file.
@ -207,7 +207,7 @@ class Base(object):
self = self.select(node) 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) node_list = self.function(node, env, path, self.argument)
else: else:
node_list = self.function(node, env, path) 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__revision__ = "src/engine/SCons/Script/Interactive.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
__doc__ = """ __doc__ = """
SCons interactive mode SCons interactive mode
@ -247,7 +245,7 @@ version Prints SCons version information.
while n: while n:
n = walker.get_next() 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 # Call node.clear() to clear most of the state
node.clear() node.clear()
# node.clear() doesn't reset node.state, so call # 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. 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) # __COPYRIGHT__
deprecated_python_version = (2, 7, 0)
# Copyright (c) 2001 - 2017 The SCons Foundation
# #
# Permission is hereby granted, free of charge, to any person obtaining # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__revision__ = "src/engine/SCons/Script/Main.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.compat import SCons.compat
import atexit
import importlib.util
import os import os
import re
import sys import sys
import time import time
import traceback import traceback
import sysconfig import sysconfig
import platform
import SCons.CacheDir import SCons.CacheDir
import SCons.Debug import SCons.Debug
@ -58,14 +59,29 @@ import SCons.Job
import SCons.Node import SCons.Node
import SCons.Node.FS import SCons.Node.FS
import SCons.Platform import SCons.Platform
import SCons.Platform.virtualenv
import SCons.SConf import SCons.SConf
import SCons.Script import SCons.Script
import SCons.Taskmaster import SCons.Taskmaster
import SCons.Util import SCons.Util
import SCons.Warnings import SCons.Warnings
import SCons.Script.Interactive 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(): def fetch_win32_parallel_msg():
# A subsidiary function that exists solely to isolate this import # A subsidiary function that exists solely to isolate this import
@ -85,17 +101,16 @@ def revert_io():
sys.stderr = sys.__stderr__ sys.stderr = sys.__stderr__
sys.stdout = sys.__stdout__ sys.stdout = sys.__stdout__
class SConsPrintHelpException(Exception): class SConsPrintHelpException(Exception):
pass pass
display = SCons.Util.display display = SCons.Util.display
progress_display = SCons.Util.DisplayEngine() progress_display = SCons.Util.DisplayEngine()
first_command_start = None
last_command_end = None
class Progressor:
class Progressor(object):
prev = '' prev = ''
count = 0 count = 0
target_string = '$TARGET' target_string = '$TARGET'
@ -194,6 +209,9 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask):
finish_time = time.time() finish_time = time.time()
last_command_end = finish_time last_command_end = finish_time
cumulative_command_time = cumulative_command_time+finish_time-start_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)) sys.stdout.write("Command execution time: %s: %f seconds\n"%(str(self.node), finish_time-start_time))
def do_failed(self, status=2): def do_failed(self, status=2):
@ -336,7 +354,7 @@ class CleanTask(SCons.Taskmaster.AlwaysTask):
display("Removed directory " + pathstr) display("Removed directory " + pathstr)
else: else:
errstr = "Path '%s' exists but isn't a file or directory." 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: except SCons.Errors.UserError as e:
print(e) print(e)
except (IOError, OSError) as e: except (IOError, OSError) as e:
@ -412,11 +430,12 @@ class QuestionTask(SCons.Taskmaster.AlwaysTask):
pass pass
class TreePrinter(object): class TreePrinter:
def __init__(self, derived=False, prune=False, status=False): def __init__(self, derived=False, prune=False, status=False, sLineDraw=False):
self.derived = derived self.derived = derived
self.prune = prune self.prune = prune
self.status = status self.status = status
self.sLineDraw = sLineDraw
def get_all_children(self, node): def get_all_children(self, node):
return node.all_children() return node.all_children()
def get_derived_children(self, node): def get_derived_children(self, node):
@ -428,7 +447,7 @@ class TreePrinter(object):
else: else:
func = self.get_all_children func = self.get_all_children
s = self.status and 2 or 0 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(): def python_version_string():
@ -441,20 +460,7 @@ def python_version_deprecated(version=sys.version_info):
return version < deprecated_python_version return version < deprecated_python_version
# Global variables class FakeOptionParser:
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):
""" """
A do-nothing option parser, used for the initial OptionsParser variable. A do-nothing option parser, used for the initial OptionsParser variable.
@ -466,7 +472,7 @@ class FakeOptionParser(object):
without blowing up. without blowing up.
""" """
class FakeOptionValues(object): class FakeOptionValues:
def __getattr__(self, attr): def __getattr__(self, attr):
return None return None
values = FakeOptionValues() values = FakeOptionValues()
@ -490,7 +496,7 @@ def SetOption(name, value):
def PrintHelp(file=None): def PrintHelp(file=None):
OptionsParser.print_help(file=file) OptionsParser.print_help(file=file)
class Stats(object): class Stats:
def __init__(self): def __init__(self):
self.stats = [] self.stats = []
self.labels = [] self.labels = []
@ -622,7 +628,7 @@ def _SConstruct_exists(dirname='', repositories=[], filelist=None):
current directory. current directory.
""" """
if not filelist: if not filelist:
filelist = ['SConstruct', 'Sconstruct', 'sconstruct'] filelist = ['SConstruct', 'Sconstruct', 'sconstruct', 'SConstruct.py', 'Sconstruct.py', 'sconstruct.py']
for file in filelist: for file in filelist:
sfile = os.path.join(dirname, file) sfile = os.path.join(dirname, file)
if os.path.isfile(sfile): if os.path.isfile(sfile):
@ -634,7 +640,7 @@ def _SConstruct_exists(dirname='', repositories=[], filelist=None):
return None return None
def _set_debug_values(options): 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 debug_values = options.debug
@ -672,6 +678,9 @@ def _set_debug_values(options):
options.tree_printers.append(TreePrinter(status=True)) options.tree_printers.append(TreePrinter(status=True))
if "time" in debug_values: if "time" in debug_values:
print_time = 1 print_time = 1
if "action-timestamps" in debug_values:
print_time = 1
print_action_timestamps = 1
if "tree" in debug_values: if "tree" in debug_values:
options.tree_printers.append(TreePrinter()) options.tree_printers.append(TreePrinter())
if "prepare" in debug_values: if "prepare" in debug_values:
@ -689,80 +698,87 @@ def _create_path(plist):
return path return path
def _load_site_scons_dir(topdir, site_dir_name=None): def _load_site_scons_dir(topdir, site_dir_name=None):
"""Load the site_scons dir under topdir. """Load the site directory under topdir.
Prepends site_scons to sys.path, imports site_scons/site_init.py,
and prepends site_scons/site_tools to default toolpath.""" 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: if site_dir_name:
err_if_not_found = True # user specified: err if missing err_if_not_found = True # user specified: err if missing
else: else:
site_dir_name = "site_scons" 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) site_dir = os.path.join(topdir, site_dir_name)
if not os.path.exists(site_dir): if not os.path.exists(site_dir):
if err_if_not_found: 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 return
sys.path.insert(0, os.path.abspath(site_dir))
site_init_filename = "site_init.py" site_init_filename = "site_init.py"
site_init_modname = "site_init" site_init_modname = "site_init"
site_tools_dirname = "site_tools" 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_init_file = os.path.join(site_dir, site_init_filename)
site_tools_dir = os.path.join(site_dir, site_tools_dirname) site_tools_dir = os.path.join(site_dir, site_tools_dirname)
if os.path.exists(site_init_file):
import imp, re
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]
# This is the magic.
exec(compile(fp.read(), fp.name, 'exec'), 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))
raise
else:
for k in site_m:
if not re_special.match(k):
m.__dict__[k] = site_m[k]
except KeyboardInterrupt:
raise
except ImportError as e:
fmt = '*** cannot import site init file %s:\n'
sys.stderr.write(fmt % repr(site_init_file))
raise
finally:
if fp:
fp.close()
if os.path.exists(site_tools_dir): if os.path.exists(site_tools_dir):
# prepend to DefaultToolpath
SCons.Tool.DefaultToolpath.insert(0, os.path.abspath(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:
m = sys.modules['SCons.Script']
except KeyError:
fmt = 'cannot import {}: missing SCons.Script module'
raise SCons.Errors.InternalError(fmt.format(site_init_file))
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:
fmt = "*** Error loading site_init file {}:\n"
sys.stderr.write(fmt.format(site_init_file))
raise
else:
# 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 Exception:
fmt = "*** cannot import site init file {}:\n"
sys.stderr.write(fmt.format(site_init_file))
raise
def _load_all_site_scons_dirs(topdir, verbose=None): def _load_all_site_scons_dirs(topdir, verbose=None):
"""Load all of the predefined site_scons dir. """Load all of the predefined site_scons dir.
Order is significant; we load them in order from most generic Order is significant; we load them in order from most generic
@ -801,7 +817,7 @@ def _load_all_site_scons_dirs(topdir, verbose=None):
sysdirs=['/usr/share/scons', sysdirs=['/usr/share/scons',
homedir('.scons')] homedir('.scons')]
dirs=sysdirs + [topdir] dirs = sysdirs + [topdir]
for d in dirs: for d in dirs:
if verbose: # this is used by unit tests. if verbose: # this is used by unit tests.
print("Loading site dir ", d) print("Loading site dir ", d)
@ -862,6 +878,13 @@ def _main(parser):
for warning_type, message in delayed_warnings: for warning_type, message in delayed_warnings:
SCons.Warnings.warn(warning_type, message) 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: if options.diskcheck:
SCons.Node.FS.set_diskcheck(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 # -U, local SConscript Default() targets
target_top = fs.Dir(target_top) target_top = fs.Dir(target_top)
def check_dir(x, target_top=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() cwd = x.cwd.srcnode()
return cwd == target_top return cwd == target_top
else: else:
@ -1240,10 +1263,14 @@ def _build_targets(fs, options, targets, target_top):
"""Leave the order of dependencies alone.""" """Leave the order of dependencies alone."""
return dependencies return dependencies
def tmtrace_cleanup(tfile):
tfile.close()
if options.taskmastertrace_file == '-': if options.taskmastertrace_file == '-':
tmtrace = sys.stdout tmtrace = sys.stdout
elif options.taskmastertrace_file: elif options.taskmastertrace_file:
tmtrace = open(options.taskmastertrace_file, 'w') tmtrace = open(options.taskmastertrace_file, 'w')
atexit.register(tmtrace_cleanup, tmtrace)
else: else:
tmtrace = None tmtrace = None
taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace) taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
@ -1253,7 +1280,11 @@ def _build_targets(fs, options, targets, target_top):
BuildTask.options = options 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. # to check if python configured with threads.
global num_jobs global num_jobs
num_jobs = options.num_jobs num_jobs = options.num_jobs
@ -1339,15 +1370,14 @@ def main():
parts = ["SCons by Steven Knight et al.:\n"] parts = ["SCons by Steven Knight et al.:\n"]
try: try:
import __main__ import SCons
parts.append(version_string("script", __main__)) parts.append(version_string("SCons", SCons))
except (ImportError, AttributeError): except (ImportError, AttributeError):
# On Windows there is no scons.py, so there is no # On Windows there is no scons.py, so there is no
# __main__.__version__, hence there is no script version. # __main__.__version__, hence there is no script version.
pass pass
parts.append(version_string("engine", SCons)) parts.append(path_string("SCons", SCons))
parts.append(path_string("engine", SCons)) parts.append(SCons.__copyright__)
parts.append("Copyright (c) 2001 - 2017 The SCons Foundation")
version = ''.join(parts) version = ''.join(parts)
from . import SConsOptions from . import SConsOptions
@ -1363,7 +1393,7 @@ def main():
revert_io() revert_io()
except SystemExit as s: except SystemExit as s:
if s: if s:
exit_status = s exit_status = s.code
except KeyboardInterrupt: except KeyboardInterrupt:
print("scons: Build interrupted.") print("scons: Build interrupted.")
sys.exit(2) 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 optparse
import re import re
import sys import sys
import textwrap import textwrap
no_hyphen_re = re.compile(r'(\s+|(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') no_hyphen_re = re.compile(r'(\s+|(?<=[\w!\"\'&.,?])-{2,}(?=\w))')
try: try:
from gettext import gettext from gettext import gettext
@ -38,6 +38,7 @@ except ImportError:
_ = gettext _ = gettext
import SCons.Node.FS import SCons.Node.FS
import SCons.Platform.virtualenv
import SCons.Warnings import SCons.Warnings
OptionValueError = optparse.OptionValueError OptionValueError = optparse.OptionValueError
@ -139,14 +140,15 @@ class SConsValues(optparse.Values):
'random', 'random',
'stack_size', 'stack_size',
'warn', 'warn',
'silent' 'silent',
'no_progress'
] ]
def set_option(self, name, value): def set_option(self, name, value):
""" """
Sets an option from an SConscript file. 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) raise SCons.Errors.UserError("This option is not settable from a SConscript file: %s"%name)
if name == 'num_jobs': if name == 'num_jobs':
@ -166,7 +168,7 @@ class SConsValues(optparse.Values):
value = str(value) value = str(value)
except ValueError: except ValueError:
raise SCons.Errors.UserError("A string is required: %s"%repr(value)) 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) raise SCons.Errors.UserError("Not a valid duplication style: %s" % value)
# Set the duplicate style right away so it can affect linking # Set the duplicate style right away so it can affect linking
# of SConscript files. # of SConscript files.
@ -196,6 +198,9 @@ class SConsValues(optparse.Values):
value = [value] value = [value]
value = self.__SConscript_settings__.get(name, []) + value value = self.__SConscript_settings__.get(name, []) + value
SCons.Warnings.process_warn_strings(value) SCons.Warnings.process_warn_strings(value)
elif name == 'no_progress':
SCons.Script.Main.progress_display.set_mode(False)
self.__SConscript_settings__[name] = value self.__SConscript_settings__[name] = value
@ -225,39 +230,8 @@ class SConsOption(optparse.Option):
fmt = "option %s: nargs='?' is incompatible with short options" fmt = "option %s: nargs='?' is incompatible with short options"
raise SCons.Errors.UserError(fmt % self._short_opts[0]) raise SCons.Errors.UserError(fmt % self._short_opts[0])
try: CHECK_METHODS = optparse.Option.CHECK_METHODS + [_check_nargs_optional]
_orig_CONST_ACTIONS = optparse.Option.CONST_ACTIONS CONST_ACTIONS = optparse.Option.CONST_ACTIONS + optparse.Option.TYPED_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
class SConsOptionGroup(optparse.OptionGroup): class SConsOptionGroup(optparse.OptionGroup):
""" """
@ -358,32 +332,30 @@ class SConsOptionParser(optparse.OptionParser):
option.process(opt, value, values, self) option.process(opt, value, values, self)
def reparse_local_options(self): def reparse_local_options(self):
""" """ Re-parse the leftover command-line options.
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.
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() 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 :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 below. When called from there, we try to reparse the
command-line arguments that command-line arguments that
1. haven't been processed so far (self.largs), but
2. are possibly not added to the list of options yet. 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. So, when we only have a value for "--myargument" so far,
Responsible for this behaviour is the method a command-line argument of "--myarg=test" would set it,
_match_long_opt(), which allows for partial matches of per the behaviour of :func:`_match_long_opt`,
the option name, as long as the common prefix appears to which allows for partial matches of the option name,
be unique. as long as the common prefix appears to be unique.
This would lead to further confusion, because we might want This would lead to further confusion, because we might want
to add another option "--myarg" later on (see issue #2929). to add another option "--myarg" later on (see issue #2929).
""" """
rargs = [] rargs = []
largs_restore = [] largs_restore = []
@ -400,7 +372,7 @@ class SConsOptionParser(optparse.OptionParser):
if "=" in l: if "=" in l:
# Split into option and value # Split into option and value
lopt = l.split("=", 1) lopt = l.split("=", 1)
if lopt[0] in self._long_opt: if lopt[0] in self._long_opt:
# Argument is already known # Argument is already known
rargs.append('='.join(lopt)) rargs.append('='.join(lopt))
@ -415,7 +387,7 @@ class SConsOptionParser(optparse.OptionParser):
skip = True skip = True
else: else:
rargs.append(l) rargs.append(l)
# Parse the filtered list # Parse the filtered list
self.parse_args(rargs, self.values) self.parse_args(rargs, self.values)
# Restore the list of remaining arguments for the # Restore the list of remaining arguments for the
@ -614,9 +586,15 @@ def Parser(version):
help="Print build actions for files from CacheDir.") help="Print build actions for files from CacheDir.")
def opt_invalid(group, value, options): 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) errmsg = "`%s' is not a valid %s option type, try:\n" % (value, group)
return errmsg + " %s" % ", ".join(options) 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"] config_options = ["auto", "force" ,"cache"]
opt_config_help = "Controls Configure subsystem: %s." \ opt_config_help = "Controls Configure subsystem: %s." \
@ -634,9 +612,11 @@ def Parser(version):
help="Search up directory tree for SConstruct, " help="Search up directory tree for SConstruct, "
"build all Default() targets.") "build all Default() targets.")
deprecated_debug_options = { deprecated_debug_options = {}
removed_debug_options = {
"dtree" : '; please use --tree=derived instead', "dtree" : '; please use --tree=derived instead',
"nomemoizer" : ' and has no effect', "nomemoizer" : '; there is no replacement',
"stree" : '; please use --tree=all,status instead', "stree" : '; please use --tree=all,status instead',
"tree" : '; please use --tree=all instead', "tree" : '; please use --tree=all instead',
} }
@ -644,15 +624,16 @@ def Parser(version):
debug_options = ["count", "duplicate", "explain", "findlibs", debug_options = ["count", "duplicate", "explain", "findlibs",
"includes", "memoizer", "memory", "objects", "includes", "memoizer", "memory", "objects",
"pdb", "prepare", "presub", "stacktrace", "pdb", "prepare", "presub", "stacktrace",
"time"] "time", "action-timestamps"]
def opt_debug(option, opt, value__, parser, def opt_debug(option, opt, value__, parser,
debug_options=debug_options, 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(','): for value in value__.split(','):
if value in debug_options: if value in debug_options:
parser.values.debug.append(value) parser.values.debug.append(value)
elif value in list(deprecated_debug_options.keys()): elif value in deprecated_debug_options:
parser.values.debug.append(value) parser.values.debug.append(value)
try: try:
parser.values.delayed_warnings parser.values.delayed_warnings
@ -662,6 +643,9 @@ def Parser(version):
w = "The --debug=%s option is deprecated%s." % (value, msg) w = "The --debug=%s option is deprecated%s." % (value, msg)
t = (SCons.Warnings.DeprecatedDebugOptionsWarning, w) t = (SCons.Warnings.DeprecatedDebugOptionsWarning, w)
parser.values.delayed_warnings.append(t) 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: else:
raise OptionValueError(opt_invalid('debug', value, debug_options)) raise OptionValueError(opt_invalid('debug', value, debug_options))
@ -689,7 +673,7 @@ def Parser(version):
metavar="TYPE") metavar="TYPE")
def opt_duplicate(option, opt, value, parser): 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, raise OptionValueError(opt_invalid('duplication', value,
SCons.Node.FS.Valid_Duplicates)) SCons.Node.FS.Valid_Duplicates))
setattr(parser.values, option.dest, value) setattr(parser.values, option.dest, value)
@ -706,6 +690,12 @@ def Parser(version):
action="callback", callback=opt_duplicate, action="callback", callback=opt_duplicate,
help=opt_duplicate_help) 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', op.add_option('-f', '--file', '--makefile', '--sconstruct',
nargs=1, type="string", nargs=1, type="string",
dest="file", default=[], dest="file", default=[],
@ -733,6 +723,11 @@ def Parser(version):
help="Search DIR for imported Python modules.", help="Search DIR for imported Python modules.",
metavar="DIR") 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', op.add_option('--implicit-cache',
dest='implicit_cache', default=False, dest='implicit_cache', default=False,
action="store_true", action="store_true",
@ -841,7 +836,7 @@ def Parser(version):
help="Trace Node evaluation to FILE.", help="Trace Node evaluation to FILE.",
metavar="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): def opt_tree(option, opt, value, parser, tree_options=tree_options):
from . import Main from . import Main
@ -855,6 +850,8 @@ def Parser(version):
tp.prune = True tp.prune = True
elif o == 'status': elif o == 'status':
tp.status = True tp.status = True
elif o == 'linedraw':
tp.sLineDraw = True
else: else:
raise OptionValueError(opt_invalid('--tree', o, tree_options)) raise OptionValueError(opt_invalid('--tree', o, tree_options))
parser.values.tree_printers.append(tp) parser.values.tree_printers.append(tp)
@ -906,6 +903,7 @@ def Parser(version):
action="append", action="append",
help="Search REPOSITORY for source and target files.") help="Search REPOSITORY for source and target files.")
# Options from Make and Cons classic that we do not yet support, # Options from Make and Cons classic that we do not yet support,
# but which we may support someday and whose (potential) meanings # 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 # 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, action="callback", callback=opt_not_yet,
# help="Warn when an undefined variable is referenced." # help="Warn when an undefined variable is referenced."
help=SUPPRESS_HELP) help=SUPPRESS_HELP)
return op return op
# Local Variables: # 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__revision__ = "src/engine/SCons/Script/SConscript.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons import SCons
import SCons.Action import SCons.Action
@ -42,8 +42,8 @@ import SCons.Platform
import SCons.SConf import SCons.SConf
import SCons.Script.Main import SCons.Script.Main
import SCons.Tool 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 from . import Main
import collections import collections
@ -98,7 +98,7 @@ def compute_exports(exports):
retval = {} retval = {}
try: try:
for export in exports: for export in exports:
if SCons.Util.is_Dict(export): if is_Dict(export):
retval.update(export) retval.update(export)
else: else:
try: try:
@ -110,7 +110,7 @@ def compute_exports(exports):
return retval return retval
class Frame(object): class Frame:
"""A frame on the SConstruct/SConscript call stack""" """A frame on the SConstruct/SConscript call stack"""
def __init__(self, fs, exports, sconscript): def __init__(self, fs, exports, sconscript):
self.globals = BuildDefaultGlobals() self.globals = BuildDefaultGlobals()
@ -133,7 +133,7 @@ call_stack = []
def Return(*vars, **kw): def Return(*vars, **kw):
retval = [] retval = []
try: try:
fvars = SCons.Util.flatten(vars) fvars = flatten(vars)
for var in fvars: for var in fvars:
for v in var.split(): for v in var.split():
retval.append(call_stack[-1].globals[v]) 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 :) 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): def _SConscript(fs, *files, **kw):
top = fs.Top top = fs.Top
sd = fs.SConstruct_dir.rdir() sd = fs.SConstruct_dir.rdir()
@ -173,6 +202,7 @@ def _SConscript(fs, *files, **kw):
else: else:
f = fs.File(str(fn)) f = fs.File(str(fn))
_file_ = None _file_ = None
SConscriptNodes.add(f)
# Change directory to the top of the source # Change directory to the top of the source
# tree to make sure the os's cwd and the cwd of # tree to make sure the os's cwd and the cwd of
@ -249,11 +279,12 @@ def _SConscript(fs, *files, **kw):
pass pass
try: try:
try: try:
# _file_ = SCons.Util.to_str(_file_)
if Main.print_time: if Main.print_time:
time1 = time.time() time1 = time.time()
exec(compile(_file_.read(), _file_.name, 'exec'), scriptdata = _file_.read()
call_stack[-1].globals) scriptname = _file_.name
_file_.close()
exec(compile(scriptdata, scriptname, 'exec'), call_stack[-1].globals)
except SConscriptReturn: except SConscriptReturn:
pass pass
finally: finally:
@ -264,8 +295,7 @@ def _SConscript(fs, *files, **kw):
if old_file is not None: if old_file is not None:
call_stack[-1].globals.update({__file__:old_file}) call_stack[-1].globals.update({__file__:old_file})
else: else:
SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning, handle_missing_SConscript(f, kw.get('must_exist', None))
"Ignoring missing SConscript '%s'" % f.get_internal_path())
finally: finally:
SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
@ -369,9 +399,9 @@ class SConsEnvironment(SCons.Environment.Base):
something like 3.2b1.""" something like 3.2b1."""
version = version_string.split(' ')[0].split('.') version = version_string.split(' ')[0].split('.')
v_major = int(version[0]) 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: if len(version) >= 3:
v_revision = int(re.match('\d+', version[2]).group()) v_revision = int(re.match(r'\d+', version[2]).group())
else: else:
v_revision = 0 v_revision = 0
return v_major, v_minor, v_revision return v_major, v_minor, v_revision
@ -391,7 +421,7 @@ class SConsEnvironment(SCons.Environment.Base):
except KeyError: except KeyError:
raise SCons.Errors.UserError("Invalid SConscript usage - no parameters") raise SCons.Errors.UserError("Invalid SConscript usage - no parameters")
if not SCons.Util.is_List(dirs): if not is_List(dirs):
dirs = [ dirs ] dirs = [ dirs ]
dirs = list(map(str, 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") raise SCons.Errors.UserError("Invalid SConscript() usage - too many arguments")
if not SCons.Util.is_List(files): if not is_List(files):
files = [ files ] files = [ files ]
if kw.get('exports'): if kw.get('exports'):
exports.extend(self.Split(kw['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 variant_dir:
if len(files) != 1: if len(files) != 1:
raise SCons.Errors.UserError("Invalid SConscript() usage - can only specify one SConscript with a variant_dir") 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) raise SCons.Errors.UserError("Import of non-existent variable '%s'"%x)
def SConscript(self, *ls, **kw): def SConscript(self, *ls, **kw):
if 'build_dir' in kw: """Execute SCons configuration files.
msg = """The build_dir keyword has been deprecated; use the variant_dir keyword instead."""
SCons.Warnings.warn(SCons.Warnings.DeprecatedBuildDirWarning, msg) 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): def subst_element(x, subst=self.subst):
if SCons.Util.is_List(x): if SCons.Util.is_List(x):
x = list(map(subst, x)) x = list(map(subst, x))
@ -535,15 +587,10 @@ class SConsEnvironment(SCons.Environment.Base):
ls = list(map(subst_element, ls)) ls = list(map(subst_element, ls))
subst_kw = {} subst_kw = {}
for key, val in kw.items(): for key, val in kw.items():
if SCons.Util.is_String(val): if is_String(val):
val = self.subst(val) val = self.subst(val)
elif SCons.Util.is_List(val): elif SCons.Util.is_List(val):
result = [] val = [self.subst(v) if is_String(v) else v for v in val]
for v in val:
if SCons.Util.is_String(v):
v = self.subst(v)
result.append(v)
val = result
subst_kw[key] = val subst_kw[key] = val
files, exports = self._get_SConscript_filenames(ls, subst_kw) files, exports = self._get_SConscript_filenames(ls, subst_kw)
@ -593,7 +640,7 @@ def get_DefaultEnvironmentProxy():
_DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env) _DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env)
return _DefaultEnvironmentProxy return _DefaultEnvironmentProxy
class DefaultEnvironmentCall(object): class DefaultEnvironmentCall:
"""A class that implements "global function" calls of """A class that implements "global function" calls of
Environment methods by fetching the specified method from the Environment methods by fetching the specified method from the
DefaultEnvironment's class. Note that this uses an intermediate 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 import time
start_time = time.time() start_time = time.time()
@ -81,8 +81,8 @@ import SCons.Action
import SCons.Builder import SCons.Builder
import SCons.Environment import SCons.Environment
import SCons.Node.FS import SCons.Node.FS
import SCons.Options
import SCons.Platform import SCons.Platform
import SCons.Platform.virtualenv
import SCons.Scanner import SCons.Scanner
import SCons.SConf import SCons.SConf
import SCons.Subst import SCons.Subst
@ -150,6 +150,7 @@ Environment = SCons.Environment.Environment
#OptParser = SCons.SConsOptions.OptParser #OptParser = SCons.SConsOptions.OptParser
FindPathDirs = SCons.Scanner.FindPathDirs FindPathDirs = SCons.Scanner.FindPathDirs
Platform = SCons.Platform.Platform Platform = SCons.Platform.Platform
Virtualenv = SCons.Platform.virtualenv.Virtualenv
Return = _SConscript.Return Return = _SConscript.Return
Scanner = SCons.Scanner.Base Scanner = SCons.Scanner.Base
Tool = SCons.Tool.Tool Tool = SCons.Tool.Tool
@ -162,12 +163,6 @@ ListVariable = SCons.Variables.ListVariable
PackageVariable = SCons.Variables.PackageVariable PackageVariable = SCons.Variables.PackageVariable
PathVariable = SCons.Variables.PathVariable 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. # Action factories.
Chmod = SCons.Defaults.Chmod Chmod = SCons.Defaults.Chmod
@ -283,12 +278,20 @@ def HelpFunction(text, append=False):
# Will be non-zero if we are reading an SConscript file. # Will be non-zero if we are reading an SConscript file.
sconscript_reading = 0 sconscript_reading = 0
# _no_missing_sconscript = False
def Variables(files=[], args=ARGUMENTS): _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) 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 # The list of global functions to add to the SConscript name space
# that end up calling corresponding methods or Builders in the # that end up calling corresponding methods or Builders in the
@ -311,7 +314,6 @@ GlobalDefaultEnvironmentFunctions = [
'AddPreAction', 'AddPreAction',
'Alias', 'Alias',
'AlwaysBuild', 'AlwaysBuild',
'BuildDir',
'CacheDir', 'CacheDir',
'Clean', 'Clean',
#The Command() method is handled separately, below. #The Command() method is handled separately, below.
@ -342,11 +344,8 @@ GlobalDefaultEnvironmentFunctions = [
'Requires', 'Requires',
'SConsignFile', 'SConsignFile',
'SideEffect', 'SideEffect',
'SourceCode',
'SourceSignatures',
'Split', 'Split',
'Tag', 'Tag',
'TargetSignatures',
'Value', 'Value',
'VariantDir', 'VariantDir',
] ]
@ -374,7 +373,9 @@ GlobalDefaultBuilders = [
'SharedObject', 'SharedObject',
'StaticLibrary', 'StaticLibrary',
'StaticObject', 'StaticObject',
'Substfile',
'Tar', 'Tar',
'Textfile',
'TypeLibrary', 'TypeLibrary',
'Zip', 'Zip',
'Package', '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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__revision__ = "src/engine/SCons/Subst.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import collections import collections
import re import re
from inspect import signature
import SCons.Errors import SCons.Errors
from SCons.Util import is_String, is_Sequence 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 """A wrapper for a string. If you use this object wrapped
around a string, then it will be interpreted as literal. around a string, then it will be interpreted as literal.
When passed to the command interpreter, all special When passed to the command interpreter, all special
@ -86,7 +86,10 @@ class Literal(object):
def __neq__(self, other): def __neq__(self, other):
return not self.__eq__(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 a wrapper for what we call a 'Node special attribute.'
This is any of the attributes of a Node that we can reference from This is any of the attributes of a Node that we can reference from
Environment variable substitution, such as $TARGET.abspath or Environment variable substitution, such as $TARGET.abspath or
@ -168,7 +171,7 @@ def escape_list(mylist, escape_func):
return e(escape_func) return e(escape_func)
return list(map(escape, mylist)) return list(map(escape, mylist))
class NLWrapper(object): class NLWrapper:
"""A wrapper class that delays turning a list of sources or targets """A wrapper class that delays turning a list of sources or targets
into a NodeList until it's needed. The specified function supplied into a NodeList until it's needed. The specified function supplied
when the object is initialized is responsible for turning raw nodes 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() nl = self.nl._create_nodelist()
return repr(nl) return repr(nl)
class Target_or_Source(object): class Target_or_Source:
"""A class that implements $TARGET or $SOURCE expansions by in turn """A class that implements $TARGET or $SOURCE expansions by in turn
wrapping a NLWrapper. This class handles the different methods used wrapping a NLWrapper. This class handles the different methods used
to access an individual proxy Node, calling the NLWrapper to create to access an individual proxy Node, calling the NLWrapper to create
@ -328,6 +331,407 @@ def subst_dict(target, source):
return dict return dict
class StringSubber:
"""A class to construct the results of a scons_subst() call.
This binds a specific construction environment, mode, target and
source with two methods (substitute() and expand()) that handle
the expansion.
"""
def __init__(self, env, mode, conv, gvars):
self.env = env
self.mode = mode
self.conv = conv
self.gvars = gvars
def expand(self, s, lvars):
"""Expand a single "token" as necessary, returning an
appropriate string containing the expansion.
This handles expanding different types of things (strings,
lists, callables) appropriately. It calls the wrapper
substitute() method to re-expand things as necessary, so that
the results of expansions of side-by-side strings still get
re-evaluated separately, not smushed together.
"""
if is_String(s):
try:
s0, s1 = s[:2]
except (IndexError, ValueError):
return s
if s0 != '$':
return s
if s1 == '$':
# In this case keep the double $'s which we'll later
# swap for a single dollar sign as we need to retain
# this information to properly avoid matching "$("" when
# the actual text was "$$("" (or "$)"" when "$$)"" )
return '$$'
elif s1 in '()':
return s
else:
key = s[1:]
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:
raise
except Exception as e:
if e.__class__ in AllowableExceptions:
return ''
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
# recursive expansion by copying the local
# variable dictionary and overwriting a null
# string for the value of the variable name
# we just expanded.
#
# This could potentially be optimized by only
# copying lvars when s contains more expansions,
# but lvars is usually supposed to be pretty
# small, and deeply nested variable expansions
# are probably more the exception than the norm,
# so it should be tolerable for now.
lv = lvars.copy()
var = key.split('.')[0]
lv[var] = ''
return self.substitute(s, lv)
elif is_Sequence(s):
def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
return conv(substitute(l, lvars))
return list(map(func, s))
elif callable(s):
# 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))
else:
# This probably indicates that it's a callable
# object that doesn't match our calling arguments
# (like an Action).
if self.mode == SUBST_RAW:
return s
s = self.conv(s)
return self.substitute(s, lvars)
elif s is None:
return ''
else:
return s
def substitute(self, args, lvars):
"""Substitute expansions in an argument or list of arguments.
This serves as a wrapper for splitting up a string into
separate tokens.
"""
if is_String(args) and not isinstance(args, CmdStringHolder):
args = str(args) # In case it's a UserString.
try:
def sub_match(match):
return self.conv(self.expand(match.group(1), lvars))
result = _dollar_exps.sub(sub_match, args)
except TypeError:
# If the internal conversion routine doesn't return
# strings (it could be overridden to return Nodes, for
# example), then the 1.5.2 re module will throw this
# exception. Back off to a slower, general-purpose
# algorithm that works for all data types.
args = _separate_args.findall(args)
result = []
for a in args:
result.append(self.conv(self.expand(a, lvars)))
if len(result) == 1:
result = result[0]
else:
result = ''.join(map(str, result))
return result
else:
return self.expand(args, lvars)
class ListSubber(collections.UserList):
"""A class to construct the results of a scons_subst_list() call.
Like StringSubber, this class binds a specific construction
environment, mode, target and source with two methods
(substitute() and expand()) that handle the expansion.
In addition, however, this class is used to track the state of
the result(s) we're gathering so we can do the appropriate thing
whenever we have to append another word to the result--start a new
line, start a new word, append to the current word, etc. We do
this by setting the "append" attribute to the right method so
that our wrapper methods only need ever call ListSubber.append(),
and the rest of the object takes care of doing the right thing
internally.
"""
def __init__(self, env, mode, conv, gvars):
collections.UserList.__init__(self, [])
self.env = env
self.mode = mode
self.conv = conv
self.gvars = gvars
if self.mode == SUBST_RAW:
self.add_strip = lambda x: self.append(x)
else:
self.add_strip = lambda x: None
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.
This handles expanding different types of things (strings,
lists, callables) appropriately. It calls the wrapper
substitute() method to re-expand things as necessary, so that
the results of expansions of side-by-side strings still get
re-evaluated separately, not smushed together.
"""
if is_String(s):
try:
s0, s1 = s[:2]
except (IndexError, ValueError):
self.append(s)
return
if s0 != '$':
self.append(s)
return
if s1 == '$':
self.append('$')
elif s1 == '(':
self.open_strip('$(')
elif s1 == ')':
self.close_strip('$)')
else:
key = s[1:]
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:
raise
except Exception as e:
if e.__class__ in AllowableExceptions:
return
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
# recursive expansion by copying the local
# variable dictionary and overwriting a null
# string for the value of the variable name
# we just expanded.
lv = lvars.copy()
var = key.split('.')[0]
lv[var] = ''
self.substitute(s, lv, 0)
self.this_word()
elif is_Sequence(s):
for a in s:
self.substitute(a, lvars, 1)
self.next_word()
elif callable(s):
# 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))
else:
# This probably indicates that it's a callable
# object that doesn't match our calling arguments
# (like an Action).
if self.mode == SUBST_RAW:
self.append(s)
return
s = self.conv(s)
self.substitute(s, lvars, within_list)
elif s is None:
self.this_word()
else:
self.append(s)
def substitute(self, args, lvars, within_list):
"""Substitute expansions in an argument or list of arguments.
This serves as a wrapper for splitting up a string into
separate tokens.
"""
if is_String(args) and not isinstance(args, CmdStringHolder):
args = str(args) # In case it's a UserString.
args = _separate_args.findall(args)
for a in args:
if a[0] in ' \t\n\r\f\v':
if '\n' in a:
self.next_line()
elif within_list:
self.append(a)
else:
self.next_word()
else:
self.expand(a, lvars, within_list)
else:
self.expand(args, lvars, within_list)
def next_line(self):
"""Arrange for the next word to start a new line. This
is like starting a new word, except that we have to append
another line to the result."""
collections.UserList.append(self, [])
self.next_word()
def this_word(self):
"""Arrange for the next word to append to the end of the
current last word in the result."""
self.append = self.add_to_current_word
def next_word(self):
"""Arrange for the next word to start a new word."""
self.append = self.add_new_word
def add_to_current_word(self, x):
"""Append the string x to the end of the current last word
in the result. If that is not possible, then just add
it as a new word. Make sure the entire concatenated string
inherits the object attributes of x (in particular, the
escape function) by wrapping it as CmdStringHolder."""
if not self.in_strip or self.mode != SUBST_SIG:
try:
current_word = self[-1][-1]
except IndexError:
self.add_new_word(x)
else:
# All right, this is a hack and it should probably
# be refactored out of existence in the future.
# The issue is that we want to smoosh words together
# and make one file name that gets escaped if
# we're expanding something like foo$EXTENSION,
# but we don't want to smoosh them together if
# it's something like >$TARGET, because then we'll
# treat the '>' like it's part of the file name.
# So for now, just hard-code looking for the special
# command-line redirection characters...
try:
last_char = str(current_word)[-1]
except IndexError:
last_char = '\0'
if last_char in '<>|':
self.add_new_word(x)
else:
y = current_word + x
# We used to treat a word appended to a literal
# as a literal itself, but this caused problems
# with interpreting quotes around space-separated
# targets on command lines. Removing this makes
# none of the "substantive" end-to-end tests fail,
# so we'll take this out but leave it commented
# for now in case there's a problem not covered
# by the test cases and we need to resurrect this.
#literal1 = self.literal(self[-1][-1])
#literal2 = self.literal(x)
y = self.conv(y)
if is_String(y):
#y = CmdStringHolder(y, literal1 or literal2)
y = CmdStringHolder(y, None)
self[-1][-1] = y
def add_new_word(self, x):
if not self.in_strip or self.mode != SUBST_SIG:
literal = self.literal(x)
x = self.conv(x)
if is_String(x):
x = CmdStringHolder(x, literal)
self[-1].append(x)
self.append = self.add_to_current_word
def literal(self, x):
try:
l = x.is_literal
except AttributeError:
return None
else:
return l()
def open_strip(self, x):
"""Handle the "open strip" $( token."""
self.add_strip(x)
self.in_strip = 1
def close_strip(self, x):
"""Handle the "close strip" $) token."""
self.add_strip(x)
self.in_strip = None
# Constants for the "mode" parameter to scons_subst_list() and # Constants for the "mode" parameter to scons_subst_list() and
# scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD # 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 suitable for passing to a shell. SUBST_SIG
@ -347,7 +751,7 @@ _rm_split = re.compile(r'(?<!\$)(\$[()])')
_regex_remove = [ _rm, None, _rm_split ] _regex_remove = [ _rm, None, _rm_split ]
def _rm_list(list): def _rm_list(list):
return [l for l in list if not l in ('$(', '$)')] return [l for l in list if l not in ('$(', '$)')]
def _remove_list(list): def _remove_list(list):
result = [] result = []
@ -391,7 +795,7 @@ _list_remove = [ _rm_list, None, _remove_list ]
# #
_dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}' _dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
_dollar_exps = re.compile(r'(%s)' % _dollar_exps_str) _dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
_separate_args = re.compile(r'(%s|\s+|[^\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 # This regular expression is used to replace strings of multiple white
# space characters in the string result from the scons_subst() function. # space characters in the string result from the scons_subst() function.
@ -406,139 +810,9 @@ def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={
handles separating command lines into lists of arguments, so see handles separating command lines into lists of arguments, so see
that function if that's what you're looking for. that function if that's what you're looking for.
""" """
if isinstance(strSubst, str) and strSubst.find('$') < 0: if (isinstance(strSubst, str) and '$' not in strSubst) or isinstance(strSubst, CmdStringHolder):
return strSubst return strSubst
class StringSubber(object):
"""A class to construct the results of a scons_subst() call.
This binds a specific construction environment, mode, target and
source with two methods (substitute() and expand()) that handle
the expansion.
"""
def __init__(self, env, mode, conv, gvars):
self.env = env
self.mode = mode
self.conv = conv
self.gvars = gvars
def expand(self, s, lvars):
"""Expand a single "token" as necessary, returning an
appropriate string containing the expansion.
This handles expanding different types of things (strings,
lists, callables) appropriately. It calls the wrapper
substitute() method to re-expand things as necessary, so that
the results of expansions of side-by-side strings still get
re-evaluated separately, not smushed together.
"""
if is_String(s):
try:
s0, s1 = s[:2]
except (IndexError, ValueError):
return s
if s0 != '$':
return s
if s1 == '$':
# In this case keep the double $'s which we'll later
# swap for a single dollar sign as we need to retain
# this information to properly avoid matching "$("" when
# the actual text was "$$("" (or "$)"" when "$$)"" )
return '$$'
elif s1 in '()':
return s
else:
key = s[1:]
if key[0] == '{' or '.' in key:
if key[0] == '{':
key = key[1:-1]
try:
s = eval(key, self.gvars, lvars)
except KeyboardInterrupt:
raise
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:
return ''
# Before re-expanding the result, handle
# recursive expansion by copying the local
# variable dictionary and overwriting a null
# string for the value of the variable name
# we just expanded.
#
# This could potentially be optimized by only
# copying lvars when s contains more expansions,
# but lvars is usually supposed to be pretty
# small, and deeply nested variable expansions
# are probably more the exception than the norm,
# so it should be tolerable for now.
lv = lvars.copy()
var = key.split('.')[0]
lv[var] = ''
return self.substitute(s, lv)
elif is_Sequence(s):
def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars):
return conv(substitute(l, lvars))
return list(map(func, s))
elif callable(s):
try:
s = s(target=lvars['TARGETS'],
source=lvars['SOURCES'],
env=self.env,
for_signature=(self.mode != SUBST_CMD))
except TypeError:
# This probably indicates that it's a callable
# object that doesn't match our calling arguments
# (like an Action).
if self.mode == SUBST_RAW:
return s
s = self.conv(s)
return self.substitute(s, lvars)
elif s is None:
return ''
else:
return s
def substitute(self, args, lvars):
"""Substitute expansions in an argument or list of arguments.
This serves as a wrapper for splitting up a string into
separate tokens.
"""
if is_String(args) and not isinstance(args, CmdStringHolder):
args = str(args) # In case it's a UserString.
try:
def sub_match(match):
return self.conv(self.expand(match.group(1), lvars))
result = _dollar_exps.sub(sub_match, args)
except TypeError:
# If the internal conversion routine doesn't return
# strings (it could be overridden to return Nodes, for
# example), then the 1.5.2 re module will throw this
# exception. Back off to a slower, general-purpose
# algorithm that works for all data types.
args = _separate_args.findall(args)
result = []
for a in args:
result.append(self.conv(self.expand(a, lvars)))
if len(result) == 1:
result = result[0]
else:
result = ''.join(map(str, result))
return result
else:
return self.expand(args, lvars)
if conv is None: if conv is None:
conv = _strconv[mode] conv = _strconv[mode]
@ -612,234 +886,7 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
The companion scons_subst() function (above) handles basic The companion scons_subst() function (above) handles basic
substitutions within strings, so see that function instead substitutions within strings, so see that function instead
if that's what you're looking for. if that's what you're looking for.
""" """
class ListSubber(collections.UserList):
"""A class to construct the results of a scons_subst_list() call.
Like StringSubber, this class binds a specific construction
environment, mode, target and source with two methods
(substitute() and expand()) that handle the expansion.
In addition, however, this class is used to track the state of
the result(s) we're gathering so we can do the appropriate thing
whenever we have to append another word to the result--start a new
line, start a new word, append to the current word, etc. We do
this by setting the "append" attribute to the right method so
that our wrapper methods only need ever call ListSubber.append(),
and the rest of the object takes care of doing the right thing
internally.
"""
def __init__(self, env, mode, conv, gvars):
collections.UserList.__init__(self, [])
self.env = env
self.mode = mode
self.conv = conv
self.gvars = gvars
if self.mode == SUBST_RAW:
self.add_strip = lambda x: self.append(x)
else:
self.add_strip = lambda x: None
self.in_strip = None
self.next_line()
def expand(self, s, lvars, within_list):
"""Expand a single "token" as necessary, appending the
expansion to the current result.
This handles expanding different types of things (strings,
lists, callables) appropriately. It calls the wrapper
substitute() method to re-expand things as necessary, so that
the results of expansions of side-by-side strings still get
re-evaluated separately, not smushed together.
"""
if is_String(s):
try:
s0, s1 = s[:2]
except (IndexError, ValueError):
self.append(s)
return
if s0 != '$':
self.append(s)
return
if s1 == '$':
self.append('$')
elif s1 == '(':
self.open_strip('$(')
elif s1 == ')':
self.close_strip('$)')
else:
key = s[1:]
if key[0] == '{' or key.find('.') >= 0:
if key[0] == '{':
key = key[1:-1]
try:
s = eval(key, self.gvars, lvars)
except KeyboardInterrupt:
raise
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:
return
# Before re-expanding the result, handle
# recursive expansion by copying the local
# variable dictionary and overwriting a null
# string for the value of the variable name
# we just expanded.
lv = lvars.copy()
var = key.split('.')[0]
lv[var] = ''
self.substitute(s, lv, 0)
self.this_word()
elif is_Sequence(s):
for a in s:
self.substitute(a, lvars, 1)
self.next_word()
elif callable(s):
try:
s = s(target=lvars['TARGETS'],
source=lvars['SOURCES'],
env=self.env,
for_signature=(self.mode != SUBST_CMD))
except TypeError:
# This probably indicates that it's a callable
# object that doesn't match our calling arguments
# (like an Action).
if self.mode == SUBST_RAW:
self.append(s)
return
s = self.conv(s)
self.substitute(s, lvars, within_list)
elif s is None:
self.this_word()
else:
self.append(s)
def substitute(self, args, lvars, within_list):
"""Substitute expansions in an argument or list of arguments.
This serves as a wrapper for splitting up a string into
separate tokens.
"""
if is_String(args) and not isinstance(args, CmdStringHolder):
args = str(args) # In case it's a UserString.
args = _separate_args.findall(args)
for a in args:
if a[0] in ' \t\n\r\f\v':
if '\n' in a:
self.next_line()
elif within_list:
self.append(a)
else:
self.next_word()
else:
self.expand(a, lvars, within_list)
else:
self.expand(args, lvars, within_list)
def next_line(self):
"""Arrange for the next word to start a new line. This
is like starting a new word, except that we have to append
another line to the result."""
collections.UserList.append(self, [])
self.next_word()
def this_word(self):
"""Arrange for the next word to append to the end of the
current last word in the result."""
self.append = self.add_to_current_word
def next_word(self):
"""Arrange for the next word to start a new word."""
self.append = self.add_new_word
def add_to_current_word(self, x):
"""Append the string x to the end of the current last word
in the result. If that is not possible, then just add
it as a new word. Make sure the entire concatenated string
inherits the object attributes of x (in particular, the
escape function) by wrapping it as CmdStringHolder."""
if not self.in_strip or self.mode != SUBST_SIG:
try:
current_word = self[-1][-1]
except IndexError:
self.add_new_word(x)
else:
# All right, this is a hack and it should probably
# be refactored out of existence in the future.
# The issue is that we want to smoosh words together
# and make one file name that gets escaped if
# we're expanding something like foo$EXTENSION,
# but we don't want to smoosh them together if
# it's something like >$TARGET, because then we'll
# treat the '>' like it's part of the file name.
# So for now, just hard-code looking for the special
# command-line redirection characters...
try:
last_char = str(current_word)[-1]
except IndexError:
last_char = '\0'
if last_char in '<>|':
self.add_new_word(x)
else:
y = current_word + x
# We used to treat a word appended to a literal
# as a literal itself, but this caused problems
# with interpreting quotes around space-separated
# targets on command lines. Removing this makes
# none of the "substantive" end-to-end tests fail,
# so we'll take this out but leave it commented
# for now in case there's a problem not covered
# by the test cases and we need to resurrect this.
#literal1 = self.literal(self[-1][-1])
#literal2 = self.literal(x)
y = self.conv(y)
if is_String(y):
#y = CmdStringHolder(y, literal1 or literal2)
y = CmdStringHolder(y, None)
self[-1][-1] = y
def add_new_word(self, x):
if not self.in_strip or self.mode != SUBST_SIG:
literal = self.literal(x)
x = self.conv(x)
if is_String(x):
x = CmdStringHolder(x, literal)
self[-1].append(x)
self.append = self.add_to_current_word
def literal(self, x):
try:
l = x.is_literal
except AttributeError:
return None
else:
return l()
def open_strip(self, x):
"""Handle the "open strip" $( token."""
self.add_strip(x)
self.in_strip = 1
def close_strip(self, x):
"""Handle the "close strip" $) token."""
self.add_strip(x)
self.in_strip = None
if conv is None: if conv is None:
conv = _strconv[mode] 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # a copy of this software and associated documentation files (the
@ -20,17 +20,15 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
import sys import sys
__doc__ = """ __doc__ = """
Generic Taskmaster module for the SCons build engine. Generic Taskmaster module for the SCons build engine.
===================================================== =====================================================
This module contains the primary interface(s) between a wrapping user This module contains the primary interface(s) between a wrapping user
interface and the SCons build engine. There are two key classes here: interface and the SCons build engine. There are two key classes here:
Taskmaster Taskmaster
---------- ----------
This is the main engine for walking the dependency graph and This is the main engine for walking the dependency graph and
@ -54,12 +52,13 @@ __doc__ = """
target(s) that it decides need to be evaluated and/or built. 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 operator
import sys import sys
import traceback import traceback
from abc import ABC, abstractmethod
from itertools import chain
import SCons.Errors import SCons.Errors
import SCons.Node import SCons.Node
@ -81,7 +80,7 @@ print_prepare = 0 # set by option --debug=prepare
CollectStats = None CollectStats = None
class Stats(object): class Stats:
""" """
A simple class for holding statistics about the disposition of a A simple class for holding statistics about the disposition of a
Node by the Taskmaster. If we're collecting statistics, each Node 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)) print((fmt % n.attributes.stats.__dict__) + str(n))
class Task(ABC):
class Task(object): """ SCons build engine abstract task class.
"""
Default SCons build engine task.
This controls the interaction of the actual building of node This controls the interaction of the actual building of node
and the rest of the engine. and the rest of the engine.
@ -171,7 +168,7 @@ class Task(object):
""" """
global print_prepare global print_prepare
T = self.tm.trace 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 # Now that it's the appropriate time, give the TaskMaster a
# chance to raise any exceptions it encountered while preparing # chance to raise any exceptions it encountered while preparing
@ -212,17 +209,9 @@ class Task(object):
""" """
return self.node return self.node
@abstractmethod
def needs_execute(self): def needs_execute(self):
# TODO(deprecate): "return True" is the old default behavior; return
# 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
def execute(self): def execute(self):
""" """
@ -233,7 +222,7 @@ class Task(object):
prepare(), executed() or failed(). prepare(), executed() or failed().
""" """
T = self.tm.trace 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: try:
cached_targets = [] cached_targets = []
@ -399,7 +388,7 @@ class Task(object):
""" """
global print_prepare global print_prepare
T = self.tm.trace 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.node))
self.out_of_date = [] self.out_of_date = []
@ -447,7 +436,7 @@ class Task(object):
that can be put back on the candidates list. that can be put back on the candidates list.
""" """
T = self.tm.trace 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 # We may have built multiple targets, some of which may have
# common parents waiting for this build. Count up how many # 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 # A node can only be in the pending_children set if it has
# some waiting_parents. # some waiting_parents.
if t.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, t,
'removing')) 'removing'))
pending_children.discard(t) pending_children.discard(t)
for p in t.waiting_parents: for p in t.waiting_parents:
parents[p] = parents.get(p, 0) + 1 parents[p] = parents.get(p, 0) + 1
t.waiting_parents = set()
for t in targets: for t in targets:
if t.side_effects is not None: if t.side_effects is not None:
for s in t.side_effects: for s in t.side_effects:
if s.get_state() == NODE_EXECUTING: if s.get_state() == NODE_EXECUTING:
s.set_state(NODE_NO_STATE) 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: for p in s.waiting_parents:
parents[p] = parents.get(p, 0) + 1 parents[p] = parents.get(p, 0) + 1
s.waiting_parents = set()
for p in s.waiting_s_e: for p in s.waiting_s_e:
if p.ref_count == 0: if p.ref_count == 0:
self.tm.candidates.append(p) self.tm.candidates.append(p)
for p, subtract in parents.items(): for p, subtract in parents.items():
p.ref_count = p.ref_count - subtract 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, p,
'adjusted parent ref count')) 'adjusted parent ref count'))
if p.ref_count == 0: if p.ref_count == 0:
@ -542,19 +540,16 @@ class Task(object):
try: try:
exc_type, exc_value, exc_traceback = exc exc_type, exc_value, exc_traceback = exc
except ValueError: except ValueError:
exc_type, exc_value = exc exc_type, exc_value = exc # pylint: disable=unbalanced-tuple-unpacking
exc_traceback = None exc_traceback = None
# raise exc_type(exc_value).with_traceback(exc_traceback) # raise exc_type(exc_value).with_traceback(exc_traceback)
if sys.version_info[0] == 2: if isinstance(exc_value, Exception): #hasattr(exc_value, 'with_traceback'):
exec("raise exc_type, exc_value, exc_traceback") # If exc_value is an exception, then just reraise
else: # sys.version_info[0] == 3: raise exc_value.with_traceback(exc_traceback)
if isinstance(exc_value, Exception): #hasattr(exc_value, 'with_traceback'): else:
# If exc_value is an exception, then just reraise # else we'll create an exception using the value and raise that
exec("raise exc_value.with_traceback(exc_traceback)") raise exc_type(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 e.__class__, e.__class__(e), sys.exc_info()[2] # raise e.__class__, e.__class__(e), sys.exc_info()[2]
@ -573,7 +568,7 @@ class AlwaysTask(Task):
dependencies) can use this as follows: dependencies) can use this as follows:
class MyTaskSubclass(SCons.Taskmaster.Task): class MyTaskSubclass(SCons.Taskmaster.Task):
needs_execute = SCons.Taskmaster.Task.execute_always needs_execute = SCons.Taskmaster.AlwaysTask.needs_execute
""" """
return True return True
@ -601,7 +596,7 @@ def find_cycle(stack, visited):
return None return None
class Taskmaster(object): class Taskmaster:
""" """
The Taskmaster for walking the dependency DAG. The Taskmaster for walking the dependency DAG.
""" """
@ -783,12 +778,12 @@ class Taskmaster(object):
self.ready_exc = None self.ready_exc = None
T = self.trace 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: while True:
node = self.next_candidate() node = self.next_candidate()
if node is None: 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 return None
node = node.disambiguate() node = node.disambiguate()
@ -811,7 +806,7 @@ class Taskmaster(object):
else: else:
S = None 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: if state == NODE_NO_STATE:
# Mark this node as being on the execution stack: # Mark this node as being on the execution stack:
@ -819,7 +814,7 @@ class Taskmaster(object):
elif state > NODE_PENDING: elif state > NODE_PENDING:
# Skip this node if it has already been evaluated: # Skip this node if it has already been evaluated:
if S: S.already_handled = S.already_handled + 1 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 continue
executor = node.get_executor() executor = node.get_executor()
@ -850,7 +845,7 @@ class Taskmaster(object):
for child in chain(executor.get_all_prerequisites(), children): for child in chain(executor.get_all_prerequisites(), children):
childstate = child.get_state() 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: if childstate == NODE_NO_STATE:
children_not_visited.append(child) children_not_visited.append(child)
@ -865,8 +860,10 @@ class Taskmaster(object):
# These nodes have not even been visited yet. Add # These nodes have not even been visited yet. Add
# them to the list so that on some next pass we can # them to the list so that on some next pass we can
# take a stab at evaluating them (or their children). # take a stab at evaluating them (or their children).
children_not_visited.reverse() if children_not_visited:
self.candidates.extend(self.order(children_not_visited)) if len(children_not_visited) > 1:
children_not_visited.reverse()
self.candidates.extend(self.order(children_not_visited))
# if T and children_not_visited: # if T and children_not_visited:
# T.write(self.trace_message(' adding to candidates: %s' % map(str, children_not_visited))) # T.write(self.trace_message(' adding to candidates: %s' % map(str, children_not_visited)))
@ -909,7 +906,7 @@ class Taskmaster(object):
# count so we can be put back on the list for # count so we can be put back on the list for
# re-evaluation when they've all finished. # re-evaluation when they've all finished.
node.ref_count = node.ref_count + child.add_to_waiting_parents(node) 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))))) (self.trace_node(node), repr(str(child)))))
if T: if T:
@ -935,7 +932,7 @@ class Taskmaster(object):
# The default when we've gotten through all of the checks above: # The default when we've gotten through all of the checks above:
# this node is ready to be built. # this node is ready to be built.
if S: S.build = S.build + 1 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))) self.trace_node(node)))
# For debugging only: # 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 from SCons.Tool.PharLapCommon import addPharLapPaths
import SCons.Util import SCons.Util

View file

@ -1,5 +1,3 @@
from __future__ import print_function
"""SCons.Tool.DCommon """SCons.Tool.DCommon
Common code for the various D tools. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# #
from __future__ import print_function __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__revision__ = "src/engine/SCons/Tool/FortranCommon.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import re import re
import os.path import os.path
@ -64,7 +62,8 @@ def _fortranEmitter(target, source, env):
if not node.exists() and not node.is_derived(): if not node.exists() and not node.is_derived():
print("Could not locate " + str(node.name)) print("Could not locate " + str(node.name))
return ([], []) 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) cre = re.compile(mod_regex,re.M)
# Retrieve all USE'd module names # Retrieve all USE'd module names
modules = cre.findall(node.get_text_contents()) modules = cre.findall(node.get_text_contents())

View file

@ -3,8 +3,8 @@
Used by several tools of `gettext` toolset. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including # "Software"), to deal in the Software without restriction, including
@ -12,10 +12,10 @@ Used by several tools of `gettext` toolset.
# distribute, sublicense, and/or sell copies of the Software, and to # distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to # permit persons to whom the Software is furnished to do so, subject to
# the following conditions: # the following conditions:
# #
# The above copyright notice and this permission notice shall be included # The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software. # in all copies or substantial portions of the Software.
# #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@ -24,7 +24,7 @@ Used by several tools of `gettext` toolset.
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__revision__ = "src/engine/SCons/Tool/GettextCommon.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Warnings import SCons.Warnings
import re import re
@ -69,9 +69,9 @@ SCons.Warnings.enableWarningClass(MsgfmtNotFound)
############################################################################# #############################################################################
############################################################################# #############################################################################
class _POTargetFactory(object): class _POTargetFactory:
""" A factory of `PO` target files. """ A factory of `PO` target files.
Factory defaults differ from these of `SCons.Node.FS.FS`. We set `precious` Factory defaults differ from these of `SCons.Node.FS.FS`. We set `precious`
(this is required by builders and actions gettext) and `noclean` flags by (this is required by builders and actions gettext) and `noclean` flags by
default for all produced nodes. default for all produced nodes.
@ -80,9 +80,9 @@ class _POTargetFactory(object):
def __init__(self, env, nodefault=True, alias=None, precious=True def __init__(self, env, nodefault=True, alias=None, precious=True
, noclean=True): , noclean=True):
""" Object constructor. """ Object constructor.
**Arguments** **Arguments**
- *env* (`SCons.Environment.Environment`) - *env* (`SCons.Environment.Environment`)
- *nodefault* (`boolean`) - if `True`, produced nodes will be ignored - *nodefault* (`boolean`) - if `True`, produced nodes will be ignored
from default target `'.'` from default target `'.'`
@ -160,13 +160,13 @@ from SCons.Builder import BuilderBase
############################################################################# #############################################################################
class _POFileBuilder(BuilderBase): class _POFileBuilder(BuilderBase):
""" `PO` file builder. """ `PO` file builder.
This is multi-target single-source builder. In typical situation the source This is multi-target single-source builder. In typical situation the source
is single `POT` file, e.g. `messages.pot`, and there are multiple `PO` is single `POT` file, e.g. `messages.pot`, and there are multiple `PO`
targets to be updated from this `POT`. We must run targets to be updated from this `POT`. We must run
`SCons.Builder.BuilderBase._execute()` separatelly for each target to track `SCons.Builder.BuilderBase._execute()` separatelly for each target to track
dependencies separatelly for each target file. dependencies separatelly for each target file.
**NOTE**: if we call `SCons.Builder.BuilderBase._execute(.., target, ...)` **NOTE**: if we call `SCons.Builder.BuilderBase._execute(.., target, ...)`
with target being list of all targets, all targets would be rebuilt each time with target being list of all targets, all targets would be rebuilt each time
one of the targets from this list is missing. This would happen, for example, one of the targets from this list is missing. This would happen, for example,
@ -198,29 +198,29 @@ class _POFileBuilder(BuilderBase):
# After that it calls emitter (which is quite too late). The emitter is # After that it calls emitter (which is quite too late). The emitter is
# also called in each iteration, what makes things yet worse. # also called in each iteration, what makes things yet worse.
def __init__(self, env, **kw): def __init__(self, env, **kw):
if not 'suffix' in kw: if 'suffix' not in kw:
kw['suffix'] = '$POSUFFIX' kw['suffix'] = '$POSUFFIX'
if not 'src_suffix' in kw: if 'src_suffix' not in kw:
kw['src_suffix'] = '$POTSUFFIX' kw['src_suffix'] = '$POTSUFFIX'
if not 'src_builder' in kw: if 'src_builder' not in kw:
kw['src_builder'] = '_POTUpdateBuilder' kw['src_builder'] = '_POTUpdateBuilder'
if not 'single_source' in kw: if 'single_source' not in kw:
kw['single_source'] = True kw['single_source'] = True
alias = None alias = None
if 'target_alias' in kw: if 'target_alias' in kw:
alias = kw['target_alias'] alias = kw['target_alias']
del 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 kw['target_factory'] = _POTargetFactory(env, alias=alias).File
BuilderBase.__init__(self, **kw) BuilderBase.__init__(self, **kw)
def _execute(self, env, target, source, *args, **kw): def _execute(self, env, target, source, *args, **kw):
""" Execute builder's actions. """ Execute builder's actions.
Here we append to `target` the languages read from `$LINGUAS_FILE` and Here we append to `target` the languages read from `$LINGUAS_FILE` and
apply `SCons.Builder.BuilderBase._execute()` separatelly to each target. apply `SCons.Builder.BuilderBase._execute()` separatelly to each target.
The arguments and return value are same as for The arguments and return value are same as for
`SCons.Builder.BuilderBase._execute()`. `SCons.Builder.BuilderBase._execute()`.
""" """
import SCons.Util import SCons.Util
import SCons.Node import SCons.Node
@ -269,10 +269,10 @@ 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 """ Callable object, which returns pathnames relative to SCons current
working directory. working directory.
It seems like `SCons.Node.FS.Base.get_path()` returns absolute paths It seems like `SCons.Node.FS.Base.get_path()` returns absolute paths
for nodes that are outside of current working directory (`env.fs.getcwd()`). for nodes that are outside of current working directory (`env.fs.getcwd()`).
Here, we often have `SConscript`, `POT` and `PO` files within `po/` Here, we often have `SConscript`, `POT` and `PO` files within `po/`
@ -285,17 +285,17 @@ class RPaths(object):
the references would be correct only on the machine, where `POT` file was the references would be correct only on the machine, where `POT` file was
recently re-created. For such reason, we need a function, which always recently re-created. For such reason, we need a function, which always
returns relative paths. This is the purpose of `RPaths` callable object. returns relative paths. This is the purpose of `RPaths` callable object.
The `__call__` method returns paths relative to current working directory, but The `__call__` method returns paths relative to current working directory, but
we assume, that *xgettext(1)* is run from the directory, where target file is we assume, that *xgettext(1)* is run from the directory, where target file is
going to be created. going to be created.
Note, that this may not work for files distributed over several hosts or Note, that this may not work for files distributed over several hosts or
across different drives on windows. We assume here, that single local across different drives on windows. We assume here, that single local
filesystem holds both source files and target `POT` templates. filesystem holds both source files and target `POT` templates.
Intended use of `RPaths` - in `xgettext.py`:: Intended use of `RPaths` - in `xgettext.py`::
def generate(env): def generate(env):
from GettextCommon import RPaths from GettextCommon import RPaths
... ...
@ -318,9 +318,9 @@ class RPaths(object):
def __init__(self, env): def __init__(self, env):
""" Initialize `RPaths` callable object. """ Initialize `RPaths` callable object.
**Arguments**: **Arguments**:
- *env* - a `SCons.Environment.Environment` object, defines *current - *env* - a `SCons.Environment.Environment` object, defines *current
working dir*. working dir*.
""" """
@ -329,16 +329,16 @@ class RPaths(object):
# FIXME: I'm not sure, how it should be implemented (what the *args are in # FIXME: I'm not sure, how it should be implemented (what the *args are in
# general, what is **kw). # general, what is **kw).
def __call__(self, nodes, *args, **kw): def __call__(self, nodes, *args, **kw):
""" Return nodes' paths (strings) relative to current working directory. """ Return nodes' paths (strings) relative to current working directory.
**Arguments**: **Arguments**:
- *nodes* ([`SCons.Node.FS.Base`]) - list of nodes. - *nodes* ([`SCons.Node.FS.Base`]) - list of nodes.
- *args* - currently unused. - *args* - currently unused.
- *kw* - currently unused. - *kw* - currently unused.
**Returns**: **Returns**:
- Tuple of strings, which represent paths relative to current working - Tuple of strings, which represent paths relative to current working
directory (for given environment). directory (for given environment).
""" """
@ -390,7 +390,7 @@ def _detect_xgettext(env):
""" Detects *xgettext(1)* binary """ """ Detects *xgettext(1)* binary """
if 'XGETTEXT' in env: if 'XGETTEXT' in env:
return env['XGETTEXT'] return env['XGETTEXT']
xgettext = env.Detect('xgettext'); xgettext = env.Detect('xgettext')
if xgettext: if xgettext:
return xgettext return xgettext
raise SCons.Errors.StopError(XgettextNotFound, "Could not detect xgettext") raise SCons.Errors.StopError(XgettextNotFound, "Could not detect xgettext")
@ -409,7 +409,7 @@ def _detect_msginit(env):
""" Detects *msginit(1)* program. """ """ Detects *msginit(1)* program. """
if 'MSGINIT' in env: if 'MSGINIT' in env:
return env['MSGINIT'] return env['MSGINIT']
msginit = env.Detect('msginit'); msginit = env.Detect('msginit')
if msginit: if msginit:
return msginit return msginit
raise SCons.Errors.StopError(MsginitNotFound, "Could not detect msginit") raise SCons.Errors.StopError(MsginitNotFound, "Could not detect msginit")
@ -428,7 +428,7 @@ def _detect_msgmerge(env):
""" Detects *msgmerge(1)* program. """ """ Detects *msgmerge(1)* program. """
if 'MSGMERGE' in env: if 'MSGMERGE' in env:
return env['MSGMERGE'] return env['MSGMERGE']
msgmerge = env.Detect('msgmerge'); msgmerge = env.Detect('msgmerge')
if msgmerge: if msgmerge:
return msgmerge return msgmerge
raise SCons.Errors.StopError(MsgmergeNotFound, "Could not detect msgmerge") raise SCons.Errors.StopError(MsgmergeNotFound, "Could not detect msgmerge")
@ -447,7 +447,7 @@ def _detect_msgfmt(env):
""" Detects *msgmfmt(1)* program. """ """ Detects *msgmfmt(1)* program. """
if 'MSGFMT' in env: if 'MSGFMT' in env:
return env['MSGFMT'] return env['MSGFMT']
msgfmt = env.Detect('msgfmt'); msgfmt = env.Detect('msgfmt')
if msgfmt: if msgfmt:
return msgfmt return msgfmt
raise SCons.Errors.StopError(MsgfmtNotFound, "Could not detect 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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
import os.path import os.path
import re import re
import glob
java_parsing = 1 java_parsing = 1
default_java_version = '1.4' 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: if java_parsing:
# Parse Java files for class names. # Parse Java files for class names.
# #
@ -55,17 +87,19 @@ if java_parsing:
# any alphanumeric token surrounded by angle brackets (generics); # any alphanumeric token surrounded by angle brackets (generics);
# the multi-line comment begin and end tokens /* and */; # the multi-line comment begin and end tokens /* and */;
# array declarations "[]". # array declarations "[]".
_reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"\{\}\;\.\(\)]|' + _reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"{\};.()]|' +
r'\d*\.\d*|[A-Za-z_][\w\$\.]*|<[A-Za-z_]\w+>|' + r'\d*\.\d*|[A-Za-z_][\w$.]*|<[A-Za-z_]\w+>|' +
r'/\*|\*/|\[\])') r'/\*|\*/|\[\])')
class OuterState(object):
class OuterState:
"""The initial state for parsing a Java file for classes, """The initial state for parsing a Java file for classes,
interfaces, and anonymous inner classes.""" interfaces, and anonymous inner classes."""
def __init__(self, version=default_java_version): 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', if version not in ('1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7',
'1.8', '5', '6'): '1.8', '5', '6', '9.0', '10.0', '11.0', '12.0'):
msg = "Java version %s not supported" % version msg = "Java version %s not supported" % version
raise NotImplementedError(msg) raise NotImplementedError(msg)
@ -115,8 +149,8 @@ if java_parsing:
ret = SkipState(1, self) ret = SkipState(1, self)
self.skipState = ret self.skipState = ret
return ret return ret
def __getAnonStack(self): def _getAnonStack(self):
return self.anonStacksStack[-1] return self.anonStacksStack[-1]
def openBracket(self): def openBracket(self):
@ -125,15 +159,16 @@ if java_parsing:
def closeBracket(self): def closeBracket(self):
self.brackets = self.brackets - 1 self.brackets = self.brackets - 1
if len(self.stackBrackets) and \ if len(self.stackBrackets) and \
self.brackets == self.stackBrackets[-1]: self.brackets == self.stackBrackets[-1]:
self.listOutputs.append('$'.join(self.listClasses)) self.listOutputs.append('$'.join(self.listClasses))
self.localClasses.pop() self.localClasses.pop()
self.listClasses.pop() self.listClasses.pop()
self.anonStacksStack.pop() self.anonStacksStack.pop()
self.stackBrackets.pop() self.stackBrackets.pop()
if len(self.stackAnonClassBrackets) and \ if len(self.stackAnonClassBrackets) and \
self.brackets == self.stackAnonClassBrackets[-1]: self.brackets == self.stackAnonClassBrackets[-1] and \
self.__getAnonStack().pop() self.version not in scopeStateVersions:
self._getAnonStack().pop()
self.stackAnonClassBrackets.pop() self.stackAnonClassBrackets.pop()
def parseToken(self, token): def parseToken(self, token):
@ -145,13 +180,13 @@ if java_parsing:
self.openBracket() self.openBracket()
elif token == '}': elif token == '}':
self.closeBracket() self.closeBracket()
elif token in [ '"', "'" ]: elif token in ['"', "'"]:
return IgnoreState(token, self) return IgnoreState(token, self)
elif token == "new": elif token == "new":
# anonymous inner class # anonymous inner class
if len(self.listClasses) > 0: if len(self.listClasses) > 0:
return self.__getAnonClassState() return self.__getAnonClassState()
return self.__getSkipState() # Skip the class name return self.__getSkipState() # Skip the class name
elif token in ['class', 'interface', 'enum']: elif token in ['class', 'interface', 'enum']:
if len(self.listClasses) == 0: if len(self.listClasses) == 0:
self.nextAnon = 1 self.nextAnon = 1
@ -171,28 +206,99 @@ if java_parsing:
if self.version in ('1.1', '1.2', '1.3', '1.4'): if self.version in ('1.1', '1.2', '1.3', '1.4'):
clazz = self.listClasses[0] clazz = self.listClasses[0]
self.listOutputs.append('%s$%d' % (clazz, self.nextAnon)) 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) self.stackAnonClassBrackets.append(self.brackets)
className = [] className = []
className.extend(self.listClasses) className.extend(self.listClasses)
self.__getAnonStack()[-1] = self.__getAnonStack()[-1] + 1 self._getAnonStack()[-1] = self._getAnonStack()[-1] + 1
for anon in self.__getAnonStack(): for anon in self._getAnonStack():
className.append(str(anon)) className.append(str(anon))
self.listOutputs.append('$'.join(className)) self.listOutputs.append('$'.join(className))
self.nextAnon = self.nextAnon + 1 self.nextAnon = self.nextAnon + 1
self.__getAnonStack().append(0) self._getAnonStack().append(0)
def setPackage(self, package): def setPackage(self, package):
self.package = 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.""" """A state that looks for anonymous inner classes."""
def __init__(self, old_state): def __init__(self, old_state):
# outer_state is always an instance of OuterState # outer_state is always an instance of OuterState
self.outer_state = old_state.outer_state self.outer_state = old_state.outer_state
self.old_state = old_state self.old_state = old_state
self.brace_level = 0 self.brace_level = 0
def parseToken(self, token): def parseToken(self, token):
# This is an anonymous class if and only if the next # This is an anonymous class if and only if the next
# non-whitespace token is a bracket. Everything between # non-whitespace token is a bracket. Everything between
@ -212,32 +318,40 @@ if java_parsing:
if token == 'new': if token == 'new':
# look further for anonymous inner class # look further for anonymous inner class
return SkipState(1, AnonClassState(self)) return SkipState(1, AnonClassState(self))
elif token in [ '"', "'" ]: elif token in ['"', "'"]:
return IgnoreState(token, self) return IgnoreState(token, self)
elif token == ')': elif token == ')':
self.brace_level = self.brace_level - 1 self.brace_level = self.brace_level - 1
return self return self
if token == '{': if token == '{':
self.outer_state.addAnonClass() 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) return self.old_state.parseToken(token)
class SkipState(object):
class SkipState:
"""A state that will skip a specified number of tokens before """A state that will skip a specified number of tokens before
reverting to the previous state.""" reverting to the previous state."""
def __init__(self, tokens_to_skip, old_state): def __init__(self, tokens_to_skip, old_state):
self.tokens_to_skip = tokens_to_skip self.tokens_to_skip = tokens_to_skip
self.old_state = old_state self.old_state = old_state
def parseToken(self, token): def parseToken(self, token):
self.tokens_to_skip = self.tokens_to_skip - 1 self.tokens_to_skip = self.tokens_to_skip - 1
if self.tokens_to_skip < 1: if self.tokens_to_skip < 1:
return self.old_state return self.old_state
return self return self
class ClassState(object):
class ClassState:
"""A state we go into when we hit a class or interface keyword.""" """A state we go into when we hit a class or interface keyword."""
def __init__(self, outer_state): def __init__(self, outer_state):
# outer_state is always an instance of OuterState # outer_state is always an instance of OuterState
self.outer_state = outer_state self.outer_state = outer_state
def parseToken(self, token): def parseToken(self, token):
# the next non-whitespace token should be the name of the class # the next non-whitespace token should be the name of the class
if token == '\n': if token == '\n':
@ -245,14 +359,14 @@ if java_parsing:
# If that's an inner class which is declared in a method, it # If that's an inner class which is declared in a method, it
# requires an index prepended to the class-name, e.g. # requires an index prepended to the class-name, e.g.
# 'Foo$1Inner' # '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 \ if self.outer_state.localClasses and \
self.outer_state.stackBrackets[-1] > \ self.outer_state.stackBrackets[-1] > \
self.outer_state.stackBrackets[-2]+1: self.outer_state.stackBrackets[-2] + 1:
locals = self.outer_state.localClasses[-1] locals = self.outer_state.localClasses[-1]
try: try:
idx = locals[token] idx = locals[token]
locals[token] = locals[token]+1 locals[token] = locals[token] + 1
except KeyError: except KeyError:
locals[token] = 1 locals[token] = 1
token = str(locals[token]) + token token = str(locals[token]) + token
@ -261,29 +375,39 @@ if java_parsing:
self.outer_state.anonStacksStack.append([0]) self.outer_state.anonStacksStack.append([0])
return self.outer_state return self.outer_state
class IgnoreState(object):
class IgnoreState:
"""A state that will ignore all tokens until it gets to a """A state that will ignore all tokens until it gets to a
specified token.""" specified token."""
def __init__(self, ignore_until, old_state): def __init__(self, ignore_until, old_state):
self.ignore_until = ignore_until self.ignore_until = ignore_until
self.old_state = old_state self.old_state = old_state
def parseToken(self, token): def parseToken(self, token):
if self.ignore_until == token: if self.ignore_until == token:
return self.old_state return self.old_state
return self return self
class PackageState(object):
class PackageState:
"""The state we enter when we encounter the package keyword. """The state we enter when we encounter the package keyword.
We assume the next token will be the package name.""" We assume the next token will be the package name."""
def __init__(self, outer_state): def __init__(self, outer_state):
# outer_state is always an instance of OuterState # outer_state is always an instance of OuterState
self.outer_state = outer_state self.outer_state = outer_state
def parseToken(self, token): def parseToken(self, token):
self.outer_state.setPackage(token) self.outer_state.setPackage(token)
return self.outer_state return self.outer_state
def parse_java_file(fn, version=default_java_version): 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): def parse_java(contents, version=default_java_version, trace=None):
"""Parse a .java file and return a double of package directory, """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 is that the file name matches the public class name, and that
the path to the file is the same as the package name. 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: # Local Variables:
# tab-width:4 # 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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__ = """ __doc__ = """
Common functions for Microsoft Visual Studio and Visual C/C++. 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, \ from SCons.Tool.MSCommon.vc import msvc_exists, \
msvc_setup_env, \ msvc_setup_env, \
msvc_setup_env_once, \ 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, \ from SCons.Tool.MSCommon.vs import get_default_version, \
get_vs_by_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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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. __doc__ = """Module to define supported Windows chip architectures.
""" """
import os import os
class ArchDefinition(object): class ArchDefinition:
""" """
A class for defining architecture-specific settings and logic. A class for defining architecture-specific settings and logic.
""" """
@ -37,22 +37,22 @@ class ArchDefinition(object):
self.synonyms = synonyms self.synonyms = synonyms
SupportedArchitectureList = [ SupportedArchitectureList = [
ArchitectureDefinition( ArchDefinition(
'x86', 'x86',
['i386', 'i486', 'i586', 'i686'], ['i386', 'i486', 'i586', 'i686'],
), ),
ArchitectureDefinition( ArchDefinition(
'x86_64', 'x86_64',
['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'], ['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'],
), ),
ArchitectureDefinition( ArchDefinition(
'ia64', 'ia64',
['IA64'], ['IA64'],
), ),
ArchitectureDefinition( ArchDefinition(
'arm', 'arm',
['ARM'], ['ARM'],
), ),

View file

@ -2,7 +2,7 @@
Common helper functions for working with the Microsoft tool chain. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# #
from __future__ import print_function __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__revision__ = "src/engine/SCons/Tool/MSCommon/common.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import copy import copy
import json
import os import os
import subprocess
import re import re
import subprocess
import sys
import SCons.Util 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') LOGFILE = os.environ.get('SCONS_MSCOMMON_DEBUG')
if LOGFILE == '-': if LOGFILE == '-':
def debug(message): def debug(message):
print(message) print(message)
elif LOGFILE: elif LOGFILE:
try: import logging
import logging logging.basicConfig(
except ImportError: # This looks like:
debug = lambda message: open(LOGFILE, 'a').write(message + '\n') # 00109ms:MSCommon/vc.py:find_vc_pdir#447:
else: format=(
logging.basicConfig(filename=LOGFILE, level=logging.DEBUG) '%(relativeCreated)05dms'
debug = logging.debug ':MSCommon/%(filename)s'
':%(funcName)s'
'#%(lineno)s'
':%(message)s: '
),
filename=LOGFILE,
level=logging.DEBUG)
debug = logging.getLogger(name=__name__).debug
else: 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 _is_win64 = None
def is_win64(): def is_win64():
"""Return true if running on windows 64 bits. """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): def read_reg(value, hkroot=SCons.Util.HKEY_LOCAL_MACHINE):
return SCons.Util.RegGetValue(hkroot, value)[0] return SCons.Util.RegGetValue(hkroot, value)[0]
def has_reg(value): def has_reg(value):
"""Return True if the given key exists in HKEY_LOCAL_MACHINE, False """Return True if the given key exists in HKEY_LOCAL_MACHINE, False
otherwise.""" otherwise."""
@ -99,6 +142,7 @@ def has_reg(value):
# Functions for fetching environment variable settings from batch files. # Functions for fetching environment variable settings from batch files.
def normalize_env(env, keys, force=False): def normalize_env(env, keys, force=False):
"""Given a dictionary representing a shell environment, add the variables """Given a dictionary representing a shell environment, add the variables
from os.environ needed for the processing of .bat files; the keys are 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.""" Note: the environment is copied."""
normenv = {} normenv = {}
if env: if env:
for k in list(env.keys()): for k, v in env.items():
normenv[k] = copy.deepcopy(env[k]) normenv[k] = copy.deepcopy(v)
for k in keys: 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] normenv[k] = os.environ[k]
# This shouldn't be necessary, since the default environment should include system32, # add some things to PATH to prevent problems:
# but keep this here to be safe, since it's needed to find reg.exe which the MSVC # Shouldn't be necessary to add system32, since the default environment
# bat scripts use. # should include it, but keep this here to be safe (needed for reg.exe)
sys32_dir = os.path.join(os.environ.get("SystemRoot", sys32_dir = os.path.join(
os.environ.get("windir", r"C:\Windows\system32")), os.environ.get("SystemRoot", os.environ.get("windir", r"C:\Windows")), "System32"
"System32") )
if sys32_dir not in normenv["PATH"]:
if sys32_dir not in normenv['PATH']: normenv["PATH"] = normenv["PATH"] + os.pathsep + sys32_dir
normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_dir
# Without Wbem in PATH, vcvarsall.bat has a "'wmic' is not recognized" # Without Wbem in PATH, vcvarsall.bat has a "'wmic' is not recognized"
# error starting with Visual Studio 2017, although the script still # 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']: if sys32_wbem_dir not in normenv['PATH']:
normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_wbem_dir 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 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.""" """Parse the output of given bat file, with given args."""
if env is None: if env is None:
# Create a blank environment, for use in launching the tools # Create a blank environment, for use in launching the tools
env = SCons.Environment.Environment(tools=[]) env = SCons.Environment.Environment(tools=[])
# TODO: This is a hard-coded list of the variables that (may) need # TODO: Hard-coded list of the variables that (may) need to be
# to be imported from os.environ[] for v[sc]*vars*.bat file # imported from os.environ[] for the chain of development batch
# execution to work. This list should really be either directly # files to execute correctly. One call to vcvars*.bat may
# controlled by vc.py, or else derived from the common_tools_var # end up running a dozen or more scripts, changes not only with
# settings in vs.py. # 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 = [ vs_vc_vars = [
'COMSPEC', 'COMSPEC', # path to "shell"
# VS100 and VS110: Still set, but modern MSVC setup scripts will 'VS160COMNTOOLS', # path to common tools for given version
# discard these if registry has values. However Intel compiler setup 'VS150COMNTOOLS',
# script still requires these as of 2013/2014.
'VS140COMNTOOLS', 'VS140COMNTOOLS',
'VS120COMNTOOLS', 'VS120COMNTOOLS',
'VS110COMNTOOLS', 'VS110COMNTOOLS',
@ -167,6 +222,8 @@ def get_output(vcbat, args = None, env = None):
'VS71COMNTOOLS', 'VS71COMNTOOLS',
'VS70COMNTOOLS', 'VS70COMNTOOLS',
'VS60COMNTOOLS', 'VS60COMNTOOLS',
'VSCMD_DEBUG', # enable logging and other debug aids
'VSCMD_SKIP_SENDTELEMETRY',
] ]
env['ENV'] = normalize_env(env['ENV'], vs_vc_vars, force=False) 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 # Use the .stdout and .stderr attributes directly because the
# .communicate() method uses the threading module on Windows # .communicate() method uses the threading module on Windows
# and won't work under Pythons not built with threading. # and won't work under Pythons not built with threading.
stdout = popen.stdout.read() with popen.stdout:
stderr = popen.stderr.read() stdout = popen.stdout.read()
with popen.stderr:
stderr = popen.stderr.read()
# Extra debug logic, uncomment if necessary # Extra debug logic, uncomment if necessary
# debug('get_output():stdout:%s'%stdout) # debug('stdout:%s' % stdout)
# debug('get_output():stderr:%s'%stderr) # 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: if stderr:
# TODO: find something better to do with stderr; # TODO: find something better to do with stderr;
# this at least prevents errors from getting swallowed. # this at least prevents errors from getting swallowed.
import sys sys.stderr.write(stderr.decode(OEM))
sys.stderr.write(stderr)
if popen.wait() != 0: if popen.wait() != 0:
raise IOError(stderr.decode("mbcs")) raise IOError(stderr.decode(OEM))
output = stdout.decode("mbcs") return stdout.decode(OEM)
return output
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 Parse output from running visual c++/studios vcvarsall.bat and running set
To capture the values listed in keep To capture the values listed in keep
""" """
# dkeep is a dict associating key: path_list, where key is one item from # 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]) dkeep = dict([(i, []) for i in keep])
# rdk will keep the regex to match the .bat file output line starts # 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__revision__ = "src/engine/SCons/Tool/MSCommon/netframework.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """ __doc__ = """
""" """
@ -68,7 +68,7 @@ def query_versions():
# sequence comparison in python is lexicographical # sequence comparison in python is lexicographical
# which is exactly what we want. # which is exactly what we want.
# Note we sort backwards so the highest version is first. # Note we sort backwards so the highest version is first.
return cmp(bbl,aal) return (aal > bbl) - (aal < bbl)
versions.sort(versrt) versions.sort(versrt)
else: 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 __doc__ = """Module to detect the Platform/Windows SDK
@ -33,9 +33,7 @@ import os
import SCons.Errors import SCons.Errors
import SCons.Util import SCons.Util
from . import common from .common import debug, read_reg
debug = common.debug
# SDK Checks. This is of course a mess as everything else on MS platforms. Here # SDK Checks. This is of course a mess as everything else on MS platforms. Here
# is what we do to detect the SDK: # is what we do to detect the SDK:
@ -58,7 +56,7 @@ _CURINSTALLED_SDK_HKEY_ROOT = \
r"Software\Microsoft\Microsoft SDKs\Windows\CurrentInstallFolder" r"Software\Microsoft\Microsoft SDKs\Windows\CurrentInstallFolder"
class SDKDefinition(object): class SDKDefinition:
""" """
An abstract base class for trying to find installed SDK directories. 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)) debug('find_sdk_dir(): checking registry:{}'.format(hkey))
try: try:
sdk_dir = common.read_reg(hkey) sdk_dir = read_reg(hkey)
except SCons.Util.WinError as e: except SCons.Util.WinError as e:
debug('find_sdk_dir(): no SDK registry key {}'.format(repr(hkey))) debug('find_sdk_dir(): no SDK registry key {}'.format(repr(hkey)))
return None return None
@ -110,19 +108,19 @@ class SDKDefinition(object):
""" Return the script to initialize the VC compiler installed by SDK """ 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 # No cross tools needed compiling 32 bits on 64 bit machine
host_arch=target_arch host_arch=target_arch
arch_string=target_arch arch_string=target_arch
if (host_arch != target_arch): if host_arch != target_arch:
arch_string='%s_%s'%(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, host_arch,
target_arch)) target_arch))
file=self.vc_setup_scripts.get(arch_string,None) 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 return file
class WindowsSDK(SDKDefinition): 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. # If you update this list, update the documentation in Tool/mssdk.xml.
SupportedSDKList = [ 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', WindowsSDK('10.0',
sanity_check_file=r'bin\SetEnv.Cmd', sanity_check_file=r'bin\SetEnv.Cmd',
include_subdir='include', include_subdir='include',
@ -276,14 +284,14 @@ InstalledSDKMap = None
def get_installed_sdks(): def get_installed_sdks():
global InstalledSDKList global InstalledSDKList
global InstalledSDKMap global InstalledSDKMap
debug('sdk.py:get_installed_sdks()') debug('get_installed_sdks()')
if InstalledSDKList is None: if InstalledSDKList is None:
InstalledSDKList = [] InstalledSDKList = []
InstalledSDKMap = {} InstalledSDKMap = {}
for sdk in SupportedSDKList: 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(): if sdk.get_sdk_dir():
debug('MSCommon/sdk.py:found SDK %s' % sdk.version) debug('found SDK %s' % sdk.version)
InstalledSDKList.append(sdk) InstalledSDKList.append(sdk)
InstalledSDKMap[sdk.version] = sdk InstalledSDKMap[sdk.version] = sdk
return InstalledSDKList return InstalledSDKList
@ -336,13 +344,13 @@ def get_default_sdk():
return InstalledSDKList[0] return InstalledSDKList[0]
def mssdk_setup_env(env): def mssdk_setup_env(env):
debug('sdk.py:mssdk_setup_env()') debug('mssdk_setup_env()')
if 'MSSDK_DIR' in env: if 'MSSDK_DIR' in env:
sdk_dir = env['MSSDK_DIR'] sdk_dir = env['MSSDK_DIR']
if sdk_dir is None: if sdk_dir is None:
return return
sdk_dir = env.subst(sdk_dir) 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: elif 'MSSDK_VERSION' in env:
sdk_version = env['MSSDK_VERSION'] sdk_version = env['MSSDK_VERSION']
if sdk_version is None: if sdk_version is None:
@ -354,22 +362,22 @@ def mssdk_setup_env(env):
msg = "SDK version %s is not installed" % sdk_version msg = "SDK version %s is not installed" % sdk_version
raise SCons.Errors.UserError(msg) raise SCons.Errors.UserError(msg)
sdk_dir = mssdk.get_sdk_dir() 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: elif 'MSVS_VERSION' in env:
msvs_version = env['MSVS_VERSION'] 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: 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 return
msvs_version = env.subst(msvs_version) msvs_version = env.subst(msvs_version)
from . import vs from . import vs
msvs = vs.get_vs_by_version(msvs_version) 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: 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 return
sdk_version = msvs.sdk_version 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: if not sdk_version:
return return
mssdk = get_sdk_by_version(sdk_version) mssdk = get_sdk_by_version(sdk_version)
@ -378,13 +386,13 @@ def mssdk_setup_env(env):
if not mssdk: if not mssdk:
return return
sdk_dir = mssdk.get_sdk_dir() 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: else:
mssdk = get_default_sdk() mssdk = get_default_sdk()
if not mssdk: if not mssdk:
return return
sdk_dir = mssdk.get_sdk_dir() 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) 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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++ __doc__ = """Module to detect Visual Studio and/or Visual C/C++
""" """
@ -40,7 +40,7 @@ from .common import debug, \
import SCons.Tool.MSCommon.vc import SCons.Tool.MSCommon.vc
class VisualStudio(object): class VisualStudio:
""" """
An abstract base class for trying to find installed versions of An abstract base class for trying to find installed versions of
Visual Studio. Visual Studio.
@ -55,62 +55,61 @@ class VisualStudio(object):
def find_batch_file(self): def find_batch_file(self):
vs_dir = self.get_vs_dir() vs_dir = self.get_vs_dir()
if not vs_dir: if not vs_dir:
debug('find_executable(): no vs_dir') debug('no vs_dir')
return None return None
batch_file = os.path.join(vs_dir, self.batch_file_path) batch_file = os.path.join(vs_dir, self.batch_file_path)
batch_file = os.path.normpath(batch_file) batch_file = os.path.normpath(batch_file)
if not os.path.isfile(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 None
return batch_file return batch_file
def find_vs_dir_by_vc(self): def find_vs_dir_by_vc(self, env):
SCons.Tool.MSCommon.vc.get_installed_vcs() SCons.Tool.MSCommon.vc.get_installed_vcs(env)
dir = SCons.Tool.MSCommon.vc.find_vc_pdir(self.vc_version) dir = SCons.Tool.MSCommon.vc.find_vc_pdir(env, self.vc_version)
if not dir: 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 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\\' root = 'Software\\'
if is_win64(): if is_win64():
root = root + 'Wow6432Node\\' root = root + 'Wow6432Node\\'
for key in self.hkeys: for key in self.hkeys:
if key=='use_dir': if key=='use_dir':
return self.find_vs_dir_by_vc() return self.find_vs_dir_by_vc(env)
key = root + key key = root + key
try: try:
comps = read_reg(key) comps = read_reg(key)
except SCons.Util.WinError as e: 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: else:
debug('find_vs_dir_by_reg(): found VS in registry: {}'.format(comps)) debug('found VS in registry: {}'.format(comps))
return comps return comps
return None return None
def find_vs_dir(self): def find_vs_dir(self, env):
""" Can use registry or location of VC to find vs dir """ 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 First try to find by registry, and if that fails find via VC dir
""" """
vs_dir=self.find_vs_dir_by_reg(env)
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()
if not vs_dir: 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 return None
executable = os.path.join(vs_dir, self.executable_path) executable = os.path.join(vs_dir, self.executable_path)
executable = os.path.normpath(executable) executable = os.path.normpath(executable)
if not os.path.isfile(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 None
return executable return executable
@ -122,21 +121,21 @@ class VisualStudio(object):
self._cache['batch_file'] = batch_file self._cache['batch_file'] = batch_file
return batch_file return batch_file
def get_executable(self): def get_executable(self, env=None):
try: try:
debug('get_executable using cache:%s'%self._cache['executable']) debug('using cache:%s'%self._cache['executable'])
return self._cache['executable'] return self._cache['executable']
except KeyError: except KeyError:
executable = self.find_executable() executable = self.find_executable(env)
self._cache['executable'] = executable self._cache['executable'] = executable
debug('get_executable not in cache:%s'%executable) debug('not in cache:%s'%executable)
return executable return executable
def get_vs_dir(self): def get_vs_dir(self, env):
try: try:
return self._cache['vs_dir'] return self._cache['vs_dir']
except KeyError: except KeyError:
vs_dir = self.find_vs_dir() vs_dir = self.find_vs_dir(env)
self._cache['vs_dir'] = vs_dir self._cache['vs_dir'] = vs_dir
return 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. # Tool/MSCommon/vc.py, and the MSVC_VERSION documentation in Tool/msvc.xml.
SupportedVSList = [ 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 # Visual Studio 2017
VisualStudio('14.1', VisualStudio('14.1',
vc_version='14.1', vc_version='14.1',
@ -206,10 +217,23 @@ SupportedVSList = [
hkeys=[], hkeys=[],
common_tools_var='VS150COMNTOOLS', common_tools_var='VS150COMNTOOLS',
executable_path=r'Common7\IDE\devenv.com', 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"], 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"],
),
# Visual Studio 2015 # Visual Studio 2015
VisualStudio('14.0', VisualStudio('14.0',
vc_version='14.0', vc_version='14.0',
@ -356,7 +380,7 @@ SupportedVSList = [
sdk_version='2003R2', sdk_version='2003R2',
hkeys=[r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir'], hkeys=[r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir'],
common_tools_var='VS70COMNTOOLS', 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', batch_file_path=r'Common7\Tools\vsvars32.bat',
default_dirname='Microsoft Visual Studio .NET', default_dirname='Microsoft Visual Studio .NET',
supported_arch=['x86'], supported_arch=['x86'],
@ -389,7 +413,7 @@ for vs in SupportedVSList:
InstalledVSList = None InstalledVSList = None
InstalledVSMap = None InstalledVSMap = None
def get_installed_visual_studios(): def get_installed_visual_studios(env=None):
global InstalledVSList global InstalledVSList
global InstalledVSMap global InstalledVSMap
if InstalledVSList is None: if InstalledVSList is None:
@ -397,7 +421,7 @@ def get_installed_visual_studios():
InstalledVSMap = {} InstalledVSMap = {}
for vs in SupportedVSList: for vs in SupportedVSList:
debug('trying to find VS %s' % vs.version) debug('trying to find VS %s' % vs.version)
if vs.get_executable(): if vs.get_executable(env):
debug('found VS %s' % vs.version) debug('found VS %s' % vs.version)
InstalledVSList.append(vs) InstalledVSList.append(vs)
InstalledVSMap[vs.version] = vs InstalledVSMap[vs.version] = vs
@ -448,21 +472,21 @@ def reset_installed_visual_studios():
# for variable, directory in env_tuple_list: # for variable, directory in env_tuple_list:
# env.PrependENVPath(variable, directory) # env.PrependENVPath(variable, directory)
def msvs_exists(): def msvs_exists(env=None):
return (len(get_installed_visual_studios()) > 0) return (len(get_installed_visual_studios(env)) > 0)
def get_vs_by_version(msvs): def get_vs_by_version(msvs):
global InstalledVSMap global InstalledVSMap
global SupportedVSMap global SupportedVSMap
debug('vs.py:get_vs_by_version()') debug('called')
if msvs not in SupportedVSMap: if msvs not in SupportedVSMap:
msg = "Visual Studio version %s is not supported" % repr(msvs) msg = "Visual Studio version %s is not supported" % repr(msvs)
raise SCons.Errors.UserError(msg) raise SCons.Errors.UserError(msg)
get_installed_visual_studios() get_installed_visual_studios()
vs = InstalledVSMap.get(msvs) vs = InstalledVSMap.get(msvs)
debug('InstalledVSMap:%s'%InstalledVSMap) debug('InstalledVSMap:%s' % InstalledVSMap)
debug('vs.py:get_vs_by_version: found vs:%s'%vs) debug('found vs:%s' % vs)
# Some check like this would let us provide a useful error message # 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. # 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 # However, we also want to be able to run tests (like the unit
@ -497,8 +521,8 @@ def get_default_version(env):
if versions: if versions:
env['MSVS_VERSION'] = versions[0] #use highest version by default env['MSVS_VERSION'] = versions[0] #use highest version by default
else: else:
debug('get_default_version: WARNING: no installed versions found, ' debug('WARNING: no installed versions found, '
'using first in SupportedVSList (%s)'%SupportedVSList[0].version) 'using first in SupportedVSList (%s)' % SupportedVSList[0].version)
env['MSVS_VERSION'] = SupportedVSList[0].version env['MSVS_VERSION'] = SupportedVSList[0].version
env['MSVS']['VERSION'] = env['MSVS_VERSION'] env['MSVS']['VERSION'] = env['MSVS_VERSION']
@ -521,7 +545,7 @@ def get_default_arch(env):
if not msvs: if not msvs:
arch = 'x86' 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" fmt = "Visual Studio version %s does not support architecture %s"
raise SCons.Errors.UserError(fmt % (env['MSVS_VERSION'], arch)) 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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
import os.path import os.path
@ -79,7 +79,8 @@ def getPharLapVersion():
include_path = os.path.join(getPharLapPath(), os.path.normpath("include/embkern.h")) include_path = os.path.join(getPharLapPath(), os.path.normpath("include/embkern.h"))
if not os.path.exists(include_path): 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?") 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: if mo:
return int(mo.group(1)) return int(mo.group(1))
# Default return for Phar Lap 9.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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 #forward proxy to the preffered cxx version
from SCons.Tool.aixcxx import * 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 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. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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
import os.path 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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.Defaults
import SCons.Tool 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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/ # Resolve FLAKE8 F401 (make sider happy)
class hierarchy. These will have deprecation warnings added (some day), generate = SCons.Tool.asm.generate
and will then be removed entirely (some day). 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: # Local Variables:
# tab-width:4 # 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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.Defaults
import SCons.Tool 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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
import os.path 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 #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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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.Tool
import SCons.Defaults 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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. # Based on SCons/Tool/gcc.py by Paweł Tomulik 2014 as a separate tool.
# Brought into the SCons mainline by Russel Winder 2017. # Brought into the SCons mainline by Russel Winder 2017.
@ -45,6 +45,9 @@ import sys
import SCons.Util import SCons.Util
import SCons.Tool.cc import SCons.Tool.cc
from SCons.Tool.clangCommon import get_clang_install_dirs
from SCons.Tool.MSCommon import msvc_setup_env_once
compilers = ['clang'] compilers = ['clang']
@ -52,11 +55,24 @@ def generate(env):
"""Add Builders and construction variables for clang to an Environment.""" """Add Builders and construction variables for clang to an Environment."""
SCons.Tool.cc.generate(env) 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' env['CC'] = env.Detect(compilers) or 'clang'
if env['PLATFORM'] in ['cygwin', 'win32']: if env['PLATFORM'] in ['cygwin', 'win32']:
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')
else: else:
env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC') env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC')
# determine compiler version # determine compiler version
if env['CC']: if env['CC']:
#pipe = SCons.Action._subproc(env, [env['CC'], '-dumpversion'], #pipe = SCons.Action._subproc(env, [env['CC'], '-dumpversion'],
@ -66,7 +82,8 @@ def generate(env):
stdout=subprocess.PIPE) stdout=subprocess.PIPE)
if pipe.wait() != 0: return if pipe.wait() != 0: return
# clang -dumpversion is of no use # clang -dumpversion is of no use
line = pipe.stdout.readline() with pipe.stdout:
line = pipe.stdout.readline()
if sys.version_info[0] > 2: if sys.version_info[0] > 2:
line = line.decode() line = line.decode()
match = re.search(r'clang +version +([0-9]+(?:\.[0-9]+)+)', line) match = re.search(r'clang +version +([0-9]+(?:\.[0-9]+)+)', line)

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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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. # Based on SCons/Tool/g++.py by Paweł Tomulik 2014 as a separate tool.
# Brought into the SCons mainline by Russel Winder 2017. # Brought into the SCons mainline by Russel Winder 2017.
@ -46,6 +46,9 @@ import sys
import SCons.Tool import SCons.Tool
import SCons.Util import SCons.Util
import SCons.Tool.cxx import SCons.Tool.cxx
from SCons.Tool.clangCommon import get_clang_install_dirs
from SCons.Tool.MSCommon import msvc_setup_env_once
compilers = ['clang++'] compilers = ['clang++']
@ -66,15 +69,29 @@ def generate(env):
env['SHOBJSUFFIX'] = '.pic.o' env['SHOBJSUFFIX'] = '.pic.o'
elif env['PLATFORM'] == 'sunos': elif env['PLATFORM'] == 'sunos':
env['SHOBJSUFFIX'] = '.pic.o' 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 # determine compiler version
if env['CXX']: if env['CXX']:
pipe = SCons.Action._subproc(env, [env['CXX'], '--version'], pipe = SCons.Action._subproc(env, [env['CXX'], '--version'],
stdin='devnull', stdin='devnull',
stderr='devnull', stderr='devnull',
stdout=subprocess.PIPE) stdout=subprocess.PIPE)
if pipe.wait() != 0: return if pipe.wait() != 0:
return
# clang -dumpversion is of no use # clang -dumpversion is of no use
line = pipe.stdout.readline() with pipe.stdout:
line = pipe.stdout.readline()
if sys.version_info[0] > 2: if sys.version_info[0] > 2:
line = line.decode() line = line.decode()
match = re.search(r'clang +version +([0-9]+(?:\.[0-9]+)+)', line) match = re.search(r'clang +version +([0-9]+(?:\.[0-9]+)+)', line)

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. 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 import os.path

View file

@ -8,8 +8,6 @@ selection method.
""" """
from __future__ import absolute_import, print_function
import re import re
import os import os
@ -133,7 +131,7 @@ def _versioned_lib_suffix(env, suffix, version):
if Verbose: if Verbose:
print("_versioned_lib_suffix: suffix= ", suffix) print("_versioned_lib_suffix: suffix= ", suffix)
print("_versioned_lib_suffix: version= ", version) print("_versioned_lib_suffix: version= ", version)
cygversion = re.sub('\.', '-', version) cygversion = re.sub(r'\.', '-', version)
if not suffix.startswith('-' + cygversion): if not suffix.startswith('-' + cygversion):
suffix = '-' + cygversion + suffix suffix = '-' + cygversion + suffix
if Verbose: 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 import SCons.Tool

View file

@ -1,5 +1,3 @@
from __future__ import print_function
"""SCons.Tool.dmd """SCons.Tool.dmd
Tool-specific initialization for the Digital Mars D compiler. 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 2010-02-07 onwards
Compiler variables: Compiler variables:
DC - The name of the D compiler to use. Defaults to dmd or gdmd,
whichever is found. DC
DPATH - List of paths to search for import modules. The name of the D compiler to use.
DVERSIONS - List of version tags to enable when compiling. Defaults to dmd or gdmd, whichever is found.
DDEBUG - List of debug tags to enable when compiling.
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: Linker related variables:
LIBS - List of library files to link in.
DLINK - Name of the linker to use. Defaults to dmd or gdmd, LIBS
whichever is found. List of library files to link in.
DLINKFLAGS - List of linker flags.
DLINK
Name of the linker to use.
Defaults to dmd or gdmd, whichever is found.
DLINKFLAGS
List of linker flags.
Lib tool variables: Lib tool variables:
DLIB - Name of the lib tool to use. Defaults to lib.
DLIBFLAGS - List of flags to pass to the lib tool. DLIB
LIBS - Same as for the linker. (libraries to pull into the .lib) 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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. # 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 os
import subprocess 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 # Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the # 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 # Local folder for the collection of DocBook XSLs
db_xsl_folder = 'docbook-xsl-1.76.1' db_xsl_folder = 'docbook-xsl-1.76.1'
# Do we have libxml2/libxslt/lxml? # Do we have lxml?
has_libxml2 = True
has_lxml = True has_lxml = True
try:
import libxml2
import libxslt
except:
has_libxml2 = False
try: try:
import lxml import lxml
except: except Exception:
has_lxml = False 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 prefer_xsltproc = False
# Regexs for parsing Docbook XML sources of MAN pages # Regexs for parsing Docbook XML sources of MAN pages
@ -84,7 +78,7 @@ def __extend_targets_sources(target, source):
source = [source] source = [source]
if len(target) < len(source): if len(target) < len(source):
target.extend(source[len(target):]) target.extend(source[len(target):])
return target, source return target, source
def __init_xsl_stylesheet(kw, env, user_xsl_var, default_path): def __init_xsl_stylesheet(kw, env, user_xsl_var, default_path):
@ -94,28 +88,20 @@ def __init_xsl_stylesheet(kw, env, user_xsl_var, default_path):
path_args = [scriptpath, db_xsl_folder] + default_path path_args = [scriptpath, db_xsl_folder] + default_path
xsl_style = os.path.join(*path_args) xsl_style = os.path.join(*path_args)
kw['DOCBOOK_XSL'] = xsl_style kw['DOCBOOK_XSL'] = xsl_style
def __select_builder(lxml_builder, libxml2_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:
return lxml_builder
else:
return cmdline_builder
return libxml2_builder def __select_builder(lxml_builder, cmdline_builder):
""" Selects a builder, based on which Python modules are present. """
if has_lxml and not prefer_xsltproc:
return lxml_builder
return cmdline_builder
def __ensure_suffix(t, suffix): def __ensure_suffix(t, suffix):
""" Ensure that the target t has the given suffix. """ """ Ensure that the target t has the given suffix. """
tpath = str(t) tpath = str(t)
if not tpath.endswith(suffix): if not tpath.endswith(suffix):
return tpath+suffix return tpath+suffix
return t return t
def __ensure_suffix_stem(t, suffix): def __ensure_suffix_stem(t, suffix):
@ -124,18 +110,18 @@ def __ensure_suffix_stem(t, suffix):
if not tpath.endswith(suffix): if not tpath.endswith(suffix):
stem = tpath stem = tpath
tpath += suffix tpath += suffix
return tpath, stem return tpath, stem
else: else:
stem, ext = os.path.splitext(tpath) stem, ext = os.path.splitext(tpath)
return t, stem return t, stem
def __get_xml_text(root): def __get_xml_text(root):
""" Return the text for the given root node (xml.dom.minidom). """ """ Return the text for the given root node (xml.dom.minidom). """
txt = "" txt = ""
for e in root.childNodes: for e in root.childNodes:
if (e.nodeType == e.TEXT_NODE): if e.nodeType == e.TEXT_NODE:
txt += e.data txt += e.data
return txt return txt
@ -151,7 +137,7 @@ def __create_output_dir(base_dir):
else: else:
if base_dir.endswith('/'): if base_dir.endswith('/'):
dir = base_dir dir = base_dir
if dir and not os.path.isdir(dir): if dir and not os.path.isdir(dir):
os.makedirs(dir) os.makedirs(dir)
@ -166,6 +152,8 @@ xsltproc_com_priority = ['xsltproc', 'saxon', 'saxon-xslt', 'xalan']
# see: http://saxon.sourceforge.net/ # see: http://saxon.sourceforge.net/
xsltproc_com = {'xsltproc' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE', xsltproc_com = {'xsltproc' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE',
'saxon' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE $DOCBOOK_XSLTPROCPARAMS', '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', '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'} 'xalan' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -q -out $TARGET -xsl $DOCBOOK_XSL -in $SOURCE'}
xmllint_com = {'xmllint' : '$DOCBOOK_XMLLINT $DOCBOOK_XMLLINTFLAGS --xinclude $SOURCE > $TARGET'} xmllint_com = {'xmllint' : '$DOCBOOK_XMLLINT $DOCBOOK_XMLLINTFLAGS --xinclude $SOURCE > $TARGET'}
@ -201,11 +189,11 @@ def _detect(env):
the requested output formats. the requested output formats.
""" """
global prefer_xsltproc global prefer_xsltproc
if env.get('DOCBOOK_PREFER_XSLTPROC',''): if env.get('DOCBOOK_PREFER_XSLTPROC',''):
prefer_xsltproc = True 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 # Try to find the XSLT processors
__detect_cl_tool(env, 'DOCBOOK_XSLTPROC', xsltproc_com, xsltproc_com_priority) __detect_cl_tool(env, 'DOCBOOK_XSLTPROC', xsltproc_com, xsltproc_com_priority)
__detect_cl_tool(env, 'DOCBOOK_XMLLINT', xmllint_com) __detect_cl_tool(env, 'DOCBOOK_XMLLINT', xmllint_com)
@ -217,58 +205,40 @@ def _detect(env):
# #
include_re = re.compile('fileref\\s*=\\s*["|\']([^\\n]*)["|\']') include_re = re.compile('fileref\\s*=\\s*["|\']([^\\n]*)["|\']')
sentity_re = re.compile('<!ENTITY\\s+%*\\s*[^\\s]+\\s+SYSTEM\\s+["|\']([^\\n]*)["|\']>') sentity_re = re.compile('<!ENTITY\\s+%*\\s*[^\\s]+\\s+SYSTEM\\s+["|\']([^\\n]*)["|\']>')
def __xml_scan(node, env, path, arg): def __xml_scan(node, env, path, arg):
""" Simple XML file scanner, detecting local images and XIncludes as implicit dependencies. """ """ Simple XML file scanner, detecting local images and XIncludes as implicit dependencies. """
# Does the node exist yet? # Does the node exist yet?
if not os.path.isfile(str(node)): if not os.path.isfile(str(node)):
return [] return []
if env.get('DOCBOOK_SCANENT',''): if env.get('DOCBOOK_SCANENT',''):
# Use simple pattern matching for system entities..., no support # Use simple pattern matching for system entities..., no support
# for recursion yet. # for recursion yet.
contents = node.get_text_contents() contents = node.get_text_contents()
return sentity_re.findall(contents) return sentity_re.findall(contents)
xsl_file = os.path.join(scriptpath,'utils','xmldepend.xsl') xsl_file = os.path.join(scriptpath,'utils','xmldepend.xsl')
if not has_libxml2 or prefer_xsltproc: if not has_lxml or prefer_xsltproc:
if has_lxml and not prefer_xsltproc: # Try to call xsltproc
xsltproc = env.subst("$DOCBOOK_XSLTPROC")
from lxml import etree if xsltproc and xsltproc.endswith('xsltproc'):
result = env.backtick(' '.join([xsltproc, xsl_file, str(node)]))
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 ")] depfiles = [x.strip() for x in str(result).splitlines() if x.strip() != "" and not x.startswith("<?xml ")]
return depfiles return depfiles
else: else:
# Try to call xsltproc # Use simple pattern matching, there is currently no support
xsltproc = env.subst("$DOCBOOK_XSLTPROC") # for xi:includes...
if xsltproc and xsltproc.endswith('xsltproc'): contents = node.get_text_contents()
result = env.backtick(' '.join([xsltproc, xsl_file, str(node)])) return include_re.findall(contents)
depfiles = [x.strip() for x in str(result).splitlines() if x.strip() != "" and not x.startswith("<?xml ")]
return depfiles from lxml import etree
else:
# Use simple pattern matching, there is currently no support
# for xi:includes...
contents = node.get_text_contents()
return include_re.findall(contents)
styledoc = libxml2.parseFile(xsl_file) xsl_tree = etree.parse(xsl_file)
style = libxslt.parseStylesheetDoc(styledoc) doc = etree.parse(str(node))
doc = libxml2.readFile(str(node), None, libxml2.XML_PARSE_NOENT) result = doc.xslt(xsl_tree)
result = style.applyStylesheet(doc, None)
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()
depfiles = [x.strip() for x in str(result).splitlines() if x.strip() != "" and not x.startswith("<?xml ")]
return depfiles return depfiles
# Creating the instance of our XML dependency scanner # Creating the instance of our XML dependency scanner
@ -280,12 +250,21 @@ docbook_xml_scanner = SCons.Script.Scanner(function = __xml_scan,
# Action generators # Action generators
# #
def __generate_xsltproc_action(source, target, env, for_signature): def __generate_xsltproc_action(source, target, env, for_signature):
cmd = env['DOCBOOK_XSLTPROCCOM'] cmd = env['DOCBOOK_XSLTPROCCOM']
# Does the environment have a base_dir defined? # Does the environment have a base_dir defined?
base_dir = env.subst('$base_dir') base_dir = env.subst('$base_dir')
if base_dir: if base_dir:
# Yes, so replace target path by its filename # 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 return cmd
@ -298,7 +277,7 @@ def __emit_xsl_basedir(target, source, env):
if base_dir: if base_dir:
# Yes, so prepend it to each target # Yes, so prepend it to each target
return [os.path.join(base_dir, str(t)) for t in target], source return [os.path.join(base_dir, str(t)) for t in target], source
# No, so simply pass target and source names through # No, so simply pass target and source names through
return target, source return target, source
@ -306,37 +285,16 @@ def __emit_xsl_basedir(target, source, env):
# #
# Builders # 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): def __build_lxml(target, source, env):
""" """
General XSLT builder (HTML/FO), using the lxml module. General XSLT builder (HTML/FO), using the lxml module.
""" """
from lxml import etree from lxml import etree
xslt_ac = etree.XSLTAccessControl(read_file=True, xslt_ac = etree.XSLTAccessControl(read_file=True,
write_file=True, write_file=True,
create_dir=True, create_dir=True,
read_network=False, read_network=False,
write_network=False) write_network=False)
xsl_style = env.subst('$DOCBOOK_XSL') xsl_style = env.subst('$DOCBOOK_XSL')
xsl_tree = etree.parse(xsl_style) xsl_tree = etree.parse(xsl_style)
@ -348,59 +306,71 @@ def __build_lxml(target, source, env):
result = transform(doc, **parampass) result = transform(doc, **parampass)
else: else:
result = transform(doc) result = transform(doc)
try: try:
of = open(str(target[0]), "wb") with open(str(target[0]), "wb") as of:
of.write(of.write(etree.tostring(result, pretty_print=True))) of.write(etree.tostring(result, encoding="utf-8", pretty_print=True))
of.close() except Exception as e:
except: print("ERROR: Failed to write {}".format(str(target[0])))
pass print(e)
return None 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) from lxml import etree
doc.xincludeProcessFlags(libxml2.XML_PARSE_NOENT)
doc.saveFile(str(target[0])) xslt_ac = etree.XSLTAccessControl(read_file=True,
doc.freeDoc() 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 return None
def __xinclude_lxml(target, source, env): def __xinclude_lxml(target, source, env):
""" """
Resolving XIncludes, using the lxml module. Resolving XIncludes, using the lxml module.
""" """
from lxml import etree from lxml import etree
doc = etree.parse(str(source[0])) doc = etree.parse(str(source[0]))
doc.xinclude() doc.xinclude()
try: try:
doc.write(str(target[0]), xml_declaration=True, doc.write(str(target[0]), xml_declaration=True,
encoding="UTF-8", pretty_print=True) encoding="UTF-8", pretty_print=True)
except: except Exception as e:
pass print("ERROR: Failed to write {}".format(str(target[0])))
print(e)
return None 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( __lxml_builder = SCons.Builder.Builder(
action = __build_lxml, action = __build_lxml,
src_suffix = '.xml', src_suffix = '.xml',
source_scanner = docbook_xml_scanner, source_scanner = docbook_xml_scanner,
emitter = __emit_xsl_basedir) emitter = __emit_xsl_basedir)
__xinclude_libxml2_builder = SCons.Builder.Builder( __lxml_noresult_builder = SCons.Builder.Builder(
action = __xinclude_libxml2, action = __build_lxml_noresult,
suffix = '.xml',
src_suffix = '.xml', src_suffix = '.xml',
source_scanner = docbook_xml_scanner) source_scanner = docbook_xml_scanner,
emitter = __emit_xsl_basedir)
__xinclude_lxml_builder = SCons.Builder.Builder( __xinclude_lxml_builder = SCons.Builder.Builder(
action = __xinclude_lxml, action = __xinclude_lxml,
suffix = '.xml', suffix = '.xml',
@ -413,6 +383,12 @@ __xsltproc_builder = SCons.Builder.Builder(
src_suffix = '.xml', src_suffix = '.xml',
source_scanner = docbook_xml_scanner, source_scanner = docbook_xml_scanner,
emitter = __emit_xsl_basedir) 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( __xmllint_builder = SCons.Builder.Builder(
action = SCons.Action.Action('$DOCBOOK_XMLLINTCOM','$DOCBOOK_XMLLINTCOMSTR'), action = SCons.Action.Action('$DOCBOOK_XMLLINTCOM','$DOCBOOK_XMLLINTCOMSTR'),
suffix = '.xml', suffix = '.xml',
@ -430,33 +406,31 @@ def DocbookEpub(env, target, source=None, *args, **kw):
""" """
import zipfile import zipfile
import shutil import shutil
def build_open_container(target, source, env): def build_open_container(target, source, env):
"""Generate the *.epub file from intermediate outputs """Generate the *.epub file from intermediate outputs
Constructs the epub file according to the Open Container Format. This Constructs the epub file according to the Open Container Format. This
function could be replaced by a call to the SCons Zip builder if support function could be replaced by a call to the SCons Zip builder if support
was added for different compression formats for separate source nodes. was added for different compression formats for separate source nodes.
""" """
zf = zipfile.ZipFile(str(target[0]), 'w') with zipfile.ZipFile(str(target[0]), 'w') as zf:
mime_file = open('mimetype', 'w') with open('mimetype', 'w') as mime_file:
mime_file.write('application/epub+zip') mime_file.write('application/epub+zip')
mime_file.close() zf.write(mime_file.name, compress_type = zipfile.ZIP_STORED)
zf.write(mime_file.name, compress_type = zipfile.ZIP_STORED) for s in source:
for s in source: if os.path.isfile(str(s)):
if os.path.isfile(str(s)): head, tail = os.path.split(str(s))
head, tail = os.path.split(str(s)) if not head:
if not head: continue
continue s = head
s = head for dirpath, dirnames, filenames in os.walk(str(s)):
for dirpath, dirnames, filenames in os.walk(str(s)): for fname in filenames:
for fname in filenames: path = os.path.join(dirpath, fname)
path = os.path.join(dirpath, fname) if os.path.isfile(path):
if os.path.isfile(path): zf.write(path, os.path.relpath(path, str(env.get('ZIPROOT', ''))),
zf.write(path, os.path.relpath(path, str(env.get('ZIPROOT', ''))), zipfile.ZIP_DEFLATED)
zipfile.ZIP_DEFLATED)
zf.close()
def add_resources(target, source, env): def add_resources(target, source, env):
"""Add missing resources to the OEBPS directory """Add missing resources to the OEBPS directory
@ -466,78 +440,52 @@ def DocbookEpub(env, target, source=None, *args, **kw):
content_file = os.path.join(source[0].get_abspath(), 'content.opf') content_file = os.path.join(source[0].get_abspath(), 'content.opf')
if not os.path.isfile(content_file): if not os.path.isfile(content_file):
return return
hrefs = [] hrefs = []
if has_libxml2: if has_lxml:
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:
from lxml import etree from lxml import etree
opf = etree.parse(content_file) opf = etree.parse(content_file)
# All the opf:item elements are resources # All the opf:item elements are resources
for item in opf.xpath('//opf:item', for item in opf.xpath('//opf:item',
namespaces= { 'opf': 'http://www.idpf.org/2007/opf' }): namespaces= { 'opf': 'http://www.idpf.org/2007/opf' }):
hrefs.append(item.attrib['href']) hrefs.append(item.attrib['href'])
for href in hrefs: for href in hrefs:
# If the resource was not already created by DocBook XSL itself, # If the resource was not already created by DocBook XSL itself,
# copy it into the OEBPS folder # copy it into the OEBPS folder
referenced_file = os.path.join(source[0].get_abspath(), href) referenced_file = os.path.join(source[0].get_abspath(), href)
if not os.path.exists(referenced_file): if not os.path.exists(referenced_file):
shutil.copy(href, os.path.join(source[0].get_abspath(), href)) shutil.copy(href, os.path.join(source[0].get_abspath(), href))
# Init list of targets/sources # Init list of targets/sources
target, source = __extend_targets_sources(target, source) target, source = __extend_targets_sources(target, source)
# Init XSL stylesheet # Init XSL stylesheet
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_EPUB', ['epub','docbook.xsl']) __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_EPUB', ['epub','docbook.xsl'])
# Setup builder # Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder) __builder = __select_builder(__lxml_noresult_builder, __xsltproc_nobase_builder)
# Create targets # Create targets
result = [] result = []
if not env.GetOption('clean'): if not env.GetOption('clean'):
# Ensure that the folders OEBPS and META-INF exist # Ensure that the folders OEBPS and META-INF exist
__create_output_dir('OEBPS/') __create_output_dir('OEBPS/')
__create_output_dir('META-INF/') __create_output_dir('META-INF/')
dirs = env.Dir(['OEBPS', 'META-INF']) dirs = env.Dir(['OEBPS', 'META-INF'])
# Set the fixed base_dir # Set the fixed base_dir
kw['base_dir'] = 'OEBPS/' kw['base_dir'] = 'OEBPS/'
tocncx = __builder.__call__(env, 'toc.ncx', source[0], **kw) tocncx = __builder.__call__(env, 'toc.ncx', source[0], **kw)
cxml = env.File('META-INF/container.xml') cxml = env.File('META-INF/container.xml')
env.SideEffect(cxml, tocncx) env.SideEffect(cxml, tocncx)
env.Depends(tocncx, kw['DOCBOOK_XSL']) env.Depends(tocncx, kw['DOCBOOK_XSL'])
result.extend(tocncx+[cxml]) result.extend(tocncx+[cxml])
container = env.Command(__ensure_suffix(str(target[0]), '.epub'), container = env.Command(__ensure_suffix(str(target[0]), '.epub'),
tocncx+[cxml], [add_resources, build_open_container]) tocncx+[cxml], [add_resources, build_open_container])
mimetype = env.File('mimetype') mimetype = env.File('mimetype')
env.SideEffect(mimetype, container) env.SideEffect(mimetype, container)
@ -553,13 +501,13 @@ def DocbookHtml(env, target, source=None, *args, **kw):
""" """
# Init list of targets/sources # Init list of targets/sources
target, source = __extend_targets_sources(target, source) target, source = __extend_targets_sources(target, source)
# Init XSL stylesheet # Init XSL stylesheet
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTML', ['html','docbook.xsl']) __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTML', ['html','docbook.xsl'])
# Setup builder # Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder) __builder = __select_builder(__lxml_builder, __xsltproc_builder)
# Create targets # Create targets
result = [] result = []
for t,s in zip(target,source): for t,s in zip(target,source):
@ -581,18 +529,18 @@ def DocbookHtmlChunked(env, target, source=None, *args, **kw):
target = ['index.html'] target = ['index.html']
elif not SCons.Util.is_List(source): elif not SCons.Util.is_List(source):
source = [source] source = [source]
# Init XSL stylesheet # Init XSL stylesheet
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTMLCHUNKED', ['html','chunkfast.xsl']) __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTMLCHUNKED', ['html','chunkfast.xsl'])
# Setup builder # Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder) __builder = __select_builder(__lxml_noresult_builder, __xsltproc_nobase_builder)
# Detect base dir # Detect base dir
base_dir = kw.get('base_dir', '') base_dir = kw.get('base_dir', '')
if base_dir: if base_dir:
__create_output_dir(base_dir) __create_output_dir(base_dir)
# Create targets # Create targets
result = [] result = []
r = __builder.__call__(env, __ensure_suffix(str(target[0]), '.html'), source[0], **kw) r = __builder.__call__(env, __ensure_suffix(str(target[0]), '.html'), source[0], **kw)
@ -615,19 +563,19 @@ def DocbookHtmlhelp(env, target, source=None, *args, **kw):
source = target source = target
target = ['index.html'] target = ['index.html']
elif not SCons.Util.is_List(source): elif not SCons.Util.is_List(source):
source = [source] source = [source]
# Init XSL stylesheet # Init XSL stylesheet
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTMLHELP', ['htmlhelp','htmlhelp.xsl']) __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTMLHELP', ['htmlhelp','htmlhelp.xsl'])
# Setup builder # Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder) __builder = __select_builder(__lxml_noresult_builder, __xsltproc_nobase_builder)
# Detect base dir # Detect base dir
base_dir = kw.get('base_dir', '') base_dir = kw.get('base_dir', '')
if base_dir: if base_dir:
__create_output_dir(base_dir) __create_output_dir(base_dir)
# Create targets # Create targets
result = [] result = []
r = __builder.__call__(env, __ensure_suffix(str(target[0]), '.html'), source[0], **kw) r = __builder.__call__(env, __ensure_suffix(str(target[0]), '.html'), source[0], **kw)
@ -650,7 +598,7 @@ def DocbookPdf(env, target, source=None, *args, **kw):
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_PDF', ['fo','docbook.xsl']) __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_PDF', ['fo','docbook.xsl'])
# Setup builder # Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder) __builder = __select_builder(__lxml_builder, __xsltproc_builder)
# Create targets # Create targets
result = [] 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']) __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_MAN', ['manpages','docbook.xsl'])
# Setup builder # Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder) __builder = __select_builder(__lxml_noresult_builder, __xsltproc_builder)
# Create targets # Create targets
result = [] result = []
@ -685,30 +633,29 @@ def DocbookMan(env, target, source=None, *args, **kw):
if os.path.isfile(srcfile): if os.path.isfile(srcfile):
try: try:
import xml.dom.minidom import xml.dom.minidom
dom = xml.dom.minidom.parse(__ensure_suffix(str(s),'.xml')) dom = xml.dom.minidom.parse(__ensure_suffix(str(s),'.xml'))
# Extract volume number, default is 1 # Extract volume number, default is 1
for node in dom.getElementsByTagName('refmeta'): for node in dom.getElementsByTagName('refmeta'):
for vol in node.getElementsByTagName('manvolnum'): for vol in node.getElementsByTagName('manvolnum'):
volnum = __get_xml_text(vol) volnum = __get_xml_text(vol)
# Extract output filenames # Extract output filenames
for node in dom.getElementsByTagName('refnamediv'): for node in dom.getElementsByTagName('refnamediv'):
for ref in node.getElementsByTagName('refname'): for ref in node.getElementsByTagName('refname'):
outfiles.append(__get_xml_text(ref)+'.'+volnum) outfiles.append(__get_xml_text(ref)+'.'+volnum)
except: except Exception:
# Use simple regex parsing # 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() content = f.read()
f.close()
for m in re_manvolnum.finditer(content): for m in re_manvolnum.finditer(content):
volnum = m.group(1) volnum = m.group(1)
for m in re_refname.finditer(content): for m in re_refname.finditer(content):
outfiles.append(m.group(1)+'.'+volnum) outfiles.append(m.group(1)+'.'+volnum)
if not outfiles: if not outfiles:
# Use stem of the source file # Use stem of the source file
spath = str(s) spath = str(s)
@ -720,14 +667,14 @@ def DocbookMan(env, target, source=None, *args, **kw):
else: else:
# We have to completely rely on the given target name # We have to completely rely on the given target name
outfiles.append(t) outfiles.append(t)
__builder.__call__(env, outfiles[0], s, **kw) __builder.__call__(env, outfiles[0], s, **kw)
env.Depends(outfiles[0], kw['DOCBOOK_XSL']) env.Depends(outfiles[0], kw['DOCBOOK_XSL'])
result.append(outfiles[0]) result.append(outfiles[0])
if len(outfiles) > 1: if len(outfiles) > 1:
env.Clean(outfiles[0], outfiles[1:]) env.Clean(outfiles[0], outfiles[1:])
return result return result
def DocbookSlidesPdf(env, target, source=None, *args, **kw): def DocbookSlidesPdf(env, target, source=None, *args, **kw):
@ -741,14 +688,14 @@ def DocbookSlidesPdf(env, target, source=None, *args, **kw):
__init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_SLIDESPDF', ['slides','fo','plain.xsl']) __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_SLIDESPDF', ['slides','fo','plain.xsl'])
# Setup builder # Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder) __builder = __select_builder(__lxml_builder, __xsltproc_builder)
# Create targets # Create targets
result = [] result = []
for t,s in zip(target,source): for t,s in zip(target,source):
t, stem = __ensure_suffix_stem(t, '.pdf') t, stem = __ensure_suffix_stem(t, '.pdf')
xsl = __builder.__call__(env, stem+'.fo', s, **kw) xsl = __builder.__call__(env, stem+'.fo', s, **kw)
env.Depends(xsl, kw['DOCBOOK_XSL']) env.Depends(xsl, kw['DOCBOOK_XSL'])
result.extend(xsl) result.extend(xsl)
result.extend(__fop_builder.__call__(env, t, xsl, **kw)) result.extend(__fop_builder.__call__(env, t, xsl, **kw))
@ -765,13 +712,13 @@ def DocbookSlidesHtml(env, target, source=None, *args, **kw):
source = target source = target
target = ['index.html'] target = ['index.html']
elif not SCons.Util.is_List(source): elif not SCons.Util.is_List(source):
source = [source] source = [source]
# Init XSL stylesheet # 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 # Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder) __builder = __select_builder(__lxml_builder, __xsltproc_builder)
# Detect base dir # Detect base dir
base_dir = kw.get('base_dir', '') base_dir = kw.get('base_dir', '')
@ -797,13 +744,13 @@ def DocbookXInclude(env, target, source, *args, **kw):
target, source = __extend_targets_sources(target, source) target, source = __extend_targets_sources(target, source)
# Setup builder # Setup builder
__builder = __select_builder(__xinclude_lxml_builder,__xinclude_libxml2_builder,__xmllint_builder) __builder = __select_builder(__xinclude_lxml_builder,__xmllint_builder)
# Create targets # Create targets
result = [] result = []
for t,s in zip(target,source): for t,s in zip(target,source):
result.extend(__builder.__call__(env, t, s, **kw)) result.extend(__builder.__call__(env, t, s, **kw))
return result return result
def DocbookXslt(env, target, source=None, *args, **kw): def DocbookXslt(env, target, source=None, *args, **kw):
@ -812,13 +759,13 @@ def DocbookXslt(env, target, source=None, *args, **kw):
""" """
# Init list of targets/sources # Init list of targets/sources
target, source = __extend_targets_sources(target, source) target, source = __extend_targets_sources(target, source)
# Init XSL stylesheet # Init XSL stylesheet
kw['DOCBOOK_XSL'] = kw.get('xsl', 'transform.xsl') kw['DOCBOOK_XSL'] = kw.get('xsl', 'transform.xsl')
# Setup builder # Setup builder
__builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder) __builder = __select_builder(__lxml_builder, __xsltproc_builder)
# Create targets # Create targets
result = [] result = []
for t,s in zip(target,source): for t,s in zip(target,source):
@ -842,18 +789,18 @@ def generate(env):
DOCBOOK_DEFAULT_XSL_MAN = '', DOCBOOK_DEFAULT_XSL_MAN = '',
DOCBOOK_DEFAULT_XSL_SLIDESPDF = '', DOCBOOK_DEFAULT_XSL_SLIDESPDF = '',
DOCBOOK_DEFAULT_XSL_SLIDESHTML = '', DOCBOOK_DEFAULT_XSL_SLIDESHTML = '',
# Paths to the detected executables # Paths to the detected executables
DOCBOOK_XSLTPROC = '', DOCBOOK_XSLTPROC = '',
DOCBOOK_XMLLINT = '', DOCBOOK_XMLLINT = '',
DOCBOOK_FOP = '', DOCBOOK_FOP = '',
# Additional flags for the text processors # Additional flags for the text processors
DOCBOOK_XSLTPROCFLAGS = SCons.Util.CLVar(''), DOCBOOK_XSLTPROCFLAGS = SCons.Util.CLVar(''),
DOCBOOK_XMLLINTFLAGS = SCons.Util.CLVar(''), DOCBOOK_XMLLINTFLAGS = SCons.Util.CLVar(''),
DOCBOOK_FOPFLAGS = SCons.Util.CLVar(''), DOCBOOK_FOPFLAGS = SCons.Util.CLVar(''),
DOCBOOK_XSLTPROCPARAMS = SCons.Util.CLVar(''), DOCBOOK_XSLTPROCPARAMS = SCons.Util.CLVar(''),
# Default command lines for the detected executables # Default command lines for the detected executables
DOCBOOK_XSLTPROCCOM = xsltproc_com['xsltproc'], DOCBOOK_XSLTPROCCOM = xsltproc_com['xsltproc'],
DOCBOOK_XMLLINTCOM = xmllint_com['xmllint'], DOCBOOK_XMLLINTCOM = xmllint_com['xmllint'],
@ -863,7 +810,7 @@ def generate(env):
DOCBOOK_XSLTPROCCOMSTR = None, DOCBOOK_XSLTPROCCOMSTR = None,
DOCBOOK_XMLLINTCOMSTR = None, DOCBOOK_XMLLINTCOMSTR = None,
DOCBOOK_FOPCOMSTR = None, DOCBOOK_FOPCOMSTR = None,
) )
_detect(env) _detect(env)

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