73

I'm getting into CMAKE usage with C and actually I'm creating two very small static libraries.

My goal is:

  1. The libraries are compiled and linked into *.a files. [THIS WORKS]
  2. Then I wish to copy that *.a files into /usr/local/lib [THIS ALSO WORKS]
  3. As far as I know about libraries (very little), they are linked using -lnameoflib, which is a compiler flag. OK. I have prepared my CMakeLists.txt and it actually copies *.a files into /usr/local/lib. However, to be able to use them in a program, I also need to copy their header files into /usr/local/include, then I can include them the easy way #include <mylibheader.h>. That's how I understand it now.

And my question is - how is the proper way of copying header files into /usr/include folder with CMAKE? I would like it to copy them automatically when make install is executed, like *.a files are.

For both of the libraries I have a smiliar CMakeLists.txt:

project(programming-network)

add_library(programming-network STATIC
    send_string.c
    recv_line.c
    )

INSTALL(TARGETS programming-network
        DESTINATION "lib"
        )
Leos313
  • 5,152
  • 6
  • 40
  • 69
Miroslav Mares
  • 2,292
  • 3
  • 22
  • 27
  • 1
    Why not just add a line in `Makefile` under `install: \n\tcp $INCLUDES/* /usr/include/` ? – lukecampbell May 07 '12 at 18:40
  • 2
    OK, but it means, that it can't be done directly in CMakeLists.txt and that I have to write it in Makefile again everytime after I run cmake? – Miroslav Mares May 07 '12 at 18:43
  • I would assume so, I'm not too familiar with cmake, and CMakeLists.txt, I prefer to use gnu-automake. – lukecampbell May 07 '12 at 18:48
  • Your answerd helped me somehow, thank you. It works now. How easy... – Miroslav Mares May 07 '12 at 18:54
  • Wow, okay cool! You know you can give me an up-arrow on the comments. I would recommend checking out the automake tool set if you have time, it's really detailed. – lukecampbell May 07 '12 at 18:59
  • 2
    If your target was installed after calling CMake with `-DCMAKE_INSTALL_PREFIX=/usr`, then your lib would end up in `/usr/lib` (as expected with prefix set to `/usr`), but your headers would end up in `/include` (probably not expected). Per's answer makes more sense. – Fraser May 07 '12 at 22:46
  • 12
    @lukecampbell manually adding lines to the makefile would defeat the purpose of using cmake (which is 100 times better than automake). – Ilia Choly Dec 18 '12 at 16:12
  • Thank you! I was trying to install the header files with using the PUBLIC_HEADER: install(TARGETS myproject RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/static PUBLIC_HEADER DESTINATION include) but it did not work! Your INSTALL(FILES ... ) works like a charm! – ady Apr 13 '16 at 05:08
  • I rolled back your edit that posted the Answer in the Question. Stack Overflow does not use that format. Please place answers in Answer blocks. Later, you can accept your own Answer. Also see [How does accepting an answer work?](https://meta.stackexchange.com/q/5234/173448) – jww Feb 26 '19 at 23:32

5 Answers5

90

A better way for newest cmake version is to use target's PUBLIC_HEADER properties.

project(myproject)

add_library(mylib some.c another.c)
set_target_properties(mylib PROPERTIES PUBLIC_HEADER "some.h;another.h")
INSTALL(TARGETS mylib 
        LIBRARY DESTINATION some/libpath
        PUBLIC_HEADER DESTINATION some/includepath
)

Some ref:

PUBLIC_HEADER

CMake install command

flm8620
  • 1,411
  • 11
  • 15
  • 1
    This was exactly what I was looking for. Thanks for the example code. – m-bitsnbites Nov 28 '16 at 09:58
  • 1
    @flm8620 What if I have many header files? Is there a smarter solution than list all of them in the string given to `set_target_properties`? – Marco Stramezzi Oct 31 '17 at 11:11
  • 2
    @MarcoStramezzi You can use file(GLOB ***) command in CMake to grab files – flm8620 Nov 01 '17 at 14:48
  • 2
    @flm8620 I believe the authors of cmake discourage the use of GLOB for various reasons. Some probably not applicable here since dependencies are not determined by the PUBLIC_HEADER property. Either way, the post was useful and helped me out. – R Schultz Nov 08 '17 at 23:22
  • 14
    This comment was intended to be an edit but others believe it is better suited as a comment. If you use a list in place of `"some.h;another.h"`, ensure you place the variable name in quotations. Otherwise it will cause an error or only the first item in the list will be installed. What actually happens depends on the number of items in the list. For example `set_target_properties(myproject PROPERTIES PUBLIC_HEADER "${my_header_files}")` is correct `set_target_properties(myproject PROPERTIES PUBLIC_HEADER ${my_header_files})` is not. – R Schultz Nov 09 '17 at 16:39
  • Looked at the source code of cmake here, https://gitlab.kitware.com/cmake/cmake/-/blob/master/Source/cmStringAlgorithms.cxx Figured out that ";" was required but the [] also i was doing which was incorrect – sandroid Jun 02 '20 at 07:14
  • @flm8620 What if the headers are located inside a folder? – Javier Garcia Dec 09 '20 at 21:36
  • Is there a way to split multiple header files across newlines? Based on https://stackoverflow.com/questions/7637539/how-to-split-strings-across-multiple-lines-in-cmake, I tried splitting lines by adding a `\` after the `;` in your answer, but cmake didn't handle that correctly. So far, I'm only able to get this working if the PUBLIC_HEADER string is all on the same line. – StoneThrow Apr 09 '21 at 02:26
24

In a much better way, will copy all files that match the pattern and will preserve the directory structure.

INSTALL (
    DIRECTORY ${CMAKE_SOURCE_DIR}/include/
    DESTINATION include
    FILES_MATCHING PATTERN "*.h*")
J.Adler
  • 1,303
  • 11
  • 19
  • 1
    This approach won't work, if target has dependencies with public headers. In this case probably it's better to use PUBLIC_HEADER from accepted answer. It will add headers recursively (from static libraries for example). – Andrei Aug 16 '19 at 01:41
  • @Akela thx, in this last year the script changed quite a bit, I will try to put the project with the whole script on github. Basically I consider all files in inlcude folder PUBLIC and all files in src PRIVATE, so I do TARGET_INCLUDE_DIRECTORIES (mylib PUBLIC $ $ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src), I also created two files, mylibConfig.cmake and mylibTargets.cmake, that includes all its dependencies, and finally install it with INSTALL (DIRECTORY include / DESTINATION include COMPONENT development). – J.Adler Aug 17 '19 at 01:39
  • Hm... I guess it can solve the problem even for dependent library case (depends on how mylibTargets.cmake is implemented). Anyway, your answer is perfectly fine for the initial question. In my case with two libraries one depends on another, I also added something like mylibTargets.cmake which contained find_dependency() call. I'm not sure whether it was right, but at least it worked for me. – Andrei Aug 18 '19 at 02:18
  • Akela, I pushed the project into github as is, take a look. https://github.com/jadler/cmake-scripts – J.Adler Aug 18 '19 at 23:03
  • 1
    Sorry, I checked it again, and it works with libraries. I have no idea, why it didn't copy headers from dependencies the first time. So far this is the best answer to my mind, because it preserves directory structure. – Andrei Aug 20 '19 at 05:43
  • @J.Adler Many libraries, like GTK for example, use multiple directories in which the headers are stored. E.g. `/usr/include/gtk-4.0/gsk` contains `gsk.h`, `vulkan/gskvulkanrenderer.h`, `gl/gskglrenderer.h` and so on. How would you achieve this in the modern CMake way? I could imagine a solution using multiple targets just for these public header files. Also is it a good idea to create a "inlude all" header using CMake and the list of public headers? Is there a neat way to do it or will I have to write the file from scratch using e.g. list()? – Tomáš Růžička Jul 08 '21 at 02:43
  • 1
    @TomášRůžička, I have a project in github with some cmake scripts, it wasn't developed with multiple targets in mind and its last update was a year ago, take a look [here](https://github.com/jadler/cmake-scripts/blob/master/CMake-config.cmake) it could helps you (1, 14 and 68). I basically consider everthing inside include directory as public and the structure defined there will be preserved, so following your example with gtk headers, it will be something like `${CMAKE_CURRENT_SOURCE_DIR}/include/gtk-4.0/gsk/gsk.h`, `${CMAKE_CURRENT_SOURCE_DIR}/include/gtk-4.0/gsk/vulkan/gskvulkanrenderer.h`. – J.Adler Jul 10 '21 at 21:01
17

Years later, with CMake 3.23, we can use FILE_SET for public headers:

project(programming-network)

add_library(programming-network STATIC)

target_include_directories(programming-network PRIVATE "${PROJECT_SOURCE_DIR}")

target_sources(programming-network 
    PRIVATE send_string.c recv_line.c
    PUBLIC FILE_SET HEADERS 
    BASE_DIRS ${PROJECT_SOURCE_DIR}
    FILES publicheader1.h publicheader2.h)

install(TARGETS programming-network FILE_SET HEADERS)

Now let's see what these commands do:

  • add_library(): defines the name of the target, STATIC for a static library, SHARED for a shared library, OBJECT for objects.

  • target_include_directories(): this line here is only for when you have subdirectories and private headers referencing each other relative to the project directory. But generally, this command is used for including external headers in a project.

  • target_sources(): This command is used to add definition files and private headers with PRIVATE keyword. Also, it is used to add public headers via FILE_SET keyword. BASE_DIRS is to turn the absolute path of public headers into a relative path by deducting the base directory from their path. So the this public header

/home/someuser/programming-network/sub1/publicheader1.h

with base dir of

/home/someuser/programming-network/

will be installed in

/cmake/install/prefix/include/sub1/publicheader.h

Note target_sources() can be used in CMakeLists.txt of subdirectories as well.

  • install(): is to install binaries, static/shared libraries and public headers. The default installation subdirectories are bin, lib and include. You can also change that like this
install(TARGETS myTarget
        # for executables and dll on Win
        RUNTIME DESTINATION bin
        # shared libraries
        LIBRARY DESTINATION lib
        # for static libraries
        ARCHIVE DESTINATION lib
        # public headers
        INCLUDES DESTINATION include)

And finally, the project is built and installed with (for multi-configuration generators: MS Visual C++, Xcode)

# in project directory
mkdir build
cd build
cmake ..
cmake --build . --config Release
cmake --install . --prefix "/usr/local/" --config Release

For single-configuration generators (make, Ninja), drop the above --config Release terms and change cmake .. to cmake -DCMAKE_BUILD_TYPE=Release ...

Sorush
  • 3,275
  • 4
  • 28
  • 42
  • 1
    +1! thank you for this. It is very easy to miss the `install(...FILE_SETS...)` options in the documentation https://cmake.org/cmake/help/v3.23/command/install.html?highlight=install#installing-targets – g19fanatic Aug 23 '22 at 12:48
  • Sorush, this is the best explanation for `FILE_SET` on the entire internet. Please write a book on CMake. I'll buy it. My question is, is `FILE_SET` mandatory for defining `install()` using newer CMake versions? – afp_2008 Nov 17 '22 at 01:37
  • I recommend removing `target_include_directories` from your answer, or being more explicit about what `PROJECT_SOURCE_DIR` is (i.e.: root vs sub1 directories are mixed in your answer). I believe that public include directories (for the target own headers) are defined already with `PUBLIC FILE_SET HEADERS`. If one wanted to include private headers, you could set `PRIVATE FILE_SET HEADERS` in `add_sources` if they don't have an associated target, or just `target_link_library` to the external target. – agirault Jan 05 '23 at 16:45
6

I don't think your solution is the correct one. /usr/include should be reserved for your vendor to put files in.

The proper thing to do IMO is to install the header in /usr/local/include and then instruct the user to export CPATH="/usr/local/include:${CPATH}".

It seems /usr/local/lib was search automatically but if you wish to use another dir export LIBRARY_PATH="/usr/local/lib:${LIBRARY_PATH}" works similar for the .a binary (but may or may not work good for shared libraries depending on your os).

Optionally, but more cumbersome is to add -I /usr/local/include and -L /usr/local/lib while compiling.

This is a somewhat subjective answer, but it's been working well for me.

Per Johansson
  • 6,697
  • 27
  • 34
  • this INSTALL(FILES ${HEADERS} DESTINATION include) adds the header files to /usr/local/include by default, if you want to change it then do: make DESTDIR=/home/user/my_include install – ady Apr 13 '16 at 22:28
4

In addition to the accepted answer, if you are creating a lot of libraries and the set_property syntax throws you off. You could wrap it in a very simple macro, such as:

# File: target_public_headers.cmake
macro(target_public_headers TARGET)
  set_target_properties(${TARGET} PROPERTIES PUBLIC_HEADER "${ARGN}")
endmacro()

Then you can use it like:

project(myproject)

include(target_public_headers)

add_library(mylib some.c another.c)
target_public_headers(mylib some.h another.h) # <<<<<

# If you're exporting this library then you need to tell
# CMake how to include the "installed" version of the headers.
target_include_directories(mylib
  PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
  PUBLIC $<INSTALL_INTERFACE:some/includepath>
)

INSTALL(TARGETS mylib 
        LIBRARY DESTINATION some/libpath
        PUBLIC_HEADER DESTINATION some/includepath
)
Samuel O'Malley
  • 3,471
  • 1
  • 23
  • 41