From b176ef5bd76d8dc245c965f479412f2b56ed0cee Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Sat, 21 Oct 2017 18:03:43 -0400 Subject: [PATCH] Adding tests and smallinc --- SUMMARY.md | 5 +-- chapters/options.md | 8 +++- chapters/smallinc.md | 69 +++++++++++++++++++++++++++++ chapters/testing.md | 101 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 chapters/smallinc.md create mode 100644 chapters/testing.md diff --git a/SUMMARY.md b/SUMMARY.md index 658cada..e22eab6 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -7,12 +7,11 @@ ## Making a CMakeLists * [Introduction to the Basics](chapters/basics.md) -* [~~Variables and the Options Cache~~](chapters/options) +* [Variables and the Cache](chapters/options) * [C++11 and Beyond](chapters/cpp11.md) * [Adding Features](chapters/features.md) * [How to Structure Your Project](chapters/structure.md) -* [~~Configuration Information~~](chapters/configure.md) -* [~~Including Small Projects~~](chapters/smallinc.md) +* [Including Small Projects](chapters/smallinc.md) * [~~Including Large Projects~~](chapters/largeinc.md) * [~~Running Other Programs~~](chapters/programs.md) * [~~Testing~~](chapters/testing.md) diff --git a/chapters/options.md b/chapters/options.md index e85202b..bc7157c 100644 --- a/chapters/options.md +++ b/chapters/options.md @@ -26,12 +26,16 @@ mark_as_advanced(MY_CACHE_VARIABLE) The first line will cause the value to be set no matter what, and the second line will keep the variable from showing up in the list of variables if you run `cmake -L ..` or use a GUI. -Since `BOOL` is such a common variable type, you can set it more susinctly with the shortcut: +Since `BOOL` is such a common variable type, you can set it more succinctly with the shortcut: ```cmake option(MY_OPTION "This is settable from the command line" OFF) ``` - For the BOOL datatype, there are several different workings for `ON` and `OFF`. + For the `BOOL` datatype, there are several different wordings for `ON` and `OFF`. + +## The Cache + +The cache is actually just a text file, `CMakeCache.txt`, that gets created in the build directory when you run CMake. This is how CMake remembers anything you set, so you don't have to re-list your options every time you rerun CMake. [^1]: `if` statements are a bit odd in that they can take the variable with or without the surrounding syntax; this is there for historical reasons: `if` predates the `${}` syntax. diff --git a/chapters/smallinc.md b/chapters/smallinc.md new file mode 100644 index 0000000..cf4e13e --- /dev/null +++ b/chapters/smallinc.md @@ -0,0 +1,69 @@ +# Including Small Projects + +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 + +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. + diff --git a/chapters/testing.md b/chapters/testing.md new file mode 100644 index 0000000..491f3f8 --- /dev/null +++ b/chapters/testing.md @@ -0,0 +1,101 @@ +# Testing + +## General Testing Information + + +## GoogleTest +### Submodule method (preferred) + +To use this method, just checkout GoogleTest as a submodule:[^1] + +```cmake +git submodule add --branch=release-1.8.0 ../../google/googletest.git extern/googletest +``` + +Then, in your main `CMakeLists.txt`: + +```cmake +option(PACKAGE_TESTS "Build the tests" ON) +if(PACKAGE_TESTS) + enable_testing() + add_subdirectory(tests) +endif() +``` + +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. + +The next line is optional, but keeps your `CACHE` cleaner: + +```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 +) +``` + +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". + + +### Download method + +You can use the downloader in my [CMake helper repository][CLIUtils/cmake], using CMake's `include` command. + +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) +> ``` + + +## Catch + + +[^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