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

19

我想从目标中在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。那么在设置好目标属性后,是否有另一种方法来读取它们?


哇 - 没有答案? :/ - Carlo Wood
一种方法是手动确保在最后调用auto_pkgconfig。另一种方法是钩子到配置即将完成的事件上。但这不是当前CMake API可以做到的。不过,有一种hacky方式 - Wang
2个回答

2
根据CMake文档INSTALL_INTERFACE的内容仅在调用install(EXPORT)时可用。除非扩展CMake,否则最好使用其他方法生成您的PkgConfig文件。理想情况下,您应该有足够的控制权来轻松地生成安装布局。
然而,这并不意味着你不能做你所要求的事情;它只是类似于“Tony the Pony”的邪恶程度。我实际上犹豫了一下是否要发布这个。请不要将其视为建议。
思路是使用install(EXPORT)让CMake生成适当的脚本。然后,生成一个虚拟的CMake项目,该项目使用您提供的file(GENERATE OUTPUT ...)代码;虚拟项目将查看导出的即INSTALL_INTERFACE属性。
我最初尝试使用install(CODE [[ ... ]])来完成此操作,但它也会看到$<BUILD_INTERFACE:...>视图。我已在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

这应该会让你感到悲伤。它让我感到悲伤。

那么,什么是正统的解决方案?(除了将目标导出为CMake文件之外)生成pkgconfig文件的美妙之处在于:CMakeLists.txt是唯一的真相来源。这里有一个解决方案,包含模板文件。 - milahu
@MilaNautikus - 嗯,这就是我们在这里讨论的问题。导出的CMake目标和pkg-config文件之间存在巨大的表现力差距。这里的技术,即从导出的目标中重建pkg-config文件,实际上是目前唯一正确的方法,只要除了$<BUILD_INTERFACE:...>$<INSTALL_INTERFACE:...>之外没有生成器表达式。 - Alex Reinking

1

编辑:离题了,因为在这里,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生成pkg-config .pc文件 相关:将目标导出为CMake文件

这确实会生成 .pc 文件。我该如何使用 install 子句将其移动到指定位置? - Victor Eijkhout
尝试使用 install(CODE [[ configure_file(...) ]]) 进行安装(安装代码)(括号字符串)。 - milahu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接