ChatGPT解决这个技术问题 Extra ChatGPT

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

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

My goal is:

The libraries are compiled and linked into *.a files. [THIS WORKS] Then I wish to copy that *.a files into /usr/local/lib [THIS ALSO WORKS] 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 . 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"
        )
Why not just add a line in Makefile under install: \n\tcp $INCLUDES/* /usr/include/ ?
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?
I would assume so, I'm not too familiar with cmake, and CMakeLists.txt, I prefer to use gnu-automake.
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.
@lukecampbell manually adding lines to the makefile would defeat the purpose of using cmake (which is 100 times better than automake).

f
flm8620

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


This was exactly what I was looking for. Thanks for the example code.
@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?
@MarcoStramezzi You can use file(GLOB ***) command in CMake to grab files
@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.
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.
J
J.Adler

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

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).
@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).
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.
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.
@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 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.
P
Per Johansson

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.


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
S
Samuel O'Malley

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
)

S
Sorush

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


关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now