Jaki jest właściwy sposób używania `pkg-config` z` cmake`?

87

Rozglądając się po sieci, widziałem dużo kodu takiego:

include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2)

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

Wydaje się jednak, że jest to niewłaściwy sposób robienia tego, ponieważ używa tylko katalogów dołączania i bibliotek, ale ignoruje definicje, ścieżki bibliotek i inne flagi, które mogą być zwracane przez pkg-config.

Jaki byłby prawidłowy sposób na zrobienie tego i upewnienie się, że wszystkie flagi kompilacji i łącza zwrócone przez pkg-configsą używane przez kompilację app? Czy jest jedno polecenie, aby to zrobić, np. Coś takiego target_use(app SDL2)?

ref:

Grumbel
źródło

Odpowiedzi:

33

Jeśli używasz cmake i pkg-config w całkiem normalny sposób, to rozwiązanie działa.

Jeśli jednak masz bibliotekę, która istnieje w jakimś katalogu programistycznym (takim jak / home / me / hack / lib), to użycie innych metod opisanych tutaj nie spowoduje skonfigurowania ścieżek konsolidatora. Biblioteki, które nie zostały znalezione w typowych lokalizacjach instalacji, spowodowałyby błędy konsolidatora, takie jak /usr/bin/ld: cannot find -lmy-hacking-library-1.0. To rozwiązanie naprawia błąd konsolidatora w tym przypadku.

Innym problemem może być to, że pliki pkg-config nie są instalowane w normalnym miejscu, a ścieżki pkg-config dla projektu muszą zostać dodane przy użyciu zmiennej środowiskowej PKG_CONFIG_PATH podczas działania cmake (zobacz inne pytania dotyczące przepełnienia stosu). Zakładając, że masz skonfigurowaną poprawną ścieżkę pkg-config, to rozwiązanie również rozwiązuje ten problem.

Rozwiązanie sprowadza się do tej ostatecznej wersji działającego 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)

Zauważ, że target_link_librariesrobi coś więcej niż tylko zmianę poleceń konsolidatora. Propaguje również inne właściwości PUBLIC określonych celów, takie jak: flagi kompilatora, definicje kompilatora, ścieżki dołączania itp.

activedecay
źródło
5
IMPORTED_TARGET wymaga CMake 3.6 lub nowszej.
Cris Luengo
1
Jeśli głosujesz w dół, upewnij się i skomentuj, dlaczego tak się stało, abyśmy mogli poprawić odpowiedź.
aktywny dzień
Myślę, że to się nie udało z powodu gitlab.kitware.com/cmake/cmake/-/issues/19387 .
Florian Zwoch
63

Po pierwsze, wezwanie:

include(FindPkgConfig)

należy zastąpić:

find_package(PkgConfig)

find_package()Rozmowa jest bardziej elastyczny i pozwala na takie opcje, jak REQUIRED, to robić rzeczy automatycznie, że trzeba by zrobić ręcznie include().

Po drugie, w pkg-configmiarę możliwości należy unikać ręcznego dzwonienia . CMake zawiera bogaty zestaw definicji pakietów, które można znaleźć w Linuksie pod /usr/share/cmake-3.0/Modules/Find*cmake. Zapewniają one więcej opcji i możliwości wyboru dla użytkownika niż surowe wywołanie pkg_search_module().

Jeśli chodzi o wspomniane hipotetyczne target_use()polecenie, CMake ma już to wbudowane w pewien sposób z PUBLIC | PRIVATE | INTERFACE. Takie wywołanie target_include_directories(mytarget PUBLIC ...)spowoduje, że katalogi dołączania będą automatycznie używane w każdym używanym celu mytarget , np target_link_libraries(myapp mytarget). Jednak ten mechanizm wydaje się być tylko dla bibliotek utworzonych w CMakeLists.txtpliku i nie działa dla bibliotek nabytych za pomocą pkg_search_module(). To wezwanie add_library(bar SHARED IMPORTED)może zostać użyte do tego, ale jeszcze się nad tym nie sprawdziłem.

Jeśli chodzi o główne pytanie, to tutaj działa w większości przypadków:

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

Plik SDL2_CFLAGS_OTHERZawiera definiuje i inne flagi niezbędne dla pomyślnej kompilacji. Flagi SDL2_LIBRARY_DIRSi SDL2_LDFLAGS_OTHERsą jednak nadal ignorowane, nie mam pojęcia, jak często będzie to problemem.

Więcej dokumentacji tutaj http://www.cmake.org/cmake/help/v3.0/module/FindPkgConfig.html

Grumbel
źródło
4
Zgadzam się, że należy unikać pkg-config, JEŻELI istnieje Find * .cmake, ale tak nie jest w przypadku najnowszej wersji cmake w 2016 roku.
Cubic
3
Nie działa to, jeśli biblioteki nie znajdują się w domyślnych katalogach. link_directories () może być obejściem, ale jest globalne.
Henry Hu
To podejście nie działa w przypadku vcpkg . Czy mogę zlokalizować SDL2_image bez zakodowanych ścieżek !?
user2023370
@HenryHu czy mógłbyś pokazać, jak by to wyglądało, gdyby taka była sytuacja w konfiguracji w tej odpowiedzi?
Tim Visée
2
Wymaganie narzędzia do budowania, takiego jak CMake, do tworzenia pakietów heurystycznych w celu obsługi wszystkich bibliotek na świecie, nie ma sensu, nie jest to jego rolą. Pakiet Pkg-config został zaprojektowany w taki sposób, że odpowiedzialność za udostępnienie go użytkownikom spoczywa na autorze biblioteki lub opiekunie pkg / distro. A jeśli przestrzega się tego schematu, prawidłowym sposobem użycia biblioteki jest zawsze wywołanie pkg-config.
Johan Boulé
10

Rzadko się zdarza, że ​​wystarczy połączyć się z SDL2. Obecnie popularna odpowiedź wykorzystuje pkg_search_module()który sprawdza dane moduły i używa pierwszego działającego.

Bardziej prawdopodobne jest, że będziesz chciał połączyć się z SDL2 i SDL2_Mixer i SDL2_TTF, itd ... pkg_check_modules()sprawdza wszystkie podane moduły.

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

Zastrzeżenie: po prostu skomentowałbym własną odpowiedź Grumbela, gdybym miał wystarczająco dużo ulicznych kredytów ze stackoverflow.

fluxrider
źródło
2
Globalizacja plików źródłowych jest złą praktyką i odradza się.
liberforce
1
U mnie target_link_libraries(my_app ${SDL2_LINK_LIBRARIES})działało lepiej.
stefan
2
@liberforce globbing plików źródłowych jest dobrą praktyką, a jeśli jest błędny , to wina CMake.
Johan Boulé
2
@ JohanBoulé: Nie, nie jest. Możesz mieć programistę, który doda kilka plików lokalnie i sprawi, że rzeczy będą działać na jego komputerze, bez zatwierdzania wszystkich potrzebnych plików. Potem forsują swoje zmiany i to zrywa dla innych ludzi. Jasne, może to być spowodowane ciągłą integracją, ale to tylko najbardziej oczywisty problem. System kompilacji Meson zdecydował się nie implementować globowania plików , a programiści CMake wyraźnie odradzają globalizowanie . Jawne jest lepsze niż niejawne.
liberforce
1
@liberforce Widziałem ten argument wiele razy częściej niż rzeczywisty problem, o którym teoretyzuje. Meson jest przeciw, build2 dla. Nikt go nie będzie miał, jak tab vs spacja.
Johan Boulé
6

W przypadku większości dostępnych odpowiedzi nie można skonfigurować nagłówków pkg-configbiblioteki. Po medytacji nad dokumentacją dla FindPkgConfig wymyśliłem rozwiązanie, które zapewnia je również:

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

( Zastąp cel w miejscu <my-target>i dowolnej bibliotece zamiast <some-lib>, odpowiednio. )

IMPORTED_TARGETOpcja wydaje się być kluczowym i sprawia, że wszystko, co potem dostępne pod PkgConfig::nazw. To było wszystko, co było wymagane, a także wszystko, co powinno być wymagane.

Eero Aaltonen
źródło
WSKAZÓWKA: wydrukuj cmake var po uruchomieniu, pkg_check_modulesaby zobaczyć dostępne vars stackoverflow.com/a/9328525/1211174
dąb
0
  1. Nie ma takiego polecenia jak target_use. Ale znam kilka projektów, które napisały takie polecenie do użytku wewnętrznego. Ale każdy projekt chce przekazać dodatkowe flagi lub definicje, dlatego nie ma sensu mieć go w ogólnym CMake. Innym powodem, dla którego go nie ma, są biblioteki szablonów C ++, takie jak Eigen, nie ma biblioteki, ale masz tylko kilka plików dołączanych.

  2. Opisany sposób jest często poprawny. Może się różnić w przypadku niektórych bibliotek, wtedy będziesz musiał dodać _LDFLAGSlub _CFLAGS. Jeszcze jeden powód, żeby tego nie mieć target_use. Jeśli to nie zadziała, zadaj nowe pytanie dotyczące SDL2 lub innej biblioteki, z której chcesz korzystać.

usr1234567
źródło
-2

Jeśli chcesz dodać definicje również z biblioteki, add_definitionsinstrukcja jest do tego. Dokumentację można znaleźć tutaj , wraz z innymi sposobami dodawania flag kompilatora.

Poniższy fragment kodu używa tej instrukcji, aby dodać GTKGL do projektu:

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})
jeanluc
źródło
3
Nie używaj include_directoriesitp. Spowoduje to zainfekowanie zasięgu globalnego! Użyj target_include_directoriesitp.
Dawid Drozd