From 90ff509892f3b6f280da49b39938435d4c8aabd1 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 7 Aug 2019 05:06:55 +0000 Subject: [PATCH] Adding extended example --- .gitlab-ci.yml | 13 ++-- chapters/basics/example.md | 2 + chapters/intro/installing.md | 5 +- chapters/intro/newcmake.md | 5 +- chapters/projects/fetch.md | 28 +++++++- chapters/testing.md | 26 ++++++- examples/CMakeLists.txt | 6 +- examples/extended-project/.gitignore | 1 + examples/extended-project/CMakeLists.txt | 70 +++++++++++++++++++ examples/extended-project/README.md | 44 ++++++++++++ examples/extended-project/apps/CMakeLists.txt | 4 ++ examples/extended-project/apps/app.cpp | 17 +++++ .../extended-project/cmake/FindSomeLib.cmake | 0 .../add_FetchContent_MakeAvailable.cmake | 8 +++ examples/extended-project/docs/CMakeLists.txt | 9 +++ examples/extended-project/docs/mainpage.md | 11 +++ .../extended-project/include/modern/lib.hpp | 14 ++++ examples/extended-project/src/CMakeLists.txt | 22 ++++++ examples/extended-project/src/lib.cpp | 21 ++++++ .../extended-project/tests/CMakeLists.txt | 22 ++++++ examples/extended-project/tests/testlib.cpp | 12 ++++ examples/root-dict/CMakeLists.txt | 1 + 22 files changed, 324 insertions(+), 17 deletions(-) create mode 100644 examples/extended-project/.gitignore create mode 100644 examples/extended-project/CMakeLists.txt create mode 100644 examples/extended-project/README.md create mode 100644 examples/extended-project/apps/CMakeLists.txt create mode 100644 examples/extended-project/apps/app.cpp create mode 100644 examples/extended-project/cmake/FindSomeLib.cmake create mode 100644 examples/extended-project/cmake/add_FetchContent_MakeAvailable.cmake create mode 100644 examples/extended-project/docs/CMakeLists.txt create mode 100644 examples/extended-project/docs/mainpage.md create mode 100644 examples/extended-project/include/modern/lib.hpp create mode 100644 examples/extended-project/src/CMakeLists.txt create mode 100644 examples/extended-project/src/lib.cpp create mode 100644 examples/extended-project/tests/CMakeLists.txt create mode 100644 examples/extended-project/tests/testlib.cpp diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 06e734d..d3af332 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,18 +1,17 @@ test_code: - image: rootproject/root-ubuntu16:6.12 + image: rootproject/root:latest stage: test before_script: + - yum install -y make cmake boost-devel git +# will install latest CMake, even though Fedora has a recent one - mkdir -p $HOME/.local - curl -s "https://cmake.org/files/v3.15/cmake-3.15.1-Linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C $HOME/.local - export PATH=$HOME/.local/bin:$PATH script: - - mkdir -p build - - cd build - - cmake ../examples - - VERBOSE=1 cmake --build . - - ctest - - cd .. + - cmake -S examples -B build + - cmake --build build -v + - cmake --build build -t test pages: image: node:10 diff --git a/chapters/basics/example.md b/chapters/basics/example.md index fced031..98dc4fa 100644 --- a/chapters/basics/example.md +++ b/chapters/basics/example.md @@ -6,3 +6,5 @@ and one application, MyExample, with one source file. [import:'main', lang:'cmake'](../../examples/simple-project/CMakeLists.txt) The complete example is available in [examples folder](https://gitlab.com/CLIUtils/modern-cmake/tree/master/examples/simple-project). + +A larger, multi-file example is [also available](https://gitlab.com/CLIUtils/modern-cmake/tree/master/examples/extended-project). \ No newline at end of file diff --git a/chapters/intro/installing.md b/chapters/intro/installing.md index ff20757..82db66c 100644 --- a/chapters/intro/installing.md +++ b/chapters/intro/installing.md @@ -49,14 +49,17 @@ Here are some common build environments and the CMake version you'll find on the | [Ubuntu 18.04 LTS: Bionic](https://launchpad.net/ubuntu/bionic/+source/cmake) | 3.10.2 | An LTS with a pretty decent minimum version! | | [Ubuntu 18.10: Cosmic](https://launchpad.net/ubuntu/cosmic/+source/cmake) | 3.12.1 | | | [Ubuntu 19.04: Disco](https://launchpad.net/ubuntu/disco/+source/cmake) | 3.13.4 | | +| [AlpineLinux 3.10](https://pkgs.alpinelinux.org/packages?name=cmake&branch=v3.10)| 3.14.5 | Useful in Docker | | [Python PyPI](https://pypi.org/project/cmake/) | 3.13.3 | Just `pip install cmake` on many systems. Add `--user` for local installs. | | [Anaconda](https://anaconda.org/anaconda/cmake) | 3.14.0 | For use with Conda | -| [Conda-Forge](https://github.com/conda-forge/cmake-feedstock) | 3.14.5 | For use with Conda | +| [Conda-Forge](https://github.com/conda-forge/cmake-feedstock) | 3.15.1 | For use with Conda | | [Homebrew on macOS](https://formulae.brew.sh/formula/cmake) | 3.15.1 | On macOS with Homebrew, this is only a few minutesa behind cmake.org. | | [Chocolaty on Windows](https://chocolatey.org/packages/cmake) | 3.14.6 | Also up to date. The normal cmake.org installers are common on Windows, as well. | | TravisCI Trusty | 3.9 | The December 2017 update added a recent version of clang and CMake! Finally! | | TravisCI Xenial | 3.12.4 | Mid November 2018 this image became ready for widescale use. | +Also see [pkgs.org/download/cmake](https://pkgs.org/download/cmake). + ## Pip This is also provided as an official package, maintained by the authors of CMake at KitWare. It's a rather new method, and might fail on some systems (Alpine isn't supported last I checked, but that has CMake 3.8), but works really well when it works (like on Travis CI). If you have pip (Python's package installer), you can do: diff --git a/chapters/intro/newcmake.md b/chapters/intro/newcmake.md index b93d4b8..0ec3e2a 100644 --- a/chapters/intro/newcmake.md +++ b/chapters/intro/newcmake.md @@ -192,13 +192,14 @@ Quite a few more find packages produce targets. The new Visual Studio 16 2019 ge ## [CMake 3.15][] : CLI upgrade -This release has many smaller polishing changes, include several of improvements to the CMake command line, such as control over the default generator through environment variables (so now it's easy to change the default generator to Ninja). Multiple targets and `--install` are supported in `--build` mode. CMake finally supports multiple levels of logging. Generator expressions gained a few handy tools. The still very new FindPython module continues to improve, and FindBoost is now more inline with Boost 1.70's new CONFIG +This release has many smaller polishing changes, include several of improvements to the CMake command line, such as control over the default generator through environment variables (so now it's easy to change the default generator to Ninja). Multiple targets are supported in `--build` mode, and `--install` mode added. CMake finally supports multiple levels of logging. Generator expressions gained a few handy tools. The still very new FindPython module continues to improve, and FindBoost is now more inline with Boost 1.70's new CONFIG module. `export(PACKAGE)` has drastically changed; it now no longer touches `$HOME/.cmake` by default (if CMake Minimum version is 3.15 or higher), and requires an extra step if a user wants to use it. This is generally less surprising. * «envvar:CMAKE_GENERATOR» environment variable added to control default generator * Multiple target support in build mode, `cmake . --build --target a b` -* Install support, `cmake . --install` +* Shortcut `-t` for `--target` +* Install support, `cmake . --install`, does not invoke the build system * Support for `--loglevel` and `NOTICE`, `VERBOSE`, `DEBUG`, and `TRACE` for `message` * The «command:list» command gained `PREPEND`, `POP_FRONT`, and `POP_BACK` * «command:execute_process» gained `COMMAND_ECHO` option («variable:CMAKE_EXECUTE_PROCESS_COMMAND_ECHO») allows you to automatically echo commands before running them diff --git a/chapters/projects/fetch.md b/chapters/projects/fetch.md index 8f0a2c8..ded548e 100644 --- a/chapters/projects/fetch.md +++ b/chapters/projects/fetch.md @@ -1,4 +1,4 @@ -#FetchContent (CMake 3.11+) +# FetchContent (CMake 3.11+) Often, you would like to do your download of data or packages as part of the configure instead of the build. This was invented several times in third party modules, but was finally added to CMake itself as part of CMake 3.11 as the [FetchContent] module. @@ -14,9 +14,17 @@ For example, to download Catch2: FetchContent_Declare( catch GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v2.2.1 + GIT_TAG v2.9.1 ) +# CMake 3.14+ +FetchContent_MakeAvailable(catch2) +``` + +If you can't use CMake 3.14+, the classic way to prepare code was: + +```cmake +# CMake 3.11+ FetchContent_GetProperties(catch) if(NOT catch_POPULATED) FetchContent_Populate(catch) @@ -24,5 +32,21 @@ if(NOT catch_POPULATED) endif() ``` +Of course, you could bundled this up into a macro: + +```cmake +if(${CMAKE_VERSION} VERSION_LESS 3.14) + macro(FetchContent_MakeAvailable NAME) + FetchContent_GetProperties(${NAME}) + if(NOT ${NAME}_POPULATED) + FetchContent_Populate(${NAME}) + add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR}) + endif() + endmacro() +endif() +``` + +Now you have the CMake 3.14+ syntax in CMake 3.11+. + [FetchContent]: https://cmake.org/cmake/help/latest/module/FetchContent.html diff --git a/chapters/testing.md b/chapters/testing.md index 479d268..3f01118 100644 --- a/chapters/testing.md +++ b/chapters/testing.md @@ -5,15 +5,31 @@ In your main CMakeLists.txt you need to add the following function call (not in a subfolder): ```cmake -include(CTest) +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + include(CTest) +endif() ``` -Which will enable testing and set a `BUILD_TESTING` option so users can turn testing on and off (Along with [a few other things](https://gitlab.kitware.com/cmake/cmake/blob/master/Modules/CTest.cmake)). Or you can do this yourself: +Which will enable testing and set a `BUILD_TESTING` option so users can turn testing on and off (Along with [a few other things](https://gitlab.kitware.com/cmake/cmake/blob/master/Modules/CTest.cmake)). Or you can do this yourself by directly calling `enable_testing()`. + +When you add your test folder, you should do something like this: ```cmake -enable_testing() +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING) + add_subdirectory(tests) +endif() ``` +The reason for this is that if someone else includes your package, and they use `BUILD_TESTING`, they probably do not want your tests to build. In the rare case that you really do want to enable testing on both packages, you can provide an override: + +```cmake +if((CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR MYPROJECT_BUILD_TESTING) AND BUILD_TESTING) + add_subdirectory(tests) +endif() +``` + +The main use case for the override above is actually in this book's own examples, as the master CMake project really does want to run all the subproject tests. + You can register targets with: ```cmake @@ -49,3 +65,7 @@ add_test( ## Testing Frameworks Look at the subchapters for recipes for popular frameworks. + +* [GoogleTest](testing/googletest.md): A popular option from Google. Development can be a bit slow. +* [Catch2](testing/catch.md): A modern, PyTest-like framework with clever macros. +* [DocTest](https://github.com/onqtam/doctest): A replacement for Catch2 that is supposed to compile much faster and be cleaner. See Catch2 chapter and replace with DocTest. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 713ee83..50192a7 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,10 +1,12 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.11...3.15) project(ModernCMakeExamples) +set(MODERN_CMAKE_BUILD_TESTING ON) -enable_testing() +include(CTest) add_subdirectory(simple-project) +add_subdirectory(extended-project) add_subdirectory(root-usefile) add_subdirectory(root-simple) diff --git a/examples/extended-project/.gitignore b/examples/extended-project/.gitignore new file mode 100644 index 0000000..f35e3c0 --- /dev/null +++ b/examples/extended-project/.gitignore @@ -0,0 +1 @@ +*build* diff --git a/examples/extended-project/CMakeLists.txt b/examples/extended-project/CMakeLists.txt new file mode 100644 index 0000000..1afe08c --- /dev/null +++ b/examples/extended-project/CMakeLists.txt @@ -0,0 +1,70 @@ +# Works with 3.11 and tested through 3.15 +cmake_minimum_required(VERSION 3.11...3.15) + +# Project name and a few useful settings. Other commands can pick up the results +project(ModernCMakeExample + VERSION 0.1 + DESCRIPTION "An example project with CMake" + LANGUAGES CXX) + +# Only do these if this is the main project, and not if it is included through add_subdirectory +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + + # Optionally set things like CMAKE_CXX_STANDARD, CMAKE_POSITION_INDEPENDENT_CODE here + + # Let's ensure -std=c++xx instead of -std=g++xx + set(CMAKE_CXX_EXTENSIONS OFF) + + # Let's nicely support folders in IDE's + set_property(GLOBAL PROPERTY USE_FOLDERS ON) + + # Testing only available if this is the main app + # Note this needs to be done in the main CMakeLists + # since it calls enable_testing, which must be in the + # main CMakeLists. + include(CTest) + + # Docs only available if this is the main app + find_package(Doxygen) + if(Doxygen_FOUND) + add_subdirectory(docs) + else() + message(STATUS "Doxygen not found, not building docs") + endif() +endif() + + +# FetchContent added in CMake 3.11, downloads during the configure step +include(FetchContent) +# FetchContent_MakeAvailable was not added until CMake 3.14 +if(${CMAKE_VERSION} VERSION_LESS 3.14) + include(cmake/add_FetchContent_MakeAvailable.cmake) +endif() + + +# Accumulator library +# This is header only, so could be replaced with git submodules or FetchContent +find_package(Boost REQUIRED) +# Adds Boost::boost + +# Formatting library +FetchContent_Declare( + fmtlib + GIT_REPOSITORY https://github.com/fmtlib/fmt.git + GIT_TAG 5.3.0 +) +FetchContent_MakeAvailable(fmtlib) +# Adds fmt::fmt + +# The compiled library code is here +add_subdirectory(src) + +# The executable code is here +add_subdirectory(apps) + +# Testing only available if this is the main app +# Emergency override MODERN_CMAKE_BUILD_TESTING provided as well +if((CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR MODERN_CMAKE_BUILD_TESTING) AND BUILD_TESTING) + add_subdirectory(tests) +endif() + diff --git a/examples/extended-project/README.md b/examples/extended-project/README.md new file mode 100644 index 0000000..a65ae64 --- /dev/null +++ b/examples/extended-project/README.md @@ -0,0 +1,44 @@ +This is an example project using CMake. + +The requirements are: + +* CMake 3.11 or better; 3.14+ highly recommended. +* A C++17 compatible compiler +* The Boost libararies (header only part is fine) +* Git +* Doxygen (optional) + +To configure: + +```bash +cmake -S . -B build +``` + +Add `-GNinja` if you have Ninja. + +To build: + +```bash +cmake --build build +``` + +To test (`--target` can be written as `-t` in CMake 3.15+): + +```bash +cmake --build build --target test +``` + +To build docs (requires Doxygen, output in `build/docs/html`): + +```bash +cmake --build build --target docs +``` + +To use an IDE, such as Xcode: + +```bash +cmake -S . -B xbuild -GXcode +cmake --open xbuild +``` + +The CMakeLists show off several useful design patters for CMake. diff --git a/examples/extended-project/apps/CMakeLists.txt b/examples/extended-project/apps/CMakeLists.txt new file mode 100644 index 0000000..cbaa521 --- /dev/null +++ b/examples/extended-project/apps/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(app app.cpp) +target_compile_features(app PRIVATE cxx_std_17) + +target_link_libraries(app PRIVATE modern_library fmt::fmt) diff --git a/examples/extended-project/apps/app.cpp b/examples/extended-project/apps/app.cpp new file mode 100644 index 0000000..186f404 --- /dev/null +++ b/examples/extended-project/apps/app.cpp @@ -0,0 +1,17 @@ +#include + +#include + +#include +#include +#include + +int main() { + std::vector input = {1.2, 2.3, 3.4, 4.5}; + + auto [mean, moment] = accumulate_vector(input); + + fmt::print("Mean: {}, Moment: {}\n", mean, moment); + + return 0; +} diff --git a/examples/extended-project/cmake/FindSomeLib.cmake b/examples/extended-project/cmake/FindSomeLib.cmake new file mode 100644 index 0000000..e69de29 diff --git a/examples/extended-project/cmake/add_FetchContent_MakeAvailable.cmake b/examples/extended-project/cmake/add_FetchContent_MakeAvailable.cmake new file mode 100644 index 0000000..3a2a4d5 --- /dev/null +++ b/examples/extended-project/cmake/add_FetchContent_MakeAvailable.cmake @@ -0,0 +1,8 @@ +macro(FetchContent_MakeAvailable NAME) + FetchContent_GetProperties(${NAME}) + if(NOT ${NAME}_POPULATED) + FetchContent_Populate(${NAME}) + add_subdirectory(${${NAME}_SOURCE_DIR} ${${NAME}_BINARY_DIR}) + endif() +endmacro() + diff --git a/examples/extended-project/docs/CMakeLists.txt b/examples/extended-project/docs/CMakeLists.txt new file mode 100644 index 0000000..a8436ba --- /dev/null +++ b/examples/extended-project/docs/CMakeLists.txt @@ -0,0 +1,9 @@ +set(DOXYGEN_EXTRACT_ALL YES) +set(DOXYGEN_BUILTIN_STL_SUPPORT YES) + +doxygen_add_docs(docs + modern/lib.hpp + "${CMAKE_CURRENT_SOURCE_DIR}/mainpage.md" + WORKING_DIRECTORY + "${PROJECT_SOURCE_DIR}/include" +) diff --git a/examples/extended-project/docs/mainpage.md b/examples/extended-project/docs/mainpage.md new file mode 100644 index 0000000..bf7f65e --- /dev/null +++ b/examples/extended-project/docs/mainpage.md @@ -0,0 +1,11 @@ +# Documentation for Modern Library {#mainpage} + +This is the documentation for my simple example library. + +It is good documentation because: + +1. It exists. +2. I wrote it. +3. Everthing is documented (pretty easy since there's only one function) + +The single provided function is `::accumulate_vector`. diff --git a/examples/extended-project/include/modern/lib.hpp b/examples/extended-project/include/modern/lib.hpp new file mode 100644 index 0000000..11ab491 --- /dev/null +++ b/examples/extended-project/include/modern/lib.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +/// \brief Accumulate a vector to produce the mean and the first moment of the distribution. +/// +/// This computes the mean and the first moment of a vector of double values. +/// +std::tuple accumulate_vector( + const std::vector& values ///< The vector of values +); + + diff --git a/examples/extended-project/src/CMakeLists.txt b/examples/extended-project/src/CMakeLists.txt new file mode 100644 index 0000000..2213707 --- /dev/null +++ b/examples/extended-project/src/CMakeLists.txt @@ -0,0 +1,22 @@ + +# Note that headers are optional, and do not affect add_library, but they will not +# show up in IDEs unless they are listed in add_library. + +# Optionally glob, but only for CMake 3.12 or later: +# file(GLOB HEADER_LIST CONFIGURE_DEPENDS "${ModernCMakeExample_SOURCE_DIR}/include/modern/*.hpp") +set(HEADER_LIST "${ModernCMakeExample_SOURCE_DIR}/include/modern/lib.hpp") + +# Make an automatic library - will be static or dynamic based on user setting +add_library(modern_library lib.cpp ${HEADER_LIST}) + +# We need this directory, and users of our library will need it too +target_include_directories(modern_library PUBLIC ../include) + +# This depends on (header only) boost +target_link_libraries(modern_library PRIVATE Boost::boost) + +# All users of this library will need at least C++11 +target_compile_features(modern_library PUBLIC cxx_std_11) + +# IDEs should put the headers in a nice place +source_group(TREE "${PROJECT_SOURCE_DIR}/include" PREFIX "Header Files" FILES ${HEADER_LIST}) diff --git a/examples/extended-project/src/lib.cpp b/examples/extended-project/src/lib.cpp new file mode 100644 index 0000000..dc20885 --- /dev/null +++ b/examples/extended-project/src/lib.cpp @@ -0,0 +1,21 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace ba = boost::accumulators; + +std::tuple accumulate_vector(const std::vector& values) { + + ba::accumulator_set>> acc; + + std::for_each(std::begin(values), std::end(values), std::ref(acc)); + + return {ba::mean(acc), ba::moment<2>(acc)}; +} diff --git a/examples/extended-project/tests/CMakeLists.txt b/examples/extended-project/tests/CMakeLists.txt new file mode 100644 index 0000000..2d06694 --- /dev/null +++ b/examples/extended-project/tests/CMakeLists.txt @@ -0,0 +1,22 @@ + +# Testing library +FetchContent_Declare( +catch2 +GIT_REPOSITORY https://github.com/catchorg/Catch2.git +GIT_TAG v2.9.1 +) +FetchContent_MakeAvailable(catch2) +# Adds Catch2::Catch2 + +# Tests need to be added as executables first +add_executable(testlib testlib.cpp) + +# I'm using C++17 in the test +target_compile_features(testlib PRIVATE cxx_std_17) + +# Should be linked to the main library, as well as the Catch2 testing library +target_link_libraries(testlib PRIVATE modern_library Catch2::Catch2) + +# If you register a test, then ctest and make test will run it. +# You can also run examples and check the output, as well. +add_test(NAME testlibtest COMMAND testlib) # Command can be a target diff --git a/examples/extended-project/tests/testlib.cpp b/examples/extended-project/tests/testlib.cpp new file mode 100644 index 0000000..3a50df9 --- /dev/null +++ b/examples/extended-project/tests/testlib.cpp @@ -0,0 +1,12 @@ +#define CATCH_CONFIG_MAIN +#include +#include + +TEST_CASE( "Quick check", "[main]" ) { + std::vector values {1, 2., 3.}; + auto [mean, moment] = accumulate_vector(values); + + REQUIRE( mean == 2.0 ); + REQUIRE( moment == Approx(4.666666) ); +} + diff --git a/examples/root-dict/CMakeLists.txt b/examples/root-dict/CMakeLists.txt index 4613105..97a9127 100644 --- a/examples/root-dict/CMakeLists.txt +++ b/examples/root-dict/CMakeLists.txt @@ -22,6 +22,7 @@ include_directories(ROOT_BUG) root_generate_dictionary(G__DictExample DictExample.h LINKDEF DictLinkDef.h) add_library(DictExample SHARED DictExample.cxx DictExample.h G__DictExample.cxx) +target_include_directories(DictExample PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries(DictExample PUBLIC ROOT::Core Threads::Threads) ## [main]