CMake target_sources and install - cmake

I am having a hard time figuring out how to install PUBLIC headers specified in target_sources().
It appears that target_sources() is somewhat of a mystery for anything other than adding private sources to an executable.
After reading a lot of material, where especially this blog entry was helpful, I managed to understand & bypass the issue with target_sources() and PUBLIC files. A CMakeLists.txt in one of the many subdirectories of my C++ library project looks like this:
target_sources(mylib
PRIVATE
foo.cpp
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/foo.hpp>
$<INSTALL_INTERFACE:foo.hpp>
)
This allows me to successfully build the library. However, upon installation, the header file(s) listed in the PUBLIC section of target_sources() are never installed.
My installation looks like this:
install(
TARGETS
mylib
EXPORT mylib-targets
LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT lib
ARCHIVE
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT lib
RUNTIME
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT bin
INCLUDES
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mylib
However, this doesn't install any of the headers.
This stackoverflow answer mentions the use of PUBLIC_HEADER but reading the documentation doesn't give me the feeling that that is relevant in my case.
Question: What is the proper way of installing PUBLIC or INTERFACE headers using install()?
Background: My goal is to have a separate CMakeLists.txt in every subdirectory of my source tree. Each of these lists is supposed to use target_sources() to add PRIVATE and PUBLIC files to the build. All PUBLIC (and INTERFACE) sources should be installed during installation.

PUBLIC section of target_sources command has nothing common with public headers installed with PUBLIC_HEADER option of install command.
The headers you want to treat as public for your library should be listed in PUBLIC_HEADER property for the target.
Documentation for install(TARGETS) has a nice example of setting and installing private headers. In your case this would be:
# This defines `foo.hpp` located in the current source directory as a 'public header'.
set_target_properties(mylib PROPERTIES PUBLIC_HEADER foo.hpp)
# This install public headers among with the library.
install(
TARGETS
mylib
PUBLIC_HEADER
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mylib
# ... other options
)
See also that related question: How to configure CMakeLists.txt to install public headers of a shared library?.
Note also, that INCLUDES and PUBLIC_HEADERS clauses for install(TARGETS) command are different things:
INCLUDES specifies include directory for the installed library
PUBLIC_HEADERS specifies directory where all target's public headers will be copied upon installation.
E.g. if you want your header file to be used via
#include <mylib/foo.h>
then you need provide following options for install() command:
PUBLIC_HEADER
# The header will be places at ${CMAKE_INSTALL_INCLUDEDIR}/mylib/foo.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mylib
INCLUDES
# This directory will be used as include directory.
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
So <include-directory> joined (via /) with the relative path in #include<> directive will give absolute path to the header file.

Related

Why no 'install' rules generated by cmake install()?

I'm running cmake v 3.18.0 on ubuntu 20.04
My install() command doesn't generate any install rules within the generated Makfile. CMakeLists.txt looks like this:
# Static library
add_library(mbaux
STATIC
mb_cheb.c mb_delaun.c mb_intersectgrid.c mb_readwritegrd.c
mb_surface.c mb_track.c mb_truecont.c mb_zgrid.c)
target_include_directories(mbaux
PUBLIC
.
${X11_INCLUDE_DIR}
${GMT_INCLUDE_DIR}
${GDAL_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/src/mbio)
# Static library
add_library(mbxgr
STATIC
mb_xgraphics.c)
target_include_directories(mbxgr
PUBLIC
.
${X11_INCLUDE_DIR}
${GMT_INCLUDE_DIR}
${GDAL_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/src/mbio)
# Install
message("set up mbaux install targets")
install(TARGETS mbaux DESTINATION cmake-example/lib)
install(TARGETS mbxgr DESTINATION cmake-example/lib)
Yet when I look in the generated Makefile I see my message "set up mbaux install targets" but there is no reference to the destination 'cmake-example/lib', and no specific 'install' target that I can see.
Thanks!
Just because you seem new to CMake, a couple words of advice re: install rules...
First, avoid hard-coding destinations. You always want to include(GNUInstallDirs) (even on Windows). This module defines a number of standard cache variables for configuring installation destinations.
See the documentation for the full list, but the following are the most relevant here:
CMAKE_INSTALL_INCLUDEDIR - the path below the installation prefix where the include structure should exist. Typically, and by default, include.
CMAKE_INSTALL_LIBDIR - the path below the installation prefix where libraries should be stored. Typically, and by default, lib. Often overridden on multiarch systems to include the target architecture, such as lib/aarch64-linux-gnu.
CMAKE_INSTALL_DATADIR - the path below the installation prefix where package-specific data should be stored. Typically, and by default, share.
In CMake 3.14+ these are used as the default destinations for install(TARGETS). This should be all you need:
include(GNUInstallDirs)
install(
TARGETS mbaux mbxgr ...
EXPORT mb-targets
RUNTIME COMPONENT mb-runtime
LIBRARY COMPONENT mb-runtime
NAMELINK_COMPONENT mb-development
ARCHIVE COMPONENT mb-development
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
Don't forget to guard your include paths with $<BUILD_INTERFACE:...> in the main build (to allow INCLUDES DESTINATION above to overwrite them):
target_include_directories(
mbaux
PUBLIC
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src/mbio>"
)
You almost certainly do not want to use the ${<PACKAGE_NAME>_INCLUDE_DIRS} variables here, either. Instead use target_link_libraries with the relevant imported targets; this will set up include paths automatically.
target_link_libraries(mbaux PRIVATE X11::X11) # likely similar for GMT/GDAL
When you are required to specify a destination, you generally want to use the TYPE argument instead of DESTINATION. If you must use DESTINATION, use a project-specific cache variable as its argument; for example:
# will install headers to ${CMAKE_INSTALL_INCLUDEDIR}/**/*.h
# without mbio prefix, remove the trailing / to include mbio
# in the path
install(DIRECTORY "${PROJECT_SOURCE_DIR}/src/mbio/"
TYPE INCLUDE
COMPONENT mb-development
FILES_MATCHING PATTERN "*.h")
set(MB_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/cmake/mb"
CACHE STRING "Installation directory for CMake package files")
install(EXPORT mb-targets
DESTINATION "${MB_INSTALL_CMAKEDIR}"
NAMESPACE mb::
FILE mb-targets.cmake)
Finally, you should never read or set CMAKE_INSTALL_PREFIX from within the CMakeLists.txt file. Instead you should rely on relative paths (as done above) or the $<INSTALL_PREFIX> generator expression (in exceptional circumstances) to avoid making your installation script non-relocatable.
As pointed out by Stephen Newell, cmake spreads rules and logic across multiple files - and I now see where they are (in this case, cmake_install.cmake)

target_include_directories - INTERFACE doesn't export an include path

I have created a very simple cmake project for testing cmake features. The project directory contains two libraries. I would like to export MyLibA include path.
The main CMakeLists.txt:
cmake_minimum_required(VERSION 3.11)
project(TestProject)
add_subdirectory(MyLibA)
add_subdirectory(MyLibB)
MyLibA CMakeLists.txt:
add_library(MyLibA SHARED)
target_sources(MyLibA PRIVATE fileA.h fileA.cpp)
target_include_directories(MyLibA INTERFACE "${CMAKE_SOURCE_DIR}/MyLibA")
MyLibB CMakeLists.txt:
add_library(MyLibB SHARED)
target_sources(MyLibB PRIVATE fileB.h fileB.cpp)
target_link_libraries(MyLibB PRIVATE /home/user/MyProjects/CmakeTestProject/build/MyLibA/libMyLibA.so)
I have exported an include path using INTERFACE keyword but the following include in fileB.h:
#include "fileA.h"
is not found. What am I doing wrong ?
What am I doing wrong?
Several things:
Never put absolute paths in your CMakeLists.txt and always link to targets rather than library files.
# Linking to a target propagates usage requirements, like include paths.
target_link_libraries(MyLibB PRIVATE MyLibA)
CMAKE_SOURCE_DIR is not what you think. It refers always to the top-level build directory, which is a bad assumption if your project might be an add_subdirectory or FetchContent target. Your usage can be replaced by:
# Not optimal, see below.
target_include_directories(MyLibA INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
Missing $<BUILD_INTERFACE:...> on include path, if you intend to export your targets. When targets are exported, their properties are copied verbatim to the output. Not guarding the local include path with $<BUILD_INTERFACE:...> will break users of the exported target.
target_include_directories(
MyLibA
INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
)
Instead of
target_link_libraries(MyLibB PRIVATE <path/to/MyLibA/file>)
use
target_link_libraries(MyLibB PRIVATE MyLibA)
This is how CMake is intended to be used: when link with the library target, CMake automatically transforms that into the path and actually propagates all interface properties of the target.

CMake: How to build multiple projects exported for find_package() via add_subdirectory()

I have a Library Project that is exported so consumers can use it like so:
find_package(myLibrary)
target_link_libraries(theirLibrary PUBLIC myNamespace::myLibrary)
MyLibrary is the main product but it lives alongside two other projects in our repository, and the layout looks like this:
MyRepository/
MyLibrary/
CMakeLists.txt
include/ //public headers
MyLibrary/ //sources and private headers
MyDependentLibrary/ //another library project
CMakeLists.txt
etc..
MyExample //executable project
CMakeLists.txt
etc..
The dependencies for each project are like so:
MyLibrary: None
MyDependentLibrary: MyLibrary
MyExample: MyLibrary, MyDependentLibrary
MyLibrary and MyDependentLibrary are both set up with install and build directory exports to be compatible with the find_package() command. So to build everthing you:
configure/build MyLibrary
configure MyDependantLibrary setting MyLibrary_DIR when prompted, then build it
configure MyExample setting MyLibrary_DIR and MyDependentLibrary_DIR when prompted, then build it
This workflow is great, most of the time we only want to package MyLibrary without the other projects when we send to customers, but occasionally we want to give them the source for all 3 projects so they have more examples to look at.
For that reason I would love to add a top level CMakeLists.txt file that would I imagine look something like this:
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(MyCombinedProject VERSION 1.0.0 LANGUAGES CXX)
add_subdirectory(MyLibrary)
add_subdirectory(MyDependentLibrary)
add_subdirectory(MyExample)
However this doesn't work. When configuring the "combined" project, MyDependentLibrary is unable to find MyLibrary_DIR, which makes sense, as MyLibrary hasn't been built yet.
Is there a way to add an export to each of the libraries so they can be found when added in this manner in addition to the find_package()? I really don't want to move any CMake code required to build MyLibrary into the top level CMakeLists.txt, as 90% of the time it will be delivered on its own.
You may ship the sources of your library (MyLibrary) with a pseudo config file, which just provides myNamespace::myLibrary target as alias for myLibrary.
MyLibrary/CMakeLists.txt:
# Assuming you create a library
add_library(mylibrary ...)
# Install and export it
install(TARGETS mylibrary EXPORT mylibraryTargets ...)
# and install the file which describes this installation
install(EXPORT mylibraryTargets NAMESPACE myNamespace ...)
# Then you install config file for your library:
install(FILES myLibrary.cmake ...)
# Then just set `MyLibrary_DIR` variable to point into in-source version of config file.
set(MyLibrary_DIR CACHE INTERNAL "A directory with the in-source config file"
${CMAKE_CURRENT_SOURCE_DIR}/cmake
)
And write that in-source config file as follows:
MyLibrary/cmake/MyLibraryConfig.cmake:
add_library(myNamespace::mylibrary ALIAS mylibrary)
That way, find_package(MyLibrary) will work if it is issued after configuring sources of your library (add_subdirectory(MyLibrary)).

How to create a cmake libary project with config file?

I am completely stuck :/. I created a simple test "library" with two
files fruits.cpp and fruits.h.
Then I created this CMakeLists.txt (following this tutorial)
cmake_minimum_required(VERSION 3.16)
project(fruits_Lib)
add_library(fruits_Lib STATIC)
target_sources(fruits_Lib
PRIVATE
"fruits.cpp"
)
set(include_dest "include/fruits-1.0")
set(main_lib_dest "lib/fruits-1.0")
set(lib_dest "${main_lib_dest}/${CMAKE_BUILD_TYPE}")
target_include_directories(fruits_Lib
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${include_dest}>"
)
add_library(fruits::fruits ALIAS fruits_Lib)
install(TARGETS fruits_Lib EXPORT fruits DESTINATION "${main_lib_dest}")
install(FILES "include/fruits/fruits.h" DESTINATION "${include_dest}")
install(EXPORT fruits DESTINATION "${lib_dest}")
This works in that it compiles and installs etc, and I can even use
it as a add_subdirectory in a parent project.
In fact, the files that this installs are:
lib/fruits-1.0/libfruits_Lib.a
lib/fruits-1.0/Debug/fruits-debug.cmake
lib/fruits-1.0/Debug/fruits.cmake
lib/fruits-1.0/libfruits_Libd.a
lib/fruits-1.0/Release/fruits-release.cmake
lib/fruits-1.0/Release/fruits.cmake
include/fruits-1.0/fruits.h
However, when I try to do the following in the parent project:
find_package(fruits CONFIG REQUIRED)
I get the error:
CMake Error at CMakeLists.txt:21 (find_package):
Could not find a package configuration file provided by "fruits" with any
of the following names:
fruitsConfig.cmake
fruits-config.cmake
Those files are definitely not created or installed by the above CMakeLists.txt :/.
What am I doing wrong? How can I create a static library that provides such config file
so that I can use it (after installation) with a find_package(fruits CONFIG) ?
I think find_package() is for locating and using external and already installed components/libraries so you'll have to install the library first.

Import my own shared library with CMakelists

I created a shared library in CMakeLists.txt by adding:
add_library(mylib SHARED ${SourceDir})
install (TARGETS mylib DESTINATION lib)
Now I can see the file libmylib.so in the correct folder after installing, but I am not sure how can I import this into another separate project's CMakeLists.txt.
CMake allows you to not only install the library, it can also install files which can be used by another project to import all the details about the library target. This allows that imported target to be treated almost the same as if it were part of the target it is imported into.
The first step is to add additional install details to your library's project:
add_library(mylib SHARED ${SourceDir})
install(TARGETS mylib
EXPORT myproj-targets
DESTINATION lib
)
install(EXPORT myproj-targets
DESTINATION lib/myproj
)
Note the added EXPORT myproj-targets to your original install() call. This tells CMake to include your mylib target in the list of targets it will export as part of the export set myproj-targets, which the second install() then directs CMake to generate as part of the install step.
In your other project where you want to use the library, you just need to include() the file that the second of the above install() calls will create. Assuming you install to ${installBase}, the following will import the details exported by the above and make the mylib target available for direct use:
include("${installBase}/lib/cmake/myproj-targets.cmake)
add_executable(myexe exeSources.cpp)
target_link_libraries(myexe PRIVATE mylib)
You can find a reasonable explanation about how all this works from the CMake documentation for install() and this wiki article.