From 1f684d4a2df8f079206e9dedf3171c9dfa421718 Mon Sep 17 00:00:00 2001 From: Omar Padron Date: Thu, 7 Jul 2016 15:10:27 -0400 Subject: [PATCH 1/4] improve weak link handling --- CMakeLists.txt | 270 +----------- README.md | 63 +-- enumerateBuild.bash | 15 +- main.c | 8 +- targetLinkLibrariesWithDynamicLookup.cmake | 467 +++++++++++++++++++++ 5 files changed, 519 insertions(+), 304 deletions(-) create mode 100644 targetLinkLibrariesWithDynamicLookup.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 316a7c9..5f3e901 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,271 +7,7 @@ project(weak_linking_example C) include(CTest) enable_testing() -if(NOT DEFINED CAN_DOUBLE_LINK) - set(TC_SRC_DIR - "${CMAKE_BINARY_DIR}/try_compile_src/double-link") - set(TC_BIN_DIR - "${CMAKE_BINARY_DIR}/try_compile_build/double-link") - - file(MAKE_DIRECTORY ${TC_SRC_DIR}) - file(MAKE_DIRECTORY ${TC_BIN_DIR}) - - file(WRITE "${TC_SRC_DIR}/CMakeLists.txt" " - cmake_minimum_required(VERSION ${CMAKE_VERSION}) - include_directories(${TC_SRC_DIR}) - project(double_link_try C) - - add_library(number SHARED number.c) - - add_library(counter MODULE counter.c) - set_target_properties(counter PROPERTIES PREFIX \"\") - target_link_libraries(counter number) - - add_executable(main main.c) - target_link_libraries(main number) - target_link_libraries(main ${CMAKE_DL_LIBS}) - - install(TARGETS number LIBRARY DESTINATION \"${TC_BIN_DIR}\") - install(TARGETS counter LIBRARY DESTINATION \"${TC_BIN_DIR}\") - install(FILES main DESTINATION \"${TC_BIN_DIR}\") - ") - - file(WRITE "${TC_SRC_DIR}/number.c" " - #include - - static int _number; - void set_number(int number) { _number = number; } - int get_number() { return _number; } - ") - - file(WRITE "${TC_SRC_DIR}/number.h" " - #ifndef _NUMBER_H - #define _NUMBER_H - extern void set_number(int); - extern int get_number(void); - #endif - ") - - file(WRITE "${TC_SRC_DIR}/counter.c" " - #include - int count() { - int result = get_number(); - set_number(result + 1); - return result; - } - ") - - file(WRITE "${TC_SRC_DIR}/counter.h" " - #ifndef _COUNTER_H - #define _COUNTER_H - extern int count(void); - #endif - ") - - file(WRITE "${TC_SRC_DIR}/main.c" " - #include - #include - #include - - int my_count() { - int result = get_number(); - set_number(result + 1); - return result; - } - - int main(int argc, char **argv) { - void *counter_module; - int (*count)(void); - int result; - - counter_module = dlopen(\"./counter.so\", RTLD_LAZY); - if(!counter_module) goto error; - - count = dlsym(counter_module, \"count\"); - if(!count) goto error; - - result = count() != 0 ? 1 : - my_count() != 1 ? 1 : - my_count() != 2 ? 1 : - count() != 3 ? 1 : - count() != 4 ? 1 : - count() != 5 ? 1 : - my_count() != 6 ? 1 : 0; - - - goto done; - error: - fprintf(stderr, \"Error occured:\\n %s\\n\", dlerror()); - result = 1; - - done: - if(counter_module) dlclose(counter_module); - return result; - } - ") - - try_compile(double_link_compiles - "${TC_BIN_DIR}" - "${TC_SRC_DIR}" - "double_link_try") - - set(double_link_works 1) - if(double_link_compiles) - execute_process(COMMAND "${TC_BIN_DIR}/main" - WORKING_DIRECTORY "${TC_BIN_DIR}" - RESULT_VARIABLE double_link_works) - endif() - - if(double_link_works EQUAL 0) - set(double_link_works TRUE) - else() - set(double_link_works FALSE) - endif() - - set(CAN_DOUBLE_LINK - ${double_link_works} CACHE BOOL "ok to double link") -endif() - - -IF(NOT DEFINED HAS_DYNAMIC_LOOKUP) - set(TC_SRC_DIR - "${CMAKE_BINARY_DIR}/try_compile_src/dynamic-lookup") - set(TC_BIN_DIR - "${CMAKE_BINARY_DIR}/try_compile_build/dynamic-lookup") - - file(MAKE_DIRECTORY ${TC_SRC_DIR}) - file(MAKE_DIRECTORY ${TC_BIN_DIR}) - - file(WRITE "${TC_SRC_DIR}/CMakeLists.txt" " - cmake_minimum_required(VERSION ${CMAKE_VERSION}) - include_directories(${TC_SRC_DIR}) - project(dynamic_lookup_try C) - - add_library(number SHARED number.c) - - add_library(counter MODULE counter.c) - set_target_properties(counter PROPERTIES PREFIX \"\") - set_target_properties( - counter - PROPERTIES LINK_FLAGS \"-undefined dynamic_lookup\") - - add_executable(main main.c) - target_link_libraries(main number) - target_link_libraries(main ${CMAKE_DL_LIBS}) - - install(TARGETS number LIBRARY DESTINATION \"${TC_BIN_DIR}\") - install(TARGETS counter LIBRARY DESTINATION \"${TC_BIN_DIR}\") - install(FILES main DESTINATION \"${TC_BIN_DIR}\") - ") - - file(WRITE "${TC_SRC_DIR}/number.c" " - #include - - static int _number; - void set_number(int number) { _number = number; } - int get_number() { return _number; } - ") - - file(WRITE "${TC_SRC_DIR}/number.h" " - #ifndef _NUMBER_H - #define _NUMBER_H - extern void set_number(int); - extern int get_number(void); - #endif - ") - - file(WRITE "${TC_SRC_DIR}/counter.c" " - #include - int count() { - int result = get_number(); - set_number(result + 1); - return result; - } - ") - - file(WRITE "${TC_SRC_DIR}/counter.h" " - #ifndef _COUNTER_H - #define _COUNTER_H - extern int count(void); - #endif - ") - - file(WRITE "${TC_SRC_DIR}/main.c" " - #include - #include - #include - - int my_count() { - int result = get_number(); - set_number(result + 1); - return result; - } - - int main(int argc, char **argv) { - void *counter_module; - int (*count)(void); - int result; - - counter_module = dlopen(\"./counter.so\", RTLD_LAZY); - if(!counter_module) goto error; - - count = dlsym(counter_module, \"count\"); - if(!count) goto error; - - result = count() != 0 ? 1 : - my_count() != 1 ? 1 : - my_count() != 2 ? 1 : - count() != 3 ? 1 : - count() != 4 ? 1 : - count() != 5 ? 1 : - my_count() != 6 ? 1 : 0; - - - goto done; - error: - fprintf(stderr, \"Error occured:\\n %s\\n\", dlerror()); - result = 1; - - done: - if(counter_module) dlclose(counter_module); - return result; - } - ") - - try_compile(dynamic_lookup_compiles - "${TC_BIN_DIR}" - "${TC_SRC_DIR}" - "dynamic_lookup_try") - - set(dynamic_lookup_works 1) - if(dynamic_lookup_compiles) - execute_process(COMMAND "${TC_BIN_DIR}/main" - WORKING_DIRECTORY "${TC_BIN_DIR}" - RESULT_VARIABLE dynamic_lookup_works) - endif() - - if(dynamic_lookup_works EQUAL 0) - set(dynamic_lookup_works TRUE) - else() - set(dynamic_lookup_works FALSE) - endif() - - set(HAS_DYNAMIC_LOOKUP ${dynamic_lookup_works} - CACHE BOOL "linker supports dynamic_lookup") -endif() - - -function(target_weak_link_libraries target) - if(HAS_DYNAMIC_LOOKUP) - set_target_properties(${target} - PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") - elseif(CAN_DOUBLE_LINK) - target_link_libraries(${target} ${ARGN}) - else() - message(FATAL_ERROR "Cannot weak link target: ${target}") - endif() -endfunction() - +include(targetLinkLibrariesWithDynamicLookup.cmake) set(LIB_TYPE SHARED CACHE STRING "library type") set(WEAK_LINK_MODULE TRUE CACHE BOOL "weakly link module against library") @@ -286,7 +22,7 @@ add_library(number ${LIB_TYPE} number.c) add_library(counter MODULE counter.c) set_target_properties(counter PROPERTIES PREFIX "") if(WEAK_LINK_MODULE) - target_weak_link_libraries(counter number) + target_link_libraries_with_dynamic_lookup(counter number) else() target_link_libraries(counter number) endif() @@ -295,7 +31,7 @@ endif() # EXECUTABLE (main) add_executable(main main.c) if(WEAK_LINK_EXE) - target_weak_link_libraries(main number) + target_link_libraries_with_dynamic_lookup(main number) else() target_link_libraries(main number) endif() diff --git a/README.md b/README.md index fb25696..a9adf33 100644 --- a/README.md +++ b/README.md @@ -55,54 +55,57 @@ Replace "d10" with your desired combination to run that particular build. ##### Expected Results -NOTE: This demo performs two series of host examinations. The first is to -determine if the host system supports weak linking. The second is to determine -if the dynamic loader can properly merge symbol entries that have been -duplicated across link boundaries (e.g: Linux). Even if the host is found to -not support weak linking, the "weak link" operation would still succeed if the -loader can cope with duplicate symbols. In this case, the link is silently -promoted to a proper linking. +NOTE: The test step of the demo can fail for a number of reasons. Their +abbreviations and meanings are: `RTE` for "runtime error", `DSYM` for "duplicate +symbols", and `DLLF` for "dynamic library load failure". ###### Linux (GCC) |CASE|CONFIG |BUILD |TEST |NOTES| |----|------------------|------------------|------------------|----:| -|s00 |:white_check_mark:|:white_check_mark:|:x: | 1,2| -|s01 |:white_check_mark:|:white_check_mark:|:x: | 1,2| -|s10 |:white_check_mark:|:white_check_mark:|:x: | 1,2| -|s11 |:white_check_mark:|:white_check_mark:|:x: | 1,2| -|d00 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 1| -|d01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 1| -|d10 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 1| -|d11 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 1,6| +|s00 |:white_check_mark:|:white_check_mark:|:x:RTE | 1,2| +|s01 |:white_check_mark:|:white_check_mark:|:x:DSYM | 3| +|s10 |:white_check_mark:|:white_check_mark:|:x:RTE | 2| +|s11 |:white_check_mark:|:white_check_mark:|:x:DSYM | 4| +|d00 |:white_check_mark:|:white_check_mark:|:x:RTE | 2| +|d01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 5| +|d10 |:white_check_mark:|:white_check_mark:|:x:RTE | 2| +|d11 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 7| ###### OSX (XCODE) |CASE|CONFIG |BUILD |TEST |NOTES| |----|------------------|------------------|------------------|-----| -|s00 |:white_check_mark:|:white_check_mark:|:x: | 3| -|s01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 4| -|s10 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 5| -|s11 |:white_check_mark:|:white_check_mark:|:x: | 2| -|d00 |:white_check_mark:|:white_check_mark:|:x: | 3| -|d01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 4| -|d10 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 5| -|d11 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 6| +|s00 |:white_check_mark:|:white_check_mark:|:x:RTE | 1| +|s01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 5| +|s10 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 6| +|s11 |:white_check_mark:|:white_check_mark:|:x:DSYM | 4| +|d00 |:white_check_mark:|:white_check_mark:|:x:RTE | 1| +|d01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 5| +|d10 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 6| +|d11 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 7| ###### NOTES - 1. On Linux, the only thing that matters is whether the library in question is - shared or static. The dynamic loader doesn't seem to have any problems with - multiple identical symbols. + 1. Test fails due to unresolved symbols. - 1. Test case fails unique symbols test (program output looks like - `0 0 1 1 2 2 ...` instead of `0 1 2 ...`). + 1. On Linux, all symbols in a binary's namespace must be resolved when the + namespace is instantiated. Because of this requirement, leaving symbols + unresolved in an executable is almost never useful, since the missing + symbols must be provided before the executable is even ran (e.g.: using + `LD_PRELOAD`). - 1. Test fails due to unresolved symbols. + 1. In the actual system test, the produced binary fails due to unresolved + symbols in the module's namespace. The result is reported as symbol + duplication because the check falls back to double-linking in an attempt to + produce a working executable (with the same result as the `s11` case). + + 1. Test case fails unique symbols test (program output looks like + `0 0 1 1 2 2 ...` instead of `0 1 2 ...`). 1. Module pulls symbols from the executable (ala Python extension modules). 1. Executable pulls symbols from the module (unusual, but it works). - 1. Duplicate symbols are successfully resolved and unified at load time. + 1. Duplicate symbols are successfully resolved and coalesced at load time. diff --git a/enumerateBuild.bash b/enumerateBuild.bash index a76c395..fe39d31 100755 --- a/enumerateBuild.bash +++ b/enumerateBuild.bash @@ -36,10 +36,10 @@ fi | while read testcase ; do &> "../../log/$testcase/configure.txt" configure_result="$?" - make &> "../../log/$testcase/build.txt" + make VERBOSE=1 &> "../../log/$testcase/build.txt" build_result="$?" - make test &> "../../log/$testcase/test.txt" + ./main &> "../../log/$testcase/test.txt" test_result="$?" code=32 @@ -64,7 +64,14 @@ fi | while read testcase ; do result=pass if [ "$test_result" '!=' '0' ] ; then code=31 - result=fail + + result="RTE" + + if [ "$test_result" '=' '250' ] ; then # wrong answer + result="DSYM" + elif [ "$test_result" '=' '251' ] ; then # dl load error + result="DLLF" + fi fi test_result="\\e[1;${code}m${result}\\e[0m" test_result="$( echo -e "$test_result" )" @@ -72,5 +79,5 @@ fi | while read testcase ; do echo "$testcase $configure_result $build_result $test_result" popd &> /dev/null -done +done 2> /dev/null diff --git a/main.c b/main.c index 1869dfa..57d9dab 100644 --- a/main.c +++ b/main.c @@ -3,6 +3,8 @@ #include #include +#include + int my_count() { int result = get_number(); set_number(result + 1); @@ -15,7 +17,7 @@ int main(int argc, char **argv) { int i, n; int result; - counter_module = dlopen("./counter.so", RTLD_LAZY); + counter_module = dlopen("./counter.so", RTLD_LAZY | RTLD_GLOBAL); if(!counter_module) goto error; count = dlsym(counter_module, "count"); @@ -24,14 +26,14 @@ int main(int argc, char **argv) { result = 0; for(i=0; i<10; ++i) { n = ((i%2) ? count : my_count)(); - result = (result || n != i) ? 1 : 0; + result = (result || n != i) ? 250 : 0; printf("%d\n", n); } goto done; error: fprintf(stderr, "Error occured:\n %s\n", dlerror()); - result = 1; + result = 251; done: if(counter_module) dlclose(counter_module); diff --git a/targetLinkLibrariesWithDynamicLookup.cmake b/targetLinkLibrariesWithDynamicLookup.cmake new file mode 100644 index 0000000..c631a21 --- /dev/null +++ b/targetLinkLibrariesWithDynamicLookup.cmake @@ -0,0 +1,467 @@ +# +# - This module provides the function +# target_link_libraries_with_dynamic_lookup which can be used to +# "weakly" link a loadable module. +# +# Link a library to a target such that the symbols are resolved at +# run-time not link-time. This should be used when compiling a +# loadable module when the symbols should be resolve from the run-time +# environment where the module is loaded, and not a specific system +# library. +# +# Specifically, for OSX it uses undefined dynamic_lookup. This is +# similar to using "-shared" on Linux where undefined symbols are +# ignored. +# +# Additionally, the linker is checked to see if it supports undefined +# symbols when linking a shared library. If it does then the library +# is not linked when specified with this function. +# +# http://blog.tim-smith.us/2015/09/python-extension-modules-os-x/ +# +# +# The following functions are defined: +# +# _get_target_type( ) +# +# **INTERNAL** Shorthand for querying an abbreviated version of the target type +# of the given ````. ```` is set to "STATIC" for a +# STATIC_LIBRARY, "SHARED" for a SHARED_LIBRARY, "MODULE" for a MODULE_LIBRARY, +# and "EXE" for an EXECUTABLE. +# +# Defined variables: +# +# ```` +# The abbreviated version of the ````'s type. +# +# +# _test_weak_link_project( +# +# +# ) +# +# **INTERNAL** Attempt to compile and run a test project where a target of type +# ```` is weakly-linked against a dependency of type ````. +# ```` can be one of "STATIC", "SHARED", "MODULE", or "EXE". +# ```` can be one of "STATIC", "SHARED", or "MODULE". +# +# Defined variables: +# +# ```` +# Whether the current C toolchain can produce a working target binary of type +# ```` that is weakly-linked against a dependency target of type +# ````. +# +# ```` +# List of flags to add to the linker command to produce a working target +# binary of type ```` that is weakly-linked against a dependency +# target of type ````. +# +# +# check_dynamic_lookup( +# +# +# ) +# +# Check if the linker requires a command line flag to allow leaving symbols +# unresolved when producing a target of type ```` that is +# weakly-linked against a dependency of type ````. ```` +# can be one of "STATIC", "SHARED", "MODULE", or "EXE". ```` can be +# one of "STATIC", "SHARED", or "MODULE". The result is cached between +# invocations and recomputed only when the value of CMake's linker flag list +# changes; ``CMAKE_STATIC_LINKER_FLAGS`` if ```` is "STATIC", and +# ``CMAKE_SHARED_LINKER_FLAGS`` otherwise. +# +# +# Defined variables: +# +# ```` +# Whether the current C toolchain supports weak-linking for target binaries of +# type ```` that are weakly-linked against a dependency target of +# type ````. +# +# ```` +# List of flags to add to the linker command to produce a working target +# binary of type ```` that is weakly-linked against a dependency +# target of type ````. +# +# ``HAS_DYNAMIC_LOOKUP__`` +# Cached, global alias for ```` +# +# ``DYNAMIC_LOOKUP_FLAGS__`` +# Cached, global alias for ```` +# +# +# target_link_libraries_with_dynamic_lookup( []) +# +# Like proper linking, except that the given ```` are not necessarily +# linked. Instead, the ```` is produced in a manner that allows for +# symbols unresolved within it to be resolved at runtime, presumably by the +# given ````. If such a target can be produced, the provided +# ```` are not actually linked. On platforms that do not support +# weak-linking, this function works just like ``target_link_libraries``. + +function(_get_target_type result_var target) + get_property(target_type TARGET ${target} PROPERTY TYPE) + + set(result "STATIC") + + if(target_type STREQUAL "STATIC_LIBRARY") + set(result "STATIC") + endif() + + if(target_type STREQUAL "SHARED_LIBRARY") + set(result "SHARED") + endif() + + if(target_type STREQUAL "MODULE_LIBRARY") + set(result "MODULE") + endif() + + if(target_type STREQUAL "EXECUTABLE") + set(result "EXE") + endif() + + set(${result_var} ${result} PARENT_SCOPE) +endfunction() + + +function(_test_weak_link_project + target_type + lib_type + can_weak_link_var + project_name) + + set(gnu_ld_ignore "-Wl,--unresolved-symbols=ignore-all") + set(osx_dynamic_lookup "-undefined dynamic_lookup") + set(no_flag "") + + foreach(link_flag_spec gnu_ld_ignore osx_dynamic_lookup no_flag) + set(link_flag "${${link_flag_spec}}") + + set(test_project_dir "${PROJECT_BINARY_DIR}/CMakeTmp") + set(test_project_dir "${test_project_dir}/${project_name}") + set(test_project_dir "${test_project_dir}/${link_flag_spec}") + set(test_project_dir "${test_project_dir}/${target_type}") + set(test_project_dir "${test_project_dir}/${lib_type}") + + set(test_project_src_dir "${test_project_dir}/src") + set(test_project_bin_dir "${test_project_dir}/build") + + file(MAKE_DIRECTORY ${test_project_src_dir}) + file(MAKE_DIRECTORY ${test_project_bin_dir}) + + set(mod_type "STATIC") + set(link_mod_lib TRUE) + set(link_exe_lib TRUE) + set(link_exe_mod FALSE) + + if("${target_type}" STREQUAL "EXE") + set(link_exe_lib FALSE) + set(link_exe_mod TRUE) + else() + set(mod_type "${target_type}") + endif() + + if("${mod_type}" STREQUAL "MODULE") + set(link_mod_lib FALSE) + endif() + + + file(WRITE "${test_project_src_dir}/CMakeLists.txt" " + cmake_minimum_required(VERSION ${CMAKE_VERSION}) + project(${project_name} C) + + include_directories(${test_project_src_dir}) + + add_library(number ${lib_type} number.c) + add_library(counter ${mod_type} counter.c) + ") + + if("${mod_type}" STREQUAL "MODULE") + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + set_target_properties(counter PROPERTIES PREFIX \"\") + ") + endif() + + if(link_mod_lib) + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + target_link_libraries(counter number) + ") + elseif(NOT link_flag STREQUAL "") + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + set_target_properties(counter PROPERTIES LINK_FLAGS \"${link_flag}\") + ") + endif() + + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + add_executable(main main.c) + ") + + if(link_exe_lib) + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + target_link_libraries(main number) + ") + elseif(NOT link_flag STREQUAL "") + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + target_link_libraries(main \"${link_flag}\") + ") + endif() + + if(link_exe_mod) + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + target_link_libraries(main counter) + ") + else() + file(APPEND "${test_project_src_dir}/CMakeLists.txt" " + target_link_libraries(main \"${CMAKE_DL_LIBS}\") + ") + endif() + + file(WRITE "${test_project_src_dir}/number.c" " + #include + + static int _number; + void set_number(int number) { _number = number; } + int get_number() { return _number; } + ") + + file(WRITE "${test_project_src_dir}/number.h" " + #ifndef _NUMBER_H + #define _NUMBER_H + extern void set_number(int); + extern int get_number(void); + #endif + ") + + file(WRITE "${test_project_src_dir}/counter.c" " + #include + int count() { + int result = get_number(); + set_number(result + 1); + return result; + } + ") + + file(WRITE "${test_project_src_dir}/counter.h" " + #ifndef _COUNTER_H + #define _COUNTER_H + extern int count(void); + #endif + ") + + file(WRITE "${test_project_src_dir}/main.c" " + #include + #include + #include + ") + + if(NOT link_exe_mod) + file(APPEND "${test_project_src_dir}/main.c" " + #include + ") + endif() + + file(APPEND "${test_project_src_dir}/main.c" " + int my_count() { + int result = get_number(); + set_number(result + 1); + return result; + } + + int main(int argc, char **argv) { + int result; + ") + + if(NOT link_exe_mod) + file(APPEND "${test_project_src_dir}/main.c" " + void *counter_module; + int (*count)(void); + + counter_module = dlopen(\"./counter.so\", RTLD_LAZY | RTLD_GLOBAL); + if(!counter_module) goto error; + + count = dlsym(counter_module, \"count\"); + if(!count) goto error; + ") + endif() + + file(APPEND "${test_project_src_dir}/main.c" " + result = count() != 0 ? 1 : + my_count() != 1 ? 1 : + my_count() != 2 ? 1 : + count() != 3 ? 1 : + count() != 4 ? 1 : + count() != 5 ? 1 : + my_count() != 6 ? 1 : 0; + ") + + if(NOT link_exe_mod) + file(APPEND "${test_project_src_dir}/main.c" " + goto done; + error: + fprintf(stderr, \"Error occured:\\n %s\\n\", dlerror()); + result = 1; + + done: + if(counter_module) dlclose(counter_module); + ") + endif() + + file(APPEND "${test_project_src_dir}/main.c" " + return result; + } + ") + + set(_rpath_arg) + if(APPLE AND ${CMAKE_VERSION} VERSION_GREATER 2.8.11) + set(_rpath_arg "-DCMAKE_MACOSX_RPATH='${CMAKE_MACOSX_RPATH}'") + endif() + + try_compile(project_compiles + "${test_project_bin_dir}" + "${test_project_src_dir}" + "${project_name}" + CMAKE_FLAGS + "-DCMAKE_SHARED_LINKER_FLAGS='${CMAKE_SHARED_LINKER_FLAGS}'" + ${_rpath_arg} + OUTPUT_VARIABLE compile_output) + + set(project_works 1) + set(run_output) + + if(project_compiles) + execute_process(COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} + "${test_project_bin_dir}/main" + WORKING_DIRECTORY "${test_project_bin_dir}" + RESULT_VARIABLE project_works + OUTPUT_VARIABLE run_output + ERROR_VARIABLE run_output) + endif() + + set(test_description + "Weak Link ${target_type} -> ${lib_type} (${link_flag_spec})") + + if(project_works EQUAL 0) + set(project_works TRUE) + message(STATUS "Performing Test ${test_description} - Success") + else() + set(project_works FALSE) + message(STATUS "Performing Test ${test_description} - Failed") + file(APPEND ${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/CMakeError.log + "Performing Test ${test_description} failed with the " + "following output:\n" + "BUILD\n-----\n${compile_output}\nRUN\n---\n${run_output}\n") + endif() + + set(${can_weak_link_var} ${project_works} PARENT_SCOPE) + if(project_works) + set(${project_name} ${link_flag} PARENT_SCOPE) + break() + endif() + endforeach() +endfunction() + + +function(check_dynamic_lookup + target_type + lib_type + has_dynamic_lookup_var + link_flags_var) + + # hash the CMAKE_FLAGS passed and check cache to know if we need to rerun + if("${target_type}" STREQUAL "STATIC") + string(MD5 cmake_flags_hash "${CMAKE_STATIC_LINKER_FLAGS}") + else() + string(MD5 cmake_flags_hash "${CMAKE_SHARED_LINKER_FLAGS}") + endif() + + set(cache_var "HAS_DYNAMIC_LOOKUP_${target_type}_${lib_type}") + set(cache_hash_var "HAS_DYNAMIC_LOOKUP_${target_type}_${lib_type}_hash") + set(result_var "DYNAMIC_LOOKUP_FLAGS_${target_type}_${lib_type}") + + if( NOT DEFINED ${cache_hash_var} + OR NOT "${${cache_hash_var}}" STREQUAL "${cmake_flags_hash}") + unset(${cache_var} CACHE) + endif() + + if(NOT DEFINED ${cache_var}) + if(NOT CMAKE_CROSSCOMPILING OR CMAKE_CROSSCOMPILING_EMULATOR) + _test_weak_link_project(${target_type} + ${lib_type} + has_dynamic_lookup + link_flags) + else() + set(has_dynamic_lookup FALSE) + set(link_flags) + endif() + + set(caveat " (when linking ${target_type} against ${lib_type})") + + set(${cache_var} "${has_dynamic_lookup}" + CACHE BOOL + "linker supports dynamic lookup for undefined symbols${caveat}") + + set(${result_var} "${link_flags}" + CACHE BOOL + "linker flags for dynamic lookup${caveat}") + + set(${cache_hash_var} "${cmake_flags_hash}" + CACHE INTERNAL "hashed flags for ${cache_var} check") + endif() + + set(${has_dynamic_lookup_var} "${${cache_var}}" PARENT_SCOPE) + set(${link_flags_var} "${${result_var}}" PARENT_SCOPE) +endfunction() + + +function(target_link_libraries_with_dynamic_lookup target) + _get_target_type(target_type ${target}) + + set(link_props) + set(link_items) + set(link_libs) + + foreach(lib ${ARGN}) + _get_target_type(lib_type ${lib}) + check_dynamic_lookup(${target_type} + ${lib_type} + has_dynamic_lookup + dynamic_lookup_flags) + + if(has_dynamic_lookup) + if(dynamic_lookup_flags) + if("${target_type}" STREQUAL "EXE") + list(APPEND link_items "${dynamic_lookup_flags}") + else() + list(APPEND link_props "${dynamic_lookup_flags}") + endif() + endif() + else() + list(APPEND link_libs "${lib}") + endif() + endforeach() + + if(link_props) + list(REMOVE_DUPLICATES link_props) + endif() + + if(link_items) + list(REMOVE_DUPLICATES link_items) + endif() + + if(link_libs) + list(REMOVE_DUPLICATES link_libs) + endif() + + if(link_props) + set_target_properties(${target} + PROPERTIES LINK_FLAGS "${link_props}") + endif() + + set(links "${link_items}" "${link_libs}") + if(links) + target_link_libraries(${target} "${links}") + endif() +endfunction() + From 094dbe0f74f23420f95b83d162cd4c16022142c9 Mon Sep 17 00:00:00 2001 From: Omar Padron Date: Fri, 8 Jul 2016 16:40:23 -0500 Subject: [PATCH 2/4] simplify test project code --- targetLinkLibrariesWithDynamicLookup.cmake | 42 ++++++---------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/targetLinkLibrariesWithDynamicLookup.cmake b/targetLinkLibrariesWithDynamicLookup.cmake index c631a21..5888409 100644 --- a/targetLinkLibrariesWithDynamicLookup.cmake +++ b/targetLinkLibrariesWithDynamicLookup.cmake @@ -219,55 +219,33 @@ function(_test_weak_link_project endif() file(WRITE "${test_project_src_dir}/number.c" " - #include - static int _number; - void set_number(int number) { _number = number; } - int get_number() { return _number; } - ") - - file(WRITE "${test_project_src_dir}/number.h" " - #ifndef _NUMBER_H - #define _NUMBER_H - extern void set_number(int); - extern int get_number(void); - #endif + int next_number() { return _number++; } ") file(WRITE "${test_project_src_dir}/counter.c" " - #include - int count() { - int result = get_number(); - set_number(result + 1); - return result; - } - ") - - file(WRITE "${test_project_src_dir}/counter.h" " - #ifndef _COUNTER_H - #define _COUNTER_H - extern int count(void); - #endif + extern int next_number(void); + int count() { return next_number(); } ") file(WRITE "${test_project_src_dir}/main.c" " #include #include - #include ") - if(NOT link_exe_mod) + if(link_exe_mod) + file(APPEND "${test_project_src_dir}/main.c" " + extern int count(void); + ") + else() file(APPEND "${test_project_src_dir}/main.c" " #include ") endif() file(APPEND "${test_project_src_dir}/main.c" " - int my_count() { - int result = get_number(); - set_number(result + 1); - return result; - } + extern int next_number(void); + int my_count() { return next_number(); } int main(int argc, char **argv) { int result; From 616d47a5e88dff6a63b2c7e78a34c9105d1092db Mon Sep 17 00:00:00 2001 From: Omar Padron Date: Fri, 8 Jul 2016 17:21:43 -0500 Subject: [PATCH 3/4] minor fix --- targetLinkLibrariesWithDynamicLookup.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/targetLinkLibrariesWithDynamicLookup.cmake b/targetLinkLibrariesWithDynamicLookup.cmake index 5888409..3f696d9 100644 --- a/targetLinkLibrariesWithDynamicLookup.cmake +++ b/targetLinkLibrariesWithDynamicLookup.cmake @@ -165,6 +165,8 @@ function(_test_weak_link_project if("${mod_type}" STREQUAL "MODULE") set(link_mod_lib FALSE) + else() + set(link_exe_mod TRUE) endif() From 6af8a057b2ae3e1e3014aecbe96d1224e7ecc4e8 Mon Sep 17 00:00:00 2001 From: Omar Padron Date: Fri, 8 Jul 2016 17:25:29 -0500 Subject: [PATCH 4/4] add platform/toolchain combinations tested --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index a9adf33..47d9fce 100644 --- a/README.md +++ b/README.md @@ -109,3 +109,8 @@ symbols", and `DLLF` for "dynamic library load failure". 1. Duplicate symbols are successfully resolved and coalesced at load time. +Tested on: + + - Arch Linux GCC 6.1.1 + - Mac OSX 10.11.4 LLVM 7.3.0 +