在CMake中制作自定义中间件(改进C可编译测试器)

时间:2014-05-12 13:26:51

标签: c++ c cmake

所以我们混合了C ++ / C项目。我想强制执行,即使所有源文件都是.cpp(C ++编译),源文件使用" *。h"扩展是C可编译的(对于仅C ++文件使用* .hpp)。我想在编译时强制执行它,这样构建就会返回一个错误(与项目相关联,包含完整的行号和C错误和警告)。在配置时运行(imho)可以获得有限的结果。

所以我目前有一个脚本可以做到这一点:

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

用法:

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

这样可行,但比我想要的更具侵略性。它需要多行,如果SRC_LIST不是一个单一的变量,那么你的工作量可能比你想要的多。另外,它添加了.c文件,这可能会让人感到困惑。

我更喜欢的是我将$ {project_name} _CCompileTest.c构建为中间件:它不作为源文件包含在目录中,可以作为单行添加到现有文件中。类似的东西:

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

通过这种方式,它可以快速添加到所有现有项目以及未来项目中。我知道我可以使用GET_TARGET_PROPERTIES(var ${project_name} SOURCES)提取源列表,我认为可以使用ADD_CUSTOM_COMMAND(TARGET $ {project_name})来完成...但我不知道如何在交叉中执行此操作平台,依赖效率的方式。任何想法如何编译C文件到.o.i而不链接或包含项目中的源文件?

编辑:我还没有完全理解的可能的解决方案路径(显然,以下代码不起作用):

  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}")

基本上,我们的想法是查找C编译命令,然后实际使用它。但是,在非make风格生成器中正确设置所有变量是一场噩梦(我目前正试图让它与Visual Studio一起工作,但需要跨平台解决方案)。

5 个答案:

答案 0 :(得分:4)

我没有真正关注你的问题,但关于:

Any ideas how to compile a C file to a .o or .i without linking 
or including the source file in the project?

参见OBJECT库。

http://www.cmake.org/cmake/help/v3.0/manual/cmake-buildsystem.7.html

答案 1 :(得分:2)

我提出了以下宏:

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)

我注意到你添加了库。所以我得到目标文件列表(比如库),并检查包含.h文件的C hello世界是否可以使用C编译器进行编译。

使用它的CMakeLists.txt是:

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)

我在Linux上测试了这个宏,但我认为它可以在其他平台和其他生成器上运行。如果没有,那么至少你可以受到启发。 :)

可以从以下网址下载示例项目:

https://dl.dropboxusercontent.com/u/68798379/cmake-c-cpp-header-check.tar.bz2

答案 2 :(得分:0)

通常* .h文件由它们自己编译,但包含在其他c / cpp文件中。当你包含一个文件时,它的所有内容都会被添加到原始文件中,因此它经历的编译将与文件的编译匹配,所以如果用c ++编译器编译* .cpp文件并且该文件包含* .h文件除了将编译更改为C编译之外,你没有太多事情要做。

答案 3 :(得分:0)

这是我提出的潜在解决方案。我们的想法是make_c_compile_tester现在是一个函数,它生成一个CMake文件,用于生成包含C头的C文件,并生成一个触发此文件处理的目标。

我从你想要的用法开始:

include(make_c_compile_tester.cmake)
project(playlib)

set(${PROJECT_NAME}_SRC_LIST play_lib.h play_lib.hpp play_lib.cpp test.c testlib.h dummy.h)
add_library(${PROJECT_NAME} SHARED ${${PROJECT_NAME}_SRC_LIST})
make_c_compile_tester(${PROJECT_NAME})

我移动了您提供的代码,该代码在CMake模板${project_name}_CCompileTest.c中生成check_ext.cmake.in

function(check_ext target_name test_file source_dir sources_list)
  set(CMAKE_CONFIGURABLE_FILE_CONTENT "")
  foreach(_src_file ${sources_list} ${ARGN})
    get_filename_component(_src_ext "${source_dir}/${_src_file}" EXT)
    if("${_src_ext}" STREQUAL ".h")
      set(CMAKE_CONFIGURABLE_FILE_CONTENT "${CMAKE_CONFIGURABLE_FILE_CONTENT}\n#include \"${source_dir}/${_src_file}\"")
    endif()
  endforeach()
  set(_check_file "${target_name}_c_compile_test.c")
  configure_file("${CMAKE_ROOT}/Modules/CMakeConfigurableFile.in" "${test_file}" @ONLY)
endfunction()

check_ext(@TARGET_NAME@ @TEST_FILE@ @SOURCE_DIR@ @SOURCES_LIST@)

它定义了一个相当通用的函数,但它的调用方式将由CMake通过make_c_compile_tester配置。我使用foreach()的方式允许提供文件列表,而不事先在单个变量中定义它们(它从${sources_list}迭代到给函数的最后一个参数)。

现在,函数make_c_compile_tester

function(make_c_compile_tester target_name)
  get_target_property(_src_list ${target_name} SOURCES)

  set(TARGET_NAME ${target_name})
  set(SOURCES_LIST ${_src_list})
  set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
  set(TEST_FILE "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_CCompileTest.c")
  configure_file(check_ext.cmake.in check_ext_${target_name}.cmake @ONLY)

  add_custom_command(OUTPUT ${TEST_FILE}
    COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/check_ext_${target_name}.cmake"
    WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
    DEPENDS ${_src_list}
    COMMENT "Generate program to check extensions."
  )
  add_custom_target(${target_name}-check-ext-depends DEPENDS "${TEST_FILE}")

  add_library(${target_name}-check-ext SHARED "${TEST_FILE}")
  add_dependencies(${target_name}-check-ext ${target_name}-check-ext-depends)

  add_dependencies(${target_name} ${target_name}-check-ext)
endfunction()

它会生成check_ext.cmake的版本(称为check_ext_${target_name}.cmake);触发此脚本的处理包含在add_custom_command()中。这样,CMake就会发现这个脚本生成check_ext_${target_name}.cmake,并且依赖于源文件。

然后我们制作一个仅依赖于${target_name}-check-ext-depends的自定义目标check_ext_${target_name}.cmake。然后,我们创建编译CCompileTest.c文件的库,并使其依赖于${target_name}-check-ext-depends,以便在编译之前修改文件时更新C文件。最后,${target_name}取决于${target_name}-check-ext,以便在实际目标之前编译检查库。

我玩的主要是虚拟C文件,因此可能仍有问题,但到目前为止它似乎有效,但对于一个案例。我发现当从源列表中删除文件时,CCompileTest.c文件不会更新。我认为这是因为生成此文件取决于源文件,因此从源列表中删除文件也会将其从依赖项中删除,这意味着该文件仍然是从CMake角度来看最新的。我不知道如何轻松解决这个问题。

答案 4 :(得分:-1)

你不能说出你想要实现的目标。

  1. 您是否希望确保所有* .h文件都是使用仅C编译器编译的(如果使用C ++则会出错)?
  2. 您是否希望确保所有* .h文件都可以与C和C ++编译器兼容?
  3. 在一般情况下,上述第2项是不可能的。在C ++中保留的标识符可能在C中未被保留。例如,如果您的标题包含:

    static int new = 0;
    

    然后,这将在C中编译,但不在C ++中编译。唯一有意义的是你可能需要一个实现函数名称修正的* .h文件,这样在头文件中声明的C函数对C ++调用者是可见的。例如,您有一个如下所示的标题:

    int foo (void); /* defined in a foo.c file somewhere
    

    要实现这一点,您应该按如下方式编写标头foo.h(注意不要使用C ++保留标识符):

    #ifdef H_FOO
    #define H_FOO
    
    ... /* type declarations and definitions go here */
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    int foo (void);
    /* Rest of your function declarations go here */
    
    #ifdef __cplusplus
    };
    #endif
    
    
    #endif