从`cmake`使用`pkg-config`的正确方法是啥?

Posted

技术标签:

【中文标题】从`cmake`使用`pkg-config`的正确方法是啥?【英文标题】:What is the proper way to use `pkg-config` from `cmake`?从`cmake`使用`pkg-config`的正确方法是什么? 【发布时间】:2015-05-25 08:34:28 【问题描述】:

在网上看了很多这样的代码:

include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2)

target_include_directories(app SYSTEM PUBLIC $SDL2_INCLUDE_DIRS)
target_link_libraries(app $SDL2_LIBRARIES)

但这似乎是错误的做法,因为它只使用包含目录和库,但忽略了 pkg-config 可能返回的定义、库路径和其他标志。

执行此操作并确保所有由pkg-config 返回的编译和链接标志都被编译的app 使用的正确方法是什么?是否有一个命令可以完成此操作,例如target_use(app SDL2)

参考:

include() FindPkgConfig

【问题讨论】:

【参考方案1】:

首先,调用:

include(FindPkgConfig)

应替换为:

find_package(PkgConfig)

find_package() 调用更加灵活,并允许使用诸如 REQUIRED 之类的选项,这些选项可以自动执行,而使用 include() 必须手动执行。

其次,应尽可能避免手动调用pkg-config。 CMake 带有一组丰富的包定义,可在 Linux 中的 /usr/share/cmake-3.0/Modules/Find*cmake 下找到。与对pkg_search_module() 的原始调用相比,这些为用户提供了更多选项和选择。

至于上述假设的target_use() 命令,CMake 已经以 PUBLIC|PRIVATE|INTERFACE 的方式内置了该命令。像target_include_directories(mytarget PUBLIC ...) 这样的调用将导致包含目录在每个使用mytarget 的目标中自动使用,例如target_link_libraries(myapp mytarget)。然而,这种机制似乎只适用于在CMakeLists.txt 文件中创建的库,不适用于使用pkg_search_module() 获取的库。调用 add_library(bar SHARED IMPORTED) 可能会用于此目的,但我尚未对此进行调查。

至于主要问题,这在大多数情况下都有效:

find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2)
...
target_link_libraries(testapp $SDL2_LIBRARIES)
target_include_directories(testapp PUBLIC $SDL2_INCLUDE_DIRS)
target_compile_options(testapp PUBLIC $SDL2_CFLAGS_OTHER)

SDL2_CFLAGS_OTHER 包含成功编译所需的定义和其他标志。然而,SDL2_LIBRARY_DIRSSDL2_LDFLAGS_OTHER 标志仍然被忽略,不知道这会成为一个问题的频率。

更多文档在这里http://www.cmake.org/cmake/help/v3.0/module/FindPkgConfig.html

【讨论】:

我同意应该避免使用 pkg-config 如果存在 Find*.cmake,但 2016 年最新版本的 cmake 仍然不是这种情况。 如果库不在默认目录中,这将不起作用。 link_directories() 可能是一种解决方法,但它是全局的。 此方法不适用于 vcpkg。我可以在没有硬编码路径的情况下找到 SDL2_image!? 需要像 CMake 这样的构建工具来捆绑启发式方法来嗅探世界上的每个库是没有意义的,这不是它的作用。 Pkg-config 被设计成 lib 作者或 pkg/distro 维护者有责任将其提供给用户。如果遵循这个方案,使用 lib 的正确方法总是通过调用 pkg-config。 虽然我真的建议使用***.com/a/57224542/211520 中使用的IMPORTED_TARGET 方法,但如果您确实需要target_link_libraries(),请使用<XXX>_LINK_LIBRARIES 而不仅仅是<XXX>_LIBRARIES:前者有完整的,绝对路径,因此也适用于非标准目录;例如在交叉编译时。【参考方案2】:

如果您以非常正常的方式使用 cmake 和 pkg-config,则此解决方案有效。

但是,如果您有一个库存在于某个开发目录(例如 /home/me/hack/lib)中,则使用此处看到的其他方法无法配置链接器路径。在典型安装位置下找不到的库会导致链接器错误,例如/usr/bin/ld: cannot find -lmy-hacking-library-1.0。此解决方案修复了该案例的链接器错误。

另一个问题可能是 pkg-config 文件没有安装在正常位置,需要在 cmake 运行时使用PKG_CONFIG_PATH 环境变量添加项目的 pkg-config 路径(参见其他 Stack Overflow关于这个的问题)。当您使用正确的 pkg-config 路径时,此解决方案也很有效。

使用IMPORTED_TARGET 是解决上述问题的关键。此解决方案是对 this earlier answer 的改进,归结为工作 CMakeLists.txt 的最终版本:

cmake_minimum_required(VERSION 3.14)
project(ya-project C)

# the `pkg_check_modules` function is created with this call
find_package(PkgConfig REQUIRED) 

# these calls create special `PkgConfig::<MODULE>` variables
pkg_check_modules(MY_PKG REQUIRED IMPORTED_TARGET any-package)
pkg_check_modules(YOUR_PKG REQUIRED IMPORTED_TARGET ya-package)

add_executable(program-name file.c ya.c)

target_link_libraries(program-name PUBLIC
        PkgConfig::MY_PKG
        PkgConfig::YOUR_PKG)

请注意,target_link_libraries 不仅仅是更改链接器命令。它还会传播指定目标的其他 PUBLIC 属性,例如编译器标志、编译器定义、包含路径等,因此请谨慎使用 PUBLIC 关键字。

【讨论】:

IMPORTED_TARGET 需要 CMake 3.6 或更高版本。 如果您对此投了反对票,请确认并评论您反对的原因,以便我们改进答案。 我认为这对我来说失败了,因为gitlab.kitware.com/cmake/cmake/-/issues/19387。【参考方案3】:

很少有人只需要与 SDL2 链接。当前流行的答案使用pkg_search_module(),它检查给定模块并使用第一个工作模块。

您更有可能希望与 SDL2 和 SDL2_Mixer 和 SDL2_TTF 等链接...pkg_check_modules() 检查所有给定模块。

# sdl2 linking variables
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2 SDL2_ttf SDL2_mixer SDL2_image)

# your app
file(GLOB SRC "my_app/*.c")
add_executable(my_app $SRC)
target_link_libraries(my_app $SDL2_LIBRARIES)
target_include_directories(my_app PUBLIC $SDL2_INCLUDE_DIRS)
target_compile_options(my_app PUBLIC $SDL2_CFLAGS_OTHER)

免责声明:如果我有足够的 *** 街头信誉,我会简单地评论 Grumbel 的自我回答。

【讨论】:

通配源文件是不好的做法,不鼓励。 对我来说,target_link_libraries(my_app $SDL2_LINK_LIBRARIES) 效果更好。 @liberforce 通配源文件是的做法,如果它有问题,那就是 CMake 的错。 @JohanBoulé:不,不是。您可能让开发人员在本地添加一堆文件,然后让这些东西在他们的计算机上运行,​​而不是提交所有需要的文件。然后他们推动他们的改变,而其他人则打破了它。当然,这可以通过一些持续集成来解决,但这只是最明显的问题。 Meson build system chose to not implement file globing 和 CMake developers explicitly discourage globbing。显式优于隐式。 @liberforce 我已经看到这个论点比它所提出的实际问题要多很多倍。 Meson 反对,build2 支持。没有人会拥有它,比如制表符 vs 空格。【参考方案4】:

大多数可用答案都无法配置 pkg-config 库的标头。在沉思Documentation for FindPkgConfig 之后,我想出了一个解决方案,它也提供了这些:

include(FindPkgConfig)
if(NOT PKG_CONFIG_FOUND)
  message(FATAL_ERROR "pkg-config not found!" )
endif()
 
pkg_check_modules(<some-lib> REQUIRED IMPORTED_TARGET <some-lib>)
 
target_link_libraries(<my-target> PkgConfig::<some-lib>)

相应地用你的目标代替&lt;my-target&gt;,用任何库代替&lt;some-lib&gt;

IMPORTED_TARGET 选项似乎是关键,然后可以在 PkgConfig:: 命名空间下使用所有内容。这就是所有需要的,也是所有应该需要的。

【讨论】:

提示:运行pkg_check_modules 后打印cmake var 以查看可用的变量***.com/a/9328525/1211174【参考方案5】:

    没有target_use这样的命令。但我知道有几个项目编写了这样的命令供内部使用。但是每个项目都想传递额外的标志或定义,因此在一般 CMake 中使用它是没有意义的。没有它的另一个原因是像 Eigen 这样的 C++ 模板库,没有库但你只有一堆包含文件。

    所描述的方法通常是正确的。某些库可能会有所不同,那么您必须添加_LDFLAGS_CFLAGS。没有target_use 的另一个原因。如果它不适合您,请询问有关 SDL2 或您想要使用的任何库的新问题。

【讨论】:

【参考方案6】:

您的代码 sn-p 有一个错误:它在对 target_include_directories 的调用结束时缺少一个括号……也许这就是问题所在?

应该是:

target_include_directories(app SYSTEM PUBLIC $SDL2_INCLUDE_DIRS)

【讨论】:

【参考方案7】:

如果您也想从库中添加定义,那么add_definitions 指令就在那里。可以在 here 找到文档,以及添加编译器标志的更多方法。

以下代码 sn-p 使用该指令将 GTKGL 添加到项目中:

pkg_check_modules(GTKGL REQUIRED gtkglext-1.0)
include_directories($GTKGL_INCLUDE_DIRS)
link_directories($GTKGL_LIBRARY_DIRS)
add_definitions($GTKGL_CFLAGS_OTHER)
set(LIBS $LIBS $GTKGL_LIBRARIES)

target_link_libraries([insert name of program] $LIBS)

【讨论】:

不要使用include_directories 等它会感染全局范围!使用target_include_directories

以上是关于从`cmake`使用`pkg-config`的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

CMake的pkg-config模块使用

CMake的pkg-config模块FindPkgConfig

在 Cmake 中包含 pkg-config --cflags --libs gtk+-2.0

CMake Pkg-Config 库链接路径

在 CMake 脚本中添加到 pkg-config 的临时路径?

如何使用以下make和pkg-config使clion工作?