Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make custom intermediate (improve C compilable tester) in CMake

Tags:

c++

c

cmake

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).

like image 598
IdeaHat Avatar asked May 12 '14 13:05

IdeaHat


2 Answers

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

like image 93
steveire Avatar answered Nov 12 '22 06:11

steveire


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

like image 31
wojciii Avatar answered Nov 12 '22 05:11

wojciii