Создайте пользовательский промежуточный (улучшите 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, хотя требуется кросс-платформенное решение).

Ответ 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