CMAKE - How to properly copy static library's header file into /usr/include?

CCmakeStatic Libraries

C Problem Overview


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"
        )

C Solutions


Solution 1 - C

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

Solution 2 - C

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*")

Solution 3 - C

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.

Solution 4 - C

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
)

Solution 5 - C

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 ...

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMiroslav MaresView Question on Stackoverflow
Solution 1 - Cflm8620View Answer on Stackoverflow
Solution 2 - CJ.AdlerView Answer on Stackoverflow
Solution 3 - CPer JohanssonView Answer on Stackoverflow
Solution 4 - CSamuel O'MalleyView Answer on Stackoverflow
Solution 5 - CSorushView Answer on Stackoverflow