mirror of
1
0
Fork 0

Adding extended example

This commit is contained in:
Henry Schreiner 2019-08-07 05:06:55 +00:00
parent f0f8878b57
commit 90ff509892
22 changed files with 324 additions and 17 deletions

View File

@ -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

View File

@ -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).

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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.

View File

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

1
examples/extended-project/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*build*

View File

@ -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()

View File

@ -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.

View File

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

View File

@ -0,0 +1,17 @@
#include <modern/lib.hpp>
#include <fmt/format.h>
#include <iostream>
#include <vector>
#include <tuple>
int main() {
std::vector<double> input = {1.2, 2.3, 3.4, 4.5};
auto [mean, moment] = accumulate_vector(input);
fmt::print("Mean: {}, Moment: {}\n", mean, moment);
return 0;
}

View File

@ -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()

View File

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

View File

@ -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`.

View File

@ -0,0 +1,14 @@
#pragma once
#include <vector>
#include <tuple>
/// \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<double, double> accumulate_vector(
const std::vector<double>& values ///< The vector of values
);

View File

@ -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})

View File

@ -0,0 +1,21 @@
#include <modern/lib.hpp>
#include <tuple>
#include <vector>
#include <algorithm>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/moment.hpp>
namespace ba = boost::accumulators;
std::tuple<double, double> accumulate_vector(const std::vector<double>& values) {
ba::accumulator_set<double, ba::stats<ba::tag::mean, ba::tag::moment<2>>> acc;
std::for_each(std::begin(values), std::end(values), std::ref(acc));
return {ba::mean(acc), ba::moment<2>(acc)};
}

View File

@ -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

View File

@ -0,0 +1,12 @@
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include <modern/lib.hpp>
TEST_CASE( "Quick check", "[main]" ) {
std::vector<double> values {1, 2., 3.};
auto [mean, moment] = accumulate_vector(values);
REQUIRE( mean == 2.0 );
REQUIRE( moment == Approx(4.666666) );
}

View File

@ -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]