diff --git a/SUMMARY.md b/SUMMARY.md index 5bfcd86..a6ff607 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -16,14 +16,14 @@ * [Small but common needs](chapters/features/small.md) * [Utilities](chapters/features/utilities.md) * [IDEs](chapters/features/ides.md) - * [Debugging (X)](chapters/features/debug.md) + * [Debugging](chapters/features/debug.md) * [Including Projects](chapters/projects.md) * [Submodule](chapters/projects/submodule.md) * [DownloadProject](chapters/projects/download.md) - * [Fetch (CMake 3.11)](chapters/projects/fetch.md) + * [Fetch (CMake 3.11) (X)](chapters/projects/fetch.md) * [Testing](chapters/testing.md) * [GoogleTest](chapters/testing/googletest.md) - * [Catch (X)](chapters/testing/catch.md) + * [Catch](chapters/testing/catch.md) * [Exporting and Installing](chapters/exporting.md) * [Looking for libraries](chapters/packages.md) * [CUDA](chapters/packages/CUDA.md) diff --git a/chapters/features/debug.md b/chapters/features/debug.md index 4dca9c8..5aafc8e 100644 --- a/chapters/features/debug.md +++ b/chapters/features/debug.md @@ -1,6 +1,6 @@ # Debugger support +## Building in debug mode + +For single-configuration generators, you can build your code with `-DCMAKE_BUILD_TYPE=Debug` to get debugging flags. In multi-configuration generators, like many IDEs, you can pick the configuration in the IDE. There are distinct flags for this mode (variables ending in `_DEBUG` as opposed to `_RELEASE`), as well as a generator expression value `CONFIG:Debug` or `CONFIG:Release`. -{% hint style='working' %} -This document is a work in progress. You can raise an issue or put in a merge request on [GitLab](https://gitlab.com/CLIUtils/modern-cmake). -{% endhint %} diff --git a/chapters/projects.md b/chapters/projects.md index 824635f..a22297d 100644 --- a/chapters/projects.md +++ b/chapters/projects.md @@ -3,70 +3,5 @@ This is where a good Git system plus CMake shines. You might not be able to solve all the world's problems, but this is pretty close for C++! -## Git Submodule Method +There are several methods listed in the chapters in this section. -If you want to add a Git repository on the same service (GitHub, GitLab, BitBucket, etc), the following is the correct Git command to set that up as a submodule in the `extern` directory: - -```bash -git submodule add ../../owner/repo.git extern/repo -``` - -The relative path to the repo is important; it allows you to keep the same access method (ssh or https) as the parent repository. This works very well in most ways. When you are inside the submodule, you can treat it just like a normal repo, and when you are in the parent repository, you can "add" to change the current commit pointer. - -But the traditional downside is that you either have to have your users know git submodule commands, so they can `init` and `update` the repo, or they have to add `--recursive` when they initially clone your repo. CMake can offer a solution: - -```cmake -find_package(Git QUIET) -if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") -# Update submodules as needed - option(GIT_SUBMODULE "Check submodules during build" ON) - if(GIT_SUBMODULE) - message(STATUS "Submodule update") - execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - RESULT_VARIABLE GIT_SUBMOD_RESULT) - if(NOT GIT_SUBMOD_RESULT EQUAL "0") - message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") - endif() - endif() -endif() - -if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/repo/CMakeLists.txt" - message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") -endif() -``` - -The first line checks for Git using CMake's built in `FindGit.cmake`. Then, if you are in a git checkout of your source, add an option (defaulting to `ON`) that allows developers to turn off the feature if they need to. We then run the command to get all repositories, and fail if that command fails, with a nice error message. Finally, we verify that the repositories exist before continuing, regardless of the method used to obtain them. You can use `OR` to list several. - -Now, your users can be completely oblivious to the existence of the submodules, and you can still keep up good development practices! The only thing to watch out for is for developers; you will reset the submodule when you rerun CMake if you are developing inside the submodule. Just add new commits to the parent staging area, and you'll be fine. - -You can then include projects that provide good CMake support: - -```cmake -add_subdirectory(extern/repo) -``` - -Or, you can build an interface library target yourself if it is a header only project. Or, you can use `find_package` if that is supported, probably preparing the initial search directory to be the one you've added (check the docs or the file for the `Find*.cmake` file you are using). You can also include a CMake helper file directory if you append to your `CMAKE_MODULE_PATH`, for example to add `pybind11`'s improved `FindPython*.cmake` files. - - -### Bonus: Git version number - -Move this to Git section: - -```cmake -execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - OUTPUT_VARIABLE PACKAGE_GIT_VERSION - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) -``` - - -## Downloading Method - -There are several different methods to get CMake to download data, either at configure time or compile time. The latter is build directly into CMake with the x command. If you prefer configure time, see the [Crascit/DownloadProject](https://github.com/Crascit/DownloadProject) repository for a drop-in solution. Submodules work so well, though, that I've discontinued most of the downloads for things like GoogleTest and moved them to submodules. Auto downloads are harder to mimic if you -don't have internet access, and they are often implemented in the build directory, wasting time and space if you have multiple build directories. - -{% hint style='working' %} -A new module in CMake 3.11, [FetchContent](https://cmake.org/cmake/help/v3.11/module/FetchContent.html) will build in this feature into CMake, stay tuned! -{% endhint %} diff --git a/chapters/projects/download.md b/chapters/projects/download.md index 0fb3536..847d45b 100644 --- a/chapters/projects/download.md +++ b/chapters/projects/download.md @@ -1,34 +1,16 @@ # GoogleTest: Download method -You can use the downloader in my [CMake helper repository][CLIUtils/cmake], using CMake's `include` command. +## Downloading Method: build time -This is a downloader for [GoogleTest], based on the excellent [DownloadProject] tool. Downloading a copy for each project is the recommended way to use GoogleTest (so much so, in fact, that they have disabled the automatic CMake install target), so this respects that design decision. This method downloads the project at configure time, so that IDE's correctly find the libraries. Using it is simple: - -```cmake -cmake_minimum_required(VERSION 3.4) -project(MyProject CXX) -list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) - -enable_testing() # Must be in main file - -include(AddGoogleTest) # Could be in /tests/CMakeLists.txt -add_executable(SimpleTest SimpleTest.cu) -add_gtest(SimpleTest) -``` - -> Note: `add_gtest` is just a macro that adds `gtest`, `gmock`, and `gtest_main`, and then runs `add_test` to create a test with the same name: -> ```cmake -> target_link_libraries(SimpleTest gtest gmock gtest_main) -> add_test(SimpleTest SimpleTest) -> ``` +Until CMake 3.11, the primary download method for packages was done at build time. This causes several issues; most important of which is that `add_subdirectory` doesn't work on a file that doesn't exist yet! The tool for this, ExternalProject, has to work around this by doing the build itself. (It can, however, build non-CMake packages as well).[^1] -## Catch +[^1]: Note that ExternalData is the tool for non-package data. + +## Downloading Method: configure time + +If you prefer configure time, see the [Crascit/DownloadProject](https://github.com/Crascit/DownloadProject) repository for a drop-in solution. Submodules work so well, though, that I've discontinued most of the downloads for things like GoogleTest and moved them to submodules. Auto downloads are harder to mimic if you +don't have internet access, and they are often implemented in the build directory, wasting time and space if you have multiple build directories. -[^1]: Here I've assumed that you are working on a GitHub repository by using the relative path to googletest. - - -[CLIUtils/cmake]: https://github.com/CLIUtils/cmake -[GoogleTest]: https://github.com/google/googletest [DownloadProject]: https://github.com/Crascit/DownloadProject diff --git a/chapters/projects/fetch.md b/chapters/projects/fetch.md index 964cf49..8513152 100644 --- a/chapters/projects/fetch.md +++ b/chapters/projects/fetch.md @@ -1,15 +1,6 @@ -# Testing +#FetchContent (CMake 3.11+) -## General Testing Information - -## GoogleTest - - -## Fetch - - - - -[CLIUtils/cmake]: https://github.com/CLIUtils/cmake -[GoogleTest]: https://github.com/google/googletest -[DownloadProject]: https://github.com/Crascit/DownloadProject +{% hint style='working' %} +This document is a work in progress. You can raise an issue or put in a merge request on [GitLab](https://gitlab.com/CLIUtils/modern-cmake). +{% endhint %} + ~ diff --git a/chapters/projects/submodule.md b/chapters/projects/submodule.md index 9da0328..6e1eb9d 100644 --- a/chapters/projects/submodule.md +++ b/chapters/projects/submodule.md @@ -1,67 +1,57 @@ -# GoogleTest: Submodule method (preferred) +# Git Submodule Method -To use this method, just checkout GoogleTest as a submodule:[^1] +If you want to add a Git repository on the same service (GitHub, GitLab, BitBucket, etc), the following is the correct Git command to set that up as a submodule in the `extern` directory: -```cmake -git submodule add --branch=release-1.8.0 ../../google/googletest.git extern/googletest +```bash +git submodule add ../../owner/repo.git extern/repo ``` -Then, in your main `CMakeLists.txt`: +The relative path to the repo is important; it allows you to keep the same access method (ssh or https) as the parent repository. This works very well in most ways. When you are inside the submodule, you can treat it just like a normal repo, and when you are in the parent repository, you can "add" to change the current commit pointer. + +But the traditional downside is that you either have to have your users know git submodule commands, so they can `init` and `update` the repo, or they have to add `--recursive` when they initially clone your repo. CMake can offer a solution: ```cmake -option(PACKAGE_TESTS "Build the tests" ON) -if(PACKAGE_TESTS) - enable_testing() - add_subdirectory(tests) +find_package(Git QUIET) +if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") +# Update submodules as needed + option(GIT_SUBMODULE "Check submodules during build" ON) + if(GIT_SUBMODULE) + message(STATUS "Submodule update") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + if(NOT GIT_SUBMOD_RESULT EQUAL "0") + message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") + endif() + endif() +endif() + +if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/repo/CMakeLists.txt" + message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") endif() ``` -As mentioned before, you have to do the `enable_testing` in your main CMakeLists. Now, in your tests directory: +The first line checks for Git using CMake's built in `FindGit.cmake`. Then, if you are in a git checkout of your source, add an option (defaulting to `ON`) that allows developers to turn off the feature if they need to. We then run the command to get all repositories, and fail if that command fails, with a nice error message. Finally, we verify that the repositories exist before continuing, regardless of the method used to obtain them. You can use `OR` to list several. + +Now, your users can be completely oblivious to the existence of the submodules, and you can still keep up good development practices! The only thing to watch out for is for developers; you will reset the submodule when you rerun CMake if you are developing inside the submodule. Just add new commits to the parent staging area, and you'll be fine. + +You can then include projects that provide good CMake support: ```cmake -add_subdirectory("${PROJECT_SOURCE_DIR}/extern/googletest" "extern/googletest") +add_subdirectory(extern/repo) ``` -If you did this in your main CMakeLists, you could use a normal add_subdirectory; the extra path here is needed to correct the build path because we are calling it from a subdirectory. +Or, you can build an interface library target yourself if it is a header only project. Or, you can use `find_package` if that is supported, probably preparing the initial search directory to be the one you've added (check the docs or the file for the `Find*.cmake` file you are using). You can also include a CMake helper file directory if you append to your `CMAKE_MODULE_PATH`, for example to add `pybind11`'s improved `FindPython*.cmake` files. -The next line is optional, but keeps your `CACHE` cleaner: + +### Bonus: Git version number + +Move this to Git section: ```cmake -mark_as_advanced( - BUILD_GMOCK BUILD_GTEST BUILD_SHARED_LIBS - gmock_build_tests gtest_build_samples gtest_build_tests - gtest_disable_pthreads gtest_force_shared_crt gtest_hide_internal_symbols -) +execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE PACKAGE_GIT_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) ``` - -If you are interested in keeping IDEs that support folders clean, I would also add these lines: - -```cmake -set_target_properties(gtest PROPERTIES FOLDER extern) -set_target_properties(gtest_main PROPERTIES FOLDER extern) -set_target_properties(gmock PROPERTIES FOLDER extern) -set_target_properties(gmock_main PROPERTIES FOLDER extern) -``` - -Then, to add a test, I'd recommend the following macro: - -```cmake -macro(package_add_test TESTNAME) - add_executable(${TESTNAME} ${ARGN}) - target_link_libraries(${TESTNAME} gtest gmock gtest_main) - add_test(${TESTNAME} ${TESTNAME}) - set_target_properties(${TESTNAME} PROPERTIES FOLDER tests) -endmacro() - -package_add_test(test1 test1.cpp) -``` - -This will allow you to quickly and simply add tests. Feel free to adjust to suit your needs. If you haven't seen it before, `ARGN` is "every argument after the listed ones". - - -[^1]: Here I've assumed that you are working on a GitHub repository by using the relative path to googletest. - - -[CLIUtils/cmake]: https://github.com/CLIUtils/cmake -[GoogleTest]: https://github.com/google/googletest -[DownloadProject]: https://github.com/Crascit/DownloadProject diff --git a/chapters/testing.md b/chapters/testing.md index a905dab..08fcd8b 100644 --- a/chapters/testing.md +++ b/chapters/testing.md @@ -8,5 +8,19 @@ In your main CMakeLists.txt you need to add the following function call (not in enable_testing() ``` +You can register targets with: + +```cmake +add_test(NAME TestName COMMAND TargetName) +``` + +If you put something else besides a target name after COMMAND, it will register as a command line to run. It would also be valid to put the generator expression: + +```cmake +add_test(NAME TestName COMMAND $) +``` + +which would use the output location (thus, the executable) of the produced target. + Look at the subchapters for recipes for popular frameworks. diff --git a/chapters/testing/catch.md b/chapters/testing/catch.md index 1dc087f..9a04188 100644 --- a/chapters/testing/catch.md +++ b/chapters/testing/catch.md @@ -1,4 +1,27 @@ # Catch +Catch and [Catch2] (C++11 only version) are powerful, idomatic testing solutions similar in philosophy to PyTest for Python. To use Catch in a CMake project, there are several options. +## Vendoring + +If you simply drop in the single include release of Catch into your project, this is what you would need to add Catch: + + +```cmake +# Prepare "Catch" library for other executables +set(CATCH_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/extern/catch) +add_library(Catch2::Catch IMPORTED INTERFACE) +set_property(Catch2::Catch PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CATCH_INCLUDE_DIR}") +``` + +Then, you would link to Catch2::Catch. This would have been okay as an INTERFACE target since you won't be exporting your tests. + + +## Direct inclusion + +If you add the library using ExternalProject, FetchContent, or git submodules, you can also `add_subdirectory` Catch (CMake 3.1+). + +Catch also provides two CMake modules that you can use to register the individual tests with CMake. + +[Catch2]: https://github.com/catchorg/Catch2 diff --git a/chapters/testing/googletest.md b/chapters/testing/googletest.md index 97a88a3..618cd25 100644 --- a/chapters/testing/googletest.md +++ b/chapters/testing/googletest.md @@ -18,13 +18,13 @@ if(PACKAGE_TESTS) endif() ``` -As mentioned before, you have to do the `enable_testing` in your main CMakeLists. Now, in your tests directory: +I would recommend using something like `PROJECT_NAME STREQUAL CMAKE_PROJECT_NAME` to set the default for the option, since this should only build by default if this is the current project. As mentioned before, you have to do the `enable_testing` in your main CMakeLists. Now, in your tests directory: ```cmake add_subdirectory("${PROJECT_SOURCE_DIR}/extern/googletest" "extern/googletest") ``` -If you did this in your main CMakeLists, you could use a normal add_subdirectory; the extra path here is needed to correct the build path because we are calling it from a subdirectory. +If you did this in your main CMakeLists, you could use a normal `add_subdirectory`; the extra path here is needed to correct the build path because we are calling it from a subdirectory. The next line is optional, but keeps your `CACHE` cleaner: @@ -85,7 +85,6 @@ add_gtest(SimpleTest) > ``` - [^1]: Here I've assumed that you are working on a GitHub repository by using the relative path to googletest.