Создайте пользовательский промежуточный (улучшите C компилируемый тестер) в CMake
Итак, у нас есть смешанные проекты на С++/C. Я хочу обеспечить это, даже если все исходные файлы являются .cpp(С++ скомпилированы), что исходные файлы с расширением "*.h" C компилируются (используя *.hpp только для файлов С++). Я хотел бы принудительно выполнить его во время компиляции, так что сборка возвращает ошибку (связанную с проектом с полными номерами строк и ошибкой и предупреждениями C). Выполнение во время настройки (imho) приводит к ограниченным результатам.
Итак, у меня есть script, который сделает это:
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 и фактически использовать ее. Но это кошмар, который правильно задает все переменные в генераторе без создания стиля (в настоящее время я пытаюсь заставить его работать с Visual Studio, хотя требуется кросс-платформенное решение).
Ответы
Ответ 1
Я действительно не следовал вашему вопросу, но касался:
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
Ответ 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.
В 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
Ответ 3
Обычно *.h файлы скомпилированы сами по себе, но включены в другие файлы c/cpp. Когда вы включаете файл, все его содержимое затем добавляется в исходный файл, и, следовательно, компиляция, которую он претерпевает, будет соответствовать файлу, поэтому, если вы скомпилируете файл *.cpp с компилятором С++, и этот файл содержит файл *.h вам нечего делать, кроме изменения компиляции на компиляцию C.
Ответ 4
Вот потенциальное решение, с которым я столкнулся. Идея состоит в том, что 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})
Я переместил код, который вы дали, который генерирует ${project_name}_CCompileTest.c
в шаблоне CMake 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(@[email protected] @[email protected] @[email protected] @[email protected])
Он определяет довольно общую функцию, но способ ее вызова будет настроен 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
); инициирование обработки этого script завернуто в 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. Я не знаю, как легко решить эту проблему.
Ответ 5
Вы не говорите, чего вы хотите достичь.
- Вы хотите, чтобы все файлы *.h были скомпилированы с использованием компилятора C (с ошибкой, если используется С++)?
- Вы хотите, чтобы все файлы *.h были скомпилированы как с компиляторами C, так и с С++?
Число 2 выше невозможно в общем случае. Идентификаторы, зарезервированные на С++, могут быть безоговорочными в C. Например, если ваш заголовок имеет следующее:
static int new = 0;
Затем это будет компилироваться в C, но не в С++. Единственное, что имеет смысл, это, возможно, вам нужен файл *.h, который реализует правильное имя функции, чтобы функции C, объявленные в заголовке, были видны вызывающим С++. Например, у вас есть заголовок, который выглядит так:
int foo (void); /* defined in a foo.c file somewhere
Для этого вы должны написать свой заголовок foo.h следующим образом (заботясь о том, чтобы не использовать зарезервированные идентификаторы С++):
#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