Search code examples
cmakecmake-modules

Using an interface library with check_c_source_runs() or try_run()


Use case: I'm trying to compile a test program that probes for a list of TrueType(tm) fonts using SDL2_ttf (with SDL2, Freetype, PNG and Zlib). The SDL2_ttf::SDL2_ttf interface library exists and links successfully with target executables. My problem is how to get check_c_source_runs() to pick up the definitions, include directories and libraries. I'd rather not have to manually extract everything from properties, as in the following code fragment:

include(CheckCSourceRuns)

get_property(defs TARGET SDL2_ttf::SDL2_ttf PROPERTY INTERFACE_COMPILE_DEFINITIONS)
get_property(incs TARGET SDL2_ttf::SDL2_ttf PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
get_property(libs TARGET SDL2_ttf::SDL2_ttf PROPERTY INTERFACE_LINK_LIBRARIES)

## Transform the definitions with "-D"
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.12")
    list(TRANSFORM defs PREPEND "-D")
    list(TRANSFORM incs PREPEND "-I")
else ()
    ## Code that does what list(TRANSFORM...) does in less capable CMake
    ## versions.
endif ()

set(CMAKE_REQUIRED_DEFINITIONS ${defs})
set(CMAKE_REQUIRED_INCLUDES    ${incs})
set(CMAKE_REQUIRED_LIBRARIES   ${libs})
check_c_source_runs("
#include <stdint.h>
#include <SDL.h>
#include <SDL_ttf.h>
int main(int argc, char *argv[])
{
    const char *fonts[] = {\"DejaVuSans.ttf\", \"LucidaSansRegular.ttf\", \"FreeSans.ttf\", \"AppleGothic.ttf\", \"tahoma.ttf\"};
    size_t i, cnt = 0;
    SDL_Init(SDL_INIT_VIDEO);
    TTF_Init();
    for (i = 0; i < sizeof(fonts)/sizeof(fonts[0]); ++i) {
        TTF_Font *ttf = TTF_OpenFont(fonts[i], 10);
        if (ttf != NULL) {
            fputs(fonts[i], stderr);
            if (cnt++ > 0) {
                fputc(';', stderr);
            }
            TTF_CloseFont(ttf);
        }
    }
    TTF_Quit();
    SDL_Quit();
    return 0;
}" ttfprobe_run)

Link libraries are hairy, since there are interface libraries referenced from within SDL2_ttf::SDL2_ttf, e.g. FreeType::FreeType.

Suggestions?


Solution

  • Functions try_compile and try_run and everything which is based on them (e.g. check_c_source_runs) are actually build some other CMake project. Because you cannot pass targets to the CMake project, you have two ways:

    1. Extract all needed target's properties to the variables and pass them to the newly generated project. As you already do.

    2. Write CMakeLists.txt for other project manually, and use calls to find_package and other package-discovery functions in it.

    E.g., you may write CMakeLists.txt for other project like that:

    # Source file is in SOURCE_FILE parameter,
    # resulted executable is copied into the file pointed by EXE_FILE parameter.
    cmake_minimum_required(...)
    project(test_project)
    
    # This will create 'SDL2_ttf::SDL2_ttf' target
    find_package(SDL2_ttf REQUIRED)
    
    add_executable(test_exe ${SOURCE_FILE})
    target_link_libraries(test_exe SDL2_ttf::SDL2_ttf)
    
    add_custom_command(OUTPUT ${EXE_FILE}
        COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:test_exe> ${EXE_FILE}
        DEPENDS $<TARGET_FILE:test_exe>
    )
    
    add_custom_target(copy_exe ALL DEPENDS ${EXE_FILE})
    

    The main challenge is to pass as many variables to the other project as needed for it to be built in the same "environment" as the main project.

    Example below handles only variables which could affect on find_package(SDL2_ttf) call:

    # Main project
    # Somewhere you have this call too.
    find_package(SDL2_ttf REQUIRED)
    
    # List of arguments for the subproject
    set(SUBPROJECT_ARGS
        # This affects on searching for possible `FindSDL2_ttf.cmake` script
        -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}
        # This affects on searching for `find_*` calls in find script.
        -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}
    )
    
    if (SDL2_ttf_DIR)
        # This is a directory with `SDL2_ttfConfig.cmake` script
        list(APPEND SUBPROJECT_ARGS -DSDL2_ttf_DIR=${SDL2_ttf_DIR})
    endif()
    
    # build subproject
    try_compile(TTF_TEST_RESULT # Variable which will contain result of building the subproject
     ${CMAKE_CURRENT_BINARY_DIR}/ttf_test # Build directory for the subproject
     <src-dir> # Source directory for the subproject, where its `CMakeLists.txt` resides.
     test_project # Project name of the subproject
     CMAKE_FLAGS
     -DSOURCE_FILE=<src-file> # Source file 
     -DEXE_FILE=<exe-file> # Path to the resulted executable file
     ${SUBPROJECT_ARGS} # The rest of arguments for subproject
     OUTPUT_VAR TTF_TEST_OUTPUT # Variable which will contain output of the build process
    )
    
    if (TTF_TEST_RESULT)
      # Subproject has been built successfully, now we can try to execute resulted file
      ...
    endif()
    

    Tricky? Yes. But this is how CMake works...