2020-08-28
# [CMake Tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/)
- step by step, various topics in one example
- document & example → [/Help/guide/tutorial](https://github.com/Kitware/CMake/tree/master/Help/guide/tutorial) in source code
* result of this step is in the next step's subfolder
# 01: [Starting Point](https://cmake.org/cmake/help/latest/guide/tutorial/#a-basic-starting-point-step-1)
- minimum project requires only 3 lines in `CMakeLists.txt`
* `cmake_minimum_required(VERSION 3.10)`
* `project(Tutorial)`
* `add_executable(Tutorial tutorial.cxx)`
- lower case commands 💬 let's follow official preference, *please*...
**[Version Number](https://cmake.org/cmake/help/latest/guide/tutorial/#adding-a-version-number-and-configured-header-file)**
- **why?** more flexible in `CMakeLists.txt` than in source code
- **how?**
1. `project(Tutorial VERSION 1.0)`
specify version number like this
2. `configure_file(TutorialConfig.h.in TutorialConfig.h)`
config file will under binary tree
3. `target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")`
so as to include config file
💬 when to quote? something may contains spaces
4. create `TutorialConfig.h.in` with
+ `@Tutorial_VERSION_MAJOR@`
+ `@Tutorial_VERSION_MINOR@`
+ 💬 also [`PATCH`, `TWEAK`](https://cmake.org/cmake/help/latest/command/project.html#options)
5. access version number by including config file
**[C++ Standard](https://cmake.org/cmake/help/latest/guide/tutorial/#specify-the-c-standard)**
1. `set(CMAKE_CXX_STANDARD 11)`
2. `set(CMAKE_CXX_STANDARD_REQUIRED True)` 💬 [`CXX_STANDARD_REQUIRED`](https://cmake.org/cmake/help/latest/prop_tgt/CXX_STANDARD_REQUIRED.html)
**[Build & Test](https://cmake.org/cmake/help/latest/guide/tutorial/#build-and-test)**
1. `cmake ..`
2. `cmake --build .`
3. find and run your program
# 02: [Add Library](https://cmake.org/cmake/help/latest/guide/tutorial/#adding-a-library-step-2)
- **subdirectory**
* create `CMakeLists.txt` under *MathFunctions* folder
+ `add_library(MathFunctions mysqrt.cxx)`
* in top-level `CMakeLists.txt`
+ `add_subdirectory(MathFunctions)`
+ `target_link_libraries(Tutorial PUBLIC MathFunctions)`
+ `target_include_directories(… "${PROJECT_SOURCE_DIR}/MathFunctions")`
- **option**
* `option(USE_MYMATH "…" ON)`
* `if(USE_MYMATH)`
+ `add_subdirectory(MathFunctions)`
+ `list(APPEND EXTRA_LIBS MathFunctions)`
+ `list(APPEND EXTRA_INCS "${PROJECT_SOURCE_DIR}/MathFunctions")`
* for *Tutorial* target
+ `target_link_libraries(Tutorial PUBLIC ${EXTRA_LIBS})`
+ `target_include_directories(… "${EXTRA_INCS}")`
* `EXTRA_LIBS`/`EXTRA_INCS` are classic approach → modern approach [next step](#03-Usage-Requirement)
* in source code
+ use `#ifdef USE_MYMATH` where in need
+ add `#cmakedefine USE_MYMATH` in `TutorialConfig.h.in`
+ why not config before option?
# 03: [Usage Requirement](https://cmake.org/cmake/help/latest/guide/tutorial/#adding-usage-requirements-for-library-step-3)
- ?? 💬 `INTERFACE` -- tell consumers what should be configed to use/link the target
`target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})`
💬 use quote here?
- 💬 keyword
* `INTERFACE`: for consumer
* `PUBLIC`: `INTERFACE` + `PRIVATE`
* `PRIVATE`: for producer
- no extra variables required for *Tutorial* now
# 04: [Install & Test](https://cmake.org/cmake/help/latest/guide/tutorial/#installing-and-testing-step-4)
**[Install](https://cmake.org/cmake/help/latest/guide/tutorial/#install-rules)**
- for the library (in `MathFunctions/CMakeLists.txt`)
* `install(TARGETS MathFunctions DESTINATION lib)`
* `install(FILES MathFunctions.h DESTINATION include)`
- for the application (in top-level `CMakeLists.txt`)
* `install(TARGETS Tutorial DESTINATION bin)`
* `install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include)`
- `cmake --install` (≧ v3.15)
* < v3.15: `cmake install`
* or build *INSTALL* target from IDE
- `CMAKE_INSTALL_PREFIX`: where to install files
* `--prefix` to customize
- 💬 why/when do we install? ...locally install
**[Test](https://cmake.org/cmake/help/latest/guide/tutorial/#testing-support)**
- `enable_testing()`
- test cases
* run without crash or error code?
* is output match the regular expression?
* custom function to simplify things
- run `ctest -N` (dry run), `ctest -VV` (extra verbose) under binary tree 💬 [options](https://cmake.org/cmake/help/latest/manual/ctest.1.html#options)
* for multi-config (e.g. Visual Studio)
+ `ctest -C Debug -VV`
+ or build *RUN_TESTS* target from IDE
# 05: [System Introspection](https://cmake.org/cmake/help/latest/guide/tutorial/#adding-system-introspection-step-5)
- **why?** assume `log`/`exp` are not common (require *m* library instead)
- **how?**
1. using the module
`include(CheckSymbolExists)`
2. check symbols
`check_symbol_exists(log "math.h" HAVE_LOG)`
`check_symbol_exists(exp "math.h" HAVE_EXP)`
3. ensure variables are set before config
`#cmakedefine HAVE_LOG`
`#cmakedefine HAVE_EXP`
4. use `HAVE_LOG`/`HAVE_EXP` where in need
5. remember to include `TutorialConfig.h`
`target_include_directories(MathFunctions … PRIVATE ${CMAKE_BINARY_DIR})`
**[Compile Definition](https://cmake.org/cmake/help/latest/guide/tutorial/#specify-compile-definition)**
- **why?** define `HAVE_LOG`/`HAVE_EXP` without `TutorialConfig.h`
- **how?**
1. remove defines from `TutorialConfig.h`
+ no more inclusion and include path as well
2. check symbols directly in `MathFunctions/CMakeLists.txt`
3. `if(HAVE_LOG AND HAVE_EXP)`, specify private definitions
`target_compile_definitions(MathFunctions PRIVATE "HAVE_LOG" "HAVE_EXP")`
💬 is quote required?
# 06: [Custom Command & Generated File](https://cmake.org/cmake/help/latest/guide/tutorial/#adding-a-custom-command-and-generated-file-step-6)
- **why?** look up the precomputed table that generated while compile time
- **how?**
1. not to check for `log`/`exp` anymore, and remove `HAVE_LOG`/`HAVE_EXP`
+ also remove `#include <cmath>`
2. build the generator
`add_executable(MakeTable MakeTable.cxx)`
3. generate the table
`add_custom_command(OUTPUT …Table.h COMMAND MakeTable …Table.h DEPENDS MakeTable)`
4. specify that `mysqrt.cxx` depends on `Table.h`
`add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h)`
5. ensure `Table.h` can be included
`target_include_directories(MathFunctions … PRIVATE ${CMAKE_CURRENT_BINARY_DIR})`
6. use the generated table
7. see how `cmake` build this project
# 07: [Installer](https://cmake.org/cmake/help/latest/guide/tutorial/#building-an-installer-step-7)
- **why?** distribute our project to others
* provide binary and source for different platforms
* differ from [step 04](#04-Install-amp-Test): package management
- **how?**
1. `include(InstallRequiredSystemLibraries)` to include required runtime libraries
2. set the variables (license file, version) then `include(CPack)`
3. build the project as usual
4. distribute binary: `cpack`
+ `-G`: generator
+ `-c`: configuration
5. distribute source: `cpack --config CPackSourceConfig.cmake`
6. alternatively,
+ `make package`
+ or build *Package* target from IDE
# 08: [Dashboard](https://cmake.org/cmake/help/latest/guide/tutorial/#adding-support-for-a-dashboard-step-8)
- **why?** for [something like this](https://my.cdash.org/index.php?project=CMakeTutorial)
- **how?**
1. take `include(CTest)` instead of `enable_testing()`
2. create `CTestConfig.cmake` at top-level directory, and specify variables
3. run `ctest -VV -D Experimental` under binary tree
# 09: [Static & Shared](https://cmake.org/cmake/help/latest/guide/tutorial/#mixing-static-and-shared-step-9)
- **why?** let `BUILD_SHARED_LIBS` make `add_library()` become `SHARED` (if no type specified)
- **refactor** for encapsulation
1. move `USE_MYMATH` option to `MathFunctions`
2. `MathFunctions` (static) links to `SqrtLibrary` if `USE_MYMATH`
3. namespace
4. *Tutorial* always use *MathFunctions* (no `#include <cmath>` required)
- **how?**
1. `option(BUILD_SHARED_LIBS "…" ON)`
usually let user decide
2. define `DECLSPEC` with `target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")`
3. `POSITION_INDEPENDENT_CODE` 💬 wiki: [Position Independent Code](https://en.wikipedia.org/wiki/Position-independent_code)
4. `set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")`
# 10: [Generator Expression](https://cmake.org/cmake/help/latest/guide/tutorial/#adding-generator-expressions-step-10)
- **why?** do something conditional
- logical expression
* `$<0:…>` → empty string
* `$<1:…>` → content of "…"
* can be nested
- **example:** `INTERFACE` target to add compiler flags
* `$<COMPILE_LANG_AND_ID:language,compiler_ids>` -- different flags for gcc/msvc
* `$<BUILD_INTERFACE:…>` -- not to apply on the consumers after installed
# 11: [Export Config](https://cmake.org/cmake/help/latest/guide/tutorial/#adding-export-configuration-step-11)
- **why?** make our project relocatable
* to be used from 1) build directory, 2) local install, or 3) when packaged
- **how?**
1. have CMake file to import all installed targets
`install(TARGETS … EXPORT MathFunctionsTargets)`
2. explicitly install this generated file
`install(EXPORT … FILE MathFunctionsTargets.cmake DESTINATION …)`
3. (try and get an error) have a machine independent path
`target_include_directories(MathFunctions INTERFACE … $<install_interface:include>)`
4. have CMake file for `find_package()` by [CMakePackageConfigHelpers](https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html)
* prepare `Config.cmake.in`
* `include(CMakePackageConfigHelpers)`
* `configure_package_config_file(…)` for `MathFunctionsConfig.cmake`
* `write_basic_package_version_file(…)` for `MathFunctionsConfigVersion.cmake`
5. used from build directory (also export one for ourself)
`export(EXPORT MathFunctionsTargets FILE "${CMAKE_CURRENT_BINSRY_DIR}/MathFunctionsTargets.cmake")`
# 12: [Package Debug+Release](https://cmake.org/cmake/help/latest/guide/tutorial/#packaging-debug-and-release-step-12)
- **why?** make a package with multiple configs
* for single-config generators
- **how?**
1. different names for different configs -- postfix *d* for debug
`set(CMAKE_DEBUG_POSTFIX d)`
2. apply debug postfix on executable
`set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})`
3. add version numbers
`set_property(TARGET MathFunctions PROPERTY VERSION "1.0.0")`
`set_property(TARGET MathFunctions PROPERTY SOVERSION "1")`
4. create *debug* and *release* sub-directories, and build both 2 configs
5. customize `MultiCPackConfig.cmake` to pack 2 builds into one
* include default config file
* use `CPACK_INSTALL_CMAKE_PROJECTS` variable
6. `cpack --config MultiCPackConfig.cmake`
{%hackmd @yipo/style %}