如何从 cmake 目标自动生成 pkgconfig 文件

Posted

技术标签:

【中文标题】如何从 cmake 目标自动生成 pkgconfig 文件【英文标题】:How to auto generate pkgconfig files from cmake targets 【发布时间】:2017-11-01 17:06:04 【问题描述】:

我想在 cmake 中从目标生成 pkgconfig 文件。我盯着写这样的东西:

function(auto_pkgconfig TARGET)

    get_target_property(INCLUDE_DIRS $TARGET INTERFACE_INCLUDE_DIRECTORIES)
    string(REPLACE "$<BUILD_INTERFACE:" "$<0:" INCLUDE_DIRS "$INCLUDE_DIRS")
    string(REPLACE "$<INSTALL_INTERFACE:" "$<1:" INCLUDE_DIRS "$INCLUDE_DIRS")
    string(REPLACE "$<INSTALL_PREFIX>" "$CMAKE_INSTALL_PREFIX" INCLUDE_DIRS "$INCLUDE_DIRS")


    file(GENERATE OUTPUT $TARGET.pc CONTENT "
Name: $TARGET
Cflags: -I$<JOIN:$INCLUDE_DIRS, -I>
Libs: -L$CMAKE_INSTALL_PREFIX/lib -l$TARGET
")
    install(FILES $TARGET.pc DESTINATION lib/pkgconfig)
endfunction()

这是一个简化版本,但它主要读取INTERFACE_INCLUDE_DIRECTORIES 属性并处理生成器表达式的INSTALL_INTERFACE。 只要在调用auto_pkgconfig 之前设置了包含目录,这样就可以很好地工作,如下所示:

add_library(foo foo.cpp)

target_include_directories(foo PUBLIC 
    $<BUILD_INTERFACE:$CMAKE_SOURCE_DIR/include>
    $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>
    $OTHER_INCLUDE_DIRS
)

auto_pkgconfig(foo)

但是,有时属性是在调用auto_pkgconfig 之后设置的,如下所示:

add_library(foo foo.cpp)
auto_pkgconfig(foo)

target_include_directories(foo PUBLIC 
    $<BUILD_INTERFACE:$CMAKE_SOURCE_DIR/include>
    $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>
    $OTHER_INCLUDE_DIRS
)

但是,这将不再正确读取包含目录。我希望auto_pkgconfig 在设置完所有目标属性后运行。我可以为此使用生成器表达式,将auto_pkgconfig 更改为:

function(auto_pkgconfig TARGET)

    file(GENERATE OUTPUT $TARGET.pc CONTENT "
Name: $TARGET
Cflags: -I$<JOIN:$<TARGET_PROPERTY:$TARGET,INTERFACE_INCLUDE_DIRECTORIES>, -I>
Libs: -L$<TARGET_FILE_DIR:$TARGET> -l$TARGET
")
    install(FILES $TARGET.pc DESTINATION lib/pkgconfig)
endfunction() 

但是,这将读取 BUILD_INTERFACE 而不是 INSTALL_INTERFACE。那么是否有另一种方法可以在设置目标属性后读取它们?

【问题讨论】:

哇 - 没有答案? :// 一种方法是手动确保最后调用 auto_pkgconfig。另一种方法是挂钩配置即将完成的事件。但这不是当前的 cmake api 可以做到的。虽然有一个hacky way 【参考方案1】:

根据CMake documentation,INSTALL_INTERFACE的内容只有在调用install(EXPORT)时才可用。除非他们扩展 CMake,否则最好执行其他操作来生成 PkgConfig 文件。理想情况下,您应该对安装布局有足够的控制权,以简化此操作。


然而,这并不意味着你不能做你所要求的;这只是"Tony the Pony" 邪恶的等级。 其实我很犹豫要不要发布这个。请不要将此作为建议。

这个想法是使用install(EXPORT) 让 CMake 生成适当的脚本。然后生成一个虚拟 CMake 项目,该项目使用您在上面给出的 file(GENERATE OUTPUT ...) 代码;虚拟项目将看到导出的,即。 INSTALL_INTERFACE 属性。

我最初尝试使用install(CODE [[ ... ]]) 来执行此操作,但它看到了$&lt;BUILD_INTERFACE:...&gt; 视图。我在CMake Discourse 上问过这个问题。

cmake_minimum_required(VERSION 3.16)
project(example)

# Dummy library for demo

add_library(example SHARED example.cpp)

target_compile_definitions(example
  PUBLIC $<BUILD_INTERFACE:BUILD>
         $<INSTALL_INTERFACE:INSTALL>)

target_include_directories(example
  PUBLIC $<BUILD_INTERFACE:$CMAKE_CURRENT_SOURCE_DIR/include>
         $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/include>)

# Here be dragons...

function(auto_pc TARGET)
  file(CONFIGURE OUTPUT "pc.$TARGET/CMakeLists.txt"
       CONTENT [[
cmake_minimum_required(VERSION 3.16)
project(pc_@TARGET@)

find_package(pc_@TARGET@ REQUIRED CONFIG)

file(GENERATE OUTPUT @TARGET@.pc
     CONTENT [=[
Name: @TARGET@
Cflags: -I$<JOIN:$<TARGET_PROPERTY:INTERFACE_INCLUDE_DIRECTORIES>, -I> -D$<JOIN:$<TARGET_PROPERTY:INTERFACE_COMPILE_DEFINITIONS>, -D>
Libs: -L$<TARGET_FILE_DIR:@TARGET@> -l@TARGET@
]=]   TARGET "@TARGET@")
]] @ONLY NEWLINE_STYLE LF)

  install(TARGETS $TARGET EXPORT pc_$TARGET)
  install(EXPORT pc_$TARGET DESTINATION "_auto_pc" FILE pc_$TARGET-config.cmake)

  file(CONFIGURE OUTPUT "pc.$TARGET/post-install.cmake"
       CONTENT [[
file(REAL_PATH "$CMAKE_INSTALL_PREFIX" prefix)
set(proj "@CMAKE_CURRENT_BINARY_DIR@/pc.@TARGET@")
execute_process(COMMAND "@CMAKE_COMMAND@" "-Dpc_@TARGET@_DIR=$prefix/_auto_pc" -S "$proj" -B "$proj/build")
file(COPY "$proj/build/@TARGET@.pc" DESTINATION "$prefix")
]] @ONLY NEWLINE_STYLE LF)

  install(SCRIPT "$CMAKE_CURRENT_BINARY_DIR/pc.$TARGET/post-install.cmake")
endfunction()

auto_pc(example)

# Clean up install path
install(CODE [[ file(REMOVE_RECURSE "$CMAKE_INSTALL_PREFIX/_auto_pc") ]])

这会导致以下结果:

alex@Alex-Desktop:~/test$ cmake -S . -B build
...
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build
alex@Alex-Desktop:~/test$ cmake --build build/
...
alex@Alex-Desktop:~/test$ cmake --install build --prefix install
-- Install configuration: ""
-- Installing: /home/alex/test/install/lib/libexample.so
-- Installing: /home/alex/test/install/_auto_pc/pc_example-config.cmake
-- Installing: /home/alex/test/install/_auto_pc/pc_example-config-noconfig.cmake
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build/pc.example/build
alex@Alex-Desktop:~/test$ ls install/
example.pc  lib
alex@Alex-Desktop:~/test$ cat install/example.pc
Name: example
Cflags: -I/home/alex/test/install/include -DINSTALL
Libs: -L/home/alex/test/install/lib -lexample

这应该让你难过。这让我很难过。

【讨论】:

那么,什么是正统的解决方案? (除了exporting targets to cmake files)生成 pkgconfig 文件的美妙之处在于:CMakeLists.txt 是唯一的事实来源。 here is a solution 带有模板文件 @MilaNautikus - 好吧,这就是这里的问题。导出的 CMake 目标和 pkg-config 文件之间存在巨大的表现力差距。这里的技术,用于导出您的目标并从导出的目标重建 pkg-config 文件,实际上是 唯一 当前正确执行此操作的方法,只要除了 @987654335 之外没有生成器表达式@和$&lt;INSTALL_INTERFACE:...&gt;【参考方案2】:

编辑:题外话,因为这里,pc文件是手动生成的

pkgconfig 模板文件

动机:

CMakeLists.txt 应该是唯一的事实来源(名称、版本) pkgconfig 文件比 cmake 文件小大约 10 倍(cmake 到 pkgconfig 是有损转换)

模板文件:my_package.pc.in

prefix="@CMAKE_INSTALL_PREFIX@"
exec_prefix="$prefix"
libdir="$prefix/lib"
includedir="$prefix/include"

Name: @PROJECT_NAME@
Description: @CMAKE_PROJECT_DESCRIPTION@
Version: @PROJECT_VERSION@
Cflags: -I$includedir
Libs: -L$libdir -l@target1@

CMakeLists.txt

cmake_minimum_required(VERSION 3.0)

project(my_library VERSION 1.1.2 LANGUAGES C
  DESCRIPTION "example library")

add_library(my_library src/my_library.c)

# generate pc file for pkg-config
set(target1 my_library)
configure_file(my_package.pc.in
  lib/pkgconfig/my_package.pc @ONLY)

基于:CMake generate pkg-config .pc

相关:exporting targets to cmake files

【讨论】:

以上是关于如何从 cmake 目标自动生成 pkgconfig 文件的主要内容,如果未能解决你的问题,请参考以下文章

CMake基础教程(30)CMake构建系统概览

CMake基础教程(30)CMake构建系统概览

如何配置 CMake 目标或命令来预处理 C 文件?

如何强制 CMake 链接到系统库而不是同名目标

如果指定文件被更改,如何执行CMake目标?

如何使用CMAKE生成makefile文件