1if(doctest_force_link_static_lib_in_target_included)
2    return()
3endif()
4set(doctest_force_link_static_lib_in_target_included true)
5
6cmake_minimum_required(VERSION 3.0)
7
8# includes the file to the source with compiler flags
9function(doctest_include_file_in_sources header sources)
10    foreach(src ${sources})
11        if(${src} MATCHES \\.\(cc|cp|cpp|CPP|c\\+\\+|cxx\)$)
12            # get old flags
13            get_source_file_property(old_compile_flags ${src} COMPILE_FLAGS)
14            if(old_compile_flags STREQUAL "NOTFOUND")
15                set(old_compile_flags "")
16            endif()
17
18            # update flags
19            if(MSVC)
20                set_source_files_properties(${src} PROPERTIES COMPILE_FLAGS
21                    "${old_compile_flags} /FI\"${header}\"")
22            else()
23                set_source_files_properties(${src} PROPERTIES COMPILE_FLAGS
24                    "${old_compile_flags} -include \"${header}\"")
25            endif()
26        endif()
27    endforeach()
28endfunction()
29
30# this is the magic function - forces every object file from the library to be linked into the target (dll or executable)
31# it doesn't work in 2 scenarios:
32# - either the target or the library uses a precompiled header - see the end of this issue for details: https://github.com/onqtam/doctest/issues/21
33# - either the target or the library is an imported target (pre-built) and not built within the current cmake tree
34# Alternatives:
35# - use CMake object libraries instead of static libraries - >> THIS IS ACTUALLY PREFERRED << to all this CMake trickery
36# - checkout these 2 repositories:
37#   - https://github.com/pthom/cmake_registertest
38#   - https://github.com/pthom/doctest_registerlibrary
39function(doctest_force_link_static_lib_in_target target lib)
40    # check if the library has generated dummy headers
41    get_target_property(DDH ${lib} DOCTEST_DUMMY_HEADER)
42    get_target_property(LIB_NAME ${lib} NAME)
43    if(${DDH} STREQUAL "DDH-NOTFOUND")
44        # figure out the paths and names of the dummy headers - should be in the build folder for the target
45        set(BD ${CMAKE_CURRENT_BINARY_DIR})
46        if(NOT CMAKE_VERSION VERSION_LESS 3.4)
47            get_target_property(BD ${lib} BINARY_DIR) # 'BINARY_DIR' target property unsupported before CMake 3.4 ...
48        endif()
49        set(dummy_dir ${BD}/${LIB_NAME}_DOCTEST_STATIC_LIB_FORCE_LINK_DUMMIES/)
50        set(dummy_header ${dummy_dir}/all_dummies.h)
51        file(MAKE_DIRECTORY ${dummy_dir})
52
53        # create a dummy header for each source file, include a dummy function in it and include it in the source file
54        set(curr_dummy "0")
55        set(DLL_PRIVATE "#ifndef _WIN32\n#define DLL_PRIVATE __attribute__ ((visibility (\"hidden\")))\n#else\n#define DLL_PRIVATE\n#endif\n\n")
56        get_target_property(lib_sources ${lib} SOURCES)
57        foreach(src ${lib_sources})
58            if(${src} MATCHES \\.\(cc|cp|cpp|CPP|c\\+\\+|cxx\)$)
59                math(EXPR curr_dummy "${curr_dummy} + 1")
60
61                set(curr_dummy_header ${dummy_dir}/dummy_${curr_dummy}.h)
62                file(WRITE ${curr_dummy_header} "${DLL_PRIVATE}namespace doctest { namespace detail { DLL_PRIVATE int dummy_for_${LIB_NAME}_${curr_dummy}(); DLL_PRIVATE int dummy_for_${LIB_NAME}_${curr_dummy}() { return ${curr_dummy}; } } }\n")
63                doctest_include_file_in_sources(${curr_dummy_header} ${src})
64            endif()
65        endforeach()
66        set(total_dummies ${curr_dummy})
67
68        # create the master dummy header
69        file(WRITE ${dummy_header} "${DLL_PRIVATE}namespace doctest { namespace detail {\n\n")
70
71        # forward declare the dummy functions in the master dummy header
72        foreach(curr_dummy RANGE 1 ${total_dummies})
73            file(APPEND ${dummy_header} "DLL_PRIVATE int dummy_for_${LIB_NAME}_${curr_dummy}();\n")
74        endforeach()
75
76        # call the dummy functions in the master dummy header
77        file(APPEND ${dummy_header} "\nDLL_PRIVATE int dummies_for_${LIB_NAME}();\nDLL_PRIVATE int dummies_for_${LIB_NAME}() {\n    int res = 0;\n")
78        foreach(curr_dummy RANGE 1 ${total_dummies})
79            file(APPEND ${dummy_header} "    res += dummy_for_${LIB_NAME}_${curr_dummy}();\n")
80        endforeach()
81        file(APPEND ${dummy_header} "    return res;\n}\n\n} } // namespaces\n")
82
83        # set the dummy header property so we don't recreate the dummy headers the next time this macro is called for this library
84        set_target_properties(${lib} PROPERTIES DOCTEST_DUMMY_HEADER ${dummy_header})
85        set(DDH ${dummy_header})
86    endif()
87
88    get_target_property(DFLLTD ${target} DOCTEST_FORCE_LINKED_LIBRARIES_THROUGH_DUMMIES)
89    get_target_property(target_sources ${target} SOURCES)
90
91    if("${DFLLTD}" STREQUAL "DFLLTD-NOTFOUND")
92        # if no library has been force linked to this target
93        foreach(src ${target_sources})
94            if(${src} MATCHES \\.\(cc|cp|cpp|CPP|c\\+\\+|cxx\)$)
95                doctest_include_file_in_sources(${DDH} ${src})
96                break()
97            endif()
98        endforeach()
99
100        # add the library as force linked to this target
101        set_target_properties(${target} PROPERTIES DOCTEST_FORCE_LINKED_LIBRARIES_THROUGH_DUMMIES ${LIB_NAME})
102    else()
103        # if this particular library hasn't been force linked to this target
104        list(FIND DFLLTD ${LIB_NAME} lib_forced_in_target)
105        if(${lib_forced_in_target} EQUAL -1)
106            foreach(src ${target_sources})
107                if(${src} MATCHES \\.\(cc|cp|cpp|CPP|c\\+\\+|cxx\)$)
108                    doctest_include_file_in_sources(${DDH} ${src})
109                    break()
110                endif()
111            endforeach()
112
113            # add this library to the list of force linked libraries for this target
114            list(APPEND DFLLTD ${LIB_NAME})
115            set_target_properties(${target} PROPERTIES DOCTEST_FORCE_LINKED_LIBRARIES_THROUGH_DUMMIES "${DFLLTD}")
116        else()
117            message(AUTHOR_WARNING "LIBRARY \"${lib}\" ALREADY FORCE-LINKED TO TARGET \"${target}\"!")
118        endif()
119    endif()
120endfunction()
121
122# a utility function to create an executable for a static library with tests - as requested by https://github.com/pthom
123function(doctest_make_exe_for_static_lib exe_name lib_name)
124    set(exe_dir ${CMAKE_CURRENT_BINARY_DIR}/${exe_name}_generated_sources)
125    file(MAKE_DIRECTORY ${exe_dir})
126    file(WRITE ${exe_dir}/main.cpp "#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN\n#include \"doctest.h\"\n")
127    add_executable(${exe_name} ${exe_dir}/main.cpp)
128    target_link_libraries(${exe_name} ${lib_name})
129    doctest_force_link_static_lib_in_target(${exe_name} ${lib_name})
130    add_test(NAME ${exe_name} COMMAND $<TARGET_FILE:${exe_name}>)
131endfunction()
132