18
\$\begingroup\$

UPDATE: I am no longer updating this post with the contents of my actual project since they have changed significantly (Unit testing is now done with doctest, code coverage is included, etc.). This post is still complete as it is and should suffice to serve as a starting point for new C++14 projects.


Recently, I have decided to create a C++14 oroject template that I plan on using for all of my C++ projects. The build system generator I use here is CMake. Here were my goals when I set this up:

  1. The project should compile with MSVC, GCC, and Clang.
  2. I can test the code on all platforms.
  3. I can install the targets on all platforms.
  4. I can generate Documentation on all platforms using Doxygen.

Here is my project structure:

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

Here is my top level 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)

The CMakeLists.txt in 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
)

The CMakeLists.txt in 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)

The CMakeLists.txt in third_party/catch (Taken from official docs and slightly modified):

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

The CMakeLists.txt in 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)

Here are the questions I want to be answered from this review:

  1. Am I following the best practices in terms of setting up my project?
  2. Is there any way my CMake syntax could be improved upon?
  3. Is my code suitable for Clang, GCC, and MSVC? (I have tested using Travis CI and Appveyor, but I just need to make sure)
  4. Am I installing all of my targets correctly (In the right directories)?
  5. How would I handle resource files in this setup? Suppose a file in src needed a file in src/resources/. Then where would this file be installed? In bin or somewhere else? (Full SO Question)
\$\endgroup\$
  • \$\begingroup\$ Is ParseAndAddCatchTests.cmake left out intentionally? Also, do you want to support other c++ compilers, or just those three? \$\endgroup\$ – hoffmale Mar 22 '18 at 7:41
  • \$\begingroup\$ @hoffmale ParseAndAddCatchTests.cmake was provided by Catch and my verbatim usage of that file led me to leave it out. My primary goal is to support those 3 compilers, but I wouldn't mind if I was able to support more... ;) \$\endgroup\$ – Arnav Borborah Mar 22 '18 at 11:24
  • \$\begingroup\$ For reference, here is ParseAndAddCatchTests.cmake. \$\endgroup\$ – Arnav Borborah Mar 22 '18 at 11:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.