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 %}