[PATCH rdma-core 3/9] Generate complete and sanitized static libraries

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Jason Gunthorpe <jgg@xxxxxxxxxxxx>

Once rdma-core started to introduce ccan and util internal libraries the
static libraries stopped working in a sane way. We need to include the
internal libraries within the static ones and sanitize the symbol names to
get things back to where they used to be.

Add a post-processing script that figures out what to do and invokes
binutils to generate clean static libraries.

Signed-off-by: Jason Gunthorpe <jgg@xxxxxxxxxxxx>
---
 CMakeLists.txt                  |   2 +
 buildlib/cbuild                 |   1 +
 buildlib/rdma_functions.cmake   |  72 ++++++---
 buildlib/sanitize_static_lib.py | 269 ++++++++++++++++++++++++++++++++
 4 files changed, 319 insertions(+), 25 deletions(-)
 create mode 100644 buildlib/sanitize_static_lib.py

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7cccf85fab7133..94f3736b5b3ad6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -82,6 +82,8 @@ set(BUILD_INCLUDE ${CMAKE_BINARY_DIR}/include)
 set(BUILD_BIN ${CMAKE_BINARY_DIR}/bin)
 # Libraries
 set(BUILD_LIB ${CMAKE_BINARY_DIR}/lib)
+# Static library pre-processing
+set(BUILD_STATIC_LIB ${CMAKE_BINARY_DIR}/lib/statics)
 # Used for IN_PLACE configuration
 set(BUILD_ETC ${CMAKE_BINARY_DIR}/etc)
 
diff --git a/buildlib/cbuild b/buildlib/cbuild
index 0e28aff94807f6..1cc54f313ca148 100755
--- a/buildlib/cbuild
+++ b/buildlib/cbuild
@@ -102,6 +102,7 @@ class centos6(YumEnvironment):
         'make',
         'pkgconfig',
         'python',
+        'python-argparse',
         'rpm-build',
         'valgrind-devel',
     };
diff --git a/buildlib/rdma_functions.cmake b/buildlib/rdma_functions.cmake
index f7c83d106d2aa5..990c052ffc1311 100644
--- a/buildlib/rdma_functions.cmake
+++ b/buildlib/rdma_functions.cmake
@@ -4,12 +4,26 @@
 # Helper functions for use in the sub CMakeLists files to make them simpler
 # and more uniform.
 
-# Global list of pairs of (SHARED STATIC) libary target names
+# Global list of tuples of (SHARED STATIC MAP) library target names
 set(RDMA_STATIC_LIBS "" CACHE INTERNAL "Doc" FORCE)
 
 set(COMMON_LIBS_PIC ccan_pic rdma_util_pic)
 set(COMMON_LIBS ccan rdma_util)
 
+function(rdma_public_static_lib SHLIB STATICLIB VERSION_SCRIPT)
+  if (NOT IS_ABSOLUTE ${VERSION_SCRIPT})
+    set(VERSION_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/${VERSION_SCRIPT}")
+  endif()
+
+  set_target_properties(${STATICLIB} PROPERTIES
+    OUTPUT_NAME ${SHLIB}
+    ARCHIVE_OUTPUT_DIRECTORY "${BUILD_STATIC_LIB}")
+  target_compile_definitions(${STATICLIB} PRIVATE _STATIC_LIBRARY_BUILD_=1)
+
+  list(APPEND RDMA_STATIC_LIBS ${SHLIB} ${STATICLIB} ${VERSION_SCRIPT})
+  set(RDMA_STATIC_LIBS "${RDMA_STATIC_LIBS}" CACHE INTERNAL "")
+endfunction()
+
 function(rdma_make_dir DDIR)
   if(NOT EXISTS "${DDIR}/")
     execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "make_directory"
@@ -88,14 +102,8 @@ function(rdma_library DEST VERSION_SCRIPT SOVERSION VERSION)
   # Create a static library
   if (ENABLE_STATIC)
     add_library(${DEST}-static STATIC ${ARGN})
-    set_target_properties(${DEST}-static PROPERTIES
-      OUTPUT_NAME ${DEST}
-      ARCHIVE_OUTPUT_DIRECTORY "${BUILD_LIB}")
-    target_compile_definitions(${DEST}-static PRIVATE _STATIC_LIBRARY_BUILD_=1)
-    install(TARGETS ${DEST}-static DESTINATION "${CMAKE_INSTALL_LIBDIR}")
-
-    list(APPEND RDMA_STATIC_LIBS ${DEST} ${DEST}-static)
-    set(RDMA_STATIC_LIBS "${RDMA_STATIC_LIBS}" CACHE INTERNAL "")
+    target_link_libraries(${DEST}-static LINK ${COMMON_LIBS})
+    rdma_public_static_lib(${DEST} ${DEST}-static ${VERSION_SCRIPT})
   endif()
 
   # Create a shared library
@@ -125,13 +133,7 @@ function(rdma_shared_provider DEST VERSION_SCRIPT SOVERSION VERSION)
   # Create a static provider library
   if (ENABLE_STATIC)
     add_library(${DEST}-static STATIC ${ARGN})
-    set_target_properties(${DEST}-static PROPERTIES OUTPUT_NAME ${DEST})
-    set_target_properties(${DEST}-static PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${BUILD_LIB}")
-    target_compile_definitions(${DEST}-static PRIVATE _STATIC_LIBRARY_BUILD_=1)
-    install(TARGETS ${DEST}-static DESTINATION "${CMAKE_INSTALL_LIBDIR}")
-
-    list(APPEND RDMA_STATIC_LIBS ${DEST} ${DEST}-static)
-    set(RDMA_STATIC_LIBS "${RDMA_STATIC_LIBS}" CACHE INTERNAL "")
+    rdma_public_static_lib(${DEST} ${DEST}-static ${VERSION_SCRIPT})
   endif()
 
   # Create the plugin shared library
@@ -177,12 +179,8 @@ function(rdma_provider DEST)
   # but we don't have any directions on how to make static linking work..
   if (ENABLE_STATIC)
     add_library(${DEST} STATIC ${ARGN})
-    set_target_properties(${DEST} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${BUILD_LIB}")
-    target_compile_definitions(${DEST} PRIVATE _STATIC_LIBRARY_BUILD_=1)
-    install(TARGETS ${DEST} DESTINATION "${CMAKE_INSTALL_LIBDIR}")
-
-    list(APPEND RDMA_STATIC_LIBS "${DEST}-rdmav${IBVERBS_PABI_VERSION}" ${DEST})
-    set(RDMA_STATIC_LIBS "${RDMA_STATIC_LIBS}" CACHE INTERNAL "")
+    rdma_public_static_lib("${DEST}-rdmav${IBVERBS_PABI_VERSION}" ${DEST} ${BUILDLIB}/provider.map)
+    set_target_properties(${DEST} PROPERTIES OUTPUT_NAME ${DEST})
   endif()
 
   # Create the plugin shared library
@@ -234,18 +232,20 @@ function(rdma_test_executable EXEC)
 endfunction()
 
 # Finalize the setup of the static libraries by copying the meta information
-# from the shared and setting up the libtool .la files.
+# from the shared to static and setting up the static builder
 function(rdma_finalize_libs)
   list(LENGTH RDMA_STATIC_LIBS LEN)
-  if (LEN LESS 2)
+  if (LEN LESS 3)
     return()
   endif()
 
   math(EXPR LEN ${LEN}-1)
-  foreach(I RANGE 0 ${LEN} 2)
+  foreach(I RANGE 0 ${LEN} 3)
     list(GET RDMA_STATIC_LIBS ${I} SHARED)
     math(EXPR I ${I}+1)
     list(GET RDMA_STATIC_LIBS ${I} STATIC)
+    math(EXPR I ${I}+1)
+    list(GET RDMA_STATIC_LIBS ${I} MAP)
 
     # PUBLIC libraries
     set(LIBS "")
@@ -263,7 +263,29 @@ function(rdma_finalize_libs)
       set_target_properties(${STATIC} PROPERTIES LINK_LIBRARIES "${TMP}")
       list(APPEND LIBS "${TMP}")
     endif()
+
+    set(ARGS ${ARGS} --map "${MAP}" --lib "$<TARGET_FILE:${STATIC}>")
+    set(DEPENDS ${DEPENDS} ${STATIC} ${MAP})
+
+    get_target_property(TMP ${STATIC} OUTPUT_NAME)
+    set(OUTPUTS ${OUTPUTS} "${BUILD_LIB}/lib${TMP}.a")
+    install(FILES "${BUILD_LIB}/lib${TMP}.a" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+  endforeach()
+
+  foreach(STATIC ${COMMON_LIBS})
+    set(ARGS ${ARGS} --internal_lib "$<TARGET_FILE:${STATIC}>")
+    set(DEPENDS ${DEPENDS} ${STATIC})
   endforeach()
+
+  add_custom_command(
+    OUTPUT ${OUTPUTS}
+    COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/buildlib/sanitize_static_lib.py"
+             --version ${PACKAGE_VERSION}
+             --ar "${CMAKE_AR}" --nm "${CMAKE_NM}" --objcopy "${CMAKE_OBJCOPY}" ${ARGS}
+    DEPENDS ${DEPENDS} "${CMAKE_SOURCE_DIR}/buildlib/sanitize_static_lib.py"
+    COMMENT "Building distributable static libraries"
+    VERBATIM)
+  add_custom_target("make_static" ALL DEPENDS ${OUTPUTS})
 endfunction()
 
 # Generate a pkg-config file
diff --git a/buildlib/sanitize_static_lib.py b/buildlib/sanitize_static_lib.py
new file mode 100644
index 00000000000000..86027ccc817457
--- /dev/null
+++ b/buildlib/sanitize_static_lib.py
@@ -0,0 +1,269 @@
+#!/usr/bin/env python
+# Copyright (c) 2018 Mellanox Technologies, Ltd.  All rights reserved.
+# Licensed under BSD (MIT variant) or GPLv2. See COPYING.
+"""This tool is used to create installable versions of the static libraries in rdma-core.
+
+This is complicated because rdma-core was not designed with static libraries
+in mind and relies on the dynamic linker to hide a variety of internal
+details.
+
+The build uses several internal utility libraries across the providers and the
+libraries. When building statically these libraries have to become inlined
+into the various main libraries. This script figures out which static
+libraries should include which internal libraries and inlines them
+appropriately.
+
+rdma-core is not careful to use globally unique names throughout all the
+libraries and all the providers.  Normally the map file in the dynamic linker
+will hide these external symbols. This script does something similar for static
+linking by analyzing the libraries and map files then renaming internal
+symbols with a globally unique prefix.
+
+This is far too complicated to handle internally with cmake, so we have cmake
+produce the nearly completed libraries, then process them here using bintuils,
+and finally produce the final installation ready libraries."""
+
+import collections
+import subprocess
+import argparse
+import tempfile
+import itertools
+import sys
+import os
+import re
+
+SymVer = collections.namedtuple(
+    "SymVer", ["version", "prior_version", "globals", "locals"])
+
+try:
+    from tempfile import TemporaryDirectory
+except ImportError:
+    import shutil
+    import tempfile
+
+    # From /usr/lib/python3/dist-packages/setuptools/py31compat.py
+    class TemporaryDirectory(object):
+        def __init__(self):
+            self.name = None
+            self.name = tempfile.mkdtemp()
+
+        def __enter__(self):
+            return self.name
+
+        def __exit__(self, exctype, excvalue, exctrace):
+            try:
+                shutil.rmtree(self.name, True)
+            except OSError:
+                pass
+            self.name = None
+
+
+try:
+    from subprocess import check_output
+except ImportError:
+    # From /usr/lib/python2.7/subprocess.py
+    def check_output(*popenargs, **kwargs):
+        if 'stdout' in kwargs:
+            raise ValueError(
+                'stdout argument not allowed, it will be overridden.')
+        process = subprocess.Popen(
+            stdout=subprocess.PIPE, *popenargs, **kwargs)
+        output, unused_err = process.communicate()
+        retcode = process.poll()
+        if retcode:
+            cmd = kwargs.get("args")
+            if cmd is None:
+                cmd = popenargs[0]
+            raise CalledProcessError(retcode, cmd, output=output)
+        return output
+
+    subprocess.check_output = check_output
+
+
+def parse_stanza(version, prior_version, lines):
+    gbl = []
+    local = []
+    cur = None
+
+    cur = 0
+    for I in re.finditer(
+            r"\s*(?:(global:)|(local:)(\s*\*\s*;)|(?:(\w+)\s*;))",
+            lines,
+            flags=re.DOTALL | re.MULTILINE):
+        if I.group(1):  # global
+            lst = gbl
+        if I.group(2):  # local
+            lst = local
+        if I.group(3):  # wildcard
+            lst.append("*")
+            assert (cur is not gbl)
+        if I.group(4):  # symbol name
+            lst.append(I.group(4))
+
+        assert cur == I.start()
+        cur = I.end()
+    assert cur == len(lines)
+
+    return SymVer(version or "", prior_version or "", gbl, local)
+
+
+def load_map(fn):
+    """This is a lame regex based parser for GNU linker map files. It asserts if
+    the map file is invalid. It returns a list of the global symbols"""
+    with open(fn, "rt") as F:
+        lines = F.read()
+    p = re.compile(r"/\*.*?\*/", flags=re.DOTALL)
+    lines = re.sub(p, "", lines)
+    lines = lines.strip()
+
+    # Extract each stanza
+    res = []
+    cur = 0
+    for I in re.finditer(
+            r"\s*(?:(\S+)\s+)?{(.*?)\s*}(\s*\S+)?\s*;",
+            lines,
+            flags=re.DOTALL | re.MULTILINE):
+        assert cur == I.start()
+        res.append(parse_stanza(I.group(1), I.group(3), I.group(2)))
+        cur = I.end()
+    assert cur == len(lines)
+
+    return res
+
+
+class Lib(object):
+    def __init__(self, libfn, tmpdir):
+        self.libfn = os.path.basename(libfn)
+        self.objdir = os.path.join(tmpdir, self.libfn)
+        self.final_objdir = os.path.join(tmpdir, "r-" + self.libfn)
+        self.final_lib = os.path.join(os.path.dirname(libfn), "..", self.libfn)
+        self.needs = set()
+        self.needed = set()
+
+        os.makedirs(self.objdir)
+        os.makedirs(self.final_objdir)
+
+        subprocess.check_call([args.ar, "x", libfn], cwd=self.objdir)
+        self.objects = [I for I in os.listdir(self.objdir)]
+        self.get_syms()
+
+    def get_syms(self):
+        """Read the definedsymbols from each object file"""
+        self.syms = set()
+        self.needed_syms = set()
+        for I in self.objects:
+            I = os.path.join(self.objdir, I)
+            syms = subprocess.check_output([args.nm, "--defined-only", I])
+            for ln in syms.decode().splitlines():
+                ln = ln.split()
+                if ln[1].isupper():
+                    self.syms.add(ln[2])
+
+            syms = subprocess.check_output([args.nm, "--undefined-only", I])
+            for ln in syms.decode().splitlines():
+                ln = ln.split()
+                if ln[0].isupper():
+                    self.needed_syms.add(ln[1])
+
+    def rename_syms(self, rename_fn):
+        """Invoke objcopy on all the objects to rename their symbols"""
+        for I in self.objects:
+            subprocess.check_call([
+                args.objcopy,
+                "--redefine-syms=%s" % (rename_fn),
+                os.path.join(self.objdir, I),
+                os.path.join(self.final_objdir, I)
+            ])
+
+    def incorporate_internal(self, internal_libs):
+        """If this library requires an internal library then we want to inline it into
+        this lib when we reconstruct it."""
+        for lib in self.needs.intersection(internal_libs):
+            self.objects.extend(
+                os.path.join(lib.final_objdir, I) for I in lib.objects)
+
+    def finalize(self):
+        """Write out the now modified library"""
+        try:
+            os.unlink(self.final_lib)
+        except OSError:
+            pass
+        subprocess.check_call(
+            [args.ar, "qsc", self.final_lib] +
+            [os.path.join(self.final_objdir, I) for I in self.objects])
+
+
+def compute_graph(libs):
+    """Look at the symbols each library provides vs the symbols each library needs
+    and organize the libraries into a graph."""
+    for a, b in itertools.permutations(libs, 2):
+        if not a.syms.isdisjoint(b.needed_syms):
+            b.needs.add(a)
+            a.needed.add(b)
+
+    # Use transitivity to prune the needs list
+    def prune(cur_lib, to_prune):
+        for I in cur_lib.needed:
+            I.needs.discard(to_prune)
+            to_prune.needed.discard(I)
+            prune(I, to_prune)
+
+    for cur_lib in libs:
+        for I in list(cur_lib.needed):
+            prune(I, cur_lib)
+
+
+parser = argparse.ArgumentParser(
+    description='Generate static libraries for distribution')
+parser.add_argument(
+    "--map",
+    dest="maps",
+    action="append",
+    help="List of map files defining all the public symbols",
+    default=[])
+parser.add_argument(
+    "--lib", dest="libs", action="append", help="The input static libraries")
+parser.add_argument(
+    "--internal_lib",
+    dest="internal_libs",
+    action="append",
+    help=
+    "The internal static libraries, these will be merged into other libraries")
+parser.add_argument(
+    "--version", action="store", help="Package version number", required=True)
+parser.add_argument("--ar", action="store", help="ar tool", required=True)
+parser.add_argument("--nm", action="store", help="nm tool", required=True)
+parser.add_argument(
+    "--objcopy", action="store", help="objcopy tool", required=True)
+args = parser.parse_args()
+
+global_syms = set()
+for fn in sorted(set(args.maps)):
+    for I in load_map(fn):
+        # Private symbols in libibverbs are also mangled for maximum safety.
+        if "PRIVATE" not in I.version:
+            global_syms.update(I.globals)
+
+with TemporaryDirectory() as tmpdir:
+    libs = set(Lib(fn, tmpdir) for fn in args.libs)
+    internal_libs = set(Lib(fn, tmpdir) for fn in args.internal_libs)
+    all_libs = libs | internal_libs
+
+    all_syms = set()
+    for I in all_libs:
+        all_syms.update(I.syms)
+    compute_graph(all_libs)
+
+    # Generate a redefine file for objcopy that will sanitize the internal names
+    prefix = re.sub(r"\W", "_", args.version)
+    redefine_fn = os.path.join(tmpdir, "redefine")
+    with open(redefine_fn, "wt") as F:
+        for I in sorted(all_syms - global_syms):
+            F.write("%s rdmacore%s_%s\n" % (I, prefix, I))
+
+    for I in all_libs:
+        I.rename_syms(redefine_fn)
+
+    for I in libs:
+        I.incorporate_internal(internal_libs)
+        I.finalize()
-- 
2.19.1




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Photo]     [Yosemite News]     [Yosemite Photos]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux