所以我们混合了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一起工作,但需要跨平台解决方案)。
答案 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)
你不能说出你想要实现的目标。
在一般情况下,上述第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