Skip to content

Commit

Permalink
Support statically linked builds
Browse files Browse the repository at this point in the history
With apologies for the CMake abuse, this adds support for statically linking the Cyclone
core library and the security and Iceoryx plugins.  It does not support statically linking
IDLC because that would at best only support the C binding, but not other language
bindings.  The recommendation for a static linked Cyclone is to build it as if it were a
cross build to the native platform, that way the IDLC complications are avoided.

Almost all the changes are in the build system, only the "dlopen" functions in ddsrt are
modified to consult a table of compiled-in plugins first and some security tests need a
small change in the specification of the security plugin wrapper libraries.

The build system defines a global CMake property "plugin_list" containing a list of all
the plugins to include, each plugin P defines a global "P_symbols" property that lists all
the symbols to be exported from the library.  The modified "dlopen" functionality consults
the table of plugins, the modified "dlsym" functionality consults the list of exported
symbols.

These properties are translated to macros on compiler command-line into comma-separated
lists of plugin names (PLUGINS) and symbol names (PLUG_SUMBOLS_P for plugin P).  The
macros are then expanded into static tables of names and addresses by the C preprocessor.

For a static build, the plugins are defined as OBJECT libraries, "installed" because
that's the only way I have found so far to keep CMake happy with the references, and
pulled into the Cyclone library using the aforementioned properties.  For example:

  if(BUILD_SHARED_LIBS)
    add_library(dds_security_ac SHARED ${sources} ${private_headers})
  else()
    add_library(dds_security_ac OBJECT ${sources} ${private_headers})
    set_property(GLOBAL APPEND PROPERTY cdds_plugin_list dds_security_ac)
    set_property(GLOBAL PROPERTY dds_security_ac_symbols init_access_control finalize_access_control)
  endif()

The other changes are then to only link the plugin against "ddsc" if building as a shared
library and, sometimes, adding a bunch of include paths because it won't work otherwise.

In a static build, the tests for PSMX that rely on the Cyclone-based plugin are disabled
because of the complications that introduces.  Those tests do run if Iceoryx is available.

Signed-off-by: Erik Boasson <eb@ilities.com>
  • Loading branch information
eboasson committed Mar 7, 2024
1 parent 504ef78 commit 6dcc6e3
Show file tree
Hide file tree
Showing 23 changed files with 462 additions and 77 deletions.
25 changes: 24 additions & 1 deletion CMakeLists.txt
Expand Up @@ -10,7 +10,8 @@
# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
#
cmake_minimum_required(VERSION 3.16)
project(CycloneDDS VERSION 0.11.0 LANGUAGES C)
# C++ is only for Iceoryx plugin
project(CycloneDDS VERSION 0.11.0 LANGUAGES C CXX)

# Set a default build type if none was specified
set(default_build_type "RelWithDebInfo")
Expand All @@ -29,6 +30,28 @@ else()
set(not_crosscompiling ON)
endif()

# By default we do shared libraries (we really prefer shared libraries because of the
# plugins for IDLC, security, PSMX, ...)
#
# For static builds, we recommend doing a regular shared library build first, then
# building the static Cyclone library with CMAKE_CROSSCOMPILING set. This avoids the
# dynamic linking in IDLC of something involving the static library, and that in turn
# avoids a position-dependent/position-independent mess.
#
# Note that on Linux that mess can be partially resolved by defining
#
# CMAKE_POSITION_INDEPENDENT_CODE=1
#
# resulting in a PIC static library, but that is in turn incompatible with the
# system-provided static libraries for OpenSSL. So much easier to avoid it for the rare
# case where it is needed.
#
# It appears that on macOS, all code is position independent and it'll work regardless.
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
if(NOT BUILD_SHARED_LIBS AND NOT CMAKE_CROSSCOMPILING)
message(WARNING "It is probably best to build a static library as-if cross compiling (e.g., use -DCMAKE_CROSSCOMPILING=1 -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME})")
endif()

# By default don't treat warnings as errors, else anyone building it with a different compiler that
# just happens to generate a warning, as well as anyone adding or modifying something and making a
# small mistake would run into errors. CI builds can be configured differently.
Expand Down
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Expand Up @@ -113,7 +113,7 @@ if(BUILD_IDLC)
add_subdirectory(idl)
endif()
add_subdirectory(security)
add_subdirectory(core)
if(ENABLE_ICEORYX)
add_subdirectory(psmx_iox)
endif()
add_subdirectory(core)
26 changes: 21 additions & 5 deletions src/core/CMakeLists.txt
Expand Up @@ -11,11 +11,7 @@
#
include (GenerateExportHeader)

if (BUILD_SHARED_LIBS OR NOT DEFINED BUILD_SHARED_LIBS)
add_library(ddsc SHARED "")
else()
add_library(ddsc)
endif()
add_library(ddsc)

if(BUILD_TESTING)
set_property(TARGET ddsc PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
Expand Down Expand Up @@ -66,6 +62,26 @@ include(cdr/CMakeLists.txt)
include(ddsi/CMakeLists.txt)
include(ddsc/CMakeLists.txt)

# The not-so-elegant inclusion of all configured plug-ins for static builds. At least it
# keeps most of the dirty things in one place.
if(NOT BUILD_SHARED_LIBS)
get_property(plugin_list GLOBAL PROPERTY cdds_plugin_list)
if(plugin_list)
list(JOIN plugin_list "," plugin_commasep)
target_compile_options(ddsc PRIVATE "-DPLUGINS=${plugin_commasep}")
target_link_libraries(ddsc PRIVATE ${plugin_list})
foreach(plugin ${plugin_list})
set(propname ${plugin}_symbols)
get_property(plugin_symbols GLOBAL PROPERTY ${propname})
list(JOIN plugin_symbols "," plugin_symbols_commasep)
target_compile_options(ddsc PRIVATE "-DPLUGIN_SYMBOLS_${plugin}=${plugin_symbols_commasep}")
endforeach()
endif()
if(ENABLE_SSL)
target_link_libraries(ddsc PUBLIC security_openssl)
endif()
endif()

add_coverage(ddsc)
target_link_libraries(ddsc PRIVATE "$<BUILD_INTERFACE:ddsrt>")
target_compile_definitions(
Expand Down
7 changes: 1 addition & 6 deletions src/core/cdr/CMakeLists.txt
Expand Up @@ -34,12 +34,7 @@ else()
include(Generate)
include(GenerateExportHeader)

if (BUILD_SHARED_LIBS OR NOT DEFINED BUILD_SHARED_LIBS)
add_library(cdr SHARED "")
else()
add_library(cdr)
endif()

add_library(cdr)
add_library(${PROJECT_NAME}::cdr ALIAS cdr)

target_sources(cdr PRIVATE ${srcs_cdr} ${hdrs_private_cdr} ${CMAKE_CURRENT_LIST_DIR}/../../ddsrt/src/bswap.c)
Expand Down
3 changes: 1 addition & 2 deletions src/core/ddsc/CMakeLists.txt
Expand Up @@ -133,7 +133,6 @@ install(
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT dev)

# TODO: improve test inclusion.
if((BUILD_TESTING) AND (BUILD_IDLC) AND ((NOT DEFINED MSVC_VERSION) OR (MSVC_VERSION GREATER "1800")))
if((BUILD_TESTING) AND ((NOT DEFINED MSVC_VERSION) OR (MSVC_VERSION GREATER "1800")))
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/tests")
endif()
71 changes: 49 additions & 22 deletions src/core/ddsc/tests/CMakeLists.txt
Expand Up @@ -10,6 +10,7 @@
# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
#
include(CUnit)
include(Generate)

idlc_generate(TARGET RoundTrip FILES RoundTrip.idl)
idlc_generate(TARGET Space FILES Space.idl WARNINGS no-implicit-extensibility)
Expand Down Expand Up @@ -100,9 +101,25 @@ set(ddsc_test_sources
"test_oneliner.h"
"cdrstream.c"
"serdata_keys.c"
"psmx.c"
)

# PSMX tests are tricky in a static build: we have a PSMX plugin that is based on Cyclone
# so we can test the interface even when Iceoryx is not available, but supporting that
# plugin is too complicated in a static build: it introduces a circular dependency and
# that's too hard for me in CMake.
#
# On most platforms (Linux, Windows, macOS) we could still load it dynamically but that
# fails to work because the shared library then includes its own copy of Cyclone DDS and
# then the access to the application readers that the plugin needs is no longer possible
# because they live in the other copy of the library.
#
# Fortunately, running them with Iceoryx doesn't suffer from this. If we simply skip
# these tests in a static build without Iceoryx and then we ensure the static build on CI
# uses Iceoryx, we should be good.
if(BUILD_SHARED_LIBS OR (ENABLE_ICEORYX AND NOT DEFINED ENV{COLCON}))
list(APPEND ddsc_test_sources "psmx.c")
endif()

if(ENABLE_LIFESPAN)
list(APPEND ddsc_test_sources "lifespan.c")
endif()
Expand Down Expand Up @@ -222,23 +239,24 @@ target_link_libraries(oneliner PRIVATE RoundTrip Space ddsc)


# PSMX implementation with Cyclone as transport, for testing
if (BUILD_SHARED_LIBS)
idlc_generate(TARGET psmx_cdds_data FILES psmx_cdds_data.idl)
set(psmx_cdds_sources
"psmx_cdds_impl.c"
"psmx_cdds_impl.h")
add_library(psmx_cdds SHARED ${psmx_cdds_sources})
target_include_directories(
psmx_cdds PRIVATE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../ddsi/include>")
target_link_libraries(psmx_cdds PRIVATE ddsc psmx_cdds_data)

idlc_generate(TARGET psmx_cdds_data FILES psmx_cdds_data.idl)
set(psmx_cdds_sources
"psmx_cdds_impl.c"
"psmx_cdds_impl.h")
add_library(psmx_cdds SHARED ${psmx_cdds_sources})
target_include_directories(
psmx_cdds PRIVATE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../ddsi/include>")
target_link_libraries(psmx_cdds PRIVATE ddsc psmx_cdds_data)

generate_export_header(psmx_cdds BASE_NAME PSMX_CDDS EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/include/dds/psmx_cdds/export.h")
generate_export_header(psmx_cdds BASE_NAME PSMX_CDDS EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/include/dds/psmx_cdds/export.h")

install(TARGETS psmx_cdds
install(TARGETS psmx_cdds
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
endif()

# If Iceoryx is available, then also run all PSMX tests using Iceoryx. Colcon complicates
# things too much and it doesn't affect Cyclone's CI run anyway.
Expand Down Expand Up @@ -285,14 +303,23 @@ kill -0 `cat ctest_fixture_iox_roudi.pid`")
set_tests_properties(stop_roudi PROPERTIES FIXTURES_CLEANUP iox)
set_tests_properties(start_roudi stop_roudi PROPERTIES RESOURCE_LOCK iox_lock)

# Construct Iceoryx-variants of all PSMX tests
# Construct Iceoryx-variants of all PSMX tests if building shared libraries, map them to
# Iceoryx in a static build (because I don't know how to delete tests!)
get_property(test_names DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY TESTS)
list(FILTER test_names INCLUDE REGEX "^CUnit_ddsc_psmx_[A-Za-z_0-9]+$")
foreach(fullname ${test_names})
string(REGEX REPLACE "^CUnit_ddsc_psmx_(.*)" "\\1" shortname "${fullname}")
add_test(NAME ${fullname}_iox COMMAND cunit_ddsc -s ddsc_psmx -t ${shortname})
set_tests_properties(${fullname}_iox PROPERTIES FIXTURES_REQUIRED iox)
set_tests_properties(${fullname}_iox PROPERTIES RESOURCE_LOCK iox_lock)
set_tests_properties(${fullname}_iox PROPERTIES ENVIRONMENT "CDDS_PSMX_NAME=iox;LD_LIBRARY_PATH=$<TARGET_FILE_DIR:psmx_iox>:$ENV{LD_LIBRARY_PATH}")
endforeach()
if(BUILD_SHARED_LIBS)
foreach(fullname ${test_names})
string(REGEX REPLACE "^CUnit_ddsc_psmx_(.*)" "\\1" shortname "${fullname}")
add_test(NAME ${fullname}_iox COMMAND cunit_ddsc -s ddsc_psmx -t ${shortname})
set_tests_properties(${fullname}_iox PROPERTIES FIXTURES_REQUIRED iox)
set_tests_properties(${fullname}_iox PROPERTIES RESOURCE_LOCK iox_lock)
set_tests_properties(${fullname}_iox PROPERTIES ENVIRONMENT "CDDS_PSMX_NAME=iox;LD_LIBRARY_PATH=$<TARGET_FILE_DIR:psmx_iox>:$ENV{LD_LIBRARY_PATH}")
endforeach()
else()
foreach(fullname ${test_names})
set_tests_properties(${fullname} PROPERTIES FIXTURES_REQUIRED iox)
set_tests_properties(${fullname} PROPERTIES RESOURCE_LOCK iox_lock)
set_tests_properties(${fullname} PROPERTIES ENVIRONMENT "CDDS_PSMX_NAME=iox")
endforeach()
endif()
endif()
5 changes: 5 additions & 0 deletions src/ddsrt/CMakeLists.txt
Expand Up @@ -78,6 +78,7 @@ set(headers
set(sources
"${source_dir}/src/atomics.c"
"${source_dir}/src/avl.c"
"${source_dir}/src/dynlib.c"
"${source_dir}/src/bits.c"
"${source_dir}/src/bswap.c"
"${source_dir}/src/io.c"
Expand Down Expand Up @@ -318,6 +319,10 @@ if(BUILD_TESTING)
set(DDS_ALLOW_NESTED_DOMAIN 1)
endif()

if (NOT BUILD_SHARED_LIBS)
set(DDS_IS_STATIC_LIBRARY 1)
endif()

configure_file(include/dds/config.h.in include/dds/config.h)
configure_file(include/dds/features.h.in include/dds/features.h)
configure_file(include/dds/version.h.in include/dds/version.h)
Expand Down
6 changes: 3 additions & 3 deletions src/ddsrt/include/dds/ddsrt/countargs.h
Expand Up @@ -18,9 +18,9 @@

#define DDSRT_COUNT_ARGS_MSVC_WORKAROUND(x) x

/** @brief Returns the number of arguments provided (at most 10) */
#define DDSRT_COUNT_ARGS(...) DDSRT_COUNT_ARGS1 (__VA_ARGS__, 10,9,8,7,6,5,4,3,2,1,0)
/** @brief Returns the number of arguments provided (at most 20) */
#define DDSRT_COUNT_ARGS(...) DDSRT_COUNT_ARGS1 (__VA_ARGS__, 20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)
#define DDSRT_COUNT_ARGS1(...) DDSRT_COUNT_ARGS_MSVC_WORKAROUND (DDSRT_COUNT_ARGS_ARGN (__VA_ARGS__))
#define DDSRT_COUNT_ARGS_ARGN(a,b,c,d,e,f,g,h,i,j,n,...) n
#define DDSRT_COUNT_ARGS_ARGN(a,b,c,d,e,f,g,h,i,j, k,l,m,n,o,p,q,r,s,t, N,...) N

#endif
21 changes: 21 additions & 0 deletions src/ddsrt/include/dds/ddsrt/dynlib.h
Expand Up @@ -64,6 +64,12 @@ ddsrt_dlopen(
bool translate,
ddsrt_dynlib_t *handle) ddsrt_nonnull_all;

dds_return_t
ddsrt_platform_dlopen(
const char *name,
bool translate,
ddsrt_dynlib_t *handle) ddsrt_nonnull_all;

/**
* @brief Close the library.
*
Expand All @@ -86,6 +92,10 @@ dds_return_t
ddsrt_dlclose(
ddsrt_dynlib_t handle);

dds_return_t
ddsrt_platform_dlclose(
ddsrt_dynlib_t handle);

/**
* @brief Get the memory address of a symbol.
*
Expand All @@ -111,6 +121,12 @@ ddsrt_dlsym(
const char *symbol,
void **address);

dds_return_t
ddsrt_platform_dlsym(
ddsrt_dynlib_t handle,
const char *symbol,
void **address);

/**
* @brief Get the most recent library related error.
*
Expand Down Expand Up @@ -138,6 +154,11 @@ ddsrt_dlerror(
char *buf,
size_t buflen);

dds_return_t
ddsrt_platform_dlerror(
char *buf,
size_t buflen);

#if defined (__cplusplus)
}
#endif
Expand Down
3 changes: 3 additions & 0 deletions src/ddsrt/include/dds/features.h.in
Expand Up @@ -41,4 +41,7 @@
/* Not for general use, specificly for testing psmx Cyclone DDS plugin */
#cmakedefine DDS_ALLOW_NESTED_DOMAIN 1

/* Not intended for general use, whether building a static library, specifically testing psmx and security */
#cmakedefine DDS_IS_STATIC_LIBRARY 1

#endif

0 comments on commit 6dcc6e3

Please sign in to comment.