Merge pull request #4217 from mapnik/scons-4.1.0

Scons 4.1.0
This commit is contained in:
Artem Pavlenko 2021-03-11 14:45:32 +00:00 committed by GitHub
commit 74c79eb17c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1527 changed files with 417246 additions and 9021 deletions

View file

@ -1,4 +1,4 @@
language: generic language: cpp
git: git:
depth: 10 depth: 10
@ -9,6 +9,7 @@ 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:
@ -28,7 +29,7 @@ matrix:
postgresql: "9.5" postgresql: "9.5"
apt: apt:
sources: [ 'ubuntu-toolchain-r-test' ] sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'xutils-dev', 'postgresql-9.5-postgis-2.4' ] packages: [ 'xutils-dev', 'libstdc++-6-dev', 'postgresql-9.5-postgis-2.4' ]
- os: linux - os: linux
name: Linux clang + coverage name: Linux clang + coverage
env: >- env: >-
@ -42,13 +43,14 @@ matrix:
postgresql: "9.5" postgresql: "9.5"
apt: apt:
sources: [ 'ubuntu-toolchain-r-test' ] sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'xutils-dev', 'postgresql-9.5-postgis-2.4' ] packages: [ 'xutils-dev', 'libstdc++-6-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 #https://docs.travis-ci.com/user/reference/osx#macos-version
osx_image: xcode7.3 # upgrades clang from 6 -> 7 osx_image: xcode12.2
env: >- env: >-
CXX="ccache clang++ -Qunused-arguments" CXX="ccache clang++ -Qunused-arguments"
before_install:
install: install:
- source scripts/travis-common.sh - source scripts/travis-common.sh
@ -81,6 +83,8 @@ before_script:
script: script:
- git_submodule_update --init deps/ - git_submodule_update --init deps/
- on 'osx' brew unlink $(brew list --formula)
- on 'osx' brew link git postgresql postgis
- configure BENCHMARK=${BENCH} ENABLE_GLIBC_WORKAROUND=${ENABLE_GLIBC_WORKAROUND:-false} QUIET=${QUIET:-false} - configure BENCHMARK=${BENCH} ENABLE_GLIBC_WORKAROUND=${ENABLE_GLIBC_WORKAROUND:-false} QUIET=${QUIET:-false}
#- cat config.log => comment out to reduce log size limit on travis-ci #- cat config.log => comment out to reduce log size limit on travis-ci
# we limit the `make` to 40 min # we limit the `make` to 40 min

View file

@ -16,8 +16,6 @@
# License along with this library; if not, write to the Free Software # License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from __future__ import print_function # support python2
import os import os
import sys import sys
import re import re
@ -34,19 +32,8 @@ try:
except: except:
HAS_DISTUTILS = False HAS_DISTUTILS = False
try: from shlex import quote as shquote
# Python 3.3+ from subprocess import DEVNULL
from shlex import quote as shquote
except:
# Python 2.7
from pipes import quote as shquote
try:
# Python 3.3+
from subprocess import DEVNULL
except:
# Python 2.7
DEVNULL = open(os.devnull, 'w')
LIBDIR_SCHEMA_DEFAULT='lib' LIBDIR_SCHEMA_DEFAULT='lib'
severities = ['debug', 'warn', 'error', 'none'] severities = ['debug', 'warn', 'error', 'none']
@ -92,7 +79,7 @@ pretty_dep_names = {
'gdal':'GDAL C++ library | configured using gdal-config program | try setting GDAL_CONFIG SCons option | more info: https://github.com/mapnik/mapnik/wiki/GDAL', 'gdal':'GDAL C++ library | configured using gdal-config program | try setting GDAL_CONFIG SCons option | more info: https://github.com/mapnik/mapnik/wiki/GDAL',
'ogr':'OGR-enabled GDAL C++ Library | configured using gdal-config program | try setting GDAL_CONFIG SCons option | more info: https://github.com/mapnik/mapnik/wiki/OGR', 'ogr':'OGR-enabled GDAL C++ Library | configured using gdal-config program | try setting GDAL_CONFIG SCons option | more info: https://github.com/mapnik/mapnik/wiki/OGR',
'cairo':'Cairo C library | configured using pkg-config | try setting PKG_CONFIG_PATH SCons option', 'cairo':'Cairo C library | configured using pkg-config | try setting PKG_CONFIG_PATH SCons option',
'proj':'Proj.4 C Projections library | configure with PROJ_LIBS & PROJ_INCLUDES | more info: http://trac.osgeo.org/proj/', 'proj':'Proj C Projections library | configure with PROJ_LIBS & PROJ_INCLUDES | more info: http://trac.osgeo.org/proj/',
'pg':'Postgres C Library required for PostGIS plugin | configure with pg_config program or configure with PG_LIBS & PG_INCLUDES | more info: https://github.com/mapnik/mapnik/wiki/PostGIS', 'pg':'Postgres C Library required for PostGIS plugin | configure with pg_config program or configure with PG_LIBS & PG_INCLUDES | more info: https://github.com/mapnik/mapnik/wiki/PostGIS',
'sqlite3':'SQLite3 C Library | configure with SQLITE_LIBS & SQLITE_INCLUDES | more info: https://github.com/mapnik/mapnik/wiki/SQLite', 'sqlite3':'SQLite3 C Library | configure with SQLITE_LIBS & SQLITE_INCLUDES | more info: https://github.com/mapnik/mapnik/wiki/SQLite',
'jpeg':'JPEG C library | configure with JPEG_LIBS & JPEG_INCLUDES', 'jpeg':'JPEG C library | configure with JPEG_LIBS & JPEG_INCLUDES',
@ -142,7 +129,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 +366,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 +679,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 +1276,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 +2152,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

@ -8,7 +8,7 @@ todo
- shrink icu data - shrink icu data
' '
MASON_VERSION="fde1d9f5" MASON_VERSION="edb620c7"
function setup_mason() { function setup_mason() {
if [[ ! -d ./.mason ]]; then if [[ ! -d ./.mason ]]; then
@ -44,7 +44,7 @@ function install() {
} }
ICU_VERSION="57.1" ICU_VERSION="57.1"
BOOST_VERSION="1.73.0" BOOST_VERSION="1.75.0"
function install_mason_deps() { function install_mason_deps() {
install ccache 3.3.1 install ccache 3.3.1
@ -54,7 +54,6 @@ function install_mason_deps() {
install libtiff 4.0.7 libtiff install libtiff 4.0.7 libtiff
install libpq 9.6.2 install libpq 9.6.2
install sqlite 3.17.0 libsqlite3 install sqlite 3.17.0 libsqlite3
install expat 2.2.0 libexpat
install icu ${ICU_VERSION} install icu ${ICU_VERSION}
install proj 4.9.3 libproj install proj 4.9.3 libproj
install pixman 0.34.0 libpixman-1 install pixman 0.34.0 libpixman-1

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 - 2021 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 - 2021 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/EnumOption.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 EnumOption(*args, **kw):
global warned
if not warned:
msg = "The EnumOption() function is deprecated; use the EnumVariable() function instead."
SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg)
warned = True
return SCons.Variables.EnumVariable(*args, **kw)
# Local Variables:
# tab-width:4
# 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

File diff suppressed because it is too large Load diff

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,236 +0,0 @@
"""SCons.Tool.cyglink
Customization of gnulink for Cygwin (http://www.cygwin.com/)
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
from __future__ import absolute_import, print_function
import re
import os
import SCons.Action
import SCons.Util
import SCons.Tool
#MAYBE: from . import gnulink
from . import gnulink
from . import link
def _lib_generator(target, source, env, for_signature, **kw):
try: cmd = kw['cmd']
except KeyError: cmd = SCons.Util.CLVar(['$SHLINK'])
try: vp = kw['varprefix']
except KeyError: vp = 'SHLIB'
dll = env.FindIxes(target, '%sPREFIX' % vp, '%sSUFFIX' % vp)
if dll: cmd.extend(['-o', dll])
cmd.extend(['$SHLINKFLAGS', '$__%sVERSIONFLAGS' % vp, '$__RPATH'])
implib = env.FindIxes(target, 'IMPLIBPREFIX', 'IMPLIBSUFFIX')
if implib:
cmd.extend([
'-Wl,--out-implib='+implib.get_string(for_signature),
'-Wl,--export-all-symbols',
'-Wl,--enable-auto-import',
'-Wl,--whole-archive', '$SOURCES',
'-Wl,--no-whole-archive', '$_LIBDIRFLAGS', '$_LIBFLAGS'
])
else:
cmd.extend(['$SOURCES', '$_LIBDIRFLAGS', '$_LIBFLAGS'])
return [cmd]
def shlib_generator(target, source, env, for_signature):
return _lib_generator(target, source, env, for_signature,
varprefix='SHLIB',
cmd = SCons.Util.CLVar(['$SHLINK']))
def ldmod_generator(target, source, env, for_signature):
return _lib_generator(target, source, env, for_signature,
varprefix='LDMODULE',
cmd = SCons.Util.CLVar(['$LDMODULE']))
def _lib_emitter(target, source, env, **kw):
Verbose = False
if Verbose:
print("_lib_emitter: target[0]=%r" % target[0].get_path())
try: vp = kw['varprefix']
except KeyError: vp = 'SHLIB'
try: libtype = kw['libtype']
except KeyError: libtype = 'ShLib'
dll = env.FindIxes(target, '%sPREFIX' % vp, '%sSUFFIX' % vp)
no_import_lib = env.get('no_import_lib', 0)
if Verbose:
print("_lib_emitter: dll=%r" % dll.get_path())
if not dll or len(target) > 1:
raise SCons.Errors.UserError("A shared library should have exactly one target with the suffix: %s" % env.subst("$%sSUFFIX" % vp))
# Remove any "lib" after the prefix
pre = env.subst('$%sPREFIX' % vp)
if dll.name[len(pre):len(pre)+3] == 'lib':
dll.name = pre + dll.name[len(pre)+3:]
if Verbose:
print("_lib_emitter: dll.name=%r" % dll.name)
orig_target = target
target = [env.fs.File(dll)]
target[0].attributes.shared = 1
if Verbose:
print("_lib_emitter: after target=[env.fs.File(dll)]: target[0]=%r" % target[0].get_path())
# Append an import lib target
if not no_import_lib:
# Create list of target libraries as strings
target_strings = env.ReplaceIxes(orig_target[0],
'%sPREFIX' % vp, '%sSUFFIX' % vp,
'IMPLIBPREFIX', 'IMPLIBSUFFIX')
if Verbose:
print("_lib_emitter: target_strings=%r" % target_strings)
implib_target = env.fs.File(target_strings)
if Verbose:
print("_lib_emitter: implib_target=%r" % implib_target.get_path())
implib_target.attributes.shared = 1
target.append(implib_target)
symlinks = SCons.Tool.ImpLibSymlinkGenerator(env, implib_target,
implib_libtype=libtype,
generator_libtype=libtype+'ImpLib')
if Verbose:
print("_lib_emitter: implib symlinks=%r" % SCons.Tool.StringizeLibSymlinks(symlinks))
if symlinks:
SCons.Tool.EmitLibSymlinks(env, symlinks, implib_target, clean_targets = target[0])
implib_target.attributes.shliblinks = symlinks
return (target, source)
def shlib_emitter(target, source, env):
return _lib_emitter(target, source, env, varprefix='SHLIB', libtype='ShLib')
def ldmod_emitter(target, source, env):
return _lib_emitter(target, source, env, varprefix='LDMODULE', libtype='LdMod')
def _versioned_lib_suffix(env, suffix, version):
"""Generate versioned shared library suffix from a unversioned one.
If suffix='.dll', and version='0.1.2', then it returns '-0-1-2.dll'"""
Verbose = False
if Verbose:
print("_versioned_lib_suffix: suffix= ", suffix)
print("_versioned_lib_suffix: version= ", version)
cygversion = re.sub('\.', '-', version)
if not suffix.startswith('-' + cygversion):
suffix = '-' + cygversion + suffix
if Verbose:
print("_versioned_lib_suffix: return suffix= ", suffix)
return suffix
def _versioned_implib_name(env, libnode, version, prefix, suffix, **kw):
return link._versioned_lib_name(env, libnode, version, prefix, suffix,
SCons.Tool.ImpLibPrefixGenerator,
SCons.Tool.ImpLibSuffixGenerator,
implib_libtype=kw['libtype'])
def _versioned_implib_symlinks(env, libnode, version, prefix, suffix, **kw):
"""Generate link names that should be created for a versioned shared library.
Returns a list in the form [ (link, linktarget), ... ]
"""
Verbose = False
if Verbose:
print("_versioned_implib_symlinks: libnode=%r" % libnode.get_path())
print("_versioned_implib_symlinks: version=%r" % version)
try: libtype = kw['libtype']
except KeyError: libtype = 'ShLib'
linkdir = os.path.dirname(libnode.get_path())
if Verbose:
print("_versioned_implib_symlinks: linkdir=%r" % linkdir)
name = SCons.Tool.ImpLibNameGenerator(env, libnode,
implib_libtype=libtype,
generator_libtype=libtype+'ImpLib')
if Verbose:
print("_versioned_implib_symlinks: name=%r" % name)
major = version.split('.')[0]
link0 = env.fs.File(os.path.join(linkdir, name))
symlinks = [(link0, libnode)]
if Verbose:
print("_versioned_implib_symlinks: return symlinks=%r" % SCons.Tool.StringizeLibSymlinks(symlinks))
return symlinks
shlib_action = SCons.Action.Action(shlib_generator, generator=1)
ldmod_action = SCons.Action.Action(ldmod_generator, generator=1)
def generate(env):
"""Add Builders and construction variables for cyglink to an Environment."""
gnulink.generate(env)
env['LINKFLAGS'] = SCons.Util.CLVar('-Wl,-no-undefined')
env['SHLINKCOM'] = shlib_action
env['LDMODULECOM'] = ldmod_action
env.Append(SHLIBEMITTER = [shlib_emitter])
env.Append(LDMODULEEMITTER = [ldmod_emitter])
env['SHLIBPREFIX'] = 'cyg'
env['SHLIBSUFFIX'] = '.dll'
env['IMPLIBPREFIX'] = 'lib'
env['IMPLIBSUFFIX'] = '.dll.a'
# Variables used by versioned shared libraries
env['_SHLIBVERSIONFLAGS'] = '$SHLIBVERSIONFLAGS'
env['_LDMODULEVERSIONFLAGS'] = '$LDMODULEVERSIONFLAGS'
# SHLIBVERSIONFLAGS and LDMODULEVERSIONFLAGS are same as in gnulink...
# LINKCALLBACKS are NOT inherited from gnulink
env['LINKCALLBACKS'] = {
'VersionedShLibSuffix' : _versioned_lib_suffix,
'VersionedLdModSuffix' : _versioned_lib_suffix,
'VersionedImpLibSuffix' : _versioned_lib_suffix,
'VersionedShLibName' : link._versioned_shlib_name,
'VersionedLdModName' : link._versioned_ldmod_name,
'VersionedShLibImpLibName' : lambda *args: _versioned_implib_name(*args, libtype='ShLib'),
'VersionedLdModImpLibName' : lambda *args: _versioned_implib_name(*args, libtype='LdMod'),
'VersionedShLibImpLibSymlinks' : lambda *args: _versioned_implib_symlinks(*args, libtype='ShLib'),
'VersionedLdModImpLibSymlinks' : lambda *args: _versioned_implib_symlinks(*args, libtype='LdMod'),
}
# these variables were set by gnulink but are not used in cyglink
try: del env['_SHLIBSONAME']
except KeyError: pass
try: del env['_LDMODULESONAME']
except KeyError: pass
def exists(env):
return gnulink.exists(env)
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View file

@ -1,331 +0,0 @@
"""SCons.Tool.link
Tool-specific initialization for the generic Posix linker.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# 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.
#
from __future__ import print_function
__revision__ = "src/engine/SCons/Tool/link.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import sys
import re
import os
import SCons.Tool
import SCons.Util
import SCons.Warnings
from SCons.Tool.FortranCommon import isfortran
from SCons.Tool.DCommon import isD
import SCons.Tool.cxx
cplusplus = SCons.Tool.cxx
# cplusplus = __import__(__package__+'.cxx', globals(), locals(), ['*'])
issued_mixed_link_warning = False
def smart_link(source, target, env, for_signature):
has_cplusplus = cplusplus.iscplusplus(source)
has_fortran = isfortran(env, source)
has_d = isD(env, source)
if has_cplusplus and has_fortran and not has_d:
global issued_mixed_link_warning
if not issued_mixed_link_warning:
msg = "Using $CXX to link Fortran and C++ code together.\n\t" + \
"This may generate a buggy executable if the '%s'\n\t" + \
"compiler does not know how to deal with Fortran runtimes."
SCons.Warnings.warn(SCons.Warnings.FortranCxxMixWarning,
msg % env.subst('$CXX'))
issued_mixed_link_warning = True
return '$CXX'
elif has_d:
env['LINKCOM'] = env['DLINKCOM']
env['SHLINKCOM'] = env['SHDLINKCOM']
return '$DC'
elif has_fortran:
return '$FORTRAN'
elif has_cplusplus:
return '$CXX'
return '$CC'
def _lib_emitter(target, source, env, **kw):
Verbose = False
if Verbose:
print("_lib_emitter: target[0]={:r}".format(target[0].get_path()))
for tgt in target:
tgt.attributes.shared = 1
try:
symlink_generator = kw['symlink_generator']
except KeyError:
pass
else:
if Verbose:
print("_lib_emitter: symlink_generator={:r}".format(symlink_generator))
symlinks = symlink_generator(env, target[0])
if Verbose:
print("_lib_emitter: symlinks={:r}".format(symlinks))
if symlinks:
SCons.Tool.EmitLibSymlinks(env, symlinks, target[0])
target[0].attributes.shliblinks = symlinks
return (target, source)
def shlib_emitter(target, source, env):
return _lib_emitter(target, source, env, symlink_generator = SCons.Tool.ShLibSymlinkGenerator)
def ldmod_emitter(target, source, env):
return _lib_emitter(target, source, env, symlink_generator = SCons.Tool.LdModSymlinkGenerator)
# This is generic enough to be included here...
def _versioned_lib_name(env, libnode, version, prefix, suffix, prefix_generator, suffix_generator, **kw):
"""For libnode='/optional/dir/libfoo.so.X.Y.Z' it returns 'libfoo.so'"""
Verbose = False
if Verbose:
print("_versioned_lib_name: libnode={:r}".format(libnode.get_path()))
print("_versioned_lib_name: version={:r}".format(version))
print("_versioned_lib_name: prefix={:r}".format(prefix))
print("_versioned_lib_name: suffix={:r}".format(suffix))
print("_versioned_lib_name: suffix_generator={:r}".format(suffix_generator))
versioned_name = os.path.basename(libnode.get_path())
if Verbose:
print("_versioned_lib_name: versioned_name={:r}".format(versioned_name))
versioned_prefix = prefix_generator(env, **kw)
versioned_suffix = suffix_generator(env, **kw)
if Verbose:
print("_versioned_lib_name: versioned_prefix={:r}".format(versioned_prefix))
print("_versioned_lib_name: versioned_suffix={:r}".format(versioned_suffix))
versioned_prefix_re = '^' + re.escape(versioned_prefix)
versioned_suffix_re = re.escape(versioned_suffix) + '$'
name = re.sub(versioned_prefix_re, prefix, versioned_name)
name = re.sub(versioned_suffix_re, suffix, name)
if Verbose:
print("_versioned_lib_name: name={:r}".format(name))
return name
def _versioned_shlib_name(env, libnode, version, prefix, suffix, **kw):
pg = SCons.Tool.ShLibPrefixGenerator
sg = SCons.Tool.ShLibSuffixGenerator
return _versioned_lib_name(env, libnode, version, prefix, suffix, pg, sg, **kw)
def _versioned_ldmod_name(env, libnode, version, prefix, suffix, **kw):
pg = SCons.Tool.LdModPrefixGenerator
sg = SCons.Tool.LdModSuffixGenerator
return _versioned_lib_name(env, libnode, version, prefix, suffix, pg, sg, **kw)
def _versioned_lib_suffix(env, suffix, version):
"""For suffix='.so' and version='0.1.2' it returns '.so.0.1.2'"""
Verbose = False
if Verbose:
print("_versioned_lib_suffix: suffix={:r}".format(suffix))
print("_versioned_lib_suffix: version={:r}".format(version))
if not suffix.endswith(version):
suffix = suffix + '.' + version
if Verbose:
print("_versioned_lib_suffix: return suffix={:r}".format(suffix))
return suffix
def _versioned_lib_soname(env, libnode, version, prefix, suffix, name_func):
"""For libnode='/optional/dir/libfoo.so.X.Y.Z' it returns 'libfoo.so.X'"""
Verbose = False
if Verbose:
print("_versioned_lib_soname: version={:r}".format(version))
name = name_func(env, libnode, version, prefix, suffix)
if Verbose:
print("_versioned_lib_soname: name={:r}".format(name))
major = version.split('.')[0]
soname = name + '.' + major
if Verbose:
print("_versioned_lib_soname: soname={:r}".format(soname))
return soname
def _versioned_shlib_soname(env, libnode, version, prefix, suffix):
return _versioned_lib_soname(env, libnode, version, prefix, suffix, _versioned_shlib_name)
def _versioned_ldmod_soname(env, libnode, version, prefix, suffix):
return _versioned_lib_soname(env, libnode, version, prefix, suffix, _versioned_ldmod_name)
def _versioned_lib_symlinks(env, libnode, version, prefix, suffix, name_func, soname_func):
"""Generate link names that should be created for a versioned shared lirbrary.
Returns a dictionary in the form { linkname : linktarget }
"""
Verbose = False
if Verbose:
print("_versioned_lib_symlinks: libnode={:r}".format(libnode.get_path()))
print("_versioned_lib_symlinks: version={:r}".format(version))
if sys.platform.startswith('openbsd'):
# OpenBSD uses x.y shared library versioning numbering convention
# and doesn't use symlinks to backwards-compatible libraries
if Verbose:
print("_versioned_lib_symlinks: return symlinks={:r}".format(None))
return None
linkdir = libnode.get_dir()
if Verbose:
print("_versioned_lib_symlinks: linkdir={:r}".format(linkdir.get_path()))
name = name_func(env, libnode, version, prefix, suffix)
if Verbose:
print("_versioned_lib_symlinks: name={:r}".format(name))
soname = soname_func(env, libnode, version, prefix, suffix)
link0 = env.fs.File(soname, linkdir)
link1 = env.fs.File(name, linkdir)
# We create direct symlinks, not daisy-chained.
if link0 == libnode:
# This enables SHLIBVERSION without periods (e.g. SHLIBVERSION=1)
symlinks = [ (link1, libnode) ]
else:
# This handles usual SHLIBVERSION, i.e. '1.2', '1.2.3', etc.
symlinks = [ (link0, libnode), (link1, libnode) ]
if Verbose:
print("_versioned_lib_symlinks: return symlinks={:r}".format(SCons.Tool.StringizeLibSymlinks(symlinks)))
return symlinks
def _versioned_shlib_symlinks(env, libnode, version, prefix, suffix):
nf = _versioned_shlib_name
sf = _versioned_shlib_soname
return _versioned_lib_symlinks(env, libnode, version, prefix, suffix, nf, sf)
def _versioned_ldmod_symlinks(env, libnode, version, prefix, suffix):
nf = _versioned_ldmod_name
sf = _versioned_ldmod_soname
return _versioned_lib_symlinks(env, libnode, version, prefix, suffix, nf, sf)
def _versioned_lib_callbacks():
return {
'VersionedShLibSuffix' : _versioned_lib_suffix,
'VersionedLdModSuffix' : _versioned_lib_suffix,
'VersionedShLibSymlinks' : _versioned_shlib_symlinks,
'VersionedLdModSymlinks' : _versioned_ldmod_symlinks,
'VersionedShLibName' : _versioned_shlib_name,
'VersionedLdModName' : _versioned_ldmod_name,
'VersionedShLibSoname' : _versioned_shlib_soname,
'VersionedLdModSoname' : _versioned_ldmod_soname,
}.copy()
def _setup_versioned_lib_variables(env, **kw):
"""
Setup all variables required by the versioning machinery
"""
tool = None
try: tool = kw['tool']
except KeyError: pass
use_soname = False
try: use_soname = kw['use_soname']
except KeyError: pass
# The $_SHLIBVERSIONFLAGS define extra commandline flags used when
# building VERSIONED shared libraries. It's always set, but used only
# when VERSIONED library is built (see __SHLIBVERSIONFLAGS in SCons/Defaults.py).
if use_soname:
# If the linker uses SONAME, then we need this little automata
if tool == 'sunlink':
env['_SHLIBVERSIONFLAGS'] = '$SHLIBVERSIONFLAGS -h $_SHLIBSONAME'
env['_LDMODULEVERSIONFLAGS'] = '$LDMODULEVERSIONFLAGS -h $_LDMODULESONAME'
else:
env['_SHLIBVERSIONFLAGS'] = '$SHLIBVERSIONFLAGS -Wl,-soname=$_SHLIBSONAME'
env['_LDMODULEVERSIONFLAGS'] = '$LDMODULEVERSIONFLAGS -Wl,-soname=$_LDMODULESONAME'
env['_SHLIBSONAME'] = '${ShLibSonameGenerator(__env__,TARGET)}'
env['_LDMODULESONAME'] = '${LdModSonameGenerator(__env__,TARGET)}'
env['ShLibSonameGenerator'] = SCons.Tool.ShLibSonameGenerator
env['LdModSonameGenerator'] = SCons.Tool.LdModSonameGenerator
else:
env['_SHLIBVERSIONFLAGS'] = '$SHLIBVERSIONFLAGS'
env['_LDMODULEVERSIONFLAGS'] = '$LDMODULEVERSIONFLAGS'
# LDOMDULVERSIONFLAGS should always default to $SHLIBVERSIONFLAGS
env['LDMODULEVERSIONFLAGS'] = '$SHLIBVERSIONFLAGS'
def generate(env):
"""Add Builders and construction variables for gnulink to an Environment."""
SCons.Tool.createSharedLibBuilder(env)
SCons.Tool.createProgBuilder(env)
env['SHLINK'] = '$LINK'
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared')
env['SHLINKCOM'] = '$SHLINK -o $TARGET $SHLINKFLAGS $__SHLIBVERSIONFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
# don't set up the emitter, cause AppendUnique will generate a list
# starting with None :-(
env.Append(SHLIBEMITTER = [shlib_emitter])
env['SMARTLINK'] = smart_link
env['LINK'] = "$SMARTLINK"
env['LINKFLAGS'] = SCons.Util.CLVar('')
# __RPATH is only set to something ($_RPATH typically) on platforms that support it.
env['LINKCOM'] = '$LINK -o $TARGET $LINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
env['LIBDIRPREFIX']='-L'
env['LIBDIRSUFFIX']=''
env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}'
env['LIBLINKPREFIX']='-l'
env['LIBLINKSUFFIX']=''
if env['PLATFORM'] == 'hpux':
env['SHLIBSUFFIX'] = '.sl'
elif env['PLATFORM'] == 'aix':
env['SHLIBSUFFIX'] = '.a'
# For most platforms, a loadable module is the same as a shared
# library. Platforms which are different can override these, but
# setting them the same means that LoadableModule works everywhere.
SCons.Tool.createLoadableModuleBuilder(env)
env['LDMODULE'] = '$SHLINK'
env.Append(LDMODULEEMITTER = [ldmod_emitter])
env['LDMODULEPREFIX'] = '$SHLIBPREFIX'
env['LDMODULESUFFIX'] = '$SHLIBSUFFIX'
env['LDMODULEFLAGS'] = '$SHLINKFLAGS'
env['LDMODULECOM'] = '$LDMODULE -o $TARGET $LDMODULEFLAGS $__LDMODULEVERSIONFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
env['LDMODULEVERSION'] = '$SHLIBVERSION'
env['LDMODULENOVERSIONSYMLINKS'] = '$SHLIBNOVERSIONSYMLINKS'
def exists(env):
# This module isn't really a Tool on its own, it's common logic for
# other linkers.
return None
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View file

@ -1,92 +0,0 @@
"""SCons.Tool.zip
Tool-specific initialization for zip.
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/zip.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import os.path
import SCons.Builder
import SCons.Defaults
import SCons.Node.FS
import SCons.Util
import zipfile
zipcompression = zipfile.ZIP_DEFLATED
def zip(target, source, env):
compression = env.get('ZIPCOMPRESSION', 0)
zf = zipfile.ZipFile(str(target[0]), 'w', compression)
for s in source:
if s.isdir():
for dirpath, dirnames, filenames in os.walk(str(s)):
for fname in filenames:
path = os.path.join(dirpath, fname)
if os.path.isfile(path):
zf.write(path, os.path.relpath(path, str(env.get('ZIPROOT', ''))))
else:
zf.write(str(s), os.path.relpath(str(s), str(env.get('ZIPROOT', ''))))
zf.close()
zipAction = SCons.Action.Action(zip, varlist=['ZIPCOMPRESSION'])
ZipBuilder = SCons.Builder.Builder(action = SCons.Action.Action('$ZIPCOM', '$ZIPCOMSTR'),
source_factory = SCons.Node.FS.Entry,
source_scanner = SCons.Defaults.DirScanner,
suffix = '$ZIPSUFFIX',
multi = 1)
def generate(env):
"""Add Builders and construction variables for zip to an Environment."""
try:
bld = env['BUILDERS']['Zip']
except KeyError:
bld = ZipBuilder
env['BUILDERS']['Zip'] = bld
env['ZIP'] = 'zip'
env['ZIPFLAGS'] = SCons.Util.CLVar('')
env['ZIPCOM'] = zipAction
env['ZIPCOMPRESSION'] = zipcompression
env['ZIPSUFFIX'] = '.zip'
env['ZIPROOT'] = SCons.Util.CLVar('')
def exists(env):
return True
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:

View file

@ -1,212 +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.
#
__doc__ = """
SCons compatibility package for old Python versions
This subpackage holds modules that provide backwards-compatible
implementations of various things that we'd like to use in SCons but which
only show up in later versions of Python than the early, old version(s)
we still support.
Other code will not generally reference things in this package through
the SCons.compat namespace. The modules included here add things to
the builtins namespace or the global module list so that the rest
of our code can use the objects and names imported here regardless of
Python version.
The rest of the things here will be in individual compatibility modules
that are either: 1) suitably modified copies of the future modules that
we want to use; or 2) backwards compatible re-implementations of the
specific portions of a future module's API that we want to use.
GENERAL WARNINGS: Implementations of functions in the SCons.compat
modules are *NOT* guaranteed to be fully compliant with these functions in
later versions of Python. We are only concerned with adding functionality
that we actually use in SCons, so be wary if you lift this code for
other uses. (That said, making these more nearly the same as later,
official versions is still a desirable goal, we just don't need to be
obsessive about it.)
We name the compatibility modules with an initial '_scons_' (for example,
_scons_subprocess.py is our compatibility module for subprocess) so
that we can still try to import the real module name and fall back to
our compatibility module if we get an ImportError. The import_as()
function defined below loads the module as the "real" name (without the
'_scons'), after which all of the "import {module}" statements in the
rest of our code will find our pre-loaded compatibility module.
"""
__revision__ = "src/engine/SCons/compat/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import os
import sys
import imp # Use the "imp" module to protect imports from fixers.
PYPY = hasattr(sys, 'pypy_translation_info')
def import_as(module, name):
"""
Imports the specified module (from our local directory) as the
specified name, returning the loaded module object.
"""
dir = os.path.split(__file__)[0]
return imp.load_module(name, *imp.find_module(module, [dir]))
def rename_module(new, old):
"""
Attempts to import the old module and load it under the new name.
Used for purely cosmetic name changes in Python 3.x.
"""
try:
sys.modules[new] = imp.load_module(old, *imp.find_module(old))
return True
except ImportError:
return False
# TODO: FIXME
# In 3.x, 'pickle' automatically loads the fast version if available.
rename_module('pickle', 'cPickle')
# Default pickle protocol. Higher protocols are more efficient/featureful
# but incompatible with older Python versions. On Python 2.7 this is 2.
# Negative numbers choose the highest available protocol.
import pickle
# Was pickle.HIGHEST_PROTOCOL
# Changed to 2 so py3.5+'s pickle will be compatible with py2.7.
PICKLE_PROTOCOL = 2
# TODO: FIXME
# In 3.x, 'profile' automatically loads the fast version if available.
rename_module('profile', 'cProfile')
# TODO: FIXME
# Before Python 3.0, the 'queue' module was named 'Queue'.
rename_module('queue', 'Queue')
# TODO: FIXME
# Before Python 3.0, the 'winreg' module was named '_winreg'
rename_module('winreg', '_winreg')
# Python 3 moved builtin intern() to sys package
# To make porting easier, make intern always live
# in sys package (for python 2.7.x)
try:
sys.intern
except AttributeError:
# We must be using python 2.7.x so monkey patch
# intern into the sys package
sys.intern = intern
# Preparing for 3.x. UserDict, UserList, UserString are in
# collections for 3.x, but standalone in 2.7.x
import collections
try:
collections.UserDict
except AttributeError:
exec ('from UserDict import UserDict as _UserDict')
collections.UserDict = _UserDict
del _UserDict
try:
collections.UserList
except AttributeError:
exec ('from UserList import UserList as _UserList')
collections.UserList = _UserList
del _UserList
try:
collections.UserString
except AttributeError:
exec ('from UserString import UserString as _UserString')
collections.UserString = _UserString
del _UserString
import shutil
try:
shutil.SameFileError
except AttributeError:
class SameFileError(Exception):
pass
shutil.SameFileError = SameFileError
def with_metaclass(meta, *bases):
"""
Function from jinja2/_compat.py. License: BSD.
Use it like this::
class BaseForm(object):
pass
class FormType(type):
pass
class Form(with_metaclass(FormType, BaseForm)):
pass
This requires a bit of explanation: the basic idea is to make a
dummy metaclass for one level of class instantiation that replaces
itself with the actual metaclass. Because of internal type checks
we also need to make sure that we downgrade the custom metaclass
for one level to something closer to type (that's why __call__ and
__init__ comes back from type etc.).
This has the advantage over six.with_metaclass of not introducing
dummy classes into the final MRO.
"""
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, this_bases, d):
if this_bases is None:
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass('temporary_class', None, {})
class NoSlotsPyPy(type):
"""
Workaround for PyPy not working well with __slots__ and __class__ assignment.
"""
def __new__(meta, name, bases, dct):
if PYPY and '__slots__' in dct:
dct.pop('__slots__')
return super(NoSlotsPyPy, meta).__new__(meta, name, bases, dct)
# 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

@ -1,11 +1,34 @@
"""SCons.Action # MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This encapsulates information about executing any sort of action that """SCons Actions.
Information about executing any sort of action that
can build one or more target Nodes (typically files) from one or more can build one or more target Nodes (typically files) from one or more
source Nodes (also typically files) given a specific Environment. source Nodes (also typically files) given a specific Environment.
The base class here is ActionBase. The base class supplies just a few The base class here is ActionBase. The base class supplies just a few
OO utility methods and some generic methods for displaying information utility methods and some generic methods for displaying information
about an Action in response to the various commands that control printing. about an Action in response to the various commands that control printing.
A second-level base class is _ActionAction. This extends ActionBase A second-level base class is _ActionAction. This extends ActionBase
@ -60,7 +83,7 @@ this module:
get_presig() get_presig()
Fetches the "contents" of a subclass for signature calculation. Fetches the "contents" of a subclass for signature calculation.
The varlist is added to this to produce the Action's contents. The varlist is added to this to produce the Action's contents.
TODO(?): Change this to always return ascii/bytes and not unicode (or py3 strings) TODO(?): Change this to always return bytes and not str?
strfunction() strfunction()
Returns a substituted string representation of the Action. Returns a substituted string representation of the Action.
@ -77,36 +100,15 @@ way for wrapping up the functions.
""" """
# 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/Action.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
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

@ -1,3 +1,26 @@
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
""" """
SCons.Builder SCons.Builder
@ -76,31 +99,7 @@ There are the following methods for internal use within this module:
""" """
# from collections import UserDict, UserList
# 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/Builder.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import collections
import SCons.Action import SCons.Action
import SCons.Debug import SCons.Debug
@ -111,7 +110,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 +196,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 +214,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 +223,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 +273,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 +292,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 +308,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 +327,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 +360,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 +395,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 +420,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
@ -434,11 +430,8 @@ class BuilderBase(object):
src_builder = [ src_builder ] src_builder = [ src_builder ]
self.src_builder = src_builder self.src_builder = src_builder
def __nonzero__(self):
raise InternalError("Do not test for the Node.builder attribute directly; use Node.has_builder() instead")
def __bool__(self): def __bool__(self):
return self.__nonzero__() raise InternalError("Do not test for the Node.builder attribute directly; use Node.has_builder() instead")
def get_name(self, env): def get_name(self, env):
"""Attempts to get the name of the Builder. """Attempts to get the name of the Builder.
@ -507,6 +500,7 @@ class BuilderBase(object):
splitext = lambda S: self.splitext(S,env) splitext = lambda S: self.splitext(S,env)
tlist = [ t_from_s(pre, suf, splitext) ] tlist = [ t_from_s(pre, suf, splitext) ]
else: else:
# orig_target = target
target = self._adjustixes(target, pre, suf, self.ensure_suffix) target = self._adjustixes(target, pre, suf, self.ensure_suffix)
tlist = env.arg2nodes(target, target_factory, target=target, source=source) tlist = env.arg2nodes(target, target_factory, target=target, source=source)
@ -554,8 +548,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 +559,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 +644,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 +749,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,6 @@
# MIT License
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -19,20 +20,18 @@
# 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.
#
__revision__ = "src/engine/SCons/CacheDir.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """CacheDir support
__doc__ = """
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 +44,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 +111,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 +132,96 @@ 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,
# produce a warning. If the directory doesn't exist or is empty,
# write a config file.
config_file = os.path.join(path, 'config')
if not os.path.exists(config_file):
# A note: There is a race hazard here, if two processes start and
# attempt to create the cache directory at the same time. However,
# python doesn't really give you the option to do exclusive file
# creation (it doesn't even give you the option to error on opening
# an existing file for writing...). The ordering of events here
# as an attempt to alleviate this, on the basis that it's a pretty
# unlikely occurence (it'd require two builds with a brand new cache
# directory)
if os.path.isdir(path) and len(os.listdir(path)) != 0:
self.config['prefix_len'] = 1
# When building the project I was testing this on, the warning
# was output over 20 times. That seems excessive
global warned
if self.path not in warned:
msg = "Please upgrade your cache by running " +\
" scons-configure-cache.py " + self.path
SCons.Warnings.warn(SCons.Warnings.CacheVersionWarning, msg)
warned[self.path] = True
else:
if not os.path.isdir(path):
try:
os.makedirs(path)
except OSError:
# If someone else is trying to create the directory at
# the same time as me, bad things will happen
msg = "Failed to create cache directory " + path
raise SCons.Errors.EnvironmentError(msg)
self._readconfig(path)
def _readconfig(self, path):
"""
Read the cache config.
If directory or config file do not exist, create. Take advantage
of Py3 capability in os.makedirs() and in file open(): just try
the operation and handle failure appropriately.
Omit the check for old cache format, assume that's old enough
there will be none of those left to worry about.
:param path: path to the cache directory
"""
config_file = os.path.join(path, 'config')
try:
os.makedirs(path, exist_ok=True)
except FileExistsError:
pass
except OSError:
msg = "Failed to create cache directory " + path
raise SCons.Errors.SConsEnvironmentError(msg)
try:
with open(config_file, 'x') as config:
self.config['prefix_len'] = 2 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 +233,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

@ -1,9 +1,6 @@
"""SCons.Conftest # MIT License
Autoconf-like configuration support; low level implementation of tests.
"""
# #
# Copyright The SCons Foundation
# Copyright (c) 2003 Stichting NLnet Labs # Copyright (c) 2003 Stichting NLnet Labs
# Copyright (c) 2001, 2002, 2003 Steven Knight # Copyright (c) 2001, 2002, 2003 Steven Knight
# #
@ -25,80 +22,72 @@ Autoconf-like configuration support; low level implementation of tests.
# 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.
#
# """Autoconf-like configuration support
# The purpose of this module is to define how a check is to be performed.
# Use one of the Check...() functions below.
#
# The purpose of this module is to define how a check is to be performed.
# A context class is used that defines functions for carrying out the tests,
# logging and messages. The following methods and members must be present: A context class is used that defines functions for carrying out the tests,
# logging and messages. The following methods and members must be present:
# context.Display(msg) Function called to print messages that are normally
# displayed for the user. Newlines are explicitly used. context.Display(msg)
# The text should also be written to the logfile! Function called to print messages that are normally displayed
# for the user. Newlines are explicitly used. The text should
# context.Log(msg) Function called to write to a log file. also be written to the logfile!
#
# context.BuildProg(text, ext) context.Log(msg)
# Function called to build a program, using "ext" for the Function called to write to a log file.
# file extention. Must return an empty string for
# success, an error message for failure. context.BuildProg(text, ext)
# For reliable test results building should be done just Function called to build a program, using "ext" for the file
# like an actual program would be build, using the same extension. Must return an empty string for success, an error
# command and arguments (including configure results so message for failure. For reliable test results building should
# far). be done just like an actual program would be build, using the
# same command and arguments (including configure results so far).
# context.CompileProg(text, ext)
# Function called to compile a program, using "ext" for context.CompileProg(text, ext)
# the file extention. Must return an empty string for Function called to compile a program, using "ext" for the file
# success, an error message for failure. extension. Must return an empty string for success, an error
# For reliable test results compiling should be done just message for failure. For reliable test results compiling should be
# like an actual source file would be compiled, using the done just like an actual source file would be compiled, using the
# same command and arguments (including configure results same command and arguments (including configure results so far).
# so far).
# context.AppendLIBS(lib_name_list)
# context.AppendLIBS(lib_name_list) Append "lib_name_list" to the value of LIBS. "lib_namelist" is
# Append "lib_name_list" to the value of LIBS. a list of strings. Return the value of LIBS before changing it
# "lib_namelist" is a list of strings. (any type can be used, it is passed to SetLIBS() later.)
# Return the value of LIBS before changing it (any type
# can be used, it is passed to SetLIBS() later.) context.PrependLIBS(lib_name_list)
# Prepend "lib_name_list" to the value of LIBS. "lib_namelist" is
# context.PrependLIBS(lib_name_list) a list of strings. Return the value of LIBS before changing it
# Prepend "lib_name_list" to the value of LIBS. (any type can be used, it is passed to SetLIBS() later.)
# "lib_namelist" is a list of strings.
# Return the value of LIBS before changing it (any type context.SetLIBS(value)
# can be used, it is passed to SetLIBS() later.) Set LIBS to "value". The type of "value" is what AppendLIBS()
# returned. Return the value of LIBS before changing it (any type
# context.SetLIBS(value) can be used, it is passed to SetLIBS() later.)
# Set LIBS to "value". The type of "value" is what
# AppendLIBS() returned. context.headerfilename
# Return the value of LIBS before changing it (any type Name of file to append configure results to, usually "confdefs.h".
# can be used, it is passed to SetLIBS() later.) The file must not exist or be empty when starting. Empty or None
# to skip this (some tests will not work!).
# context.headerfilename
# Name of file to append configure results to, usually context.config_h (may be missing).
# "confdefs.h". If present, must be a string, which will be filled with the
# The file must not exist or be empty when starting. contents of a config_h file.
# Empty or None to skip this (some tests will not work!).
# context.vardict
# context.config_h (may be missing). If present, must be a string, which Dictionary holding variables used for the tests and stores results
# will be filled with the contents of a config_h file. from the tests, used for the build commands. Normally contains
# "CC", "LIBS", "CPPFLAGS", etc.
# context.vardict Dictionary holding variables used for the tests and
# stores results from the tests, used for the build context.havedict
# commands. Dictionary holding results from the tests that are to be used
# Normally contains "CC", "LIBS", "CPPFLAGS", etc. inside a program. Names often start with "HAVE_". These are zero
# (feature not present) or one (feature present). Other variables
# context.havedict Dictionary holding results from the tests that are to may have any value, e.g., "PERLVERSION" can be a number and
# be used inside a program. "SYSTEMNAME" a string.
# Names often start with "HAVE_". These are zero """
# (feature not present) or one (feature present). Other
# variables may have any value, e.g., "PERLVERSION" can
# be a number and "SYSTEMNAME" a string.
#
import re import re
@ -136,7 +125,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 +146,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 +166,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 +186,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 +206,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 +279,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 +304,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
@ -400,7 +393,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))
@ -455,7 +448,7 @@ def CheckTypeSize(context, type_name, header = None, language = None, expect = N
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 +458,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;
@ -498,7 +491,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;
} }
@ -560,7 +553,7 @@ def CheckDeclaration(context, symbol, includes = None, language = None):
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;
@ -704,7 +697,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 +716,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:

View file

@ -1,15 +1,6 @@
"""SCons.Debug # MIT License
Code for debugging SCons internal things. Shouldn't be
needed by most users. Quick shortcuts:
from SCons.Debug import caller_trace
caller_trace()
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -29,10 +20,16 @@ caller_trace()
# 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.
#
__revision__ = "src/engine/SCons/Debug.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Code for debugging SCons internal things.
Shouldn't be needed by most users. Quick shortcuts:
from SCons.Debug import caller_trace
caller_trace()
"""
import atexit
import os import os
import sys import sys
import time import time
@ -93,7 +90,6 @@ def dumpLoggedInstances(classes, file=sys.stdout):
file.write(' %20s : %s\n' % (key, value)) file.write(' %20s : %s\n' % (key, value))
if sys.platform[:5] == "linux": if sys.platform[:5] == "linux":
# Linux doesn't actually support memory usage stats from getrusage(). # Linux doesn't actually support memory usage stats from getrusage().
def memory(): def memory():
@ -105,28 +101,23 @@ elif sys.platform[:6] == 'darwin':
#TODO really get memory stats for OS X #TODO really get memory stats for OS X
def memory(): def memory():
return 0 return 0
elif sys.platform == 'win32':
from SCons.compat.win32 import get_peak_memory_usage
memory = get_peak_memory_usage
else: else:
try: try:
import resource import resource
except ImportError: except ImportError:
try: def memory():
import win32process return 0
import win32api
except ImportError:
def memory():
return 0
else:
def memory():
process_handle = win32api.GetCurrentProcess()
memory_info = win32process.GetProcessMemoryInfo( process_handle )
return memory_info['PeakWorkingSetSize']
else: else:
def memory(): def memory():
res = resource.getrusage(resource.RUSAGE_SELF) res = resource.getrusage(resource.RUSAGE_SELF)
return res[4] return res[4]
# returns caller's stack
def caller_stack(): def caller_stack():
"""return caller's stack"""
import traceback import traceback
tb = traceback.extract_stack() tb = traceback.extract_stack()
# strip itself and the caller from the output # strip itself and the caller from the output
@ -201,40 +192,57 @@ 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, tracefile=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 tracefile argument persists
across calls unless overridden.
Args:
tracefile: file to write trace message to. If omitted,
write to the previous trace file (default: console).
mode: file open mode (default: 'w')
tstamp: write relative timestamps with trace. Outputs time since
scons was started, and time since last trace (default: False)
"""
global TraceDefault global TraceDefault
global TimeStampDefault global TimeStampDefault
global PreviousTime global PreviousTime
if file is None:
file = TraceDefault def trace_cleanup(traceFP):
traceFP.close()
if tracefile is None:
tracefile = TraceDefault
else: else:
TraceDefault = file TraceDefault = tracefile
if tstamp is None: if not tstamp:
tstamp = TimeStampDefault tstamp = TimeStampDefault
else: else:
TimeStampDefault = tstamp TimeStampDefault = tstamp
try: try:
fp = TraceFP[file] fp = TraceFP[tracefile]
except KeyError: except KeyError:
try: try:
fp = TraceFP[file] = open(file, mode) fp = TraceFP[tracefile] = open(tracefile, 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 = tracefile
if tstamp: if tstamp:
now = time.time() now = time.time()
fp.write('%8.4f %8.4f: ' % (now - StartTime, now - PreviousTime)) fp.write('%8.4f %8.4f: ' % (now - StartTime, now - PreviousTime))
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

@ -1,16 +1,6 @@
"""SCons.Defaults # MIT License
Builders and other things for the local site. Here's where we'll
duplicate the functionality of autoconf until we move it into the
installation procedure or use something like qmconf.
The code that reads the registry to find MSVC components was borrowed
from distutils.msvccompiler.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -31,17 +21,22 @@ 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__ = "src/engine/SCons/Defaults.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Builders and other things for the local site.
Here's where we'll duplicate the functionality of autoconf until we
move it into the installation procedure or use something like qmconf.
The code that reads the registry to find MSVC components was borrowed
from distutils.msvccompiler.
"""
import os
import errno import errno
import os
import shutil import shutil
import stat import stat
import time
import sys import sys
import time
import SCons.Action import SCons.Action
import SCons.Builder import SCons.Builder
@ -50,6 +45,7 @@ import SCons.Environment
import SCons.PathList import SCons.PathList
import SCons.Subst import SCons.Subst
import SCons.Tool import SCons.Tool
import SCons.Scanner.Dir
# A placeholder for a default Environment (for fetching source files # A placeholder for a default Environment (for fetching source files
# from source code management systems and the like). This must be # from source code management systems and the like). This must be
@ -57,15 +53,15 @@ import SCons.Tool
# interface. # interface.
_default_env = None _default_env = None
# Lazily instantiate the default environment so the overhead of creating # Lazily instantiate the default environment so the overhead of creating
# it doesn't apply when it's not needed. # it doesn't apply when it's not needed.
def _fetch_DefaultEnvironment(*args, **kw): def _fetch_DefaultEnvironment(*args, **kw):
""" """Returns the already-created default construction environment."""
Returns the already-created default construction environment.
"""
global _default_env global _default_env
return _default_env return _default_env
def DefaultEnvironment(*args, **kw): def DefaultEnvironment(*args, **kw):
""" """
Initial public entry point for creating the default construction Initial public entry point for creating the default construction
@ -95,6 +91,7 @@ def DefaultEnvironment(*args, **kw):
_default_env._CacheDir_path = None _default_env._CacheDir_path = None
return _default_env return _default_env
# Emitters for setting the shared attribute on object files, # Emitters for setting the shared attribute on object files,
# and an action for checking that all of the source files # and an action for checking that all of the source files
# going into a shared library are, in fact, shared. # going into a shared library are, in fact, shared.
@ -103,11 +100,13 @@ def StaticObjectEmitter(target, source, env):
tgt.attributes.shared = None tgt.attributes.shared = None
return (target, source) return (target, source)
def SharedObjectEmitter(target, source, env): def SharedObjectEmitter(target, source, env):
for tgt in target: for tgt in target:
tgt.attributes.shared = 1 tgt.attributes.shared = 1
return (target, source) return (target, source)
def SharedFlagChecker(source, target, env): def SharedFlagChecker(source, target, env):
same = env.subst('$STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME') same = env.subst('$STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME')
if same == '0' or same == '' or same == 'False': if same == '0' or same == '' or same == 'False':
@ -117,7 +116,9 @@ def SharedFlagChecker(source, target, env):
except AttributeError: except AttributeError:
shared = None shared = None
if not shared: if not shared:
raise SCons.Errors.UserError("Source file: %s is static and is not compatible with shared target: %s" % (src, target[0])) raise SCons.Errors.UserError(
"Source file: %s is static and is not compatible with shared target: %s" % (src, target[0]))
SharedCheck = SCons.Action.Action(SharedFlagChecker, None) SharedCheck = SCons.Action.Action(SharedFlagChecker, None)
@ -134,7 +135,7 @@ ProgScan = SCons.Tool.ProgramScanner
# These aren't really tool scanners, so they don't quite belong with # These aren't really tool scanners, so they don't quite belong with
# the rest of those in Tool/__init__.py, but I'm not sure where else # the rest of those in Tool/__init__.py, but I'm not sure where else
# they should go. Leave them here for now. # they should go. Leave them here for now.
import SCons.Scanner.Dir
DirScanner = SCons.Scanner.Dir.DirScanner() DirScanner = SCons.Scanner.Dir.DirScanner()
DirEntryScanner = SCons.Scanner.Dir.DirEntryScanner() DirEntryScanner = SCons.Scanner.Dir.DirEntryScanner()
@ -159,6 +160,7 @@ LdModuleLinkAction = SCons.Action.Action("$LDMODULECOM", "$LDMODULECOMSTR")
# ways by creating ActionFactory instances. # ways by creating ActionFactory instances.
ActionFactory = SCons.Action.ActionFactory ActionFactory = SCons.Action.ActionFactory
def get_paths_str(dest): def get_paths_str(dest):
# If dest is a list, we need to manually call str() on each element # If dest is a list, we need to manually call str() on each element
if SCons.Util.is_List(dest): if SCons.Util.is_List(dest):
@ -169,31 +171,33 @@ def get_paths_str(dest):
else: else:
return '"' + str(dest) + '"' return '"' + str(dest) + '"'
permission_dic = { permission_dic = {
'u':{ 'u': {
'r':stat.S_IRUSR, 'r': stat.S_IRUSR,
'w':stat.S_IWUSR, 'w': stat.S_IWUSR,
'x':stat.S_IXUSR 'x': stat.S_IXUSR
}, },
'g':{ 'g': {
'r':stat.S_IRGRP, 'r': stat.S_IRGRP,
'w':stat.S_IWGRP, 'w': stat.S_IWGRP,
'x':stat.S_IXGRP 'x': stat.S_IXGRP
}, },
'o':{ 'o': {
'r':stat.S_IROTH, 'r': stat.S_IROTH,
'w':stat.S_IWOTH, 'w': stat.S_IWOTH,
'x':stat.S_IXOTH 'x': stat.S_IXOTH
} }
} }
def chmod_func(dest, mode): def chmod_func(dest, mode):
import SCons.Util import SCons.Util
from string import digits from string import digits
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 +214,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()
@ -230,6 +234,7 @@ def chmod_func(dest, mode):
elif operator == "-": elif operator == "-":
os.chmod(str(element), curr_perm & ~new_perm) os.chmod(str(element), curr_perm & ~new_perm)
def chmod_strfunc(dest, mode): def chmod_strfunc(dest, mode):
import SCons.Util import SCons.Util
if not SCons.Util.is_String(mode): if not SCons.Util.is_String(mode):
@ -237,8 +242,10 @@ def chmod_strfunc(dest, mode):
else: else:
return 'Chmod(%s, "%s")' % (get_paths_str(dest), str(mode)) return 'Chmod(%s, "%s")' % (get_paths_str(dest), str(mode))
Chmod = ActionFactory(chmod_func, chmod_strfunc) Chmod = ActionFactory(chmod_func, chmod_strfunc)
def copy_func(dest, src, symlinks=True): def copy_func(dest, src, symlinks=True):
""" """
If symlinks (is true), then a symbolic link will be If symlinks (is true), then a symbolic link will be
@ -269,11 +276,13 @@ def copy_func(dest, src, symlinks=True):
# A error is raised in both cases, so we can just return 0 for success # A error is raised in both cases, so we can just return 0 for success
return 0 return 0
Copy = ActionFactory( Copy = ActionFactory(
copy_func, copy_func,
lambda dest, src, symlinks=True: 'Copy("%s", "%s")' % (dest, src) lambda dest, src, symlinks=True: 'Copy("%s", "%s")' % (dest, src)
) )
def delete_func(dest, must_exist=0): def delete_func(dest, must_exist=0):
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):
@ -290,11 +299,14 @@ def delete_func(dest, must_exist=0):
continue continue
os.unlink(entry) os.unlink(entry)
def delete_strfunc(dest, must_exist=0): def delete_strfunc(dest, must_exist=0):
return 'Delete(%s)' % get_paths_str(dest) return 'Delete(%s)' % get_paths_str(dest)
Delete = ActionFactory(delete_func, delete_strfunc) Delete = ActionFactory(delete_func, delete_strfunc)
def mkdir_func(dest): def mkdir_func(dest):
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):
@ -305,24 +317,28 @@ def mkdir_func(dest):
except os.error as e: except os.error as e:
p = str(entry) p = str(entry)
if (e.args[0] == errno.EEXIST or if (e.args[0] == errno.EEXIST or
(sys.platform=='win32' and e.args[0]==183)) \ (sys.platform == 'win32' and e.args[0] == 183)) \
and os.path.isdir(str(entry)): and os.path.isdir(str(entry)):
pass # not an error if already exists pass # not an error if already exists
else: else:
raise raise
Mkdir = ActionFactory(mkdir_func, Mkdir = ActionFactory(mkdir_func,
lambda dir: 'Mkdir(%s)' % get_paths_str(dir)) lambda dir: 'Mkdir(%s)' % get_paths_str(dir))
def move_func(dest, src): def move_func(dest, src):
SCons.Node.FS.invalidate_node_memos(dest) SCons.Node.FS.invalidate_node_memos(dest)
SCons.Node.FS.invalidate_node_memos(src) SCons.Node.FS.invalidate_node_memos(src)
shutil.move(src, dest) shutil.move(src, dest)
Move = ActionFactory(move_func, Move = ActionFactory(move_func,
lambda dest, src: 'Move("%s", "%s")' % (dest, src), lambda dest, src: 'Move("%s", "%s")' % (dest, src),
convert=str) convert=str)
def touch_func(dest): def touch_func(dest):
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):
@ -333,15 +349,18 @@ 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,
lambda file: 'Touch(%s)' % get_paths_str(file)) lambda file: 'Touch(%s)' % get_paths_str(file))
# 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 +377,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
@ -391,10 +411,11 @@ def _concat_ixes(prefix, list, suffix, env):
if suffix[0] == ' ': if suffix[0] == ' ':
result.append(suffix[1:]) result.append(suffix[1:])
elif x[-len(suffix):] != suffix: elif x[-len(suffix):] != suffix:
result[-1] = result[-1]+suffix result[-1] = result[-1] + suffix
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
@ -447,6 +468,7 @@ def _stripixes(prefix, itms, suffix, stripprefixes, stripsuffixes, env, c=None):
return c(prefix, stripped, suffix, env) return c(prefix, stripped, suffix, env)
def processDefines(defs): def processDefines(defs):
"""process defines, resolving strings, lists, dictionaries, into a list of """process defines, resolving strings, lists, dictionaries, into a list of
strings strings
@ -462,7 +484,7 @@ def processDefines(defs):
else: else:
l.append(str(d[0])) l.append(str(d[0]))
elif SCons.Util.is_Dict(d): elif SCons.Util.is_Dict(d):
for macro,value in d.items(): for macro, value in d.items():
if value is not None: if value is not None:
l.append(str(macro) + '=' + str(value)) l.append(str(macro) + '=' + str(value))
else: else:
@ -470,7 +492,7 @@ def processDefines(defs):
elif SCons.Util.is_String(d): elif SCons.Util.is_String(d):
l.append(str(d)) l.append(str(d))
else: else:
raise SCons.Errors.UserError("DEFINE %s is not a list, dict, string or None."%repr(d)) raise SCons.Errors.UserError("DEFINE %s is not a list, dict, string or None." % repr(d))
elif SCons.Util.is_Dict(defs): elif SCons.Util.is_Dict(defs):
# The items in a dictionary are stored in random order, but # The items in a dictionary are stored in random order, but
# if the order of the command-line options changes from # if the order of the command-line options changes from
@ -479,7 +501,7 @@ def processDefines(defs):
# Consequently, we have to sort the keys to ensure a # Consequently, we have to sort the keys to ensure a
# consistent order... # consistent order...
l = [] l = []
for k,v in sorted(defs.items()): for k, v in sorted(defs.items()):
if v is None: if v is None:
l.append(str(k)) l.append(str(k))
else: else:
@ -497,7 +519,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 +538,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.
@ -528,11 +550,14 @@ class Variable_Method_Caller(object):
the way of Memoizing construction environments, because we had to the way of Memoizing construction environments, because we had to
create new environment objects to hold the variables.) create new environment objects to hold the variables.)
""" """
def __init__(self, variable, method): def __init__(self, variable, method):
self.variable = variable self.variable = variable
self.method = method self.method = method
def __call__(self, *args, **kw): def __call__(self, *args, **kw):
try: 1//0 try:
1 // 0
except ZeroDivisionError: except ZeroDivisionError:
# Don't start iterating with the current stack-frame to # Don't start iterating with the current stack-frame to
# prevent creating reference cycles (f_back is safe). # prevent creating reference cycles (f_back is safe).
@ -547,43 +572,69 @@ class Variable_Method_Caller(object):
frame = frame.f_back frame = frame.f_back
return None return None
# if $version_var is not empty, returns env[flags_var], otherwise returns None
def __libversionflags(env, version_var, flags_var): def __libversionflags(env, version_var, flags_var):
"""
if version_var is not empty, returns env[flags_var], otherwise returns None
:param env:
:param version_var:
:param flags_var:
:return:
"""
try: try:
if env.subst('$'+version_var): if env.subst('$' + version_var):
return env[flags_var] return env[flags_var]
except KeyError: except KeyError:
pass pass
return None return None
def __lib_either_version_flag(env, version_var1, version_var2, flags_var):
"""
if $version_var1 or $version_var2 is not empty, returns env[flags_var], otherwise returns None
:param env:
:param version_var1:
:param version_var2:
:param flags_var:
:return:
"""
try:
if env.subst('$' + version_var1) or env.subst('$' + version_var2):
return env[flags_var]
except KeyError:
pass
return None
ConstructionEnvironment = { ConstructionEnvironment = {
'BUILDERS' : {}, 'BUILDERS': {},
'SCANNERS' : [ SCons.Tool.SourceFileScanner ], 'SCANNERS': [SCons.Tool.SourceFileScanner],
'CONFIGUREDIR' : '#/.sconf_temp', 'CONFIGUREDIR': '#/.sconf_temp',
'CONFIGURELOG' : '#/config.log', 'CONFIGURELOG': '#/config.log',
'CPPSUFFIXES' : SCons.Tool.CSuffixes, 'CPPSUFFIXES': SCons.Tool.CSuffixes,
'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, '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}',
'_LIBFLAGS' : '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}', '_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
'_LIBDIRFLAGS' : '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', '_CPPINCFLAGS': '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
'_CPPINCFLAGS' : '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}',
'_CPPDEFFLAGS' : '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}',
'__libversionflags' : __libversionflags, '__libversionflags': __libversionflags,
'__SHLIBVERSIONFLAGS' : '${__libversionflags(__env__,"SHLIBVERSION","_SHLIBVERSIONFLAGS")}', '__SHLIBVERSIONFLAGS': '${__libversionflags(__env__,"SHLIBVERSION","_SHLIBVERSIONFLAGS")}',
'__LDMODULEVERSIONFLAGS' : '${__libversionflags(__env__,"LDMODULEVERSION","_LDMODULEVERSIONFLAGS")}', '__LDMODULEVERSIONFLAGS': '${__libversionflags(__env__,"LDMODULEVERSION","_LDMODULEVERSIONFLAGS")}',
'__DSHLIBVERSIONFLAGS' : '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}', '__DSHLIBVERSIONFLAGS': '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}',
'__lib_either_version_flag': __lib_either_version_flag,
'TEMPFILE' : NullCmdGenerator, 'TEMPFILE': NullCmdGenerator,
'Dir' : Variable_Method_Caller('TARGET', 'Dir'), 'TEMPFILEARGJOIN': ' ',
'Dirs' : Variable_Method_Caller('TARGET', 'Dirs'), 'Dir': Variable_Method_Caller('TARGET', 'Dir'),
'File' : Variable_Method_Caller('TARGET', 'File'), 'Dirs': Variable_Method_Caller('TARGET', 'Dirs'),
'RDirs' : Variable_Method_Caller('TARGET', 'RDirs'), 'File': Variable_Method_Caller('TARGET', 'File'),
'RDirs': Variable_Method_Caller('TARGET', 'RDirs'),
} }
# Local Variables: # Local Variables:

View file

@ -0,0 +1,119 @@
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import re
_is_valid_var = re.compile(r'[_a-zA-Z]\w*$')
_rm = re.compile(r'\$[()]')
_remove = re.compile(r'\$\([^$]*(\$[^)][^$]*)*\$\)')
# Regular expressions for splitting strings and handling substitutions,
# for use by the scons_subst() and scons_subst_list() functions:
#
# The first expression compiled matches all of the $-introduced tokens
# that we need to process in some way, and is used for substitutions.
# The expressions it matches are:
#
# "$$"
# "$("
# "$)"
# "$variable" [must begin with alphabetic or underscore]
# "${any stuff}"
#
# The second expression compiled is used for splitting strings into tokens
# to be processed, and it matches all of the tokens listed above, plus
# the following that affect how arguments do or don't get joined together:
#
# " " [white space]
# "non-white-space" [without any dollar signs]
# "$" [single dollar sign]
#
_dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}'
_dollar_exps = re.compile(r'(%s)' % _dollar_exps_str)
_separate_args = re.compile(r'(%s|\s+|[^\s$]+|\$)' % _dollar_exps_str)
# This regular expression is used to replace strings of multiple white
# space characters in the string result from the scons_subst() function.
_space_sep = re.compile(r'[\t ]+(?![^{]*})')
class ValueTypes:
"""
Enum to store what type of value the variable holds.
"""
UNKNOWN = 0
STRING = 1
CALLABLE = 2
VARIABLE = 3
class EnvironmentValue:
"""
Hold a single value. We're going to cache parsed version of the file
We're going to keep track of variables which feed into this values evaluation
"""
def __init__(self, value):
self.value = value
self.var_type = ValueTypes.UNKNOWN
if callable(self.value):
self.var_type = ValueTypes.CALLABLE
else:
self.parse_value()
def parse_value(self):
"""
Scan the string and break into component values
"""
try:
if '$' not in self.value:
self._parsed = self.value
self.var_type = ValueTypes.STRING
else:
# Now we need to parse the specified string
result = _dollar_exps.sub(sub_match, args)
print(result)
except TypeError:
# likely callable? either way we don't parse
self._parsed = self.value
def parse_trial(self):
"""
Try alternate parsing methods.
:return:
"""
parts = []
for c in self.value:
pass
class EnvironmentValues:
"""
A class to hold all the environment variables
"""
def __init__(self, **kw):
self._dict = {}
for k in kw:
self._dict[k] = EnvironmentValue(kw[k])

View file

@ -0,0 +1,39 @@
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import 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,6 @@
# MIT License
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -19,73 +20,65 @@
# 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.
#
"""SCons.Errors """SCons exception classes.
This file contains the exception classes used to handle internal Used to handle internal and user errors in SCons.
and user errors in SCons.
""" """
__revision__ = "src/engine/SCons/Errors.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
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 +88,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 +117,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 +131,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):
@ -184,20 +178,19 @@ def convert_to_BuildError(status, exc_info=None):
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)

View file

@ -1,12 +1,6 @@
"""SCons.Executor # MIT License
A module for executing actions with specific lists of target and source
Nodes.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -26,9 +20,8 @@ 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__ = "src/engine/SCons/Executor.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Execute actions with specific lists of target and source Nodes."""
import collections import collections
@ -36,9 +29,10 @@ 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."""
@ -71,7 +65,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 +74,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,7 +121,7 @@ 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(
@ -155,7 +149,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 +444,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 +566,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 +582,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.
@ -613,7 +608,8 @@ class Null(object, with_metaclass(NoSlotsPyPy)):
'_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()

View file

@ -1,13 +1,6 @@
"""SCons.Job # MIT License
This module defines the Serial and Parallel classes that execute tasks to
complete a build. The Jobs class provides a higher level interface to start,
stop, and wait on jobs.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -27,9 +20,12 @@ stop, and wait on jobs.
# 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.
#
__revision__ = "src/engine/SCons/Job.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Serial and Parallel classes to execute build tasks.
The Jobs class provides a higher level interface to start,
stop, and wait on jobs.
"""
import SCons.compat import SCons.compat
@ -52,18 +48,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.
""" """
@ -163,7 +159,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.
@ -199,7 +195,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,7 +260,7 @@ 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):
@ -281,7 +277,7 @@ else:
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)
@ -338,7 +334,7 @@ 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.

View file

@ -1,5 +1,6 @@
# MIT License
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -19,12 +20,8 @@
# 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__ = "src/engine/SCons/Memoize.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Decorator-based memoizer to count caching stats.
__doc__ = """Memoizer
A decorator-based implementation to count hits and misses of the computed A decorator-based implementation to count hits and misses of the computed
values that various methods cache in memory. values that various methods cache in memory.
@ -106,7 +103,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

@ -1,14 +1,6 @@
# MIT License
"""scons.Node.Alias
Alias nodes.
This creates a hash of global Aliases (dummy targets).
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,15 +20,18 @@ This creates a hash of global Aliases (dummy targets).
# 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.
#
__revision__ = "src/engine/SCons/Node/Alias.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Alias nodes.
This creates a hash of global Aliases (dummy targets).
"""
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 +161,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

@ -1,17 +1,6 @@
"""scons.Node.FS # MIT License
File system nodes.
These Nodes represent the canonical external objects that people think
of when they think of building software: files and directories.
This holds a "default_fs" variable that should be initialized with an FS
that can be used by scripts or modules looking for the canonical default.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -31,9 +20,15 @@ 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__ = "src/engine/SCons/Node/FS.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """File system nodes.
These Nodes represent the canonical external objects that people think
of when they think of building software: files and directories.
This holds a "default_fs" variable that should be initialized with an FS
that can be used by scripts or modules looking for the canonical default.
"""
import fnmatch import fnmatch
import os import os
@ -43,22 +38,25 @@ 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
from SCons.Debug import logInstanceCreation from SCons.Debug import logInstanceCreation, Trace
import SCons.Errors import SCons.Errors
import SCons.Memoize import SCons.Memoize
import SCons.Node 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
print_duplicate = 0 print_duplicate = 0
MD5_TIMESTAMP_DEBUG = False
def sconsign_none(node): def sconsign_none(node):
raise NotImplementedError raise NotImplementedError
@ -74,6 +72,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 +133,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 +276,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 +286,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 +334,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 +362,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 +378,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
@ -423,7 +434,7 @@ class EntryProxy(SCons.Util.Proxy):
# In PY3 if a class defines __eq__, then it must explicitly provide # In PY3 if a class defines __eq__, then it must explicitly provide
# __hash__. Since SCons.Util.Proxy provides __eq__ we need the following # __hash__. Since SCons.Util.Proxy provides __eq__ we need the following
# see: https://docs.python.org/3.1/reference/datamodel.html#object.__hash__ # see: https://docs.python.org/3/reference/datamodel.html#object.__hash__
__hash__ = SCons.Util.Delegate('__hash__') __hash__ = SCons.Util.Delegate('__hash__')
def __get_abspath(self): def __get_abspath(self):
@ -463,7 +474,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 +525,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 +693,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 +726,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()
@ -939,13 +981,13 @@ 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 +1089,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):
@ -1391,7 +1434,7 @@ class FS(LocalFS):
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
@ -1400,22 +1443,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 +1553,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 +1614,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 +1625,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 +1691,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 +1731,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 +1865,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 +2263,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 +2305,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 +2335,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 +2471,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 +2493,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
@ -2567,7 +2629,8 @@ class File(Base):
NodeInfo = FileNodeInfo NodeInfo = FileNodeInfo
BuildInfo = FileBuildInfo BuildInfo = FileBuildInfo
md5_chunksize = 64 # Although the command-line argument is in kilobytes, this is in bytes.
md5_chunksize = 65536
def diskcheck_match(self): def diskcheck_match(self):
diskcheck_match(self, self.isdir, diskcheck_match(self, self.isdir,
@ -2630,11 +2693,13 @@ class File(Base):
def scanner_key(self): def scanner_key(self):
return self.get_suffix() return self.get_suffix()
def get_contents(self): def get_contents(self) -> bytes:
"""Return the contents of the file as bytes."""
return SCons.Node._get_contents_map[self._func_get_contents](self) return SCons.Node._get_contents_map[self._func_get_contents](self)
def get_text_contents(self): def get_text_contents(self) -> str:
""" """Return the contents of the file in text form.
This attempts to figure out what the encoding of the text is This attempts to figure out what the encoding of the text is
based upon the BOM bytes, and then decodes the contents so that based upon the BOM bytes, and then decodes the contents so that
it's a valid python string. it's a valid python string.
@ -2658,19 +2723,16 @@ class File(Base):
try: try:
return contents.decode('latin-1') return contents.decode('latin-1')
except UnicodeDecodeError as e: except UnicodeDecodeError as e:
return contents.decode('utf-8', error='backslashreplace') return contents.decode('utf-8', errors='backslashreplace')
def get_content_hash(self): def get_content_hash(self) -> str:
""" """Compute and return the hash of the file contents."""
Compute and return the MD5 hash for this file.
"""
if not self.rexists(): if not self.rexists():
return SCons.Util.MD5signature('') return MD5signature(SCons.Util.NOFILE)
fname = self.rfile().get_abspath() fname = self.rfile().get_abspath()
try: try:
cs = SCons.Util.MD5filesignature(fname, cs = MD5filesignature(fname, chunksize=File.md5_chunksize)
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
@ -2678,7 +2740,7 @@ class File(Base):
return cs return cs
@SCons.Memoize.CountMethodCall @SCons.Memoize.CountMethodCall
def get_size(self): def get_size(self) -> int:
try: try:
return self._memo['get_size'] return self._memo['get_size']
except KeyError: except KeyError:
@ -2687,14 +2749,14 @@ class File(Base):
if self.rexists(): if self.rexists():
size = self.rfile().getsize() size = self.rfile().getsize()
else: else:
size = 0 # sentinel value for doesn't exist, even in repository
size = -1
self._memo['get_size'] = size self._memo['get_size'] = size
return size return size
@SCons.Memoize.CountMethodCall @SCons.Memoize.CountMethodCall
def get_timestamp(self): def get_timestamp(self) -> int:
try: try:
return self._memo['get_timestamp'] return self._memo['get_timestamp']
except KeyError: except KeyError:
@ -2706,7 +2768,6 @@ class File(Base):
timestamp = 0 timestamp = 0
self._memo['get_timestamp'] = timestamp self._memo['get_timestamp'] = timestamp
return timestamp return timestamp
convert_copy_attrs = [ convert_copy_attrs = [
@ -2718,7 +2779,6 @@ class File(Base):
'ninfo', 'ninfo',
] ]
convert_sig_attrs = [ convert_sig_attrs = [
'bsourcesigs', 'bsourcesigs',
'bimplicitsigs', 'bimplicitsigs',
@ -2971,7 +3031,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 +3119,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:
@ -3108,7 +3171,7 @@ class File(Base):
# SIGNATURE SUBSYSTEM # SIGNATURE SUBSYSTEM
# #
def get_max_drift_csig(self): def get_max_drift_csig(self) -> str:
""" """
Returns the content signature currently stored for this node Returns the content signature currently stored for this node
if it's been unmodified longer than the max_drift value, or the if it's been unmodified longer than the max_drift value, or the
@ -3134,15 +3197,8 @@ class File(Base):
return None return None
def get_csig(self): def get_csig(self) -> str:
""" """Generate a node's content signature."""
Generate a node's content signature, the digested signature
of its content.
node - the node
cache - alternate node to use for the signature cache
returns - the content signature
"""
ninfo = self.get_ninfo() ninfo = self.get_ninfo()
try: try:
return ninfo.csig return ninfo.csig
@ -3151,9 +3207,11 @@ class File(Base):
csig = self.get_max_drift_csig() csig = self.get_max_drift_csig()
if csig is None: if csig is None:
try: try:
if self.get_size() < SCons.Node.FS.File.md5_chunksize: size = self.get_size()
if size == -1:
contents = SCons.Util.NOFILE
elif 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 +3283,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 +3516,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 +3539,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 +3566,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 +3612,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)
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 +3632,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 +3644,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 +3655,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 +3674,7 @@ def get_default_fs():
default_fs = FS() default_fs = FS()
return default_fs return default_fs
class FileFinder(object): class FileFinder:
""" """
""" """
@ -3472,7 +3747,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

@ -1,11 +1,6 @@
"""scons.Node.Python # MIT License
Python nodes.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -25,12 +20,14 @@ Python 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.
#
__revision__ = "src/engine/SCons/Node/Python.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Python nodes."""
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 +35,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 +73,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 +82,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 +90,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)
@ -124,7 +129,7 @@ class Value(SCons.Node.Node):
self.built_value = self.value self.built_value = self.value
return self.built_value return self.built_value
def get_text_contents(self): def get_text_contents(self) -> str:
"""By the assumption that the node.built_value is a """By the assumption that the node.built_value is a
deterministic product of the sources, the contents of a Value deterministic product of the sources, the contents of a Value
are the concatenation of all the contents of its sources. As are the concatenation of all the contents of its sources. As
@ -133,37 +138,63 @@ class Value(SCons.Node.Node):
###TODO: something reasonable about universal newlines ###TODO: something reasonable about universal newlines
contents = str(self.value) contents = str(self.value)
for kid in self.children(None): for kid in self.children(None):
contents = contents + kid.get_contents().decode() # Get csig() value of child as this is more efficent
contents = contents + kid.get_csig()
return contents return contents
def get_contents(self): def get_contents(self) -> bytes:
text_contents = self.get_text_contents() """Get contents for signature calculations."""
try: return self.get_text_contents().encode()
return text_contents.encode()
except UnicodeDecodeError:
# Already encoded as python2 str are bytes
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:
return cur_csig != prev_ni.csig return cur_csig != prev_ni.csig
except AttributeError: except AttributeError:
return 1 return True
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

@ -1,28 +1,6 @@
"""SCons.Node # MIT License
The Node package for the SCons software construction utility.
This is, in many ways, the heart of SCons.
A Node is where we encapsulate all of the dependency information about
any thing that SCons can build, or about any thing which SCons can use
to build some other thing. The canonical "thing," of course, is a file,
but a Node can also represent something remote (like a web page) or
something completely abstract (like an Alias).
Each specific type of "thing" is specifically represented by a subclass
of the Node base class: Node.FS.File for files, Node.Alias for aliases,
etc. Dependency information is kept here in the base class, and
information specific to files/aliases/etc. is in the subclass. The
goal, if we've done this correctly, is that any type of "thing" should
be able to depend on any other type of "thing."
"""
from __future__ import print_function
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -43,21 +21,36 @@ 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" """The Node package for the SCons software construction utility.
This is, in many ways, the heart of SCons.
A Node is where we encapsulate all of the dependency information about
any thing that SCons can build, or about any thing which SCons can use
to build some other thing. The canonical "thing," of course, is a file,
but a Node can also represent something remote (like a web page) or
something completely abstract (like an Alias).
Each specific type of "thing" is specifically represented by a subclass
of the Node base class: Node.FS.File for files, Node.Alias for aliases,
etc. Dependency information is kept here in the base class, and
information specific to files/aliases/etc. is in the subclass. The
goal, if we've done this correctly, is that any type of "thing" should
be able to depend on any other type of "thing."
"""
import collections import collections
import copy import copy
from itertools import chain from itertools import chain, zip_longest
import SCons.Debug import SCons.Debug
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.compat import NoSlotsPyPy
from SCons.Debug import Trace from SCons.Debug import logInstanceCreation, Trace
from SCons.Util import MD5signature
from SCons.compat import with_metaclass, NoSlotsPyPy
print_duplicate = 0 print_duplicate = 0
@ -102,9 +95,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 +135,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 +152,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 +246,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 +266,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 +344,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 +383,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 +439,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 +502,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 +550,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 +604,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 +671,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 +766,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:
@ -786,25 +811,21 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
def release_target_info(self): def release_target_info(self):
"""Called just after this node has been marked """Called just after this node has been marked
up-to-date or was built completely. up-to-date or was built completely.
This is where we try to release as many target node infos This is where we try to release as many target node infos
as possible for clean builds and update runs, in order as possible for clean builds and update runs, in order
to minimize the overall memory consumption. to minimize the overall memory consumption.
By purging attributes that aren't needed any longer after By purging attributes that aren't needed any longer after
a Node (=File) got built, we don't have to care that much how a Node (=File) got built, we don't have to care that much how
many KBytes a Node actually requires...as long as we free many KBytes a Node actually requires...as long as we free
the memory shortly afterwards. the memory shortly afterwards.
@see: built() and File.release_target_info() @see: built() and File.release_target_info()
""" """
pass pass
#
#
#
def add_to_waiting_s_e(self, node): def add_to_waiting_s_e(self, node):
self.waiting_s_e.add(node) self.waiting_s_e.add(node)
@ -840,10 +861,12 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
self.clear_memoized_values() self.clear_memoized_values()
self.ninfo = self.new_ninfo() self.ninfo = self.new_ninfo()
self.executor_cleanup() self.executor_cleanup()
try: for attr in ['cachedir_csig', 'cachesig', 'contentsig']:
delattr(self, '_calculated_sig') try:
except AttributeError: delattr(self, attr)
pass except AttributeError:
pass
self.cached = 0
self.includes = None self.includes = None
def clear_memoized_values(self): def clear_memoized_values(self):
@ -863,7 +886,7 @@ class Node(object, with_metaclass(NoSlotsPyPy)):
than simply examining the builder attribute directly ("if than simply examining the builder attribute directly ("if
node.builder: ..."). When the builder attribute is examined node.builder: ..."). When the builder attribute is examined
directly, it ends up calling __getattr__ for both the __len__ directly, it ends up calling __getattr__ for both the __len__
and __nonzero__ attributes on instances of our Builder Proxy and __bool__ attributes on instances of our Builder Proxy
class(es), generating a bazillion extra calls and slowing class(es), generating a bazillion extra calls and slowing
things down immensely. things down immensely.
""" """
@ -912,6 +935,18 @@ 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 is_conftest(self):
""" Returns true if this node is an conftest node"""
try:
self.attributes.conftest_node
except AttributeError:
return False
return True
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 +1132,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 +1167,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 +1179,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 +1205,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 +1251,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 +1490,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 +1644,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 +1713,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,6 @@
# MIT License
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -19,17 +20,13 @@
# 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.
#
__revision__ = "src/engine/SCons/PathList.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Handle lists of directory paths.
__doc__ = """SCons.PathList
A module for handling lists of directory paths (the sort of things
that get set as CPPPATH, LIBPATH, etc.) with as much caching of data and
efficiency as we can, while still keeping the evaluation delayed so that we
Do the Right Thing (almost) regardless of how the variable is specified.
These are the path lists that get set as CPPPATH, LIBPATH,
etc.) with as much caching of data and efficiency as we can, while
still keeping the evaluation delayed so that we Do the Right Thing
(almost) regardless of how the variable is specified.
""" """
import os import os
@ -66,7 +63,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 +140,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

@ -1,26 +1,6 @@
"""SCons.Platform # MIT License
SCons platform selection.
This looks for modules that define a callable object that can modify a
construction environment as appropriate for a given platform.
Note that we take a more simplistic view of "platform" than Python does.
We're looking for a single string that determines a set of
tool-independent variables with which to initialize a construction
environment. Consequently, we'll examine both sys.platform and os.name
(and anything else that might come in to play) in order to return some
specification which is unique enough for our purposes.
Note that because this subsystem just *selects* a callable that can
modify a construction environment, it's possible for people to define
their own "platform specification" in an arbitrary callable function.
No one needs to use or tie in to this subsystem in order to roll
their own platform definition.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -40,14 +20,29 @@ their own platform definition.
# 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__ = "src/engine/SCons/Platform/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """SCons platform selection.
Looks for modules that define a callable object that can modify a
construction environment as appropriate for a given platform.
Note that we take a more simplistic view of "platform" than Python does.
We're looking for a single string that determines a set of
tool-independent variables with which to initialize a construction
environment. Consequently, we'll examine both sys.platform and os.name
(and anything else that might come in to play) in order to return some
specification which is unique enough for our purposes.
Note that because this subsystem just *selects* a callable that can
modify a construction environment, it's possible for people to define
their own "platform specification" in an arbitrary callable function.
No one needs to use or tie in to this subsystem in order to roll
their own platform definition.
"""
import SCons.compat import SCons.compat
import imp import importlib
import os import os
import sys import sys
import tempfile import tempfile
@ -60,8 +55,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 +82,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 +96,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 +108,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 +126,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
@ -182,24 +188,41 @@ class TempFileMunge(object):
# Check if we already created the temporary file for this target # Check if we already created the temporary file for this target
# It should have been previously done by Action.strfunction() call # It should have been previously done by Action.strfunction() call
node = target[0] if SCons.Util.is_List(target) else target if SCons.Util.is_List(target):
cmdlist = getattr(node.attributes, 'tempfile_cmdlist', None) \ node = target[0]
if node is not None else None else:
if cmdlist is not None : node = target
cmdlist = None
if SCons.Util.is_List(self.cmd):
cmdlist_key = tuple(self.cmd)
else:
cmdlist_key = self.cmd
if node and hasattr(node.attributes, 'tempfile_cmdlist'):
cmdlist = node.attributes.tempfile_cmdlist.get(cmdlist_key, None)
if cmdlist is not None:
return cmdlist 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 +240,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,19 +264,41 @@ 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)
cmdlist = [ cmd[0], prefix + native_tmp + '\n' + rm, native_tmp ]
# 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.
cmdlist = [ cmd[0], prefix + native_tmp + '\n' + rm, native_tmp ]
if node is not None: if node is not None:
try : try:
setattr(node.attributes, 'tempfile_cmdlist', cmdlist) # Storing in tempfile_cmdlist by self.cmd provided when intializing
# $TEMPFILE{} fixes issue raised in PR #3140 and #3553
node.attributes.tempfile_cmdlist[cmdlist_key] = cmdlist
except AttributeError: except AttributeError:
pass node.attributes.tempfile_cmdlist = {cmdlist_key:cmdlist}
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,14 +1,6 @@
"""engine.SCons.Platform.aix # MIT License
Platform-specific initialization for IBM AIX systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,11 +20,14 @@ selection method.
# 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.
#
__revision__ = "src/engine/SCons/Platform/aix.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Platform-specific initialization for IBM AIX systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
import os
import subprocess import subprocess
from . import posix from . import posix
@ -55,6 +50,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
@ -69,14 +65,13 @@ def get_xlc(env, xlc=None, packages=[]):
or ('/' not in xlc and filename.endswith('/' + xlc)): or ('/' not in xlc and filename.endswith('/' + xlc)):
xlcVersion = fileset.split()[1] xlcVersion = fileset.split()[1]
xlcPath, sep, xlc = filename.rpartition('/') xlcPath, sep, xlc = filename.rpartition('/')
pass
pass
return (xlcPath, xlc, xlcVersion) return (xlcPath, xlc, xlcVersion)
def generate(env): def generate(env):
posix.generate(env) posix.generate(env)
#Based on AIX 5.2: ARG_MAX=24576 - 3000 for environment expansion #Based on AIX 5.2: ARG_MAX=24576 - 3000 for environment expansion
env['MAXLINELENGTH'] = 21576 env['MAXLINELENGTH'] = 21576
env['SHLIBSUFFIX'] = '.a'
# Local Variables: # Local Variables:
# tab-width:4 # tab-width:4

View file

@ -1,14 +1,6 @@
"""SCons.Platform.cygwin # MIT License
Platform-specific initialization for Cygwin systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,13 +20,26 @@ selection method.
# 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.
#
__revision__ = "src/engine/SCons/Platform/cygwin.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Platform-specific initialization for Cygwin systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
import sys
from . import posix from . 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,14 +1,6 @@
"""engine.SCons.Platform.darwin # MIT License
Platform-specific initialization for Mac OS X systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,9 +20,13 @@ selection method.
# 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.
#
__revision__ = "src/engine/SCons/Platform/darwin.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Platform-specific initialization for Mac OS X systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
from . import posix from . import posix
import os import os
@ -56,12 +52,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,14 +1,6 @@
"""engine.SCons.Platform.hpux # MIT License
Platform-specific initialization for HP-UX systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,9 +20,13 @@ selection method.
# 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.
#
__revision__ = "src/engine/SCons/Platform/hpux.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Platform-specific initialization for HP-UX systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
from . import posix from . import posix
@ -39,6 +35,8 @@ def generate(env):
#Based on HP-UX11i: ARG_MAX=2048000 - 3000 for environment expansion #Based on HP-UX11i: ARG_MAX=2048000 - 3000 for environment expansion
env['MAXLINELENGTH'] = 2045000 env['MAXLINELENGTH'] = 2045000
env['SHLIBSUFFIX'] = '.sl'
# Local Variables: # Local Variables:
# tab-width:4 # tab-width:4
# indent-tabs-mode:nil # indent-tabs-mode:nil

View file

@ -1,14 +1,6 @@
"""SCons.Platform.irix # MIT License
Platform-specific initialization for SGI IRIX systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,9 +20,13 @@ selection method.
# 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.
#
__revision__ = "src/engine/SCons/Platform/irix.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Platform-specific initialization for SGI IRIX systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
from . import posix from . import posix

View file

@ -0,0 +1,33 @@
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""Platform-specific initialization for the MinGW system."""
import sys
MINGW_DEFAULT_PATHS = []
if sys.platform == 'win32':
MINGW_DEFAULT_PATHS = [
r'C:\msys64',
r'C:\msys'
]

View file

@ -1,14 +1,6 @@
"""SCons.Platform.os2 # MIT License
Platform-specific initialization for OS/2 systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,9 +20,14 @@ selection method.
# 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.
#
__revision__ = "src/engine/SCons/Platform/os2.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Platform-specific initialization for OS/2 systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
from . import win32 from . import win32
def generate(env): def generate(env):

View file

@ -1,14 +1,6 @@
"""SCons.Platform.posix # MIT License
Platform-specific initialization for POSIX (Linux, UNIX, etc.) systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,19 +20,22 @@ selection method.
# 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.
#
__revision__ = "src/engine/SCons/Platform/posix.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Platform-specific initialization for POSIX (Linux, UNIX, etc.) systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
import errno import errno
import os
import os.path
import subprocess import subprocess
import sys
import select 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 +43,7 @@ exitvalmap = {
} }
def escape(arg): def escape(arg):
"escape shell special characters" """escape shell special characters"""
slash = '\\' slash = '\\'
special = '"$' special = '"$'
@ -89,7 +84,7 @@ def generate(env):
if 'ENV' not in env: if 'ENV' not in env:
env['ENV'] = {} env['ENV'] = {}
env['ENV']['PATH'] = '/usr/local/bin:/opt/bin:/bin:/usr/bin' env['ENV']['PATH'] = '/usr/local/bin:/opt/bin:/bin:/usr/bin:/snap/bin'
env['OBJPREFIX'] = '' env['OBJPREFIX'] = ''
env['OBJSUFFIX'] = '.o' env['OBJSUFFIX'] = '.o'
env['SHOBJPREFIX'] = '$OBJPREFIX' env['SHOBJPREFIX'] = '$OBJPREFIX'
@ -119,6 +114,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,14 +1,6 @@
"""engine.SCons.Platform.sunos # MIT License
Platform-specific initialization for Sun systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,9 +20,13 @@ selection method.
# 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.
#
__revision__ = "src/engine/SCons/Platform/sunos.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Platform-specific initialization for Sun systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
from . import posix from . import posix

View file

@ -0,0 +1,115 @@
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""'Platform" support for a Python virtualenv."""
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

@ -1,14 +1,6 @@
"""SCons.Platform.win32 # MIT License
Platform-specific initialization for Win32 systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,9 +20,13 @@ selection method.
# 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.
#
__revision__ = "src/engine/SCons/Platform/win32.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Platform-specific initialization for Win32 systems.
There normally shouldn't be any need to import this module directly. It
will usually be imported through the generic SCons.Platform.Platform()
selection method.
"""
import os import os
import os.path import os.path
@ -39,59 +35,13 @@ 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
try: CHOCO_DEFAULT_PATH = [
import msvcrt r'C:\ProgramData\chocolatey\bin'
import win32api ]
import win32con
msvcrt.get_osfhandle
win32api.SetHandleInformation
win32con.HANDLE_FLAG_INHERIT
except ImportError:
parallel_msg = \
"you do not seem to have the pywin32 extensions installed;\n" + \
"\tparallel (-j) builds may not work reliably with open Python files."
except AttributeError:
parallel_msg = \
"your pywin32 extensions do not support file handle operations;\n" + \
"\tparallel (-j) builds may not work reliably with open Python files."
else:
parallel_msg = None
_builtin_open = open
def _scons_open(*args, **kw):
fp = _builtin_open(*args, **kw)
win32api.SetHandleInformation(msvcrt.get_osfhandle(fp.fileno()),
win32con.HANDLE_FLAG_INHERIT,
0)
return fp
open = _scons_open
if sys.version_info.major == 2:
_builtin_file = file
class _scons_file(_builtin_file):
def __init__(self, *args, **kw):
_builtin_file.__init__(self, *args, **kw)
win32api.SetHandleInformation(msvcrt.get_osfhandle(self.fileno()),
win32con.HANDLE_FLAG_INHERIT, 0)
file = _scons_file
else:
import io
for io_class in ['BufferedReader', 'BufferedWriter', 'BufferedRWPair',
'BufferedRandom', 'TextIOWrapper']:
_builtin_file = getattr(io, io_class)
class _scons_file(_builtin_file):
def __init__(self, *args, **kw):
_builtin_file.__init__(self, *args, **kw)
win32api.SetHandleInformation(msvcrt.get_osfhandle(self.fileno()),
win32con.HANDLE_FLAG_INHERIT, 0)
setattr(io, io_class, _scons_file)
if False: 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 +82,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 +118,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 +247,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
@ -312,7 +268,6 @@ def get_program_files_dir():
val, tok = SCons.Util.RegQueryValueEx(k, 'ProgramFilesDir') val, tok = SCons.Util.RegQueryValueEx(k, 'ProgramFilesDir')
except SCons.Util.RegError: except SCons.Util.RegError:
val = '' val = ''
pass
if val == '': if val == '':
# A reasonable default if we can't read the registry # A reasonable default if we can't read the registry
@ -322,7 +277,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 +391,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 +417,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

@ -1,18 +1,6 @@
"""SCons.SConf # MIT License
Autoconf-like configuration support.
In other words, SConf allows to run tests on the build machine to detect
capabilities of system and do some things based on result: generate config
files, header files for C/C++, update variables in environment.
Tests on the build system can detect if compiler sees header files, if
libraries are installed, if some command line options are supported etc.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -32,13 +20,20 @@ libraries are installed, if some command line options are supported etc.
# 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__ = "src/engine/SCons/SConf.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Autoconf-like configuration support.
In other words, SConf allows to run tests on the build machine to detect
capabilities of system and do some things based on result: generate config
files, header files for C/C++, update variables in environment.
Tests on the build system can detect if compiler sees header files, if
libraries are installed, if some command line options are supported etc.
"""
import SCons.compat import SCons.compat
import atexit
import io import io
import os import os
import re import re
@ -56,6 +51,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 +61,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 +94,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,11 +128,11 @@ 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.SConsWarning):
pass pass
SCons.Warnings.enableWarningClass(SConfWarning) SCons.Warnings.enableWarningClass(SConfWarning)
@ -161,11 +157,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 +186,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 +245,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 +269,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 +279,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 +323,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 +371,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 +393,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 +466,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):
@ -498,11 +516,27 @@ class SConfBase(object):
# we override the store_info() method with a null place-holder # we override the store_info() method with a null place-holder
# so we really control how it gets written. # so we really control how it gets written.
for n in nodes: for n in nodes:
self._set_conftest_node(n)
n.store_info = 0 n.store_info = 0
if not hasattr(n, 'attributes'): if not hasattr(n, 'attributes'):
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)
self._set_conftest_node(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 +575,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 +593,33 @@ 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)
self._set_conftest_node(sourcetext)
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)
self._set_conftest_node(sourcetext)
textFileNode = self.env.SConfSourceBuilder(target=textFile,
source=sourcetext)
nodesToBeBuilt.extend(textFileNode)
source = textFile
target = textFile.File(f + "SConfActionsContentDummyTarget")
self._set_conftest_node(target)
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 +628,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 +638,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 +656,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 +683,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 +694,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 +718,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 ):
@ -683,6 +730,9 @@ class SConfBase(object):
if not os.path.isdir( dirName ): if not os.path.isdir( dirName ):
os.makedirs( dirName ) os.makedirs( dirName )
def _set_conftest_node(self, node):
node.attributes.conftest_node = 1
def _startup(self): def _startup(self):
"""Private method. Set up logstream, and set the environment """Private method. Set up logstream, and set the environment
variables necessary for a piped build variables necessary for a piped build
@ -705,11 +755,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 +794,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 +889,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 +1063,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 +1090,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

@ -1,11 +1,6 @@
"""SCons.SConsign # MIT License
Writing and reading information to the .sconsign file or files.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -25,11 +20,8 @@ Writing and reading information to the .sconsign file or files.
# 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 """Operations on signature database files (.sconsign). """
__revision__ = "src/engine/SCons/SConsign.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import SCons.compat import SCons.compat
@ -43,10 +35,12 @@ from SCons.compat import PICKLE_PROTOCOL
def corrupt_dblite_warning(filename): def corrupt_dblite_warning(filename):
SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, SCons.Warnings.warn(
"Ignoring corrupt .sconsign file: %s"%filename) SCons.Warnings.CorruptSConsignWarning,
"Ignoring corrupt .sconsign file: %s" % filename,
)
SCons.dblite.ignore_corrupt_dbfiles = 1 SCons.dblite.IGNORE_CORRUPT_DBFILES = True
SCons.dblite.corruption_warning = corrupt_dblite_warning SCons.dblite.corruption_warning = corrupt_dblite_warning
# XXX Get rid of the global array so this becomes re-entrant. # XXX Get rid of the global array so this becomes re-entrant.
@ -76,7 +70,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 +117,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.
@ -148,7 +143,7 @@ class SConsignEntry(object):
def __getstate__(self): def __getstate__(self):
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)
@ -161,11 +156,11 @@ class SConsignEntry(object):
def __setstate__(self, state): def __setstate__(self, state):
for key, value in state.items(): for key, value in state.items():
if key not in ('_version_id','__weakref__'): if key not in ('_version_id', '__weakref__'):
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 +329,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 +394,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 +417,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,6 @@
"""SCons.Scanner.C # MIT License
This module implements the dependency scanner for C/C++ code.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -25,9 +20,8 @@ This module implements the dependency scanner for C/C++ code.
# 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.
#
__revision__ = "src/engine/SCons/Scanner/C.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Dependency scanner for C/C++ code."""
import SCons.Node.FS import SCons.Node.FS
import SCons.Scanner import SCons.Scanner
@ -36,8 +30,7 @@ import SCons.Util
import SCons.cpp import SCons.cpp
class SConsCPPScanner(SCons.cpp.PreProcessor): class SConsCPPScanner(SCons.cpp.PreProcessor):
""" """SCons-specific subclass of the cpp.py module's processing.
SCons-specific subclass of the cpp.py module's processing.
We subclass this so that: 1) we can deal with files represented 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 by Nodes, not strings; 2) we can keep track of the files that are
@ -80,9 +73,8 @@ 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.
This is the actual glue between the calling conventions of generic This is the actual glue between the calling conventions of generic
SCons scanners, and the (subclass of) cpp.py class that knows how SCons scanners, and the (subclass of) cpp.py class that knows how
@ -116,14 +108,109 @@ def CScanner():
# knows how to evaluate #if/#ifdef/#else/#elif lines when searching # knows how to evaluate #if/#ifdef/#else/#elif lines when searching
# for #includes. This is commented out for now until we add the # for #includes. This is commented out for now until we add the
# right configurability to let users pick between the scanners. # right configurability to let users pick between the scanners.
#return SConsCPPScannerWrapper("CScanner", "CPPPATH") # return SConsCPPScannerWrapper("CScanner", "CPPPATH")
cs = SCons.Scanner.ClassicCPP("CScanner", cs = SCons.Scanner.ClassicCPP(
"$CPPSUFFIXES", "CScanner",
"CPPPATH", "$CPPSUFFIXES",
'^[ \t]*#[ \t]*(?:include|import)[ \t]*(<|")([^>"]+)(>|")') "CPPPATH",
r'^[ \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

@ -1,14 +1,6 @@
"""SCons.Scanner.D # MIT License
Scanner for the Digital Mars "D" programming language.
Coded by Andy Friesen
17 Nov 2003
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -28,9 +20,11 @@ Coded by Andy Friesen
# 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.
#
__revision__ = "src/engine/SCons/Scanner/D.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Scanner for the Digital Mars "D" programming language.
Coded by Andy Friesen, 17 Nov 2003
"""
import SCons.Scanner import SCons.Scanner
@ -46,7 +40,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,6 @@
# MIT License
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -20,8 +21,6 @@
# 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"
import SCons.Node.FS import SCons.Node.FS
import SCons.Scanner import SCons.Scanner

View file

@ -1,11 +1,6 @@
"""SCons.Scanner.Fortran # MIT License
This module implements the dependency scanner for Fortran code.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -26,7 +21,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" """Dependency scanner for Fortran code."""
import re import re
@ -78,7 +73,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 +182,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 +270,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 +280,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

@ -1,12 +1,6 @@
"""SCons.Scanner.IDL # MIT License
This module implements the dependency scanner for IDL (Interface
Definition Language) files.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -26,19 +20,20 @@ Definition Language) files.
# 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.
#
__revision__ = "src/engine/SCons/Scanner/IDL.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Dependency scanner for IDL (Interface Definition Language) files."""
import SCons.Node.FS import SCons.Node.FS
import SCons.Scanner import SCons.Scanner
def IDLScan(): def IDLScan():
"""Return a prototype Scanner instance for scanning IDL source files""" """Return a prototype Scanner instance for scanning IDL source files"""
cs = SCons.Scanner.ClassicCPP("IDLScan", cs = SCons.Scanner.ClassicCPP(
"$IDLSUFFIXES", "IDLScan",
"CPPPATH", "$IDLSUFFIXES",
'^[ \t]*(?:#[ \t]*include|[ \t]*import)[ \t]+(<|")([^>"]+)(>|")') "CPPPATH",
r'^[ \t]*(?:#[ \t]*include|[ \t]*import)[ \t]+(<|")([^>"]+)(>|")',
)
return cs return cs
# Local Variables: # Local Variables:

View file

@ -1,11 +1,6 @@
"""SCons.Scanner.LaTeX # MIT License
This module implements the dependency scanner for LaTeX code.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -25,9 +20,8 @@ This module implements the dependency scanner for LaTeX code.
# 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.
#
__revision__ = "src/engine/SCons/Scanner/LaTeX.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Dependency scanner for LaTeX code."""
import os.path import os.path
import re import re
@ -42,7 +36,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 +71,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.
@ -96,7 +90,6 @@ class FindENVPathDirs(object):
return tuple(dir.Rfindalldirs(path)) return tuple(dir.Rfindalldirs(path))
def LaTeXScanner(): def LaTeXScanner():
""" """
Return a prototype Scanner instance for scanning LaTeX source files Return a prototype Scanner instance for scanning LaTeX source files
@ -109,6 +102,7 @@ def LaTeXScanner():
recursive = 0) recursive = 0)
return ds return ds
def PDFLaTeXScanner(): def PDFLaTeXScanner():
""" """
Return a prototype Scanner instance for scanning LaTeX source files Return a prototype Scanner instance for scanning LaTeX source files
@ -121,9 +115,9 @@ def PDFLaTeXScanner():
recursive = 0) recursive = 0)
return ds return ds
class LaTeX(SCons.Scanner.Base): class LaTeX(SCons.Scanner.Base):
""" """Class for scanning LaTeX files for included files.
Class for scanning LaTeX files for included files.
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
@ -179,15 +173,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 +205,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 +233,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 +319,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 +334,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 +358,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

View file

@ -1,5 +1,6 @@
# MIT License
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -19,9 +20,8 @@
# 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.
#
__revision__ = "src/engine/SCons/Scanner/Prog.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Dependency scanner for program files."""
import SCons.Node import SCons.Node
import SCons.Node.FS import SCons.Node.FS
@ -39,9 +39,7 @@ def ProgramScanner(**kw):
return ps return ps
def _subst_libs(env, libs): def _subst_libs(env, libs):
""" """Substitute environment variables and split into list."""
Substitute environment variables and split into list.
"""
if SCons.Util.is_String(libs): if SCons.Util.is_String(libs):
libs = env.subst(libs) libs = env.subst(libs)
if SCons.Util.is_String(libs): if SCons.Util.is_String(libs):
@ -57,9 +55,9 @@ def _subst_libs(env, libs):
return libs return libs
def scan(node, env, libpath = ()): def scan(node, env, libpath = ()):
""" """Scans program files for static-library dependencies.
This scanner scans program files for static-library
dependencies. It will search the LIBPATH environment variable It will search the LIBPATH environment variable
for libraries specified in the LIBS variable, returning any for libraries specified in the LIBS variable, returning any
files it finds as dependencies. files it finds as dependencies.
""" """

View file

@ -0,0 +1,166 @@
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""Dependency scanner for 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.
"""
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

@ -1,12 +1,6 @@
"""SCons.Scanner.RC # MIT License
This module implements the dependency scanner for RC (Interface
Definition Language) files.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -26,20 +20,17 @@ Definition Language) files.
# 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.
#
__revision__ = "src/engine/SCons/Scanner/RC.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Dependency scanner for RC (Interface Definition Language) files."""
import re
import SCons.Node.FS import SCons.Node.FS
import SCons.Scanner import SCons.Scanner
def no_tlb(nodes): def no_tlb(nodes):
""" """Filter out .tlb files as they are binary and shouldn't be scanned."""
Filter out .tlb files as they are binary and shouldn't be scanned
"""
# print("Nodes:%s"%[str(n) for n in nodes]) # print("Nodes:%s"%[str(n) for n in nodes])
return [n for n in nodes if str(n)[-4:] != '.tlb'] return [n for n in nodes if str(n)[-4:] != '.tlb']
@ -47,15 +38,15 @@ def no_tlb(nodes):
def RCScan(): 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 = (
'.*?\s+(?:ICON|BITMAP|CURSOR|HTML|FONT|MESSAGETABLE|TYPELIB|REGISTRY|D3DFX)' \ r'^(?:\s*#\s*(?:include)|'
'\s*.*?)' \ r'.*?\s+(?:ICON|BITMAP|CURSOR|HTML|FONT|MESSAGETABLE|TYPELIB|REGISTRY|D3DFX)'
'\s*(<|"| )([^>"\s]+)(?:[>"\s])*$' r'\s*.*?)'
resScanner = SCons.Scanner.ClassicCPP("ResourceScanner", r'\s*(<|"| )([^>"\s]+)(?:[>"\s])*$'
"$RCSUFFIXES", )
"CPPPATH", resScanner = SCons.Scanner.ClassicCPP(
res_re, "ResourceScanner", "$RCSUFFIXES", "CPPPATH", res_re, recursive=no_tlb
recursive=no_tlb) )
return resScanner return resScanner

View file

@ -1,11 +1,6 @@
"""SCons.Scanner.SWIG # MIT License
This module implements the dependency scanner for SWIG code.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -25,16 +20,15 @@ This module implements the dependency scanner for SWIG code.
# 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.
#
__revision__ = "src/engine/SCons/Scanner/SWIG.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """Dependency scanner for SWIG code."""
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

@ -1,11 +1,6 @@
"""SCons.Scanner # MIT License
The Scanner package for the SCons software construction utility.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -25,9 +20,8 @@ The Scanner package for the SCons software construction utility.
# 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.
#
__revision__ = "src/engine/SCons/Scanner/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """The Scanner package for the SCons software construction utility."""
import re import re
@ -35,7 +29,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
@ -43,16 +37,16 @@ class _Null(object):
_null = _Null _null = _Null
def Scanner(function, *args, **kw): def Scanner(function, *args, **kw):
""" """Factory function to create a Scanner Object.
Public interface factory function for creating different types
of Scanners based on the different types of "functions" that may Creates the appropriate Scanner based on the type of "function".
be supplied.
TODO: Deprecate this some day. We've moved the functionality TODO: Deprecate this some day. We've moved the functionality
inside the Base class and really don't need this factory function inside the Base class and really don't need this factory function
any more. It was, however, used by some of our Tool modules, so any more. It was, however, used by some of our Tool modules, so
the call probably ended up in various people's custom modules the call probably ended up in various people's custom modules
patterned on SCons code. patterned on SCons code.
""" """
if SCons.Util.is_Dict(function): if SCons.Util.is_Dict(function):
return Selector(function, *args, **kw) return Selector(function, *args, **kw)
@ -60,10 +54,8 @@ def Scanner(function, *args, **kw):
return Base(function, *args, **kw) return Base(function, *args, **kw)
class FindPathDirs:
class FindPathDirs(object): """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.
""" """
def __init__(self, variable): def __init__(self, variable):
@ -81,67 +73,26 @@ class FindPathDirs(object):
class Base(object): class Base:
"""Base class for dependency scanners.
This implements straightforward, single-pass scanning of a single file.
""" """
The base class for dependency scanners. This implements def __init__(
straightforward, single-pass scanning of a single file. self,
""" function,
name="NONE",
def __init__(self, argument=_null,
function, skeys=_null,
name = "NONE", path_function=None,
argument = _null, # Node.FS.Base so that, by default, it's okay for a
skeys = _null, # scanner to return a Dir, File or Entry.
path_function = None, node_class=SCons.Node.FS.Base,
# Node.FS.Base so that, by default, it's okay for a node_factory=None,
# scanner to return a Dir, File or Entry. scan_check=None,
node_class = SCons.Node.FS.Base, recursive=None,
node_factory = None, ):
scan_check = None, """Construct a new scanner object given a scanner function.
recursive = None):
"""
Construct a new scanner object given a scanner function.
'function' - a scanner function taking two or three
arguments and returning a list of strings.
'name' - a name for identifying this scanner object.
'argument' - an optional argument that, if specified, will be
passed to both the scanner function and the path_function.
'skeys' - an optional list argument that can be used to determine
which scanner should be used for a given Node. In the case of File
nodes, for example, the 'skeys' would be file suffixes.
'path_function' - a function that takes four or five arguments
(a construction environment, Node for the directory containing
the SConscript file that defined the primary target, list of
target nodes, list of source nodes, and optional argument for
this instance) and returns a tuple of the directories that can
be searched for implicit dependency files. May also return a
callable() which is called with no args and returns the tuple
(supporting Bindable class).
'node_class' - the class of Nodes which this scan will return.
If node_class is None, then this scanner will not enforce any
Node conversion and will return the raw results from the
underlying scanner function.
'node_factory' - the factory function to be called to translate
the raw results returned by the scanner function into the
expected node_class objects.
'scan_check' - a function to be called to first check whether
this node really needs to be scanned.
'recursive' - specifies that this scanner should be invoked
recursively on all of the implicit dependencies it returns
(the canonical example being #include lines in C source files).
May be a callable, which will be called to filter the list
of nodes found to select a subset for recursive scanning
(the canonical example being only recursively scanning
subdirectories within a directory).
The scanner function's first argument will be a Node that should The scanner function's first argument will be a Node that should
be scanned for dependencies, the second argument will be an be scanned for dependencies, the second argument will be an
@ -152,14 +103,55 @@ class Base(object):
Examples: Examples:
s = Scanner(my_scanner_function) s = Scanner(my_scanner_function)
s = Scanner(function = my_scanner_function)
s = Scanner(function = my_scanner_function, argument = 'foo')
s = Scanner(function = my_scanner_function) Args:
function: a scanner function taking two or three arguments
and returning a list of strings.
s = Scanner(function = my_scanner_function, argument = 'foo') name: a name for identifying this scanner object.
argument: an optional argument that, if specified, will be
passed to both the scanner function and the path_function.
skeys: an optional list argument that can be used
to determine which scanner should be used for a given
Node. In the case of File nodes, for example, the 'skeys'
would be file suffixes.
path_function: a function that takes four or five arguments
(a construction environment, Node for the directory
containing the SConscript file that defined the primary
target, list of target nodes, list of source nodes, and
optional argument for this instance) and returns a tuple
of the directories that can be searched for implicit
dependency files. May also return a callable() which
is called with no args and returns the tuple (supporting
Bindable class).
node_class: the class of Nodes which this scan will return.
If node_class is None, then this scanner will not enforce
any Node conversion and will return the raw results from
the underlying scanner function.
node_factory: the factory function to be called to
translate the raw results returned by the scanner function
into the expected node_class objects.
scan_check: a function to be called to first check whether
this node really needs to be scanned.
recursive: specifies that this scanner should be invoked
recursively on all of the implicit dependencies it returns
(the canonical example being #include lines in C source
files). May be a callable, which will be called to filter
the list of nodes found to select a subset for recursive
scanning (the canonical example being only recursively
scanning subdirectories within a directory).
""" """
# Note: this class could easily work with scanner functions that take # Note: this class could easily work with scanner functions that take
# something other than a filename as an argument (e.g. a database # something other than a filename as an argument (e.g. a database
# node) and a dependencies list that aren't file names. All that # node) and a dependencies list that aren't file names. All that
@ -196,18 +188,22 @@ class Base(object):
return self.path_function(env, dir, target, source) return self.path_function(env, dir, target, source)
def __call__(self, node, env, path=()): def __call__(self, node, env, path=()):
""" """Scans a single object.
This method scans a single object. 'node' is the node
that will be passed to the scanner function, and 'env' is the Args:
environment that will be passed to the scanner function. A list of node: the node that will be passed to the scanner function
direct dependency nodes for the specified node will be returned. env: the environment that will be passed to the scanner function.
Returns:
A list of direct dependency nodes for the specified node.
""" """
if self.scan_check and not self.scan_check(node, env): if self.scan_check and not self.scan_check(node, env):
return [] return []
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,6 @@
# MIT License
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -19,13 +20,8 @@
# 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__ = "src/engine/SCons/Script/Interactive.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """SCons interactive mode. """
__doc__ = """
SCons interactive mode
"""
# TODO: # TODO:
# #
@ -247,7 +243,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

@ -1,23 +1,6 @@
"""SCons.Script # MIT License
#
This file implements the main() function used by the scons script. # Copyright The SCons Foundation
Architecturally, this *is* the scons script, and will likely only be
called from the external "scons" wrapper. Consequently, anything here
should not be, or be considered, part of the build engine. If it's
something that we expect other software to want to use, it should go in
some other module. If it's specific to the "scons" script invocation,
it goes here.
"""
from __future__ import print_function
unsupported_python_version = (2, 6, 0)
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 +21,32 @@ 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" """The main() function used by the scons script.
Architecturally, this *is* the scons script, and will likely only be
called from the external "scons" wrapper. Consequently, anything here
should not be, or be considered, part of the build engine. If it's
something that we expect other software to want to use, it should go in
some other module. If it's specific to the "scons" script invocation,
it goes here.
"""
# these define the range of versions SCons supports
unsupported_python_version = (3, 4, 0)
deprecated_python_version = (3, 4, 0)
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 threading
import SCons.CacheDir import SCons.CacheDir
import SCons.Debug import SCons.Debug
@ -58,24 +57,28 @@ 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
def fetch_win32_parallel_msg(): first_command_start = None
# A subsidiary function that exists solely to isolate this import last_command_end = None
# so we don't have to pull it in on all platforms, and so that an print_objects = 0
# in-line "import" statement in the _main() function below doesn't print_memoizer = 0
# cause warnings about local names shadowing use of the 'SCons' print_stacktrace = 0
# global in nest scopes and UnboundLocalErrors and the like in some print_time = 0
# versions (2.1) of Python. print_action_timestamps = 0
import SCons.Platform.win32 sconscript_time = 0
return SCons.Platform.win32.parallel_msg 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 revert_io(): def revert_io():
@ -85,17 +88,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 +196,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 +341,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 +417,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 +434,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 +447,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 +459,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 +483,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 +615,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 +627,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 +665,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 +685,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 +804,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 +865,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)
@ -1089,7 +1099,7 @@ def _main(parser):
SCons.Job.explicit_stack_size = options.stack_size SCons.Job.explicit_stack_size = options.stack_size
if options.md5_chunksize: if options.md5_chunksize:
SCons.Node.FS.File.md5_chunksize = options.md5_chunksize SCons.Node.FS.File.md5_chunksize = options.md5_chunksize * 1024
platform = SCons.Platform.platform_module() platform = SCons.Platform.platform_module()
@ -1161,7 +1171,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 +1250,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,16 +1267,23 @@ 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
# As of python 3.4 threading has a dummy_threading module for use when there is no threading
# it's get_ident() will allways return -1, while real threading modules get_ident() will
# always return a positive integer
python_has_threads = threading.get_ident() != -1
# 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
jobs = SCons.Job.Jobs(num_jobs, taskmaster) jobs = SCons.Job.Jobs(num_jobs, taskmaster)
if num_jobs > 1: if num_jobs > 1:
msg = None msg = None
if sys.platform == 'win32': if jobs.num_jobs == 1 or not python_has_threads:
msg = fetch_win32_parallel_msg()
elif jobs.num_jobs == 1 or not python_has_threads:
msg = "parallel builds are unsupported by this version of Python;\n" + \ msg = "parallel builds are unsupported by this version of Python;\n" + \
"\tignoring -j or num_jobs option.\n" "\tignoring -j or num_jobs option.\n"
if msg: if msg:
@ -1312,8 +1333,7 @@ def _exec_main(parser, values):
import pdb import pdb
pdb.Pdb().runcall(_main, parser) pdb.Pdb().runcall(_main, parser)
elif options.profile_file: elif options.profile_file:
# compat layer imports "cProfile" for us if it's available. from cProfile import Profile
from profile import Profile
prof = Profile() prof = Profile()
try: try:
@ -1339,15 +1359,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 +1382,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,6 @@
# MIT License
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -19,25 +20,19 @@
# 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.
#
__revision__ = "src/engine/SCons/Script/SConsOptions.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
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: import gettext
from gettext import gettext _ = gettext.gettext
except ImportError:
def gettext(message):
return message
_ = 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
@ -125,7 +120,8 @@ class SConsValues(optparse.Values):
# is not available. # is not available.
raise AttributeError(attr) raise AttributeError(attr)
# keep this list in sync with the SetOption doc in SCons/Script/Main.xml
# search for UPDATE_SETOPTION_DOCS there.
settable = [ settable = [
'clean', 'clean',
'diskcheck', 'diskcheck',
@ -139,14 +135,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 +163,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 +193,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 +225,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):
""" """
@ -299,8 +268,7 @@ class SConsOptionParser(optparse.OptionParser):
""" """
arg = rargs.pop(0) arg = rargs.pop(0)
# Value explicitly attached to arg? Pretend it's the next # Value explicitly attached to arg? Pretend it's the next argument.
# argument.
if "=" in arg: if "=" in arg:
(opt, next_arg) = arg.split("=", 1) (opt, next_arg) = arg.split("=", 1)
rargs.insert(0, next_arg) rargs.insert(0, next_arg)
@ -310,7 +278,11 @@ class SConsOptionParser(optparse.OptionParser):
had_explicit_value = False had_explicit_value = False
try: try:
opt = self._match_long_opt(opt) if opt != self._match_long_opt(opt):
raise optparse.BadOptionError(
"'%s'. Did you mean '%s'?"
% (opt, self._match_long_opt(opt))
)
except optparse.BadOptionError: except optparse.BadOptionError:
if self.preserve_unknown_options: if self.preserve_unknown_options:
# SCons-specific: if requested, add unknown options to # SCons-specific: if requested, add unknown options to
@ -358,29 +330,27 @@ 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 Parse options stored in `self.largs`, so that any value
command line is immediately available if the user turns overridden on the command line is immediately available
around and does a GetOption() right away. if the user turns around and does a :func:`GetOption` right away.
We mimic the processing of the single args We mimic the processing of the single args
in the original OptionParser._process_args(), but here we in the original OptionParser :func:`_process_args`, but here we
allow exact matches for long-opts only (no partial allow exact matches for long-opts only (no partial argument names!).
argument names!). Otherwise there could be problems in :func:`add_local_option`
Else, this would lead to problems in 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.
So, when we only have a value for "--myargument" yet, 1. haven't been processed so far (`self.largs`), but
a command-line argument of "--myarg=test" would set it. 2. are possibly not added to the list of options yet.
Responsible for this behaviour is the method
_match_long_opt(), which allows for partial matches of So, when we only have a value for "--myargument" so far,
the option name, as long as the common prefix appears to a command-line argument of "--myarg=test" would set it,
be unique. per the behaviour of :func:`_match_long_opt`,
which allows for partial matches of the option name,
as long as the common prefix appears to be unique.
This would lead to further confusion, because we might want 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).
@ -426,7 +396,7 @@ class SConsOptionParser(optparse.OptionParser):
""" """
Adds a local option to the parser. Adds a local option to the parser.
This is initiated by a SetOption() call to add a user-defined This is initiated by an AddOption() call to add a user-defined
command-line option. We add the option to a separate option command-line option. We add the option to a separate option
group for the local options, creating the group if necessary. group for the local options, creating the group if necessary.
""" """
@ -614,9 +584,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 +610,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 +622,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 +641,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 +671,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 +688,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 +721,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 +834,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 +848,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 +901,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 +974,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

@ -1,12 +1,6 @@
"""SCons.Script.SConscript # MIT License
This module defines the Python API provided to SConscript and SConstruct
files.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -27,7 +21,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" """This module defines the Python API provided to SConscript files."""
import SCons import SCons
import SCons.Action import SCons.Action
@ -42,11 +36,10 @@ 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 os import os
import os.path import os.path
import re import re
@ -98,7 +91,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 +103,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 +126,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 +146,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 and must_exist is None:
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 +195,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 +272,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 +288,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 +392,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 +414,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 +435,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 +546,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 +580,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 +633,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

@ -1,18 +1,6 @@
"""SCons.Script # MIT License
This file implements the main() function used by the scons script.
Architecturally, this *is* the scons script, and will likely only be
called from the external "scons" wrapper. Consequently, anything here
should not be, or be considered, part of the build engine. If it's
something that we expect other software to want to use, it should go in
some other module. If it's specific to the "scons" script invocation,
it goes here.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -32,20 +20,23 @@ it goes here.
# 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.
#
__revision__ = "src/engine/SCons/Script/__init__.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """The main() function used by the scons script.
Architecturally, this *is* the scons script, and will likely only be
called from the external "scons" wrapper. Consequently, anything here
should not be, or be considered, part of the build engine. If it's
something that we expect other software to want to use, it should go in
some other module. If it's specific to the "scons" script invocation,
it goes here.
"""
import time import time
start_time = time.time() start_time = time.time()
import collections import collections
import os import os
from io import StringIO
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
import sys import sys
@ -69,7 +60,7 @@ if "--debug=memoizer" in _args:
import SCons.Warnings import SCons.Warnings
try: try:
SCons.Memoize.EnableMemoization() SCons.Memoize.EnableMemoization()
except SCons.Warnings.Warning: except SCons.Warnings.SConsWarning:
# Some warning was thrown. Arrange for it to be displayed # Some warning was thrown. Arrange for it to be displayed
# or not after warnings are configured. # or not after warnings are configured.
from . import Main from . import Main
@ -81,8 +72,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
@ -134,7 +125,6 @@ GetBuildFailures = Main.GetBuildFailures
#profiling = Main.profiling #profiling = Main.profiling
#repositories = Main.repositories #repositories = Main.repositories
#
from . import SConscript from . import SConscript
_SConscript = SConscript _SConscript = SConscript
@ -150,6 +140,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 +153,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 +268,24 @@ 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 +308,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 +338,8 @@ GlobalDefaultEnvironmentFunctions = [
'Requires', 'Requires',
'SConsignFile', 'SConsignFile',
'SideEffect', 'SideEffect',
'SourceCode',
'SourceSignatures',
'Split', 'Split',
'Tag', 'Tag',
'TargetSignatures',
'Value', 'Value',
'VariantDir', 'VariantDir',
] ]
@ -374,7 +367,9 @@ GlobalDefaultBuilders = [
'SharedObject', 'SharedObject',
'StaticLibrary', 'StaticLibrary',
'StaticObject', 'StaticObject',
'Substfile',
'Tar', 'Tar',
'Textfile',
'TypeLibrary', 'TypeLibrary',
'Zip', 'Zip',
'Package', 'Package',

View file

@ -1,11 +1,6 @@
"""SCons.Subst # MIT License
SCons string substitution.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -26,28 +21,30 @@ 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" """SCons string substitution."""
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
# Indexed by the SUBST_* constants below. # Indexed by the SUBST_* constants below.
_strconv = [SCons.Util.to_String_for_subst, _strconv = [
SCons.Util.to_String_for_subst, SCons.Util.to_String_for_subst,
SCons.Util.to_String_for_signature] SCons.Util.to_String_for_subst,
SCons.Util.to_String_for_signature,
]
AllowableExceptions = (IndexError, NameError) AllowableExceptions = (IndexError, NameError)
def SetAllowableExceptions(*excepts): def SetAllowableExceptions(*excepts):
global AllowableExceptions global AllowableExceptions
AllowableExceptions = [_f for _f in excepts if _f] AllowableExceptions = [_f for _f in excepts if _f]
def raise_exception(exception, target, s): def raise_exception(exception, target, s):
name = exception.__class__.__name__ name = exception.__class__.__name__
msg = "%s `%s' trying to evaluate `%s'" % (name, exception, s) msg = "%s `%s' trying to evaluate `%s'" % (name, exception, s)
@ -57,8 +54,7 @@ def raise_exception(exception, target, s):
raise SCons.Errors.UserError(msg) raise SCons.Errors.UserError(msg)
class Literal:
class Literal(object):
"""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 +82,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 +167,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 +228,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 +327,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 +747,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 +791,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 +806,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]
@ -613,233 +883,6 @@ def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gv
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,6 @@
# MIT License
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -20,46 +21,35 @@
# 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 """Generic Taskmaster module for the SCons build engine.
import sys This module contains the primary interface(s) between a wrapping user
interface and the SCons build engine. There are two key classes here:
__doc__ = """ Taskmaster
Generic Taskmaster module for the SCons build engine. This is the main engine for walking the dependency graph and
===================================================== calling things to decide what does or doesn't need to be built.
This module contains the primary interface(s) between a wrapping user Task
interface and the SCons build engine. There are two key classes here: This is the base class for allowing a wrapping interface to
decide what does or doesn't actually need to be done. The
intention is for a wrapping interface to subclass this as
appropriate for different types of behavior it may need.
Taskmaster The canonical example is the SCons native Python interface,
---------- which has Task subclasses that handle its specific behavior,
This is the main engine for walking the dependency graph and like printing "'foo' is up to date" when a top-level target
calling things to decide what does or doesn't need to be built. doesn't need to be built, and handling the -c option by removing
targets as its "build" action. There is also a separate subclass
for suppressing this output when the -q option is used.
Task The Taskmaster instantiates a Task object for each (set of)
---- target(s) that it decides need to be evaluated and/or built.
This is the base class for allowing a wrapping interface to
decide what does or doesn't actually need to be done. The
intention is for a wrapping interface to subclass this as
appropriate for different types of behavior it may need.
The canonical example is the SCons native Python interface,
which has Task subclasses that handle its specific behavior,
like printing "'foo' is up to date" when a top-level target
doesn't need to be built, and handling the -c option by removing
targets as its "build" action. There is also a separate subclass
for suppressing this output when the -q option is used.
The Taskmaster instantiates a Task object for each (set of)
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"
from itertools import chain
import operator
import sys import sys
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 +71,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 +107,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 +159,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 +200,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 +213,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 +379,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 +427,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 +444,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 +531,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 +559,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 +587,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 +769,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 +797,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 +805,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 +836,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 +851,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 +897,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 +923,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,15 +1,6 @@
from __future__ import print_function # MIT License
"""SCons.Tool.DCommon
Common code for the various D tools.
Coded by Russel Winder (russel@winder.org.uk)
2012-09-06
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -29,9 +20,14 @@ Coded by Russel Winder (russel@winder.org.uk)
# 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.
#
__revision__ = "src/engine/SCons/Tool/DCommon.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog" """SCons.Tool.DCommon
Common code for the various D tools.
Coded by Russel Winder (russel@winder.org.uk)
2012-09-06
"""
import os.path import os.path
@ -61,7 +57,6 @@ def allAtOnceEmitter(target, source, env):
env.Clean(target[0], str(target[0]) + '.o') env.Clean(target[0], str(target[0]) + '.o')
return target, source return target, source
# Local Variables: # Local Variables:
# tab-width:4 # tab-width:4
# indent-tabs-mode:nil # indent-tabs-mode:nil

View file

@ -4,8 +4,9 @@ Stuff for processing Fortran, common to all fortran dialects.
""" """
# MIT License
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # Copyright 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
@ -25,16 +26,11 @@ Stuff for processing Fortran, common to all fortran dialects.
# 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__ = "src/engine/SCons/Tool/FortranCommon.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
import re import re
import os.path import os.path
import SCons.Action import SCons.Action
import SCons.Defaults
import SCons.Scanner.Fortran import SCons.Scanner.Fortran
import SCons.Tool import SCons.Tool
import SCons.Util import SCons.Util
@ -64,7 +60,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())
@ -79,10 +76,12 @@ def _fortranEmitter(target, source, env):
return (target, source) return (target, source)
def FortranEmitter(target, source, env): def FortranEmitter(target, source, env):
import SCons.Defaults
target, source = _fortranEmitter(target, source, env) target, source = _fortranEmitter(target, source, env)
return SCons.Defaults.StaticObjectEmitter(target, source, env) return SCons.Defaults.StaticObjectEmitter(target, source, env)
def ShFortranEmitter(target, source, env): def ShFortranEmitter(target, source, env):
import SCons.Defaults
target, source = _fortranEmitter(target, source, env) target, source = _fortranEmitter(target, source, env)
return SCons.Defaults.SharedObjectEmitter(target, source, env) return SCons.Defaults.SharedObjectEmitter(target, source, env)

View file

@ -3,7 +3,7 @@
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
@ -24,32 +24,32 @@ 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
############################################################################# #############################################################################
class XgettextToolWarning(SCons.Warnings.Warning): pass class XgettextToolWarning(SCons.Warnings.SConsWarning): pass
class XgettextNotFound(XgettextToolWarning): pass class XgettextNotFound(XgettextToolWarning): pass
class MsginitToolWarning(SCons.Warnings.Warning): pass class MsginitToolWarning(SCons.Warnings.SConsWarning): pass
class MsginitNotFound(MsginitToolWarning): pass class MsginitNotFound(MsginitToolWarning): pass
class MsgmergeToolWarning(SCons.Warnings.Warning): pass class MsgmergeToolWarning(SCons.Warnings.SConsWarning): pass
class MsgmergeNotFound(MsgmergeToolWarning): pass class MsgmergeNotFound(MsgmergeToolWarning): pass
class MsgfmtToolWarning(SCons.Warnings.Warning): pass class MsgfmtToolWarning(SCons.Warnings.SConsWarning): pass
class MsgfmtNotFound(MsgfmtToolWarning): pass class MsgfmtNotFound(MsgfmtToolWarning): pass
@ -69,7 +69,7 @@ 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`
@ -198,19 +198,19 @@ 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)
@ -269,7 +269,7 @@ def _translate(env, target=None, source=SCons.Environment._null, *args, **kw):
############################################################################# #############################################################################
############################################################################# #############################################################################
class RPaths(object): class RPaths:
""" Callable object, which returns pathnames relative to SCons current """ Callable object, which returns pathnames relative to SCons current
working directory. working directory.
@ -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,20 @@ 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|\\\\|//|\\[\'"]|[\'"\{\}\;\.\(\)]|' + # Lambda function symbols: ->
r'\d*\.\d*|[A-Za-z_][\w\$\.]*|<[A-Za-z_]\w+>|' + _reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"{\};.()]|' +
r'/\*|\*/|\[\])') r'\d*\.\d*|[A-Za-z_][\w$.]*|<[A-Za-z_]\w+>|' +
r'/\*|\*/|\[\]|->)')
class OuterState(object):
class OuterState:
"""The initial state for parsing a Java file for classes, """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)
@ -116,7 +151,7 @@ if java_parsing:
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 +160,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 +181,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 +207,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 +319,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 +360,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 +376,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 +440,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,16 +21,12 @@
# 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++.
""" """
import copy
import os
import re
import subprocess
import SCons.Errors import SCons.Errors
import SCons.Platform.win32 import SCons.Platform.win32
@ -42,7 +38,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,13 @@
# 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
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 +36,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,105 @@ 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 modulelist = (
except ImportError: # root module and parent/root module
debug = lambda message: open(LOGFILE, 'a').write(message + '\n') 'MSCommon', 'Tool',
else: # python library and below: correct iff scons does not have a lib folder
logging.basicConfig(filename=LOGFILE, level=logging.DEBUG) 'lib',
debug = logging.debug # scons modules
'SCons', 'test', 'scons'
)
def get_relative_filename(filename, module_list):
if not filename:
return filename
for module in module_list:
try:
ind = filename.rindex(module)
return filename[ind:]
except ValueError:
pass
return filename
class _Debug_Filter(logging.Filter):
# custom filter for module relative filename
def filter(self, record):
relfilename = get_relative_filename(record.pathname, modulelist)
relfilename = relfilename.replace('\\', '/')
record.relfilename = relfilename
return True
logging.basicConfig(
# This looks like:
# 00109ms:MSCommon/vc.py:find_vc_pdir#447:
format=(
'%(relativeCreated)05dms'
':%(relfilename)s'
':%(funcName)s'
'#%(lineno)s'
':%(message)s: '
),
filename=LOGFILE,
level=logging.DEBUG)
logger = logging.getLogger(name=__name__)
logger.addFilter(_Debug_Filter())
debug = logger.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 +156,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 +169,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 +184,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 +207,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 +249,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 +272,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,955 @@
#
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
"""Module for Visual C/C++ detection and configuration.
# 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
"""
import SCons.compat
import SCons.Util
import subprocess
import os
import platform
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("TARGET_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 get_installed_vcs(env=None):
global __INSTALLED_VCS_RUN
if __INSTALLED_VCS_RUN is not None:
return __INSTALLED_VCS_RUN
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)))
__INSTALLED_VCS_RUN = installed_versions
return __INSTALLED_VCS_RUN
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 = 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, 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 = 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

@ -0,0 +1,868 @@
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""SCons.Tool
SCons tool selection.
This looks for modules that define a callable object that can modify
a construction environment as appropriate for a given tool (or tool
chain).
Note that because this subsystem just *selects* a callable that can
modify a construction environment, it's possible for people to define
their own "tool specification" in an arbitrary callable function. No
one needs to use or tie in to this subsystem in order to roll their own
tool specifications.
"""
import sys
import os
import importlib.util
import SCons.Builder
import SCons.Errors
import SCons.Node.FS
import SCons.Scanner
import SCons.Scanner.C
import SCons.Scanner.D
import SCons.Scanner.LaTeX
import SCons.Scanner.Prog
import SCons.Scanner.SWIG
from SCons.Tool.linkCommon import LibSymlinksActionFunction, LibSymlinksStrFun
DefaultToolpath = []
CScanner = SCons.Scanner.C.CScanner()
DScanner = SCons.Scanner.D.DScanner()
LaTeXScanner = SCons.Scanner.LaTeX.LaTeXScanner()
PDFLaTeXScanner = SCons.Scanner.LaTeX.PDFLaTeXScanner()
ProgramScanner = SCons.Scanner.Prog.ProgramScanner()
SourceFileScanner = SCons.Scanner.Base({}, name='SourceFileScanner')
SWIGScanner = SCons.Scanner.SWIG.SWIGScanner()
CSuffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
".h", ".H", ".hxx", ".hpp", ".hh",
".F", ".fpp", ".FPP",
".m", ".mm",
".S", ".spp", ".SPP", ".sx"]
DSuffixes = ['.d']
IDLSuffixes = [".idl", ".IDL"]
LaTeXSuffixes = [".tex", ".ltx", ".latex"]
SWIGSuffixes = ['.i']
for suffix in CSuffixes:
SourceFileScanner.add_scanner(suffix, CScanner)
for suffix in DSuffixes:
SourceFileScanner.add_scanner(suffix, DScanner)
for suffix in SWIGSuffixes:
SourceFileScanner.add_scanner(suffix, SWIGScanner)
# FIXME: what should be done here? Two scanners scan the same extensions,
# but look for different files, e.g., "picture.eps" vs. "picture.pdf".
# The builders for DVI and PDF explicitly reference their scanners
# I think that means this is not needed???
for suffix in LaTeXSuffixes:
SourceFileScanner.add_scanner(suffix, LaTeXScanner)
SourceFileScanner.add_scanner(suffix, PDFLaTeXScanner)
# Tool aliases are needed for those tools whose module names also
# occur in the python standard library (This causes module shadowing and
# can break using python library functions under python3) or if the current tool/file names
# are not legal module names (violate python's identifier rules or are
# python language keywords).
TOOL_ALIASES = {
'gettext': 'gettext_tool',
'clang++': 'clangxx',
'as': 'asm',
}
class Tool:
def __init__(self, name, toolpath=None, **kw):
if toolpath is None:
toolpath = []
# Rename if there's a TOOL_ALIAS for this tool
self.name = TOOL_ALIASES.get(name, name)
self.toolpath = toolpath + DefaultToolpath
# remember these so we can merge them into the call
self.init_kw = kw
module = self._tool_module()
self.generate = module.generate
self.exists = module.exists
if hasattr(module, 'options'):
self.options = module.options
def _load_dotted_module_py2(self, short_name, full_name, searchpaths=None):
import imp
splitname = short_name.split('.')
index = 0
srchpths = searchpaths
for item in splitname:
file, path, desc = imp.find_module(item, srchpths)
mod = imp.load_module(full_name, file, path, desc)
srchpths = [path]
return mod, file
def _tool_module(self):
oldpythonpath = sys.path
sys.path = self.toolpath + sys.path
# sys.stderr.write("Tool:%s\nPATH:%s\n"%(self.name,sys.path))
# From: http://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path/67692#67692
# import importlib.util
# spec = importlib.util.spec_from_file_location("module.name", "/path/to/file.py")
# foo = importlib.util.module_from_spec(spec)
# spec.loader.exec_module(foo)
# foo.MyClass()
# Py 3 code
# sys.stderr.write("toolpath:%s\n" % self.toolpath)
# sys.stderr.write("SCONS.TOOL path:%s\n" % sys.modules['SCons.Tool'].__path__)
debug = False
spec = None
found_name = self.name
add_to_scons_tools_namespace = False
for path in self.toolpath:
sepname = self.name.replace('.', os.path.sep)
file_path = os.path.join(path, "%s.py" % sepname)
file_package = os.path.join(path, sepname)
if debug: sys.stderr.write("Trying:%s %s\n" % (file_path, file_package))
if os.path.isfile(file_path):
spec = importlib.util.spec_from_file_location(self.name, file_path)
if debug: print("file_Path:%s FOUND" % file_path)
break
elif os.path.isdir(file_package):
file_package = os.path.join(file_package, '__init__.py')
spec = importlib.util.spec_from_file_location(self.name, file_package)
if debug: print("PACKAGE:%s Found" % file_package)
break
else:
continue
if spec is None:
if debug: sys.stderr.write("NO SPEC :%s\n" % self.name)
spec = importlib.util.find_spec("." + self.name, package='SCons.Tool')
if spec:
found_name = 'SCons.Tool.' + self.name
add_to_scons_tools_namespace = True
if debug: sys.stderr.write("Spec Found? .%s :%s\n" % (self.name, spec))
if spec is None:
sconstools = os.path.normpath(sys.modules['SCons.Tool'].__path__[0])
if self.toolpath:
sconstools = ", ".join(self.toolpath) + ", " + sconstools
error_string = "No tool module '%s' found in %s" % (self.name, sconstools)
raise SCons.Errors.UserError(error_string)
module = importlib.util.module_from_spec(spec)
if module is None:
if debug: print("MODULE IS NONE:%s" % self.name)
error_string = "Tool module '%s' failed import" % self.name
raise SCons.Errors.SConsEnvironmentError(error_string)
# Don't reload a tool we already loaded.
sys_modules_value = sys.modules.get(found_name, False)
found_module = None
if sys_modules_value and sys_modules_value.__file__ == spec.origin:
found_module = sys.modules[found_name]
else:
# Not sure what to do in the case that there already
# exists sys.modules[self.name] but the source file is
# different.. ?
module = spec.loader.load_module(spec.name)
sys.modules[found_name] = module
if add_to_scons_tools_namespace:
# If we found it in SCons.Tool, then add it to the module
setattr(SCons.Tool, self.name, module)
found_module = module
if found_module is not None:
sys.path = oldpythonpath
return found_module
sys.path = oldpythonpath
full_name = 'SCons.Tool.' + self.name
try:
return sys.modules[full_name]
except KeyError:
try:
smpath = sys.modules['SCons.Tool'].__path__
try:
module, file = self._load_dotted_module_py2(self.name, full_name, smpath)
setattr(SCons.Tool, self.name, module)
if file:
file.close()
return module
except ImportError as e:
if str(e) != "No module named %s" % self.name:
raise SCons.Errors.SConsEnvironmentError(e)
try:
import zipimport
importer = zipimport.zipimporter(sys.modules['SCons.Tool'].__path__[0])
module = importer.load_module(full_name)
setattr(SCons.Tool, self.name, module)
return module
except ImportError as e:
m = "No tool named '%s': %s" % (self.name, e)
raise SCons.Errors.SConsEnvironmentError(m)
except ImportError as e:
m = "No tool named '%s': %s" % (self.name, e)
raise SCons.Errors.SConsEnvironmentError(m)
def __call__(self, env, *args, **kw):
if self.init_kw is not None:
# Merge call kws into init kws;
# but don't bash self.init_kw.
if kw is not None:
call_kw = kw
kw = self.init_kw.copy()
kw.update(call_kw)
else:
kw = self.init_kw
env.Append(TOOLS=[self.name])
if hasattr(self, 'options'):
import SCons.Variables
if 'options' not in env:
from SCons.Script import ARGUMENTS
env['options'] = SCons.Variables.Variables(args=ARGUMENTS)
opts = env['options']
self.options(opts)
opts.Update(env)
self.generate(env, *args, **kw)
def __str__(self):
return self.name
LibSymlinksAction = SCons.Action.Action(LibSymlinksActionFunction, LibSymlinksStrFun)
##########################################################################
# Create common executable program / library / object builders
def createProgBuilder(env):
"""This is a utility function that creates the Program
Builder in an Environment if it is not there already.
If it is already there, we return the existing one.
"""
try:
program = env['BUILDERS']['Program']
except KeyError:
import SCons.Defaults
program = SCons.Builder.Builder(action=SCons.Defaults.LinkAction,
emitter='$PROGEMITTER',
prefix='$PROGPREFIX',
suffix='$PROGSUFFIX',
src_suffix='$OBJSUFFIX',
src_builder='Object',
target_scanner=ProgramScanner)
env['BUILDERS']['Program'] = program
return program
def createStaticLibBuilder(env):
"""This is a utility function that creates the StaticLibrary
Builder in an Environment if it is not there already.
If it is already there, we return the existing one.
"""
try:
static_lib = env['BUILDERS']['StaticLibrary']
except KeyError:
action_list = [SCons.Action.Action("$ARCOM", "$ARCOMSTR")]
if env.get('RANLIB', False) or env.Detect('ranlib'):
ranlib_action = SCons.Action.Action("$RANLIBCOM", "$RANLIBCOMSTR")
action_list.append(ranlib_action)
static_lib = SCons.Builder.Builder(action=action_list,
emitter='$LIBEMITTER',
prefix='$LIBPREFIX',
suffix='$LIBSUFFIX',
src_suffix='$OBJSUFFIX',
src_builder='StaticObject')
env['BUILDERS']['StaticLibrary'] = static_lib
env['BUILDERS']['Library'] = static_lib
return static_lib
def createSharedLibBuilder(env, shlib_suffix='$_SHLIBSUFFIX'):
"""This is a utility function that creates the SharedLibrary
Builder in an Environment if it is not there already.
If it is already there, we return the existing one.
Args:
shlib_suffix: The suffix specified for the shared library builder
"""
try:
shared_lib = env['BUILDERS']['SharedLibrary']
except KeyError:
import SCons.Defaults
action_list = [SCons.Defaults.SharedCheck,
SCons.Defaults.ShLinkAction,
LibSymlinksAction]
shared_lib = SCons.Builder.Builder(action=action_list,
emitter="$SHLIBEMITTER",
prefix="$SHLIBPREFIX",
suffix=shlib_suffix,
target_scanner=ProgramScanner,
src_suffix='$SHOBJSUFFIX',
src_builder='SharedObject')
env['BUILDERS']['SharedLibrary'] = shared_lib
return shared_lib
def createLoadableModuleBuilder(env, loadable_module_suffix='$_LDMODULESUFFIX'):
"""This is a utility function that creates the LoadableModule
Builder in an Environment if it is not there already.
If it is already there, we return the existing one.
Args:
loadable_module_suffix: The suffix specified for the loadable module builder
"""
try:
ld_module = env['BUILDERS']['LoadableModule']
except KeyError:
import SCons.Defaults
action_list = [SCons.Defaults.SharedCheck,
SCons.Defaults.LdModuleLinkAction,
LibSymlinksAction]
ld_module = SCons.Builder.Builder(action=action_list,
emitter="$LDMODULEEMITTER",
prefix="$LDMODULEPREFIX",
suffix=loadable_module_suffix,
target_scanner=ProgramScanner,
src_suffix='$SHOBJSUFFIX',
src_builder='SharedObject')
env['BUILDERS']['LoadableModule'] = ld_module
return ld_module
def createObjBuilders(env):
"""This is a utility function that creates the StaticObject
and SharedObject Builders in an Environment if they
are not there already.
If they are there already, we return the existing ones.
This is a separate function because soooo many Tools
use this functionality.
The return is a 2-tuple of (StaticObject, SharedObject)
"""
try:
static_obj = env['BUILDERS']['StaticObject']
except KeyError:
static_obj = SCons.Builder.Builder(action={},
emitter={},
prefix='$OBJPREFIX',
suffix='$OBJSUFFIX',
src_builder=['CFile', 'CXXFile'],
source_scanner=SourceFileScanner,
single_source=1)
env['BUILDERS']['StaticObject'] = static_obj
env['BUILDERS']['Object'] = static_obj
try:
shared_obj = env['BUILDERS']['SharedObject']
except KeyError:
shared_obj = SCons.Builder.Builder(action={},
emitter={},
prefix='$SHOBJPREFIX',
suffix='$SHOBJSUFFIX',
src_builder=['CFile', 'CXXFile'],
source_scanner=SourceFileScanner,
single_source=1)
env['BUILDERS']['SharedObject'] = shared_obj
return (static_obj, shared_obj)
def createCFileBuilders(env):
"""This is a utility function that creates the CFile/CXXFile
Builders in an Environment if they
are not there already.
If they are there already, we return the existing ones.
This is a separate function because soooo many Tools
use this functionality.
The return is a 2-tuple of (CFile, CXXFile)
"""
try:
c_file = env['BUILDERS']['CFile']
except KeyError:
c_file = SCons.Builder.Builder(action={},
emitter={},
suffix={None: '$CFILESUFFIX'})
env['BUILDERS']['CFile'] = c_file
env.SetDefault(CFILESUFFIX='.c')
try:
cxx_file = env['BUILDERS']['CXXFile']
except KeyError:
cxx_file = SCons.Builder.Builder(action={},
emitter={},
suffix={None: '$CXXFILESUFFIX'})
env['BUILDERS']['CXXFile'] = cxx_file
env.SetDefault(CXXFILESUFFIX='.cc')
return (c_file, cxx_file)
##########################################################################
# Create common Java builders
def CreateJarBuilder(env):
"""The Jar builder expects a list of class files
which it can package into a jar file.
The jar tool provides an interface for passing other types
of java files such as .java, directories or swig interfaces
and will build them to class files in which it can package
into the jar.
"""
try:
java_jar = env['BUILDERS']['JarFile']
except KeyError:
fs = SCons.Node.FS.get_default_fs()
jar_com = SCons.Action.Action('$JARCOM', '$JARCOMSTR')
java_jar = SCons.Builder.Builder(action=jar_com,
suffix='$JARSUFFIX',
src_suffix='$JAVACLASSSUFFIX',
src_builder='JavaClassFile',
source_factory=fs.Entry)
env['BUILDERS']['JarFile'] = java_jar
return java_jar
def CreateJavaHBuilder(env):
try:
java_javah = env['BUILDERS']['JavaH']
except KeyError:
fs = SCons.Node.FS.get_default_fs()
java_javah_com = SCons.Action.Action('$JAVAHCOM', '$JAVAHCOMSTR')
java_javah = SCons.Builder.Builder(action=java_javah_com,
src_suffix='$JAVACLASSSUFFIX',
target_factory=fs.Entry,
source_factory=fs.File,
src_builder='JavaClassFile')
env['BUILDERS']['JavaH'] = java_javah
return java_javah
def CreateJavaClassFileBuilder(env):
try:
java_class_file = env['BUILDERS']['JavaClassFile']
except KeyError:
fs = SCons.Node.FS.get_default_fs()
javac_com = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR')
java_class_file = SCons.Builder.Builder(action=javac_com,
emitter={},
# suffix = '$JAVACLASSSUFFIX',
src_suffix='$JAVASUFFIX',
src_builder=['JavaFile'],
target_factory=fs.Entry,
source_factory=fs.File)
env['BUILDERS']['JavaClassFile'] = java_class_file
return java_class_file
def CreateJavaClassDirBuilder(env):
try:
java_class_dir = env['BUILDERS']['JavaClassDir']
except KeyError:
fs = SCons.Node.FS.get_default_fs()
javac_com = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR')
java_class_dir = SCons.Builder.Builder(action=javac_com,
emitter={},
target_factory=fs.Dir,
source_factory=fs.Dir)
env['BUILDERS']['JavaClassDir'] = java_class_dir
return java_class_dir
def CreateJavaFileBuilder(env):
try:
java_file = env['BUILDERS']['JavaFile']
except KeyError:
java_file = SCons.Builder.Builder(action={},
emitter={},
suffix={None: '$JAVASUFFIX'})
env['BUILDERS']['JavaFile'] = java_file
env['JAVASUFFIX'] = '.java'
return java_file
class ToolInitializerMethod:
"""
This is added to a construction environment in place of a
method(s) normally called for a Builder (env.Object, env.StaticObject,
etc.). When called, it has its associated ToolInitializer
object search the specified list of tools and apply the first
one that exists to the construction environment. It then calls
whatever builder was (presumably) added to the construction
environment in place of this particular instance.
"""
def __init__(self, name, initializer):
"""
Note: we store the tool name as __name__ so it can be used by
the class that attaches this to a construction environment.
"""
self.__name__ = name
self.initializer = initializer
def get_builder(self, env):
"""
Returns the appropriate real Builder for this method name
after having the associated ToolInitializer object apply
the appropriate Tool module.
"""
builder = getattr(env, self.__name__)
self.initializer.apply_tools(env)
builder = getattr(env, self.__name__)
if builder is self:
# There was no Builder added, which means no valid Tool
# for this name was found (or possibly there's a mismatch
# between the name we were called by and the Builder name
# added by the Tool module).
return None
self.initializer.remove_methods(env)
return builder
def __call__(self, env, *args, **kw):
"""
"""
builder = self.get_builder(env)
if builder is None:
return [], []
return builder(*args, **kw)
class ToolInitializer:
"""
A class for delayed initialization of Tools modules.
Instances of this class associate a list of Tool modules with
a list of Builder method names that will be added by those Tool
modules. As part of instantiating this object for a particular
construction environment, we also add the appropriate
ToolInitializerMethod objects for the various Builder methods
that we want to use to delay Tool searches until necessary.
"""
def __init__(self, env, tools, names):
if not SCons.Util.is_List(tools):
tools = [tools]
if not SCons.Util.is_List(names):
names = [names]
self.env = env
self.tools = tools
self.names = names
self.methods = {}
for name in names:
method = ToolInitializerMethod(name, self)
self.methods[name] = method
env.AddMethod(method)
def remove_methods(self, env):
"""
Removes the methods that were added by the tool initialization
so we no longer copy and re-bind them when the construction
environment gets cloned.
"""
for method in self.methods.values():
env.RemoveMethod(method)
def apply_tools(self, env):
"""
Searches the list of associated Tool modules for one that
exists, and applies that to the construction environment.
"""
for t in self.tools:
tool = SCons.Tool.Tool(t)
if tool.exists(env):
env.Tool(tool)
return
# If we fall through here, there was no tool module found.
# This is where we can put an informative error message
# about the inability to find the tool. We'll start doing
# this as we cut over more pre-defined Builder+Tools to use
# the ToolInitializer class.
def Initializers(env):
ToolInitializer(env, ['install'], ['_InternalInstall', '_InternalInstallAs', '_InternalInstallVersionedLib'])
def Install(self, *args, **kw):
return self._InternalInstall(*args, **kw)
def InstallAs(self, *args, **kw):
return self._InternalInstallAs(*args, **kw)
def InstallVersionedLib(self, *args, **kw):
return self._InternalInstallVersionedLib(*args, **kw)
env.AddMethod(Install)
env.AddMethod(InstallAs)
env.AddMethod(InstallVersionedLib)
def FindTool(tools, env):
for tool in tools:
t = Tool(tool)
if t.exists(env):
return tool
return None
def FindAllTools(tools, env):
def ToolExists(tool, env=env):
return Tool(tool).exists(env)
return list(filter(ToolExists, tools))
def tool_list(platform, env):
other_plat_tools = []
# XXX this logic about what tool to prefer on which platform
# should be moved into either the platform files or
# the tool files themselves.
# The search orders here are described in the man page. If you
# change these search orders, update the man page as well.
if str(platform) == 'win32':
"prefer Microsoft tools on Windows"
linkers = ['mslink', 'gnulink', 'ilink', 'linkloc', 'ilink32']
c_compilers = ['msvc', 'mingw', 'gcc', 'intelc', 'icl', 'icc', 'cc', 'bcc32']
cxx_compilers = ['msvc', 'intelc', 'icc', 'g++', 'cxx', 'bcc32']
assemblers = ['masm', 'nasm', 'gas', '386asm']
fortran_compilers = ['gfortran', 'g77', 'ifl', 'cvf', 'f95', 'f90', 'fortran']
ars = ['mslib', 'ar', 'tlib']
other_plat_tools = ['msvs', 'midl', 'wix']
elif str(platform) == 'os2':
"prefer IBM tools on OS/2"
linkers = ['ilink', 'gnulink', ] # 'mslink']
c_compilers = ['icc', 'gcc', ] # 'msvc', 'cc']
cxx_compilers = ['icc', 'g++', ] # 'msvc', 'cxx']
assemblers = ['nasm', ] # 'masm', 'gas']
fortran_compilers = ['ifl', 'g77']
ars = ['ar', ] # 'mslib']
elif str(platform) == 'irix':
"prefer MIPSPro on IRIX"
linkers = ['sgilink', 'gnulink']
c_compilers = ['sgicc', 'gcc', 'cc']
cxx_compilers = ['sgicxx', 'g++', 'cxx']
assemblers = ['as', 'gas']
fortran_compilers = ['f95', 'f90', 'f77', 'g77', 'fortran']
ars = ['sgiar']
elif str(platform) == 'sunos':
"prefer Forte tools on SunOS"
linkers = ['sunlink', 'gnulink']
c_compilers = ['suncc', 'gcc', 'cc']
cxx_compilers = ['suncxx', 'g++', 'cxx']
assemblers = ['as', 'gas']
fortran_compilers = ['sunf95', 'sunf90', 'sunf77', 'f95', 'f90', 'f77',
'gfortran', 'g77', 'fortran']
ars = ['sunar']
elif str(platform) == 'hpux':
"prefer aCC tools on HP-UX"
linkers = ['hplink', 'gnulink']
c_compilers = ['hpcc', 'gcc', 'cc']
cxx_compilers = ['hpcxx', 'g++', 'cxx']
assemblers = ['as', 'gas']
fortran_compilers = ['f95', 'f90', 'f77', 'g77', 'fortran']
ars = ['ar']
elif str(platform) == 'aix':
"prefer AIX Visual Age tools on AIX"
linkers = ['aixlink', 'gnulink']
c_compilers = ['aixcc', 'gcc', 'cc']
cxx_compilers = ['aixcxx', 'g++', 'cxx']
assemblers = ['as', 'gas']
fortran_compilers = ['f95', 'f90', 'aixf77', 'g77', 'fortran']
ars = ['ar']
elif str(platform) == 'darwin':
"prefer GNU tools on Mac OS X, except for some linkers and IBM tools"
linkers = ['applelink', 'gnulink']
c_compilers = ['gcc', 'cc']
cxx_compilers = ['g++', 'cxx']
assemblers = ['as']
fortran_compilers = ['gfortran', 'f95', 'f90', 'g77']
ars = ['ar']
elif str(platform) == 'cygwin':
"prefer GNU tools on Cygwin, except for a platform-specific linker"
linkers = ['cyglink', 'mslink', 'ilink']
c_compilers = ['gcc', 'msvc', 'intelc', 'icc', 'cc']
cxx_compilers = ['g++', 'msvc', 'intelc', 'icc', 'cxx']
assemblers = ['gas', 'nasm', 'masm']
fortran_compilers = ['gfortran', 'g77', 'ifort', 'ifl', 'f95', 'f90', 'f77']
ars = ['ar', 'mslib']
else:
"prefer GNU tools on all other platforms"
linkers = ['gnulink', 'ilink']
c_compilers = ['gcc', 'intelc', 'icc', 'cc']
cxx_compilers = ['g++', 'intelc', 'icc', 'cxx']
assemblers = ['gas', 'nasm', 'masm']
fortran_compilers = ['gfortran', 'g77', 'ifort', 'ifl', 'f95', 'f90', 'f77']
ars = ['ar', ]
if not str(platform) == 'win32':
other_plat_tools += ['m4', 'rpm']
c_compiler = FindTool(c_compilers, env) or c_compilers[0]
# XXX this logic about what tool provides what should somehow be
# moved into the tool files themselves.
if c_compiler and c_compiler == 'mingw':
# MinGW contains a linker, C compiler, C++ compiler,
# Fortran compiler, archiver and assembler:
cxx_compiler = None
linker = None
assembler = None
fortran_compiler = None
ar = None
else:
# Don't use g++ if the C compiler has built-in C++ support:
if c_compiler in ('msvc', 'intelc', 'icc'):
cxx_compiler = None
else:
cxx_compiler = FindTool(cxx_compilers, env) or cxx_compilers[0]
linker = FindTool(linkers, env) or linkers[0]
assembler = FindTool(assemblers, env) or assemblers[0]
fortran_compiler = FindTool(fortran_compilers, env) or fortran_compilers[0]
ar = FindTool(ars, env) or ars[0]
d_compilers = ['dmd', 'ldc', 'gdc']
d_compiler = FindTool(d_compilers, env) or d_compilers[0]
other_tools = FindAllTools(other_plat_tools + [
# TODO: merge 'install' into 'filesystem' and
# make 'filesystem' the default
'filesystem',
# Parser generators
'lex', 'yacc',
# Foreign function interface
'rpcgen', 'swig',
# Java
'jar', 'javac', 'javah', 'rmic',
# TeX
'dvipdf', 'dvips', 'gs',
'tex', 'latex', 'pdflatex', 'pdftex',
# Archivers
'tar', 'zip',
# File builders (text)
'textfile',
], env)
tools = [
linker,
c_compiler,
cxx_compiler,
fortran_compiler,
assembler,
ar,
d_compiler,
] + other_tools
return [x for x in tools if x]
def find_program_path(env, key_program, default_paths=None):
"""
Find the location of a tool using various means.
Mainly for windows where tools aren't all installed in /usr/bin, etc.
:param env: Current Construction Environment.
:param key_program: Tool to locate.
:param default_paths: List of additional paths this tool might be found in.
"""
# First search in the SCons path
path = env.WhereIs(key_program)
if path:
return path
# Then in the OS path
path = SCons.Util.WhereIs(key_program)
if path:
return path
# Finally, add the defaults and check again. Do not change
# ['ENV']['PATH'] permananetly, the caller can do that if needed.
if default_paths is None:
return path
save_path = env['ENV']['PATH']
for p in default_paths:
env.AppendENVPath('PATH', p)
path = env.WhereIs(key_program)
env['ENV']['PATH'] = save_path
return path
# 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/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

@ -7,8 +7,6 @@ It will usually be imported through the generic SCons.Tool.Tool()
selection method. selection method.
""" """
#
# 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
@ -30,8 +28,6 @@ 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"
import os import os
import os.path import os.path
@ -42,7 +38,6 @@ from . import link
import SCons.Tool.cxx import SCons.Tool.cxx
cplusplus = SCons.Tool.cxx cplusplus = SCons.Tool.cxx
#cplusplus = __import__('cxx', globals(), locals(), [])
def smart_linkflags(source, target, env, for_signature): def smart_linkflags(source, target, env, for_signature):
@ -52,6 +47,7 @@ def smart_linkflags(source, target, env, for_signature):
return '-qtempinc=' + os.path.join(build_dir, 'tempinc') return '-qtempinc=' + os.path.join(build_dir, 'tempinc')
return '' return ''
def generate(env): def generate(env):
""" """
Add Builders and construction variables for Visual Age linker to Add Builders and construction variables for Visual Age linker to
@ -64,12 +60,13 @@ def generate(env):
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -qmkshrobj -qsuppress=1501-218') env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -qmkshrobj -qsuppress=1501-218')
env['SHLIBSUFFIX'] = '.a' env['SHLIBSUFFIX'] = '.a'
def exists(env): def exists(env):
# TODO: sync with link.smart_link() to choose a linker # TODO: sync with link.smart_link() to choose a linker
linkers = { 'CXX': ['aixc++'], 'CC': ['aixcc'] } linkers = { 'CXX': ['aixc++'], 'CC': ['aixcc'] }
alltools = [] alltools = []
for langvar, linktools in linkers.items(): for langvar, linktools in linkers.items():
if langvar in env: # use CC over CXX when user specified CC but not CXX if langvar in env: # use CC over CXX when user specified CC but not CXX
return SCons.Tool.FindTool(linktools, env) return SCons.Tool.FindTool(linktools, env)
alltools.extend(linktools) alltools.extend(linktools)
return SCons.Tool.FindTool(alltools, env) return SCons.Tool.FindTool(alltools, env)

View file

@ -0,0 +1,189 @@
"""SCons.Tool.applelink
Tool-specific initialization for Apple's gnu-like linker.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
#
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Even though the Mac is based on the GNU toolchain, it doesn't understand
# the -rpath option, so we use the "link" tool instead of "gnulink".
from SCons.Util import CLVar
from . import link
# User programmatically describes how SHLIBVERSION maps to values for compat/current.
_APPLELIB_MAX_VERSION_VALUES = (65535, 255, 255)
class AppleLinkInvalidCurrentVersionException(Exception):
pass
class AppleLinkInvalidCompatibilityVersionException(Exception):
pass
def _applelib_check_valid_version(version_string):
"""
Check that the version # is valid.
X[.Y[.Z]]
where X 0-65535
where Y either not specified or 0-255
where Z either not specified or 0-255
:param version_string:
:return:
"""
parts = version_string.split('.')
if len(parts) > 3:
return False, "Version string has too many periods [%s]" % version_string
if len(parts) <= 0:
return False, "Version string unspecified [%s]" % version_string
for (i, p) in enumerate(parts):
try:
p_i = int(p)
except ValueError:
return False, "Version component %s (from %s) is not a number" % (p, version_string)
if p_i < 0 or p_i > _APPLELIB_MAX_VERSION_VALUES[i]:
return False, "Version component %s (from %s) is not valid value should be between 0 and %d" % (
p, version_string, _APPLELIB_MAX_VERSION_VALUES[i])
return True, ""
def _applelib_currentVersionFromSoVersion(source, target, env, for_signature):
"""
A generator function to create the -Wl,-current_version flag if needed.
If env['APPLELINK_NO_CURRENT_VERSION'] contains a true value no flag will be generated
Otherwise if APPLELINK_CURRENT_VERSION is not specified, env['SHLIBVERSION']
will be used.
:param source:
:param target:
:param env:
:param for_signature:
:return: A string providing the flag to specify the current_version of the shared library
"""
if env.get('APPLELINK_NO_CURRENT_VERSION', False):
return ""
elif env.get('APPLELINK_CURRENT_VERSION', False):
version_string = env['APPLELINK_CURRENT_VERSION']
elif env.get('SHLIBVERSION', False):
version_string = env['SHLIBVERSION']
else:
return ""
version_string = ".".join(version_string.split('.')[:3])
valid, reason = _applelib_check_valid_version(version_string)
if not valid:
raise AppleLinkInvalidCurrentVersionException(reason)
return "-Wl,-current_version,%s" % version_string
def _applelib_compatVersionFromSoVersion(source, target, env, for_signature):
"""
A generator function to create the -Wl,-compatibility_version flag if needed.
If env['APPLELINK_NO_COMPATIBILITY_VERSION'] contains a true value no flag will be generated
Otherwise if APPLELINK_COMPATIBILITY_VERSION is not specified
the first two parts of env['SHLIBVERSION'] will be used with a .0 appended.
:param source:
:param target:
:param env:
:param for_signature:
:return: A string providing the flag to specify the compatibility_version of the shared library
"""
if env.get('APPLELINK_NO_COMPATIBILITY_VERSION', False):
return ""
elif env.get('APPLELINK_COMPATIBILITY_VERSION', False):
version_string = env['APPLELINK_COMPATIBILITY_VERSION']
elif env.get('SHLIBVERSION', False):
version_string = ".".join(env['SHLIBVERSION'].split('.')[:2] + ['0'])
else:
return ""
if version_string is None:
return ""
valid, reason = _applelib_check_valid_version(version_string)
if not valid:
raise AppleLinkInvalidCompatibilityVersionException(reason)
return "-Wl,-compatibility_version,%s" % version_string
def generate(env):
"""Add Builders and construction variables for applelink to an
Environment."""
link.generate(env)
env['FRAMEWORKPATHPREFIX'] = '-F'
env['_FRAMEWORKPATH'] = '${_concat(FRAMEWORKPATHPREFIX, FRAMEWORKPATH, "", __env__, RDirs)}'
env['_FRAMEWORKS'] = '${_concat("-framework ", FRAMEWORKS, "", __env__)}'
env['LINKCOM'] = env['LINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
env['SHLINKFLAGS'] = CLVar('$LINKFLAGS -dynamiclib')
env['SHLINKCOM'] = env['SHLINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
env['_APPLELINK_CURRENT_VERSION'] = _applelib_currentVersionFromSoVersion
env['_APPLELINK_COMPATIBILITY_VERSION'] = _applelib_compatVersionFromSoVersion
env['_SHLIBVERSIONFLAGS'] = '$_APPLELINK_CURRENT_VERSION $_APPLELINK_COMPATIBILITY_VERSION '
env['_LDMODULEVERSIONFLAGS'] = '$_APPLELINK_CURRENT_VERSION $_APPLELINK_COMPATIBILITY_VERSION '
# override the default for loadable modules, which are different
# on OS X than dynamic shared libs. echoing what XCode does for
# pre/suffixes:
env['LDMODULEPREFIX'] = ''
env['LDMODULESUFFIX'] = ''
env['LDMODULEFLAGS'] = CLVar('$LINKFLAGS -bundle')
env['LDMODULECOM'] = '$LDMODULE -o ${TARGET} $LDMODULEFLAGS' \
' $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
# New stuff
#
env['_SHLIBSUFFIX'] = '${_SHLIBVERSION}${SHLIBSUFFIX}'
env['__SHLIBVERSIONFLAGS'] = '${__lib_either_version_flag(__env__,' \
'"SHLIBVERSION","_APPLELINK_CURRENT_VERSION", "_SHLIBVERSIONFLAGS")}'
env['__LDMODULEVERSIONFLAGS'] = '${__lib_either_version_flag(__env__,' \
'"LDMODULEVERSION","_APPLELINK_CURRENT_VERSION", "_LDMODULEVERSIONFLAGS")}'
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/ListOption.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 ListOption(*args, **kw):
global warned
if not warned:
msg = "The ListOption() function is deprecated; use the ListVariable() function instead."
SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg)
warned = True
return SCons.Variables.ListVariable(*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

@ -1,17 +1,8 @@
# -*- coding: utf-8; -*- # -*- coding: utf-8; -*-
"""SCons.Tool.clang
Tool-specific initialization for clang.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
# #
# Copyright (c) 2001 - 2017 The SCons Foundation # MIT License
#
# Copyright 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
@ -32,8 +23,16 @@ selection method.
# 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.
# #
"""SCons.Tool.clang
Tool-specific initialization for clang.
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
# __revision__ = "src/engine/SCons/Tool/clang.py 74b2c53bc42290e911b334a6b44f187da698a668 2017/11/14 13:16:53 bdbaddog"
# 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 +44,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 +54,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,9 +81,9 @@ 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:
if sys.version_info[0] > 2: line = pipe.stdout.readline()
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)
if match: if match:
env['CCVERSION'] = match.group(1) env['CCVERSION'] = match.group(1)

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,17 +69,30 @@ 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:
if sys.version_info[0] > 2: line = pipe.stdout.readline()
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)
if match: if match:
env['CXXVERSION'] = match.group(1) env['CXXVERSION'] = match.group(1)

View file

@ -0,0 +1,255 @@
"""
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 fnmatch
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']
use_path_filter = env.subst('$COMPILATIONDB_PATH_FILTER')
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
if use_path_filter and not fnmatch.fnmatch(output_file, use_path_filter):
continue
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
env['COMPILATIONDB_PATH_FILTER'] = ''
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

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