diff --git a/.gitignore b/.gitignore index 4db0a5a30d39fc71365ae434455906339e839d3a..6fe82b4d7f6c460e5d300c48c4b908b873824acc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *build* +*install* *idea* *.so *.a diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fe735161de552fb33119df9519bddf894884209a..15fc608bbdf4f2ed47a2464f4006af4151bc4477 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,8 +3,10 @@ image: gitlab-registry.mpcdf.mpg.de/mpcdf/module-image check-recipes: before_script: - module purge - - module load cmake/3.22 doxygen git + - module load cmake/3.24 doxygen git - module load intel/21.5.0 + - module load gsl + - module list - cmake --version variables: @@ -48,6 +50,18 @@ check-recipes: - cmake --build 08_build - ctest --test-dir 08_build || FAILED=true + - cmake -S 09_install/external -B 09_install/external-build -DBUILD_SHARED_LIBS=ON + - cmake --build 09_install/external-build + - cmake --install 09_install/external-build --prefix 09_install/external-install + + - export add_ROOT=$(pwd)/09_install/external-install + - cmake -S 09_install/internal -B 09_install/internal-build -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON -DCMAKE_INSTALL_RPATH=$(pwd)/09_install/internal-install/lib64 + - cmake --build 09_install/internal-build + - ./09_install/internal-build/app + - cmake --install 09_install/internal-build --prefix 09_install/internal-install + - ldd 09_install/internal-install/bin/app + - ./09_install/internal-install/bin/app + - cmake --preset dev -S 10_presets -B 10_dev - cmake --build 10_dev - ./10_dev/app diff --git a/09_install/README.md b/09_install/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8fd68f1930e0ca63deb8a9f652d7e73ff536856b --- /dev/null +++ b/09_install/README.md @@ -0,0 +1,37 @@ +# How to install a project with CMake? + +This recipe consists of two folders. `external` resembles some third-party library. `internal` is your project +consisting of a library and a frontend executable. + +## How to define your targets to facilitate installation? +Since CMake 3.23 supports [FILE_SET](https://cmake.org/cmake/help/latest/command/target_sources.html#command:target_sources). +If you define your header files within the `HEADERS` file set. They will be automatically installed into the include +directory. If you use a lower CMake version you need to take care to install the headers yourself. + +## Install the files +All targets that need to be installed need an [install](https://cmake.org/cmake/help/latest/command/install.html#targets) +statement. For installing files manually (for example, header files if you do not use file sets) you can use a different +version of the [install](https://cmake.org/cmake/help/latest/command/install.html#files) command. With these install commands +you can already install your library and application. However, if someone wants to include your library they need +to manually link or write a find script. CMake has the concept of config files to solve this problem. + +## How to create config files? +CMake can automatically generate files that import your targets. For this you can use another version of the +[install](https://cmake.org/cmake/help/latest/command/install.html#export) command. If you have external dependencies, +you have to make sure they are available when someone links against your library. This is done using +[find_dependency](https://cmake.org/cmake/help/latest/module/CMakeFindDependencyMacro.html). You can see how all of this +looks like in the external subfolder. + +## Installing into non-standard locations and linking against dependencies in non-standard locations. +A design decision of CMake is to clear all `rpath`s when you install executables or shared libraries. The idea behind this +is everything gets installed into standard locations and can be found at runtime. It also makes sure you do not link against +libraries in your build folder. This is bad practice as one should be able to delete the build folder after installation. + +However, in some situations this behaviour is unwanted. For example, if you link against libraries installed in +non-standard locations. In this case you will not find the libraries at runtime. CMake can keep all `rpath`s pointing +into locations that are not within your build folder. This is achieved by +[CMAKE_INSTALL_RPATH_USE_LINK_PATH](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_RPATH_USE_LINK_PATH.html). + +Unfortunately your installation might still fail if you have a local library in your build folder and install into +a non-standard location. Then this library will not be found at runtime. You can solve this by manually specifying your +installation directory as a rpath. [CMAKE_INSTALL_RPATH](https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_RPATH.html) \ No newline at end of file diff --git a/09_install/external/CMakeLists.txt b/09_install/external/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..0631a40e8716aec3ce12ba6b6f82d4e308b363d8 --- /dev/null +++ b/09_install/external/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.23) + +project(09_external + LANGUAGES CXX) + +# create a simple add library +add_library(add) + +# It is good practice to put everything into namespaces to avoid name collisions. +# A consistent view for internal as well as external projects can be achieved by +# providing an alias target. This target should be equivalent with how a find script +# imports the library. +add_library(add::add ALIAS add) + +# add the source files to the library +target_sources(add + PRIVATE add/add.cpp + PUBLIC FILE_SET HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR} + FILES add/add.hpp) + +# install the library, i.e. copying all the files +install( + TARGETS add + EXPORT addTargets + FILE_SET HEADERS) + +# generate a config file that can be used by find_package +install( + EXPORT addTargets + FILE addConfig.cmake + NAMESPACE add:: + DESTINATION lib/cmake/add) + diff --git a/09_install/external/add/add.cpp b/09_install/external/add/add.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2998a6cfa5287dd5be6804d9f6f959704c4a8dd8 --- /dev/null +++ b/09_install/external/add/add.cpp @@ -0,0 +1,3 @@ +#include "add.hpp" + +double add(const double& lhs, const double& rhs) { return lhs + rhs; } diff --git a/09_install/external/add/add.hpp b/09_install/external/add/add.hpp new file mode 100644 index 0000000000000000000000000000000000000000..333800d08697bcbb79c3be4cd79d3db26673b745 --- /dev/null +++ b/09_install/external/add/add.hpp @@ -0,0 +1,3 @@ +#pragma once + +double add(const double& lhs, const double& rhs); diff --git a/09_install/internal/CMakeLists.txt b/09_install/internal/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..0eef3a8854d591ba698548e675b7c15b07b3dfc0 --- /dev/null +++ b/09_install/internal/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 3.23) + +project(09_library + LANGUAGES CXX) + +# make cache variables for install destinations +include(GNUInstallDirs) + +# create a simple math library +add_library(math) + +# It is good practice to put everything into namespaces to avoid name collisions. +# A consistent view for internal as well as external projects can be achieved by +# providing an alias target. This target should be equivalent with how a find script +# imports the library. +add_library(math::math ALIAS math) + +# add the source files to the library +target_sources(math + PRIVATE math/math.cpp + PUBLIC FILE_SET HEADERS + BASE_DIRS ${PROJECT_SOURCE_DIR} + FILES math/math.hpp) + +find_package(GSL REQUIRED) +target_link_libraries(math PRIVATE GSL::gsl) + +# install the library, i.e. copying all the files +install( + TARGETS math + EXPORT mathTargets + FILE_SET HEADERS) + +# generate a config file that can be used by find_package +install( + EXPORT mathTargets + FILE mathTargets.cmake + NAMESPACE math:: + DESTINATION lib/cmake/math) + +configure_file(math/mathConfig.cmake.in mathConfig.cmake @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/mathConfig.cmake" + DESTINATION lib/cmake/math + ) + +# ================================================================================= + +# add an internal executable using this library. +add_executable(app app/main.cpp) +target_link_libraries(app PRIVATE math::math) + +find_package(add REQUIRED) +target_link_libraries(app PRIVATE add::add) + +# No need for exports since this executable is not intended to be included in +# another project. +install(TARGETS app) + diff --git a/09_install/internal/app/main.cpp b/09_install/internal/app/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1f84ca1425fed9f4024018d1a81e698911732a3d --- /dev/null +++ b/09_install/internal/app/main.cpp @@ -0,0 +1,9 @@ +#include <math/math.hpp> +#include <add/add.hpp> +#include <iostream> + +int main() +{ + std::cout << bessel(5.0) << std::endl; + std::cout << add(3, 4) << std::endl; +} diff --git a/09_install/internal/math/math.cpp b/09_install/internal/math/math.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7fc03f3043b5ffb4332a0fbc10a8a8d66e1af462 --- /dev/null +++ b/09_install/internal/math/math.cpp @@ -0,0 +1,3 @@ +#include <gsl/gsl_sf_bessel.h> + +double bessel(const double& x) { return gsl_sf_bessel_J0(x); } diff --git a/09_install/internal/math/math.hpp b/09_install/internal/math/math.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0b335a1bd9ea0ad7a04518e5be633ed692069ad3 --- /dev/null +++ b/09_install/internal/math/math.hpp @@ -0,0 +1,3 @@ +#pragma once + +double bessel(const double& x); diff --git a/09_install/internal/math/mathConfig.cmake.in b/09_install/internal/math/mathConfig.cmake.in new file mode 100644 index 0000000000000000000000000000000000000000..d6280264825d8936fd1c574c61d0a9998f3572de --- /dev/null +++ b/09_install/internal/math/mathConfig.cmake.in @@ -0,0 +1,7 @@ +include(CMakeFindDependencyMacro) + +# Same syntax as find_package +find_dependency(GSL) + +# Add the targets file +include("${CMAKE_CURRENT_LIST_DIR}/mathTargets.cmake") diff --git a/README.md b/README.md index 58c37df63eec6aff0c07c3f6d488196ed7d2d7a7..fdcaede61f2690becf483c463dd7d4ffbcffcb3d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ we run them in our CI. * [How to get the current git hash at configure time?](06_git_configure) * [How to get the current git hash at build time?](07_git_build) * [How to manage all the tests in your project?](08_ctest) + * [How to install a project with CMake?](09_install) * [How to add compilation configurations to your project?](10_presets) ## Why CMake?