When to use pkg_check_modules as a fallback to find<package>? - cmake

I use find<package> to probe for packages (ZLIB, PNG, ...) and pkg_check_modules as a fallback if FindPackage fails to set up a superbuild with external dependencies:
find_package(PkgConfig)
## find_package(ZLIB) is a bit redundant; find_package(PNG) seems to probe for ZLIB
## in newer versions of FindPNG.cmake...
find_package(ZLIB)
find_package(PNG)
if (PKG_CONFIG_FOUND)
if (NOT ZLIB_FOUND)
pkg_check_modules(ZLIB IMPORTED_TARGET zlib)
endif (NOT ZLIB_FOUND)
if (NOT PNG_FOUND)
pkg_check_modules(PNG IMPORTED_TARGET libpng)
endif (NOT PNG_FOUND)
endif ()
if (NOT ZLIB_FOUND)
## Set up external project dependency build
else ()
## Found it, incorporate into target's link stage
endif ()
The problem is when MinGWw64 is installed and CMake uses the VS2019 compiler -- pkg_check_modules finds MinGWw64's zlib, which can't be used due to the different library naming convention.
I've added MSVC to the condition, since this seems to only impact MSVC-based builds:
if (PKG_CONFIG_FOUND AND NOT (MSVC))
# ...
endif()
Since I'm aiming to be as cross-platform as possible (*nix, MSVC, MinGW), is there a better way to fall back to pkg_check_modules when find<package> doesn't succeed? Or should I avoid pkg_check_modules altogether?

Avoid stupid: Let find_package do the work, as some Find<package>.cmake scripts (recipes) use PkgConfig.cmake internally as a fallback.

Related

Check Eigen version in cmake with header only

I want to use Eigen in one of my projects.
The user directly decides to turn Eigen ON/OFF and configures the path to the includes. So far, CMakeLists.txt looks like:
set(EIGEN_MODULE "OFF" CACHE BOOL "Enabled EIGEN MODULE ?")
if (EIGEN_MODULE)
include_directories(${EIGEN_INCLUDE_DIR})
set(EIGEN_INCLUDE_DIR /usr/local CACHE PATH "eigen include dir")
if(NOT EXISTS ${EIGEN_INCLUDE_DIR})
message(FATAL_ERROR "Bad eigen include dir")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEIGEN_MODULE")
include_directories(${EIGEN_INCLUDE_DIR})
endif(EIGEN_MODULE)
However, I don't know how to check the version of Eigen (I need to ensure 3.4.0 at least), knowing that I want to avoid find_package (Eigen3 3.4 REQUIRED NO_MODULE) which would require the user to compile Eigen.
Is there any way to do that ?
First a comment about your question: Eigen is a header only library, it means that the user will have to compile the library, no matter what.
Then, to answer your question: you shouldn't be scared to use find_package(Eigen3), actually the documentation of Eigen specifically recommends to use find_package before performing a target_link_libraries. So you can validate that Eigen has the proper version with find_package (Eigen3 3.4 REQUIRED), this is the best way to do it. find_package will read the file Eigen3Config.cmake found in the CMAKE_PREFIX_PATH, and that will contain the proper version.
It can seem a little confusing to use target_link_libraries to compile Eigen, since it is header-only (you could think that all you have to do is to include the directories, since Eigen is merely composed of header files, like you have done in your example). The reason is that CMake supports what is called interface library, and this is what is recommended by Eigen.

How do I use find_package in CMakeLists.txt such that if an optional dependency is not installed, I still get a makefile?

My goal is to create a CMakeLists.txt file that builds a suite of programs for the local system. If the conditions for building some programs are not satisfied, I wish to be able to build the rest of the programs. The sticking point seems to be gtk, but bear in mind that I don't think a gtk-specific method is the best choice.
For many modules you'd want to build with, you can use
find_package(PkgConfig REQUIRED)
To set up the pkg-config wrapper pkg_check_Modules and if you don't use REQUIRED you can check the _FOUND variable:
pkg_check_Modules(FT REQUIRED freetype2)
if (FT_FOUND)
message(STATIC "Found freetype2")
#Continue and succeed, using FreeType library
else()
message(STATIC "Didn't find freetype2")
#Continue and succeed, without using FreeType library
endif()
Other libraries don't seem to work with pkg_check_modules so I have resorted to find_package
find_package(GTK2 2.0 QUIET gtk)
if (GTK2_FOUND)
...
endif()
Here's a complete reduction:
cmake_minimum_required(VERSION 3.6.2)
project (test C CXX)
set (SOURCE a.cpp b.cpp)
find_package(AMAZING 2.0 QUIET amazing_testing)
if (AMAZING_FOUND)
list(APPEND SOURCE c.cpp)
else()
list(APPEND SOURCE d.cpp)
endif()
add_executable(test ${SOURCE})
The above allows my whole CMakeLists.txt to proceed to the end, but I get an error, no makefile is created, and build/CMakeFiles/CMakeOutput.log doesn't even mention gtk:
CMake Error at gtk/CMakeLists.txt:4 (find_package):
find_package called with invalid argument "amazing_testing"
## here would be further cmake processing logs from other necessary setup
-- Configuring incomplete, errors occurred!
See also "/home/menright/bit/build/CMakeFiles/CMakeOutput.log".
How can I tweak such a usage of find_package so that if the optional package does not exist, the configure will complete successfully anyway? Alternatively, what should I use instead of find_package that will concisely set up usage of a library the way pkg_check_modules does and yet allow success if the library is not available?

Make FetchContent compatible with find_package

I try to add all dependencies needed for my project to compile over CMake. This should reduce the overhead others will have when they want to compile the project for the first time.
To achive this, I tried to use FetchContent. So far so good, when I link the generated targets its not a problem at all. But now I have a library depending itself on annother lib which isn't included as submodule. The lib tries to find the dependency over find_package. How can I get find_package to find the library?
What I tried so far:
adding an alias target and defined all variables set by find_package
Setting the LIB_DIR to the build directory and called find_package
Here a minimal snipped of my CMake code of the later:
cmake_minimum_required(VERSION 3.14)
find_package(ZLIB)
if (NOT ZLIB_FOUND)
FetchContent_Declare(zlib_fetch
GIT_REPOSITORY https://github.com/madler/zlib.git
GIT_TAG cacf7f1d4e3d44d871b605da3b647f07d718623f
)
FetchContent_MakeAvailable(zlib_fetch)
set(ZLIB_DIR ${zlib_fetch_BINARY_DIR})
message(${zlib_fetch_BINARY_DIR})
#simulates the call in the other library:
find_package(ZLIB REQUIRED)
endif (NOT ZLIB_FOUND)
Starting with CMake 3.24, FetchContent_Declare has OVERRIDE_FIND_PACKAGE option which, when specified, makes CMake redirect the subsequent calls to find_package(<name>) so that FetchContent_Declare(<name> ...) satisfies the dependency (note that <name> must stay the same here).

How can you add warning flags using cmake cross platform?

I see a number of articles that suggest you check for compiler and add flags as appropriate, eg.
if (CMAKE_COMPILER_IS_GNUCC)
...
endif()
if (MSVC)
...
endif()
This is a deeply undesirable situation though.
It relies on you having, for every project, to add specific support for each compiler that you support, one at a time.
Other things, like C++11 features and debug flags are automatically generated by cmake for each of the compilers it supports.
Is there no equivalent solution for adding the equivalent of -Wall / /W3 to the compile simply via a cmake setting?
It relies on you having, for every project, to add specific support for each >compiler that you support, one at a time.
At now you can only have something like compiler.cmake, where you configure suitable flags for each compiler, and share compiler.cmake among projects.
Is there no equivalent solution for adding the equivalent of -Wall / /W3 to the >compile simply via a cmake setting?
No, now there is only disscussion about similar feature and it's possible implementation, see
https://cmake.org/pipermail/cmake-developers/2016-March/028107.html
For anyone else who finds this...
There is a reasonably robust implementation of this which can be found here, as a 3rd party addition:
https://github.com/ruslo/sugar/wiki/Cross-platform-warning-suppression
You use it like this:
## Project
cmake_minimum_required(VERSION 3.1)
project(npp)
# Dependencies
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/npp)
... whatever ...
# Clone entire sugar repo to source folder and import
include(${CMAKE_CURRENT_SOURCE_DIR}/sugar/cmake/Sugar)
include(sugar_generate_warning_flags)
# Generate flags, included excluded flags, etc.
# see: https://github.com/ruslo/leathers/wiki/List
sugar_generate_warning_flags(
flags
properties
ENABLE ALL
DISABLE c++98-compat padded
TREAT_AS_ERROR ALL
CLEAR_GLOBAL)
# Library / executable if any
file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/npp/*.cpp)
add_library(npp STATIC ${SOURCES})
# Set flags
set_target_properties(npp PROPERTIES ${properties} COMPILE_OPTIONS "${flags}")
# Local tests
enable_testing()
add_executable(tests "${CMAKE_CURRENT_SOURCE_DIR}/tests/tests.cpp")
# Set flags
set_target_properties(tests PROPERTIES ${properties} COMPILE_OPTIONS "${flags}")
target_link_libraries(tests npp)
add_test(tests tests)
Obviously this is far from ideal, as it's quite irritating to have to clone a set of modules, but it's practical for the moment.

How to use cmake find_package() with a local copy of the package?

I'm trying to make a project that has both ZLIB and LIBPNG (and other libraries). LibPNG's CMakeLists.txt file has this in it: find_package(ZLIB REQUIRED) It's stock code that comes with it and I don't want to change it.
I'm building on Windows (Visual Studio). This is a cross-platform application (Windows, Mac, Linux and Mobile devices) I cannot rely on /usr/lib versions of any libraries. So I'm building them all with my project together.
I can't get LibPNG to build unless I hack this up. In an upper-level CMakeLists.txt file, I put this in there:
ADD_SUBDIRECTORY(contrib/${CUSTOM_ZLIB_LOCATION})
SET(ZLIB_FOUND ON CACHE BOOL "Yes")
SET(ZLIB_INCLUDE_DIR ${CMAKE_BINARY_DIR}/contrib/${CUSTOM_ZLIB_LOCATION} {CMAKE_SOURCE_DIR}/contrib/${CUSTOM_ZLIB_LOCATION})
SET(ZLIB_LIBRARY zlib CACHE STRING "zlib library name")
This satisfies find_package(ZLIB REQUIRED) But I think this is a hack. Is there some straight forward way to build the local copy of zlib without all the 3 extra lines?
I only added this line at the beginning (at least before find_package(ZLIB REQUIRED)) and it worked for me.
set(ZLIB_ROOT <zlib folder here>)
But others may need doing something like:
if (CMAKE_VERSION VERSION_GREATER 3.12 OR CMAKE_VERSION VERSION_EQUAL 3.12)
# Enable find_package uses of <PackageName>_ROOT variables.
cmake_policy(SET CMP0074 NEW)
endif()
set(ZLIB_ROOT <zlib folder here>)
We set the policy to NEW.
The OLD behavior for this policy is to ignore <PackageName>_ROOT variables.
CMake version 3.22.1 warns when the policy is not set (and defaults to OLD behavior).