What is the proper way to use `pkg-config` from `cmake`?
Asked Answered
R

6

135

I have seen a lot of code like this:

include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2)

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

However, that seems to be the wrong way about doing it, as it only uses the include directories and libraries, but ignores defines, library paths and other flags that might be returned by pkg-config.

What would be the correct way to do this and ensure that all compile and link flags returned by pkg-config are used by the compiled app? And is there a single command to accomplish this, i.e., something like target_use(app SDL2)?

ref:

Rugger answered 22/3, 2015 at 7:4 Comment(0)
R
71

If you're using cmake and pkg-config in a pretty normal way, this solution works.

If, however, you have a library that exists in some development directory (such as /home/me/hack/lib), then using other methods seen here fail to configure the linker paths. Libraries that are not found under the typical install locations would result in linker errors, like /usr/bin/ld: cannot find -lmy-hacking-library-1.0. This solution fixes the linker error for that case.

Another issue could be that the pkg-config files are not installed in the normal place, and the pkg-config paths for the project need to be added using the PKG_CONFIG_PATH environment variable while cmake is running (see other Stack Overflow questions regarding this). This solution also works well when you use the correct pkg-config path.

Using IMPORTED_TARGET is key to solving the issues above. This solution is an improvement on this earlier answer and boils down to this final version of a working 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)

Note that target_link_libraries does more than change the linker commands. It also propagates other PUBLIC properties of specified targets like compiler flags, compiler defines, include paths, etc., so, use the PUBLIC keyword with caution.

Rosser answered 26/7, 2019 at 17:21 Comment(2)
IMPORTED_TARGET requires CMake 3.6 or newer.Vue
I think this failed for me because of gitlab.kitware.com/cmake/cmake/-/issues/19387.Allocate
R
100

First off, the call:

include(FindPkgConfig)

should be replaced with:

find_package(PkgConfig)

The find_package() call is more flexible and allows options, such as REQUIRED, that do things automatically that one would have to do manually with include().

Secondly, manually calling pkg-config should be avoided when possible. CMake comes with a rich set of package definitions, found on Linux under /usr/share/cmake-3.0/Modules/Find*cmake. These provide more options and choice for the user than a raw call to pkg_search_module().

As for the mentioned hypothetical target_use() command, CMake already has that built-in in a way with PUBLIC|PRIVATE|INTERFACE. A call like target_include_directories(mytarget PUBLIC ...) will cause the include directories to be automatically used in every target that uses mytarget, e.g., target_link_libraries(myapp mytarget).

However, this mechanism seems to be only for libraries created within the CMakeLists.txt file and does not work for libraries acquired with pkg_search_module(). The call add_library(bar SHARED IMPORTED) might be used for that, but I haven't yet looked into that.

As for the main question, this here works in most cases:

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})

The SDL2_CFLAGS_OTHER contains defines and other flags necessary for a successful compile. The flags SDL2_LIBRARY_DIRS and SDL2_LDFLAGS_OTHER are however still ignored, no idea how often that would become a problem.

More documentation here: CMake Documentation — FindPkgConfig.

Rugger answered 28/3, 2015 at 10:32 Comment(9)
I agree that pkg-config should be avoided IF a Find*.cmake exists, but that still isn't the case for the latest version of cmake in 2016.Bicapsular
This does not work if the libraries are not in the default directories. link_directories() can be a workaround, but it is global.Admeasure
This approach doesn't work for vcpkg. Can I locate SDL2_image without hard-coding paths!?Rajasthani
@HenryHu could you show how this would be done if this was the situation in the configuration in this answer?Unbutton
Requiring a build tool like CMake to bundle heuristics to snif every library in the world makes no sense, it's not its role. Pkg-config is designed so that it's the responsibility of the lib author or the pkg/distro maintainer to make it available to the users. And if this scheme is followed, the correct way to use a lib is always through calling pkg-config.Laura
since FindPkgConfig is a cmake builtin module (like CTest) using include(FindPkgConfig) is the way to go IMHO (since include search first in the cmake builtin module directory first). see CTest documentation page: cmake.org/cmake/help/latest/module/CTest.html which use include()Talos
BTW include() also have the OPTIONAL options.... src: cmake.org/cmake/help/latest/command/include.htmlTalos
Regarding my earlier comment, I show rectify something: it's not CMake that bring all those files but the library authors. However, pkg-config is still a concept since the library author doesn't have to know or require what build system their users will use.Laura
While I would really recommend to use the IMPORTED_TARGET approach used in https://mcmap.net/q/167169/-what-is-the-proper-way-to-use-pkg-config-from-cmake, if you really need to target_link_libraries() then please use <XXX>_LINK_LIBRARIES instead of just <XXX>_LIBRARIES: The former has the full, absolute paths and thus works for non-standard directories too; for example when cross-compiling.Hubble
R
71

If you're using cmake and pkg-config in a pretty normal way, this solution works.

If, however, you have a library that exists in some development directory (such as /home/me/hack/lib), then using other methods seen here fail to configure the linker paths. Libraries that are not found under the typical install locations would result in linker errors, like /usr/bin/ld: cannot find -lmy-hacking-library-1.0. This solution fixes the linker error for that case.

Another issue could be that the pkg-config files are not installed in the normal place, and the pkg-config paths for the project need to be added using the PKG_CONFIG_PATH environment variable while cmake is running (see other Stack Overflow questions regarding this). This solution also works well when you use the correct pkg-config path.

Using IMPORTED_TARGET is key to solving the issues above. This solution is an improvement on this earlier answer and boils down to this final version of a working 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)

Note that target_link_libraries does more than change the linker commands. It also propagates other PUBLIC properties of specified targets like compiler flags, compiler defines, include paths, etc., so, use the PUBLIC keyword with caution.

Rosser answered 26/7, 2019 at 17:21 Comment(2)
IMPORTED_TARGET requires CMake 3.6 or newer.Vue
I think this failed for me because of gitlab.kitware.com/cmake/cmake/-/issues/19387.Allocate
P
15

It's rare that one would only need to link with SDL2. The currently popular answer uses pkg_search_module() which checks for given modules and uses the first working one.

It is more likely that you want to link with SDL2 and SDL2_Mixer and SDL2_TTF, etc... pkg_check_modules() checks for all the given 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})

Disclaimer: I would have simply commented on Grumbel's self answer if I had enough street creds with stackoverflow.

Parasitic answered 28/12, 2017 at 20:0 Comment(8)
Globbing source files is bad practice and discouraged.Shelley
For me, target_link_libraries(my_app ${SDL2_LINK_LIBRARIES}) worked better.Gsuit
@Shelley globbing source files is good practice, it's CMake's fault if it's buggy.Laura
@JohanBoulé: No it's not. You may have a developer add a bunch of files locally and have the stuff work on their computer, and not commiting all the files needed. Then they push their changes and it breaks for other people. Sure, this can be caught by some continuous integration, but this is only the most obvious problem. The Meson build system chose to not implement file globing, and CMake developers explicitly discourage globbing. Explicit is better than implicit.Shelley
@Shelley I've seen that argument already many times more than the actual problem it theorises on. Meson's against, build2's for. None will have it, like tab vs space.Laura
@Shelley That my friend is what we call an XY Problem. Solution is a CI hook that build on every push and rejects pull request that do not build.Stature
@Stature please read the links I provided. Meson developers explicitly say: This can not be made both reliable and fast. By reliable we mean that if the user adds a new source file to the subdirectory, Meson should detect that and make it part of the build automatically. One of the main requirements of Meson is that it must be fast. This means that a no-op build in a tree of 10 000 source files must take no more than a fraction of a second. This is only possible because Meson knows the exact list of files to check.Shelley
@liberforce: Explicit or implicit, the build breaks either way if a file doesn't get checked in. Using an explicit list just provides TWO ways to break a build instead of one. I cannot imagine what the performance advantage of stat()-ing 10,000 source files would be over opendir/readdir-ing 10,000 source files would be. And it's not as if most of us have 10,000 source files OR glob patterns like /*/*/.cpp OR are going to glob build output directories.Tellford
M
14

Most of the available answers fail to configure the headers for the pkg-config library. After meditating on the Documentation for FindPkgConfig I came up with a solution that provides those also:

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>)

(Substitute your target in place of <my-target> and whatever library in place of <some-lib>, accordingly.)

The IMPORTED_TARGET option seems to be key and makes everything then available under the PkgConfig:: namespace. This was all that was required and also all that should be required.

Montanez answered 4/6, 2019 at 11:43 Comment(1)
TIP: print cmake var after running pkg_check_modules to see the available vars https://mcmap.net/q/99368/-cmake-print-out-all-accessible-variables-in-a-scriptGreenockite
U
1
  1. There is no such command as target_use. But I know several projects that have written such a command for their internal use. But every project want to pass additional flags or defines, thus it does not make sense to have it in general CMake. Another reason not to have it are C++ templated libraries like Eigen, there is no library but you only have a bunch of include files.

  2. The described way is often correct. It might differ for some libraries, then you'll have to add _LDFLAGS or _CFLAGS. One more reason for not having target_use. If it does not work for you, ask a new question specific about SDL2 or whatever library you want use.

Unsuspecting answered 22/3, 2015 at 10:3 Comment(0)
L
0

If you are looking to add definitions from the library as well, the add_definitions instruction is there for that. Documentation can be found here, along with more ways to add compiler flags.

The following code snippet uses this instruction to add GTKGL to the project:

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})
Lyndes answered 21/6, 2017 at 16:45 Comment(1)
Don't use include_directories etc it will infect global scope! Use target_include_directories etcOvoviviparous

© 2022 - 2024 — McMap. All rights reserved.