Build systems are a way to deploy software.
They are used to:
Configure means:
Install dependencies, then compile and install.
Doxygen (CMake)
cd /path/to/doxygen/src/ mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/opt/doxygen ../ make -j<N> (sudo) make install
GNU Scientific Library (autotools)
cd /path/to/gsl/src/ ./configure --prefix=/opt/gsl --enable-shared --disable-static make -j<N> (sudo) make install
Who else is using CMake?
The root of a project using CMake must contain a CMakeLists.txt file.
cmake_minimum_required(VERSION 3.12) # This is a comment. project(MyProject VERSION 1.0 DESCRIPTION "A very nice project" LANGUAGES CXX)
Please use a CMake version more recent than your compiler (at least ≥ 3.0).
Command names are case insensitive.
cd /path/to/src/ mkdir build && cd build cmake .. [options...] # Or: # cmake -S /path/to/src/ -B /path/to/build/ [options...]
cd /path/to/build/ make -j<N>
cd /path/to/build/ cmake /path/to/src/ -L
CMake is all about targets and properties. An executable is a target, a library is a target. Your application is built as a collection of targets depending on each other.
# Header files are optional. add_executable(my_exec my_main.cpp my_header.hpp) # Options are STATIC, SHARED (dynamic) or MODULE (plugins). add_library(my_lib STATIC my_class.cpp my_class.hpp)
Targets can be associated with various properties:
add_library(my_lib STATIC my_class.cpp my_class.hpp) target_include_directories(my_lib PUBLIC include_dir) # "PUBLIC" propagates the property to # other targets depending on "my_lib". target_link_libraries(my_lib PUBLIC another_lib) add_executable(my_exec my_main.cpp my_header.h) target_link_libraries(my_exec my_lib) target_compile_features(my_exec cxx_std_20) # Last command is equivalent to: # set_target_properties(my_exec PROPERTIES CXX_STANDARD 20) target_compile_options(my_exec PUBLIC -Wall -Wpedantic)
set(LIB_NAME "my_lib") # List items are space- or semicolon-separated. set(SRCS "my_class.cpp;my_main.cpp") set(INCLUDE_DIRS "include_one;include_two") add_library(${LIB_NAME} STATIC ${SRCS} my_class.hpp) target_include_directories(${LIB_NAME} PUBLIC ${INCLUDE_DIRS}) add_executable(my_exec my_main.cpp my_header.h) target_link_libraries(my_exec ${LIB_NAME})
Cache variables are used to interact with the command line:
# "VALUE" is just the default value. set(MY_CACHE_VARIABLE "VALUE" CACHE STRING "Description") # Boolean specialization. option(MY_OPTION "This is settable from the command line" OFF)
Then:
cmake /path/to/src/ \ -DMY_CACHE_VARIABLE="SOME_CUSTOM_VALUE" \ -DMY_OPTION=OFF
# Read. message("PATH is set to: $ENV{PATH}") # Write. set(ENV{variable_name} value)
(although it is generally a good idea to avoid them).
if("${variable}") # Or if("condition"). # else() # Undefined variables would be treated as empty strings, thus false. endif()
The following operators can be used.
Unary: NOT, TARGET, EXISTS (file), DEFINED, etc. Binary: STREQUAL, AND, OR, MATCHES (regular expression), ...
NOT
TARGET
EXISTS
DEFINED
STREQUAL
AND
OR
MATCHES
Parentheses can be used to group.
Useful for switching among different implementations or versions of any third-party library.
#ifdef USE_ARRAY std::array<double, 100> my_array; #else std::vector<double> my_array(100); #endif
How to select the correct branch?
target_compile_definitions(my_exec PRIVATE USE_ARRAY=1)
Or let the user set the desired flag:
option(WITH_ARRAY "Use std::array instead of std::vector" ON) if(WITH_ARRAY) target_compile_definitions(my_exec PRIVATE USE_ARRAY=1) endif()
print_version.hpp.in
void print_version() { std::cout << "Version number: " << @MY_PROJECT_VERSION@ << std::endl; }
CMakeLists.txt
set(MY_PROJECT_VERSION 1.2.0) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/print_version.hpp.in" "${CMAKE_CURRENT_BINARY_DIR}/print_version.hpp")
See also: #cmakedefine.
#cmakedefine
Content of variables is printed with
message("MY_VAR is: ${MY_VAR}")
Error messages can be printed with
message(FATAL_ERROR "MY_VAR has the wrong value: ${MY_VAR}")
Commands being executed are printed with
cmake /path/to/src/ -B build --trace-source=CMakeLists.txt make VERBOSE=1
If the project is organized in sub-folders:
# Options are "Release", "Debug", # "RelWithDebInfo", "MinSizeRel" set(CMAKE_BUILD_TYPE Release) set(CMAKE_CXX_COMPILER "/path/to/c++") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY lib)
CMake looks for module files FindPackage.cmake in the directories specified in CMAKE_PREFIX_PATH.
FindPackage.cmake
CMAKE_PREFIX_PATH
set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH} /path/to/modules/") # Specify "REQUIRED" if the library is mandatory. find_package(Boost 1.50 COMPONENTS filesystem graph)
If the library is not located in a system folder, often a hint can be provided:
cmake /path/to/src/ -DBOOST_ROOT=/path/to/boost/installation/
Once the library is found, proper variables are populated.
if(${Boost_FOUND}) target_include_directories(my_lib PUBLIC ${Boost_INCLUDE_DIRS}) target_link_directories(my_lib PUBLIC ${Boost_LIBRARY_DIRS}) # Old CMake versions: # link_directories(${Boost_LIBRARY_DIRS}) target_link_libraries(my_lib ${Boost_LIBRARIES}) endif()
CMake can try to compile a source and save the exit status in a local variable.
try_compile( HAVE_ZIP "${CMAKE_BINARY_DIR}/temp" "${CMAKE_SOURCE_DIR}/tests/test_zip.cpp" LINK_LIBRARIES ${ZIP_LIBRARY} CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${ZIP_INCLUDE_PATH}" "-DLINK_DIRECTORIES=${ZIP_LIB_PATH}")
See also: try_run.
try_run
CMake can run specific executables and check their exit status to determine (un)successful runs.
include(CTest) enable_testing() add_test(NAME MyTest COMMAND my_test_executable)
cmake_minimum_required(VERSION 3.12) project(ExampleProject VERSION 1.0 LANGUAGES CXX) find_package(...) find_package(...) add_subdirectory(src) add_subdirectory(apps) add_subdirectory(tests)
project/ ├── apps/ │ ├── CMakeLists.txt │ └── my_app.cpp ├── cmake/ │ └── FindSomeLib.cmake ├── doc/ │ └── Doxyfile.in ├── scripts/ │ └── do_something.sh ├── src/ │ ├── CMakeLists.txt │ └── my_lib.{hpp,cpp} ├── tests/ │ ├── CMakeLists.txt │ └── my_test.cpp ├── .gitignore ├── CMakeLists.txt ├── LICENSE.md └── README.md
exercises/07/solutions/ex1
muParserX
CMake
ex1.cpp
Re-do exercises/07/solutions/ex3 with the help of CMake.
exercises/07/solutions/ex3