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?
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:
Extract all needed target's properties to the variables and pass them to the newly generated project. As you already do.
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...