summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Announce.txt50
-rw-r--r--src/CHANGES.txt58
-rw-r--r--src/README.txt43
-rw-r--r--src/RELEASE.txt2
-rw-r--r--src/engine/SCons/Action.py2
-rw-r--r--src/engine/SCons/Builder.py11
-rw-r--r--src/engine/SCons/BuilderTests.py69
-rw-r--r--src/engine/SCons/CacheDir.py16
-rw-r--r--src/engine/SCons/Debug.py13
-rw-r--r--src/engine/SCons/Defaults.py15
-rw-r--r--src/engine/SCons/Environment.py16
-rw-r--r--src/engine/SCons/EnvironmentTests.py92
-rw-r--r--src/engine/SCons/Executor.py133
-rw-r--r--src/engine/SCons/Memoize.py159
-rw-r--r--src/engine/SCons/MemoizeTests.py18
-rw-r--r--src/engine/SCons/Node/Alias.py49
-rw-r--r--src/engine/SCons/Node/AliasTests.py4
-rw-r--r--src/engine/SCons/Node/FS.py597
-rw-r--r--src/engine/SCons/Node/FSTests.py272
-rw-r--r--src/engine/SCons/Node/NodeTests.py129
-rw-r--r--src/engine/SCons/Node/Python.py40
-rw-r--r--src/engine/SCons/Node/PythonTests.py4
-rw-r--r--src/engine/SCons/Node/__init__.py472
-rw-r--r--src/engine/SCons/PathList.py8
-rw-r--r--src/engine/SCons/Platform/cygwin.py4
-rw-r--r--src/engine/SCons/SConf.py24
-rw-r--r--src/engine/SCons/SConfTests.py2
-rw-r--r--src/engine/SCons/SConsign.py38
-rw-r--r--src/engine/SCons/SConsignTests.py4
-rw-r--r--src/engine/SCons/Scanner/CTests.py13
-rw-r--r--src/engine/SCons/Scanner/Dir.py2
-rw-r--r--src/engine/SCons/Scanner/FortranTests.py13
-rw-r--r--src/engine/SCons/Scanner/IDLTests.py13
-rw-r--r--src/engine/SCons/Script/Main.py14
-rw-r--r--src/engine/SCons/Script/SConsOptions.py2
-rw-r--r--src/engine/SCons/Script/SConscript.py8
-rw-r--r--src/engine/SCons/Script/SConscript.xml16
-rw-r--r--src/engine/SCons/Script/__init__.py26
-rw-r--r--src/engine/SCons/Taskmaster.py2
-rw-r--r--src/engine/SCons/Tool/MSCommon/arch.py6
-rw-r--r--src/engine/SCons/Tool/MSCommon/vc.py6
-rw-r--r--src/engine/SCons/Tool/MSCommon/vs.py22
-rw-r--r--src/engine/SCons/Tool/__init__.py562
-rw-r--r--src/engine/SCons/Tool/__init__.xml51
-rw-r--r--src/engine/SCons/Tool/cyglink.py158
-rw-r--r--src/engine/SCons/Tool/cyglink.xml49
-rw-r--r--src/engine/SCons/Tool/dmd.py13
-rw-r--r--src/engine/SCons/Tool/docbook/__init__.py6
-rw-r--r--src/engine/SCons/Tool/gdc.py13
-rw-r--r--src/engine/SCons/Tool/gettext.xml2
-rw-r--r--src/engine/SCons/Tool/gnulink.py13
-rw-r--r--src/engine/SCons/Tool/gnulink.xml4
-rw-r--r--src/engine/SCons/Tool/install.py131
-rw-r--r--src/engine/SCons/Tool/install.xml11
-rw-r--r--src/engine/SCons/Tool/ldc.py13
-rw-r--r--src/engine/SCons/Tool/link.py287
-rw-r--r--src/engine/SCons/Tool/link.xml154
-rw-r--r--src/engine/SCons/Tool/linkloc.py1
-rw-r--r--src/engine/SCons/Tool/midl.xml2
-rw-r--r--src/engine/SCons/Tool/mingw.py1
-rw-r--r--src/engine/SCons/Tool/msgmerge.xml2
-rw-r--r--src/engine/SCons/Tool/mslink.py5
-rw-r--r--src/engine/SCons/Tool/msvc.xml2
-rw-r--r--src/engine/SCons/Tool/msvs.py6
-rw-r--r--src/engine/SCons/Tool/mwld.py1
-rw-r--r--src/engine/SCons/Tool/packaging/__init__.py16
-rw-r--r--src/engine/SCons/Tool/packaging/ipk.py2
-rw-r--r--src/engine/SCons/Tool/packaging/msi.py2
-rw-r--r--src/engine/SCons/Tool/packaging/rpm.py11
-rw-r--r--src/engine/SCons/Tool/qt.py1
-rw-r--r--src/engine/SCons/Tool/rpm.py6
-rw-r--r--src/engine/SCons/Tool/sunar.py3
-rw-r--r--src/engine/SCons/Tool/sunar.xml4
-rw-r--r--src/engine/SCons/Tool/sunlink.py4
-rw-r--r--src/engine/SCons/Tool/swig.py9
-rw-r--r--src/engine/SCons/Tool/swig.xml6
-rw-r--r--src/engine/SCons/Tool/xgettext.xml2
-rw-r--r--src/engine/SCons/Util.py6
-rw-r--r--src/engine/SCons/UtilTests.py3
-rw-r--r--src/script/scons.py2
-rw-r--r--src/script/sconsign.py2
81 files changed, 2834 insertions, 1219 deletions
diff --git a/src/Announce.txt b/src/Announce.txt
index 7c6fdd5..7a13b81 100644
--- a/src/Announce.txt
+++ b/src/Announce.txt
@@ -18,6 +18,23 @@ So that everyone using SCons can help each other learn how to use it more
effectively, please go to http://scons.org/lists.php#users to sign up for
the scons-users mailing list.
+==============IMPORTANT NOTICE===========
+
+As has been pre-announced in SCons's mailing lists:
+
+* https://pairlist4.pair.net/pipermail/scons-users/2014-July/002734.html ,
+* https://pairlist2.pair.net/pipermail/scons-dev/2014-December/002107.html
+* https://pairlist4.pair.net/pipermail/scons-users/2015-February/003454.html
+
+We're planning to switch the Node class to using "slots" in the core sources,
+mainly to reduce memory consumption by up to 35% in large build projects.
+
+This feature has been tested extensively and we don't expect any problems for you.
+However as with all major changes it would be wise to test V2.4.0 when it is
+released. Especially if you are directly using the Node class.
+
+=================================================================
+
RELEASE VERSION/DATE TO BE FILLED IN LATER
@@ -26,6 +43,39 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
since last release. This announcement highlights only the important
changes.
+ Please note the following important changes since release 2.3.6:
+ - Switch several core classes to use "slots" to reduce memory
+ usage. (PR #2180, #2178, #2198)
+
+ Please note the following important changes since release 2.3.5:
+ - Support for Visual Studio 2015
+
+ Please note the following important changes since release 2.3.4:
+ - Documentation fixes for libraries.xml and
+ builders-writing.xml (#2989 and #2990)
+ - Extended docs for InstallVersionedLib/SharedLibrary,
+ and added SKIP_WIN_PACKAGES argument to build script
+ bootstrap.py (PR #230, #3002).
+ - Fixed symlink support (PR #227, #2395).
+ - Updated debug-count test case (PR #229).
+ - Fixed incomplete LIBS flattening and substitution in
+ Program scanner(PR #205, #2954).
+ - Added new method rentry_exists_on_disk to Node.FS (PR #193).
+ - Fixed several D tests under the different OS.
+ - Add support for f08 file extensions for Fortran 2008 code.
+ - Show --config choices if no argument is specified (PR #202).
+ - Fixed build crash when XML toolchain isn't installed, and
+ activated compression for ZIP archives.
+ - Fix for VersionedSharedLibrary under 'sunos' platform.
+ - Fixed dll link with precompiled headers on MSVC 2012
+ - Added an 'exclude' parameter to Glob()
+ - Support for multiple cmdargs (one per variant) in VS project files.
+ - Various improvements for TempFileMunge class.
+ - Added an implementation for Visual Studio users files (PR #209).
+ - Added support for the 'PlatformToolset' tag in VS project files (#2978).
+ - Added support for '-isystem' to ParseFlags.
+
+
Please note the following important changes since release 2.3.3:
-- Fix for EnsureSConsVersion regression in 2.3.3.
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 2a2c21f..11fdeba 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -6,6 +6,62 @@
RELEASE VERSION/DATE TO BE FILLED IN LATER
+ From Florian Miedniak:
+ - Fixed tigris issue #3011: Glob() excludes didn't work when used with VariantDir(duplicate=0)
+
+ From William Blevins:
+ - InstallVersionedLib now available in the DefaultEnvironment context.
+ - Improves orthogonality of use cases between different Install functions.
+
+ From William Roberts:
+ - Fix bug 2831 and allow Help() text to be appended to AddOption() help.
+
+ From Paweł Tomulik:
+ - Reimplemented versioning for shared libraries, with the following effects
+ - Fixed tigris issues #3001, #3006.
+ - Fixed several other issues not reported to tigris, including:
+ issues with versioned libraries in subdirectories with tricky names,
+ issues with versioned libraries and variant directories,
+ issue with soname not being injected to library when using D linkers,
+ - Switched to direct symlinks instead of daisy-chained ones -- soname and
+ development symlinks point directly to the versioned shared library now),
+ for rationale see:
+ https://www.debian.org/doc/debian-policy/ch-sharedlibs.html
+ https://fedoraproject.org/wiki/Packaging:Guidelines#Devel_Packages
+ https://bitbucket.org/scons/scons/pull-requests/247/new-versioned-libraries-gnulink-cyglink/diff#comment-10063929
+ - New construction variables to allow override default behavior: SONAME,
+ SHLIBVERSIONFLAGS, _SHLIBVERSIONFLAGS, SHLIBNOVERSIONSYMLINKS,
+ LDMODULEVERSION, LDMODULEVERSIONFLAGS, _LDMODULEVERSIONFLAGS,
+ LDMODULENOVERSIONSYMLINKS.
+ - Changed logic used to configure the versioning machinery from
+ platform-centric to linker-oriented.
+ - The SHLIBVERSION/LDMODULEVERSION variables are no longer validated by
+ SCons (more freedom to users).
+ - InstallVersionedLib() doesn't use SHLIBVERSION anymore.
+ - Enchanced docs for the library versioning stuff.
+ - New tests for versioned libraries.
+ - Library versioning is currently implemented for the following linker
+ tools: 'cyglink', 'gnulink', 'sunlink'.
+
+RELEASE 2.4.0 - Mon, 21 Sep 2015 08:56:00 -0700
+
+ From Dirk Baechle:
+ - Switched several core classes to use "slots", to
+ reduce the overall memory consumption in large
+ projects (fixes #2180, #2178, #2198)
+ - Memoizer counting uses decorators now, instead of
+ the old metaclasses approach.
+
+ From Andrew Featherstone
+ - Fixed typo in SWIGPATH description
+
+RELEASE 2.3.6 - Mon, 31 Jul 2015 14:35:03 -0700
+
+ From Rob Smith:
+ - Added support for Visual Studio 2015
+
+RELEASE 2.3.5 - Mon, 17 Jun 2015 21:07:32 -0700
+
From Stephen Pollard:
- Documentation fixes for libraries.xml and
builders-writing.xml (#2989 and #2990)
@@ -36,8 +92,6 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- Show --config choices if no argument is specified (PR #202).
- Fixed build crash when XML toolchain isn't installed, and
activated compression for ZIP archives.
- - Fixed --tree=all print when build tree contains non-ascii
- Node representation (PR #235).
From Alexandre Feblot:
- Fix for VersionedSharedLibrary under 'sunos' platform.
diff --git a/src/README.txt b/src/README.txt
index 5d880c2..d80460a 100644
--- a/src/README.txt
+++ b/src/README.txt
@@ -28,7 +28,8 @@ the latest version by checking the SCons download page at:
EXECUTION REQUIREMENTS
======================
-Running SCons requires Python version 2.4 or later. There should be
+Running SCons requires Python version 2.7.*. Currently it does not
+run on the Python 3.x release. There should be
no other dependencies or requirements to run SCons. (There is, however,
an additional requirement to *install* SCons from this particular
package; see the next section.)
@@ -224,20 +225,26 @@ Check the SCons web site at:
AUTHOR INFO
===========
-
-Steven Knight
-knight at baldmt dot com
-http://www.baldmt.com/~knight/
-
-With plenty of help from the SCons Development team:
- Chad Austin
- Charles Crain
- Steve Leblanc
- Greg Noel
- Gary Oberbrunner
- Anthony Roach
- Greg Spencer
- Christoph Wiedemann
-
-__COPYRIGHT__
-__FILE__ __REVISION__ __DATE__ __DEVELOPER__
+SCons was originally written by Steven Knight, knight at baldmt dot com.
+Since around 2010 it has been maintained by the SCons
+development team, co-managed by Bill Deegan and Gary Oberbrunner, with
+many contributors, including but not at all limited to:
+
+- Chad Austin
+- Dirk Baechle
+- Charles Crain
+- William Deegan
+- Steve Leblanc
+- Rob Managan
+- Greg Noel
+- Gary Oberbrunner
+- Anthony Roach
+- Greg Spencer
+- Tom Tanner
+- Anatoly Techtonik
+- Christoph Wiedemann
+- Russel Winder
+
+\... and many others.
+
+Copyright (c) 2001 - 2015 The SCons Foundation
diff --git a/src/RELEASE.txt b/src/RELEASE.txt
index 1fa033b..1bf2eb1 100644
--- a/src/RELEASE.txt
+++ b/src/RELEASE.txt
@@ -1,4 +1,4 @@
- A new SCons checkpoint release, 2.3.5.alpha.yyyymmdd, is now available
+ A new SCons checkpoint release, 2.3.13.alpha.yyyymmdd, is now available
on the SCons download page:
http://www.scons.org/download.php
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index eecea11..5a34825 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -540,7 +540,7 @@ class _ActionAction(ActionBase):
if chdir:
save_cwd = os.getcwd()
try:
- chdir = str(chdir.abspath)
+ chdir = str(chdir.get_abspath())
except AttributeError:
if not is_String(chdir):
if executor:
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
index c82f6ac..769b15d 100644
--- a/src/engine/SCons/Builder.py
+++ b/src/engine/SCons/Builder.py
@@ -352,11 +352,6 @@ class BuilderBase(object):
nodes (files) from input nodes (files).
"""
- if SCons.Memoize.use_memoizer:
- __metaclass__ = SCons.Memoize.Memoized_Metaclass
-
- memoizer_counters = []
-
def __init__(self, action = None,
prefix = '',
suffix = '',
@@ -758,8 +753,7 @@ class BuilderBase(object):
def _get_src_builders_key(self, env):
return id(env)
- memoizer_counters.append(SCons.Memoize.CountDict('get_src_builders', _get_src_builders_key))
-
+ @SCons.Memoize.CountDictCall(_get_src_builders_key)
def get_src_builders(self, env):
"""
Returns the list of source Builders for this Builder.
@@ -795,8 +789,7 @@ class BuilderBase(object):
def _subst_src_suffixes_key(self, env):
return id(env)
- memoizer_counters.append(SCons.Memoize.CountDict('subst_src_suffixes', _subst_src_suffixes_key))
-
+ @SCons.Memoize.CountDictCall(_subst_src_suffixes_key)
def subst_src_suffixes(self, env):
"""
The suffix list may contain construction variable expansions,
diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py
index 70a7a3f..3eca588 100644
--- a/src/engine/SCons/BuilderTests.py
+++ b/src/engine/SCons/BuilderTests.py
@@ -163,7 +163,8 @@ class MyNode_without_target_from_source(object):
self.builder = None
self.is_explicit = None
self.side_effect = 0
- self.suffix = os.path.splitext(name)[1]
+ def get_suffix(self):
+ return os.path.splitext(self.name)[1]
def disambiguate(self):
return self
def __str__(self):
@@ -349,7 +350,7 @@ class BuilderTestCase(unittest.TestCase):
builder = SCons.Builder.Builder(action="foo")
target = builder(env, None, source='n22', srcdir='src_dir')[0]
- p = target.sources[0].path
+ p = target.sources[0].get_internal_path()
assert p == os.path.join('src_dir', 'n22'), p
def test_mistaken_variables(self):
@@ -487,20 +488,20 @@ class BuilderTestCase(unittest.TestCase):
builder = SCons.Builder.Builder(prefix = 'lib', action='')
assert builder.get_prefix(env) == 'lib'
tgt = builder(env, target = 'tgt1', source = 'src1')[0]
- assert tgt.path == 'libtgt1', \
- "Target has unexpected name: %s" % tgt.path
+ assert tgt.get_internal_path() == 'libtgt1', \
+ "Target has unexpected name: %s" % tgt.get_internal_path()
tgt = builder(env, target = 'tgt2a tgt2b', source = 'src2')[0]
- assert tgt.path == 'libtgt2a tgt2b', \
- "Target has unexpected name: %s" % tgt.path
+ assert tgt.get_internal_path() == 'libtgt2a tgt2b', \
+ "Target has unexpected name: %s" % tgt.get_internal_path()
tgt = builder(env, target = None, source = 'src3')[0]
- assert tgt.path == 'libsrc3', \
- "Target has unexpected name: %s" % tgt.path
+ assert tgt.get_internal_path() == 'libsrc3', \
+ "Target has unexpected name: %s" % tgt.get_internal_path()
tgt = builder(env, target = None, source = 'lib/src4')[0]
- assert tgt.path == os.path.join('lib', 'libsrc4'), \
- "Target has unexpected name: %s" % tgt.path
+ assert tgt.get_internal_path() == os.path.join('lib', 'libsrc4'), \
+ "Target has unexpected name: %s" % tgt.get_internal_path()
tgt = builder(env, target = 'lib/tgt5', source = 'lib/src5')[0]
- assert tgt.path == os.path.join('lib', 'libtgt5'), \
- "Target has unexpected name: %s" % tgt.path
+ assert tgt.get_internal_path() == os.path.join('lib', 'libtgt5'), \
+ "Target has unexpected name: %s" % tgt.get_internal_path()
def gen_prefix(env, sources):
return "gen_prefix() says " + env['FOO']
@@ -520,17 +521,17 @@ class BuilderTestCase(unittest.TestCase):
'.zzz' : my_emit},
action = '')
tgt = builder(my_env, target = None, source = 'f1')[0]
- assert tgt.path == 'default-f1', tgt.path
+ assert tgt.get_internal_path() == 'default-f1', tgt.get_internal_path()
tgt = builder(my_env, target = None, source = 'f2.c')[0]
- assert tgt.path == 'default-f2', tgt.path
+ assert tgt.get_internal_path() == 'default-f2', tgt.get_internal_path()
tgt = builder(my_env, target = None, source = 'f3.in')[0]
- assert tgt.path == 'out-f3', tgt.path
+ assert tgt.get_internal_path() == 'out-f3', tgt.get_internal_path()
tgt = builder(my_env, target = None, source = 'f4.x')[0]
- assert tgt.path == 'y-f4', tgt.path
+ assert tgt.get_internal_path() == 'y-f4', tgt.get_internal_path()
tgt = builder(my_env, target = None, source = 'f5.foo')[0]
- assert tgt.path == 'foo-f5', tgt.path
+ assert tgt.get_internal_path() == 'foo-f5', tgt.get_internal_path()
tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
- assert tgt.path == 'emit-f6', tgt.path
+ assert tgt.get_internal_path() == 'emit-f6', tgt.get_internal_path()
def test_set_suffix(self):
"""Test the set_suffix() method"""
@@ -560,13 +561,13 @@ class BuilderTestCase(unittest.TestCase):
assert b1.src_suffixes(env) == ['.c'], b1.src_suffixes(env)
tgt = b1(env, target = 'tgt2', source = 'src2')[0]
- assert tgt.sources[0].path == 'src2.c', \
- "Source has unexpected name: %s" % tgt.sources[0].path
+ assert tgt.sources[0].get_internal_path() == 'src2.c', \
+ "Source has unexpected name: %s" % tgt.sources[0].get_internal_path()
tgt = b1(env, target = 'tgt3', source = 'src3a src3b')[0]
assert len(tgt.sources) == 1
- assert tgt.sources[0].path == 'src3a src3b.c', \
- "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].path
+ assert tgt.sources[0].get_internal_path() == 'src3a src3b.c', \
+ "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].get_internal_path()
b2 = SCons.Builder.Builder(src_suffix = '.2', src_builder = b1)
r = sorted(b2.src_suffixes(env))
@@ -636,14 +637,14 @@ class BuilderTestCase(unittest.TestCase):
builder = SCons.Builder.Builder(suffix = 'o', action='')
assert builder.get_suffix(env) == '.o', builder.get_suffix(env)
tgt = builder(env, target = 'tgt3', source = 'src3')[0]
- assert tgt.path == 'tgt3.o', \
- "Target has unexpected name: %s" % tgt.path
+ assert tgt.get_internal_path() == 'tgt3.o', \
+ "Target has unexpected name: %s" % tgt.get_internal_path()
tgt = builder(env, target = 'tgt4a tgt4b', source = 'src4')[0]
- assert tgt.path == 'tgt4a tgt4b.o', \
- "Target has unexpected name: %s" % tgt.path
+ assert tgt.get_internal_path() == 'tgt4a tgt4b.o', \
+ "Target has unexpected name: %s" % tgt.get_internal_path()
tgt = builder(env, target = None, source = 'src5')[0]
- assert tgt.path == 'src5.o', \
- "Target has unexpected name: %s" % tgt.path
+ assert tgt.get_internal_path() == 'src5.o', \
+ "Target has unexpected name: %s" % tgt.get_internal_path()
def gen_suffix(env, sources):
return "gen_suffix() says " + env['BAR']
@@ -663,17 +664,17 @@ class BuilderTestCase(unittest.TestCase):
'.zzz' : my_emit},
action='')
tgt = builder(my_env, target = None, source = 'f1')[0]
- assert tgt.path == 'f1.default', tgt.path
+ assert tgt.get_internal_path() == 'f1.default', tgt.get_internal_path()
tgt = builder(my_env, target = None, source = 'f2.c')[0]
- assert tgt.path == 'f2.default', tgt.path
+ assert tgt.get_internal_path() == 'f2.default', tgt.get_internal_path()
tgt = builder(my_env, target = None, source = 'f3.in')[0]
- assert tgt.path == 'f3.out', tgt.path
+ assert tgt.get_internal_path() == 'f3.out', tgt.get_internal_path()
tgt = builder(my_env, target = None, source = 'f4.x')[0]
- assert tgt.path == 'f4.y', tgt.path
+ assert tgt.get_internal_path() == 'f4.y', tgt.get_internal_path()
tgt = builder(my_env, target = None, source = 'f5.bar')[0]
- assert tgt.path == 'f5.new', tgt.path
+ assert tgt.get_internal_path() == 'f5.new', tgt.get_internal_path()
tgt = builder(my_env, target = None, source = 'f6.zzz')[0]
- assert tgt.path == 'f6.emit', tgt.path
+ assert tgt.get_internal_path() == 'f6.emit', tgt.get_internal_path()
def test_single_source(self):
"""Test Builder with single_source flag set"""
diff --git a/src/engine/SCons/CacheDir.py b/src/engine/SCons/CacheDir.py
index 9dd18e5..f32b326 100644
--- a/src/engine/SCons/CacheDir.py
+++ b/src/engine/SCons/CacheDir.py
@@ -50,11 +50,11 @@ def CacheRetrieveFunc(target, source, env):
cd.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile)
if SCons.Action.execute_actions:
if fs.islink(cachefile):
- fs.symlink(fs.readlink(cachefile), t.path)
+ fs.symlink(fs.readlink(cachefile), t.get_internal_path())
else:
- env.copy_from_cache(cachefile, t.path)
+ env.copy_from_cache(cachefile, t.get_internal_path())
st = fs.stat(cachefile)
- fs.chmod(t.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
def CacheRetrieveString(target, source, env):
@@ -63,7 +63,7 @@ def CacheRetrieveString(target, source, env):
cd = env.get_CacheDir()
cachedir, cachefile = cd.cachepath(t)
if t.fs.exists(cachefile):
- return "Retrieved `%s' from cache" % t.path
+ return "Retrieved `%s' from cache" % t.get_internal_path()
return None
CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
@@ -106,12 +106,12 @@ def CachePushFunc(target, source, env):
raise SCons.Errors.EnvironmentError(msg)
try:
- if fs.islink(t.path):
- fs.symlink(fs.readlink(t.path), tempfile)
+ if fs.islink(t.get_internal_path()):
+ fs.symlink(fs.readlink(t.get_internal_path()), tempfile)
else:
- fs.copy2(t.path, tempfile)
+ fs.copy2(t.get_internal_path(), tempfile)
fs.rename(tempfile, cachefile)
- st = fs.stat(t.path)
+ st = fs.stat(t.get_internal_path())
fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
except EnvironmentError:
# It's possible someone else tried writing the file at the
diff --git a/src/engine/SCons/Debug.py b/src/engine/SCons/Debug.py
index 9974039..b47c24c 100644
--- a/src/engine/SCons/Debug.py
+++ b/src/engine/SCons/Debug.py
@@ -34,6 +34,7 @@ import os
import sys
import time
import weakref
+import inspect
# Global variable that gets set to 'True' by the Main script,
# when the creation of class instances should get tracked.
@@ -46,7 +47,12 @@ def logInstanceCreation(instance, name=None):
name = instance.__class__.__name__
if name not in tracked_classes:
tracked_classes[name] = []
- tracked_classes[name].append(weakref.ref(instance))
+ if hasattr(instance, '__dict__'):
+ tracked_classes[name].append(weakref.ref(instance))
+ else:
+ # weakref doesn't seem to work when the instance
+ # contains only slots...
+ tracked_classes[name].append(instance)
def string_to_classes(s):
if s == '*':
@@ -66,7 +72,10 @@ def listLoggedInstances(classes, file=sys.stdout):
for classname in string_to_classes(classes):
file.write('\n%s:\n' % classname)
for ref in tracked_classes[classname]:
- obj = ref()
+ if inspect.isclass(ref):
+ obj = ref()
+ else:
+ obj = ref
if obj is not None:
file.write(' %s\n' % repr(obj))
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index 6500443..744da5f 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -482,6 +482,15 @@ class Variable_Method_Caller(object):
frame = frame.f_back
return None
+# if env[version_var] id defined, returns env[flags_var], otherwise returns None
+def __libversionflags(env, version_var, flags_var):
+ try:
+ if env[version_var]:
+ return env[flags_var]
+ except KeyError:
+ pass
+ return None
+
ConstructionEnvironment = {
'BUILDERS' : {},
'SCANNERS' : [],
@@ -499,6 +508,12 @@ ConstructionEnvironment = {
'_LIBDIRFLAGS' : '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
'_CPPINCFLAGS' : '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)',
'_CPPDEFFLAGS' : '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}',
+
+ '__libversionflags' : __libversionflags,
+ '__SHLIBVERSIONFLAGS' : '${__libversionflags(__env__,"SHLIBVERSION","_SHLIBVERSIONFLAGS")}',
+ '__LDMODULEVERSIONFLAGS' : '${__libversionflags(__env__,"LDMODULEVERSION","_LDMODULEVERSIONFLAGS")}',
+ '__DSHLIBVERSIONFLAGS' : '${__libversionflags(__env__,"DSHLIBVERSION","_DSHLIBVERSIONFLAGS")}',
+
'TEMPFILE' : NullCmdGenerator,
'Dir' : Variable_Method_Caller('TARGET', 'Dir'),
'Dirs' : Variable_Method_Caller('TARGET', 'Dirs'),
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index 6886e85..7e2f896 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -365,9 +365,6 @@ class SubstitutionEnvironment(object):
class actually becomes useful.)
"""
- if SCons.Memoize.use_memoizer:
- __metaclass__ = SCons.Memoize.Memoized_Metaclass
-
def __init__(self, **kw):
"""Initialization of an underlying SubstitutionEnvironment class.
"""
@@ -615,7 +612,7 @@ class SubstitutionEnvironment(object):
def Override(self, overrides):
"""
- Produce a modified environment whose variables are overriden by
+ Produce a modified environment whose variables are overridden by
the overrides dictionaries. "overrides" is a dictionary that
will override the variables of this environment.
@@ -902,8 +899,6 @@ class Base(SubstitutionEnvironment):
Environment.
"""
- memoizer_counters = []
-
#######################################################################
# This is THE class for interacting with the SCons build engine,
# and it contains a lot of stuff, so we're going to try to keep this
@@ -1071,8 +1066,7 @@ class Base(SubstitutionEnvironment):
factory = getattr(self.fs, name)
return factory
- memoizer_counters.append(SCons.Memoize.CountValue('_gsm'))
-
+ @SCons.Memoize.CountMethodCall
def _gsm(self):
try:
return self._memo['_gsm']
@@ -1528,8 +1522,8 @@ class Base(SubstitutionEnvironment):
def Dump(self, key = None):
"""
- Using the standard Python pretty printer, dump the contents of the
- scons build environment to stdout.
+ Using the standard Python pretty printer, return the contents of the
+ scons build environment as a string.
If the key passed in is anything other than None, then that will
be used as an index into the build environment dictionary and
@@ -1802,7 +1796,7 @@ class Base(SubstitutionEnvironment):
self.Replace(**kw)
def _find_toolpath_dir(self, tp):
- return self.fs.Dir(self.subst(tp)).srcnode().abspath
+ return self.fs.Dir(self.subst(tp)).srcnode().get_abspath()
def Tool(self, tool, toolpath=None, **kw):
if SCons.Util.is_String(tool):
diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
index a0869e8..9a9d2b0 100644
--- a/src/engine/SCons/EnvironmentTests.py
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -2703,25 +2703,25 @@ def generate(env):
t = env.AlwaysBuild('a', 'b$FOO', ['c', 'd'], '$BAR',
env.fs.Dir('dir'), env.fs.File('file'))
assert t[0].__class__.__name__ == 'Entry'
- assert t[0].path == 'a'
+ assert t[0].get_internal_path() == 'a'
assert t[0].always_build
assert t[1].__class__.__name__ == 'Entry'
- assert t[1].path == 'bfff'
+ assert t[1].get_internal_path() == 'bfff'
assert t[1].always_build
assert t[2].__class__.__name__ == 'Entry'
- assert t[2].path == 'c'
+ assert t[2].get_internal_path() == 'c'
assert t[2].always_build
assert t[3].__class__.__name__ == 'Entry'
- assert t[3].path == 'd'
+ assert t[3].get_internal_path() == 'd'
assert t[3].always_build
assert t[4].__class__.__name__ == 'Entry'
- assert t[4].path == 'bbb'
+ assert t[4].get_internal_path() == 'bbb'
assert t[4].always_build
assert t[5].__class__.__name__ == 'Dir'
- assert t[5].path == 'dir'
+ assert t[5].get_internal_path() == 'dir'
assert t[5].always_build
assert t[6].__class__.__name__ == 'File'
- assert t[6].path == 'file'
+ assert t[6].get_internal_path() == 'file'
assert t[6].always_build
def test_VariantDir(self):
@@ -2811,13 +2811,13 @@ def generate(env):
assert t.builder is not None
assert t.builder.action.__class__.__name__ == 'CommandAction'
assert t.builder.action.cmd_list == 'buildfoo $target $source'
- assert 'foo1.in' in [x.path for x in t.sources]
- assert 'foo2.in' in [x.path for x in t.sources]
+ assert 'foo1.in' in [x.get_internal_path() for x in t.sources]
+ assert 'foo2.in' in [x.get_internal_path() for x in t.sources]
sub = env.fs.Dir('sub')
t = env.Command(target='bar.out', source='sub',
action='buildbar $target $source')[0]
- assert 'sub' in [x.path for x in t.sources]
+ assert 'sub' in [x.get_internal_path() for x in t.sources]
def testFunc(env, target, source):
assert str(target[0]) == 'foo.out'
@@ -2828,8 +2828,8 @@ def generate(env):
assert t.builder is not None
assert t.builder.action.__class__.__name__ == 'FunctionAction'
t.build()
- assert 'foo1.in' in [x.path for x in t.sources]
- assert 'foo2.in' in [x.path for x in t.sources]
+ assert 'foo1.in' in [x.get_internal_path() for x in t.sources]
+ assert 'foo2.in' in [x.get_internal_path() for x in t.sources]
x = []
def test2(baz, x=x):
@@ -2846,7 +2846,7 @@ def generate(env):
action = 'foo',
X = 'xxx')[0]
assert str(t) == 'xxx.out', str(t)
- assert 'xxx.in' in [x.path for x in t.sources]
+ assert 'xxx.in' in [x.get_internal_path() for x in t.sources]
env = self.TestEnvironment(source_scanner = 'should_not_find_this')
t = env.Command(target='file.out', source='file.in',
@@ -2890,27 +2890,27 @@ def generate(env):
t = env.Depends(target='EnvironmentTest.py',
dependency='Environment.py')[0]
assert t.__class__.__name__ == 'Entry', t.__class__.__name__
- assert t.path == 'EnvironmentTest.py'
+ assert t.get_internal_path() == 'EnvironmentTest.py'
assert len(t.depends) == 1
d = t.depends[0]
assert d.__class__.__name__ == 'Entry', d.__class__.__name__
- assert d.path == 'Environment.py'
+ assert d.get_internal_path() == 'Environment.py'
t = env.Depends(target='${FOO}.py', dependency='${BAR}.py')[0]
assert t.__class__.__name__ == 'File', t.__class__.__name__
- assert t.path == 'xxx.py'
+ assert t.get_internal_path() == 'xxx.py'
assert len(t.depends) == 1
d = t.depends[0]
assert d.__class__.__name__ == 'File', d.__class__.__name__
- assert d.path == 'yyy.py'
+ assert d.get_internal_path() == 'yyy.py'
t = env.Depends(target='dir1', dependency='dir2')[0]
assert t.__class__.__name__ == 'Dir', t.__class__.__name__
- assert t.path == 'dir1'
+ assert t.get_internal_path() == 'dir1'
assert len(t.depends) == 1
d = t.depends[0]
assert d.__class__.__name__ == 'Dir', d.__class__.__name__
- assert d.path == 'dir2'
+ assert d.get_internal_path() == 'dir2'
def test_Dir(self):
"""Test the Dir() method"""
@@ -2944,19 +2944,19 @@ def generate(env):
t = env.NoClean('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO')
assert t[0].__class__.__name__ == 'Entry', t[0].__class__.__name__
- assert t[0].path == 'p_a'
+ assert t[0].get_internal_path() == 'p_a'
assert t[0].noclean
assert t[1].__class__.__name__ == 'Dir', t[1].__class__.__name__
- assert t[1].path == 'p_hhhb'
+ assert t[1].get_internal_path() == 'p_hhhb'
assert t[1].noclean
assert t[2].__class__.__name__ == 'Entry', t[2].__class__.__name__
- assert t[2].path == 'p_c'
+ assert t[2].get_internal_path() == 'p_c'
assert t[2].noclean
assert t[3].__class__.__name__ == 'File', t[3].__class__.__name__
- assert t[3].path == 'p_d'
+ assert t[3].get_internal_path() == 'p_d'
assert t[3].noclean
assert t[4].__class__.__name__ == 'Entry', t[4].__class__.__name__
- assert t[4].path == 'p_ggg'
+ assert t[4].get_internal_path() == 'p_ggg'
assert t[4].noclean
def test_Dump(self):
@@ -3074,27 +3074,27 @@ def generate(env):
t = env.Ignore(target='targ.py', dependency='dep.py')[0]
assert t.__class__.__name__ == 'Entry', t.__class__.__name__
- assert t.path == 'targ.py'
+ assert t.get_internal_path() == 'targ.py'
assert len(t.ignore) == 1
i = t.ignore[0]
assert i.__class__.__name__ == 'Entry', i.__class__.__name__
- assert i.path == 'dep.py'
+ assert i.get_internal_path() == 'dep.py'
t = env.Ignore(target='$FOO$BAR', dependency='$BAR$FOO')[0]
assert t.__class__.__name__ == 'File', t.__class__.__name__
- assert t.path == 'yyyzzz'
+ assert t.get_internal_path() == 'yyyzzz'
assert len(t.ignore) == 1
i = t.ignore[0]
assert i.__class__.__name__ == 'File', i.__class__.__name__
- assert i.path == 'zzzyyy'
+ assert i.get_internal_path() == 'zzzyyy'
t = env.Ignore(target='dir1', dependency='dir2')[0]
assert t.__class__.__name__ == 'Dir', t.__class__.__name__
- assert t.path == 'dir1'
+ assert t.get_internal_path() == 'dir1'
assert len(t.ignore) == 1
i = t.ignore[0]
assert i.__class__.__name__ == 'Dir', i.__class__.__name__
- assert i.path == 'dir2'
+ assert i.get_internal_path() == 'dir2'
def test_Literal(self):
"""Test the Literal() method"""
@@ -3123,19 +3123,19 @@ def generate(env):
t = env.Precious('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO')
assert t[0].__class__.__name__ == 'Entry', t[0].__class__.__name__
- assert t[0].path == 'p_a'
+ assert t[0].get_internal_path() == 'p_a'
assert t[0].precious
assert t[1].__class__.__name__ == 'Dir', t[1].__class__.__name__
- assert t[1].path == 'p_hhhb'
+ assert t[1].get_internal_path() == 'p_hhhb'
assert t[1].precious
assert t[2].__class__.__name__ == 'Entry', t[2].__class__.__name__
- assert t[2].path == 'p_c'
+ assert t[2].get_internal_path() == 'p_c'
assert t[2].precious
assert t[3].__class__.__name__ == 'File', t[3].__class__.__name__
- assert t[3].path == 'p_d'
+ assert t[3].get_internal_path() == 'p_d'
assert t[3].precious
assert t[4].__class__.__name__ == 'Entry', t[4].__class__.__name__
- assert t[4].path == 'p_ggg'
+ assert t[4].get_internal_path() == 'p_ggg'
assert t[4].precious
def test_Pseudo(self):
@@ -3146,19 +3146,19 @@ def generate(env):
t = env.Pseudo('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO')
assert t[0].__class__.__name__ == 'Entry', t[0].__class__.__name__
- assert t[0].path == 'p_a'
+ assert t[0].get_internal_path() == 'p_a'
assert t[0].pseudo
assert t[1].__class__.__name__ == 'Dir', t[1].__class__.__name__
- assert t[1].path == 'p_hhhb'
+ assert t[1].get_internal_path() == 'p_hhhb'
assert t[1].pseudo
assert t[2].__class__.__name__ == 'Entry', t[2].__class__.__name__
- assert t[2].path == 'p_c'
+ assert t[2].get_internal_path() == 'p_c'
assert t[2].pseudo
assert t[3].__class__.__name__ == 'File', t[3].__class__.__name__
- assert t[3].path == 'p_d'
+ assert t[3].get_internal_path() == 'p_d'
assert t[3].pseudo
assert t[4].__class__.__name__ == 'Entry', t[4].__class__.__name__
- assert t[4].path == 'p_ggg'
+ assert t[4].get_internal_path() == 'p_ggg'
assert t[4].pseudo
def test_Repository(self):
@@ -3263,7 +3263,7 @@ def generate(env):
bar = env.Object('bar.obj', 'bar.cpp')[0]
s = env.SideEffect('mylib.pdb', ['foo.obj', 'bar.obj'])[0]
assert s.__class__.__name__ == 'Entry', s.__class__.__name__
- assert s.path == 'mylib.pdb'
+ assert s.get_internal_path() == 'mylib.pdb'
assert s.side_effect
assert foo.side_effects == [s]
assert bar.side_effects == [s]
@@ -3272,7 +3272,7 @@ def generate(env):
bbb = env.Object('bbb.obj', 'bbb.cpp')[0]
s = env.SideEffect('my${LIB}.pdb', ['${FOO}.obj', '${BAR}.obj'])[0]
assert s.__class__.__name__ == 'File', s.__class__.__name__
- assert s.path == 'mylll.pdb'
+ assert s.get_internal_path() == 'mylll.pdb'
assert s.side_effect
assert fff.side_effects == [s], fff.side_effects
assert bbb.side_effects == [s], bbb.side_effects
@@ -3281,7 +3281,7 @@ def generate(env):
ccc = env.Object('ccc.obj', 'ccc.cpp')[0]
s = env.SideEffect('mymmm.pdb', ['ggg.obj', 'ccc.obj'])[0]
assert s.__class__.__name__ == 'Dir', s.__class__.__name__
- assert s.path == 'mymmm.pdb'
+ assert s.get_internal_path() == 'mymmm.pdb'
assert s.side_effect
assert ggg.side_effects == [s], ggg.side_effects
assert ccc.side_effects == [s], ccc.side_effects
@@ -3290,18 +3290,18 @@ def generate(env):
"""Test the SourceCode() method."""
env = self.TestEnvironment(FOO='mmm', BAR='nnn')
e = env.SourceCode('foo', None)[0]
- assert e.path == 'foo'
+ assert e.get_internal_path() == 'foo'
s = e.src_builder()
assert s is None, s
b = Builder()
e = env.SourceCode(e, b)[0]
- assert e.path == 'foo'
+ assert e.get_internal_path() == 'foo'
s = e.src_builder()
assert s is b, s
e = env.SourceCode('$BAR$FOO', None)[0]
- assert e.path == 'nnnmmm'
+ assert e.get_internal_path() == 'nnnmmm'
s = e.src_builder()
assert s is None, s
diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py
index 388f8ac..98ed758 100644
--- a/src/engine/SCons/Executor.py
+++ b/src/engine/SCons/Executor.py
@@ -40,6 +40,10 @@ import SCons.Memoize
class Batch(object):
"""Remembers exact association between targets
and sources of executor."""
+
+ __slots__ = ('targets',
+ 'sources')
+
def __init__(self, targets=[], sources=[]):
self.targets = targets
self.sources = sources
@@ -109,6 +113,48 @@ def rfile(node):
return rfile()
+def execute_nothing(obj, target, kw):
+ return 0
+
+def execute_action_list(obj, target, kw):
+ """Actually execute the action list."""
+ env = obj.get_build_env()
+ kw = obj.get_kw(kw)
+ status = 0
+ for act in obj.get_action_list():
+ #args = (self.get_all_targets(), self.get_all_sources(), env)
+ args = ([], [], env)
+ status = act(*args, **kw)
+ if isinstance(status, SCons.Errors.BuildError):
+ status.executor = obj
+ raise status
+ elif status:
+ msg = "Error %s" % status
+ raise SCons.Errors.BuildError(
+ errstr=msg,
+ node=obj.batches[0].targets,
+ executor=obj,
+ action=act)
+ return status
+
+_do_execute_map = {0 : execute_nothing,
+ 1 : execute_action_list}
+
+
+def execute_actions_str(obj):
+ env = obj.get_build_env()
+ return "\n".join([action.genstring(obj.get_all_targets(),
+ obj.get_all_sources(),
+ env)
+ for action in obj.get_action_list()])
+
+def execute_null_str(obj):
+ return ''
+
+_execute_str_map = {0 : execute_null_str,
+ 1 : execute_actions_str}
+
+
class Executor(object):
"""A class for controlling instances of executing an action.
@@ -117,10 +163,21 @@ class Executor(object):
and sources for later processing as needed.
"""
- if SCons.Memoize.use_memoizer:
- __metaclass__ = SCons.Memoize.Memoized_Metaclass
-
- memoizer_counters = []
+ __slots__ = ('pre_actions',
+ 'post_actions',
+ 'env',
+ 'overridelist',
+ 'batches',
+ 'builder_kw',
+ '_memo',
+ 'lvars',
+ '_changed_sources_list',
+ '_changed_targets_list',
+ '_unchanged_sources_list',
+ '_unchanged_targets_list',
+ 'action_list',
+ '_do_execute',
+ '_execute_str')
def __init__(self, action, env=None, overridelist=[{}],
targets=[], sources=[], builder_kw={}):
@@ -135,6 +192,8 @@ class Executor(object):
else:
self.batches = []
self.builder_kw = builder_kw
+ self._do_execute = 1
+ self._execute_str = 1
self._memo = {}
def get_lvars(self):
@@ -284,8 +343,7 @@ class Executor(object):
result.extend(target.side_effects)
return result
- memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
-
+ @SCons.Memoize.CountMethodCall
def get_build_env(self):
"""Fetch or create the appropriate build Environment
for this Executor.
@@ -330,36 +388,12 @@ class Executor(object):
result['executor'] = self
return result
- def do_nothing(self, target, kw):
- return 0
-
- def do_execute(self, target, kw):
- """Actually execute the action list."""
- env = self.get_build_env()
- kw = self.get_kw(kw)
- status = 0
- for act in self.get_action_list():
- #args = (self.get_all_targets(), self.get_all_sources(), env)
- args = ([], [], env)
- status = act(*args, **kw)
- if isinstance(status, SCons.Errors.BuildError):
- status.executor = self
- raise status
- elif status:
- msg = "Error %s" % status
- raise SCons.Errors.BuildError(
- errstr=msg,
- node=self.batches[0].targets,
- executor=self,
- action=act)
- return status
-
# use extra indirection because with new-style objects (Python 2.2
# and above) we can't override special methods, and nullify() needs
# to be able to do this.
def __call__(self, target, **kw):
- return self.do_execute(target, kw)
+ return _do_execute_map[self._do_execute](self, target, kw)
def cleanup(self):
self._memo = {}
@@ -403,24 +437,15 @@ class Executor(object):
# another extra indirection for new-style objects and nullify...
- def my_str(self):
- env = self.get_build_env()
- return "\n".join([action.genstring(self.get_all_targets(),
- self.get_all_sources(),
- env)
- for action in self.get_action_list()])
-
-
def __str__(self):
- return self.my_str()
+ return _execute_str_map[self._execute_str](self)
def nullify(self):
self.cleanup()
- self.do_execute = self.do_nothing
- self.my_str = lambda: ''
-
- memoizer_counters.append(SCons.Memoize.CountValue('get_contents'))
+ self._do_execute = 0
+ self._execute_str = 0
+ @SCons.Memoize.CountMethodCall
def get_contents(self):
"""Fetch the signature contents. This is the main reason this
class exists, so we can compute this once and cache it regardless
@@ -493,8 +518,7 @@ class Executor(object):
def _get_unignored_sources_key(self, node, ignore=()):
return (node,) + tuple(ignore)
- memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key))
-
+ @SCons.Memoize.CountDictCall(_get_unignored_sources_key)
def get_unignored_sources(self, node, ignore=()):
key = (node,) + tuple(ignore)
try:
@@ -579,6 +603,23 @@ class Null(object):
disassociate Builders from Nodes entirely, so we're not
going to worry about unit tests for this--at least for now.
"""
+
+ __slots__ = ('pre_actions',
+ 'post_actions',
+ 'env',
+ 'overridelist',
+ 'batches',
+ 'builder_kw',
+ '_memo',
+ 'lvars',
+ '_changed_sources_list',
+ '_changed_targets_list',
+ '_unchanged_sources_list',
+ '_unchanged_targets_list',
+ 'action_list',
+ '_do_execute',
+ '_execute_str')
+
def __init__(self, *args, **kw):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Executor.Null')
self.batches = [Batch(kw['targets'][:], [])]
diff --git a/src/engine/SCons/Memoize.py b/src/engine/SCons/Memoize.py
index e77aacf..5144f83 100644
--- a/src/engine/SCons/Memoize.py
+++ b/src/engine/SCons/Memoize.py
@@ -25,17 +25,17 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
__doc__ = """Memoizer
-A metaclass 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.
Use of this modules assumes that wrapped methods be coded to cache their
-values in a consistent way. Here is an example of wrapping a method
-that returns a computed value, with no input parameters:
+values in a consistent way. In particular, it requires that the class uses a
+dictionary named "_memo" to store the cached values.
- memoizer_counters = [] # Memoization
-
- memoizer_counters.append(SCons.Memoize.CountValue('foo')) # Memoization
+Here is an example of wrapping a method that returns a computed value,
+with no input parameters:
+ @SCons.Memoize.CountMethodCall
def foo(self):
try: # Memoization
@@ -55,8 +55,7 @@ based on one or more input arguments:
def _bar_key(self, argument): # Memoization
return argument # Memoization
- memoizer_counters.append(SCons.Memoize.CountDict('bar', _bar_key)) # Memoization
-
+ @SCons.Memoize.CountDictCall(_bar_key)
def bar(self, argument):
memo_key = argument # Memoization
@@ -77,10 +76,6 @@ based on one or more input arguments:
return result
-At one point we avoided replicating this sort of logic in all the methods
-by putting it right into this module, but we've moved away from that at
-present (see the "Historical Note," below.).
-
Deciding what to cache is tricky, because different configurations
can have radically different performance tradeoffs, and because the
tradeoffs involved are often so non-obvious. Consequently, deciding
@@ -102,51 +97,37 @@ cache return values from a method that's being called a lot:
input arguments, you don't need to use all of the arguments
if some of them don't affect the return values.
-Historical Note: The initial Memoizer implementation actually handled
-the caching of values for the wrapped methods, based on a set of generic
-algorithms for computing hashable values based on the method's arguments.
-This collected caching logic nicely, but had two drawbacks:
-
- Running arguments through a generic key-conversion mechanism is slower
- (and less flexible) than just coding these things directly. Since the
- methods that need memoized values are generally performance-critical,
- slowing them down in order to collect the logic isn't the right
- tradeoff.
-
- Use of the memoizer really obscured what was being called, because
- all the memoized methods were wrapped with re-used generic methods.
- This made it more difficult, for example, to use the Python profiler
- to figure out how to optimize the underlying methods.
"""
-import types
-
# A flag controlling whether or not we actually use memoization.
use_memoizer = None
-CounterList = []
+# Global list of counter objects
+CounterList = {}
class Counter(object):
"""
Base class for counting memoization hits and misses.
- We expect that the metaclass initialization will have filled in
- the .name attribute that represents the name of the function
- being counted.
+ We expect that the initialization in a matching decorator will
+ fill in the correct class name and method name that represents
+ the name of the function being counted.
"""
- def __init__(self, method_name):
+ def __init__(self, cls_name, method_name):
"""
"""
+ self.cls_name = cls_name
self.method_name = method_name
self.hit = 0
self.miss = 0
- CounterList.append(self)
+ def key(self):
+ return self.cls_name+'.'+self.method_name
def display(self):
fmt = " %7d hits %7d misses %s()"
- print fmt % (self.hit, self.miss, self.name)
+ print fmt % (self.hit, self.miss, self.key())
def __cmp__(self, other):
try:
- return cmp(self.name, other.name)
+ return cmp(self.key(), other.key())
except AttributeError:
return 0
@@ -154,45 +135,39 @@ class CountValue(Counter):
"""
A counter class for simple, atomic memoized values.
- A CountValue object should be instantiated in a class for each of
+ A CountValue object should be instantiated in a decorator for each of
the class's methods that memoizes its return value by simply storing
the return value in its _memo dictionary.
-
- We expect that the metaclass initialization will fill in the
- .underlying_method attribute with the method that we're wrapping.
- We then call the underlying_method method after counting whether
- its memoized value has already been set (a hit) or not (a miss).
"""
- def __call__(self, *args, **kw):
+ def count(self, *args, **kw):
+ """ Counts whether the memoized value has already been
+ set (a hit) or not (a miss).
+ """
obj = args[0]
if self.method_name in obj._memo:
self.hit = self.hit + 1
else:
self.miss = self.miss + 1
- return self.underlying_method(*args, **kw)
class CountDict(Counter):
"""
A counter class for memoized values stored in a dictionary, with
keys based on the method's input arguments.
- A CountDict object is instantiated in a class for each of the
+ A CountDict object is instantiated in a decorator for each of the
class's methods that memoizes its return value in a dictionary,
indexed by some key that can be computed from one or more of
its input arguments.
-
- We expect that the metaclass initialization will fill in the
- .underlying_method attribute with the method that we're wrapping.
- We then call the underlying_method method after counting whether the
- computed key value is already present in the memoization dictionary
- (a hit) or not (a miss).
"""
- def __init__(self, method_name, keymaker):
+ def __init__(self, cls_name, method_name, keymaker):
"""
"""
- Counter.__init__(self, method_name)
+ Counter.__init__(self, cls_name, method_name)
self.keymaker = keymaker
- def __call__(self, *args, **kw):
+ def count(self, *args, **kw):
+ """ Counts whether the computed key value is already present
+ in the memoization dictionary (a hit) or not (a miss).
+ """
obj = args[0]
try:
memo_dict = obj._memo[self.method_name]
@@ -204,39 +179,65 @@ class CountDict(Counter):
self.hit = self.hit + 1
else:
self.miss = self.miss + 1
- return self.underlying_method(*args, **kw)
-
-class Memoizer(object):
- """Object which performs caching of method calls for its 'primary'
- instance."""
-
- def __init__(self):
- pass
def Dump(title=None):
+ """ Dump the hit/miss count for all the counters
+ collected so far.
+ """
if title:
print title
- CounterList.sort()
- for counter in CounterList:
- counter.display()
-
-class Memoized_Metaclass(type):
- def __init__(cls, name, bases, cls_dict):
- super(Memoized_Metaclass, cls).__init__(name, bases, cls_dict)
-
- for counter in cls_dict.get('memoizer_counters', []):
- method_name = counter.method_name
-
- counter.name = cls.__name__ + '.' + method_name
- counter.underlying_method = cls_dict[method_name]
-
- replacement_method = types.MethodType(counter, None, cls)
- setattr(cls, method_name, replacement_method)
+ for counter in sorted(CounterList):
+ CounterList[counter].display()
def EnableMemoization():
global use_memoizer
use_memoizer = 1
+def CountMethodCall(fn):
+ """ Decorator for counting memoizer hits/misses while retrieving
+ a simple value in a class method. It wraps the given method
+ fn and uses a CountValue object to keep track of the
+ caching statistics.
+ Wrapping gets enabled by calling EnableMemoization().
+ """
+ if use_memoizer:
+ def wrapper(self, *args, **kwargs):
+ global CounterList
+ key = self.__class__.__name__+'.'+fn.__name__
+ if key not in CounterList:
+ CounterList[key] = CountValue(self.__class__.__name__, fn.__name__)
+ CounterList[key].count(self, *args, **kwargs)
+ return fn(self, *args, **kwargs)
+ wrapper.__name__= fn.__name__
+ return wrapper
+ else:
+ return fn
+
+def CountDictCall(keyfunc):
+ """ Decorator for counting memoizer hits/misses while accessing
+ dictionary values with a key-generating function. Like
+ CountMethodCall above, it wraps the given method
+ fn and uses a CountDict object to keep track of the
+ caching statistics. The dict-key function keyfunc has to
+ get passed in the decorator call and gets stored in the
+ CountDict instance.
+ Wrapping gets enabled by calling EnableMemoization().
+ """
+ def decorator(fn):
+ if use_memoizer:
+ def wrapper(self, *args, **kwargs):
+ global CounterList
+ key = self.__class__.__name__+'.'+fn.__name__
+ if key not in CounterList:
+ CounterList[key] = CountDict(self.__class__.__name__, fn.__name__, keyfunc)
+ CounterList[key].count(self, *args, **kwargs)
+ return fn(self, *args, **kwargs)
+ wrapper.__name__= fn.__name__
+ return wrapper
+ else:
+ return fn
+ return decorator
+
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
diff --git a/src/engine/SCons/MemoizeTests.py b/src/engine/SCons/MemoizeTests.py
index 3606d57..77ff6bc 100644
--- a/src/engine/SCons/MemoizeTests.py
+++ b/src/engine/SCons/MemoizeTests.py
@@ -30,22 +30,18 @@ import TestUnit
import SCons.Memoize
-
+# Enable memoization counting
+SCons.Memoize.EnableMemoization()
class FakeObject(object):
- __metaclass__ = SCons.Memoize.Memoized_Metaclass
-
- memoizer_counters = []
-
def __init__(self):
self._memo = {}
def _dict_key(self, argument):
return argument
- memoizer_counters.append(SCons.Memoize.CountDict('dict', _dict_key))
-
+ @SCons.Memoize.CountDictCall(_dict_key)
def dict(self, argument):
memo_key = argument
@@ -66,8 +62,7 @@ class FakeObject(object):
return result
- memoizer_counters.append(SCons.Memoize.CountValue('value'))
-
+ @SCons.Memoize.CountMethodCall
def value(self):
try:
@@ -82,10 +77,7 @@ class FakeObject(object):
return result
def get_memoizer_counter(self, name):
- for mc in self.memoizer_counters:
- if mc.method_name == name:
- return mc
- return None
+ return SCons.Memoize.CounterList.get(self.__class__.__name__+'.'+name, None)
class Returner(object):
def __init__(self, result):
diff --git a/src/engine/SCons/Node/Alias.py b/src/engine/SCons/Node/Alias.py
index f817356..a035816 100644
--- a/src/engine/SCons/Node/Alias.py
+++ b/src/engine/SCons/Node/Alias.py
@@ -56,13 +56,47 @@ class AliasNameSpace(collections.UserDict):
return None
class AliasNodeInfo(SCons.Node.NodeInfoBase):
- current_version_id = 1
+ __slots__ = ('csig',)
+ current_version_id = 2
field_list = ['csig']
def str_to_node(self, s):
return default_ans.Alias(s)
+ def __getstate__(self):
+ """
+ Return all fields that shall be pickled. Walk the slots in the class
+ hierarchy and add those to the state dictionary. If a '__dict__' slot is
+ available, copy all entries to the dictionary. Also include the version
+ id, which is fixed for all instances of a class.
+ """
+ state = getattr(self, '__dict__', {}).copy()
+ for obj in type(self).mro():
+ for name in getattr(obj,'__slots__',()):
+ if hasattr(self, name):
+ state[name] = getattr(self, name)
+
+ state['_version_id'] = self.current_version_id
+ try:
+ del state['__weakref__']
+ except KeyError:
+ pass
+
+ return state
+
+ def __setstate__(self, state):
+ """
+ Restore the attributes from a pickled state.
+ """
+ # TODO check or discard version
+ del state['_version_id']
+ for key, value in state.items():
+ if key not in ('__weakref__',):
+ setattr(self, key, value)
+
+
class AliasBuildInfo(SCons.Node.BuildInfoBase):
- current_version_id = 1
+ __slots__ = ()
+ current_version_id = 2
class Alias(SCons.Node.Node):
@@ -72,7 +106,9 @@ class Alias(SCons.Node.Node):
def __init__(self, name):
SCons.Node.Node.__init__(self)
self.name = name
-
+ self.changed_since_last_build = 1
+ self.store_info = 0
+
def str_for_display(self):
return '"' + self.__str__() + '"'
@@ -105,13 +141,6 @@ class Alias(SCons.Node.Node):
#
#
- def changed_since_last_build(self, target, prev_ni):
- cur_csig = self.get_csig()
- try:
- return cur_csig != prev_ni.csig
- except AttributeError:
- return 1
-
def build(self):
"""A "builder" for aliases."""
pass
diff --git a/src/engine/SCons/Node/AliasTests.py b/src/engine/SCons/Node/AliasTests.py
index 2d11bdf..8e31875 100644
--- a/src/engine/SCons/Node/AliasTests.py
+++ b/src/engine/SCons/Node/AliasTests.py
@@ -103,14 +103,14 @@ class AliasNodeInfoTestCase(unittest.TestCase):
"""Test AliasNodeInfo initialization"""
ans = SCons.Node.Alias.AliasNameSpace()
aaa = ans.Alias('aaa')
- ni = SCons.Node.Alias.AliasNodeInfo(aaa)
+ ni = SCons.Node.Alias.AliasNodeInfo()
class AliasBuildInfoTestCase(unittest.TestCase):
def test___init__(self):
"""Test AliasBuildInfo initialization"""
ans = SCons.Node.Alias.AliasNameSpace()
aaa = ans.Alias('aaa')
- bi = SCons.Node.Alias.AliasBuildInfo(aaa)
+ bi = SCons.Node.Alias.AliasBuildInfo()
if __name__ == "__main__":
suite = unittest.TestSuite()
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py
index eec4e42..4e78852 100644
--- a/src/engine/SCons/Node/FS.py
+++ b/src/engine/SCons/Node/FS.py
@@ -56,10 +56,23 @@ import SCons.Warnings
from SCons.Debug import Trace
-do_store_info = True
print_duplicate = 0
+def sconsign_none(node):
+ raise NotImplementedError
+
+def sconsign_dir(node):
+ """Return the .sconsign file info for this directory,
+ creating it first if necessary."""
+ if not node._sconsign:
+ import SCons.SConsign
+ node._sconsign = SCons.SConsign.ForDirectory(node)
+ return node._sconsign
+
+_sconsign_map = {0 : sconsign_none,
+ 1 : sconsign_dir}
+
class EntryProxyAttributeError(AttributeError):
"""
An AttributeError subclass for recording and displaying the name
@@ -268,8 +281,8 @@ def LinkFunc(target, source, env):
# who want to move their soft-linked src-trees around. Those
# people should use the 'hard-copy' mode, softlinks cannot be
# used for that; at least I have no idea how ...
- src = source[0].abspath
- dest = target[0].abspath
+ src = source[0].get_abspath()
+ dest = target[0].get_abspath()
dir, file = os.path.split(dest)
if dir and not target[0].fs.isdir(dir):
os.makedirs(dir)
@@ -302,7 +315,7 @@ LocalCopy = SCons.Action.Action(LinkFunc, LocalString)
def UnlinkFunc(target, source, env):
t = target[0]
- t.fs.unlink(t.abspath)
+ t.fs.unlink(t.get_abspath())
return 0
Unlink = SCons.Action.Action(UnlinkFunc, None)
@@ -310,7 +323,7 @@ Unlink = SCons.Action.Action(UnlinkFunc, None)
def MkdirFunc(target, source, env):
t = target[0]
if not t.exists():
- t.fs.mkdir(t.abspath)
+ t.fs.mkdir(t.get_abspath())
return 0
Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None)
@@ -403,7 +416,7 @@ def do_diskcheck_match(node, predicate, errorfmt):
except (AttributeError, KeyError):
pass
if result:
- raise TypeError(errorfmt % node.abspath)
+ raise TypeError(errorfmt % node.get_abspath())
def ignore_diskcheck_match(node, predicate, errorfmt):
pass
@@ -573,7 +586,20 @@ class Base(SCons.Node.Node):
object identity comparisons.
"""
- memoizer_counters = []
+ __slots__ = ['name',
+ 'fs',
+ '_abspath',
+ '_labspath',
+ '_path',
+ '_tpath',
+ '_path_elements',
+ 'dir',
+ 'cwd',
+ 'duplicate',
+ '_local',
+ 'sbuilder',
+ '_proxy',
+ '_func_sconsign']
def __init__(self, name, directory, fs):
"""Initialize a generic Node.FS.Base object.
@@ -591,27 +617,26 @@ class Base(SCons.Node.Node):
#: Filename with extension as it was specified when the object was
#: created; to obtain filesystem path, use Python str() function
self.name = SCons.Util.silent_intern(name)
- #: Cached filename extension
- self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1])
self.fs = fs #: Reference to parent Node.FS object
assert directory, "A directory must be provided"
- self.abspath = SCons.Util.silent_intern(directory.entry_abspath(name))
- self.labspath = SCons.Util.silent_intern(directory.entry_labspath(name))
- if directory.path == '.':
- self.path = SCons.Util.silent_intern(name)
- else:
- self.path = SCons.Util.silent_intern(directory.entry_path(name))
- if directory.tpath == '.':
- self.tpath = SCons.Util.silent_intern(name)
- else:
- self.tpath = SCons.Util.silent_intern(directory.entry_tpath(name))
- self.path_elements = directory.path_elements + [self]
+ self._abspath = None
+ self._labspath = None
+ self._path = None
+ self._tpath = None
+ self._path_elements = None
self.dir = directory
self.cwd = None # will hold the SConscript directory for target nodes
self.duplicate = directory.duplicate
+ self.changed_since_last_build = 2
+ self._func_sconsign = 0
+ self._func_exists = 2
+ self._func_rexists = 2
+ self._func_get_contents = 0
+ self._func_target_from_source = 1
+ self.store_info = 1
def str_for_display(self):
return '"' + self.__str__() + '"'
@@ -624,17 +649,38 @@ class Base(SCons.Node.Node):
if isinstance(self, klass) or klass is Entry:
return
raise TypeError("Tried to lookup %s '%s' as a %s." %\
- (self.__class__.__name__, self.path, klass.__name__))
+ (self.__class__.__name__, self.get_internal_path(), klass.__name__))
def get_dir(self):
return self.dir
def get_suffix(self):
- return self.suffix
+ return SCons.Util.splitext(self.name)[1]
def rfile(self):
return self
+ def __getattr__(self, attr):
+ """ Together with the node_bwcomp dict defined below,
+ this method provides a simple backward compatibility
+ layer for the Node attributes 'abspath', 'labspath',
+ 'path', 'tpath', 'suffix' and 'path_elements'. These Node
+ attributes used to be directly available in v2.3 and earlier, but
+ have been replaced by getter methods that initialize the
+ single variables lazily when required, in order to save memory.
+ The redirection to the getters lets older Tools and
+ SConstruct continue to work without any additional changes,
+ fully transparent to the user.
+ Note, that __getattr__ is only called as fallback when the
+ requested attribute can't be found, so there should be no
+ speed performance penalty involved for standard builds.
+ """
+ if attr in node_bwcomp:
+ return node_bwcomp[attr](self)
+
+ raise AttributeError("%r object has no attribute %r" %
+ (self.__class__, attr))
+
def __str__(self):
"""A Node.FS.Base object's string representation is its path
name."""
@@ -643,8 +689,7 @@ class Base(SCons.Node.Node):
return self._save_str()
return self._get_str()
- memoizer_counters.append(SCons.Memoize.CountValue('_save_str'))
-
+ @SCons.Memoize.CountMethodCall
def _save_str(self):
try:
return self._memo['_save_str']
@@ -681,21 +726,20 @@ class Base(SCons.Node.Node):
rstr = __str__
- memoizer_counters.append(SCons.Memoize.CountValue('stat'))
-
+ @SCons.Memoize.CountMethodCall
def stat(self):
try: return self._memo['stat']
except KeyError: pass
- try: result = self.fs.stat(self.abspath)
+ try: result = self.fs.stat(self.get_abspath())
except os.error: result = None
self._memo['stat'] = result
return result
def exists(self):
- return self.stat() is not None
+ return SCons.Node._exists_map[self._func_exists](self)
def rexists(self):
- return self.rfile().exists()
+ return SCons.Node._rexists_map[self._func_rexists](self)
def getmtime(self):
st = self.stat()
@@ -717,7 +761,7 @@ class Base(SCons.Node.Node):
if hasattr(os, 'symlink'):
def islink(self):
- try: st = self.fs.lstat(self.abspath)
+ try: st = self.fs.lstat(self.get_abspath())
except os.error: return 0
return stat.S_ISLNK(st[stat.ST_MODE])
else:
@@ -752,7 +796,7 @@ class Base(SCons.Node.Node):
dir = self.fs.getcwd()
if self == dir:
return '.'
- path_elems = self.path_elements
+ path_elems = self.get_path_elements()
pathname = ''
try: i = path_elems.index(dir)
except ValueError:
@@ -785,7 +829,26 @@ class Base(SCons.Node.Node):
def get_abspath(self):
"""Get the absolute path of the file."""
- return self.abspath
+ return self.dir.entry_abspath(self.name)
+
+ def get_labspath(self):
+ """Get the absolute path of the file."""
+ return self.dir.entry_labspath(self.name)
+
+ def get_internal_path(self):
+ if self.dir._path == '.':
+ return self.name
+ else:
+ return self.dir.entry_path(self.name)
+
+ def get_tpath(self):
+ if self.dir._tpath == '.':
+ return self.name
+ else:
+ return self.dir.entry_tpath(self.name)
+
+ def get_path_elements(self):
+ return self.dir._path_elements + [self]
def for_signature(self):
# Return just our name. Even an absolute path would not work,
@@ -811,13 +874,12 @@ class Base(SCons.Node.Node):
files that need different behavior. See Tool/swig.py for
an example.
"""
- return self.dir.Entry(prefix + splitext(self.name)[0] + suffix)
+ return SCons.Node._target_from_source_map[self._func_target_from_source](self, prefix, suffix, splitext)
def _Rfindalldirs_key(self, pathlist):
return pathlist
- memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key))
-
+ @SCons.Memoize.CountDictCall(_Rfindalldirs_key)
def Rfindalldirs(self, pathlist):
"""
Return all of the directories for a given path list, including
@@ -856,8 +918,7 @@ class Base(SCons.Node.Node):
cwd = self.cwd or self.fs._cwd
return cwd.Rfindalldirs(pathlist)
- memoizer_counters.append(SCons.Memoize.CountValue('rentry'))
-
+ @SCons.Memoize.CountMethodCall
def rentry(self):
try:
return self._memo['rentry']
@@ -878,6 +939,17 @@ class Base(SCons.Node.Node):
def _glob1(self, pattern, ondisk=True, source=False, strings=False):
return []
+
+# Dict that provides a simple backward compatibility
+# layer for the Node attributes 'abspath', 'labspath',
+# 'path', 'tpath' and 'path_elements'.
+# @see Base.__getattr__ above
+node_bwcomp = {'abspath' : Base.get_abspath,
+ 'labspath' : Base.get_labspath,
+ 'path' : Base.get_internal_path,
+ 'tpath' : Base.get_tpath,
+ 'path_elements' : Base.get_path_elements,
+ 'suffix' : Base.get_suffix}
class Entry(Base):
"""This is the class for generic Node.FS entries--that is, things
@@ -887,6 +959,28 @@ class Entry(Base):
time comes, and then call the same-named method in the transformed
class."""
+ __slots__ = ['scanner_paths',
+ 'cachedir_csig',
+ 'cachesig',
+ 'repositories',
+ 'srcdir',
+ 'entries',
+ 'searched',
+ '_sconsign',
+ 'variant_dirs',
+ 'root',
+ 'dirname',
+ 'on_disk_entries',
+ 'sccs_dir',
+ 'rcs_dir',
+ 'released_target_info',
+ 'contentsig']
+
+ def __init__(self, name, directory, fs):
+ Base.__init__(self, name, directory, fs)
+ self._func_exists = 3
+ self._func_get_contents = 1
+
def diskcheck_match(self):
pass
@@ -917,7 +1011,7 @@ class Entry(Base):
self.__class__ = Dir
self._morph()
elif must_exist:
- msg = "No such file or directory: '%s'" % self.abspath
+ msg = "No such file or directory: '%s'" % self.get_abspath()
raise SCons.Errors.UserError(msg)
else:
self.__class__ = File
@@ -939,17 +1033,7 @@ class Entry(Base):
def get_contents(self):
"""Fetch the contents of the entry. Returns the exact binary
contents of the file."""
- try:
- self = self.disambiguate(must_exist=1)
- except SCons.Errors.UserError:
- # There was nothing on disk with which to disambiguate
- # this entry. Leave it as an Entry, but return a null
- # string so calls to get_contents() in emitters and the
- # like (e.g. in qt.py) don't have to disambiguate by hand
- # or catch the exception.
- return ''
- else:
- return self.get_contents()
+ return SCons.Node._get_contents_map[self._func_get_contents](self)
def get_text_contents(self):
"""Fetch the decoded text contents of a Unicode encoded Entry.
@@ -989,10 +1073,7 @@ class Entry(Base):
# to make various tests pass.
def exists(self):
- """Return if the Entry exists. Check the file system to see
- what we should turn into first. Assume a file if there's no
- directory."""
- return self.disambiguate().exists()
+ return SCons.Node._exists_map[self._func_exists](self)
def rel_path(self, other):
d = self.disambiguate()
@@ -1003,9 +1084,6 @@ class Entry(Base):
def new_ninfo(self):
return self.disambiguate().new_ninfo()
- def changed_since_last_build(self, target, prev_ni):
- return self.disambiguate().changed_since_last_build(target, prev_ni)
-
def _glob1(self, pattern, ondisk=True, source=False, strings=False):
return self.disambiguate()._glob1(pattern, ondisk, source, strings)
@@ -1019,9 +1097,6 @@ _classEntry = Entry
class LocalFS(object):
- if SCons.Memoize.use_memoizer:
- __metaclass__ = SCons.Memoize.Memoized_Metaclass
-
# 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
@@ -1101,8 +1176,6 @@ class LocalFS(object):
class FS(LocalFS):
- memoizer_counters = []
-
def __init__(self, path = None):
"""Initialize the Node.FS subsystem.
@@ -1128,8 +1201,8 @@ class FS(LocalFS):
self.defaultDrive = _my_normcase(_my_splitdrive(self.pathTop)[0])
self.Top = self.Dir(self.pathTop)
- self.Top.path = '.'
- self.Top.tpath = '.'
+ self.Top._path = '.'
+ self.Top._tpath = '.'
self._cwd = self.Top
DirNodeInfo.fs = self
@@ -1160,7 +1233,7 @@ class FS(LocalFS):
if dir is not None:
self._cwd = dir
if change_os_dir:
- os.chdir(dir.abspath)
+ os.chdir(dir.get_abspath())
except OSError:
self._cwd = curr
raise
@@ -1249,9 +1322,9 @@ class FS(LocalFS):
# The path is relative to the top-level SCons directory.
if p in ('', '.'):
- p = directory.labspath
+ p = directory.get_labspath()
else:
- p = directory.labspath + '/' + p
+ p = directory.get_labspath() + '/' + p
else:
if do_splitdrive:
drive, p = _my_splitdrive(p)
@@ -1285,9 +1358,9 @@ class FS(LocalFS):
directory = self._cwd
if p in ('', '.'):
- p = directory.labspath
+ p = directory.get_labspath()
else:
- p = directory.labspath + '/' + p
+ p = directory.get_labspath() + '/' + p
if drive:
root = self.get_root(drive)
@@ -1393,7 +1466,7 @@ class FS(LocalFS):
if start_dir.is_under(bd):
# If already in the build-dir location, don't reflect
return [orig], fmt % str(orig)
- p = os.path.join(bd.path, *tail)
+ p = os.path.join(bd._path, *tail)
targets.append(self.Entry(p))
tail = [dir.name] + tail
dir = dir.up()
@@ -1412,8 +1485,9 @@ class FS(LocalFS):
return cwd.glob(pathname, ondisk, source, strings, exclude)
class DirNodeInfo(SCons.Node.NodeInfoBase):
+ __slots__ = ()
# This should get reset by the FS initialization.
- current_version_id = 1
+ current_version_id = 2
fs = None
@@ -1425,11 +1499,12 @@ class DirNodeInfo(SCons.Node.NodeInfoBase):
if drive:
root = self.fs.get_root(drive)
if not os.path.isabs(s):
- s = top.labspath + '/' + s
+ s = top.get_labspath() + '/' + s
return root._lookup_abs(s, Entry)
class DirBuildInfo(SCons.Node.BuildInfoBase):
- current_version_id = 1
+ __slots__ = ()
+ current_version_id = 2
glob_magic_check = re.compile('[*?[]')
@@ -1440,7 +1515,22 @@ class Dir(Base):
"""A class for directories in a file system.
"""
- memoizer_counters = []
+ __slots__ = ['scanner_paths',
+ 'cachedir_csig',
+ 'cachesig',
+ 'repositories',
+ 'srcdir',
+ 'entries',
+ 'searched',
+ '_sconsign',
+ 'variant_dirs',
+ 'root',
+ 'dirname',
+ 'on_disk_entries',
+ 'sccs_dir',
+ 'rcs_dir',
+ 'released_target_info',
+ 'contentsig']
NodeInfo = DirNodeInfo
BuildInfo = DirBuildInfo
@@ -1470,6 +1560,22 @@ class Dir(Base):
self._sconsign = None
self.variant_dirs = []
self.root = self.dir.root
+ self.changed_since_last_build = 3
+ self._func_sconsign = 1
+ self._func_exists = 2
+ self._func_get_contents = 2
+
+ self._abspath = SCons.Util.silent_intern(self.dir.entry_abspath(self.name))
+ self._labspath = SCons.Util.silent_intern(self.dir.entry_labspath(self.name))
+ if self.dir._path == '.':
+ self._path = SCons.Util.silent_intern(self.name)
+ else:
+ self._path = SCons.Util.silent_intern(self.dir.entry_path(self.name))
+ if self.dir._tpath == '.':
+ self._tpath = SCons.Util.silent_intern(self.name)
+ else:
+ self._tpath = SCons.Util.silent_intern(self.dir.entry_tpath(self.name))
+ self._path_elements = self.dir._path_elements + [self]
# For directories, we make a difference between the directory
# 'name' and the directory 'dirname'. The 'name' attribute is
@@ -1562,8 +1668,7 @@ class Dir(Base):
return self.srcdir.get_all_rdirs() + self.repositories
return self.repositories
- memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs'))
-
+ @SCons.Memoize.CountMethodCall
def get_all_rdirs(self):
try:
return list(self._memo['get_all_rdirs'])
@@ -1589,7 +1694,7 @@ class Dir(Base):
def addRepository(self, dir):
if dir != self and not dir in self.repositories:
self.repositories.append(dir)
- dir.tpath = '.'
+ dir._tpath = '.'
self.__clearRepositoryCache()
def up(self):
@@ -1598,8 +1703,7 @@ class Dir(Base):
def _rel_path_key(self, other):
return str(other)
- memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key))
-
+ @SCons.Memoize.CountDictCall(_rel_path_key)
def rel_path(self, other):
"""Return a path to "other" relative to this directory.
"""
@@ -1628,7 +1732,7 @@ class Dir(Base):
if self is other:
result = '.'
- elif not other in self.path_elements:
+ elif not other in self._path_elements:
try:
other_dir = other.get_dir()
except AttributeError:
@@ -1643,10 +1747,10 @@ class Dir(Base):
else:
result = dir_rel_path + OS_SEP + other.name
else:
- i = self.path_elements.index(other) + 1
+ i = self._path_elements.index(other) + 1
- path_elems = ['..'] * (len(self.path_elements) - i) \
- + [n.name for n in other.path_elements[i:]]
+ path_elems = ['..'] * (len(self._path_elements) - i) \
+ + [n.name for n in other._path_elements[i:]]
result = OS_SEP.join(path_elems)
@@ -1713,7 +1817,7 @@ class Dir(Base):
if p is None:
# Don't use while: - else: for this condition because
# if so, then parent is None and has no .path attribute.
- raise SCons.Errors.StopError(parent.path)
+ raise SCons.Errors.StopError(parent._path)
parent = p
listDirs.reverse()
for dirnode in listDirs:
@@ -1753,10 +1857,7 @@ class Dir(Base):
def get_contents(self):
"""Return content signatures and names of all our children
separated by new-lines. Ensure that the nodes are sorted."""
- contents = []
- for node in sorted(self.children(), key=lambda t: t.name):
- contents.append('%s %s\n' % (node.get_csig(), node.name))
- return ''.join(contents)
+ return SCons.Node._get_contents_map[self._func_get_contents](self)
def get_csig(self):
"""Compute the content signature for Directory nodes. In
@@ -1770,8 +1871,6 @@ class Dir(Base):
def do_duplicate(self, src):
pass
- changed_since_last_build = SCons.Node.Node.state_has_changed
-
def is_up_to_date(self):
"""If any child is not up-to-date, then this directory isn't,
either."""
@@ -1795,12 +1894,8 @@ class Dir(Base):
return self
def sconsign(self):
- """Return the .sconsign file info for this directory,
- creating it first if necessary."""
- if not self._sconsign:
- import SCons.SConsign
- self._sconsign = SCons.SConsign.ForDirectory(self)
- return self._sconsign
+ """Return the .sconsign file info for this directory. """
+ return _sconsign_map[self._func_sconsign](self)
def srcnode(self):
"""Dir has a special need for srcnode()...if we
@@ -1817,17 +1912,34 @@ class Dir(Base):
stamp = kid.get_timestamp()
return stamp
+ def get_abspath(self):
+ """Get the absolute path of the file."""
+ return self._abspath
+
+ def get_labspath(self):
+ """Get the absolute path of the file."""
+ return self._labspath
+
+ def get_internal_path(self):
+ return self._path
+
+ def get_tpath(self):
+ return self._tpath
+
+ def get_path_elements(self):
+ return self._path_elements
+
def entry_abspath(self, name):
- return self.abspath + OS_SEP + name
+ return self._abspath + OS_SEP + name
def entry_labspath(self, name):
- return self.labspath + '/' + name
+ return self._labspath + '/' + name
def entry_path(self, name):
- return self.path + OS_SEP + name
+ return self._path + OS_SEP + name
def entry_tpath(self, name):
- return self.tpath + OS_SEP + name
+ return self._tpath + OS_SEP + name
def entry_exists_on_disk(self, name):
""" Searches through the file/dir entries of the current
@@ -1841,7 +1953,7 @@ class Dir(Base):
except AttributeError:
d = {}
try:
- entries = os.listdir(self.abspath)
+ entries = os.listdir(self._abspath)
except OSError:
pass
else:
@@ -1854,7 +1966,7 @@ class Dir(Base):
if result is None:
# Belt-and-suspenders for Windows: check directly for
# 8.3 file names that don't show up in os.listdir().
- result = os.path.exists(self.abspath + OS_SEP + name)
+ result = os.path.exists(self._abspath + OS_SEP + name)
d[name] = result
return result
else:
@@ -1887,8 +1999,7 @@ class Dir(Base):
break
return rentry_exists
- memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list'))
-
+ @SCons.Memoize.CountMethodCall
def srcdir_list(self):
try:
return self._memo['srcdir_list']
@@ -1929,8 +2040,7 @@ class Dir(Base):
def _srcdir_find_file_key(self, filename):
return filename
- memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key))
-
+ @SCons.Memoize.CountDictCall(_srcdir_find_file_key)
def srcdir_find_file(self, filename):
try:
memo_dict = self._memo['srcdir_find_file']
@@ -2072,7 +2182,12 @@ class Dir(Base):
r = [os.path.join(str(dir), x) for x in r]
result.extend(r)
if exclude:
- result = filter(lambda x: not any(fnmatch.fnmatch(str(x), e) for e in SCons.Util.flatten(exclude)), result)
+ excludes = []
+ excludeList = SCons.Util.flatten(exclude)
+ for x in excludeList:
+ r = self.glob(x, ondisk, source, strings)
+ excludes.extend(r)
+ result = filter(lambda x: not any(fnmatch.fnmatch(str(x), str(e)) for e in SCons.Util.flatten(excludes)), result)
return sorted(result, key=lambda a: str(a))
def _glob1(self, pattern, ondisk=True, source=False, strings=False):
@@ -2105,7 +2220,7 @@ class Dir(Base):
for name in node_names: selfEntry(name)
if ondisk:
try:
- disk_names = os.listdir(dir.abspath)
+ disk_names = os.listdir(dir._abspath)
except os.error:
continue
names.extend(disk_names)
@@ -2151,18 +2266,12 @@ class RootDir(Dir):
add a separator when creating the path names of entries within
this directory.
"""
+
+ __slots__ = ['_lookupDict']
+
def __init__(self, drive, fs):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.FS.RootDir')
- # We're going to be our own parent directory (".." entry and .dir
- # attribute) so we have to set up some values so Base.__init__()
- # won't gag won't it calls some of our methods.
- self.abspath = ''
- self.labspath = ''
- self.path = ''
- self.tpath = ''
- self.path_elements = []
- self.duplicate = 0
- self.root = self
+ SCons.Node.Node.__init__(self)
# Handle all the types of drives:
if drive == '':
@@ -2178,33 +2287,85 @@ class RootDir(Dir):
name = drive
dirname = drive + OS_SEP
- Base.__init__(self, name, self, fs)
+ #: Filename with extension as it was specified when the object was
+ #: created; to obtain filesystem path, use Python str() function
+ self.name = SCons.Util.silent_intern(name)
+ self.fs = fs #: Reference to parent Node.FS object
+
+ self._path_elements = [self]
+ self.dir = self
+ self._func_rexists = 2
+ self._func_target_from_source = 1
+ self.store_info = 1
# Now set our paths to what we really want them to be. The
# name should already contain any necessary separators, such
# as the initial drive letter (the name) plus the directory
# separator, except for the "lookup abspath," which does not
# have the drive letter.
- self.abspath = dirname
- self.labspath = ''
- self.path = dirname
- self.tpath = dirname
- self._morph()
-
- # Must be reset after Dir._morph() is invoked...
+ self._abspath = dirname
+ self._labspath = ''
+ self._path = dirname
+ self._tpath = dirname
self.dirname = dirname
+ self._morph()
+
+ self.duplicate = 0
self._lookupDict = {}
self._lookupDict[''] = self
self._lookupDict['/'] = self
-
+ self.root = self
# The // entry is necessary because os.path.normpath()
# preserves double slashes at the beginning of a path on Posix
# platforms.
if not has_unc:
self._lookupDict['//'] = self
+ def _morph(self):
+ """Turn a file system Node (either a freshly initialized directory
+ object or a separate Entry object) into a proper directory object.
+
+ Set up this directory's entries and hook it into the file
+ system tree. Specify that directories (this Node) don't use
+ signatures for calculating whether they're current.
+ """
+
+ self.repositories = []
+ self.srcdir = None
+
+ self.entries = {}
+ self.entries['.'] = self
+ self.entries['..'] = self.dir
+ self.cwd = self
+ self.searched = 0
+ self._sconsign = None
+ self.variant_dirs = []
+ self.changed_since_last_build = 3
+ self._func_sconsign = 1
+ self._func_exists = 2
+ self._func_get_contents = 2
+
+ # Don't just reset the executor, replace its action list,
+ # because it might have some pre-or post-actions that need to
+ # be preserved.
+ #
+ # But don't reset the executor if there is a non-null executor
+ # attached already. The existing executor might have other
+ # targets, in which case replacing the action list with a
+ # Mkdir action is a big mistake.
+ if not hasattr(self, 'executor'):
+ self.builder = get_MkdirBuilder()
+ self.get_executor().set_action_list(self.builder.action)
+ else:
+ # Prepend MkdirBuilder action to existing action list
+ l = self.get_executor().action_list
+ a = get_MkdirBuilder().action
+ l.insert(0, a)
+ self.get_executor().set_action_list(l)
+
+
def must_be_same(self, klass):
if klass is Dir:
return
@@ -2253,19 +2414,19 @@ class RootDir(Dir):
return result
def __str__(self):
- return self.abspath
+ return self._abspath
def entry_abspath(self, name):
- return self.abspath + name
+ return self._abspath + name
def entry_labspath(self, name):
return '/' + name
def entry_path(self, name):
- return self.path + name
+ return self._path + name
def entry_tpath(self, name):
- return self.tpath + name
+ return self._tpath + name
def is_under(self, dir):
if self is dir:
@@ -2283,7 +2444,8 @@ class RootDir(Dir):
return _null
class FileNodeInfo(SCons.Node.NodeInfoBase):
- current_version_id = 1
+ __slots__ = ('csig', 'timestamp', 'size')
+ current_version_id = 2
field_list = ['csig', 'timestamp', 'size']
@@ -2298,11 +2460,43 @@ class FileNodeInfo(SCons.Node.NodeInfoBase):
if drive:
root = self.fs.get_root(drive)
if not os.path.isabs(s):
- s = top.labspath + '/' + s
+ s = top.get_labspath() + '/' + s
return root._lookup_abs(s, Entry)
+ def __getstate__(self):
+ """
+ Return all fields that shall be pickled. Walk the slots in the class
+ hierarchy and add those to the state dictionary. If a '__dict__' slot is
+ available, copy all entries to the dictionary. Also include the version
+ id, which is fixed for all instances of a class.
+ """
+ state = getattr(self, '__dict__', {}).copy()
+ for obj in type(self).mro():
+ for name in getattr(obj,'__slots__',()):
+ if hasattr(self, name):
+ state[name] = getattr(self, name)
+
+ state['_version_id'] = self.current_version_id
+ try:
+ del state['__weakref__']
+ except KeyError:
+ pass
+
+ return state
+
+ def __setstate__(self, state):
+ """
+ Restore the attributes from a pickled state.
+ """
+ # TODO check or discard version
+ del state['_version_id']
+ for key, value in state.items():
+ if key not in ('__weakref__',):
+ setattr(self, key, value)
+
class FileBuildInfo(SCons.Node.BuildInfoBase):
- current_version_id = 1
+ __slots__ = ()
+ current_version_id = 2
def convert_to_sconsign(self):
"""
@@ -2317,7 +2511,7 @@ class FileBuildInfo(SCons.Node.BuildInfoBase):
else:
def node_to_str(n):
try:
- s = n.path
+ s = n.get_internal_path()
except AttributeError:
s = str(n)
else:
@@ -2358,6 +2552,8 @@ class FileBuildInfo(SCons.Node.BuildInfoBase):
nodeinfos = getattr(self, sattr)
except AttributeError:
continue
+ if strings is None or nodeinfos is None:
+ continue
nodes = []
for s, ni in zip(strings, nodeinfos):
if not isinstance(s, SCons.Node.Node):
@@ -2371,6 +2567,8 @@ class FileBuildInfo(SCons.Node.BuildInfoBase):
for bkid, bkidsig in zip(bkids, bkidsigs):
result.append(str(bkid) + ': ' +
' '.join(bkidsig.format(names=names)))
+ if not hasattr(self,'bact'):
+ self.bact = "none"
result.append('%s [%s]' % (self.bactsig, self.bact))
return '\n'.join(result)
@@ -2378,7 +2576,22 @@ class File(Base):
"""A class for files in a file system.
"""
- memoizer_counters = []
+ __slots__ = ['scanner_paths',
+ 'cachedir_csig',
+ 'cachesig',
+ 'repositories',
+ 'srcdir',
+ 'entries',
+ 'searched',
+ '_sconsign',
+ 'variant_dirs',
+ 'root',
+ 'dirname',
+ 'on_disk_entries',
+ 'sccs_dir',
+ 'rcs_dir',
+ 'released_target_info',
+ 'contentsig']
NodeInfo = FileNodeInfo
BuildInfo = FileBuildInfo
@@ -2429,6 +2642,14 @@ class File(Base):
if not hasattr(self, 'released_target_info'):
self.released_target_info = False
+ self.store_info = 1
+ self._func_exists = 4
+ self._func_get_contents = 3
+
+ # Initialize this Node's decider function to decide_source() because
+ # every file is a source file until it has a Builder attached...
+ self.changed_since_last_build = 4
+
# If there was already a Builder set on this entry, then
# we need to make sure we call the target-decider function,
# not the source-decider. Reaching in and doing this by hand
@@ -2440,22 +2661,13 @@ class File(Base):
# not clear right now how to fix that, stick with what works
# until it becomes clear...
if self.has_builder():
- self.changed_since_last_build = self.decide_target
+ self.changed_since_last_build = 5
def scanner_key(self):
return self.get_suffix()
def get_contents(self):
- if not self.rexists():
- return ''
- fname = self.rfile().abspath
- try:
- contents = open(fname, "rb").read()
- except EnvironmentError, e:
- if not e.filename:
- e.filename = fname
- raise
- return contents
+ return SCons.Node._get_contents_map[self._func_get_contents](self)
# This attempts to figure out what the encoding of the text is
# based upon the BOM bytes, and then decodes the contents so that
@@ -2482,7 +2694,7 @@ class File(Base):
"""
if not self.rexists():
return SCons.Util.MD5signature('')
- fname = self.rfile().abspath
+ fname = self.rfile().get_abspath()
try:
cs = SCons.Util.MD5filesignature(fname,
chunksize=SCons.Node.FS.File.md5_chunksize*1024)
@@ -2492,9 +2704,7 @@ class File(Base):
raise
return cs
-
- memoizer_counters.append(SCons.Memoize.CountValue('get_size'))
-
+ @SCons.Memoize.CountMethodCall
def get_size(self):
try:
return self._memo['get_size']
@@ -2510,8 +2720,7 @@ class File(Base):
return size
- memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp'))
-
+ @SCons.Memoize.CountMethodCall
def get_timestamp(self):
try:
return self._memo['get_timestamp']
@@ -2527,14 +2736,6 @@ class File(Base):
return timestamp
- def store_info(self):
- # Merge our build information into the already-stored entry.
- # This accomodates "chained builds" where a file that's a target
- # in one build (SConstruct file) is a source in a different build.
- # See test/chained-build.py for the use case.
- if do_store_info:
- self.dir.sconsign().store_info(self.name, self)
-
convert_copy_attrs = [
'bsources',
'bimplicit',
@@ -2647,8 +2848,7 @@ class File(Base):
delattr(old_entry, attr)
return new_entry
- memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info'))
-
+ @SCons.Memoize.CountMethodCall
def get_stored_info(self):
try:
return self._memo['get_stored_info']
@@ -2688,8 +2888,7 @@ class File(Base):
def _get_found_includes_key(self, env, scanner, path):
return (id(env), id(scanner), path)
- memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key))
-
+ @SCons.Memoize.CountDictCall(_get_found_includes_key)
def get_found_includes(self, env, scanner, path):
"""Return the included implicit dependencies in this file.
Cache results so we only scan the file once per path
@@ -2773,9 +2972,9 @@ class File(Base):
# any build information that's stored in the .sconsign file
# into our binfo object so it doesn't get lost.
old = self.get_stored_info()
- self.get_binfo().__dict__.update(old.binfo.__dict__)
+ self.get_binfo().merge(old.binfo)
- self.store_info()
+ SCons.Node.store_info_map[self.store_info](self)
def release_target_info(self):
"""Called just after this node has been marked
@@ -2875,7 +3074,7 @@ class File(Base):
def _rmv_existing(self):
self.clear_memoized_values()
- if print_duplicate:
+ if SCons.Node.print_duplicate:
print "dup: removing existing target %s"%self
e = Unlink(self, [], None)
if isinstance(e, SCons.Errors.BuildError):
@@ -2911,18 +3110,18 @@ class File(Base):
def remove(self):
"""Remove this file."""
if self.exists() or self.islink():
- self.fs.unlink(self.path)
+ self.fs.unlink(self.get_internal_path())
return 1
return None
def do_duplicate(self, src):
self._createDir()
- if print_duplicate:
+ if SCons.Node.print_duplicate:
print "dup: relinking variant '%s' from '%s'"%(self, src)
Unlink(self, None, None)
e = Link(self, src, None)
if isinstance(e, SCons.Errors.BuildError):
- desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr)
+ desc = "Cannot duplicate `%s' in `%s': %s." % (src.get_internal_path(), self.dir._path, e.errstr)
raise SCons.Errors.StopError(desc)
self.linked = 1
# The Link() action may or may not have actually
@@ -2931,36 +3130,14 @@ class File(Base):
# _rexists attributes so they can be reevaluated.
self.clear()
- memoizer_counters.append(SCons.Memoize.CountValue('exists'))
-
+ @SCons.Memoize.CountMethodCall
def exists(self):
try:
return self._memo['exists']
except KeyError:
pass
- # Duplicate from source path if we are set up to do this.
- if self.duplicate and not self.is_derived() and not self.linked:
- src = self.srcnode()
- if src is not self:
- # At this point, src is meant to be copied in a variant directory.
- src = src.rfile()
- if src.abspath != self.abspath:
- if src.exists():
- self.do_duplicate(src)
- # Can't return 1 here because the duplication might
- # not actually occur if the -n option is being used.
- else:
- # The source file does not exist. Make sure no old
- # copy remains in the variant directory.
- if print_duplicate:
- print "dup: no src for %s, unlinking old variant copy"%self
- if Base.exists(self) or self.islink():
- self.fs.unlink(self.path)
- # Return None explicitly because the Base.exists() call
- # above will have cached its value if the file existed.
- self._memo['exists'] = None
- return None
- result = Base.exists(self)
+
+ result = SCons.Node._exists_map[self._func_exists](self)
self._memo['exists'] = result
return result
@@ -3037,7 +3214,7 @@ class File(Base):
def builder_set(self, builder):
SCons.Node.Node.builder_set(self, builder)
- self.changed_since_last_build = self.decide_target
+ self.changed_since_last_build = 5
def built(self):
"""Called just after this File node is successfully built.
@@ -3054,10 +3231,10 @@ class File(Base):
if (not SCons.Node.interactive and
not hasattr(self.attributes, 'keep_targetinfo')):
# Ensure that the build infos get computed and cached...
- self.store_info()
+ SCons.Node.store_info_map[self.store_info](self)
# ... then release some more variables.
self._specific_sources = False
- self.labspath = None
+ self._labspath = None
self._save_str()
self.cwd = None
@@ -3116,16 +3293,6 @@ class File(Base):
except AttributeError:
return 1
- def decide_source(self, target, prev_ni):
- return target.get_build_env().decide_source(self, target, prev_ni)
-
- def decide_target(self, target, prev_ni):
- return target.get_build_env().decide_target(self, target, prev_ni)
-
- # Initialize this Node's decider function to decide_source() because
- # every file is a source file until it has a Builder attached...
- changed_since_last_build = decide_source
-
def is_up_to_date(self):
T = 0
if T: Trace('is_up_to_date(%s):' % self)
@@ -3143,7 +3310,7 @@ class File(Base):
e = LocalCopy(self, r, None)
if isinstance(e, SCons.Errors.BuildError):
raise
- self.store_info()
+ SCons.Node.store_info_map[self.store_info](self)
if T: Trace(' 1\n')
return 1
self.changed()
@@ -3154,8 +3321,7 @@ class File(Base):
if T: Trace(' self.exists(): %s\n' % r)
return not r
- memoizer_counters.append(SCons.Memoize.CountValue('rfile'))
-
+ @SCons.Memoize.CountMethodCall
def rfile(self):
try:
return self._memo['rfile']
@@ -3257,7 +3423,7 @@ class File(Base):
# Append this node's signature...
sigs.append(self.get_contents_sig())
# ...and it's path
- sigs.append(self.path)
+ sigs.append(self.get_internal_path())
# Merge this all into a single signature
result = self.cachesig = SCons.Util.MD5collect(sigs)
return result
@@ -3273,10 +3439,6 @@ def get_default_fs():
class FileFinder(object):
"""
"""
- if SCons.Memoize.use_memoizer:
- __metaclass__ = SCons.Memoize.Memoized_Metaclass
-
- memoizer_counters = []
def __init__(self):
self._memo = {}
@@ -3319,8 +3481,7 @@ class FileFinder(object):
def _find_file_key(self, filename, paths, verbose=None):
return (filename, paths)
- memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key))
-
+ @SCons.Memoize.CountDictCall(_find_file_key)
def find_file(self, filename, paths, verbose=None):
"""
find_file(str, [Dir()]) -> [nodes]
diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py
index 0326717..0c1e71f 100644
--- a/src/engine/SCons/Node/FSTests.py
+++ b/src/engine/SCons/Node/FSTests.py
@@ -130,34 +130,34 @@ class VariantDirTestCase(unittest.TestCase):
fs.VariantDir('build', 'src')
f2 = fs.File('build/test2')
d1 = fs.Dir('build')
- assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
- assert f2.srcnode().path == os.path.normpath('src/test2'), f2.srcnode().path
- assert d1.srcnode().path == 'src', d1.srcnode().path
+ assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path()
+ assert f2.srcnode().get_internal_path() == os.path.normpath('src/test2'), f2.srcnode().get_internal_path()
+ assert d1.srcnode().get_internal_path() == 'src', d1.srcnode().get_internal_path()
fs = SCons.Node.FS.FS()
f1 = fs.File('build/test1')
fs.VariantDir('build', '.')
f2 = fs.File('build/test2')
d1 = fs.Dir('build')
- assert f1.srcnode().path == 'test1', f1.srcnode().path
- assert f2.srcnode().path == 'test2', f2.srcnode().path
- assert d1.srcnode().path == '.', d1.srcnode().path
+ assert f1.srcnode().get_internal_path() == 'test1', f1.srcnode().get_internal_path()
+ assert f2.srcnode().get_internal_path() == 'test2', f2.srcnode().get_internal_path()
+ assert d1.srcnode().get_internal_path() == '.', d1.srcnode().get_internal_path()
fs = SCons.Node.FS.FS()
fs.VariantDir('build/var1', 'src')
fs.VariantDir('build/var2', 'src')
f1 = fs.File('build/var1/test1')
f2 = fs.File('build/var2/test1')
- assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
- assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path
+ assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path()
+ assert f2.srcnode().get_internal_path() == os.path.normpath('src/test1'), f2.srcnode().get_internal_path()
fs = SCons.Node.FS.FS()
fs.VariantDir('../var1', 'src')
fs.VariantDir('../var2', 'src')
f1 = fs.File('../var1/test1')
f2 = fs.File('../var2/test1')
- assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path
- assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path
+ assert f1.srcnode().get_internal_path() == os.path.normpath('src/test1'), f1.srcnode().get_internal_path()
+ assert f2.srcnode().get_internal_path() == os.path.normpath('src/test1'), f2.srcnode().get_internal_path()
# Set up some files
test.subdir('work', ['work', 'src'])
@@ -210,8 +210,8 @@ class VariantDirTestCase(unittest.TestCase):
f2out_2.builder = 1
fs.Repository(test.workpath('rep1'))
- assert f1.srcnode().path == os.path.normpath('src/test.in'),\
- f1.srcnode().path
+ assert f1.srcnode().get_internal_path() == os.path.normpath('src/test.in'),\
+ f1.srcnode().get_internal_path()
# str(node) returns source path for duplicate = 0
assert str(f1) == os.path.normpath('src/test.in'), str(f1)
# Build path does not exist
@@ -221,11 +221,11 @@ class VariantDirTestCase(unittest.TestCase):
# And duplicate=0 should also work just like a Repository
assert f1.rexists()
# rfile() should point to the source path
- assert f1.rfile().path == os.path.normpath('src/test.in'),\
- f1.rfile().path
+ assert f1.rfile().get_internal_path() == os.path.normpath('src/test.in'),\
+ f1.rfile().get_internal_path()
- assert f2.srcnode().path == os.path.normpath('src/test.in'),\
- f2.srcnode().path
+ assert f2.srcnode().get_internal_path() == os.path.normpath('src/test.in'),\
+ f2.srcnode().get_internal_path()
# str(node) returns build path for duplicate = 1
assert str(f2) == os.path.normpath('build/var2/test.in'), str(f2)
# Build path exists
@@ -239,8 +239,8 @@ class VariantDirTestCase(unittest.TestCase):
f3 = fs.File('build/var1/test2.in')
f4 = fs.File('build/var2/test2.in')
- assert f3.srcnode().path == os.path.normpath('src/test2.in'),\
- f3.srcnode().path
+ assert f3.srcnode().get_internal_path() == os.path.normpath('src/test2.in'),\
+ f3.srcnode().get_internal_path()
# str(node) returns source path for duplicate = 0
assert str(f3) == os.path.normpath('src/test2.in'), str(f3)
# Build path does not exist
@@ -250,11 +250,11 @@ class VariantDirTestCase(unittest.TestCase):
# But we do have a file in the Repository
assert f3.rexists()
# rfile() should point to the source path
- assert f3.rfile().path == os.path.normpath(test.workpath('rep1/src/test2.in')),\
- f3.rfile().path
+ assert f3.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/src/test2.in')),\
+ f3.rfile().get_internal_path()
- assert f4.srcnode().path == os.path.normpath('src/test2.in'),\
- f4.srcnode().path
+ assert f4.srcnode().get_internal_path() == os.path.normpath('src/test2.in'),\
+ f4.srcnode().get_internal_path()
# str(node) returns build path for duplicate = 1
assert str(f4) == os.path.normpath('build/var2/test2.in'), str(f4)
# Build path should exist
@@ -264,8 +264,8 @@ class VariantDirTestCase(unittest.TestCase):
# should exist in repository, since exists() is true
assert f4.rexists()
# rfile() should point to ourselves
- assert f4.rfile().path == os.path.normpath('build/var2/test2.in'),\
- f4.rfile().path
+ assert f4.rfile().get_internal_path() == os.path.normpath('build/var2/test2.in'),\
+ f4.rfile().get_internal_path()
f5 = fs.File('build/var1/test.out')
f6 = fs.File('build/var2/test.out')
@@ -285,14 +285,14 @@ class VariantDirTestCase(unittest.TestCase):
assert not f7.exists()
assert f7.rexists()
- r = f7.rfile().path
+ r = f7.rfile().get_internal_path()
expect = os.path.normpath(test.workpath('rep1/build/var1/test2.out'))
assert r == expect, (repr(r), repr(expect))
assert not f8.exists()
assert f8.rexists()
- assert f8.rfile().path == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\
- f8.rfile().path
+ assert f8.rfile().get_internal_path() == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\
+ f8.rfile().get_internal_path()
# Verify the Mkdir and Link actions are called
d9 = fs.Dir('build/var2/new_dir')
@@ -319,9 +319,9 @@ class VariantDirTestCase(unittest.TestCase):
d9.reset_executor()
f9.exists()
expect = os.path.join('build', 'var2', 'new_dir')
- assert dir_made[0].path == expect, dir_made[0].path
+ assert dir_made[0].get_internal_path() == expect, dir_made[0].get_internal_path()
expect = os.path.join('build', 'var2', 'new_dir', 'test9.out')
- assert link_made[0].path == expect, link_made[0].path
+ assert link_made[0].get_internal_path() == expect, link_made[0].get_internal_path()
assert f9.linked
finally:
SCons.Node.FS.Link = save_Link
@@ -338,7 +338,7 @@ class VariantDirTestCase(unittest.TestCase):
f11 = fs.File('src/file11')
t, m = f11.alter_targets()
- bdt = [n.path for n in t]
+ bdt = [n.get_internal_path() for n in t]
var1_file11 = os.path.normpath('build/var1/file11')
var2_file11 = os.path.normpath('build/var2/file11')
assert bdt == [var1_file11, var2_file11], bdt
@@ -346,11 +346,11 @@ class VariantDirTestCase(unittest.TestCase):
f12 = fs.File('src/file12')
f12.builder = 1
bdt, m = f12.alter_targets()
- assert bdt == [], [n.path for n in bdt]
+ assert bdt == [], [n.get_internal_path() for n in bdt]
d13 = fs.Dir('src/new_dir')
t, m = d13.alter_targets()
- bdt = [n.path for n in t]
+ bdt = [n.get_internal_path() for n in t]
var1_new_dir = os.path.normpath('build/var1/new_dir')
var2_new_dir = os.path.normpath('build/var2/new_dir')
assert bdt == [var1_new_dir, var2_new_dir], bdt
@@ -566,13 +566,13 @@ class VariantDirTestCase(unittest.TestCase):
f = dir + '/f'
fnode = fs.File(dir + '/f')
- dp = dnode.srcnode().path
+ dp = dnode.srcnode().get_internal_path()
expect = os.path.normpath(srcnode_map.get(dir, dir))
if dp != expect:
print "Dir `%s' srcnode() `%s' != expected `%s'" % (dir, dp, expect)
errors = errors + 1
- fp = fnode.srcnode().path
+ fp = fnode.srcnode().get_internal_path()
expect = os.path.normpath(srcnode_map.get(f, f))
if fp != expect:
print "File `%s' srcnode() `%s' != expected `%s'" % (f, fp, expect)
@@ -584,14 +584,14 @@ class VariantDirTestCase(unittest.TestCase):
fnode = fs.File(dir + '/f')
t, m = dnode.alter_targets()
- tp = t[0].path
+ tp = t[0].get_internal_path()
expect = os.path.normpath(alter_map.get(dir, dir))
if tp != expect:
print "Dir `%s' alter_targets() `%s' != expected `%s'" % (dir, tp, expect)
errors = errors + 1
t, m = fnode.alter_targets()
- tp = t[0].path
+ tp = t[0].get_internal_path()
expect = os.path.normpath(alter_map.get(f, f))
if tp != expect:
print "File `%s' alter_targets() `%s' != expected `%s'" % (f, tp, expect)
@@ -698,19 +698,19 @@ class DirNodeInfoTestCase(_tempdirTestCase):
def test___init__(self):
"""Test DirNodeInfo initialization"""
ddd = self.fs.Dir('ddd')
- ni = SCons.Node.FS.DirNodeInfo(ddd)
+ ni = SCons.Node.FS.DirNodeInfo()
class DirBuildInfoTestCase(_tempdirTestCase):
def test___init__(self):
"""Test DirBuildInfo initialization"""
ddd = self.fs.Dir('ddd')
- bi = SCons.Node.FS.DirBuildInfo(ddd)
+ bi = SCons.Node.FS.DirBuildInfo()
class FileNodeInfoTestCase(_tempdirTestCase):
def test___init__(self):
"""Test FileNodeInfo initialization"""
fff = self.fs.File('fff')
- ni = SCons.Node.FS.FileNodeInfo(fff)
+ ni = SCons.Node.FS.FileNodeInfo()
assert isinstance(ni, SCons.Node.FS.FileNodeInfo)
def test_update(self):
@@ -718,7 +718,7 @@ class FileNodeInfoTestCase(_tempdirTestCase):
test = self.test
fff = self.fs.File('fff')
- ni = SCons.Node.FS.FileNodeInfo(fff)
+ ni = SCons.Node.FS.FileNodeInfo()
test.write('fff', "fff\n")
@@ -765,37 +765,41 @@ class FileBuildInfoTestCase(_tempdirTestCase):
def test___init__(self):
"""Test File.BuildInfo initialization"""
fff = self.fs.File('fff')
- bi = SCons.Node.FS.FileBuildInfo(fff)
+ bi = SCons.Node.FS.FileBuildInfo()
assert bi, bi
def test_convert_to_sconsign(self):
"""Test converting to .sconsign file format"""
fff = self.fs.File('fff')
- bi = SCons.Node.FS.FileBuildInfo(fff)
+ bi = SCons.Node.FS.FileBuildInfo()
assert hasattr(bi, 'convert_to_sconsign')
def test_convert_from_sconsign(self):
"""Test converting from .sconsign file format"""
fff = self.fs.File('fff')
- bi = SCons.Node.FS.FileBuildInfo(fff)
+ bi = SCons.Node.FS.FileBuildInfo()
assert hasattr(bi, 'convert_from_sconsign')
def test_prepare_dependencies(self):
"""Test that we have a prepare_dependencies() method"""
fff = self.fs.File('fff')
- bi = SCons.Node.FS.FileBuildInfo(fff)
+ bi = SCons.Node.FS.FileBuildInfo()
bi.prepare_dependencies()
def test_format(self):
"""Test the format() method"""
f1 = self.fs.File('f1')
- bi1 = SCons.Node.FS.FileBuildInfo(f1)
+ bi1 = SCons.Node.FS.FileBuildInfo()
- s1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n1'))
+ self.fs.File('n1')
+ self.fs.File('n2')
+ self.fs.File('n3')
+
+ s1sig = SCons.Node.FS.FileNodeInfo()
s1sig.csig = 1
- d1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n2'))
+ d1sig = SCons.Node.FS.FileNodeInfo()
d1sig.timestamp = 2
- i1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n3'))
+ i1sig = SCons.Node.FS.FileNodeInfo()
i1sig.size = 3
bi1.bsources = [self.fs.File('s1')]
@@ -953,7 +957,7 @@ class FSTestCase(_tempdirTestCase):
assert isinstance(f1, SCons.Node.FS.File)
d1_f1 = os.path.join('d1', 'f1')
- assert f1.path == d1_f1, "f1.path %s != %s" % (f1.path, d1_f1)
+ assert f1.get_internal_path() == d1_f1, "f1.path %s != %s" % (f1.get_internal_path(), d1_f1)
assert str(f1) == d1_f1, "str(f1) %s != %s" % (str(f1), d1_f1)
x1 = d1.File('x1')
@@ -1045,16 +1049,16 @@ class FSTestCase(_tempdirTestCase):
name = os.sep
if dir.up() is None:
- dir_up_path = dir.path
+ dir_up_path = dir.get_internal_path()
else:
- dir_up_path = dir.up().path
+ dir_up_path = dir.up().get_internal_path()
assert dir.name == name, \
"dir.name %s != expected name %s" % \
(dir.name, name)
- assert dir.path == path, \
+ assert dir.get_internal_path() == path, \
"dir.path %s != expected path %s" % \
- (dir.path, path)
+ (dir.get_internal_path(), path)
assert str(dir) == path, \
"str(dir) %s != expected path %s" % \
(str(dir), path)
@@ -1136,9 +1140,9 @@ class FSTestCase(_tempdirTestCase):
# Test for a bug in 0.04 that did not like looking up
# dirs with a trailing slash on Windows.
d=fs.Dir('./')
- assert d.path == '.', d.abspath
+ assert d.get_internal_path() == '.', d.get_abspath()
d=fs.Dir('foo/')
- assert d.path == 'foo', d.abspath
+ assert d.get_internal_path() == 'foo', d.get_abspath()
# Test for sub-classing of node building.
global built_it
@@ -1167,50 +1171,50 @@ class FSTestCase(_tempdirTestCase):
e1 = fs.Entry("d1")
assert e1.__class__.__name__ == 'Dir'
- match(e1.path, "d1")
- match(e1.dir.path, ".")
+ match(e1.get_internal_path(), "d1")
+ match(e1.dir.get_internal_path(), ".")
e2 = fs.Entry("d1/f1")
assert e2.__class__.__name__ == 'File'
- match(e2.path, "d1/f1")
- match(e2.dir.path, "d1")
+ match(e2.get_internal_path(), "d1/f1")
+ match(e2.dir.get_internal_path(), "d1")
e3 = fs.Entry("e3")
assert e3.__class__.__name__ == 'Entry'
- match(e3.path, "e3")
- match(e3.dir.path, ".")
+ match(e3.get_internal_path(), "e3")
+ match(e3.dir.get_internal_path(), ".")
e4 = fs.Entry("d1/e4")
assert e4.__class__.__name__ == 'Entry'
- match(e4.path, "d1/e4")
- match(e4.dir.path, "d1")
+ match(e4.get_internal_path(), "d1/e4")
+ match(e4.dir.get_internal_path(), "d1")
e5 = fs.Entry("e3/e5")
assert e3.__class__.__name__ == 'Dir'
- match(e3.path, "e3")
- match(e3.dir.path, ".")
+ match(e3.get_internal_path(), "e3")
+ match(e3.dir.get_internal_path(), ".")
assert e5.__class__.__name__ == 'Entry'
- match(e5.path, "e3/e5")
- match(e5.dir.path, "e3")
+ match(e5.get_internal_path(), "e3/e5")
+ match(e5.dir.get_internal_path(), "e3")
e6 = fs.Dir("d1/e4")
assert e6 is e4
assert e4.__class__.__name__ == 'Dir'
- match(e4.path, "d1/e4")
- match(e4.dir.path, "d1")
+ match(e4.get_internal_path(), "d1/e4")
+ match(e4.dir.get_internal_path(), "d1")
e7 = fs.File("e3/e5")
assert e7 is e5
assert e5.__class__.__name__ == 'File'
- match(e5.path, "e3/e5")
- match(e5.dir.path, "e3")
+ match(e5.get_internal_path(), "e3/e5")
+ match(e5.dir.get_internal_path(), "e3")
fs.chdir(fs.Dir('subdir'))
f11 = fs.File("f11")
- match(f11.path, "subdir/f11")
+ match(f11.get_internal_path(), "subdir/f11")
d12 = fs.Dir("d12")
e13 = fs.Entry("subdir/e13")
- match(e13.path, "subdir/subdir/e13")
+ match(e13.get_internal_path(), "subdir/subdir/e13")
fs.chdir(fs.Dir('..'))
# Test scanning
@@ -1220,13 +1224,13 @@ class FSTestCase(_tempdirTestCase):
f1.builder.target_scanner = Scanner(xyz)
f1.scan()
- assert f1.implicit[0].path == "xyz"
+ assert f1.implicit[0].get_internal_path() == "xyz"
f1.implicit = []
f1.scan()
assert f1.implicit == []
f1.implicit = None
f1.scan()
- assert f1.implicit[0].path == "xyz"
+ assert f1.implicit[0].get_internal_path() == "xyz"
# Test underlying scanning functionality in get_found_includes()
env = Environment()
@@ -1284,9 +1288,9 @@ class FSTestCase(_tempdirTestCase):
fs.chdir(fs.Dir('subdir'))
# The cwd's path is always "."
assert str(fs.getcwd()) == ".", str(fs.getcwd())
- assert fs.getcwd().path == 'subdir', fs.getcwd().path
+ assert fs.getcwd().get_internal_path() == 'subdir', fs.getcwd().get_internal_path()
fs.chdir(fs.Dir('../..'))
- assert fs.getcwd().path == test.workdir, fs.getcwd().path
+ assert fs.getcwd().get_internal_path() == test.workdir, fs.getcwd().get_internal_path()
f1 = fs.File(test.workpath("do_i_exist"))
assert not f1.exists()
@@ -1589,15 +1593,15 @@ class FSTestCase(_tempdirTestCase):
assert dir.name == name, \
"dir.name %s != expected name %s" % \
(dir.name, name)
- assert dir.path == path, \
+ assert dir.get_internal_path() == path, \
"dir.path %s != expected path %s" % \
- (dir.path, path)
+ (dir.get_internal_path(), path)
assert str(dir) == path, \
"str(dir) %s != expected path %s" % \
(str(dir), path)
- assert dir.up().path == up_path, \
+ assert dir.up().get_internal_path() == up_path, \
"dir.up().path %s != expected parent path %s" % \
- (dir.up().path, up_path)
+ (dir.up().get_internal_path(), up_path)
save_os_path = os.path
save_os_sep = os.sep
@@ -1691,22 +1695,22 @@ class FSTestCase(_tempdirTestCase):
name = path.split(os.sep)[-1]
if dir.up() is None:
- dir_up_path = dir.path
+ dir_up_path = dir.get_internal_path()
else:
- dir_up_path = dir.up().path
+ dir_up_path = dir.up().get_internal_path()
assert dir.name == name, \
"dir.name %s != expected name %s" % \
(dir.name, name)
- assert dir.path == path, \
+ assert dir.get_internal_path() == path, \
"dir.path %s != expected path %s" % \
- (dir.path, path)
+ (dir.get_internal_path(), path)
assert str(dir) == path, \
"str(dir) %s != expected path %s" % \
(str(dir), path)
assert dir_up_path == up_path, \
"dir.up().path %s != expected parent path %s" % \
- (dir.up().path, up_path)
+ (dir.up().get_internal_path(), up_path)
save_os_path = os.path
save_os_sep = os.sep
@@ -1792,7 +1796,7 @@ class FSTestCase(_tempdirTestCase):
d1 = fs.Dir('d1')
d2 = d1.Dir('d2')
- dirs = os.path.normpath(d2.abspath).split(os.sep)
+ dirs = os.path.normpath(d2.get_abspath()).split(os.sep)
above_path = os.path.join(*['..']*len(dirs) + ['above'])
above = d2.Dir(above_path)
@@ -1831,9 +1835,9 @@ class FSTestCase(_tempdirTestCase):
fs = self.fs
if sys.platform not in ('win32',):
return
- p = fs.Dir(r"\\computername\sharename").abspath
+ p = fs.Dir(r"\\computername\sharename").get_abspath()
assert p == r"\\computername\sharename", p
- p = fs.Dir(r"\\\computername\sharename").abspath
+ p = fs.Dir(r"\\\computername\sharename").get_abspath()
assert p == r"\\computername\sharename", p
def test_rel_path(self):
@@ -1980,7 +1984,7 @@ class DirTestCase(_tempdirTestCase):
fs.Dir(os.path.join('ddd', 'd1', 'f4'))
fs.Dir(os.path.join('ddd', 'd1', 'f5'))
dir.scan()
- kids = sorted([x.path for x in dir.children(None)])
+ kids = sorted([x.get_internal_path() for x in dir.children(None)])
assert kids == [os.path.join('ddd', 'd1'),
os.path.join('ddd', 'f1'),
os.path.join('ddd', 'f2'),
@@ -2024,12 +2028,12 @@ class DirTestCase(_tempdirTestCase):
fs.File(os.path.join('ddd', 'f1'))
dir.scan()
- kids = sorted([x.path for x in dir.children()])
+ kids = sorted([x.get_internal_path() for x in dir.children()])
assert kids == [os.path.join('ddd', 'f1')], kids
fs.File(os.path.join('ddd', 'f2'))
dir.scan()
- kids = sorted([x.path for x in dir.children()])
+ kids = sorted([x.get_internal_path() for x in dir.children()])
assert kids == [os.path.join('ddd', 'f1'),
os.path.join('ddd', 'f2')], kids
@@ -2171,8 +2175,12 @@ class DirTestCase(_tempdirTestCase):
"""
test = self.test
- return_true = lambda: 1
+ def return_true(node):
+ return 1
+ SCons.Node._is_derived_map[2] = return_true
+ SCons.Node._exists_map[5] = return_true
+
test.subdir('src0')
test.write(['src0', 'on-disk-f1'], "src0/on-disk-f1\n")
test.write(['src0', 'on-disk-f2'], "src0/on-disk-f2\n")
@@ -2184,14 +2192,14 @@ class DirTestCase(_tempdirTestCase):
self.fs.VariantDir(bld0, src0, duplicate=0)
derived_f = src0.File('derived-f')
- derived_f.is_derived = return_true
+ derived_f._func_is_derived = 2
exists_f = src0.File('exists-f')
- exists_f.exists = return_true
+ exists_f._func_exists = 5
derived_e = src0.Entry('derived-e')
- derived_e.is_derived = return_true
+ derived_e._func_is_derived = 2
exists_e = src0.Entry('exists-e')
- exists_e.exists = return_true
+ exists_e._func_exists = 5
def check(result, expect):
result = list(map(str, result))
@@ -2245,14 +2253,14 @@ class DirTestCase(_tempdirTestCase):
self.fs.VariantDir(bld1, src1, duplicate=1)
derived_f = src1.File('derived-f')
- derived_f.is_derived = return_true
+ derived_f._func_is_derived = 2
exists_f = src1.File('exists-f')
- exists_f.exists = return_true
+ exists_f._func_exists = 5
derived_e = src1.Entry('derived-e')
- derived_e.is_derived = return_true
+ derived_e._func_is_derived = 2
exists_e = src1.Entry('exists-e')
- exists_e.exists = return_true
+ exists_e._func_exists = 5
# First check from the source directory.
n = src1.srcdir_find_file('does_not_exist')
@@ -2432,7 +2440,7 @@ class FileTestCase(_tempdirTestCase):
build_f1 = fs.File('build/f1')
assert build_f1.exists(), "%s did not realize that %s exists" % (build_f1, src_f1)
- assert os.path.exists(build_f1.abspath), "%s did not get duplicated on disk" % build_f1.abspath
+ assert os.path.exists(build_f1.get_abspath()), "%s did not get duplicated on disk" % build_f1.get_abspath()
test.unlink(['src', 'f1'])
src_f1.clear() # so the next exists() call will look on disk again
@@ -2441,7 +2449,7 @@ class FileTestCase(_tempdirTestCase):
build_f1.clear()
build_f1.linked = None
assert not build_f1.exists(), "%s did not realize that %s disappeared" % (build_f1, src_f1)
- assert not os.path.exists(build_f1.abspath), "%s did not get removed after %s was removed" % (build_f1, src_f1)
+ assert not os.path.exists(build_f1.get_abspath()), "%s did not get removed after %s was removed" % (build_f1, src_f1)
@@ -2535,7 +2543,7 @@ class GlobTestCase(_tempdirTestCase):
for input, string_expect, node_expect in cases:
r = self.fs.Glob(input, **kwargs)
if node_expect:
- r = sorted(r, key=lambda a: a.path)
+ r = sorted(r, key=lambda a: a.get_internal_path())
result = []
for n in node_expect:
if isinstance(n, str):
@@ -2906,8 +2914,14 @@ class RepositoryTestCase(_tempdirTestCase):
def test_rdir(self):
"""Test the Dir.rdir() method"""
- return_true = lambda: 1
- return_false = lambda: 0
+ def return_true(obj):
+ return 1
+ def return_false(obj):
+ return 0
+ SCons.Node._exists_map[5] = return_true
+ SCons.Node._exists_map[6] = return_false
+ SCons.Node._is_derived_map[2] = return_true
+ SCons.Node._is_derived_map[3] = return_false
d1 = self.fs.Dir('d1')
d2 = self.fs.Dir('d2')
@@ -2931,19 +2945,19 @@ class RepositoryTestCase(_tempdirTestCase):
assert r == os.path.join(self.rep3, 'd3'), r
e1 = self.fs.Dir('e1')
- e1.exists = return_false
+ e1._func_exists = 6
e2 = self.fs.Dir('e2')
- e2.exists = return_false
+ e2._func_exists = 6
# Make sure we match entries in repositories,
# regardless of whether they're derived or not.
re1 = self.fs.Entry(os.path.join(self.rep1, 'e1'))
- re1.exists = return_true
- re1.is_derived = return_true
+ re1._func_exists = 5
+ re1._func_is_derived = 2
re2 = self.fs.Entry(os.path.join(self.rep2, 'e2'))
- re2.exists = return_true
- re2.is_derived = return_false
+ re2._func_exists = 5
+ re2._func_is_derived = 3
r = e1.rdir()
assert r is re1, r
@@ -2953,8 +2967,14 @@ class RepositoryTestCase(_tempdirTestCase):
def test_rfile(self):
"""Test the File.rfile() method"""
- return_true = lambda: 1
- return_false = lambda: 0
+ def return_true(obj):
+ return 1
+ def return_false(obj):
+ return 0
+ SCons.Node._exists_map[5] = return_true
+ SCons.Node._exists_map[6] = return_false
+ SCons.Node._is_derived_map[2] = return_true
+ SCons.Node._is_derived_map[3] = return_false
f1 = self.fs.File('f1')
f2 = self.fs.File('f2')
@@ -2978,19 +2998,19 @@ class RepositoryTestCase(_tempdirTestCase):
assert r == os.path.join(self.rep3, 'f3'), r
e1 = self.fs.File('e1')
- e1.exists = return_false
+ e1._func_exists = 6
e2 = self.fs.File('e2')
- e2.exists = return_false
+ e2._func_exists = 6
# Make sure we match entries in repositories,
# regardless of whether they're derived or not.
re1 = self.fs.Entry(os.path.join(self.rep1, 'e1'))
- re1.exists = return_true
- re1.is_derived = return_true
+ re1._func_exists = 5
+ re1._func_is_derived = 2
re2 = self.fs.Entry(os.path.join(self.rep2, 'e2'))
- re2.exists = return_true
- re2.is_derived = return_false
+ re2._func_exists = 5
+ re2._func_is_derived = 3
r = e1.rfile()
assert r is re1, r
@@ -3244,9 +3264,13 @@ class stored_infoTestCase(unittest.TestCase):
self.xyzzy = 7
def get_entry(self, name):
return self.Null()
+
+ def test_sconsign(node):
+ return MySConsign()
f = fs.File('file2', d)
- f.dir.sconsign = MySConsign
+ SCons.Node.FS._sconsign_map[2] = test_sconsign
+ f.dir._func_sconsign = 2
bi = f.get_stored_info()
assert bi.xyzzy == 7, bi
@@ -3360,7 +3384,7 @@ class prepareTestCase(unittest.TestCase):
xyz.set_state(0)
xyz.prepare()
- assert dir_made[0].path == "new_dir", dir_made[0]
+ assert dir_made[0].get_internal_path() == "new_dir", dir_made[0]
dir = fs.Dir("dir")
dir.prepare()
@@ -3373,7 +3397,7 @@ class SConstruct_dirTestCase(unittest.TestCase):
fs = SCons.Node.FS.FS()
fs.set_SConstruct_dir(fs.Dir('xxx'))
- assert fs.SConstruct_dir.path == 'xxx'
+ assert fs.SConstruct_dir.get_internal_path() == 'xxx'
@@ -3552,7 +3576,7 @@ class SpecialAttrTestCase(unittest.TestCase):
for_sig = f.suffix.for_signature()
assert for_sig == 'baz.blat_suffix', for_sig
- s = str(f.abspath)
+ s = str(f.get_abspath())
assert s == test.workpath('work', 'foo', 'bar', 'baz.blat'), s
assert f.abspath.is_literal(), f.abspath
for_sig = f.abspath.for_signature()
diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py
index da502b0..a6471b4 100644
--- a/src/engine/SCons/Node/NodeTests.py
+++ b/src/engine/SCons/Node/NodeTests.py
@@ -184,7 +184,7 @@ class Scanner(object):
called = None
def __call__(self, node):
self.called = 1
- return node.found_includes
+ return node.GetTag('found_includes')
def path(self, env, dir, target=None, source=None):
return ()
def select(self, node):
@@ -200,7 +200,7 @@ class MyNode(SCons.Node.Node):
def __init__(self, name):
SCons.Node.Node.__init__(self)
self.name = name
- self.found_includes = []
+ self.Tag('found_includes', [])
def __str__(self):
return self.name
def get_found_includes(self, env, scanner, target):
@@ -224,11 +224,18 @@ class Calculator(object):
class NodeInfoBaseTestCase(unittest.TestCase):
+ # The abstract class NodeInfoBase has not enough default slots to perform
+ # the merge and format test (arbitrary attributes do not work). Do it with a
+ # derived class that does provide the slots.
def test_merge(self):
"""Test merging NodeInfoBase attributes"""
- ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node())
- ni2 = SCons.Node.NodeInfoBase(SCons.Node.Node())
+
+ class TestNodeInfo(SCons.Node.NodeInfoBase):
+ __slots__ = ('a1', 'a2', 'a3')
+
+ ni1 = TestNodeInfo()
+ ni2 = TestNodeInfo()
ni1.a1 = 1
ni1.a2 = 2
@@ -237,27 +244,32 @@ class NodeInfoBaseTestCase(unittest.TestCase):
ni2.a3 = 333
ni1.merge(ni2)
- expect = {'a1':1, 'a2':222, 'a3':333, '_version_id':1}
- assert ni1.__dict__ == expect, ni1.__dict__
+ assert ni1.a1 == 1, ni1.a1
+ assert ni1.a2 == 222, ni1.a2
+ assert ni1.a3 == 333, ni1.a3
def test_update(self):
"""Test the update() method"""
- ni = SCons.Node.NodeInfoBase(SCons.Node.Node())
+ ni = SCons.Node.NodeInfoBase()
ni.update(SCons.Node.Node())
def test_format(self):
"""Test the NodeInfoBase.format() method"""
- ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node())
+
+ class TestNodeInfo(SCons.Node.NodeInfoBase):
+ __slots__ = ('xxx', 'yyy', 'zzz')
+
+ ni1 = TestNodeInfo()
ni1.xxx = 'x'
ni1.yyy = 'y'
ni1.zzz = 'z'
f = ni1.format()
- assert f == ['1', 'x', 'y', 'z'], f
+ assert f == ['x', 'y', 'z'], f
+
+ field_list = ['xxx', 'zzz', 'aaa']
- ni1.field_list = ['xxx', 'zzz', 'aaa']
-
- f = ni1.format()
+ f = ni1.format(field_list)
assert f == ['x', 'z', 'None'], f
@@ -267,26 +279,26 @@ class BuildInfoBaseTestCase(unittest.TestCase):
def test___init__(self):
"""Test BuildInfoBase initialization"""
n = SCons.Node.Node()
- bi = SCons.Node.BuildInfoBase(n)
+ bi = SCons.Node.BuildInfoBase()
assert bi
def test_merge(self):
"""Test merging BuildInfoBase attributes"""
n1 = SCons.Node.Node()
- bi1 = SCons.Node.BuildInfoBase(n1)
+ bi1 = SCons.Node.BuildInfoBase()
n2 = SCons.Node.Node()
- bi2 = SCons.Node.BuildInfoBase(n2)
+ bi2 = SCons.Node.BuildInfoBase()
- bi1.a1 = 1
- bi1.a2 = 2
+ bi1.bsources = 1
+ bi1.bdepends = 2
- bi2.a2 = 222
- bi2.a3 = 333
+ bi2.bdepends = 222
+ bi2.bact = 333
bi1.merge(bi2)
- assert bi1.a1 == 1, bi1.a1
- assert bi1.a2 == 222, bi1.a2
- assert bi1.a3 == 333, bi1.a3
+ assert bi1.bsources == 1, bi1.bsources
+ assert bi1.bdepends == 222, bi1.bdepends
+ assert bi1.bact == 333, bi1.bact
class NodeTestCase(unittest.TestCase):
@@ -427,6 +439,7 @@ class NodeTestCase(unittest.TestCase):
def test_built(self):
"""Test the built() method"""
class SubNodeInfo(SCons.Node.NodeInfoBase):
+ __slots__ = ('updated',)
def update(self, node):
self.updated = 1
class SubNode(SCons.Node.Node):
@@ -434,7 +447,7 @@ class NodeTestCase(unittest.TestCase):
self.cleared = 1
n = SubNode()
- n.ninfo = SubNodeInfo(n)
+ n.ninfo = SubNodeInfo()
n.built()
assert n.cleared, n.cleared
assert n.ninfo.updated, n.ninfo.cleared
@@ -568,32 +581,56 @@ class NodeTestCase(unittest.TestCase):
def test_get_csig(self):
"""Test generic content signature calculation
"""
- node = SCons.Node.Node()
- node.get_contents = lambda: 444
- result = node.get_csig()
- assert result == '550a141f12de6341fba65b0ad0433500', result
+
+ class TestNodeInfo(SCons.Node.NodeInfoBase):
+ __slots__ = ('csig',)
+ try:
+ SCons.Node.Node.NodeInfo = TestNodeInfo
+ def my_contents(obj):
+ return 444
+ SCons.Node._get_contents_map[4] = my_contents
+ node = SCons.Node.Node()
+ node._func_get_contents = 4
+ result = node.get_csig()
+ assert result == '550a141f12de6341fba65b0ad0433500', result
+ finally:
+ SCons.Node.Node.NodeInfo = SCons.Node.NodeInfoBase
def test_get_cachedir_csig(self):
"""Test content signature calculation for CacheDir
"""
- node = SCons.Node.Node()
- node.get_contents = lambda: 555
- result = node.get_cachedir_csig()
- assert result == '15de21c670ae7c3f6f3f1f37029303c9', result
+ class TestNodeInfo(SCons.Node.NodeInfoBase):
+ __slots__ = ('csig',)
+ try:
+ SCons.Node.Node.NodeInfo = TestNodeInfo
+ def my_contents(obj):
+ return 555
+ SCons.Node._get_contents_map[4] = my_contents
+ node = SCons.Node.Node()
+ node._func_get_contents = 4
+ result = node.get_cachedir_csig()
+ assert result == '15de21c670ae7c3f6f3f1f37029303c9', result
+ finally:
+ SCons.Node.Node.NodeInfo = SCons.Node.NodeInfoBase
def test_get_binfo(self):
"""Test fetching/creating a build information structure
"""
+ class TestNodeInfo(SCons.Node.NodeInfoBase):
+ __slots__ = ('csig',)
+ SCons.Node.Node.NodeInfo = TestNodeInfo
node = SCons.Node.Node()
-
+
binfo = node.get_binfo()
assert isinstance(binfo, SCons.Node.BuildInfoBase), binfo
node = SCons.Node.Node()
d = SCons.Node.Node()
- d.get_ninfo().csig = 777
+ ninfo = d.get_ninfo()
+ assert isinstance(ninfo, SCons.Node.NodeInfoBase), ninfo
i = SCons.Node.Node()
- i.get_ninfo().csig = 888
+ ninfo = i.get_ninfo()
+ assert isinstance(ninfo, SCons.Node.NodeInfoBase), ninfo
node.depends = [d]
node.implicit = [i]
@@ -655,7 +692,7 @@ class NodeTestCase(unittest.TestCase):
"""Test calling the method to store build information
"""
node = SCons.Node.Node()
- node.store_info()
+ SCons.Node.store_info_map[node.store_info](node)
def test_get_stored_info(self):
"""Test calling the method to fetch stored build information
@@ -888,7 +925,7 @@ class NodeTestCase(unittest.TestCase):
s = Scanner()
d1 = MyNode("d1")
d2 = MyNode("d2")
- node.found_includes = [d1, d2]
+ node.Tag('found_includes', [d1, d2])
# Simple return of the found includes
deps = node.get_implicit_deps(env, s, target)
@@ -898,14 +935,14 @@ class NodeTestCase(unittest.TestCase):
e = MyNode("eee")
f = MyNode("fff")
g = MyNode("ggg")
- d1.found_includes = [e, f]
- d2.found_includes = [e, f]
- f.found_includes = [g]
+ d1.Tag('found_includes', [e, f])
+ d2.Tag('found_includes', [e, f])
+ f.Tag('found_includes', [g])
deps = node.get_implicit_deps(env, s, target)
assert deps == [d1, d2, e, f, g], list(map(str, deps))
# Recursive scanning eliminates duplicates
- e.found_includes = [f]
+ e.Tag('found_includes', [f])
deps = node.get_implicit_deps(env, s, target)
assert deps == [d1, d2, e, f, g], list(map(str, deps))
@@ -994,7 +1031,7 @@ class NodeTestCase(unittest.TestCase):
s = Scanner()
d = MyNode("ddd")
- node.found_includes = [d]
+ node.Tag('found_includes', [d])
node.builder.target_scanner = s
assert node.implicit is None
@@ -1207,7 +1244,7 @@ class NodeTestCase(unittest.TestCase):
def test_Annotate(self):
"""Test using an interface-specific Annotate function."""
def my_annotate(node, self=self):
- node.annotation = self.node_string
+ node.Tag('annotation', self.node_string)
save_Annotate = SCons.Node.Annotate
SCons.Node.Annotate = my_annotate
@@ -1215,11 +1252,13 @@ class NodeTestCase(unittest.TestCase):
try:
self.node_string = '#1'
n = SCons.Node.Node()
- assert n.annotation == '#1', n.annotation
+ a = n.GetTag('annotation')
+ assert a == '#1', a
self.node_string = '#2'
n = SCons.Node.Node()
- assert n.annotation == '#2', n.annotation
+ a = n.GetTag('annotation')
+ assert a == '#2', a
finally:
SCons.Node.Annotate = save_Annotate
@@ -1230,7 +1269,7 @@ class NodeTestCase(unittest.TestCase):
n.set_state(3)
n.binfo = 'xyz'
n.includes = 'testincludes'
- n.found_include = {'testkey':'testvalue'}
+ n.Tag('found_includes', {'testkey':'testvalue'})
n.implicit = 'testimplicit'
x = MyExecutor()
diff --git a/src/engine/SCons/Node/Python.py b/src/engine/SCons/Node/Python.py
index 3d8bdaa..f151fc5 100644
--- a/src/engine/SCons/Node/Python.py
+++ b/src/engine/SCons/Node/Python.py
@@ -32,15 +32,49 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Node
class ValueNodeInfo(SCons.Node.NodeInfoBase):
- current_version_id = 1
+ __slots__ = ('csig',)
+ current_version_id = 2
field_list = ['csig']
def str_to_node(self, s):
return Value(s)
+ def __getstate__(self):
+ """
+ Return all fields that shall be pickled. Walk the slots in the class
+ hierarchy and add those to the state dictionary. If a '__dict__' slot is
+ available, copy all entries to the dictionary. Also include the version
+ id, which is fixed for all instances of a class.
+ """
+ state = getattr(self, '__dict__', {}).copy()
+ for obj in type(self).mro():
+ for name in getattr(obj,'__slots__',()):
+ if hasattr(self, name):
+ state[name] = getattr(self, name)
+
+ state['_version_id'] = self.current_version_id
+ try:
+ del state['__weakref__']
+ except KeyError:
+ pass
+
+ return state
+
+ def __setstate__(self, state):
+ """
+ Restore the attributes from a pickled state.
+ """
+ # TODO check or discard version
+ del state['_version_id']
+ for key, value in state.items():
+ if key not in ('__weakref__',):
+ setattr(self, key, value)
+
+
class ValueBuildInfo(SCons.Node.BuildInfoBase):
- current_version_id = 1
+ __slots__ = ()
+ current_version_id = 2
class Value(SCons.Node.Node):
"""A class for Python variables, typically passed on the command line
@@ -53,6 +87,8 @@ class Value(SCons.Node.Node):
def __init__(self, value, built_value=None):
SCons.Node.Node.__init__(self)
self.value = value
+ self.changed_since_last_build = 6
+ self.store_info = 0
if built_value is not None:
self.built_value = built_value
diff --git a/src/engine/SCons/Node/PythonTests.py b/src/engine/SCons/Node/PythonTests.py
index fcdfe77..e2e36bf 100644
--- a/src/engine/SCons/Node/PythonTests.py
+++ b/src/engine/SCons/Node/PythonTests.py
@@ -104,13 +104,13 @@ class ValueNodeInfoTestCase(unittest.TestCase):
def test___init__(self):
"""Test ValueNodeInfo initialization"""
vvv = SCons.Node.Python.Value('vvv')
- ni = SCons.Node.Python.ValueNodeInfo(vvv)
+ ni = SCons.Node.Python.ValueNodeInfo()
class ValueBuildInfoTestCase(unittest.TestCase):
def test___init__(self):
"""Test ValueBuildInfo initialization"""
vvv = SCons.Node.Python.Value('vvv')
- bi = SCons.Node.Python.ValueBuildInfo(vvv)
+ bi = SCons.Node.Python.ValueBuildInfo()
if __name__ == "__main__":
suite = unittest.TestSuite()
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index 1f62971..f2d37c2 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -55,6 +55,8 @@ import SCons.Util
from SCons.Debug import Trace
+print_duplicate = 0
+
def classname(obj):
return str(obj.__class__).split('.')[-1]
@@ -105,6 +107,233 @@ Annotate = do_nothing
# clean builds and update runs (see release_target_info).
interactive = False
+def is_derived_none(node):
+ raise NotImplementedError
+
+def is_derived_node(node):
+ """
+ Returns true if this node is derived (i.e. built).
+ """
+ return node.has_builder() or node.side_effect
+
+_is_derived_map = {0 : is_derived_none,
+ 1 : is_derived_node}
+
+def exists_none(node):
+ raise NotImplementedError
+
+def exists_always(node):
+ return 1
+
+def exists_base(node):
+ return node.stat() is not None
+
+def exists_entry(node):
+ """Return if the Entry exists. Check the file system to see
+ what we should turn into first. Assume a file if there's no
+ directory."""
+ node.disambiguate()
+ return _exists_map[node._func_exists](node)
+
+def exists_file(node):
+ # Duplicate from source path if we are set up to do this.
+ if node.duplicate and not node.is_derived() and not node.linked:
+ src = node.srcnode()
+ if src is not node:
+ # At this point, src is meant to be copied in a variant directory.
+ src = src.rfile()
+ if src.get_abspath() != node.get_abspath():
+ if src.exists():
+ node.do_duplicate(src)
+ # Can't return 1 here because the duplication might
+ # not actually occur if the -n option is being used.
+ else:
+ # The source file does not exist. Make sure no old
+ # copy remains in the variant directory.
+ if print_duplicate:
+ print "dup: no src for %s, unlinking old variant copy"%self
+ if exists_base(node) or node.islink():
+ node.fs.unlink(node.get_internal_path())
+ # Return None explicitly because the Base.exists() call
+ # above will have cached its value if the file existed.
+ return None
+ return exists_base(node)
+
+_exists_map = {0 : exists_none,
+ 1 : exists_always,
+ 2 : exists_base,
+ 3 : exists_entry,
+ 4 : exists_file}
+
+
+def rexists_none(node):
+ raise NotImplementedError
+
+def rexists_node(node):
+ return node.exists()
+
+def rexists_base(node):
+ return node.rfile().exists()
+
+_rexists_map = {0 : rexists_none,
+ 1 : rexists_node,
+ 2 : rexists_base}
+
+def get_contents_none(node):
+ raise NotImplementedError
+
+def get_contents_entry(node):
+ """Fetch the contents of the entry. Returns the exact binary
+ contents of the file."""
+ try:
+ node = node.disambiguate(must_exist=1)
+ except SCons.Errors.UserError:
+ # There was nothing on disk with which to disambiguate
+ # this entry. Leave it as an Entry, but return a null
+ # string so calls to get_contents() in emitters and the
+ # like (e.g. in qt.py) don't have to disambiguate by hand
+ # or catch the exception.
+ return ''
+ else:
+ return _get_contents_map[node._func_get_contents](node)
+
+def get_contents_dir(node):
+ """Return content signatures and names of all our children
+ separated by new-lines. Ensure that the nodes are sorted."""
+ contents = []
+ for n in sorted(node.children(), key=lambda t: t.name):
+ contents.append('%s %s\n' % (n.get_csig(), n.name))
+ return ''.join(contents)
+
+def get_contents_file(node):
+ if not node.rexists():
+ return ''
+ fname = node.rfile().get_abspath()
+ try:
+ contents = open(fname, "rb").read()
+ except EnvironmentError, e:
+ if not e.filename:
+ e.filename = fname
+ raise
+ return contents
+
+_get_contents_map = {0 : get_contents_none,
+ 1 : get_contents_entry,
+ 2 : get_contents_dir,
+ 3 : get_contents_file}
+
+def target_from_source_none(node, prefix, suffix, splitext):
+ raise NotImplementedError
+
+def target_from_source_base(node, prefix, suffix, splitext):
+ return node.dir.Entry(prefix + splitext(node.name)[0] + suffix)
+
+_target_from_source_map = {0 : target_from_source_none,
+ 1 : target_from_source_base}
+
+#
+# The new decider subsystem for Nodes
+#
+# We would set and overwrite the changed_since_last_build function
+# before, but for being able to use slots (less memory!) we now have
+# a dictionary of the different decider functions. Then in the Node
+# subclasses we simply store the index to the decider that should be
+# used by it.
+#
+
+#
+# First, the single decider functions
+#
+def changed_since_last_build_node(node, target, prev_ni):
+ """
+
+ Must be overridden in a specific subclass to return True if this
+ Node (a dependency) has changed since the last time it was used
+ to build the specified target. prev_ni is this Node's state (for
+ example, its file timestamp, length, maybe content signature)
+ as of the last time the target was built.
+
+ Note that this method is called through the dependency, not the
+ target, because a dependency Node must be able to use its own
+ logic to decide if it changed. For example, File Nodes need to
+ obey if we're configured to use timestamps, but Python Value Nodes
+ never use timestamps and always use the content. If this method
+ were called through the target, then each Node's implementation
+ of this method would have to have more complicated logic to
+ handle all the different Node types on which it might depend.
+ """
+ raise NotImplementedError
+
+def changed_since_last_build_alias(node, target, prev_ni):
+ cur_csig = node.get_csig()
+ try:
+ return cur_csig != prev_ni.csig
+ except AttributeError:
+ return 1
+
+def changed_since_last_build_entry(node, target, prev_ni):
+ node.disambiguate()
+ return _decider_map[node.changed_since_last_build](node, target, prev_ni)
+
+def changed_since_last_build_state_changed(node, target, prev_ni):
+ return (node.state != SCons.Node.up_to_date)
+
+def decide_source(node, target, prev_ni):
+ return target.get_build_env().decide_source(node, target, prev_ni)
+
+def 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):
+ cur_csig = node.get_csig()
+ try:
+ return cur_csig != prev_ni.csig
+ except AttributeError:
+ return 1
+
+
+#
+# Now, the mapping from indices to decider functions
+#
+_decider_map = {0 : changed_since_last_build_node,
+ 1 : changed_since_last_build_alias,
+ 2 : changed_since_last_build_entry,
+ 3 : changed_since_last_build_state_changed,
+ 4 : decide_source,
+ 5 : decide_target,
+ 6 : changed_since_last_build_python}
+
+do_store_info = True
+
+#
+# The new store_info subsystem for Nodes
+#
+# We would set and overwrite the store_info function
+# before, but for being able to use slots (less memory!) we now have
+# a dictionary of the different functions. Then in the Node
+# subclasses we simply store the index to the info method that should be
+# used by it.
+#
+
+#
+# First, the single info functions
+#
+
+def store_info_pass(node):
+ pass
+
+def store_info_file(node):
+ # Merge our build information into the already-stored entry.
+ # This accommodates "chained builds" where a file that's a target
+ # in one build (SConstruct file) is a source in a different build.
+ # See test/chained-build.py for the use case.
+ if do_store_info:
+ node.dir.sconsign().store_info(node.name, node)
+
+
+store_info_map = {0 : store_info_pass,
+ 1 : store_info_file}
+
# Classes for signature info for Nodes.
class NodeInfoBase(object):
@@ -114,11 +343,8 @@ class NodeInfoBase(object):
Node subclasses should subclass NodeInfoBase to provide their own
logic for dealing with their own Node-specific signature information.
"""
- current_version_id = 1
- def __init__(self, node=None):
- # Create an object attribute from the class attribute so it ends up
- # in the pickled data in the .sconsign file.
- self._version_id = self.current_version_id
+ __slots__ = ('__weakref__',)
+ current_version_id = 2
def update(self, node):
try:
field_list = self.field_list
@@ -138,13 +364,25 @@ class NodeInfoBase(object):
def convert(self, node, val):
pass
def merge(self, other):
- self.__dict__.update(other.__dict__)
+ """
+ Merge the fields of another object into this object. Already existing
+ information is overwritten by the other instance's data.
+ WARNING: If a '__dict__' slot is added, it should be updated instead of
+ replaced.
+ """
+ state = other.__getstate__()
+ self.__setstate__(state)
def format(self, field_list=None, names=0):
if field_list is None:
try:
field_list = self.field_list
except AttributeError:
- field_list = sorted(self.__dict__.keys())
+ field_list = getattr(self, '__dict__', {}).keys()
+ for obj in type(self).mro():
+ for slot in getattr(obj, '__slots__', ()):
+ if slot not in ('__weakref__', '__dict__'):
+ field_list.append(slot)
+ field_list.sort()
fields = []
for field in field_list:
try:
@@ -157,6 +395,38 @@ class NodeInfoBase(object):
fields.append(f)
return fields
+ def __getstate__(self):
+ """
+ Return all fields that shall be pickled. Walk the slots in the class
+ hierarchy and add those to the state dictionary. If a '__dict__' slot is
+ available, copy all entries to the dictionary. Also include the version
+ id, which is fixed for all instances of a class.
+ """
+ state = getattr(self, '__dict__', {}).copy()
+ for obj in type(self).mro():
+ for name in getattr(obj,'__slots__',()):
+ if hasattr(self, name):
+ state[name] = getattr(self, name)
+
+ state['_version_id'] = self.current_version_id
+ try:
+ del state['__weakref__']
+ except KeyError:
+ pass
+ return state
+
+ def __setstate__(self, state):
+ """
+ Restore the attributes from a pickled state. The version is discarded.
+ """
+ # TODO check or discard version
+ del state['_version_id']
+
+ for key, value in state.items():
+ if key not in ('__weakref__',):
+ setattr(self, key, value)
+
+
class BuildInfoBase(object):
"""
The generic base class for build information for a Node.
@@ -167,30 +437,106 @@ class BuildInfoBase(object):
generic build stuff we have to track: sources, explicit dependencies,
implicit dependencies, and action information.
"""
- current_version_id = 1
- def __init__(self, node=None):
+ __slots__ = ("bsourcesigs", "bdependsigs", "bimplicitsigs", "bactsig",
+ "bsources", "bdepends", "bact", "bimplicit", "__weakref__")
+ current_version_id = 2
+ def __init__(self):
# Create an object attribute from the class attribute so it ends up
# in the pickled data in the .sconsign file.
- self._version_id = self.current_version_id
self.bsourcesigs = []
self.bdependsigs = []
self.bimplicitsigs = []
self.bactsig = None
def merge(self, other):
- self.__dict__.update(other.__dict__)
+ """
+ Merge the fields of another object into this object. Already existing
+ information is overwritten by the other instance's data.
+ WARNING: If a '__dict__' slot is added, it should be updated instead of
+ replaced.
+ """
+ state = other.__getstate__()
+ self.__setstate__(state)
+
+ def __getstate__(self):
+ """
+ Return all fields that shall be pickled. Walk the slots in the class
+ hierarchy and add those to the state dictionary. If a '__dict__' slot is
+ available, copy all entries to the dictionary. Also include the version
+ id, which is fixed for all instances of a class.
+ """
+ state = getattr(self, '__dict__', {}).copy()
+ for obj in type(self).mro():
+ for name in getattr(obj,'__slots__',()):
+ if hasattr(self, name):
+ state[name] = getattr(self, name)
+
+ state['_version_id'] = self.current_version_id
+ try:
+ del state['__weakref__']
+ except KeyError:
+ pass
+ return state
+
+ def __setstate__(self, state):
+ """
+ Restore the attributes from a pickled state.
+ """
+ # TODO check or discard version
+ del state['_version_id']
+ for key, value in state.items():
+ if key not in ('__weakref__',):
+ setattr(self, key, value)
class Node(object):
"""The base Node class, for entities that we know how to
build, or use to build other Nodes.
"""
- if SCons.Memoize.use_memoizer:
- __metaclass__ = SCons.Memoize.Memoized_Metaclass
-
- memoizer_counters = []
+ __slots__ = ['sources',
+ 'sources_set',
+ '_specific_sources',
+ 'depends',
+ 'depends_set',
+ 'ignore',
+ 'ignore_set',
+ 'prerequisites',
+ 'implicit',
+ 'waiting_parents',
+ 'waiting_s_e',
+ 'ref_count',
+ 'wkids',
+ 'env',
+ 'state',
+ 'precious',
+ 'noclean',
+ 'nocache',
+ 'cached',
+ 'always_build',
+ 'includes',
+ 'attributes',
+ 'side_effect',
+ 'side_effects',
+ 'linked',
+ '_memo',
+ 'executor',
+ 'binfo',
+ 'ninfo',
+ 'builder',
+ 'is_explicit',
+ 'implicit_set',
+ 'changed_since_last_build',
+ 'store_info',
+ 'pseudo',
+ '_tags',
+ '_func_is_derived',
+ '_func_exists',
+ '_func_rexists',
+ '_func_get_contents',
+ '_func_target_from_source']
class Attrs(object):
- pass
+ __slots__ = ('shared', '__dict__')
+
def __init__(self):
if SCons.Debug.track_instances: logInstanceCreation(self, 'Node.Node')
@@ -234,7 +580,15 @@ class Node(object):
self.side_effect = 0 # true iff this node is a side effect
self.side_effects = [] # the side effects of building this target
self.linked = 0 # is this node linked to the variant directory?
-
+ self.changed_since_last_build = 0
+ self.store_info = 0
+ self._tags = None
+ self._func_is_derived = 1
+ self._func_exists = 1
+ self._func_rexists = 1
+ self._func_get_contents = 0
+ self._func_target_from_source = 0
+
self.clear_memoized_values()
# Let the interface in which the build engine is embedded
@@ -248,8 +602,7 @@ class Node(object):
def get_suffix(self):
return ''
- memoizer_counters.append(SCons.Memoize.CountValue('get_build_env'))
-
+ @SCons.Memoize.CountMethodCall
def get_build_env(self):
"""Fetch the appropriate Environment to build this node.
"""
@@ -418,7 +771,7 @@ class Node(object):
pass
else:
self.ninfo.update(self)
- self.store_info()
+ SCons.Node.store_info_map[self.store_info](self)
def release_target_info(self):
"""Called just after this node has been marked
@@ -546,7 +899,7 @@ class Node(object):
example: source with source builders are not derived in this sense,
and hence should not return true.
"""
- return self.has_builder() or self.side_effect
+ return _is_derived_map[self._func_is_derived](self)
def alter_targets(self):
"""Return a list of alternate targets for this Node.
@@ -706,7 +1059,7 @@ class Node(object):
BuildInfo = BuildInfoBase
def new_ninfo(self):
- ninfo = self.NodeInfo(self)
+ ninfo = self.NodeInfo()
return ninfo
def get_ninfo(self):
@@ -717,7 +1070,7 @@ class Node(object):
return self.ninfo
def new_binfo(self):
- binfo = self.BuildInfo(self)
+ binfo = self.BuildInfo()
return binfo
def get_binfo(self):
@@ -802,14 +1155,6 @@ class Node(object):
def get_cachedir_csig(self):
return self.get_csig()
- def store_info(self):
- """Make the build signature permanent (that is, store it in the
- .sconsign file or equivalent)."""
- pass
-
- def do_not_store_info(self):
- pass
-
def get_stored_info(self):
return None
@@ -847,13 +1192,16 @@ class Node(object):
def exists(self):
"""Does this node exists?"""
- # All node exist by default:
- return 1
+ return _exists_map[self._func_exists](self)
def rexists(self):
"""Does this node exist locally or in a repositiory?"""
# There are no repositories by default:
- return self.exists()
+ return _rexists_map[self._func_rexists](self)
+
+ def get_contents(self):
+ """Fetch the contents of the entry."""
+ return _get_contents_map[self._func_get_contents](self)
def missing(self):
return not self.is_derived() and \
@@ -941,11 +1289,10 @@ class Node(object):
# build info that it's cached so we can re-calculate it.
self.executor_cleanup()
- memoizer_counters.append(SCons.Memoize.CountValue('_children_get'))
-
+ @SCons.Memoize.CountMethodCall
def _children_get(self):
try:
- return self._memo['children_get']
+ return self._memo['_children_get']
except KeyError:
pass
@@ -976,7 +1323,7 @@ class Node(object):
else:
children = self.all_children(scan=0)
- self._memo['children_get'] = children
+ self._memo['_children_get'] = children
return children
def all_children(self, scan=1):
@@ -1016,9 +1363,6 @@ class Node(object):
def get_state(self):
return self.state
- def state_has_changed(self, target, prev_ni):
- return (self.state != SCons.Node.up_to_date)
-
def get_env(self):
env = self.env
if not env:
@@ -1026,28 +1370,28 @@ class Node(object):
env = SCons.Defaults.DefaultEnvironment()
return env
- def changed_since_last_build(self, target, prev_ni):
- """
-
- Must be overridden in a specific subclass to return True if this
- Node (a dependency) has changed since the last time it was used
- to build the specified target. prev_ni is this Node's state (for
- example, its file timestamp, length, maybe content signature)
- as of the last time the target was built.
-
- Note that this method is called through the dependency, not the
- target, because a dependency Node must be able to use its own
- logic to decide if it changed. For example, File Nodes need to
- obey if we're configured to use timestamps, but Python Value Nodes
- never use timestamps and always use the content. If this method
- were called through the target, then each Node's implementation
- of this method would have to have more complicated logic to
- handle all the different Node types on which it might depend.
- """
- raise NotImplementedError
-
def Decider(self, function):
- SCons.Util.AddMethod(self, function, 'changed_since_last_build')
+ foundkey = None
+ for k, v in _decider_map.iteritems():
+ if v == function:
+ foundkey = k
+ break
+ if not foundkey:
+ foundkey = len(_decider_map)
+ _decider_map[foundkey] = function
+ self.changed_since_last_build = foundkey
+
+ def Tag(self, key, value):
+ """ Add a user-defined tag. """
+ if not self._tags:
+ self._tags = {}
+ self._tags[key] = value
+
+ def GetTag(self, key):
+ """ Return a user-defined tag. """
+ if not self._tags:
+ return None
+ return self._tags.get(key, None)
def changed(self, node=None, allowcache=False):
"""
@@ -1095,7 +1439,7 @@ class Node(object):
result = True
for child, prev_ni in zip(children, then):
- if child.changed_since_last_build(self, prev_ni):
+ if _decider_map[child.changed_since_last_build](child, self, prev_ni):
if t: Trace(': %s changed' % child)
result = True
@@ -1266,7 +1610,7 @@ class Node(object):
for k in new_bkids:
if not k in old_bkids:
lines.append("`%s' is a new dependency\n" % stringify(k))
- elif k.changed_since_last_build(self, osig[k]):
+ elif _decider_map[k.changed_since_last_build](k, self, osig[k]):
lines.append("`%s' changed\n" % stringify(k))
if len(lines) == 0 and old_bkids != new_bkids:
diff --git a/src/engine/SCons/PathList.py b/src/engine/SCons/PathList.py
index f3de57c..350e1ac 100644
--- a/src/engine/SCons/PathList.py
+++ b/src/engine/SCons/PathList.py
@@ -171,11 +171,6 @@ class PathListCache(object):
cheaply avoid re-parsing both values of CPPPATH by using the
common value from this cache.
"""
- if SCons.Memoize.use_memoizer:
- __metaclass__ = SCons.Memoize.Memoized_Metaclass
-
- memoizer_counters = []
-
def __init__(self):
self._memo = {}
@@ -196,8 +191,7 @@ class PathListCache(object):
pathlist = tuple(SCons.Util.flatten(pathlist))
return pathlist
- memoizer_counters.append(SCons.Memoize.CountDict('PathList', _PathList_key))
-
+ @SCons.Memoize.CountDictCall(_PathList_key)
def PathList(self, pathlist):
"""
Returns the cached _PathList object for the specified pathlist,
diff --git a/src/engine/SCons/Platform/cygwin.py b/src/engine/SCons/Platform/cygwin.py
index a012682..34c79ff 100644
--- a/src/engine/SCons/Platform/cygwin.py
+++ b/src/engine/SCons/Platform/cygwin.py
@@ -42,8 +42,8 @@ def generate(env):
env['PROGSUFFIX'] = '.exe'
env['SHLIBPREFIX'] = ''
env['SHLIBSUFFIX'] = '.dll'
- env['LIBPREFIXES'] = [ '$LIBPREFIX', '$SHLIBPREFIX' ]
- env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ]
+ env['LIBPREFIXES'] = [ '$LIBPREFIX', '$SHLIBPREFIX', '$IMPLIBPREFIX' ]
+ env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX', '$IMPLIBSUFFIX' ]
env['TEMPFILE'] = TempFileMunge
env['TEMPFILEPREFIX'] = '@'
env['MAXLINELENGTH'] = 2048
diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py
index 87432ef..8bce8ce 100644
--- a/src/engine/SCons/SConf.py
+++ b/src/engine/SCons/SConf.py
@@ -175,8 +175,11 @@ class SConfBuildInfo(SCons.Node.FS.FileBuildInfo):
are result (did the builder succeed last time?) and string, which
contains messages of the original build phase.
"""
- result = None # -> 0/None -> no error, != 0 error
- string = None # the stdout / stderr output when building the target
+ __slots__ = ('result', 'string')
+
+ def __init__(self):
+ self.result = None # -> 0/None -> no error, != 0 error
+ self.string = None # the stdout / stderr output when building the target
def set_build_result(self, result, string):
self.result = result
@@ -352,8 +355,10 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
raise SCons.Errors.ExplicitExit(self.targets[0],exc_value.code)
except Exception, e:
for t in self.targets:
- binfo = t.get_binfo()
- binfo.__class__ = SConfBuildInfo
+ #binfo = t.get_binfo()
+ #binfo.__class__ = SConfBuildInfo
+ binfo = SConfBuildInfo()
+ binfo.merge(t.get_binfo())
binfo.set_build_result(1, s.getvalue())
sconsign_entry = SCons.SConsign.SConsignEntry()
sconsign_entry.binfo = binfo
@@ -370,8 +375,10 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
raise e
else:
for t in self.targets:
- binfo = t.get_binfo()
- binfo.__class__ = SConfBuildInfo
+ #binfo = t.get_binfo()
+ #binfo.__class__ = SConfBuildInfo
+ binfo = SConfBuildInfo()
+ binfo.merge(t.get_binfo())
binfo.set_build_result(0, s.getvalue())
sconsign_entry = SCons.SConsign.SConsignEntry()
sconsign_entry.binfo = binfo
@@ -500,7 +507,7 @@ class SConfBase(object):
# we override the store_info() method with a null place-holder
# so we really control how it gets written.
for n in nodes:
- n.store_info = n.do_not_store_info
+ n.store_info = 0
if not hasattr(n, 'attributes'):
n.attributes = SCons.Node.Node.Attrs()
n.attributes.keep_targetinfo = 1
@@ -640,7 +647,7 @@ class SConfBase(object):
ok = self.TryLink(text, extension)
if( ok ):
prog = self.lastTarget
- pname = prog.path
+ pname = prog.get_internal_path()
output = self.confdir.File(os.path.basename(pname)+'.out')
node = self.env.Command(output, prog, [ [ pname, ">", "${TARGET}"] ])
ok = self.BuildNodes(node)
@@ -684,7 +691,6 @@ class SConfBase(object):
else:
if not os.path.isdir( dirName ):
os.makedirs( dirName )
- node._exists = 1
def _startup(self):
"""Private method. Set up logstream, and set the environment
diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py
index 1c4b401..233ee78 100644
--- a/src/engine/SCons/SConfTests.py
+++ b/src/engine/SCons/SConfTests.py
@@ -221,8 +221,6 @@ class SConfTestCase(unittest.TestCase):
pass
def get_stored_info(self):
pass
- def do_not_store_info(self):
- pass
def get_executor(self):
class Executor(object):
def __init__(self, targets):
diff --git a/src/engine/SCons/SConsign.py b/src/engine/SCons/SConsign.py
index 6555fcb..74ee804 100644
--- a/src/engine/SCons/SConsign.py
+++ b/src/engine/SCons/SConsign.py
@@ -122,16 +122,40 @@ class SConsignEntry(object):
XXX As coded below, we do expect a '.binfo' attribute to be added,
but we'll probably generalize this in the next refactorings.
"""
- current_version_id = 1
+ __slots__ = ("binfo", "ninfo", "__weakref__")
+ current_version_id = 2
+
def __init__(self):
# Create an object attribute from the class attribute so it ends up
# in the pickled data in the .sconsign file.
- _version_id = self.current_version_id
+ #_version_id = self.current_version_id
+ pass
+
def convert_to_sconsign(self):
self.binfo.convert_to_sconsign()
+
def convert_from_sconsign(self, dir, name):
self.binfo.convert_from_sconsign(dir, name)
+ def __getstate__(self):
+ state = getattr(self, '__dict__', {}).copy()
+ for obj in type(self).mro():
+ for name in getattr(obj,'__slots__',()):
+ if hasattr(self, name):
+ state[name] = getattr(self, name)
+
+ state['_version_id'] = self.current_version_id
+ try:
+ del state['__weakref__']
+ except KeyError:
+ pass
+ return state
+
+ def __setstate__(self, state):
+ for key, value in state.items():
+ if key not in ('_version_id','__weakref__'):
+ setattr(self, key, value)
+
class Base(object):
"""
This is the controlling class for the signatures for the collection of
@@ -202,7 +226,7 @@ class DB(Base):
# Read using the path relative to the top of the Repository
# (self.dir.tpath) from which we're fetching the signature
# information.
- path = normcase(dir.tpath)
+ path = normcase(dir.get_tpath())
try:
rawentries = db[path]
except KeyError:
@@ -217,7 +241,7 @@ class DB(Base):
raise
except Exception, e:
SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
- "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.tpath, e))
+ "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.get_tpath(), e))
for key, entry in self.entries.items():
entry.convert_from_sconsign(dir, key)
@@ -244,7 +268,7 @@ class DB(Base):
# directory (self.dir.path), not relative to the top of
# the Repository; we only write to our own .sconsign file,
# not to .sconsign files in Repositories.
- path = normcase(self.dir.path)
+ path = normcase(self.dir.get_internal_path())
for key, entry in self.entries.items():
entry.convert_to_sconsign()
db[path] = pickle.dumps(self.entries, 1)
@@ -287,7 +311,7 @@ class DirFile(Dir):
"""
self.dir = dir
- self.sconsign = os.path.join(dir.path, '.sconsign')
+ self.sconsign = os.path.join(dir.get_internal_path(), '.sconsign')
try:
fp = open(self.sconsign, 'rb')
@@ -323,7 +347,7 @@ class DirFile(Dir):
self.merge()
- temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
+ temp = os.path.join(self.dir.get_internal_path(), '.scons%d' % os.getpid())
try:
file = open(temp, 'wb')
fname = temp
diff --git a/src/engine/SCons/SConsignTests.py b/src/engine/SCons/SConsignTests.py
index f71e53e..d40a7b6 100644
--- a/src/engine/SCons/SConsignTests.py
+++ b/src/engine/SCons/SConsignTests.py
@@ -62,6 +62,10 @@ class DummyNode(object):
return self.binfo
def get_binfo(self):
return self.binfo
+ def get_internal_path(self):
+ return self.path
+ def get_tpath(self):
+ return self.tpath
class SConsignTestCase(unittest.TestCase):
def setUp(self):
diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py
index 6418754..9c7df12 100644
--- a/src/engine/SCons/Scanner/CTests.py
+++ b/src/engine/SCons/Scanner/CTests.py
@@ -270,17 +270,18 @@ class CScannerTestCase5(unittest.TestCase):
path = s.path(env)
n = env.File('f3.cpp')
- def my_rexists(s=n):
- s.rexists_called = 1
- return s.old_rexists()
- setattr(n, 'old_rexists', n.rexists)
- setattr(n, 'rexists', my_rexists)
+ def my_rexists(s):
+ s.Tag('rexists_called', 1)
+ return SCons.Node._rexists_map[s.GetTag('old_rexists')](s)
+ n.Tag('old_rexists', n._func_rexists)
+ SCons.Node._rexists_map[3] = my_rexists
+ n._func_rexists = 3
deps = s(n, env, path)
# Make sure rexists() got called on the file node being
# scanned, essential for cooperation with VariantDir functionality.
- assert n.rexists_called
+ assert n.GetTag('rexists_called')
headers = ['f1.h', 'f2.h', 'f3-test.h',
'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h']
diff --git a/src/engine/SCons/Scanner/Dir.py b/src/engine/SCons/Scanner/Dir.py
index 1cecfb7..cbfb6fb 100644
--- a/src/engine/SCons/Scanner/Dir.py
+++ b/src/engine/SCons/Scanner/Dir.py
@@ -77,7 +77,7 @@ def scan_on_disk(node, env, path=()):
that and then call the in-memory scanning function.
"""
try:
- flist = node.fs.listdir(node.abspath)
+ flist = node.fs.listdir(node.get_abspath())
except (IOError, OSError):
return []
e = node.Entry
diff --git a/src/engine/SCons/Scanner/FortranTests.py b/src/engine/SCons/Scanner/FortranTests.py
index 252da64..aaefa79 100644
--- a/src/engine/SCons/Scanner/FortranTests.py
+++ b/src/engine/SCons/Scanner/FortranTests.py
@@ -356,17 +356,18 @@ class FortranScannerTestCase9(unittest.TestCase):
path = s.path(env)
n = env.File('fff3.f')
- def my_rexists(s=n):
- s.rexists_called = 1
- return s.old_rexists()
- setattr(n, 'old_rexists', n.rexists)
- setattr(n, 'rexists', my_rexists)
+ def my_rexists(s):
+ s.Tag('rexists_called', 1)
+ return SCons.Node._rexists_map[s.GetTag('old_rexists')](s)
+ n.Tag('old_rexists', n._func_rexists)
+ SCons.Node._rexists_map[3] = my_rexists
+ n._func_rexists = 3
deps = s(n, env, path)
# Make sure rexists() got called on the file node being
# scanned, essential for cooperation with VariantDir functionality.
- assert n.rexists_called
+ assert n.GetTag('rexists_called')
headers = ['d1/f3.f', 'f3.f']
deps_match(self, deps, headers)
diff --git a/src/engine/SCons/Scanner/IDLTests.py b/src/engine/SCons/Scanner/IDLTests.py
index 675c70c..227799e 100644
--- a/src/engine/SCons/Scanner/IDLTests.py
+++ b/src/engine/SCons/Scanner/IDLTests.py
@@ -290,17 +290,18 @@ class IDLScannerTestCase5(unittest.TestCase):
path = s.path(env)
n = env.File('t3.idl')
- def my_rexists(s=n):
- s.rexists_called = 1
- return s.old_rexists()
- setattr(n, 'old_rexists', n.rexists)
- setattr(n, 'rexists', my_rexists)
+ def my_rexists(s):
+ s.Tag('rexists_called', 1)
+ return SCons.Node._rexists_map[s.GetTag('old_rexists')](s)
+ n.Tag('old_rexists', n._func_rexists)
+ SCons.Node._rexists_map[3] = my_rexists
+ n._func_rexists = 3
deps = s(n, env, path)
# Make sure rexists() got called on the file node being
# scanned, essential for cooperation with VariantDir functionality.
- assert n.rexists_called
+ assert n.GetTag('rexists_called')
headers = ['d1/f1.idl', 'd1/f2.idl',
'f1.idl', 'f2.idl', 'f3-test.idl',
diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py
index c7a9d27..bd227bd 100644
--- a/src/engine/SCons/Script/Main.py
+++ b/src/engine/SCons/Script/Main.py
@@ -214,7 +214,7 @@ class BuildTask(SCons.Taskmaster.OutOfDateTask):
if self.top and not t.has_builder() and not t.side_effect:
if not t.exists():
if t.__class__.__name__ in ('File', 'Dir', 'Entry'):
- errstr="Do not know how to make %s target `%s' (%s)." % (t.__class__.__name__, t, t.abspath)
+ errstr="Do not know how to make %s target `%s' (%s)." % (t.__class__.__name__, t, t.get_abspath())
else: # Alias or Python or ...
errstr="Do not know how to make %s target `%s'." % (t.__class__.__name__, t)
sys.stderr.write("scons: *** " + errstr)
@@ -351,7 +351,7 @@ class CleanTask(SCons.Taskmaster.AlwaysTask):
if target in SCons.Environment.CleanTargets:
files = SCons.Environment.CleanTargets[target]
for f in files:
- self.fs_delete(f.abspath, str(f), remove)
+ self.fs_delete(f.get_abspath(), str(f), remove)
def show(self):
for t in self._get_files_to_clean():
@@ -484,6 +484,9 @@ def GetOption(name):
def SetOption(name, value):
return OptionsParser.values.set_option(name, value)
+def PrintHelp(file=None):
+ OptionsParser.print_help(file=file)
+
#
class Stats(object):
def __init__(self):
@@ -672,7 +675,7 @@ def _set_debug_values(options):
if "prepare" in debug_values:
SCons.Taskmaster.print_prepare = 1
if "duplicate" in debug_values:
- SCons.Node.FS.print_duplicate = 1
+ SCons.Node.print_duplicate = 1
def _create_path(plist):
path = '.'
@@ -946,9 +949,9 @@ def _main(parser):
progress_display.set_mode(0)
if options.site_dir:
- _load_site_scons_dir(d.path, options.site_dir)
+ _load_site_scons_dir(d.get_internal_path(), options.site_dir)
elif not options.no_site_dir:
- _load_all_site_scons_dirs(d.path)
+ _load_all_site_scons_dirs(d.get_internal_path())
if options.include_dir:
sys.path = options.include_dir + sys.path
@@ -1111,7 +1114,6 @@ def _build_targets(fs, options, targets, target_top):
display.set_mode(not options.silent)
SCons.Action.print_actions = not options.silent
SCons.Action.execute_actions = not options.no_exec
- SCons.Node.FS.do_store_info = not options.no_exec
SCons.Node.do_store_info = not options.no_exec
SCons.SConf.dryrun = options.no_exec
diff --git a/src/engine/SCons/Script/SConsOptions.py b/src/engine/SCons/Script/SConsOptions.py
index 8ecc093..68c60cc 100644
--- a/src/engine/SCons/Script/SConsOptions.py
+++ b/src/engine/SCons/Script/SConsOptions.py
@@ -268,7 +268,7 @@ class SConsOptionParser(optparse.OptionParser):
preserve_unknown_options = False
def error(self, msg):
- # overriden OptionValueError exception handler
+ # overridden OptionValueError exception handler
self.print_usage(sys.stderr)
sys.stderr.write("SCons Error: %s\n" % msg)
sys.exit(2)
diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py
index f4a7f07..b832ff7 100644
--- a/src/engine/SCons/Script/SConscript.py
+++ b/src/engine/SCons/Script/SConscript.py
@@ -265,7 +265,7 @@ def _SConscript(fs, *files, **kw):
call_stack[-1].globals.update({__file__:old_file})
else:
SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
- "Ignoring missing SConscript '%s'" % f.path)
+ "Ignoring missing SConscript '%s'" % f.get_internal_path())
finally:
SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
@@ -438,7 +438,7 @@ class SConsEnvironment(SCons.Environment.Base):
fname = fn.get_path(src_dir)
files = [os.path.join(str(variant_dir), fname)]
else:
- files = [fn.abspath]
+ files = [fn.get_abspath()]
kw['src_dir'] = variant_dir
self.fs.VariantDir(variant_dir, src_dir, duplicate)
@@ -499,9 +499,9 @@ class SConsEnvironment(SCons.Environment.Base):
name = self.subst(name)
return SCons.Script.Main.GetOption(name)
- def Help(self, text):
+ def Help(self, text, append=False):
text = self.subst(text, raw=1)
- SCons.Script.HelpFunction(text)
+ SCons.Script.HelpFunction(text, append=append)
def Import(self, *vars):
try:
diff --git a/src/engine/SCons/Script/SConscript.xml b/src/engine/SCons/Script/SConscript.xml
index c74ad5e..8553fbe 100644
--- a/src/engine/SCons/Script/SConscript.xml
+++ b/src/engine/SCons/Script/SConscript.xml
@@ -240,7 +240,7 @@ file is found.
<scons_function name="Help">
<arguments>
-(text)
+(text, append=False)
</arguments>
<summary>
<para>
@@ -248,12 +248,18 @@ This specifies help text to be printed if the
<option>-h</option>
argument is given to
&scons;.
-If
+If
&f-Help;
-is called multiple times, the text is appended together in the order
-that
+is called multiple times, the text is appended together in the order that
&f-Help;
-is called.
+is called. With append set to False, any
+&f-Help;
+text generated with
+&f-AddOption;
+is clobbered. If append is True, the AddOption help is prepended to the help
+string, thus preserving the
+<option>-h</option>
+message.
</para>
</summary>
</scons_function>
diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py
index bb7b632..6bfc36f 100644
--- a/src/engine/SCons/Script/__init__.py
+++ b/src/engine/SCons/Script/__init__.py
@@ -41,6 +41,7 @@ start_time = time.time()
import collections
import os
+import StringIO
import sys
# Special chicken-and-egg handling of the "--debug=memoizer" flag:
@@ -107,6 +108,7 @@ QuestionTask = Main.QuestionTask
#SConscriptSettableOptions = Main.SConscriptSettableOptions
AddOption = Main.AddOption
+PrintHelp = Main.PrintHelp
GetOption = Main.GetOption
SetOption = Main.SetOption
Progress = Main.Progress
@@ -258,12 +260,25 @@ def _Set_Default_Targets(env, tlist):
#
help_text = None
-def HelpFunction(text):
+def HelpFunction(text, append=False):
global help_text
- if SCons.Script.help_text is None:
- SCons.Script.help_text = text
- else:
- help_text = help_text + text
+ if help_text is None:
+ if append:
+ s = StringIO.StringIO()
+ PrintHelp(s)
+ help_text = s.getvalue()
+ s.close()
+ else:
+ help_text = ""
+#
+# Was in original patch but this text is arbitrary and breaks tests
+# so I removed it (Deegan)
+# help_text = help_text + "\nLocal Build Variables:\n" + text
+# else:
+# help_text = help_text + text
+
+ help_text= help_text + text
+
#
# Will be non-zero if we are reading an SConscript file.
@@ -318,6 +333,7 @@ GlobalDefaultEnvironmentFunctions = [
'Ignore',
'Install',
'InstallAs',
+ 'InstallVersionedLib',
'Literal',
'Local',
'ParseDepends',
diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py
index 5de1cda..345534e 100644
--- a/src/engine/SCons/Taskmaster.py
+++ b/src/engine/SCons/Taskmaster.py
@@ -242,7 +242,7 @@ class Task(object):
#
for t in cached_targets:
try:
- t.fs.unlink(t.path)
+ t.fs.unlink(t.get_internal_path())
except (IOError, OSError):
pass
self.targets[0].build()
diff --git a/src/engine/SCons/Tool/MSCommon/arch.py b/src/engine/SCons/Tool/MSCommon/arch.py
index 2c90950..9bcc221 100644
--- a/src/engine/SCons/Tool/MSCommon/arch.py
+++ b/src/engine/SCons/Tool/MSCommon/arch.py
@@ -51,6 +51,12 @@ SupportedArchitectureList = [
'ia64',
['IA64'],
),
+
+ ArchitectureDefinition(
+ 'arm',
+ ['ARM'],
+ ),
+
]
SupportedArchitectureMap = {}
diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py
index 97cb349..0ee6324 100644
--- a/src/engine/SCons/Tool/MSCommon/vc.py
+++ b/src/engine/SCons/Tool/MSCommon/vc.py
@@ -134,9 +134,13 @@ def get_host_target(env):
# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the
# MSVC_VERSION documentation in Tool/msvc.xml.
-_VCVER = ["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 = ["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.0' : [
+ r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir'],
+ '14.0Exp' : [
+ r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'],
'12.0' : [
r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'],
'12.0Exp' : [
diff --git a/src/engine/SCons/Tool/MSCommon/vs.py b/src/engine/SCons/Tool/MSCommon/vs.py
index 2ec403b..d9eb1e3 100644
--- a/src/engine/SCons/Tool/MSCommon/vs.py
+++ b/src/engine/SCons/Tool/MSCommon/vs.py
@@ -203,6 +203,28 @@ class VisualStudio(object):
# Tool/MSCommon/vc.py, and the MSVC_VERSION documentation in Tool/msvc.xml.
SupportedVSList = [
+ # Visual Studio 2015
+ VisualStudio('14.0',
+ vc_version='14.0',
+ sdk_version='10.0A',
+ hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'],
+ common_tools_var='VS140COMNTOOLS',
+ executable_path=r'Common7\IDE\devenv.com',
+ batch_file_path=r'Common7\Tools\vsvars32.bat',
+ supported_arch=['x86', 'amd64', "arm"],
+ ),
+
+ # Visual C++ 2015 Express Edition (for Desktop)
+ VisualStudio('14.0Exp',
+ vc_version='14.0',
+ sdk_version='10.0A',
+ hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'],
+ common_tools_var='VS140COMNTOOLS',
+ executable_path=r'Common7\IDE\WDExpress.exe',
+ batch_file_path=r'Common7\Tools\vsvars32.bat',
+ supported_arch=['x86', 'amd64', "arm"],
+ ),
+
# Visual Studio 2013
VisualStudio('12.0',
vc_version='12.0',
diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py
index 2621cf9..7374687 100644
--- a/src/engine/SCons/Tool/__init__.py
+++ b/src/engine/SCons/Tool/__init__.py
@@ -236,150 +236,436 @@ def createStaticLibBuilder(env):
return static_lib
-def VersionShLibLinkNames(version, libname, env):
- """Generate names of symlinks to the versioned shared library"""
+def _call_linker_cb(env, callback, args, result = None):
+ """Returns the result of env['LINKCALLBACKS'][callback](*args)
+ if env['LINKCALLBACKS'] is a dictionary and env['LINKCALLBACKS'][callback]
+ is callable. If these conditions are not met, return the value provided as
+ the *result* argument. This function is mainly used for generating library
+ info such as versioned suffixes, symlink maps, sonames etc. by delegating
+ the core job to callbacks configured by current linker tool"""
+
Verbose = False
- platform = env.subst('$PLATFORM')
- shlib_suffix = env.subst('$SHLIBSUFFIX')
- shlink_flags = SCons.Util.CLVar(env.subst('$SHLINKFLAGS'))
-
- linknames = []
- if version.count(".") != 2:
- # We need a version string of the form x.y.z to proceed
- # Several changes need to be made to support versions like x.y
- raise ValueError
-
- if platform == 'darwin':
- # For libfoo.x.y.z.dylib, linknames libfoo.so
- suffix_re = re.escape('.' + version + shlib_suffix)
- linkname = re.sub(suffix_re, shlib_suffix, libname)
+
+ if Verbose:
+ print '_call_linker_cb: args=%r' % args
+ print '_call_linker_cb: callback=%r' % callback
+
+ try:
+ cbfun = env['LINKCALLBACKS'][callback]
+ except (KeyError, TypeError):
if Verbose:
- print "VersionShLibLinkNames: linkname = ",linkname
- linknames.append(linkname)
- elif platform == 'posix' or platform == 'sunos':
- if sys.platform.startswith('openbsd'):
- # OpenBSD uses x.y shared library versioning numbering convention
- # and doesn't use symlinks to backwards-compatible libraries
- return []
- # For libfoo.so.x.y.z, linknames libfoo.so libfoo.so.x.y libfoo.so.x
- suffix_re = re.escape(shlib_suffix + '.' + version)
- # First linkname has no version number
- linkname = re.sub(suffix_re, shlib_suffix, libname)
+ print '_call_linker_cb: env["LINKCALLBACKS"][%r] not found or can not be used' % callback
+ pass
+ else:
if Verbose:
- print "VersionShLibLinkNames: linkname = ",linkname
- linknames.append(linkname)
- versionparts = version.split('.')
- major_name = linkname + "." + versionparts[0]
- minor_name = major_name + "." + versionparts[1]
- #Only add link for major_name
- #for linkname in [major_name, minor_name]:
- for linkname in [major_name, ]:
+ print '_call_linker_cb: env["LINKCALLBACKS"][%r] found' % callback
+ print '_call_linker_cb: env["LINKCALLBACKS"][%r]=%r' % (callback, cbfun)
+ if(callable(cbfun)):
if Verbose:
- print "VersionShLibLinkNames: linkname ",linkname, ", target ",libname
- linknames.append(linkname)
- # note: no Windows case here (win32 or cygwin);
- # MSVC doesn't support this type of versioned shared libs.
- # (could probably do something for MinGW though)
- return linknames
-
-def VersionedSharedLibrary(target = None, source= None, env=None):
- """Build a shared library. If the environment has SHLIBVERSION
-defined make a versioned shared library and create the appropriate
-symlinks for the platform we are on"""
- Verbose = False
- try:
- version = env.subst('$SHLIBVERSION')
- except KeyError:
- version = None
+ print '_call_linker_cb: env["LINKCALLBACKS"][%r] is callable' % callback
+ result = cbfun(env, *args)
+ return result
- # libname includes the version number if one was given
- libname = getattr(target[0].attributes, 'shlibname', target[0].name)
- platform = env.subst('$PLATFORM')
- shlib_suffix = env.subst('$SHLIBSUFFIX')
- shlink_flags = SCons.Util.CLVar(env.subst('$SHLINKFLAGS'))
- if Verbose:
- print "VersionShLib: libname = ",libname
- print "VersionShLib: platform = ",platform
- print "VersionShLib: shlib_suffix = ",shlib_suffix
- print "VersionShLib: target = ",str(target[0])
-
- if version:
- # set the shared library link flags
- if platform == 'posix':
- shlink_flags += [ '-Wl,-Bsymbolic' ]
- # OpenBSD doesn't usually use SONAME for libraries
- if not sys.platform.startswith('openbsd'):
- # continue setup of shlink flags for all other POSIX systems
- suffix_re = re.escape(shlib_suffix + '.' + version)
- (major, age, revision) = version.split(".")
- # soname will have only the major version number in it
- soname = re.sub(suffix_re, shlib_suffix, libname) + '.' + major
- shlink_flags += [ '-Wl,-soname=%s' % soname ]
- if Verbose:
- print " soname ",soname,", shlink_flags ",shlink_flags
- elif platform == 'sunos':
- suffix_re = re.escape(shlib_suffix + '.' + version)
- (major, age, revision) = version.split(".")
- soname = re.sub(suffix_re, shlib_suffix, libname) + '.' + major
- shlink_flags += [ '-h', soname ]
- elif platform == 'cygwin':
- shlink_flags += [ '-Wl,-Bsymbolic',
- '-Wl,--out-implib,${TARGET.base}.a' ]
- elif platform == 'darwin':
- shlink_flags += [ '-current_version', '%s' % version,
- '-compatibility_version', '%s' % version,
- '-undefined', 'dynamic_lookup' ]
+def _call_env_subst(env, string, *args, **kw):
+ kw2 = {}
+ for k in ('raw', 'target', 'source', 'conv', 'executor'):
+ try: kw2[k] = kw[k]
+ except KeyError: pass
+ return env.subst(string, *args, **kw2)
+
+class _ShLibInfoSupport(object):
+ def get_libtype(self):
+ return 'ShLib'
+ def get_lib_prefix(self, env, *args, **kw):
+ return _call_env_subst(env,'$SHLIBPREFIX', *args, **kw)
+ def get_lib_suffix(self, env, *args, **kw):
+ return _call_env_subst(env,'$SHLIBSUFFIX', *args, **kw)
+ def get_lib_version(self, env, *args, **kw):
+ return _call_env_subst(env,'$SHLIBVERSION', *args, **kw)
+ def get_lib_noversionsymlinks(self, env, *args, **kw):
+ return _call_env_subst(env,'$SHLIBNOVERSIONSYMLINKS', *args, **kw)
+
+class _LdModInfoSupport(object):
+ def get_libtype(self):
+ return 'LdMod'
+ def get_lib_prefix(self, env, *args, **kw):
+ return _call_env_subst(env,'$LDMODULEPREFIX', *args, **kw)
+ def get_lib_suffix(self, env, *args, **kw):
+ return _call_env_subst(env,'$LDMODULESUFFIX', *args, **kw)
+ def get_lib_version(self, env, *args, **kw):
+ return _call_env_subst(env,'$LDMODULEVERSION', *args, **kw)
+ def get_lib_noversionsymlinks(self, env, *args, **kw):
+ return _call_env_subst(env,'$LDMODULENOVERSIONSYMLINKS', *args, **kw)
+
+class _ImpLibInfoSupport(object):
+ def get_libtype(self):
+ return 'ImpLib'
+ def get_lib_prefix(self, env, *args, **kw):
+ return _call_env_subst(env,'$IMPLIBPREFIX', *args, **kw)
+ def get_lib_suffix(self, env, *args, **kw):
+ return _call_env_subst(env,'$IMPLIBSUFFIX', *args, **kw)
+ def get_lib_version(self, env, *args, **kw):
+ version = _call_env_subst(env,'$IMPLIBVERSION', *args, **kw)
+ if not version:
+ try: lt = kw['implib_libtype']
+ except KeyError: pass
+ else:
+ if lt == 'ShLib':
+ version = _call_env_subst(env,'$SHLIBVERSION', *args, **kw)
+ elif lt == 'LdMod':
+ version = _call_env_subst(env,'$LDMODULEVERSION', *args, **kw)
+ return version
+ def get_lib_noversionsymlinks(self, env, *args, **kw):
+ disable = None
+ try: env['IMPLIBNOVERSIONSYMLINKS']
+ except KeyError:
+ try: lt = kw['implib_libtype']
+ except KeyError: pass
+ else:
+ if lt == 'ShLib':
+ disable = _call_env_subst(env,'$SHLIBNOVERSIONSYMLINKS', *args, **kw)
+ elif lt == 'LdMod':
+ disable = _call_env_subst(env,'$LDMODULENOVERSIONSYMLINKS', *args, **kw)
+ else:
+ disable = _call_env_subst(env,'$IMPLIBNOVERSIONSYMLINKS', *args, **kw)
+ return disable
+
+class _LibInfoGeneratorBase(object):
+ """Generator base class for library-related info such as suffixes for
+ versioned libraries, symlink maps, sonames etc. It handles commonities
+ of SharedLibrary and LoadableModule
+ """
+ _support_classes = { 'ShLib' : _ShLibInfoSupport,
+ 'LdMod' : _LdModInfoSupport,
+ 'ImpLib' : _ImpLibInfoSupport }
+ def __init__(self, libtype, infoname):
+ self.set_libtype(libtype)
+ self.set_infoname(infoname)
+
+ def set_libtype(self, libtype):
+ try:
+ support_class = self._support_classes[libtype]
+ except KeyError:
+ raise ValueError('unsupported libtype %r' % libtype)
+ self._support = support_class()
+
+ def get_libtype(self):
+ return self._support.get_libtype()
+
+ def set_infoname(self, infoname):
+ self.infoname = infoname
+
+ def get_infoname(self):
+ return self.infoname
+
+ def get_lib_prefix(self, env, *args, **kw):
+ return self._support.get_lib_prefix(env,*args,**kw)
+
+ def get_lib_suffix(self, env, *args, **kw):
+ return self._support.get_lib_suffix(env,*args,**kw)
+
+ def get_lib_version(self, env, *args, **kw):
+ return self._support.get_lib_version(env,*args,**kw)
+
+ def get_lib_noversionsymlinks(self, env, *args, **kw):
+ return self._support.get_lib_noversionsymlinks(env,*args,**kw)
+
+ # Returns name of generator linker callback that shall be used to generate
+ # our info for a versioned library. For example, if our libtype is 'ShLib'
+ # and infoname is 'Prefix', it would return 'VersionedShLibPrefix'.
+ def get_versioned_lib_info_generator(self, **kw):
+ try: libtype = kw['generator_libtype']
+ except KeyError: libtype = self.get_libtype()
+ infoname = self.get_infoname()
+ return 'Versioned%s%s' % (libtype, infoname)
+
+ def generate_versioned_lib_info(self, env, args, result = None, **kw):
+ callback = self.get_versioned_lib_info_generator(**kw)
+ return _call_linker_cb(env, callback, args, result)
+
+class _LibPrefixGenerator(_LibInfoGeneratorBase):
+ """Library prefix generator, used as target_prefix in SharedLibrary and
+ LoadableModule builders"""
+ def __init__(self, libtype):
+ super(_LibPrefixGenerator, self).__init__(libtype, 'Prefix')
+
+ def __call__(self, env, sources = None, **kw):
+ Verbose = False
+
+ if sources and 'source' not in kw:
+ kw2 = kw.copy()
+ kw2['source'] = sources
+ else:
+ kw2 = kw
+
+ prefix = self.get_lib_prefix(env,**kw2)
if Verbose:
- print "VersionShLib: shlink_flags = ",shlink_flags
- envlink = env.Clone()
- envlink['SHLINKFLAGS'] = shlink_flags
- else:
- envlink = env
+ print "_LibPrefixGenerator: input prefix=%r" % prefix
+
+ version = self.get_lib_version(env, **kw2)
+ if Verbose:
+ print "_LibPrefixGenerator: version=%r" % version
- result = SCons.Defaults.ShLinkAction(target, source, envlink)
+ if version:
+ prefix = self.generate_versioned_lib_info(env, [prefix, version], prefix, **kw2)
- if version:
- # here we need the full pathname so the links end up in the right directory
- libname = getattr(target[0].attributes, 'shlibpath', target[0].path)
if Verbose:
- print "VerShLib: target lib is = ", libname
- print "VerShLib: name is = ", target[0].name
- print "VerShLib: dir is = ", target[0].dir.path
- linknames = VersionShLibLinkNames(version, libname, env)
+ print "_LibPrefixGenerator: return prefix=%r" % prefix
+ return prefix
+
+ShLibPrefixGenerator = _LibPrefixGenerator('ShLib')
+LdModPrefixGenerator = _LibPrefixGenerator('LdMod')
+ImpLibPrefixGenerator = _LibPrefixGenerator('ImpLib')
+
+class _LibSuffixGenerator(_LibInfoGeneratorBase):
+ """Library suffix generator, used as target_suffix in SharedLibrary and
+ LoadableModule builders"""
+ def __init__(self, libtype):
+ super(_LibSuffixGenerator, self).__init__(libtype, 'Suffix')
+
+ def __call__(self, env, sources = None, **kw):
+ Verbose = False
+
+ if sources and 'source' not in kw:
+ kw2 = kw.copy()
+ kw2['source'] = sources
+ else:
+ kw2 = kw
+
+ suffix = self.get_lib_suffix(env, **kw2)
if Verbose:
- print "VerShLib: linknames ",linknames
- # Here we just need the file name w/o path as the target of the link
- lib_ver = getattr(target[0].attributes, 'shlibname', target[0].name)
- # make symlink of adjacent names in linknames
- for count in range(len(linknames)):
- linkname = linknames[count]
- if count > 0:
- try:
- os.remove(lastlinkname)
- except:
- pass
- os.symlink(os.path.basename(linkname),lastlinkname)
- if Verbose:
- print "VerShLib: made sym link of %s -> %s" % (lastlinkname,linkname)
- lastlinkname = linkname
- # finish chain of sym links with link to the actual library
- if len(linknames)>0:
+ print "_LibSuffixGenerator: input suffix=%r" % suffix
+
+ version = self.get_lib_version(env, **kw2)
+ if Verbose:
+ print "_LibSuffixGenerator: version=%r" % version
+
+ if version:
+ suffix = self.generate_versioned_lib_info(env, [suffix, version], suffix, **kw2)
+
+ if Verbose:
+ print "_LibSuffixGenerator: return suffix=%r" % suffix
+ return suffix
+
+ShLibSuffixGenerator = _LibSuffixGenerator('ShLib')
+LdModSuffixGenerator = _LibSuffixGenerator('LdMod')
+ImpLibSuffixGenerator = _LibSuffixGenerator('ImpLib')
+
+class _LibSymlinkGenerator(_LibInfoGeneratorBase):
+ """Library symlink map generator. It generates a list of symlinks that
+ should be created by SharedLibrary or LoadableModule builders"""
+ def __init__(self, libtype):
+ super(_LibSymlinkGenerator, self).__init__(libtype, 'Symlinks')
+
+ def __call__(self, env, libnode, **kw):
+ Verbose = False
+
+ if libnode and 'target' not in kw:
+ kw2 = kw.copy()
+ kw2['target'] = libnode
+ else:
+ kw2 = kw
+
+ if Verbose:
+ print "_LibSymLinkGenerator: libnode=%r" % libnode.get_path()
+
+ symlinks = None
+
+ version = self.get_lib_version(env, **kw2)
+ disable = self.get_lib_noversionsymlinks(env, **kw2)
+ if Verbose:
+ print '_LibSymlinkGenerator: version=%r' % version
+ print '_LibSymlinkGenerator: disable=%r' % disable
+
+ if version and not disable:
+ prefix = self.get_lib_prefix(env,**kw2)
+ suffix = self.get_lib_suffix(env,**kw2)
+ symlinks = self.generate_versioned_lib_info(env, [libnode, version, prefix, suffix], **kw2)
+
+ if Verbose:
+ print '_LibSymlinkGenerator: return symlinks=%r' % StringizeLibSymlinks(symlinks)
+ return symlinks
+
+ShLibSymlinkGenerator = _LibSymlinkGenerator('ShLib')
+LdModSymlinkGenerator = _LibSymlinkGenerator('LdMod')
+ImpLibSymlinkGenerator = _LibSymlinkGenerator('ImpLib')
+
+class _LibNameGenerator(_LibInfoGeneratorBase):
+ """Generates "unmangled" library name from a library file node.
+
+ Generally, it's thought to revert modifications done by prefix/suffix
+ generators (_LibPrefixGenerator/_LibSuffixGenerator) used by a library
+ builder. For example, on gnulink the suffix generator used by SharedLibrary
+ builder appends $SHLIBVERSION to $SHLIBSUFFIX producing node name which
+ ends with "$SHLIBSUFFIX.$SHLIBVERSION". Correspondingly, the implementation
+ of _LibNameGenerator replaces "$SHLIBSUFFIX.$SHLIBVERSION" with
+ "$SHLIBSUFFIX" in the node's basename. So that, if $SHLIBSUFFIX is ".so",
+ $SHLIBVERSION is "0.1.2" and the node path is "/foo/bar/libfoo.so.0.1.2",
+ the _LibNameGenerator shall return "libfoo.so". Other link tools may
+ implement it's own way of library name unmangling.
+ """
+ def __init__(self, libtype):
+ super(_LibNameGenerator, self).__init__(libtype, 'Name')
+
+ def __call__(self, env, libnode, **kw):
+ """Returns "demangled" library name"""
+ Verbose = False
+
+ if libnode and 'target' not in kw:
+ kw2 = kw.copy()
+ kw2['target'] = libnode
+ else:
+ kw2 = kw
+
+ if Verbose:
+ print "_LibNameGenerator: libnode=%r" % libnode.get_path()
+
+ version = self.get_lib_version(env, **kw2)
+ if Verbose:
+ print '_LibNameGenerator: version=%r' % version
+
+ name = None
+ if version:
+ prefix = self.get_lib_prefix(env,**kw2)
+ suffix = self.get_lib_suffix(env,**kw2)
+ name = self.generate_versioned_lib_info(env, [libnode, version, prefix, suffix], **kw2)
+
+ if not name:
+ name = os.path.basename(libnode.get_path())
+
+ if Verbose:
+ print '_LibNameGenerator: return name=%r' % name
+
+ return name
+
+ShLibNameGenerator = _LibNameGenerator('ShLib')
+LdModNameGenerator = _LibNameGenerator('LdMod')
+ImpLibNameGenerator = _LibNameGenerator('ImpLib')
+
+class _LibSonameGenerator(_LibInfoGeneratorBase):
+ """Library soname generator. Returns library soname (e.g. libfoo.so.0) for
+ a given node (e.g. /foo/bar/libfoo.so.0.1.2)"""
+ def __init__(self, libtype):
+ super(_LibSonameGenerator, self).__init__(libtype, 'Soname')
+
+ def __call__(self, env, libnode, **kw):
+ """Returns a SONAME based on a shared library's node path"""
+ Verbose = False
+
+ if libnode and 'target' not in kw:
+ kw2 = kw.copy()
+ kw2['target'] = libnode
+ else:
+ kw2 = kw
+
+ if Verbose:
+ print "_LibSonameGenerator: libnode=%r" % libnode.get_path()
+
+ soname = _call_env_subst(env, '$SONAME', **kw2)
+ if not soname:
+ version = self.get_lib_version(env,**kw2)
+ if Verbose:
+ print "_LibSonameGenerator: version=%r" % version
+ if version:
+ prefix = self.get_lib_prefix(env,**kw2)
+ suffix = self.get_lib_suffix(env,**kw2)
+ soname = self.generate_versioned_lib_info(env, [libnode, version, prefix, suffix], **kw2)
+
+ if not soname:
+ # fallback to library name (as returned by appropriate _LibNameGenerator)
+ soname = _LibNameGenerator(self.get_libtype())(env, libnode)
+ if Verbose:
+ print "_LibSonameGenerator: FALLBACK: soname=%r" % soname
+
+ if Verbose:
+ print "_LibSonameGenerator: return soname=%r" % soname
+
+ return soname
+
+ShLibSonameGenerator = _LibSonameGenerator('ShLib')
+LdModSonameGenerator = _LibSonameGenerator('LdMod')
+
+def StringizeLibSymlinks(symlinks):
+ """Converts list with pairs of nodes to list with pairs of node paths
+ (strings). Used mainly for debugging."""
+ if SCons.Util.is_List(symlinks):
+ try:
+ return [ (k.get_path(), v.get_path()) for k,v in symlinks ]
+ except (TypeError, ValueError):
+ return symlinks
+ else:
+ return symlinks
+
+def EmitLibSymlinks(env, symlinks, libnode, **kw):
+ """Used by emitters to handle (shared/versioned) library symlinks"""
+ Verbose = False
+
+ # nodes involved in process... all symlinks + library
+ nodes = list(set([ x for x,y in symlinks ] + [libnode]))
+
+ clean_targets = kw.get('clean_targets', [])
+ if not SCons.Util.is_List(clean_targets):
+ clean_targets = [ clean_targets ]
+
+ for link, linktgt in symlinks:
+ env.SideEffect(link, linktgt)
+ if(Verbose):
+ print "EmitLibSymlinks: SideEffect(%r,%r)" % (link.get_path(), linktgt.get_path())
+ clean_list = filter(lambda x : x != linktgt, nodes)
+ env.Clean(list(set([linktgt] + clean_targets)), clean_list)
+ if(Verbose):
+ print "EmitLibSymlinks: Clean(%r,%r)" % (linktgt.get_path(), map(lambda x : x.get_path(), clean_list))
+
+def CreateLibSymlinks(env, symlinks):
+ """Physically creates symlinks. The symlinks argument must be a list in
+ form [ (link, linktarget), ... ], where link and linktarget are SCons
+ nodes.
+ """
+
+ Verbose = False
+ for link, linktgt in symlinks:
+ linktgt = link.get_dir().rel_path(linktgt)
+ link = link.get_path()
+ if(Verbose):
+ print "CreateLibSymlinks: preparing to add symlink %r -> %r" % (link, linktgt)
+ # Delete the (previously created) symlink if exists. Let only symlinks
+ # to be deleted to prevent accidental deletion of source files...
+ if env.fs.islink(link):
+ env.fs.unlink(link)
+ if(Verbose):
+ print "CreateLibSymlinks: removed old symlink %r" % link
+ # If a file or directory exists with the same name as link, an OSError
+ # will be thrown, which should be enough, I think.
+ env.fs.symlink(linktgt, link)
+ if(Verbose):
+ print "CreateLibSymlinks: add symlink %r -> %r" % (link, linktgt)
+ return 0
+
+def LibSymlinksActionFunction(target, source, env):
+ for tgt in target:
+ symlinks = getattr(getattr(tgt,'attributes', None), 'shliblinks', None)
+ if symlinks:
+ CreateLibSymlinks(env, symlinks)
+ return 0
+
+def LibSymlinksStrFun(target, source, env, *args):
+ cmd = None
+ for tgt in target:
+ symlinks = getattr(getattr(tgt,'attributes', None), 'shliblinks', None)
+ if symlinks:
+ if cmd is None: cmd = ""
+ if cmd: cmd += "\n"
+ cmd += "Create symlinks for: %r" % tgt.get_path()
try:
- os.remove(lastlinkname)
- except:
+ linkstr = ', '.join([ "%r->%r" %(k,v) for k,v in StringizeLibSymlinks(symlinks)])
+ except (KeyError, ValueError):
pass
- os.symlink(lib_ver,lastlinkname)
- if Verbose:
- print "VerShLib: made sym link of %s -> %s" % (linkname, lib_ver)
- return result
+ else:
+ cmd += ": %s" % linkstr
+ return cmd
+
-# Fix http://scons.tigris.org/issues/show_bug.cgi?id=2903 :
-# Ensure we still depend on SCons.Defaults.ShLinkAction command line which is $SHLINKCOM.
-# This was tricky because we don't want changing LIBPATH to cause a rebuild, but
-# changing other link args should. LIBPATH has $( ... $) around it but until this
-# fix, when the varlist was added to the build sig those ignored parts weren't getting
-# ignored.
-ShLibAction = SCons.Action.Action(VersionedSharedLibrary, None, varlist=['SHLINKCOM'])
+LibSymlinksAction = SCons.Action.Action(LibSymlinksActionFunction, LibSymlinksStrFun)
def createSharedLibBuilder(env):
"""This is a utility function that creates the SharedLibrary
@@ -393,11 +679,12 @@ def createSharedLibBuilder(env):
except KeyError:
import SCons.Defaults
action_list = [ SCons.Defaults.SharedCheck,
- ShLibAction ]
+ SCons.Defaults.ShLinkAction,
+ LibSymlinksAction ]
shared_lib = SCons.Builder.Builder(action = action_list,
emitter = "$SHLIBEMITTER",
- prefix = '$SHLIBPREFIX',
- suffix = '$SHLIBSUFFIX',
+ prefix = ShLibPrefixGenerator,
+ suffix = ShLibSuffixGenerator,
target_scanner = ProgramScanner,
src_suffix = '$SHOBJSUFFIX',
src_builder = 'SharedObject')
@@ -417,11 +704,12 @@ def createLoadableModuleBuilder(env):
except KeyError:
import SCons.Defaults
action_list = [ SCons.Defaults.SharedCheck,
- SCons.Defaults.LdModuleLinkAction ]
+ SCons.Defaults.LdModuleLinkAction,
+ LibSymlinksAction ]
ld_module = SCons.Builder.Builder(action = action_list,
emitter = "$LDMODULEEMITTER",
- prefix = '$LDMODULEPREFIX',
- suffix = '$LDMODULESUFFIX',
+ prefix = LdModPrefixGenerator,
+ suffix = LdModSuffixGenerator,
target_scanner = ProgramScanner,
src_suffix = '$SHOBJSUFFIX',
src_builder = 'SharedObject')
diff --git a/src/engine/SCons/Tool/__init__.xml b/src/engine/SCons/Tool/__init__.xml
index ee56fc3..69cc597 100644
--- a/src/engine/SCons/Tool/__init__.xml
+++ b/src/engine/SCons/Tool/__init__.xml
@@ -200,22 +200,20 @@ For maximum portability, use the &b-LoadableModule; builder for the latter.
When the &cv-link-SHLIBVERSION; construction variable is defined a versioned
shared library is created. This modifies the &cv-link-SHLINKFLAGS; as required,
adds the version number to the library name, and creates the symlinks that
-are needed. &cv-link-SHLIBVERSION; needs to be of the form X.Y.Z, where X
-and Y are numbers, and Z is a number but can also contain letters to designate
-alpha, beta, or release candidate patch levels.
+are needed.
</para>
<example_commands>
env.SharedLibrary(target = 'bar', source = ['bar.c', 'foo.o'], SHLIBVERSION='1.5.2')
</example_commands>
-
<para>
-This builder may create multiple links to the library. On a POSIX system,
-for the shared library libbar.so.2.3.1, the links created would be
-libbar.so and libbar.so.2; on a Darwin (OSX) system
-the library would be libbar.2.3.1.dylib and the link would be
-libbar.dylib.
+On a POSIX system, versions with a single token create exactly one symlink:
+libbar.so.6 would have symlinks libbar.so only.
+On a POSIX system, versions with two or more
+tokens create exactly two symlinks: libbar.so.2.3.1 would have symlinks
+libbar.so and libbar.so.2; on a Darwin (OSX) system the library would be
+libbar.2.3.1.dylib and the link would be libbar.dylib.
</para>
<para>
@@ -464,6 +462,17 @@ as C++ files.
</summary>
</cvar>
+<cvar name="IMPLIBVERSION">
+<summary>
+<para>
+Used to override &cv-link-SHLIBVERSION;/&cv-link-LDMODULEVERSION; when
+generating versioned import library for a shared library/loadable module. If
+undefined, the &cv-link-SHLIBVERSION;/&cv-link-LDMODULEVERSION; is used to
+determine the version of versioned import library.
+</para>
+</summary>
+</cvar>
+
<cvar name="LIBEMITTER">
<summary>
<para>
@@ -472,6 +481,19 @@ TODO
</summary>
</cvar>
+<cvar name="LDMODULEVERSION">
+<summary>
+<para>
+When this construction variable is defined, a versioned loadable module
+is created by &b-link-LoadableModule; builder. This activates the
+&cv-link-_LDMODULEVERSIONFLAGS; and thus modifies the &cv-link-LDMODULECOM; as
+required, adds the version number to the library name, and creates the symlinks
+that are needed. &cv-link-LDMODULEVERSION; versions should exist in the same
+format as &cv-link-SHLIBVERSION;.
+</para>
+</summary>
+</cvar>
+
<cvar name="SHLIBEMITTER">
<summary>
<para>
@@ -492,11 +514,12 @@ TODO
<summary>
<para>
When this construction variable is defined, a versioned shared library
-is created. This modifies the &cv-link-SHLINKFLAGS; as required, adds
-the version number to the library name, and creates the symlinks that
-are needed. &cv-link-SHLIBVERSION; needs to be of the form X.Y.Z,
-where X and Y are numbers, and Z is a number but can also contain
-letters to designate alpha, beta, or release candidate patch levels.
+is created by &b-link-SharedLibrary; builder. This activates the
+&cv-link-_SHLIBVERSIONFLAGS; and thus modifies the &cv-link-SHLINKCOM; as
+required, adds the version number to the library name, and creates the symlinks
+that are needed. &cv-link-SHLIBVERSION; versions should exist as alpha-numeric,
+decimal-delimited values as defined by the regular expression "\w+[\.\w+]*".
+Example &cv-link-SHLIBVERSION; values include '1', '1.2.3', and '1.2.gitaa412c8b'.
</para>
</summary>
</cvar>
diff --git a/src/engine/SCons/Tool/cyglink.py b/src/engine/SCons/Tool/cyglink.py
index 87716cf..5230910 100644
--- a/src/engine/SCons/Tool/cyglink.py
+++ b/src/engine/SCons/Tool/cyglink.py
@@ -7,19 +7,27 @@ It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
+import re
+import os
import SCons.Action
import SCons.Util
+import SCons.Tool
import gnulink
+import link
-def shlib_generator(target, source, env, for_signature):
- cmd = SCons.Util.CLVar(['$SHLINK'])
+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, 'SHLIBPREFIX', 'SHLIBSUFFIX')
+ dll = env.FindIxes(target, '%sPREFIX' % vp, '%sSUFFIX' % vp)
if dll: cmd.extend(['-o', dll])
- cmd.extend(['$SHLINKFLAGS', '$__RPATH'])
+ cmd.extend(['$SHLINKFLAGS', '$__%sVERSIONFLAGS' % vp, '$__RPATH'])
implib = env.FindIxes(target, 'IMPLIBPREFIX', 'IMPLIBSUFFIX')
if implib:
@@ -35,37 +43,141 @@ def shlib_generator(target, source, env, for_signature):
return [cmd]
-def shlib_emitter(target, source, env):
- dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX')
+
+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("$SHLIBSUFFIX"))
+ 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('$SHLIBPREFIX')
+ 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],
- 'SHLIBPREFIX', 'SHLIBSUFFIX',
+ '%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 lirbrary.
+ 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."""
@@ -74,8 +186,9 @@ def generate(env):
env['LINKFLAGS'] = SCons.Util.CLVar('-Wl,-no-undefined')
env['SHLINKCOM'] = shlib_action
- env['LDMODULECOM'] = shlib_action
+ env['LDMODULECOM'] = ldmod_action
env.Append(SHLIBEMITTER = [shlib_emitter])
+ env.Append(LDMODULEEMITTER = [ldmod_emitter])
env['SHLIBPREFIX'] = 'cyg'
env['SHLIBSUFFIX'] = '.dll'
@@ -83,6 +196,31 @@ def generate(env):
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)
diff --git a/src/engine/SCons/Tool/cyglink.xml b/src/engine/SCons/Tool/cyglink.xml
new file mode 100644
index 0000000..42208f1
--- /dev/null
+++ b/src/engine/SCons/Tool/cyglink.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+__COPYRIGHT__
+
+This file is processed by the bin/SConsDoc.py module.
+See its __doc__ string for a discussion of the format.
+-->
+
+<!DOCTYPE sconsdoc [
+<!ENTITY % scons SYSTEM '../../../../doc/scons.mod'>
+%scons;
+<!ENTITY % builders-mod SYSTEM '../../../../doc/generated/builders.mod'>
+%builders-mod;
+<!ENTITY % functions-mod SYSTEM '../../../../doc/generated/functions.mod'>
+%functions-mod;
+<!ENTITY % tools-mod SYSTEM '../../../../doc/generated/tools.mod'>
+%tools-mod;
+<!ENTITY % variables-mod SYSTEM '../../../../doc/generated/variables.mod'>
+%variables-mod;
+]>
+
+<sconsdoc xmlns="http://www.scons.org/dbxsd/v1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.scons.org/dbxsd/v1.0 http://www.scons.org/dbxsd/v1.0/scons.xsd">
+
+<tool name="cyglink">
+<summary>
+<para>
+Set construction variables for cygwin linker/loader.
+</para>
+</summary>
+<sets>
+<item>IMPLIBPREFIX</item>
+<item>IMPLIBSUFFIX</item>
+<item>LDMODULEVERSIONFLAGS</item>
+<item>LINKFLAGS</item>
+<item>RPATHPREFIX</item>
+<item>RPATHSUFFIX</item>
+<item>SHLIBPREFIX</item>
+<item>SHLIBSUFFIX</item>
+<item>SHLIBVERSIONFLAGS</item>
+<item>SHLINKCOM</item>
+<item>SHLINKFLAGS</item>
+<item>_LDMODULEVERSIONFLAGS</item>
+<item>_SHLIBVERSIONFLAGS</item>
+</sets>
+</tool>
+
+</sconsdoc>
diff --git a/src/engine/SCons/Tool/dmd.py b/src/engine/SCons/Tool/dmd.py
index a7d46c6..3722936 100644
--- a/src/engine/SCons/Tool/dmd.py
+++ b/src/engine/SCons/Tool/dmd.py
@@ -114,7 +114,7 @@ def generate(env):
env['DSHLINK'] = '$DC'
env['DSHLINKFLAGS'] = SCons.Util.CLVar('$DLINKFLAGS -shared -defaultlib=libphobos2.so')
- env['SHDLINKCOM'] = '$DLINK -of$TARGET $DSHLINKFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS'
+ env['SHDLINKCOM'] = '$DLINK -of$TARGET $DSHLINKFLAGS $__DSHLIBVERSIONFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS'
env['DLIBLINKPREFIX'] = '' if env['PLATFORM'] == 'win32' else '-L-l'
env['DLIBLINKSUFFIX'] = '.lib' if env['PLATFORM'] == 'win32' else ''
@@ -139,6 +139,17 @@ def generate(env):
env['DRPATHSUFFIX'] = ''
env['_DRPATH'] = '${_concat(DRPATHPREFIX, RPATH, DRPATHSUFFIX, __env__)}'
+ # Support for versioned libraries
+ env['_DSHLIBVERSIONFLAGS'] = '$DSHLIBVERSIONFLAGS -L-soname=$_DSHLIBSONAME'
+ env['_DSHLIBSONAME'] = '${DShLibSonameGenerator(__env__,TARGET)}'
+ # NOTE: this is a quick hack, the soname will only work if there is
+ # c/c++ linker loaded which provides callback for the ShLibSonameGenerator
+ env['DShLibSonameGenerator'] = SCons.Tool.ShLibSonameGenerator
+ # NOTE: this is only for further reference, currently $DSHLIBVERSION does
+ # not work, the user must use $SHLIBVERSION
+ env['DSHLIBVERSION'] = '$SHLIBVERSION'
+ env['DSHLIBVERSIONFLAGS'] = []
+
SCons.Tool.createStaticLibBuilder(env)
diff --git a/src/engine/SCons/Tool/docbook/__init__.py b/src/engine/SCons/Tool/docbook/__init__.py
index 26a1a95..aead43c 100644
--- a/src/engine/SCons/Tool/docbook/__init__.py
+++ b/src/engine/SCons/Tool/docbook/__init__.py
@@ -448,7 +448,7 @@ def DocbookEpub(env, target, source=None, *args, **kw):
Ensure all the resources in the manifest are present in the OEBPS directory.
"""
hrefs = []
- content_file = os.path.join(source[0].abspath, 'content.opf')
+ content_file = os.path.join(source[0].get_abspath(), 'content.opf')
if not os.path.isfile(content_file):
return
@@ -491,9 +491,9 @@ def DocbookEpub(env, target, source=None, *args, **kw):
for href in hrefs:
# If the resource was not already created by DocBook XSL itself,
# copy it into the OEBPS folder
- referenced_file = os.path.join(source[0].abspath, href)
+ referenced_file = os.path.join(source[0].get_abspath(), href)
if not os.path.exists(referenced_file):
- shutil.copy(href, os.path.join(source[0].abspath, href))
+ shutil.copy(href, os.path.join(source[0].get_abspath(), href))
# Init list of targets/sources
target, source = __extend_targets_sources(target, source)
diff --git a/src/engine/SCons/Tool/gdc.py b/src/engine/SCons/Tool/gdc.py
index 799c3ab..32199b3 100644
--- a/src/engine/SCons/Tool/gdc.py
+++ b/src/engine/SCons/Tool/gdc.py
@@ -97,7 +97,7 @@ def generate(env):
env['DSHLINK'] = '$DC'
env['DSHLINKFLAGS'] = SCons.Util.CLVar('$DLINKFLAGS -shared')
- env['SHDLINKCOM'] = '$DLINK -o $TARGET $DSHLINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
+ env['SHDLINKCOM'] = '$DLINK -o $TARGET $DSHLINKFLAGS $__DSHLIBVERSIONFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
env['DLIB'] = 'lib' if env['PLATFORM'] == 'win32' else 'ar cr'
env['DLIBCOM'] = '$DLIB $_DLIBFLAGS {0}$TARGET $SOURCES $_DLINKLIBFLAGS'.format('-c ' if env['PLATFORM'] == 'win32' else '')
@@ -115,6 +115,17 @@ def generate(env):
env['RPATHSUFFIX'] = ''
env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}'
+ # Support for versioned libraries
+ env['_DSHLIBVERSIONFLAGS'] = '$DSHLIBVERSIONFLAGS -Wl,-soname=$_DSHLIBSONAME'
+ env['_DSHLIBSONAME'] = '${DShLibSonameGenerator(__env__,TARGET)}'
+ # NOTE: this is a quick hack, the soname will only work if there is
+ # c/c++ linker loaded which provides callback for the ShLibSonameGenerator
+ env['DShLibSonameGenerator'] = SCons.Tool.ShLibSonameGenerator
+ # NOTE: this is only for further reference, currently $DSHLIBVERSION does
+ # not work, the user must use $SHLIBVERSION
+ env['DSHLIBVERSION'] = '$SHLIBVERSION'
+ env['DSHLIBVERSIONFLAGS'] = '$SHLIBVERSIONFLAGS'
+
SCons.Tool.createStaticLibBuilder(env)
diff --git a/src/engine/SCons/Tool/gettext.xml b/src/engine/SCons/Tool/gettext.xml
index 96e467a..f9f8b81 100644
--- a/src/engine/SCons/Tool/gettext.xml
+++ b/src/engine/SCons/Tool/gettext.xml
@@ -27,7 +27,7 @@ See its __doc__ string for a discussion of the format.
<summary>
<para>
This is actually a toolset, which supports internationalization and
-localization of sofware being constructed with SCons. The toolset loads
+localization of software being constructed with SCons. The toolset loads
following tools:
</para>
diff --git a/src/engine/SCons/Tool/gnulink.py b/src/engine/SCons/Tool/gnulink.py
index 3dc8f51..6b0d5b3 100644
--- a/src/engine/SCons/Tool/gnulink.py
+++ b/src/engine/SCons/Tool/gnulink.py
@@ -34,9 +34,14 @@ selection method.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import SCons.Util
+import SCons.Tool
+import os
+import sys
+import re
import link
+
def generate(env):
"""Add Builders and construction variables for gnulink to an Environment."""
link.generate(env)
@@ -49,6 +54,14 @@ def generate(env):
env['RPATHPREFIX'] = '-Wl,-rpath='
env['RPATHSUFFIX'] = ''
env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}'
+
+ # OpenBSD doesn't usually use SONAME for libraries
+ use_soname = not sys.platform.startswith('openbsd')
+ link._setup_versioned_lib_variables(env, tool = 'gnulink', use_soname = use_soname)
+ env['LINKCALLBACKS'] = link._versioned_lib_callbacks()
+
+ # For backward-compatiblity with older SCons versions
+ env['SHLIBVERSIONFLAGS'] = SCons.Util.CLVar('-Wl,-Bsymbolic')
def exists(env):
# TODO: sync with link.smart_link() to choose a linker
diff --git a/src/engine/SCons/Tool/gnulink.xml b/src/engine/SCons/Tool/gnulink.xml
index 2a36de2..0e055c7 100644
--- a/src/engine/SCons/Tool/gnulink.xml
+++ b/src/engine/SCons/Tool/gnulink.xml
@@ -33,6 +33,10 @@ Set construction variables for GNU linker/loader.
<item>SHLINKFLAGS</item>
<item>RPATHPREFIX</item>
<item>RPATHSUFFIX</item>
+<item>_LDMODULESONAME</item>
+<item>_SHLIBSONAME</item>
+<item>LDMODULEVERSIONFLAGS</item>
+<item>SHLIBVERSIONFLAGS</item>
</sets>
</tool>
diff --git a/src/engine/SCons/Tool/install.py b/src/engine/SCons/Tool/install.py
index 9f2e937..9d5db9f 100644
--- a/src/engine/SCons/Tool/install.py
+++ b/src/engine/SCons/Tool/install.py
@@ -38,6 +38,7 @@ import shutil
import stat
import SCons.Action
+import SCons.Tool
from SCons.Util import make_path_relative
#
@@ -141,98 +142,35 @@ def copyFuncVersionedLib(dest, source, env):
shutil.copy2(source, dest)
st = os.stat(source)
os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
- versionedLibLinks(dest, source, env)
+ installShlibLinks(dest, source, env)
return 0
-def versionedLibVersion(dest, source, env):
- """Check if dest is a version shared library name. Return version, libname, & install_dir if it is."""
+def listShlibLinksToInstall(dest, source, env):
+ install_links = []
+ source = env.arg2nodes(source)
+ dest = env.fs.File(dest)
+ install_dir = dest.get_dir()
+ for src in source:
+ symlinks = getattr(getattr(src,'attributes',None), 'shliblinks', None)
+ if symlinks:
+ for link, linktgt in symlinks:
+ link_base = os.path.basename(link.get_path())
+ linktgt_base = os.path.basename(linktgt.get_path())
+ install_link = env.fs.File(link_base, install_dir)
+ install_linktgt = env.fs.File(linktgt_base, install_dir)
+ install_links.append((install_link, install_linktgt))
+ return install_links
+
+def installShlibLinks(dest, source, env):
+ """If we are installing a versioned shared library create the required links."""
Verbose = False
- platform = env.subst('$PLATFORM')
- if not (platform == 'posix' or platform == 'darwin' or platform == 'sunos'):
- return (None, None, None)
- if (hasattr(source[0], 'attributes') and
- hasattr(source[0].attributes, 'shlibname')):
- libname = source[0].attributes.shlibname
- else:
- libname = os.path.basename(str(dest))
- install_dir = os.path.dirname(str(dest))
- shlib_suffix = env.subst('$SHLIBSUFFIX')
- # See if the source name is a versioned shared library, get the version number
- result = False
-
- version_re = re.compile("[0-9]+\\.[0-9]+\\.[0-9a-zA-Z]+")
- version_File = None
- if platform == 'posix' or platform == 'sunos':
- # handle unix names
- versioned_re = re.compile(re.escape(shlib_suffix + '.') + "[0-9]+\\.[0-9]+\\.[0-9a-zA-Z]+")
- result = versioned_re.findall(libname)
- if result:
- version_File = version_re.findall(versioned_re.findall(libname)[-1])[-1]
- elif platform == 'darwin':
- # handle OSX names
- versioned_re = re.compile("\\.[0-9]+\\.[0-9]+\\.[0-9a-zA-Z]+" + re.escape(shlib_suffix) )
- result = versioned_re.findall(libname)
- if result:
- version_File = version_re.findall(versioned_re.findall(libname)[-1])[-1]
-
+ symlinks = listShlibLinksToInstall(dest, source, env)
if Verbose:
- print "install: version_File ", version_File
- # result is False if we did not find a versioned shared library name, so return and empty list
- if not result:
- return (None, libname, install_dir)
-
- version = None
- # get version number from the environment
- try:
- version = env.subst('$SHLIBVERSION')
- except KeyError:
- version = None
-
- if version != version_File:
- #raise SCons.Errors.UserError("SHLIBVERSION '%s' does not match the version # '%s' in the filename" % (version, version_File) )
- print "SHLIBVERSION '%s' does not match the version # '%s' in the filename, proceeding based on file name" % (version, version_File)
- version = version_File
- return (version, libname, install_dir)
-
-def versionedLibLinks(dest, source, env):
- """If we are installing a versioned shared library create the required links."""
- Verbose = False
- linknames = []
- version, libname, install_dir = versionedLibVersion(dest, source, env)
-
- if version != None:
- # libname includes the version number if one was given
- linknames = SCons.Tool.VersionShLibLinkNames(version,libname,env)
- if Verbose:
- print "versionedLibLinks: linknames ",linknames
- # Here we just need the file name w/o path as the target of the link
- lib_ver = libname
- # make symlink of adjacent names in linknames
- for count in range(len(linknames)):
- linkname = linknames[count]
- fulllinkname = os.path.join(install_dir, linkname)
- if Verbose:
- print "full link name ",fulllinkname
- if count > 0:
- try:
- os.remove(lastlinkname)
- except:
- pass
- os.symlink(os.path.basename(fulllinkname),lastlinkname)
- if Verbose:
- print "versionedLibLinks: made sym link of %s -> %s" % (lastlinkname,os.path.basename(fulllinkname))
- lastlinkname = fulllinkname
- # finish chain of sym links with link to the actual library
- if len(linknames)>0:
- try:
- os.remove(lastlinkname)
- except:
- pass
- os.symlink(lib_ver,lastlinkname)
- if Verbose:
- print "versionedLibLinks: made sym link of %s -> %s" % (lib_ver,lastlinkname)
+ print 'installShlibLinks: symlinks=%r' % SCons.Tool.StringizeLibSymlinks(symlinks)
+ if symlinks:
+ SCons.Tool.CreateLibSymlinks(env, symlinks)
return
def installFunc(target, source, env):
@@ -306,22 +244,11 @@ def add_versioned_targets_to_INSTALLED_FILES(target, source, env):
Verbose = False
_INSTALLED_FILES.extend(target)
if Verbose:
- print "ver lib emitter ",repr(target)
-
- # see if we have a versioned shared library, if so generate side effects
- version, libname, install_dir = versionedLibVersion(target[0], source, env)
- if version != None:
- # generate list of link names
- linknames = SCons.Tool.VersionShLibLinkNames(version,libname,env)
- for linkname in linknames:
- if Verbose:
- print "make side effect of %s" % os.path.join(install_dir, linkname)
- fulllinkname = os.path.join(install_dir, linkname)
- env.SideEffect(fulllinkname,target[0])
- env.Clean(target[0],fulllinkname)
- _INSTALLED_FILES.append(fulllinkname)
- if Verbose:
- print "installed list ", _INSTALLED_FILES
+ print "add_versioned_targets_to_INSTALLED_FILES: target=%r" % map(str, target)
+
+ symlinks = listShlibLinksToInstall(target[0], source, env)
+ if symlinks:
+ SCons.Tool.EmitLibSymlinks(env, symlinks, target[0])
_UNIQUE_INSTALLED_FILES = None
return (target, source)
diff --git a/src/engine/SCons/Tool/install.xml b/src/engine/SCons/Tool/install.xml
index 0aa9384..6ae3e30 100644
--- a/src/engine/SCons/Tool/install.xml
+++ b/src/engine/SCons/Tool/install.xml
@@ -82,20 +82,13 @@ env.InstallAs(target = ['../lib/libfoo.a', '../lib/libbar.a'],
<builder name="InstallVersionedLib">
<summary>
<para>
-Installs a versioned shared library. The &cv-link-SHLIBVERSION;
-construction variable should be defined in the environment
-to confirm the version number in the library name.
-If &cv-link-SHLIBVERSION; is not defined a warning will be issued
-and the name of the library will be parsed to derive the version.
-The symlinks appropriate to the architecture will be generated.
+Installs a versioned shared library. The symlinks appropriate to the
+architecture will be generated based on symlinks of the source library.
</para>
<example_commands>
env.InstallVersionedLib(target = '/usr/local/bin/foo',
source = 'libxyz.1.5.2.so')
-env.InstallVersionedLib(target = '/usr/local/bin/foo',
- source = 'libxyz.1.5.2.so',
- SHLIBVERSION='1.5.2')
</example_commands>
</summary>
</builder>
diff --git a/src/engine/SCons/Tool/ldc.py b/src/engine/SCons/Tool/ldc.py
index 8f9b117..ade95db 100644
--- a/src/engine/SCons/Tool/ldc.py
+++ b/src/engine/SCons/Tool/ldc.py
@@ -105,7 +105,7 @@ def generate(env):
# Hack for Fedora the packages of which use the wrong name :-(
if os.path.exists('/usr/lib64/libphobos-ldc.so') or os.path.exists('/usr/lib32/libphobos-ldc.so') or os.path.exists('/usr/lib/libphobos-ldc.so') :
env['DSHLINKFLAGS'] = SCons.Util.CLVar('$DLINKFLAGS -shared -defaultlib=phobos-ldc')
- env['SHDLINKCOM'] = '$DLINK -of=$TARGET $DSHLINKFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS'
+ env['SHDLINKCOM'] = '$DLINK -of=$TARGET $DSHLINKFLAGS $__DSHLIBVERSIONFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS'
env['DLIBLINKPREFIX'] = '' if env['PLATFORM'] == 'win32' else '-L-l'
env['DLIBLINKSUFFIX'] = '.lib' if env['PLATFORM'] == 'win32' else ''
@@ -131,6 +131,17 @@ def generate(env):
env['DRPATHSUFFIX'] = ''
env['_DRPATH'] = '${_concat(DRPATHPREFIX, RPATH, DRPATHSUFFIX, __env__)}'
+ # Support for versioned libraries
+ env['_DSHLIBVERSIONFLAGS'] = '$DSHLIBVERSIONFLAGS -L-soname=$_DSHLIBSONAME'
+ env['_DSHLIBSONAME'] = '${DShLibSonameGenerator(__env__,TARGET)}'
+ # NOTE: this is a quick hack, the soname will only work if there is
+ # c/c++ linker loaded which provides callback for the ShLibSonameGenerator
+ env['DShLibSonameGenerator'] = SCons.Tool.ShLibSonameGenerator
+ # NOTE: this is only for further reference, currently $DSHLIBVERSION does
+ # not work, the user must use $SHLIBVERSION
+ env['DSHLIBVERSION'] = '$SHLIBVERSION'
+ env['DSHLIBVERSIONFLAGS'] = []
+
SCons.Tool.createStaticLibBuilder(env)
diff --git a/src/engine/SCons/Tool/link.py b/src/engine/SCons/Tool/link.py
index 2624946..a4a2a4c 100644
--- a/src/engine/SCons/Tool/link.py
+++ b/src/engine/SCons/Tool/link.py
@@ -33,9 +33,10 @@ selection method.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import sys
import re
+import os
-import SCons.Defaults
import SCons.Tool
import SCons.Util
import SCons.Warnings
@@ -72,97 +73,205 @@ def smart_link(source, target, env, for_signature):
return '$CXX'
return '$CC'
-def shlib_emitter(target, source, env):
+def _lib_emitter(target, source, env, **kw):
Verbose = False
- platform = env.subst('$PLATFORM')
+ if Verbose:
+ print "_lib_emitter: target[0]=%r" % target[0].get_path()
for tgt in target:
tgt.attributes.shared = 1
+
try:
- # target[0] comes in as libtest.so. Add the version extensions
- version = env.subst('$SHLIBVERSION')
- if version:
- version_names = shlib_emitter_names(target, source, env)
- # mark the target with the shared libraries name, including
- # the version number
- target[0].attributes.shlibname = version_names[0]
- shlib = env.File(version_names[0], directory=target[0].get_dir())
- target[0].attributes.shlibpath = shlib.path
- for name in version_names[1:]:
- env.SideEffect(name, shlib)
- env.Clean(shlib, name)
- if Verbose:
- print "shlib_emitter: add side effect - ",name
- env.Clean(shlib, target[0])
- return ([shlib], source)
+ symlink_generator = kw['symlink_generator']
except KeyError:
- version = None
+ pass
+ else:
+ if Verbose:
+ print "_lib_emitter: symlink_generator=%r" % symlink_generator
+ symlinks = symlink_generator(env, target[0])
+ if Verbose:
+ print "_lib_emitter: symlinks=%r" % symlinks
+
+ if symlinks:
+ SCons.Tool.EmitLibSymlinks(env, symlinks, target[0])
+ target[0].attributes.shliblinks = symlinks
return (target, source)
-def shlib_emitter_names(target, source, env):
- """Return list of file names that are side effects for a versioned library build. The first name in the list is the new name for the target"""
+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
- platform = env.subst('$PLATFORM')
- version_names = []
- try:
- # target[0] comes in as libtest.so. Add the version extensions
- version = env.subst('$SHLIBVERSION')
- if version.count(".") != 2:
- # We need a version of the form x.y.z to proceed
- raise ValueError
- if version:
- if platform == 'posix' or platform == 'sunos':
- versionparts = version.split('.')
- if hasattr(target[0].attributes, 'shlibname'):
- name = target[0].attributes.shlibname
- else:
- name = target[0].name
- # generate library name with the version number
- version_name = name + '.' + version
- if Verbose:
- print "shlib_emitter_names: target is ", version_name
- print "shlib_emitter_names: side effect: ", name
- # add version_name to list of names to be a Side effect
- version_names.append(version_name)
- if Verbose:
- print "shlib_emitter_names: versionparts ",versionparts
- for ver in versionparts[0:-1]:
- name = name + '.' + ver
- if Verbose:
- print "shlib_emitter_names: side effect: ", name
- # add name to list of names to be a Side effect
- version_names.append(name)
- elif platform == 'darwin':
- shlib_suffix = env.subst('$SHLIBSUFFIX')
- if hasattr(target[0].attributes, 'shlibname'):
- name = target[0].attributes.shlibname
- else:
- name = target[0].name
- # generate library name with the version number
- suffix_re = re.escape(shlib_suffix)
- version_name = re.sub(suffix_re, '.' + version + shlib_suffix, name)
- if Verbose:
- print "shlib_emitter_names: target is ", version_name
- print "shlib_emitter_names: side effect: ", name
- # add version_name to list of names to be a Side effect
- version_names.append(version_name)
- elif platform == 'cygwin':
- shlib_suffix = env.subst('$SHLIBSUFFIX')
- if hasattr(target[0].attributes, 'shlibname'):
- name = target[0].attributes.shlibname
- else:
- name = target[0].name
- # generate library name with the version number
- suffix_re = re.escape(shlib_suffix)
- version_name = re.sub(suffix_re, '-' + re.sub('\.', '-', version) + shlib_suffix, name)
- if Verbose:
- print "shlib_emitter_names: target is ", version_name
- print "shlib_emitter_names: side effect: ", name
- # add version_name to list of names to be a Side effect
- version_names.append(version_name)
- except KeyError:
- version = None
- return version_names
+ if Verbose:
+ print "_versioned_lib_name: libnode=%r" % libnode.get_path()
+ print "_versioned_lib_name: version=%r" % version
+ print "_versioned_lib_name: prefix=%r" % prefix
+ print "_versioned_lib_name: suffix=%r" % suffix
+ print "_versioned_lib_name: suffix_generator=%r" % suffix_generator
+
+ versioned_name = os.path.basename(libnode.get_path())
+ if Verbose:
+ print "_versioned_lib_name: versioned_name=%r" % versioned_name
+
+ versioned_prefix = prefix_generator(env, **kw)
+ versioned_suffix = suffix_generator(env, **kw)
+ if Verbose:
+ print "_versioned_lib_name: versioned_prefix=%r" % versioned_prefix
+ print "_versioned_lib_name: versioned_suffix=%r" % 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" % 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" % suffix
+ print "_versioned_lib_suffix: version=%r" % version
+ if not suffix.endswith(version):
+ suffix = suffix + '.' + version
+ if Verbose:
+ print "_versioned_lib_suffix: return suffix=%r" % 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" % version
+ name = name_func(env, libnode, version, prefix, suffix)
+ if Verbose:
+ print "_versioned_lib_soname: name=%r" % name
+ major = version.split('.')[0]
+ soname = name + '.' + major
+ if Verbose:
+ print "_versioned_lib_soname: soname=%r" % 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" % libnode.get_path()
+ print "_versioned_lib_symlinks: version=%r" % 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" % None
+ return None
+
+ linkdir = libnode.get_dir()
+ if Verbose:
+ print "_versioned_lib_symlinks: linkdir=%r" % linkdir.get_path()
+
+ name = name_func(env, libnode, version, prefix, suffix)
+ if Verbose:
+ print "_versioned_lib_symlinks: name=%r" % 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" % 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()
+
+# Setup all variables required by the versioning machinery
+def _setup_versioned_lib_variables(env, **kw):
+
+ 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."""
@@ -171,7 +280,7 @@ def generate(env):
env['SHLINK'] = '$LINK'
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared')
- env['SHLINKCOM'] = '$SHLINK -o $TARGET $SHLINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
+ 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])
@@ -196,15 +305,13 @@ def generate(env):
# setting them the same means that LoadableModule works everywhere.
SCons.Tool.createLoadableModuleBuilder(env)
env['LDMODULE'] = '$SHLINK'
- # don't set up the emitter, cause AppendUnique will generate a list
- # starting with None :-(
- env.Append(LDMODULEEMITTER='$SHLIBEMITTER')
+ env.Append(LDMODULEEMITTER = [ldmod_emitter])
env['LDMODULEPREFIX'] = '$SHLIBPREFIX'
env['LDMODULESUFFIX'] = '$SHLIBSUFFIX'
env['LDMODULEFLAGS'] = '$SHLINKFLAGS'
- env['LDMODULECOM'] = '$LDMODULE -o $TARGET $LDMODULEFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
-
-
+ 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
diff --git a/src/engine/SCons/Tool/link.xml b/src/engine/SCons/Tool/link.xml
index d58b9e2..2f913fe 100644
--- a/src/engine/SCons/Tool/link.xml
+++ b/src/engine/SCons/Tool/link.xml
@@ -41,11 +41,16 @@ Sets construction variables for generic POSIX linkers.
<item>LIBLINKPREFIX</item>
<item>LIBLINKSUFFIX</item>
<item>SHLIBSUFFIX</item>
+<item>__SHLIBVERSIONFLAGS</item>
<item>LDMODULE</item>
<item>LDMODULEPREFIX</item>
<item>LDMODULESUFFIX</item>
<item>LDMODULEFLAGS</item>
<item>LDMODULECOM</item>
+<item>LDMODULEVERSION</item>
+<item>LDMODULENOVERSIONSYMLINKS</item>
+<item>LDMODULEVERSIONFLAGS</item>
+<item>__LDMODULEVERSIONFLAGS</item>
</sets>
<uses>
<item>SHLINKCOMSTR</item>
@@ -54,6 +59,105 @@ Sets construction variables for generic POSIX linkers.
</uses>
</tool>
+<cvar name="__LDMODULEVERSIONFLAGS">
+<summary>
+<para>
+This construction variable automatically introduces &cv-link-_LDMODULEVERSIONFLAGS;
+if &cv-link-LDMODULEVERSION; is set. Othervise it evaluates to an empty string.
+</para>
+</summary>
+</cvar>
+
+<cvar name="__SHLIBVERSIONFLAGS">
+<summary>
+<para>
+This construction variable automatically introduces &cv-link-_SHLIBVERSIONFLAGS;
+if &cv-link-SHLIBVERSION; is set. Othervise it evaluates to an empty string.
+</para>
+</summary>
+</cvar>
+
+<cvar name="_LDMODULESONAME">
+<summary>
+<para>
+A macro that automatically generates loadable module's SONAME based on $TARGET,
+$LDMODULEVERSION and $LDMODULESUFFIX. Used by &b-link-LoadableModule; builder
+when the linker tool supports SONAME (e.g. &t-link-gnulink;).
+</para>
+</summary>
+</cvar>
+
+<cvar name="_LDMODULEVERSIONFLAGS">
+<summary>
+<para>
+This macro automatically introduces extra flags to &cv-link-LDMODULECOM; when
+building versioned &b-link-LoadableModule; (that is when
+&cv-link-LDMODULEVERSION; is set). <literal>_LDMODULEVERSIONFLAGS</literal>
+usually adds &cv-link-SHLIBVERSIONFLAGS; and some extra dynamically generated
+options (such as <literal>-Wl,-soname=$_LDMODULESONAME</literal>). It is unused
+by plain (unversioned) loadable modules.
+</para>
+</summary>
+</cvar>
+
+<cvar name="_SHLIBVERSIONFLAGS">
+<summary>
+<para>
+This macro automatically introduces extra flags to &cv-link-SHLINKCOM; when
+building versioned &b-link-SharedLibrary; (that is when &cv-link-SHLIBVERSION;
+is set). <literal>_SHLIBVERSIONFLAGS</literal> usually adds &cv-link-SHLIBVERSIONFLAGS;
+and some extra dynamically generated options (such as
+<literal>-Wl,-soname=$_SHLIBSONAME</literal>. It is unused by "plain"
+(unversioned) shared libraries.
+</para>
+</summary>
+</cvar>
+
+<cvar name="_SHLIBSONAME">
+<summary>
+<para>
+A macro that automatically generates shared library's SONAME based on $TARGET,
+$SHLIBVERSION and $SHLIBSUFFIX. Used by &b-link-SharedLibrary; builder when
+the linker tool supports SONAME (e.g. &t-link-gnulink;).
+</para>
+</summary>
+</cvar>
+
+<cvar name="IMPLIBPREFIX">
+<summary>
+<para>
+The prefix used for import library names. For example, cygwin uses import
+libraries (<literal>libfoo.dll.a</literal>) in pair with dynamic libraries
+(<literal>cygfoo.dll</literal>). The &t-link-cyglink; linker sets
+&cv-link-IMPLIBPREFIX; to <literal>'lib'</literal> and &cv-link-SHLIBPREFIX;
+to <literal>'cyg'</literal>.
+</para>
+</summary>
+</cvar>
+
+<cvar name="IMPLIBSUFFIX">
+<summary>
+<para>
+The suffix used for import library names. For example, cygwin uses import
+libraries (<literal>libfoo.dll.a</literal>) in pair with dynamic libraries
+(<literal>cygfoo.dll</literal>). The &t-link-cyglink; linker sets
+&cv-link-IMPLIBSUFFIX; to <literal>'.dll.a'</literal> and &cv-link-SHLIBSUFFIX;
+to <literal>'.dll'</literal>.
+</para>
+</summary>
+</cvar>
+
+<cvar name="IMPLIBNOVERSIONSYMLINKS">
+<summary>
+<para>
+Used to override &cv-link-SHLIBNOVERSIONSYMLINKS;/&cv-link-LDMODULENOVERSIONSYMLINKS; when
+creating versioned import library for a shared library/loadable module. If not defined,
+then &cv-link-SHLIBNOVERSIONSYMLINKS;/&cv-link-LDMODULENOVERSIONSYMLINKS; is used to determine
+whether to disable symlink generation or not.
+</para>
+</summary>
+</cvar>
+
<cvar name="LDMODULE">
<summary>
<para>
@@ -92,6 +196,15 @@ General user options passed to the linker for building loadable modules.
</summary>
</cvar>
+<cvar name="LDMODULENOVERSIONSYMLINKS">
+<summary>
+<para>
+Instructs the &b-link-LoadableModule; builder to not automatically create symlinks
+for versioned modules. Defaults to <literal>$SHLIBNOVERSIONSYMLINKS</literal>
+</para>
+</summary>
+</cvar>
+
<cvar name="LDMODULEPREFIX">
<summary>
<para>
@@ -114,6 +227,16 @@ the same as $SHLIBSUFFIX.
</summary>
</cvar>
+<cvar name="LDMODULEVERSIONFLAGS">
+<summary>
+<para>
+Extra flags added to &cv-link-LDMODULECOM; when building versioned
+&b-link-LoadableModule;. These flags are only used when &cv-link-LDMODULEVERSION; is
+set.
+</para>
+</summary>
+</cvar>
+
<cvar name="LINK">
<summary>
<para>
@@ -169,6 +292,25 @@ for the variable that expands to library search path options.
</summary>
</cvar>
+<cvar name="SHLIBNOVERSIONSYMLINKS">
+<summary>
+<para>
+Instructs the &b-link-SharedLibrary; builder to not create symlinks for versioned
+shared libraries.
+</para>
+</summary>
+</cvar>
+
+<cvar name="SHLIBVERSIONFLAGS">
+<summary>
+<para>
+Extra flags added to &cv-link-SHLINKCOM; when building versioned
+&b-link-SharedLibrary;. These flags are only used when &cv-link-SHLIBVERSION; is
+set.
+</para>
+</summary>
+</cvar>
+
<cvar name="SHLINK">
<summary>
<para>
@@ -223,6 +365,18 @@ for the variable that expands to library search path options.
</summary>
</cvar>
+<cvar name="SONAME">
+<summary>
+<para>
+Variable used to hard-code SONAME for versioned shared library/loadable module.
+<example_commands>
+env.SharedLibrary('test', 'test.c', SHLIBVERSION='0.1.2', SONAME='libtest.so.2')
+</example_commands>
+The variable is used, for example, by &t-link-gnulink; linker tool.
+</para>
+</summary>
+</cvar>
+
<cvar name="STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME">
<summary>
<para>
diff --git a/src/engine/SCons/Tool/linkloc.py b/src/engine/SCons/Tool/linkloc.py
index 9c58561..bd643f7 100644
--- a/src/engine/SCons/Tool/linkloc.py
+++ b/src/engine/SCons/Tool/linkloc.py
@@ -86,6 +86,7 @@ def generate(env):
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS')
env['SHLINKCOM'] = '${SUBST_CMD_FILE("$SHLINK $SHLINKFLAGS $_LIBDIRFLAGS $_LIBFLAGS -dll $TARGET $SOURCES")}'
env['SHLIBEMITTER']= None
+ env['LDMODULEEMITTER']= None
env['LINK'] = "linkloc"
env['LINKFLAGS'] = SCons.Util.CLVar('')
env['LINKCOM'] = '${SUBST_CMD_FILE("$LINK $LINKFLAGS $_LIBDIRFLAGS $_LIBFLAGS -exe $TARGET $SOURCES")}'
diff --git a/src/engine/SCons/Tool/midl.xml b/src/engine/SCons/Tool/midl.xml
index 1420978..efd83cc 100644
--- a/src/engine/SCons/Tool/midl.xml
+++ b/src/engine/SCons/Tool/midl.xml
@@ -44,7 +44,7 @@ Sets construction variables for the Microsoft IDL compiler.
<para>
Builds a Windows type library (<filename>.tlb</filename>)
file from an input IDL file (<filename>.idl</filename>).
-In addition, it will build the associated inteface stub and
+In addition, it will build the associated interface stub and
proxy source files,
naming them according to the base name of the <filename>.idl</filename> file.
For example,
diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py
index 601ec3b..948ebe5 100644
--- a/src/engine/SCons/Tool/mingw.py
+++ b/src/engine/SCons/Tool/mingw.py
@@ -146,6 +146,7 @@ def generate(env):
env['SHLINKCOM'] = shlib_action
env['LDMODULECOM'] = shlib_action
env.Append(SHLIBEMITTER = [shlib_emitter])
+ env.Append(LDMODULEEMITTER = [shlib_emitter])
env['AS'] = 'as'
env['WIN32DEFPREFIX'] = ''
diff --git a/src/engine/SCons/Tool/msgmerge.xml b/src/engine/SCons/Tool/msgmerge.xml
index 2bfc6df..139b21c 100644
--- a/src/engine/SCons/Tool/msgmerge.xml
+++ b/src/engine/SCons/Tool/msgmerge.xml
@@ -64,7 +64,7 @@ Target nodes defined through &b-POUpdate; are not built by default
(they're <literal>Ignore</literal>d from <literal>'.'</literal> node). Instead,
they are added automatically to special <literal>Alias</literal>
(<literal>'po-update'</literal> by default). The alias name may be changed
-through the &cv-link-POUPDATE_ALIAS; construction variable. You can easilly
+through the &cv-link-POUPDATE_ALIAS; construction variable. You can easily
update <literal>PO</literal> files in your project by <command>scons
po-update</command>.
</para>
diff --git a/src/engine/SCons/Tool/mslink.py b/src/engine/SCons/Tool/mslink.py
index 827161e..1390c20 100644
--- a/src/engine/SCons/Tool/mslink.py
+++ b/src/engine/SCons/Tool/mslink.py
@@ -216,7 +216,7 @@ def embedManifestDllCheck(target, source, env):
"""Function run by embedManifestDllCheckAction to check for existence of manifest
and other conditions, and embed the manifest by calling embedManifestDllAction if so."""
if env.get('WINDOWS_EMBED_MANIFEST', 0):
- manifestSrc = target[0].abspath + '.manifest'
+ manifestSrc = target[0].get_abspath() + '.manifest'
if os.path.exists(manifestSrc):
ret = (embedManifestDllAction) ([target[0]],None,env)
if ret:
@@ -230,7 +230,7 @@ def embedManifestExeCheck(target, source, env):
"""Function run by embedManifestExeCheckAction to check for existence of manifest
and other conditions, and embed the manifest by calling embedManifestExeAction if so."""
if env.get('WINDOWS_EMBED_MANIFEST', 0):
- manifestSrc = target[0].abspath + '.manifest'
+ manifestSrc = target[0].get_abspath() + '.manifest'
if os.path.exists(manifestSrc):
ret = (embedManifestExeAction) ([target[0]],None,env)
if ret:
@@ -263,6 +263,7 @@ def generate(env):
env['_SHLINK_SOURCES'] = windowsShlinkSources
env['SHLINKCOM'] = compositeShLinkAction
env.Append(SHLIBEMITTER = [windowsLibEmitter])
+ env.Append(LDMODULEEMITTER = [windowsLibEmitter])
env['LINK'] = 'link'
env['LINKFLAGS'] = SCons.Util.CLVar('/nologo')
env['_PDB'] = pdbGenerator
diff --git a/src/engine/SCons/Tool/msvc.xml b/src/engine/SCons/Tool/msvc.xml
index 793784f..2b4619e 100644
--- a/src/engine/SCons/Tool/msvc.xml
+++ b/src/engine/SCons/Tool/msvc.xml
@@ -86,7 +86,7 @@ file as the second element. Normally the object file is ignored.
This builder method is only
provided when Microsoft Visual C++ is being used as the compiler.
The PCH builder method is generally used in
-conjuction with the PCH construction variable to force object files to use
+conjunction with the PCH construction variable to force object files to use
the precompiled header:
</para>
diff --git a/src/engine/SCons/Tool/msvs.py b/src/engine/SCons/Tool/msvs.py
index d00413d..355eeed 100644
--- a/src/engine/SCons/Tool/msvs.py
+++ b/src/engine/SCons/Tool/msvs.py
@@ -1535,7 +1535,9 @@ class _GenerateV7DSW(_DSWGenerator):
def PrintSolution(self):
"""Writes a solution file"""
self.file.write('Microsoft Visual Studio Solution File, Format Version %s\n' % self.versionstr)
- if self.version_num >= 11.0:
+ if self.version_num >= 12.0:
+ self.file.write('# Visual Studio 14\n')
+ elif self.version_num >= 11.0:
self.file.write('# Visual Studio 11\n')
elif self.version_num >= 10.0:
self.file.write('# Visual Studio 2010\n')
@@ -1981,7 +1983,7 @@ def generate(env):
env['MSVSSCONSCRIPT'] = default_MSVS_SConscript
env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
- env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.abspath}" -f ${MSVSSCONSCRIPT.name}'
+ env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.get_abspath()}" -f ${MSVSSCONSCRIPT.name}'
env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
env['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
diff --git a/src/engine/SCons/Tool/mwld.py b/src/engine/SCons/Tool/mwld.py
index e762d55..2067660 100644
--- a/src/engine/SCons/Tool/mwld.py
+++ b/src/engine/SCons/Tool/mwld.py
@@ -56,6 +56,7 @@ def generate(env):
env['SHLINKFLAGS'] = '$LINKFLAGS'
env['SHLINKCOM'] = shlib_action
env['SHLIBEMITTER']= shlib_emitter
+ env['LDMODULEEMITTER']= shlib_emitter
def exists(env):
diff --git a/src/engine/SCons/Tool/packaging/__init__.py b/src/engine/SCons/Tool/packaging/__init__.py
index 95311a2..1a979ab 100644
--- a/src/engine/SCons/Tool/packaging/__init__.py
+++ b/src/engine/SCons/Tool/packaging/__init__.py
@@ -80,7 +80,7 @@ def Tag(env, target, source, *more_tags, **kw_tags):
#if not k.startswith('PACKAGING_'):
if k[:10] != 'PACKAGING_':
k='PACKAGING_'+k
- setattr(t, k, v)
+ t.Tag(k, v)
def Package(env, target=None, source=None, **kw):
""" Entry point for the package tool.
@@ -235,9 +235,11 @@ def copy_attr(f1, f2):
#pattrs = [x for x in dir(f1) if not hasattr(f2, x) and\
# x.startswith('PACKAGING_')]
copyit = lambda x: not hasattr(f2, x) and x[:10] == 'PACKAGING_'
- pattrs = list(filter(copyit, dir(f1)))
- for attr in pattrs:
- setattr(f2, attr, getattr(f1, attr))
+ if f1._tags:
+ pattrs = list(filter(copyit, f1._tags))
+ for attr in pattrs:
+ f2.Tag(attr, f1.GetTag(attr))
+
def putintopackageroot(target, source, env, pkgroot, honor_install_location=1):
""" Uses the CopyAs builder to copy all source files to the directory given
in pkgroot.
@@ -262,9 +264,9 @@ def putintopackageroot(target, source, env, pkgroot, honor_install_location=1):
if file.is_under(pkgroot):
new_source.append(file)
else:
- if hasattr(file, 'PACKAGING_INSTALL_LOCATION') and\
+ if file.GetTag('PACKAGING_INSTALL_LOCATION') and\
honor_install_location:
- new_name=make_path_relative(file.PACKAGING_INSTALL_LOCATION)
+ new_name=make_path_relative(file.GetTag('PACKAGING_INSTALL_LOCATION'))
else:
new_name=make_path_relative(file.get_path())
@@ -301,7 +303,7 @@ def stripinstallbuilder(target, source, env):
for ss in s.sources:
n_source.append(ss)
copy_attr(s, ss)
- setattr(ss, 'PACKAGING_INSTALL_LOCATION', s.get_path())
+ ss.Tag('PACKAGING_INSTALL_LOCATION', s.get_path())
return (target, n_source)
diff --git a/src/engine/SCons/Tool/packaging/ipk.py b/src/engine/SCons/Tool/packaging/ipk.py
index 6549445..84f4e20 100644
--- a/src/engine/SCons/Tool/packaging/ipk.py
+++ b/src/engine/SCons/Tool/packaging/ipk.py
@@ -120,7 +120,7 @@ def build_specfiles(source, target, env):
return opened_files[needle]
except KeyError:
file=filter(lambda x: x.get_path().rfind(needle)!=-1, haystack)[0]
- opened_files[needle]=open(file.abspath, 'w')
+ opened_files[needle]=open(file.get_abspath(), 'w')
return opened_files[needle]
control_file=open_file('control', target)
diff --git a/src/engine/SCons/Tool/packaging/msi.py b/src/engine/SCons/Tool/packaging/msi.py
index fe78c9c..172038f 100644
--- a/src/engine/SCons/Tool/packaging/msi.py
+++ b/src/engine/SCons/Tool/packaging/msi.py
@@ -189,7 +189,7 @@ def build_wxsfile(target, source, env):
""" compiles a .wxs file from the keywords given in env['msi_spec'] and
by analyzing the tree of source nodes and their tags.
"""
- file = open(target[0].abspath, 'w')
+ file = open(target[0].get_abspath(), 'w')
try:
# Create a document with the Wix root tag
diff --git a/src/engine/SCons/Tool/packaging/rpm.py b/src/engine/SCons/Tool/packaging/rpm.py
index 2bc3063..92977d0 100644
--- a/src/engine/SCons/Tool/packaging/rpm.py
+++ b/src/engine/SCons/Tool/packaging/rpm.py
@@ -130,8 +130,7 @@ def build_specfile(target, source, env):
""" Builds a RPM specfile from a dictionary with string metadata and
by analyzing a tree of nodes.
"""
- file = open(target[0].abspath, 'w')
- str = ""
+ file = open(target[0].get_abspath(), 'w')
try:
file.write( build_specfile_header(env) )
@@ -169,7 +168,7 @@ def build_specfile_sections(spec):
'X_RPM_POSTUNINSTALL' : '%%postun\n%s\n\n',
'X_RPM_VERIFY' : '%%verify\n%s\n\n',
- # These are for internal use but could possibly be overriden
+ # These are for internal use but could possibly be overridden
'X_RPM_PREP' : '%%prep\n%s\n\n',
'X_RPM_BUILD' : '%%build\n%s\n\n',
'X_RPM_INSTALL' : '%%install\n%s\n\n',
@@ -279,7 +278,9 @@ def build_specfile_filesection(spec, files):
tags = {}
for k in supported_tags.keys():
try:
- tags[k]=getattr(file, k)
+ v = file.GetTag(k)
+ if v:
+ tags[k] = v
except AttributeError:
pass
@@ -287,7 +288,7 @@ def build_specfile_filesection(spec, files):
str = str + SimpleTagCompiler(supported_tags, mandatory=0).compile( tags )
str = str + ' '
- str = str + file.PACKAGING_INSTALL_LOCATION
+ str = str + file.GetTag('PACKAGING_INSTALL_LOCATION')
str = str + '\n\n'
return str
diff --git a/src/engine/SCons/Tool/qt.py b/src/engine/SCons/Tool/qt.py
index 716c7d5..7bc0ef6 100644
--- a/src/engine/SCons/Tool/qt.py
+++ b/src/engine/SCons/Tool/qt.py
@@ -320,6 +320,7 @@ def generate(env):
# correctly later by our emitter.
env.AppendUnique(PROGEMITTER =[AutomocStatic],
SHLIBEMITTER=[AutomocShared],
+ LDMODULEEMITTER=[AutomocShared],
LIBEMITTER =[AutomocStatic],
# Of course, we need to link against the qt libraries
CPPPATH=["$QT_CPPPATH"],
diff --git a/src/engine/SCons/Tool/rpm.py b/src/engine/SCons/Tool/rpm.py
index 1f6eafe..b7d65a8 100644
--- a/src/engine/SCons/Tool/rpm.py
+++ b/src/engine/SCons/Tool/rpm.py
@@ -51,11 +51,11 @@ def get_cmd(source, env):
if SCons.Util.is_List(source):
tar_file_with_included_specfile = source[0]
return "%s %s %s"%(env['RPM'], env['RPMFLAGS'],
- tar_file_with_included_specfile.abspath )
+ tar_file_with_included_specfile.get_abspath() )
def build_rpm(target, source, env):
# create a temporary rpm build root.
- tmpdir = os.path.join( os.path.dirname( target[0].abspath ), 'rpmtemp' )
+ tmpdir = os.path.join( os.path.dirname( target[0].get_abspath() ), 'rpmtemp' )
if os.path.exists(tmpdir):
shutil.rmtree(tmpdir)
@@ -87,7 +87,7 @@ def build_rpm(target, source, env):
expected = os.path.basename(input.get_path())
assert expected == rpm_output, "got %s but expected %s" % (rpm_output, expected)
- shutil.copy( output, input.abspath )
+ shutil.copy( output, input.get_abspath() )
# cleanup before leaving.
diff --git a/src/engine/SCons/Tool/sunar.py b/src/engine/SCons/Tool/sunar.py
index 779414f..eb58457 100644
--- a/src/engine/SCons/Tool/sunar.py
+++ b/src/engine/SCons/Tool/sunar.py
@@ -51,9 +51,6 @@ def generate(env):
env['ARFLAGS'] = SCons.Util.CLVar('r')
env['ARCOM'] = '$AR $ARFLAGS $TARGET $SOURCES'
- env['SHLINK'] = '$LINK'
- env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -G')
- env['SHLINKCOM'] = '$SHLINK $SHLINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
env['LIBPREFIX'] = 'lib'
env['LIBSUFFIX'] = '.a'
diff --git a/src/engine/SCons/Tool/sunar.xml b/src/engine/SCons/Tool/sunar.xml
index 65f0c9e..f875217 100644
--- a/src/engine/SCons/Tool/sunar.xml
+++ b/src/engine/SCons/Tool/sunar.xml
@@ -33,15 +33,11 @@ Sets construction variables for the Sun library archiver.
<item>AR</item>
<item>ARFLAGS</item>
<item>ARCOM</item>
-<item>SHLINK</item>
-<item>SHLINKFLAGS</item>
-<item>SHLINKCOM</item>
<item>LIBPREFIX</item>
<item>LIBSUFFIX</item>
</sets>
<uses>
<item>ARCOMSTR</item>
-<item>SHLINKCOMSTR</item>
</uses>
</tool>
diff --git a/src/engine/SCons/Tool/sunlink.py b/src/engine/SCons/Tool/sunlink.py
index 5996a30..680af03 100644
--- a/src/engine/SCons/Tool/sunlink.py
+++ b/src/engine/SCons/Tool/sunlink.py
@@ -66,6 +66,10 @@ def generate(env):
env['RPATHSUFFIX'] = ''
env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}'
+ # Support for versioned libraries
+ link._setup_versioned_lib_variables(env, tool = 'sunlink', use_soname = True)
+ env['LINKCALLBACKS'] = link._versioned_lib_callbacks()
+
def exists(env):
return ccLinker
diff --git a/src/engine/SCons/Tool/swig.py b/src/engine/SCons/Tool/swig.py
index f166174..a315182 100644
--- a/src/engine/SCons/Tool/swig.py
+++ b/src/engine/SCons/Tool/swig.py
@@ -42,6 +42,7 @@ import SCons.Defaults
import SCons.Scanner
import SCons.Tool
import SCons.Util
+import SCons.Node
SwigAction = SCons.Action.Action('$SWIGCOM', '$SWIGCOMSTR')
@@ -117,9 +118,13 @@ def _swigEmitter(target, source, env):
if outdir:
java_files = [os.path.join(outdir, j) for j in java_files]
java_files = list(map(env.fs.File, java_files))
+ def t_from_s(t, p, s, x):
+ return t.dir
+ tsm = SCons.Node._target_from_source_map
+ tkey = len(tsm)
+ tsm[tkey] = t_from_s
for jf in java_files:
- t_from_s = lambda t, p, s, x: t.dir
- SCons.Util.AddMethod(jf, t_from_s, 'target_from_source')
+ jf._func_target_from_source = tkey
target.extend(java_files)
return (target, source)
diff --git a/src/engine/SCons/Tool/swig.xml b/src/engine/SCons/Tool/swig.xml
index 2b817dc..1160804 100644
--- a/src/engine/SCons/Tool/swig.xml
+++ b/src/engine/SCons/Tool/swig.xml
@@ -202,9 +202,7 @@ and translated into the
The list of directories that the scripting language wrapper
and interface generate will search for included files.
The SWIG implicit dependency scanner will search these
-directories for include files.
-The default is to use the same path
-specified as &cv-CPPPATH;.
+directories for include files. The default value is an empty list.
</para>
<para>
@@ -251,7 +249,7 @@ include &cv-_SWIGINCFLAGS;:
</para>
<example_commands>
-env = Environment(SWIGCOM="my_swig -o $TARGET $_SWIGINCFLAGS $SORUCES")
+env = Environment(SWIGCOM="my_swig -o $TARGET $_SWIGINCFLAGS $SOURCES")
</example_commands>
</summary>
</cvar>
diff --git a/src/engine/SCons/Tool/xgettext.xml b/src/engine/SCons/Tool/xgettext.xml
index 6e28a9d..380f92a 100644
--- a/src/engine/SCons/Tool/xgettext.xml
+++ b/src/engine/SCons/Tool/xgettext.xml
@@ -108,7 +108,7 @@ the results shall be as the comments above say.
<emphasis>Example 2.</emphasis>
The &b-POTUpdate; builder may be used with no target specified, in which
case default target <filename>messages.pot</filename> will be used. The
-default target may also be overriden by setting &cv-link-POTDOMAIN; construction
+default target may also be overridden by setting &cv-link-POTDOMAIN; construction
variable or providing it as an override to &b-POTUpdate; builder:
</para>
<example_commands>
diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py
index 6dd64ec..4890ba2 100644
--- a/src/engine/SCons/Util.py
+++ b/src/engine/SCons/Util.py
@@ -264,10 +264,10 @@ def print_tree(root, child_func, prune=0, showtags=0, margin=[0], visited={}):
children = child_func(root)
if prune and rname in visited and children:
- sys.stdout.write(''.join(tags + margins + ['+-[', rname, ']']) + '\n')
+ sys.stdout.write(''.join(tags + margins + ['+-[', rname, ']']) + u'\n')
return
- sys.stdout.write(''.join(tags + margins + ['+-', rname]) + '\n')
+ sys.stdout.write(''.join(tags + margins + ['+-', rname]) + u'\n')
visited[rname] = 1
@@ -992,7 +992,7 @@ class Selector(OrderedDict):
def __call__(self, env, source, ext=None):
if ext is None:
try:
- ext = source[0].suffix
+ ext = source[0].get_suffix()
except IndexError:
ext = ""
try:
diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py
index b0c15c5..795bc46 100644
--- a/src/engine/SCons/UtilTests.py
+++ b/src/engine/SCons/UtilTests.py
@@ -591,10 +591,11 @@ class UtilTestCase(unittest.TestCase):
class MyNode(object):
def __init__(self, name):
self.name = name
- self.suffix = os.path.splitext(name)[1]
def __str__(self):
return self.name
+ def get_suffix(self):
+ return os.path.splitext(self.name)[1]
s = Selector({'a' : 'AAA', 'b' : 'BBB'})
assert s['a'] == 'AAA', s['a']
diff --git a/src/script/scons.py b/src/script/scons.py
index 0c7b44c..e522f57 100644
--- a/src/script/scons.py
+++ b/src/script/scons.py
@@ -191,7 +191,7 @@ if __name__ == "__main__":
except:
print("Import failed. Unable to find SCons files in:")
for path in libs:
- print " %s" % path
+ print(" %s" % path)
raise
# this does all the work, and calls sys.exit
diff --git a/src/script/sconsign.py b/src/script/sconsign.py
index e5e9d4f..ef32a93 100644
--- a/src/script/sconsign.py
+++ b/src/script/sconsign.py
@@ -278,7 +278,7 @@ def field(name, entry, verbose=Verbose):
def nodeinfo_raw(name, ninfo, prefix=""):
# This just formats the dictionary, which we would normally use str()
# to do, except that we want the keys sorted for deterministic output.
- d = ninfo.__dict__
+ d = ninfo.__getstate__()
try:
keys = ninfo.field_list + ['_version_id']
except AttributeError: