从 `cmake` 使用 `pkg-config` 的正确方法是什么?
What is the proper way to use `pkg-config` from `cmake`?
网上找了很多这样的代码:
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
返回的标志。
执行此操作并确保编译的 app
使用 pkg-config
返回的所有编译和 link 标志的正确方法是什么?是否有一个命令可以完成此操作,即 target_use(app SDL2)
?
参考:
没有target_use
这样的命令。但我知道有几个项目已经编写了这样的命令供内部使用。但是每个项目都想传递额外的标志或定义,因此在一般的 CMake 中拥有它是没有意义的。没有它的另一个原因是像 Eigen 这样的 C++ 模板化库,没有库,但你只有一堆包含文件。
描述的方式往往是正确的。某些库可能会有所不同,那么您必须添加 _LDFLAGS
或 _CFLAGS
。没有 target_use
的另一个原因。如果它对您不起作用,请提出一个关于 SDL2 或您要使用的任何库的新问题。
首先,电话:
include(FindPkgConfig)
应替换为:
find_package(PkgConfig)
find_package()
调用更加灵活,并允许使用 REQUIRED
等选项,这些选项可以自动执行必须手动执行 include()
的操作。
其次,应尽可能避免手动调用pkg-config
。 CMake 带有一组丰富的包定义,可在 /usr/share/cmake-3.0/Modules/Find*cmake
下的 Linux 中找到。与对 pkg_search_module()
.
的原始调用相比,这些为用户提供了更多选项和选择。
至于提到的假设 target_use()
命令,CMake 已经以某种方式内置了 PUBLIC|PRIVATE|INTERFACE。像 target_include_directories(mytarget PUBLIC ...)
这样的调用会导致 include 目录在每个使用 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_DIRS
和 SDL2_LDFLAGS_OTHER
仍然被忽略,不知道这会成为一个问题的频率。
这里有更多文档http://www.cmake.org/cmake/help/v3.0/module/FindPkgConfig.html
如果您还想从库中添加定义,可以使用 add_definitions
指令。可以找到文档 here,以及更多添加编译器标志的方法。
以下代码片段使用此指令将 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})
很少有人只需要 link 使用 SDL2。当前流行的答案使用 pkg_search_module()
检查给定模块并使用第一个工作模块。
您更有可能希望 link 使用 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})
免责声明:如果我有足够的 Whosebug 街头信誉,我会简单地评论 Grumbel 的自我回答。
大多数可用答案无法为 pkg-config
库配置 headers。在思考 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>)
(相应地用你的目标代替 <my-target>
并用任何库代替 <some-lib>
。)
IMPORTED_TARGET
选项似乎是关键,它使 PkgConfig::
命名空间下的所有内容都可用。这就是所需要的,也是应该所需要的。
如果您以非常正常的方式使用 cmake 和 pkg-config,则此解决方案有效。
但是,如果您有一个库存在于某个开发目录中(例如 /home/me/hack/lib),则使用此处看到的其他方法无法配置链接器路径。在典型安装位置下找不到的库会导致链接器错误,如 /usr/bin/ld: cannot find -lmy-hacking-library-1.0
。此解决方案修复了该情况下的链接器错误。
另一个问题可能是pkg-config文件没有安装到正常位置,需要使用PKG_CONFIG_PATH
环境变量添加项目的pkg-config路径,而cmake是运行(请参阅与此相关的其他 Stack Overflow 问题)。当您使用正确的 pkg-config 路径时,此解决方案也能正常工作。
使用IMPORTED_TARGET
是解决上述问题的关键。此解决方案是对 的改进,归结为工作 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 关键字。
网上找了很多这样的代码:
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
返回的标志。
执行此操作并确保编译的 app
使用 pkg-config
返回的所有编译和 link 标志的正确方法是什么?是否有一个命令可以完成此操作,即 target_use(app SDL2)
?
参考:
没有
target_use
这样的命令。但我知道有几个项目已经编写了这样的命令供内部使用。但是每个项目都想传递额外的标志或定义,因此在一般的 CMake 中拥有它是没有意义的。没有它的另一个原因是像 Eigen 这样的 C++ 模板化库,没有库,但你只有一堆包含文件。描述的方式往往是正确的。某些库可能会有所不同,那么您必须添加
_LDFLAGS
或_CFLAGS
。没有target_use
的另一个原因。如果它对您不起作用,请提出一个关于 SDL2 或您要使用的任何库的新问题。
首先,电话:
include(FindPkgConfig)
应替换为:
find_package(PkgConfig)
find_package()
调用更加灵活,并允许使用 REQUIRED
等选项,这些选项可以自动执行必须手动执行 include()
的操作。
其次,应尽可能避免手动调用pkg-config
。 CMake 带有一组丰富的包定义,可在 /usr/share/cmake-3.0/Modules/Find*cmake
下的 Linux 中找到。与对 pkg_search_module()
.
至于提到的假设 target_use()
命令,CMake 已经以某种方式内置了 PUBLIC|PRIVATE|INTERFACE。像 target_include_directories(mytarget PUBLIC ...)
这样的调用会导致 include 目录在每个使用 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_DIRS
和 SDL2_LDFLAGS_OTHER
仍然被忽略,不知道这会成为一个问题的频率。
这里有更多文档http://www.cmake.org/cmake/help/v3.0/module/FindPkgConfig.html
如果您还想从库中添加定义,可以使用 add_definitions
指令。可以找到文档 here,以及更多添加编译器标志的方法。
以下代码片段使用此指令将 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})
很少有人只需要 link 使用 SDL2。当前流行的答案使用 pkg_search_module()
检查给定模块并使用第一个工作模块。
您更有可能希望 link 使用 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})
免责声明:如果我有足够的 Whosebug 街头信誉,我会简单地评论 Grumbel 的自我回答。
大多数可用答案无法为 pkg-config
库配置 headers。在思考 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>)
(相应地用你的目标代替 <my-target>
并用任何库代替 <some-lib>
。)
IMPORTED_TARGET
选项似乎是关键,它使 PkgConfig::
命名空间下的所有内容都可用。这就是所需要的,也是应该所需要的。
如果您以非常正常的方式使用 cmake 和 pkg-config,则此解决方案有效。
但是,如果您有一个库存在于某个开发目录中(例如 /home/me/hack/lib),则使用此处看到的其他方法无法配置链接器路径。在典型安装位置下找不到的库会导致链接器错误,如 /usr/bin/ld: cannot find -lmy-hacking-library-1.0
。此解决方案修复了该情况下的链接器错误。
另一个问题可能是pkg-config文件没有安装到正常位置,需要使用PKG_CONFIG_PATH
环境变量添加项目的pkg-config路径,而cmake是运行(请参阅与此相关的其他 Stack Overflow 问题)。当您使用正确的 pkg-config 路径时,此解决方案也能正常工作。
使用IMPORTED_TARGET
是解决上述问题的关键。此解决方案是对
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 关键字。