I would like to generate pkgconfig files in cmake from the targets. I stared by writing something like this:
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()
This is a simplified version but it basically reads the INTERFACE_INCLUDE_DIRECTORIES
properties and processes the INSTALL_INTERFACE
of the generator expressions.
This works well as long as the include directories are set before calling auto_pkgconfig
, like this:
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)
However, sometimes properties are set after the call to auto_pkgconfig
, like this:
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}
)
However, this won't properly read the include directories anymore. I would like auto_pkgconfig
to run after all the target properties are set. I could use generator expressions for this, by changing auto_pkgconfig
to this:
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()
However, this will read the BUILD_INTERFACE
instead of the INSTALL_INTERFACE
. So is there another way to read target properties after they have been set?
According to the CMake documentation, the contents of INSTALL_INTERFACE
are only available when calling install(EXPORT)
. Unless they extend CMake, it will be best to do something else to generate your PkgConfig files. Ideally you would have enough control over your install layout to make this easy.
However, this doesn't mean you can't do what you ask; it's just "Tony the Pony" levels of evil. I actually hesitated to post this. Please don't take this as a recommendation.
The idea is to use install(EXPORT)
to have CMake generate the appropriate scripts. Then generate a dummy CMake project that uses the file(GENERATE OUTPUT ...)
code you gave above; the dummy project will see the exported, ie. INSTALL_INTERFACE
properties.
I initially tried to use install(CODE [[ ... ]])
to do this, but it also sees the $<BUILD_INTERFACE:...>
view. I've asked about this on the 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 @[email protected]
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/@[email protected]" 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") ]])
This results in the following:
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
This should make you sad. It makes me sad.
edit: off topic, since here, the pc files are generated manually
motivation:
template file: 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)
based on: CMake generate pkg-config .pc
related: exporting targets to cmake files
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With