summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x[-rw-r--r--]runtest.py0
-rw-r--r--src/CHANGES.txt4
-rw-r--r--src/engine/SCons/Environment.py1
-rw-r--r--src/engine/SCons/Tool/__init__.py105
-rw-r--r--src/engine/SCons/Tool/__init__.xml13
-rw-r--r--src/engine/SCons/Tool/install.py79
-rw-r--r--src/engine/SCons/Tool/install.xml5
-rw-r--r--src/engine/SCons/Tool/link.py67
-rw-r--r--test/LINK/VersionedLib.py154
9 files changed, 424 insertions, 4 deletions
diff --git a/runtest.py b/runtest.py
index a88cfd2..a88cfd2 100644..100755
--- a/runtest.py
+++ b/runtest.py
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index 2c89d8d..33fa239 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -42,6 +42,10 @@ RELEASE 2.X.X -
in LaTeX's glossaries package and the files it creates.
- Improve support for new versions of biblatex in the TeX builder
so biber is called automatically if biblatex requires it.
+ - Add SHLIBVERSION as an option that tells SharedLibrary to build
+ a versioned shared library and create the required symlinks.
+ Update Install to create the required symlinks when installing
+ a versioned shared library.
RELEASE 2.2.0 - Mon, 05 Aug 2012 15:37:48 +0000
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index 2c71e61..8cc033e 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -2247,6 +2247,7 @@ class Base(SubstitutionEnvironment):
install._UNIQUE_INSTALLED_FILES = SCons.Util.uniquer_hashables(install._INSTALLED_FILES)
return install._UNIQUE_INSTALLED_FILES
+
class OverrideEnvironment(Base):
"""A proxy that overrides variables in a wrapped construction
environment by returning values from an overrides dictionary in
diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py
index 5357959..35a05f0 100644
--- a/src/engine/SCons/Tool/__init__.py
+++ b/src/engine/SCons/Tool/__init__.py
@@ -39,6 +39,9 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import imp
import sys
+import re
+import os
+import shutil
import SCons.Builder
import SCons.Errors
@@ -233,6 +236,103 @@ def createStaticLibBuilder(env):
return static_lib
+def VersionShLibLinkNames(version, libname, env):
+ """Generate names of symlinks to the versioned shared library"""
+ 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
+ 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 "VersionShLibLinkNames: linkname = ",linkname
+ linknames.append(linkname)
+ elif platform == 'posix':
+ # 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)
+ if Verbose:
+ print "VersionShLibLinkNames: linkname = ",linkname
+ linknames.append(linkname)
+ versionparts = version.split('.')
+ major_name = linkname + "." + versionparts[0]
+ minor_name = major_name + "." + versionparts[1]
+ for linkname in [major_name, minor_name]:
+ if Verbose:
+ print "VersionShLibLinkNames: linkname ",linkname, ", target ",libname
+ linknames.append(linkname)
+ 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
+
+ # libname includes the version number if one was given
+ libname = 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':
+ 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,-Bsymbolic', '-Wl,-soname=%s' % soname ]
+ if Verbose:
+ print " soname ",soname,", shlink_flags ",shlink_flags
+ 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' ]
+ if Verbose:
+ print "VersionShLib: shlink_flags = ",shlink_flags
+ envlink = env.Clone()
+ envlink['SHLINKFLAGS'] = shlink_flags
+ else:
+ envlink = env
+
+ result = SCons.Defaults.ShLinkAction(target, source, envlink)
+
+ if version:
+ # here we need the full pathname so the links end up in the right directory
+ libname = target[0].path
+ linknames = VersionShLibLinkNames(version, libname, env)
+ # Here we just need the file name w/o path as the target of the link
+ lib_ver = target[0].name
+ for linkname in linknames:
+ os.symlink(lib_ver,linkname)
+ if Verbose:
+ print "VerShLib: made sym link of %s -> %s" % (linkname, lib_ver)
+ return result
+
+ShLibAction = SCons.Action.Action(VersionedSharedLibrary, None)
+
def createSharedLibBuilder(env):
"""This is a utility function that creates the SharedLibrary
Builder in an Environment if it is not there already.
@@ -245,7 +345,7 @@ def createSharedLibBuilder(env):
except KeyError:
import SCons.Defaults
action_list = [ SCons.Defaults.SharedCheck,
- SCons.Defaults.ShLinkAction ]
+ ShLibAction ]
shared_lib = SCons.Builder.Builder(action = action_list,
emitter = "$SHLIBEMITTER",
prefix = '$SHLIBPREFIX',
@@ -679,3 +779,6 @@ def tool_list(platform, env):
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:
+
+
+
diff --git a/src/engine/SCons/Tool/__init__.xml b/src/engine/SCons/Tool/__init__.xml
index d274a95..d491fa5 100644
--- a/src/engine/SCons/Tool/__init__.xml
+++ b/src/engine/SCons/Tool/__init__.xml
@@ -146,6 +146,19 @@ On some platforms, there is a distinction between a shared library
and a loadable module (explicitly loaded by user action).
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.
+
+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, libbar.so.2, and libbar.so.2.3; on a Darwin (OSX) system
+the library would be libbar.2.3.1.dylib and the link would be
+libbar.dylib.
+
On Windows systems, specifying
<literal>register=1</literal>
will cause the <filename>.dll</filename> to be
diff --git a/src/engine/SCons/Tool/install.py b/src/engine/SCons/Tool/install.py
index 7e5fed5..eb50405 100644
--- a/src/engine/SCons/Tool/install.py
+++ b/src/engine/SCons/Tool/install.py
@@ -33,6 +33,7 @@ selection method.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os
+import re
import shutil
import stat
@@ -118,9 +119,73 @@ def copyFunc(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)
return 0
+def versionedLibVersion(dest, env):
+ """Check if dest is a version shared library name. Return version, libname, & install_dir if it is."""
+ Verbose = False
+ platform = env.subst('$PLATFORM')
+ if not (platform == 'posix' or platform == 'darwin'):
+ return (None, None, None)
+
+ libname = os.path.basename(dest)
+ install_dir = os.path.dirname(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':
+ # 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]
+
+ 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, env)
+
+ if version != None:
+ # libname includes the version number if one was given
+ linknames = SCons.Tool.VersionShLibLinkNames(version,libname,env)
+ for linkname in linknames:
+ if Verbose:
+ print "make link of %s to %s" %(libname, os.path.join(install_dir, linkname))
+ fulllinkname = os.path.join(install_dir, linkname)
+ os.symlink(libname,fulllinkname)
+ return
+
def installFunc(target, source, env):
"""Install a source file into a target using the function specified
as the INSTALL construction variable."""
@@ -158,7 +223,21 @@ def add_targets_to_INSTALLED_FILES(target, source, env):
scons call will be collected.
"""
global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
+ Verbose = False
_INSTALLED_FILES.extend(target)
+
+ # see if we have a versioned shared library, if so generate side effects
+ version, libname, install_dir = versionedLibVersion(target[0].path, 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)
+
_UNIQUE_INSTALLED_FILES = None
return (target, source)
diff --git a/src/engine/SCons/Tool/install.xml b/src/engine/SCons/Tool/install.xml
index 4b57a68..b83bb60 100644
--- a/src/engine/SCons/Tool/install.xml
+++ b/src/engine/SCons/Tool/install.xml
@@ -22,7 +22,10 @@ Installs one or more source files or directories
in the specified target,
which must be a directory.
The names of the specified source files or directories
-remain the same within the destination directory.
+remain the same within the destination directory. The
+sources may be given as a string or as a node returned by
+a builder. If the source is a versioned shared library
+the appropriate symlinks to it will be generated.
<example>
env.Install('/usr/local/bin', source = ['foo', 'bar'])
diff --git a/src/engine/SCons/Tool/link.py b/src/engine/SCons/Tool/link.py
index fae7f63..67d5b19 100644
--- a/src/engine/SCons/Tool/link.py
+++ b/src/engine/SCons/Tool/link.py
@@ -33,6 +33,8 @@ selection method.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+import re
+
import SCons.Defaults
import SCons.Tool
import SCons.Util
@@ -64,10 +66,71 @@ def smart_link(source, target, env, for_signature):
return '$CC'
def shlib_emitter(target, source, env):
+ Verbose = False
+ platform = env.subst('$PLATFORM')
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)
+ # change the name of the target to include the version number
+ target[0].name = version_names[0]
+ for name in version_names:
+ env.SideEffect(name, target[0])
+ env.Clean(target[0], name)
+ if Verbose:
+ print "shlib_emitter: add side effect - ",name
+ except KeyError:
+ version = None
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"""
+ 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':
+ versionparts = version.split('.')
+ name = target[0].name
+ # generate library name with the version number
+ version_name = target[0].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')
+ 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)
+ except KeyError:
+ version = None
+ return version_names
+
def generate(env):
"""Add Builders and construction variables for gnulink to an Environment."""
SCons.Tool.createSharedLibBuilder(env)
@@ -77,7 +140,7 @@ def generate(env):
env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared')
env['SHLINKCOM'] = '$SHLINK -o $TARGET $SHLINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
# don't set up the emitter, cause AppendUnique will generate a list
- # starting with None :-(
+ # starting with None
env.Append(SHLIBEMITTER = [shlib_emitter])
env['SMARTLINK'] = smart_link
env['LINK'] = "$SMARTLINK"
@@ -101,7 +164,7 @@ def generate(env):
SCons.Tool.createLoadableModuleBuilder(env)
env['LDMODULE'] = '$SHLINK'
# don't set up the emitter, cause AppendUnique will generate a list
- # starting with None :-(
+ # starting with None
env.Append(LDMODULEEMITTER='$SHLIBEMITTER')
env['LDMODULEPREFIX'] = '$SHLIBPREFIX'
env['LDMODULESUFFIX'] = '$SHLIBSUFFIX'
diff --git a/test/LINK/VersionedLib.py b/test/LINK/VersionedLib.py
new file mode 100644
index 0000000..0460444
--- /dev/null
+++ b/test/LINK/VersionedLib.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import sys
+import TestSCons
+
+# copied from SCons/Platform/__init__.py
+def platform_default():
+ """Return the platform string for our execution environment.
+
+ The returned value should map to one of the SCons/Platform/*.py
+ files. Since we're architecture independent, though, we don't
+ care about the machine architecture.
+ """
+ osname = os.name
+ if osname == 'java':
+ osname = os._osType
+ if osname == 'posix':
+ if sys.platform == 'cygwin':
+ return 'cygwin'
+ elif sys.platform.find('irix') != -1:
+ return 'irix'
+ elif sys.platform.find('sunos') != -1:
+ return 'sunos'
+ elif sys.platform.find('hp-ux') != -1:
+ return 'hpux'
+ elif sys.platform.find('aix') != -1:
+ return 'aix'
+ elif sys.platform.find('darwin') != -1:
+ return 'darwin'
+ else:
+ return 'posix'
+ elif os.name == 'os2':
+ return 'os2'
+ else:
+ return sys.platform
+
+_exe = TestSCons._exe
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """\
+import os
+env = Environment()
+objs = env.SharedObject('test.c')
+mylib = env.SharedLibrary('test', objs, SHLIBVERSION = '2.5.4')
+env.Program(source=['testapp.c',mylib])
+env.Program(target=['testapp2'],source=['testapp.c','libtest.dylib'])
+instnode = env.Install("#/installtest",mylib)
+env.Default(instnode)
+""")
+
+test.write('test.c', """\
+int testlib(int n)
+{
+return n+1 ;
+}
+""")
+
+test.write('testapp.c', """\
+#include <stdio.h>
+int main(int argc, char **argv)
+{
+int itest ;
+
+itest = testlib(2) ;
+printf("results: testlib(2) = %d\n",itest) ;
+return 0 ;
+}
+""")
+
+platform = platform_default()
+
+test.run()
+
+if platform == 'posix':
+ # All (?) the files we expect will get created in the current directory
+ files = [
+ 'libtest.so',
+ 'libtest.so.2',
+ 'libtest.so.2.5',
+ 'libtest.so.2.5.4',
+ 'test.os',
+ ]
+ # All (?) the files we expect will get created in the 'installtest' directory
+ instfiles = [
+ 'libtest.so',
+ 'libtest.so.2',
+ 'libtest.so.2.5',
+ 'libtest.so.2.5.4',
+ ]
+elif platform == 'darwin':
+ # All (?) the files we expect will get created in the current directory
+ files = [
+ 'libtest.dylib',
+ 'libtest.2.5.4.dylib',
+ 'test.os',
+ ]
+ # All (?) the files we expect will get created in the 'installtest' directory
+ instfiles = [
+ 'libtest.dylib',
+ 'libtest.2.5.4.dylib',
+ ]
+else:
+ # All (?) the files we expect will get created in the current directory
+ files= [
+ 'libtest.so',
+ 'test.os']
+ # All (?) the files we expect will get created in the 'installtest' directory
+ instfiles = []
+
+for f in files:
+ test.must_exist([ f])
+for f in instfiles:
+ test.must_exist(['installtest', f])
+
+test.run(arguments = '-c')
+
+for f in files:
+ test.must_not_exist([ f])
+for f in instfiles:
+ test.must_not_exist(['installtest', f])
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: