ChatGPT解决这个技术问题 Extra ChatGPT

CMAKE - 如何正确地将静态库的头文件复制到 /usr/include 中?

我开始使用 C 来使用 CMAKE,实际上我正在创建两个非常小的静态库。

我的目标是:

这些库被编译并链接到 *.a 文件中。 [这工作] 然后我希望将 *.a 文件复制到 /usr/local/lib [这也有效] 据我所知(很少),它们是使用 -lnameoflib 链接的,这是一个编译器标志.好的。我已经准备好我的 CMakeLists.txt,它实际上将 *.a 文件复制到 /usr/local/lib 中。但是,为了能够在程序中使用它们,我还需要将它们的头文件复制到 /usr/local/include 中,然后我可以通过简单的方法 #include 来包含它们。我现在就是这样理解的。

我的问题是 - 使用 CMAKE 将头文件复制到 /usr/include 文件夹的正确方法是什么?我希望它在执行 make install 时自动复制它们,就像 *.a 文件一样。

对于这两个库,我都有一个类似的 CMakeLists.txt:

project(programming-network)

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

INSTALL(TARGETS programming-network
        DESTINATION "lib"
        )
为什么不在 install: \n\tcp $INCLUDES/* /usr/include/ 下的 Makefile 中添加一行?
好的,但这意味着它不能直接在 CMakeLists.txt 中完成,并且每次运行 cmake 后我都必须在 Makefile 中再次写入它?
我会假设是这样,我对 cmake 和 CMakeLists.txt 不太熟悉,我更喜欢使用 gnu-automake。
如果您的目标是在使用 -DCMAKE_INSTALL_PREFIX=/usr 调用 CMake 之后安装的,那么您的 lib 最终会在 /usr/lib 中(正如预期的前缀设置为 /usr),但您的标头最终会在 /include 中(可能不是预期的) . Per的回答更有意义。
@lukecampbell 手动向 makefile 添加行会破坏使用 cmake 的目的(这比 automake 好 100 倍)。

f
flm8620

最新 cmake 版本的更好方法是使用目标的 PUBLIC_HEADER 属性。

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
)

一些参考:

PUBLIC_HEADER

CMake install command


这正是我想要的。感谢您的示例代码。
@flm8620 如果我有很多头文件怎么办?有没有比在 set_target_properties 的字符串中列出所有这些更聪明的解决方案?
@MarcoStramezzi 您可以在 CMake 中使用 file(GLOB ***) 命令来抓取文件
@flm8620 我相信 cmake 的作者出于各种原因不鼓励使用 GLOB。有些可能在这里不适用,因为依赖关系不是由 PUBLIC_HEADER 属性确定的。无论哪种方式,该帖子都很有用并帮助了我。
此评论旨在进行编辑,但其他人认为它更适合作为评论。如果您使用列表代替 "some.h;another.h",请确保将变量名放在引号中。否则将导致错误或仅安装列表中的第一项。实际发生的情况取决于列表中的项目数。例如 set_target_properties(myproject PROPERTIES PUBLIC_HEADER "${my_header_files}") 是正确的 set_target_properties(myproject PROPERTIES PUBLIC_HEADER ${my_header_files}) 不是。
J
J.Adler

以更好的方式,将复制与模式匹配的所有文件并保留目录结构。

INSTALL (
    DIRECTORY ${CMAKE_SOURCE_DIR}/include/
    DESTINATION include
    FILES_MATCHING PATTERN "*.h*")

如果目标与公共标头有依赖关系,则此方法将不起作用。在这种情况下,最好从接受的答案中使用 PUBLIC_HEADER 。它将递归地添加标题(例如来自静态库)。
@Akela thx,在去年,脚本发生了很大变化,我将尝试将项目与整个脚本放在 github 上。基本上我考虑 inlcude 文件夹 PUBLIC 中的所有文件和 src PRIVATE 中的所有文件,所以我做 TARGET_INCLUDE_DIRECTORIES (mylib PUBLIC $ $ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src),我还创建了两个文件,mylibConfig.cmake 和 mylibTargets.cmake,包含了它所有的依赖,最后用 INSTALL 安装(DIRECTORY include / DESTINATION include COMPONENT development)。
嗯......我想它甚至可以解决依赖库案例的问题(取决于 mylibTargets.cmake 的实现方式)。无论如何,您的回答对于最初的问题来说非常好。在我有两个库的情况下,一个依赖于另一个,我还添加了类似 mylibTargets.cmake 的内容,其中包含 find_dependency() 调用。我不确定它是否正确,但至少它对我有用。
抱歉,我再次检查了它,它适用于库。我不知道,为什么它第一次没有从依赖项中复制标头。到目前为止,这是我心中最好的答案,因为它保留了目录结构。
@TomášRůžička,我在 github 中有一个项目,其中包含一些 cmake 脚本,它的开发并未考虑到多个目标,并且它的最后一次更新是一年前,看看here它可以帮助你(1、14 和 68) .我基本上认为包含目录中的所有内容都是公共的,并且在那里定义的结构将被保留,因此按照您的带有 gtk 标头的示例,它将类似于 ${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

我不认为你的解决方案是正确的。 /usr/include 应保留给您的供应商以放入文件。

IMO 的正确做法是在 /usr/local/include 中安装标头,然后指示用户安装 export CPATH="/usr/local/include:${CPATH}"

似乎 /usr/local/lib 是自动搜索的,但如果您希望使用另一个目录 export LIBRARY_PATH="/usr/local/lib:${LIBRARY_PATH}" 对 .a 二进制文件的工作方式类似(但取决于您的操作系统,对于共享库可能适用也可能不好)。

可选,但更麻烦的是在编译时添加 -I /usr/local/include-L /usr/local/lib

这是一个有点主观的答案,但对我来说效果很好。


这个 INSTALL(FILES ${HEADERS} DESTINATION include) 默认情况下将头文件添加到 /usr/local/include,如果要更改它,请执行以下操作: make DESTDIR=/home/user/my_include install
S
Samuel O'Malley

除了 the accepted answer,如果您要创建大量库并且 set_property 语法会让您望而却步。您可以将其包装在一个非常简单的宏中,例如:

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

然后你可以像这样使用它:

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

多年后,使用 CMake 3.23,我们可以将 FILE_SET 用于公共标头:

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)

现在让我们看看这些命令的作用:

add_library():定义目标的名称,静态库为STATIC,共享库为SHARED,对象为OBJECT。

target_include_directories():这里的这一行仅适用于当您有子目录和私有头文件相对于项目目录相互引用时。但通常,此命令用于在项目中包含外部头文件。

target_sources():该命令用于添加带有 PRIVATE 关键字的定义文件和私有头文件。此外,它还用于通过 FILE_SET 关键字添加公共标头。 BASE_DIRS 是把公共头的绝对路径从它们的路径中减去基目录,变成相对路径。所以这个公共标头

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

基本目录为

/home/someuser/programming-network/

将安装在

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

注意 target_sources() 也可以在子目录的 CMakeLists.txt 中使用。

install():是安装二进制文件、静态/共享库和公共头文件。默认安装子目录是 bin、lib 和 include。您也可以像这样更改

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)

最后,项目的构建和安装(对于多配置生成器:MS Visual C++、Xcode)

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

对于单一配置生成器(make、Ninja),删除上述 --config Release 术语并将 cmake .. 更改为 cmake -DCMAKE_BUILD_TYPE=Release ..


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

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅