更新:我不再用我的实际项目的内容来更新此帖子,因为它们已经发生了很大的变化(单元测试现在通过doctest完成,包括了代码覆盖率,等等)。这篇文章仍然是完整的,可以作为新C ++ 14项目的起点。


最近,我决定创建一个C ++ 14 oroject我计划用于所有C ++项目的模板。我在这里使用的构建系统生成器是CMake。设置时,这是我的目标:


该项目应使用MSVC,GCC和Clang进行编译。
我可以在所有平台上测试代码。
我可以在所有平台上安装目标。
我可以使用Doxygen在所有平台上生成文档。

这是我的项目结构:

project-name/
├── CMakeLists.txt
├── cmake
│   └── Modules
│       └── ParseAndAddCatchTests.cmake
├── doc
│   ├── CMakeLists.txt
│   ├── Doxyfile.in
│   └── main_page.md
├── include
│   └── project-abbr
│       ├── config.hpp <-- Contains project versioning
│       └── factorial.hpp
├── src
│   ├── CMakeLists.txt
│   ├── factorial.cpp
│   └── main.cpp
├── test
│   ├── CMakeLists.txt
│   ├── factorial_test.cpp
│   └── test_runner.cpp
└── third_party
    └── catch
        └── CMakeLists.txt


这是我的顶级CMakeLists.txt

cmake_minimum_required(VERSION 3.1)

if (POLICY CMP0048)
    cmake_policy(SET CMP0048 NEW)
endif (POLICY CMP0048)

project(Project-Name VERSION 0.1.0)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Options
option(BUILD_TESTS "Build test executable" OFF)
option(GEN_DOCS "Generate documentation" OFF)

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")

if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
    message(STATUS "Setting build type to 'Release' as no build type was specified")
    set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the build type (Debug/Release)" FORCE)
endif (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)

if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2")
endif(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")

include_directories(include)
add_subdirectory(src)

if (BUILD_TESTS)
    add_subdirectory(third_party/catch)
    include(CTest)
    enable_testing()
    add_subdirectory(test)
endif (BUILD_TESTS)

if (GEN_DOCS)
    add_subdirectory(doc)
endif (GEN_DOCS)

# Install the project header files into the appropriate directory
# Other installs are in src/CMakeLists.txt
install(DIRECTORY include/ DESTINATION include)


CMakeLists.txt中的src

add_executable(Project-Name main.cpp) # The main executable
add_library(Project-Name-lib hello_world.cpp factorial.cpp) # A library for tests

SET_TARGET_PROPERTIES(Project-Name-lib PROPERTIES PREFIX "") # Remove the lib prefix

target_link_libraries(Project-Name Project-Name-lib) # Link our sources to the executable

install(TARGETS Project-Name DESTINATION bin)
install(TARGETS Project-Name-lib
    ARCHIVE DESTINATION lib
    LIBRARY DESTINATION lib
    RUNTIME DESTINATION bin
)


CMakeLists.txt中的test

set(TEST_SOURCES factorial_test.cpp hello_world_test.cpp)

add_executable(test_runner test_runner.cpp ${TEST_SOURCES})
target_link_libraries(test_runner Project-Name-lib)
target_include_directories(test_runner PRIVATE ${CATCH_INCLUDE_DIR} ${COMMON_INCLUDES})

include(ParseAndAddCatchTests)
ParseAndAddCatchTests(test_runner)


CMakeLists.txt中的third_party/catch(摘自官方文档,并略有修改):

include(ExternalProject)
find_package(Git REQUIRED)

ExternalProject_Add(
    catch
    PREFIX ${CMAKE_BINARY_DIR}/catch
    GIT_REPOSITORY https://github.com/catchorg/Catch2.git
    TIMEOUT 10
    UPDATE_COMMAND ${GIT_EXECUTABLE} pull
    CONFIGURE_COMMAND ""
    BUILD_COMMAND ""
    INSTALL_COMMAND ""
    LOG_DOWNLOAD ON
)

# Expose required variable (CATCH_INCLUDE_DIR) to parent scope
ExternalProject_Get_Property(catch source_dir)
set(CATCH_INCLUDE_DIR ${source_dir}/single_include CACHE INTERNAL "Path to include folder for Catch")


CMakeLists.txt中的doc

find_package(Doxygen)

if (DOXYGEN_FOUND)
    set(DOXYGEN_IN Doxyfile.in)
    set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)

    configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)

    add_custom_target(doc
        COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
        VERBATIM
    )
else (DOXYGEN_FOUND)
    message(FATAL_ERROR "Doxygen needs to be installed to generate documentation!")
endif (DOXYGEN_FOUND)


这里是我想从这次评论中得到回答的问题:


我在建立项目方面遵循最佳实践吗?
我的CMake语法是否可以通过任何方式改进?
我的代码是否适合Clang,GCC和MSVC? (我已经使用Travis CI和Appveyor进行了测试,但是我只需要确定一下)
我是否正确地在正确的目录中安装了所有目标? ?假设src中的文件需要src/resources/中的文件。那么该文件将安装在哪里?在bin或其他地方? (完整的问题)


评论

是ParseAndAddCatchTests.cmake故意遗漏了吗?另外,您是否要支持其他c ++编译器,或仅支持这三个编译器?

@hoffmale ParseAndAddCatchTests.cmake由Catch提供,我对该文件的逐字用法使我忽略了它。我的主要目标是支持这3个编译器,但是我不介意我是否能够支持更多...;)

作为参考,这里是ParseAndAddCatchTests.cmake。