Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to configure CMakeLists.txt to install public headers of a shared library?

Tags:

c++

cmake

I want to use cmake to install my library edv but when I execute:

cmake --build . --target install

It installs but it only creates the bin/edv.dll and lib/ < empty >. How can I make cmake to install the EDV_PUBLIC_INCLUDE_DIRECTORIES inside an include/... ?

Here's my CMakeLists.txt:

cmake_minimum_required(VERSION 3.12)

project(edv)

# include PUBLIC directories
set(EDV_PUBLIC_INCLUDE_DIRECTORIES      include/ )

set(EDV_PRIVATE_INCLUDE_DIRECTORIES     src/   )

# Edv source files list
file(GLOB_RECURSE EDV_SOURCE_FILES "src/*.cpp" "src/*.hpp*")


# build the library
add_library(${PROJECT_NAME} SHARED ${EDV_SOURCE_FILES} )

target_include_directories(${PROJECT_NAME} PUBLIC ${EDV_PUBLIC_INCLUDE_DIRECTORIES})
target_include_directories(${PROJECT_NAME} PRIVATE ${EDV_PRIVATE_INCLUDE_DIRECTORIES})

install (TARGETS ${PROJECT_NAME}
    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    PUBLIC_HEADER DESTINATION include
)
like image 857
FrameBuffer Avatar asked Jan 19 '19 22:01

FrameBuffer


People also ask

How do I add a header file to Cmakelist?

To include headers in CMake targets, use the command target_include_directories(...) . Depending on the purpose of the included directories, you will need to define the scope specifier – either PUBLIC , PRIVATE or INTERFACE .

How do I create a header only library in CMake?

How to make a header-only library with cmake? Like this: add_library(project INTERFACE) target_include_directories(project INTERFACE .) I found that in the target_link_libraries if the header only library is INTERFACE then when building the project cmake will not detect the header files of the library.

What is Cmake_install CMake file?

As previous answer tells, the cmake_install. cmake contains the commands generated by install command from your CMakeLists. txt . You can execute it by cmake -P cmake_install. cmake and it performs the installation of your project even on windows.


2 Answers

The Cmake install documentation has almost the same proposal as ComicSansMS.

The following example shows how to follow this advice (mentioned earlier in the documentation) while installing headers to a project-specific subdirectory:

include(GNUInstallDirs)
install(FILES mylib.h
        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/myproj
)

In this solution, the destination is not hard coded, but uses variable defined by the GNUInstallDirs Cmake module (and adds the subdirectory after a slash). Using variables make things more flexible for package maintainers and the default value of the variable may be system specific. (May not specific for CMAKE_INSTALL_INCLUDEDIR but definitely system specific for CMAKE_INSTALL_LIBDIR.) Here is a good explanation from Craig Scott from CPPCon 2019 - Deep CMake for Library Authors

Or even better, if one has a sub-folder within include, then that whole directory can be installed as well:

include(GNUInstallDirs)
install(DIRECTORY include/myproj
        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
like image 20
Dudly01 Avatar answered Sep 21 '22 15:09

Dudly01


CMake cannot deduce the set of header files to be installed from the target. This makes sense, as the target may contain both private and public header files, but CMake does not differentiate between those. As a consequence, you have to list the header files explicitly in an INSTALL(FILES ...) command:

install(FILES ${MY_HEADER_FILES} DESTINATION include)

Update for CMake 3.23 and later:

CMake version 3.23 introduced File Sets, which allow handling header files in a more elegant way. This way header files only need to be listed once in the CMakeLists.txt to get both integrated in the IDE and also installed correctly:

cmake_minimum_required(VERSION 3.23)

# example library with sources and headers located at ${MY_LIB_SOURCE_DIR}
add_library(my_lib)
target_include_directories(my_lib PUBLIC ${MY_LIB_SOURCE_DIR})

# source files get added as before
target_sources(my_lib PUBLIC ${MY_LIB_SOURCE_DIR}/my_lib/my_lib.cpp)

# header files get added via FILE_SET
# BASE_DIR should point to the same directory that is used as the 
# target_include_directory, otherwise the install paths may be off.
target_sources(my_lib PUBLIC FILE_SET HEADERS
  BASE_DIRS ${MY_LIB_SOURCE_DIR}
  FILES ${MY_LIB_SOURCE_DIR}/my_lib/my_lib.hpp)

# install command needs to reference the file set again
install(TARGETS my_lib FILE_SET HEADERS)

End of 3.23 update

The PUBLIC_HEADER field that you stumbled upon is related to the OSX framework mechanism, which is its very own can of worms. I suggest you stay clear of it, unless you actually want to deploy your library as a .framework on OSX.

Take special note of the INCLUDES DESTINATION option to the INSTALL(TARGET ...) command. While this does not actually copy any files itself, it allows the include directory to be added to the imported target provided by the config package script automatically. If you intend to provide a package config script to your users (which you probably should, at least if you expect your users to also use CMake), you probably want to set this option.

Since the install mechanism is quite complicated overall, I have a small github project of mine where you can observe all the elements in action.

like image 65
ComicSansMS Avatar answered Sep 20 '22 15:09

ComicSansMS