So we have mixed C++/C projects. I want to enforce that, even if all source files are .cpp (C++ compiled) that source files with the "*.h" extension are C compileable (using *.hpp for C++ only files). I'd like to enforce it at compile time, such that the build returns an error (associated with the project, with full line numbers and C error and warnings). Running at configure time is (imho) achieves limited results.
So I currently have a script that will do that:
cmake_minimum_required (VERSION 2.8)
MACRO(MAKE_C_COMPILE_TESTER project_name target_sources cvar)
SET(CMAKE_CONFIGURABLE_FILE_CONTENT "")
FOREACH(src ${target_sources})
MESSAGE(STATUS "TESTING src ${src}")
GET_FILENAME_COMPONENT(SRC_EXT "${src}" EXT)
MESSAGE(STATUS "SRC_EXT=${SRC_EXT}")
if("${SRC_EXT}" STREQUAL ".h")
set(CMAKE_CONFIGURABLE_FILE_CONTENT "${CMAKE_CONFIGURABLE_FILE_CONTENT}\n#include \"${src}\"")
endif()
ENDFOREACH()
set(${cvar} "${${project_name}_BINARY_DIR}/${project_name}_CCompileTest.c")
configure_file("${CMAKE_ROOT}/Modules/CMakeConfigurableFile.in"
"${${cvar}}"
@ONLY)
set_source_files_properties("${cvar}" PROPERTIES GENERATED TRUE)
ENDMACRO()
USAGE:
cmake_minimum_required (VERSION 2.8)
INCLUDE(MAKE_C_COMPILE_TESTER.cmake)
project(playlib)
add_definitions(-DPLAY_LIB_EXPORTS)
SET(${PROJECT_NAME}_SRC_LIST play_lib.h play_lib.hpp play_lib.cpp test.c testlib.h)
MAKE_C_COMPILE_TESTER(${PROJECT_NAME} "${${PROJECT_NAME}_SRC_LIST}" ${PROJECT_NAME}_CTESTER)
add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SRC_LIST} ${${PROJECT_NAME}_CTESTER})
This works, but is a little more invasive than I want. It requires multiple lines, and if the SRC_LIST isn't a single variable than you may have more work than you want. In addition, it adds the .c file which could be confusing to people.
What I'd prefer is that I build ${project_name}_CCompileTest.c as an intermediate: it is not included as a source file in the directory, and can be added as a single line to existing files. Something like:
SET(${PROJECT_NAME}_SRC_LIST play_lib.h play_lib.hpp play_lib.cpp test.c testlib.h)
add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SRC_LIST})
MAKE_C_COMPILE_TESTER(${PROJECT_NAME})
This way it can be quickly added to all existing projects as well as future projects. I know I can extract the source list using GET_TARGET_PROPERTIES(var ${project_name} SOURCES)
, and I think is can do it with ADD_CUSTOM_COMMAND(TARGET ${project_name})...but I don't know how to do it in a cross-platform, dependency-efficient way. Any ideas how to compile a C file to a .o
or .i
without linking or including the source file in the project?
Edit: possible solution path that I haven't figured out completely yet (so obviously, the following code does not work):
SET(MYCOMPILE "${CMAKE_C_COMPILE_OBJECT}")
STRING(REGEX REPLACE "<CMAKE_C_COMPILER>" "\"\${CMAKE_C_COMPILER}\"" MYCOMPILE "${MYCOMPILE}")
#STRING(REGEX REPLACE "<FLAGS>" "\${FLAGS}" MYCOMPILE "${MYCOMPILE}")
STRING(REGEX REPLACE ">" "}" MYCOMPILE "${MYCOMPILE}")
STRING(REGEX REPLACE "<" "\${" MYCOMPILE "${MYCOMPILE}")
SET(MYCOMPILE "MACRO(ADD_COMPILE_FILE_COMMAND SOURCE)
GET_FILENAME_COMPONENT(SOURCE_BASE_NAME \"${SOURCE}\" NAME_WE)
SET(FLAGS
\"\$<\$<CONFIG:None>:\${CMAKE_C_FLAGS}>\$<\$<CONFIG:Debug>:\${CMAKE_C_FLAGS_DEBUG}>\$<\$<CONFIG:Release>:\${CMAKE_C_FLAGS_RELEASE}>\$<\$<CONFIG:RelWithDebInfo>:\${CMAKE_C_FLAGS_RELWITHDEBINFO}>\$<\$<CONFIG:MinSizeRel>:\${CMAKE_C_FLAGS_MINSIZEREL}>\"
)
SET(OBJECT \"${CMAKE_CURRENT_BINARY_DIR}/${SOURCE_BASE_NAME}.o\")
GET_PROPERTY(${PROJECT_NAME}_COMPILE_DEFINITIONS_NONE DIRECTORY \${CMAKE_CURRENT_SOURCE_DIR} PROPERTY COMPILE_DEFINITIONS)
GET_PROPERTY(${PROJECT_NAME}_COMPILE_DEFINITIONS_DEBUG DIRECTORY \${CMAKE_CURRENT_SOURCE_DIR} PROPERTY COMPILE_DEFINITIONS_DEBUG)
GET_PROPERTY(${PROJECT_NAME}_COMPILE_DEFINITIONS_RELEASE DIRECTORY \${CMAKE_CURRENT_SOURCE_DIR} PROPERTY COMPILE_DEFINITIONS_RELEASE)
GET_PROPERTY(${PROJECT_NAME}_COMPILE_DEFINITIONS_RELWITHDEBINFO DIRECTORY \${CMAKE_CURRENT_SOURCE_DIR} PROPERTY COMPILE_DEFINITIONS_RELWITHDEBINFO)
GET_PROPERTY(${PROJECT_NAME}_COMPILE_DEFINITIONS_MINSIZEREL DIRECTORY \${CMAKE_CURRENT_SOURCE_DIR} PROPERTY COMPILE_DEFINITIONS_MINSIZEREL)
SET (DEFINES
\"\$<\$<CONFIG:None>:\${PROJECT_NAME}_COMPILE_DEFINITIONS_NONE>\$<\$<CONFIG:Debug>:\${PROJECT_NAME}_COMPILE_DEFINITIONS_DEBUG>\$<\$<CONFIG:Release>:\${PROJECT_NAME}_COMPILE_DEFINITIONS_RELEASE>\$<\$<CONFIG:RelWithDebInfo>:\${PROJECT_NAME}_COMPILE_DEFINITIONS_RELWITHDEBINFO>\$<\$<CONFIG:MinSizeRel>:\${PROJECT_NAME}_COMPILE_DEFINITIONS_MINSIZEREL>\")
ADD_CUSTOM_COMMAND(TARGET ${PROJECT_NAME}
COMMAND \"\${CMAKE_COMMAND}\" -E echo \"*******\${\${DEFINES}}\")
ADD_CUSTOM_COMMAND(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${MYCOMPILE}
)
ENDMACRO()
")
MESSAGE(STATUS "MYCOMPILE=${MYCOMPILE}")
MESSAGE(STATUS "CMAKE_C_COMPILER=${CMAKE_C_COMPILER}")
GET_PROPERTY(COMPILE_DEFINITIONS DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY COMPILE_DEFINITIONS)
MESSAGE(STATUS "COMPILE_DEFINITIONS=${COMPILE_DEFINITIONS}")
MESSAGE(STATUS "COMPILE_DEFINITIONS_DEBUG=${COMPILE_DEFINITIONS_DEBUG}")
GET_PROPERTY(COMPILE_DEFINITIONS_RELEASE DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY COMPILE_DEFINITIONS_RELEASE)
MESSAGE(STATUS "COMPILE_DEFINITIONS_RELEASE=${COMPILE_DEFINITIONS_RELEASE}")
MESSAGE(STATUS "COMPILE_DEFINITIONS_RELWITHDEBINFO=${COMPILE_DEFINITIONS_RELWITHDEBINFO}")
MESSAGE(STATUS "COMPILE_DEFINITIONS_MINSIZEREL =${COMPILE_DEFINITIONS_MINSIZEREL}")
set(CMAKE_CONFIGURABLE_FILE_CONTENT ${MYCOMPILE})
CONFIGURE_FILE("${CMAKE_ROOT}/Modules/CMakeConfigurableFile.in"
"${PROJECT_BINARY_DIR}/cbuild.cmake"
)
INCLUDE("${PROJECT_BINARY_DIR}/cbuild.cmake")
ADD_COMPILE_FILE_COMMAND("${${PROJECT_NAME}_CTESTER}")
Basically, the idea is to look up the C compile command and then actually use it. But it is a nightmare getting all the variables set correctly in a non-make style generator (I'm currently trying to get it to work with Visual Studio, though a cross platform solution is required).
I didn't really follow your question, but regarding:
Any ideas how to compile a C file to a .o or .i without linking
or including the source file in the project?
see OBJECT libraries.
http://www.cmake.org/cmake/help/v3.0/manual/cmake-buildsystem.7.html
I came up with the following macro:
MACRO(MAKE_C_COMPILE_TESTER LIBRARY)
# Get the sources LIBRARY consists of:
get_target_property(FILES ${LIBRARY} SOURCES)
SET(HEADERS_TO_USE "")
FOREACH(file ${FILES})
GET_FILENAME_COMPONENT(FILE_EXT "${file}" EXT)
if ("${FILE_EXT}" STREQUAL ".h")
LIST(APPEND HEADERS_TO_USE ${file})
endif()
ENDFOREACH()
SET(COMPILE_INPUT "")
FOREACH(header ${HEADERS_TO_USE})
MESSAGE(STATUS "FOUND C header ${header}")
SET(COMPILE_INPUT "${COMPILE_INPUT}\n #include \"${CMAKE_CURRENT_SOURCE_DIR}/${header}\"")
ENDFOREACH()
INCLUDE(CheckCSourceCompiles)
CHECK_C_SOURCE_COMPILES("#include<stdio.h>
${COMPILE_INPUT}
int main(int argc, char** argv)
{
printf(\"Hello World\");
return 0;
}" C_INCLUDE_CHECK)
IF (${C_INCLUDE_CHECK} MATCHES "1")
MESSAGE(STATUS "C_INCLUDE_CHECK: success.")
ELSE()
message(SEND_ERROR "C_INCLUDE_CHECK: FAIL, check log.")
ENDIF()
ENDMACRO(MAKE_C_COMPILE_TESTER)
I noticed that you add libraries. So I get the list of files for a target (like a library) and check that a C hello world which includes the .h files can compile using a C compiler.
The CMakeLists.txt where it is being used is:
cmake_minimum_required (VERSION 2.8)
INCLUDE(MAKE_C_COMPILE_TESTER.cmake)
project(playlib)
add_definitions(-DPLAY_LIB_EXPORTS)
include_directories(include)
# Works, only cpp/hpp c/h files.
#add_library(MIXEDTESTLIB SHARED include/lib1.h include/lib2.hpp src/lib1.c src/lib2.cpp)
# Will not work, cpp/h files, header which cannot compile using C compiler.
add_library(MIXEDTESTLIB SHARED include/lib1.h include/lib2.hpp include/lib3.h src/lib1.c src/lib2.cpp src/lib3.cpp)
MAKE_C_COMPILE_TESTER(MIXEDTESTLIB)
I tested this macro on Linux, but I assume that it will work on other platforms and with other generators. If not then at least you can be inspired. :)
An example project can be downloaded from:
https://dl.dropboxusercontent.com/u/68798379/cmake-c-cpp-header-check.tar.bz2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With