Introduction
We write CMake for our library and it compiles correctly. However, a user of the library needs to write some CMake code to find the include headers, shared/static libraries, and executables. This extra task for the user of our library can be a pain. We as the developers of the library can add several boilerplate lines to our CMake script to make everything get imported by the user with a simple line of find_package()
.
In this post, with an example, I show how to create
- a version file and
- a config file for a library so it can be easily imported to other projects.
Prerequisites
This post is the third on CMake, I assume you had a look at the previous ones : CMake programming and build with CMake.
Example
The example code is on GitHub here. It is a shared library with the file structure below:
--- geometry
|
---- shape
| |
| --- shape.h, shape.cpp
| |
| --- CMakeLists.txt
|
---- square
| |
| --- square.h, square.cpp, info.h
| |
| --- CMakeLists.txt
|
---- example
| |
| --- app.cpp
|
---- CMakeLists.txt
CMakeLists.txt
looks like this:
cmake_minimum_required(VERSION 3.23)
project(geometry LANGUAGES CXX VERSION 5.4.3)
if (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
add_library(geo SHARED)
target_include_directories(geo PRIVATE "${PROJECT_SOURCE_DIR}")
add_subdirectory("shape")
add_subdirectory("square")
install(TARGETS geo
EXPORT geoTargets
FILE_SET HEADERS
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include)
install(EXPORT geoTargets
FILE geoTargets.cmake
NAMESPACE geo::
DESTINATION lib/cmake/geo)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"geoConfigVersion.cmake"
VERSION ${geo_VERSION}
COMPATIBILITY AnyNewerVersion)
install(FILES "geoConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/geoConfigVersion.cmake"
DESTINATION lib/cmake/geo)
The targets and dependencies in this example are explained in the previous post on CMake. So I continue from there.
install(TARGETS geo EXPORT geoTargets ...)
: here EXPORT defines geoTargets variable. Somehow it says that if you need to export targets of geo, work with geoTargets.install(EXPORT geoTargets)
: This is the mate of the previous install command, we define the name export target file, namespace, and where this file will be installed. The namespace is a prefix added to name of the package when it is loaded in other projects. So geo target will be seen asgeo::geo
in other projects.include(CMakePackageConfigHelpers)
: This is a module loaded by CMake to create a config file.write_basic_package_version_file()
: This function createsgeoConfigVersion.cmake
file. It stores version information of the package and its compatibility.AnyNewerVersion
means CMake imports the package if its version is newer or the same version as the requested one. For more details, see CMake Manual.install(FILES...)
: to copy the config file and version file into the installation directory. The version file is created by this script. ButgeoConfig.cmake
is written by us.
geometry/geoConfig.cmake
is
include(CMakeFindDependencyMacro)
# find_dependency(xxx 2.0)
include(${CMAKE_CURRENT_LIST_DIR}/geoTargets.cmake)
I commented find_dependency()
line because this example doesn’t have any other dependency. If your package has any dependency, they need to be mentioned here.
That’s it.
Build and Install
Now we can compile and install the library. In the source directory in a terminal, run
mkdir build
cd build
For single configuration generators like make or Ninja
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
cmake --install . --prefix ../../geo-install
For a multi-configuration generator like VS C++ or Xcode:
cmake ..
cmake --build . --config release
cmake --install . --prefix ../../geo-install --config release
Find the package in another project
I created another example (see the code on GitHub), mesh project, which uses geo library. The file structure of it is like this:
--- mesh
|
---- app.cpp
|
---- CMakeLists.txt
app.cpp is
#include "square/square.h"
#include<iostream>
int main() {
Square s;
s.WriteInfo();
PrintShape(s);
return 0;
}
So I am using a header of the library and some implementations.
mesh/CMakeLists.txt file is
cmake_minimum_required(VERSION 3.23)
project(mesh LANGUAGES CXX)
set(geo_DIR "../geo-install/lib/cmake/geo")
find_package(geo)
add_executable(app)
target_sources(app PRIVATE "app.cpp")
target_link_libraries(app PRIVATE geo::geo)
if(MSVC)
get_target_property(geo_dll geo::geo IMPORTED_LOCATION_RELEASE)
add_custom_command(TARGET app POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy
${geo_dll}
${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG> )
endif()
The key lines for importing geo library are
set(geo_DIR ...)
: (nameOfpackage)_DIR is where CMake looks for a package. This is the address of the installed config files.find_package(geo)
: asking CMake to include the package and its targets.target_link_libraries
: remember to use the namespace you set for your package, here geo::geo.if(MSVC) ...
: this block is only for Windows to copy dll files from the package installed directory to the executable directory. This is because on Windows, the location of shared libraries, dll files, is not coded into the executable that uses them.get_target_property(geo_dll ...)
: sets geo_dll to the path to the binary file (dll) of installed geo.add_custom_command
: I used it here for copying a file after build. You can run any customized task with this command, read CMake Manual here.$<CONFIG>
: will be defined as Debug, Release or so on depending on the cofiguration at build time.
Now you can compile and run your program, in the source directory
mkdir build
cmake ..
cmake --build .
Debug/app.exe
More on CMake
In case you haven’t seen them, my previous posts on CMake are:
Even more
Designing a big project needs a good understanding of namespaces, see my post on how namespaces are used in big projects.
Did you know CMake is supported by Visual Studio code? have a look at my essential list of VS code extensions for C++.
Latest Posts
- A C++ MPI code for 2D unstructured halo exchange
- Essential bash customizations: prompt, ls, aliases, and history date
- Script to copy a directory path in memory in Bash terminal
- What is the difference between .bashrc, .bash_profile, and .profile?
- A C++ MPI code for 2D arbitrary-thickness halo exchange with sparse blocks