[Cmake] Shared library versioning

Gavin Baker gavinb at antonym . org
17 Jun 2003 01:39:05 +1000


--=-9aNsp8zzi0B67ocWO8TW
Content-Type: text/plain
Content-Transfer-Encoding: 7bit


Hello,

I am the maintainer of the (unofficial) Debian packages of ITK.  During
the process of packaging ITK, the issue of versioning came up.  Debian
policy requires that all shared libraries have proper soversion support,
with appropriate symlinks and filenames.  This ensures multiple versions
of the one library can coexist, and preserves binary compatability
between systems.

Since ITK uses CMake for building, ITK does not have any versioning in
place for the shared libraries.  For the purpose of packaging, I wrote a
script to patch the generated makefiles and add soversions and create
the correct symlinks. However this is a hack, as library versioning
should be done upstream.

So to enable library versioning for ITK (as well as VTK which suffers
from the same problem), I have written a patch for CMake that will add
versioning to shared libraries.  This adds the SONAME link flags and
correctly sets up the symlinks.

I have added a new command, SET_LIBRARY_VERSION.  A simple example:

PROJECT(Farcical)
ADD_LIBRARY(hello SHARED hello.cxx)
SET_LIBRARY_VERSION(hello 1.6.3 4)

The generated Makefile then contains:

#---------------------------------------------------------
# shared library
#
 
libhello.so: $(hello_SRC_OBJS)  $(hello_DEPEND_LIBS)
        echo "Building shared library libhello.so..."
        $(RM) libhello.so
        c++ -fPIC  -rdynamic -Wl,-soname,libhello.so.4  -shared -o
libhello.so $(hello_SRC_OBJS)
        mv libhello.so libhello.so.1.6.3
        ln -sf libhello.so.1.6.3 libhello.so.4
        ln -sf libhello.so.4 libhello.so
 
If no version is specified, the behaviour is as normal.

The patch is designed for minimal impact on the existing code, and thus
is not as elegant as it might be (in the absence of some refactoring).

I would like to solicit feedback from the CMake developers.  The patch
is included for your perusal below.  Let me know what you think!

Thanks,

  :: Gavin


-- 
Gavin Baker // gavinb*antonym_org // Linux|Python|Esperanto|MIDI|Cheese

--=-9aNsp8zzi0B67ocWO8TW
Content-Disposition: attachment; filename=cmake_soversion.diff
Content-Type: text/x-patch; name=cmake_soversion.diff; charset=ANSI_X3.4-1968
Content-Transfer-Encoding: 7bit

diff -ruN cmake-1.6.5-1/Modules/Platform/Linux.cmake cmake-1.6.5-1_sover/Modules/Platform/Linux.cmake
--- cmake-1.6.5-1/Modules/Platform/Linux.cmake	2002-11-09 10:06:53.000000000 +1100
+++ cmake-1.6.5-1_sover/Modules/Platform/Linux.cmake	2003-06-16 00:01:58.000000000 +1000
@@ -7,3 +7,4 @@
 SET(CMAKE_SHARED_LIBRARY_LINK_FLAGS "-rdynamic")  
 SET(CMAKE_SHARED_LIBRARY_RUNTIME_FLAG "-Wl,-rpath,")
 SET(CMAKE_SHARED_LIBRARY_RUNTIME_FLAG_SEP ":")
+SET(CMAKE_SHARED_LIBRARY_SONAME_FLAGS "-rdynamic -Wl,-soname,")
diff -ruN cmake-1.6.5-1/Source/cmCommands.cxx cmake-1.6.5-1_sover/Source/cmCommands.cxx
--- cmake-1.6.5-1/Source/cmCommands.cxx	2003-01-23 04:31:49.000000000 +1100
+++ cmake-1.6.5-1_sover/Source/cmCommands.cxx	2003-06-15 12:30:22.000000000 +1000
@@ -94,6 +94,7 @@
 #include "cmQTWrapUICommand.cxx"
 #include "cmWrapExcludeFilesCommand.cxx"
 #include "cmWriteFileCommand.cxx"
+#include "cmSetLibraryVersionCommand.cxx"
 
 // on regular builds add in the load command command
 // we do not add it in on the bootstrap because it 
@@ -183,4 +184,5 @@
   commands.push_back(new cmQTWrapUICommand);
   commands.push_back(new cmWrapExcludeFilesCommand);
   commands.push_back(new cmWriteFileCommand);
+  commands.push_back(new cmSetLibraryVersionCommand);
 }
diff -ruN cmake-1.6.5-1/Source/cmLocalUnixMakefileGenerator.cxx cmake-1.6.5-1_sover/Source/cmLocalUnixMakefileGenerator.cxx
--- cmake-1.6.5-1/Source/cmLocalUnixMakefileGenerator.cxx	2003-02-21 00:55:14.000000000 +1100
+++ cmake-1.6.5-1_sover/Source/cmLocalUnixMakefileGenerator.cxx	2003-06-17 01:05:19.000000000 +1000
@@ -855,6 +855,28 @@
     {
     commands.push_back(customCommands);
     }
+
+  // Provide symlinks for proper shared library/soname support
+  const char* libVersion = t.GetProperty("LIBRARY_VERSION");
+  const char* libSoversion = t.GetProperty("LIBRARY_SOVERSION");
+  if( libVersion && libSoversion )
+  {
+      std::string mv =
+          "mv " + targetName + " " + targetName + "." + libVersion;
+
+      std::string ln1 =
+          "ln -sf " + targetName + "." + libVersion +
+          " " + targetName + "." + libSoversion;
+
+      std::string ln2 =
+          "ln -sf " + targetName + "." + libSoversion +
+          " " + targetName;
+
+      commands.push_back(mv);
+      commands.push_back(ln1);
+      commands.push_back(ln2);
+  }
+
   // collect up the link libraries
   cmOStringStream linklibs;
   this->OutputLinkLibraries(linklibs, name, t);
@@ -928,12 +950,25 @@
     linkFlags += targetLinkFlags;
     linkFlags += " ";
     }
+
+  const char* soversion = t.GetProperty("LIBRARY_SOVERSION");
+  if(soversion)
+    {
+        linkFlags += this->GetSafeDefinition("CMAKE_SHARED_LIBRARY_SONAME_FLAGS");
+        linkFlags += targetPrefix;
+        linkFlags += name;
+        linkFlags += ".so.";
+        linkFlags += soversion;
+        linkFlags += " ";
+    }
+  
   this->OutputLibraryRule(fout, name, t,
                           targetPrefix,
                           targetSuffix,
                           createRule,
                           "shared library",
-                          linkFlags.c_str());
+                          linkFlags.c_str()
+                          );
 }
 
 void cmLocalUnixMakefileGenerator::OutputModuleLibraryRule(std::ostream& fout, 
diff -ruN cmake-1.6.5-1/Source/cmMakefile.cxx cmake-1.6.5-1_sover/Source/cmMakefile.cxx
--- cmake-1.6.5-1/Source/cmMakefile.cxx	2003-01-23 04:32:27.000000000 +1100
+++ cmake-1.6.5-1_sover/Source/cmMakefile.cxx	2003-06-17 00:53:34.000000000 +1000
@@ -529,6 +529,23 @@
     }
 }
 
+void cmMakefile:: SetTargetVersion(const char* target,
+                                   const char* ver,
+                                   const char* sover)
+{
+  cmTargets::iterator i = m_Targets.find(target);
+  if ( i != m_Targets.end())
+    {
+    i->second.SetProperty( "LIBRARY_VERSION", ver );
+    i->second.SetProperty( "LIBRARY_SOVERSION", sover );
+    }
+  else
+    {
+    cmSystemTools::Error("Attempt to set version of non-existant target: ", 
+                         target );
+    }
+}
+
 void cmMakefile::AddSubDirectory(const char* sub)
 {
   m_SubDirectories.push_back(sub);
diff -ruN cmake-1.6.5-1/Source/cmMakefile.h cmake-1.6.5-1_sover/Source/cmMakefile.h
--- cmake-1.6.5-1/Source/cmMakefile.h	2003-02-21 00:30:13.000000000 +1100
+++ cmake-1.6.5-1_sover/Source/cmMakefile.h	2003-06-17 00:54:19.000000000 +1000
@@ -174,6 +174,13 @@
   void AddLinkDirectoryForTarget(const char *tgt, const char* d);
 
   /**
+   * Specify Library versioning (eg. ver=2.3.1, sover=4)
+   */
+  void SetTargetVersion( const char* target,
+                         const char* ver,
+                         const char* sover);
+
+  /**
    * Add a link directory to the build.
    */
   void AddLinkDirectory(const char*);
diff -ruN cmake-1.6.5-1/Source/cmSetLibraryVersionCommand.cxx cmake-1.6.5-1_sover/Source/cmSetLibraryVersionCommand.cxx
--- cmake-1.6.5-1/Source/cmSetLibraryVersionCommand.cxx	1970-01-01 10:00:00.000000000 +1000
+++ cmake-1.6.5-1_sover/Source/cmSetLibraryVersionCommand.cxx	2003-06-15 12:04:43.000000000 +1000
@@ -0,0 +1,41 @@
+/*=========================================================================
+
+  Program:   CMake - Cross-Platform Makefile Generator
+  Module:    $RCSfile: $
+  Language:  C++
+  Date:      $Date: $
+  Version:   $Revision: $
+
+  Copyright (c) 2003 Kitware, Inc., Insight Consortium.  All rights reserved.
+  See Copyright.txt or http://www . cmake . org/HTML/Copyright . html for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even 
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+     PURPOSE.  See the above copyright notices for more information.
+
+=========================================================================*/
+
+#include "cmSetLibraryVersionCommand.h"
+
+// cmLibraryCommand
+bool
+cmSetLibraryVersionCommand::InitialPass( std::vector<std::string> const& args )
+{
+  if(args.size() < 1 || args.size() > 3)
+    {
+    this->SetError("Require libraryname and soversion (integer) as arguments");
+    return false;
+    }
+
+  m_LibName = args[0];
+  m_Version = args[1];
+  m_Soversion = args[2];
+
+  m_Makefile->SetTargetVersion( m_LibName.c_str(),
+                                m_Version.c_str(),
+                                m_Soversion.c_str() );
+
+  return true;
+}
+
+
diff -ruN cmake-1.6.5-1/Source/cmSetLibraryVersionCommand.h cmake-1.6.5-1_sover/Source/cmSetLibraryVersionCommand.h
--- cmake-1.6.5-1/Source/cmSetLibraryVersionCommand.h	1970-01-01 10:00:00.000000000 +1000
+++ cmake-1.6.5-1_sover/Source/cmSetLibraryVersionCommand.h	2003-06-17 01:03:28.000000000 +1000
@@ -0,0 +1,95 @@
+/*=========================================================================
+
+  Program:   CMake - Cross-Platform Makefile Generator
+  Module:    $RCSfile: $
+  Language:  C++
+  Date:      $Date: $
+  Version:   $Revision: $
+
+  Copyright (c) 2003 Kitware, Inc., Insight Consortium.  All rights reserved.
+  See Copyright.txt or http://www . cmake . org/HTML/Copyright . html for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even 
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+     PURPOSE.  See the above copyright notices for more information.
+
+=========================================================================*/
+#ifndef cmSetLibraryVersionCommand_h
+#define cmSetLibraryVersionCommand_h
+
+#include "cmStandardIncludes.h"
+#include "cmCommand.h"
+
+/** \class cmSetLibraryVersionCommand
+ * \brief Specifies the versioning of a shared library.
+ *
+ * The full version number will normally include major, minor and patch
+ * numbers, in the style of "2.5.1".  This version relates to the release of
+ * the software.
+ *
+ * The soversion (shared object version) is a single integer that
+ * monotonically increases each time the published interface changes.
+ * It does *not* equate to the product version (above).  It should
+ * only be incremented when the published interface changes, and not
+ * necessarily with each release.
+ *
+ * Example:
+ *
+ *     SET_LIBARY_VERSION( parser 2.3.6 5)
+ *
+ * \author Gavin Baker <gavinb_at_antonym.org>
+ */
+class cmSetLibraryVersionCommand : public cmCommand
+{
+public:
+  /**
+   * This is a virtual constructor for the command.
+   */
+  virtual cmCommand* Clone() 
+    {
+    return new cmSetLibraryVersionCommand;
+    }
+
+  /**
+   * This is called when the command is first encountered in
+   * the CMakeLists.txt file.
+   */
+  virtual bool InitialPass(std::vector<std::string> const& args);
+
+  /**
+   * The name of the command as specified in CMakeList.txt.
+   */
+  virtual const char* GetName() { return "SET_LIBRARY_VERSION";}
+
+  /**
+   * Succinct documentation.
+   */
+  virtual const char* GetTerseDocumentation() 
+    {
+    return "Specifies the version and soversion of a shared library";
+    }
+  
+  /**
+   * More documentation.
+   */
+  virtual const char* GetFullDocumentation()
+    {
+    return
+      "SET_LIBRARY_VERSION(target version soversion)\n"
+      "Specifies the product version and soversion (revision of the public\n"
+      "interface) of the SHARED library target.  Ignored for static libraries.\n"
+      "The version is normally of the form 2.5.3 (maj.min.rev).\n"
+      "The soversion is a monotonically increasing integer that should only\n"
+      "be incremented each time the public interface of the library changes.\n";
+    }
+  
+  cmTypeMacro(cmSetLibraryVersionCommand, cmCommand);
+
+private:
+  std::string m_LibName;
+  std::string m_Version;
+  std::string m_Soversion;
+};
+
+
+#endif
diff -ruN cmake-1.6.5-1/Source/cmSetLibraryVersionCommand.h~ cmake-1.6.5-1_sover/Source/cmSetLibraryVersionCommand.h~
--- cmake-1.6.5-1/Source/cmSetLibraryVersionCommand.h~	1970-01-01 10:00:00.000000000 +1000
+++ cmake-1.6.5-1_sover/Source/cmSetLibraryVersionCommand.h~	2003-06-15 12:03:42.000000000 +1000
@@ -0,0 +1,79 @@
+/*=========================================================================
+
+  Program:   CMake - Cross-Platform Makefile Generator
+  Module:    $RCSfile: $
+  Language:  C++
+  Date:      $Date: $
+  Version:   $Revision: $
+
+  Copyright (c) 2003 Kitware, Inc., Insight Consortium.  All rights reserved.
+  See Copyright.txt or http://www . cmake . org/HTML/Copyright . html for details.
+
+     This software is distributed WITHOUT ANY WARRANTY; without even 
+     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+     PURPOSE.  See the above copyright notices for more information.
+
+=========================================================================*/
+#ifndef cmSetLibraryVersionCommand_h
+#define cmSetLibraryVersionCommand_h
+
+#include "cmStandardIncludes.h"
+#include "cmCommand.h"
+
+/** \class cmSetLibraryVersionCommand
+ * \brief Specifies the version number of a shared library.
+ *
+ * Used for link flags, ie. to set soname.
+ */
+class cmSetLibraryVersionCommand : public cmCommand
+{
+public:
+  /**
+   * This is a virtual constructor for the command.
+   */
+  virtual cmCommand* Clone() 
+    {
+    return new cmSetLibraryVersionCommand;
+    }
+
+  /**
+   * This is called when the command is first encountered in
+   * the CMakeLists.txt file.
+   */
+  virtual bool InitialPass(std::vector<std::string> const& args);
+
+  /**
+   * The name of the command as specified in CMakeList.txt.
+   */
+  virtual const char* GetName() { return "SET_LIBRARY_VERSION";}
+
+  /**
+   * Succinct documentation.
+   */
+  virtual const char* GetTerseDocumentation() 
+    {
+    return "Specifies the version of a shared library";
+    }
+  
+  /**
+   * More documentation.
+   */
+  virtual const char* GetFullDocumentation()
+    {
+    return
+      "SET_LIBRARY_VERSION(libname version)\n"
+      "Specifies the version of the SHARED library target.\n"
+      "This should be incremented each time the interface of the library changes.\n"
+      "If this variable is not set, the soname is not set.";
+    }
+  
+  cmTypeMacro(cmSetLibraryVersionCommand, cmCommand);
+
+private:
+  std::string m_LibName;
+  std::string m_Version;
+  std::string m_Soversion;
+};
+
+
+#endif

--=-9aNsp8zzi0B67ocWO8TW--