Skip to content

Commit

Permalink
Add functions to enable binary relocatability in downstream libraries
Browse files Browse the repository at this point in the history
Signed-off-by: Silvio Traversaro <[email protected]>
  • Loading branch information
traversaro committed Jan 16, 2023
1 parent 4aa9523 commit c0f4ecd
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 2 deletions.
177 changes: 175 additions & 2 deletions cmake/IgnUtils.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1622,7 +1622,8 @@ endmacro()
# SOURCES <sources>
# [LIB_DEPS <library_dependencies>]
# [INCLUDE_DIRS <include_dependencies>]
# [TEST_LIST <output_var>])
# [TEST_LIST <output_var>]
# [ENVIRONMENT <environment>])
#
# Build tests for an ignition project. Arguments are as follows:
#
Expand All @@ -1647,12 +1648,14 @@ endmacro()
# will also skip the step of copying the runtime library
# into your executable's directory.
#
# ENVIRONMENT: Optional. Used to set the ENVIRONMENT property of the tests.
#
macro(ign_build_tests)

#------------------------------------
# Define the expected arguments
set(options SOURCE EXCLUDE_PROJECT_LIB) # NOTE: DO NOT USE "SOURCE", we're adding it here to catch typos
set(oneValueArgs TYPE TEST_LIST)
set(oneValueArgs TYPE TEST_LIST ENVIRONMENT)
set(multiValueArgs SOURCES LIB_DEPS INCLUDE_DIRS)


Expand Down Expand Up @@ -1725,6 +1728,10 @@ macro(ign_build_tests)
add_test(NAME ${target_name} COMMAND
${target_name} --gtest_output=xml:${CMAKE_BINARY_DIR}/test_results/${target_name}.xml)

if(ign_build_tests_ENVIRONMENT)
set_property(TEST ${target_name} PROPERTY ENVIRONMENT ${ign_build_tests_ENVIRONMENT})
endif()

if(UNIX)
# gtest requies pthread when compiled on a Unix machine
target_link_libraries(${target_name} pthread)
Expand Down Expand Up @@ -1804,3 +1811,169 @@ macro(_ign_cmake_parse_arguments prefix options oneValueArgs multiValueArgs)
endif()

endmacro()

#################################################
# ign_add_get_install_prefix_impl(GET_INSTALL_PREFIX_FUNCTION <get_install_prefix_function>
# GET_INSTALL_PREFIX_HEADER <get_install_prefix_function>
# OVERRIDE_INSTALL_PREFIX_ENV_VARIABLE <override_install_prefix_env_variable>)
#
# This macro adds to ${PROJECT_LIBRARY_TARGET_NAME} the implementation of
# of the function passed by GET_INSTALL_PREFIX_FUNCTION and declared in
# GET_INSTALL_PREFIX_HEADER .
#
# The defined functions implements a GET_INSTALL_PREFIX_FUNCTION that returns
# the installation directory of the package (CMAKE_INSTALL_PREFIX at build time)
# as following:
# * if the library is shared and IGN_DISABLE_RELOCATABLE, by extract the
# location of the shared library via dladdr, and computing the corresponding
# install prefix from it
# * if the library is static or IGN_DISABLE_RELOCATABLE is ON, by using the exact
# value of CMAKE_INSTALL_PREFIX that was hardcoded in the library at compilation time
#
# As in some cases it is important to have the ability to control and change the value returned by
# the GET_INSTALL_PREFIX_FUNCTION at runtime, in both cases the library returns the value of
# the OVERRIDE_INSTALL_PREFIX_ENV_VARIABLE if the environment variable is defined
#
# To use this macro, please add ign_find_package(DL)
# in the dependencies of your project
macro(ign_add_get_install_prefix_impl)
set(_options)
set(_oneValueArgs
GET_INSTALL_PREFIX_FUNCTION
GET_INSTALL_PREFIX_HEADER
OVERRIDE_INSTALL_PREFIX_ENV_VARIABLE
)
set(_multiValueArgs )
cmake_parse_arguments(ign_add_get_install_prefix_impl "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN})

if(NOT DEFINED ign_add_get_install_prefix_impl_GET_INSTALL_PREFIX_FUNCTION)
message(FATAL_ERROR
"ign_add_get_install_prefix_impl: missing parameter GET_INSTALL_PREFIX_FUNCTION")
endif()

if(NOT DEFINED ign_add_get_install_prefix_impl_GET_INSTALL_PREFIX_HEADER)
message(FATAL_ERROR
"ign_add_get_install_prefix_impl: missing parameter GET_INSTALL_PREFIX_HEADER")
endif()

if(NOT DEFINED ign_add_get_install_prefix_impl_OVERRIDE_INSTALL_PREFIX_ENV_VARIABLE)
message(FATAL_ERROR
"ign_add_get_install_prefix_impl: missing parameter OVERRIDE_INSTALL_PREFIX_ENV_VARIABLE")
endif()


if(NOT TARGET ${PROJECT_LIBRARY_TARGET_NAME})
message(FATAL_ERROR
"Target ${PROJECT_LIBRARY_TARGET_NAME} required by ign_add_get_install_prefix_impl\n"
"does not exist.")
endif()

if(NOT TARGET ${DL_TARGET})
message(FATAL_ERROR
"ign_add_get_install_prefix_impl called without DL_TARGET defined,\n"
"please add ign_find_package(DL) if you want to use ign_add_get_install_prefix_impl.")
endif()

get_target_property(target_type ${PROJECT_LIBRARY_TARGET_NAME} TYPE)
if(NOT (target_type STREQUAL "STATIC_LIBRARY" OR target_type STREQUAL "MODULE_LIBRARY" OR target_type STREQUAL "SHARED_LIBRARY"))
message(FATAL_ERROR "ign_add_get_install_prefix_impl: library ${_library} is of unsupported type ${target_type}")
endif()


set(ign_add_get_install_prefix_impl_GENERATED_CPP
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_LIBRARY_TARGET_NAME}_get_install_prefix_impl.cc)

# Write cpp for shared or module library type
option(IGN_DISABLE_RELOCATABLE "If ON, disable the feature of providing a relocatable install prefix in shared library." OFF)
if ((target_type STREQUAL "MODULE_LIBRARY" OR target_type STREQUAL "SHARED_LIBRARY") AND NOT IGN_DISABLE_RELOCATABLE)
# We can't query the LOCATION property of the target due to https://cmake.org/cmake/help/v3.25/policy/CMP0026.html
# We can only access the directory of the library at generation time via $<TARGET_FILE_DIR:tgt>
file(GENERATE OUTPUT "${ign_add_get_install_prefix_impl_GENERATED_CPP}"
CONTENT
"// This file is automatically generated by the ign_add_get_install_prefix_impl CMake macro.
#include <cstdlib>
#include <filesystem>
#include <string>
#include <dlfcn.h>
#include <${ign_add_get_install_prefix_impl_GET_INSTALL_PREFIX_HEADER}>
std::string ${ign_add_get_install_prefix_impl_GET_INSTALL_PREFIX_FUNCTION}()
{
if(const char* override_env_var = std::getenv(\"${ign_add_get_install_prefix_impl_OVERRIDE_INSTALL_PREFIX_ENV_VARIABLE}\"))
{
return std::string(override_env_var);
}
std::error_code fs_error;
std::filesystem::path library_location;
// Get location of the library
Dl_info address_info;
int res_val = dladdr(reinterpret_cast<void *>(&${ign_add_get_install_prefix_impl_GET_INSTALL_PREFIX_FUNCTION}), &address_info);
if (address_info.dli_fname && res_val > 0)
{
library_location = address_info.dli_fname;
}
else
{
return \"${CMAKE_INSTALL_PREFIX}\";
}
const std::filesystem::path library_directory = library_location.parent_path();
// Given the library_directory, return the install prefix via the relative path
#ifndef _WIN32
const std::filesystem::path rel_path_from_install_prefix_to_lib = std::string(\"${CMAKE_INSTALL_LIBDIR}\");
#else
const std::filesystem::path rel_path_from_install_prefix_to_lib = std::string(\"${CMAKE_INSTALL_BINDIR}\");
#endif
const std::filesystem::path rel_path_from_lib_to_install_prefix =
std::filesystem::relative(std::filesystem::current_path(), std::filesystem::current_path() / rel_path_from_install_prefix_to_lib, fs_error);
if (fs_error)
{
return \"${CMAKE_INSTALL_PREFIX}\";
}
const std::filesystem::path install_prefix = library_directory / rel_path_from_lib_to_install_prefix;
const std::filesystem::path install_prefix_canonical = std::filesystem::canonical(install_prefix, fs_error);
if (fs_error)
{
return \"${CMAKE_INSTALL_PREFIX}\";
}
// Return install prefix
return install_prefix_canonical.string();
}
")
else()
# For static library, fallback to just provide return CMAKE_INSTALL_PREFIX
file(GENERATE OUTPUT "${ign_add_get_install_prefix_impl_GENERATED_CPP}"
CONTENT
"// This file is automatically generated by the ign_add_get_install_prefix_impl CMake macro.
#include <string>
#include <${ign_add_get_install_prefix_impl_GET_INSTALL_PREFIX_HEADER}>
std::string ${ign_add_get_install_prefix_impl_GET_INSTALL_PREFIX_FUNCTION}()
{
if(const char* override_env_var = std::getenv(\"${ign_add_get_install_prefix_impl_OVERRIDE_INSTALL_PREFIX_ENV_VARIABLE}\"))
{
return std::string(override_env_var);
}
return \"${CMAKE_INSTALL_PREFIX}\";
}
")
endif()

# Add cpp to library
target_sources(${PROJECT_LIBRARY_TARGET_NAME} PRIVATE ${ign_add_get_install_prefix_impl_GENERATED_CPP})

# Link DL_TARGET that provides dladdr
target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} PRIVATE ${DL_TARGET})

endmacro()
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ if (UNIX)
)
endif()

add_subdirectory(get_install_prefix)
add_subdirectory(warning)
51 changes: 51 additions & 0 deletions test/get_install_prefix/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# For the tests, we make sure that the relative path in the build location is the same
# of the install, and we make sure that the value returned by the shared library is ${CMAKE_BINARY_DIR}
# while for the static library we make sure that the value returned is ${CMAKE_INSTALL_PREFIX}
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}")

include(IgnUtils)

# dladdr from dl is a compulsory requirement for
# ign_add_get_install_prefix_impl
ign_find_package(DL REQUIRED)

# shared test
add_library(get-install-prefix-test-shared SHARED)
set(PROJECT_LIBRARY_TARGET_NAME get-install-prefix-test-shared)

ign_add_get_install_prefix_impl(GET_INSTALL_PREFIX_HEADER get_install_prefix_test_shared.h
GET_INSTALL_PREFIX_FUNCTION ignition::cmake::test::sharedlib::getInstallPrefix
OVERRIDE_INSTALL_PREFIX_ENV_VARIABLE GET_INSTALL_PREFIX_TEST_INSTALL_PREFIX)
set_target_properties(get-install-prefix-test-shared PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
target_include_directories(get-install-prefix-test-shared PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})


# static test
add_library(get-install-prefix-test-static STATIC)
set(PROJECT_LIBRARY_TARGET_NAME get-install-prefix-test-static)

ign_add_get_install_prefix_impl(GET_INSTALL_PREFIX_HEADER get_install_prefix_test_static.h
GET_INSTALL_PREFIX_FUNCTION ignition::cmake::test::staticlib::getInstallPrefix
OVERRIDE_INSTALL_PREFIX_ENV_VARIABLE GET_INSTALL_PREFIX_TEST_INSTALL_PREFIX)
target_include_directories(get-install-prefix-test-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})


# Write header with CMake variables to check
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/get_install_prefix_test_cmake_variables.h
"#pragma once
#define CMAKE_INSTALL_PREFIX \"${CMAKE_INSTALL_PREFIX}\"
#define CMAKE_BINARY_DIR \"${CMAKE_BINARY_DIR}\"
")



# Add test executable
add_executable(get-install-prefix-test get_install_prefix_test.cc)
target_include_directories(get-install-prefix-test PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(get-install-prefix-test PRIVATE get-install-prefix-test-shared
get-install-prefix-test-static)
add_test(NAME get-install-prefix-test COMMAND get-install-prefix-test)
82 changes: 82 additions & 0 deletions test/get_install_prefix/get_install_prefix_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2022 Open Source Robotics Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

#include <cstdlib>
#include <iostream>
#include <filesystem>

#include <get_install_prefix_test_shared.h>
#include <get_install_prefix_test_static.h>
#include <get_install_prefix_test_cmake_variables.h>

std::string toCanonical(const std::string input_path)
{
return std::filesystem::weakly_canonical(std::filesystem::path(input_path)).string();
}

int main()
{
// Test nominal behaviour

std::string sharedInstallPrefix = ignition::cmake::test::sharedlib::getInstallPrefix();
std::string staticInstallPrefix = ignition::cmake::test::staticlib::getInstallPrefix();

std::cerr << "get-install-prefix test:" << std::endl;
std::cerr << "sharedInstallPrefix: " << sharedInstallPrefix << std::endl;
std::cerr << "CMAKE_BINARY_DIR: " << CMAKE_BINARY_DIR << std::endl;
std::cerr << "staticInstallPrefix: " << staticInstallPrefix << std::endl;
std::cerr << "CMAKE_INSTALL_PREFIX: " << CMAKE_INSTALL_PREFIX << std::endl;

if (toCanonical(sharedInstallPrefix) != toCanonical(CMAKE_BINARY_DIR))
{
std::cerr << "getInstallPrefixShared returned unexpected value, test is failing." << std::endl;
return EXIT_FAILURE;
}

if (toCanonical(staticInstallPrefix) != toCanonical(CMAKE_INSTALL_PREFIX))
{
std::cerr << "getInstallPrefixStatic returned unexpected value, test is failing." << std::endl;
return EXIT_FAILURE;
}

// Test behaviour after setting the environment variable to modify the return values (only on Unix so we can use setenv)
#ifndef _WIN32
std::string overrideValue = "test_override_value";
int overwrite = 1;
setenv("GET_INSTALL_PREFIX_TEST_INSTALL_PREFIX" , overrideValue.c_str(), overwrite);
std::string sharedInstallPrefixWithOverride = ignition::cmake::test::sharedlib::getInstallPrefix();
std::string staticInstallPrefixWithOverride = ignition::cmake::test::staticlib::getInstallPrefix();

std::cerr << "overrideValue: " << overrideValue << std::endl;
std::cerr << "sharedInstallPrefixWithOverride: " << sharedInstallPrefixWithOverride << std::endl;
std::cerr << "staticInstallPrefixWithOverride: " << staticInstallPrefixWithOverride << std::endl;

if (overrideValue != sharedInstallPrefixWithOverride)
{
std::cerr << "getInstallPrefixShared with env variable override returned unexpected value, test is failing." << std::endl;
return EXIT_FAILURE;
}

if (overrideValue != sharedInstallPrefixWithOverride)
{
std::cerr << "getInstallPrefixShared with env variable override returned unexpected value, test is failing." << std::endl;
return EXIT_FAILURE;
}
#endif

return EXIT_SUCCESS;
}
13 changes: 13 additions & 0 deletions test/get_install_prefix/get_install_prefix_test_shared.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace ignition
{
namespace cmake
{
namespace test
{
namespace sharedlib
{
std::string getInstallPrefix();
}
}
}
}
13 changes: 13 additions & 0 deletions test/get_install_prefix/get_install_prefix_test_static.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace ignition
{
namespace cmake
{
namespace test
{
namespace staticlib
{
std::string getInstallPrefix();
}
}
}
}

0 comments on commit c0f4ecd

Please sign in to comment.