>> Home / tutorial / cpp, software-tools
∵ Mari-chan ∴ 2023-11-22 ∞ 10'
This is part of a series where I write down things I know or I've learned while trying to keep myself sharp in various subjects that I don't use so often anymore.
First, you might be confused about Make and CMAKE, like I was when I learned about both. Make is a much simpler system that’s not cross platform compatible. CMAKE is also just a better system at handling complex build systems, it also contains robust mechanisms to locate libraries and executables. CMake is primarily a build system generator, not a build tool itself. It sets up the environment for building but delegates the actual compilation and installation to native build tools (like Make).
List all of the commands and a short description about them organized by phase they should be added in your CMake file:
project
: Sets the name and version of the project. This command also sets useful variables like PROJECT_NAME
, PROJECT_VERSION
, and more.
cmake_minimum_required
: Sets the minimum required version of CMake for a project, ensuring compatibility and proper interpretation of the CMake scripts.
**target_compile_features**
: You will also need to set the language version you’re using.
target_compile_features(particles PUBLIC cxx_std_11)
set
: Used to define and set CMake variables that control various aspects of the build process.option
: Introduces a boolean option that the user can control from the command line. It's useful for conditional compilation or including optional features.include_directories
: Is used to add directory paths to the list of directories that the compiler will search for include files during the compilation process.
cmake -I path/to/dir
. It simply adds a directory for the CMAKE to bundle header files in there with source files.find_package
: Searches for and locates external libraries or packages, ensuring that the necessary dependencies are included in the build process. It's key for managing external dependencies.
find_package
function you can just write a normal CMAKE file in which you. First you add the path to library, then you find the library itself, name it, then you include and handle any dependencies this library has (and add them to the CMAKE file) and then you can finally create a library with the the add_library
function.find_path(MY_LIB_INCLUDE_DIR MyLib.h)
find_library(MY_LIB_LIBRARY NAMES mylib)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLib DEFAULT_MSG MY_LIB_INCLUDE_DIR MY_LIB_LIBRARY)
if(MY_LIB_FOUND)
add_library(MyLib::MyLib UNKNOWN IMPORTED)
set_target_properties(MyLib::MyLib PROPERTIES
IMPORTED_LOCATION "${MY_LIB_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${MY_LIB_INCLUDE_DIR}")
endif()
include
: the primary functionality of include
is to include other CMAKE scripts into the code.find_package
every time, if the library is simple enough you can just add a few lines of code into your current CMAKE. In the following case we find the library and link it together with our executable target_link_libraries
.include_directories("/path/to/library/include")
link_directories("/path/to/library/lib")
add_executable(my_app main.cpp)
target_link_libraries(my_app libname)
REQUIRED
key word: will throw an error if package not foundMODULE
key word: find_package
searches for pkgs in two ways: through modules and through config. The config way it expects a FooConfig.cmake
file that will look for a proper cmake configuration file and the module way is the usual way that packages that do not support CMAKE natively use and you have written it yourself something like Foo.cmake
CONFIG
: as explained in the previous point will look for a config file, which are simpler. Usually they can be automatically configured, for example.CONFIG
nor MODULE
keywords are available than that means that CMAKE will try to look for pkgs both ways.**add_executable**
: creates an executable from the specified source files. This executable is the final binary program that runs on a computer. It typically defines the main entry point for a program. In a build system you can have multiple targets, the add_executable
specifically identifies one of the targets (libs, modules, tests…) as the executable, making it clear which component is the final runnable output. If you have different executables for different operational systems then you have it indicated through flags:
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# Check the operating system
if(WIN32)
# Windows-specific build
add_executable(MyExecutable_win main_windows.cpp)
# Additional Windows-specific configuration here
elseif(APPLE)
# macOS-specific build
add_executable(MyExecutable_mac main_macos.cpp)
# Additional macOS-specific configuration here
elseif(UNIX AND NOT APPLE)
# Linux/Unix-specific build
add_executable(MyExecutable_unix main_unix.cpp)
# Additional Unix-specific configuration here
else()
message(FATAL_ERROR "Unsupported operating system")
endif()
# Common configuration that applies to all executables
# ...
**add_library**
: this builds a library from the source code. A library is a collection of pre-compilled code that can be reused in various program. This function will typically generate either static or shared(dynamic) libraries.
target_link_libraries
: is used to specify which libraries an executable target or another library should link against during the linking phase of the build process. When building an executable or a library that depends on other libraries, you need to link these libraries to your target. target_link_libraries
is used for this purpose. It ensures that the linker knows which libraries to link to your target during the build process.
target_link_libraries(<target> <PRIVATE|PUBLIC|INTERFACE> <item>...)
PUBLIC
or INTERFACE
flags it will retroactively deal with the dependencies for youPRIVATE
, PUBLIC
, and INTERFACE
flags relate to how you want to manage the visibility of the propagation of link dependencies and include paths in the project.
PRIVATE
: The linked libraries are used by the target, but their link dependencies and include paths are not propagated to other targets.PUBLIC
: They are propagated to the projects that are using this libraryINTERFACE
: Is meant for when you’re not compiling the target into a library but you still want the libraries that connect to it to be able to find and inherit its dependencies.**set_target_properties**
: Can configure how the target (typically an executable or a shared library) finds its dependencies at runtime. It uses the RPATH
variable for that. But it’s also used to a myriad of things: set properties can control various aspects of how the target is built and linked, such as compiler options, definitions, include directories, and more.
set_target_properties(my_target PROPERTIES CXX_STANDARD 17)
set_target_properties(my_target PROPERTIES COMPILE_DEFINITIONS "DEBUG=1")
set_target_properties(my_target PROPERTIES INCLUDE_DIRECTORIES "/path/to/includes")
set_target_properties(my_target PROPERTIES COMPILE_OPTIONS "-Wall;-Wextra;-O2")
set_target_properties(my_target PROPERTIES OUTPUT_NAME "my_custom_name")
set_target_properties(my_target PROPERTIES LINK_LIBRARIES "lib1;lib2")
target_compile_definitions
and target_compile_options
: These commands add preprocessor definitions and compile options to a target, respectively. They offer fine-grained control over the compilation process. But they’re kind of like set_target_properties
.
target_include_directories
: The main difference between include_directories
and target_include_directories
is that the first one has a global scope, while the later sets the scope for a specific target. This should be used in more complex projects in which you have several targets with different include requirements.
add_custom_command
and add_custom_target
: These commands are used to add custom build steps and targets, allowing the execution of custom commands during the build process.
add_custom_target(
generate_docs
COMMAND doxygen Doxyfile
COMMENT "Generating documentation with Doxygen"
)
make generate_docs
and this piece of code will run.ExternalProject_Add
: Adds a step to download, update, and build an external project. It's useful for integrating external code bases that are not part of the main project.
ExternalProject_Add(name
[GIT_REPOSITORY <git_repo_url>]
[GIT_TAG <git_tag>]
[SOURCE_DIR <source_dir>]
[BINARY_DIR <binary_dir>]
[CONFIGURE_COMMAND <command>]
[BUILD_COMMAND <command>]
[INSTALL_COMMAND <command>]
[...other options...]
)
add_subdirectory
: Adds a subdirectory to the build. This is essential for structuring larger projects with multiple components or libraries.
configure_file
: Copies and processes a file, replacing variable references with their current values. Useful for generating configuration headers and similar tasks.
file
: Provides various file manipulation capabilities, such as generating a list of source files, copying files, etc.
install
: Specifies how and where files should be installed on the system when you run the make install
command (or the installation step of your chosen build system).
# Create an executable target
add_executable(my_app src/main.cpp)
# Define where to install the executable
install(TARGETS my_app DESTINATION bin)
# Create a library target
add_library(my_lib SHARED src/my_lib.cpp)
# Define where to install the library
install(TARGETS my_lib LIBRARY DESTINATION lib)
# Install header files
install(DIRECTORY include/ DESTINATION include)
DESTINATION
). The destination is usually relative to the installation prefix, which is typically /usr/local
on Unix systems but can be set to a different value.LIBRARY
is for shared libraries, RUNTIME
for executables and DLLs, and ARCHIVE
for static libraries.enable_testing
and add_test
: These commands are used to enable and add tests to a project. They integrate with CTest for managing test suites.CPack
: CMake's packaging system, which is used to create installers and distributable packages for a project.
**macro
vs function
**: macros don’t have a defined scope, while functions do.message
: Generates a message during the CMake run. Useful for debugging or providing build-time information.