cmake find_package ignores required exact package version - cmake

Myproject/CMakeLists.txt:
find_package(Foo 2.0 EXACT REQUIRED)
Foo/CMakeLists.txt:
set(Foo_SOVERSION 1)
set(Foo_VERSION ${Foo_SOVERSION}.8)
Why does cmake Myproject not fail? It only notifies
-- Found Foo: /usr/local/lib/libfoo.so (Required is exact version "2.0")
and happily proceeds. How to enforce termination if an exact requirement is not met?

If you are also writing the FindFoo.cmake macro, then you can add to the macro something like following to enforce version checking.
if(FOO_FIND_VERSION)
# Add a version check logic and set FOO_FOUND as true conditionally
else()
set(FOO_FOUND TRUE)
endif()
Refer https://cmake.org/cmake/help/latest/manual/cmake-developer.7.html#find-modules for more details.

Related

How to use FetchContent_Populate with Eigen?

I want to use FetchContent to automatically manage the dependency to Eigen for my project, which works in general. However, when using the recommended method of FetchContent_Declare() and FetchContent_MakeAvailable() a subsequent call to install also installs all Eigen headers and documentation which is not necessary in my case.
To circumvent this behavior, I tried the method suggested in this answer: Disable install for FetchContent
FetchConten_Populate() however fails to fill the variables ${Eigen_SOURCE_DIR} and ${Eigen_BIN_DIR} (which the documentation told me should happen) so that I cannot call add_subdirectory().
Here is a minimal CMakeLists.txt:
cmake_minimum_required (VERSION 3.12)
project (FetchContentExample)
include (FetchContent)
FetchContent_Declare(
Eigen
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_TAG 3.4.0
)
FetchContent_GetProperties(Eigen)
if(NOT Eigen_POPULATED)
FetchContent_Populate(Eigen)
message("SRC; ${Eigen_SOURCE_DIR}") # Apparently empty?
message("BIN: ${Eigen_BINARY_DIR}") # Apparently empty?
add_subdirectory(${Eigen_SOURCE_DIR} ${Eigen_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
add_executable(FetchContentExample
main.cpp
)
target_link_libraries (FetchContentExample
PRIVATE
Eigen3::Eigen
)
install(
TARGETS FetchContentExample
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT Runtime
)
The same setup works fine when I use e.g.
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 5.3.0
)
instead of Eigen.
What specifically am I doing wrong when it comes to Eigen?
FetchContent_Populate() however fails to fill the variables ${Eigen_SOURCE_DIR} and ${Eigen_BINARY_DIR} (which the documentation told me should happen).
Actually, FetchContent fills variables ${eigen_SOURCE_DIR} and ${eigen_BINARY_DIR} which names are constructed from the lowercase variant of the project's name. This is written in the documentation:
FetchContent_Populate() will set three variables in the scope of the caller:
<lowercaseName>_POPULATED
This will always be set to TRUE by the call.
<lowercaseName>_SOURCE_DIR
The location where the populated content can be found upon return.
<lowercaseName>_BINARY_DIR
A directory intended for use as a corresponding build directory.
So the correct sequence of commands for EXCLUDE_FROM_ALL inclusion of Eigen would be:
FetchContent_GetProperties(Eigen)
if(NOT eigen_POPULATED)
FetchContent_Populate(Eigen)
add_subdirectory(${eigen_SOURCE_DIR} ${eigen_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()

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.

Add FreeType dependency with cmake supporting all vcpkg/apt-get/brew

I have a project which depends on FreeType, and uses CMake as build system. CMake has a FindFreeType built-in module which is supposed to be used like this, see for example this other SO question:
find_package(Freetype REQUIRED)
target_link_libraries(mylib ${FREETYPE_LIBRARIES})
target_include_directories(mylib PRIVATE ${FREETYPE_INCLUDE_DIRS})
Since CMake 3.10, there is also the Freetype::Freetype imported target so we can avoid the target_include_directories:
find_package(Freetype REQUIRED)
target_link_libraries(mylib Freetype::Freetype)
This worked great on Ubuntu 18.04 with FreeType installed via apt install libfreetype6-dev. I assume it also works on macOS when the package is installed via homebrew (I haven't tested yet).
However, on Windows, I wish to allow developers to depend on a vcpkg-installed FreeType:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg integrate install
.\vcpkg install freetype:x64-windows
Which they would target by running the following CMake command:
cmake .. -G "Visual Studio 15 2017" -A x64 -DCMAKE_TOOLCHAIN_FILE=C:/Users/Boris/vcpkg/scripts/buildsystems/vcpkg.cmake
Unfortunately, the above CMake command won't work with any of the two CMakeLists.txt at the beginning of this question, because the proper way to find and link to FreeType when it is installed via vcpkg is the following:
find_package(freetype CONFIG REQUIRED) # `Freetype` works too, but vcpkg doc recommends `freetype`
target_link_libraries(mylib freetype) # Here, all-lowercase is required
In particular, the freetype-config.cmake config file provided by vcpkg defines the target freetype (not Freetype::Freetype like the builtin find module), and doesn't define any of the FREETYPE_LIBRARIES or FREETYPE_INCLUDE_DIRS variables.
What would be a proper way to keep my CMakeLists.txt compatible with both "traditional" ways of finding FreeType, but also vcpkg?
Assuming pre-CMake 3.10, I'm thinking of something along the lines of:
if(DEFINED VCPKG_TARGET_TRIPLET)
find_package(freetype CONFIG REQUIRED)
set(FREETYPE_LIBRARIES freetype)
set(FREETYPE_INCLUDE_DIRS "")
else()
find_package(Freetype REQUIRED)
endif()
target_link_libraries(mylib ${FREETYPE_LIBRARIES})
target_include_directories(mylib PRIVATE ${FREETYPE_INCLUDE_DIRS})
Would that seem like good practice? Any better idea?
It feels ugly, and besides, there is always the possibility of a developer wanting to use vcpkg for some other dependencies but not for FreeType (e.g., explicitly providing FREETYPE_DIR instead), so this trick wouldn't even be enough in all situations, and we would need another CMake option like MYLIB_IGNORE_VCPKG_FREETYPE which starts to be even uglier.
Would that seem like good practice? Any better idea?
No be agnostic about a possible package manager.
Do the following:
find_package(Freetype CONFIG) # should find freetype-config.cmake if available
find_package(Freetype REQUIRED) # Will not be executed if Freetype_FOUND ist already set
# if you do not want two find_package calls consider using CMAKE_FIND_PACKAGE_PREFER_CONFIG
Then test if the target Freetype::Freetype or freetype exists
if(TARGET freetype AND NOT TARGET Freetype::Freetype)
add_library(Freetype::Freetype ALIAS freetype) # target freetype is defined by freetype-targets.cmake
# might need to add freetype to global scope if cmake errors here
# alternativly if the above does not work for you you can use
# add_library(Freetype::Freetype INTERFACE IMPORTED)
# target_link_libraries(Freetype::Freetype INTERFACE freetype)
endif()
if(NOT TARGET Freetype::Freetype)
# insert error here
# or create the target correctly (see cmakes newer FindFreetype.cmake)
endif()
target_link_libraries(mylib PRIVATE Freetype::Freetype)
if you don't want to alias the target you could also define a variable called FREETYPE_TARGET and set it to the correct target for linking against.
In older versions of vcpkg (< Jan 2020, see vcpkg#9311), there used to be the following message when installing Freetype:
The package freetype is compatible with built-in CMake targets:
find_package(Freetype REQUIRED)
target_link_libraries(main PRIVATE Freetype::Freetype)
In current versions, they instead recommend find_package(freetype CONFIG REQUIRED), which ensures that the config package vcpkg/<...>/freetype-config.cmake takes precedence over the CMake's built-in module package FindFreetype.cmake.
However, using the built-in module package still works correctly: it will find the vcpkg-installed library, and will define a Freetype::Freetype target (if CMake >= 3.10) rather than a freetype target.
Note that by default, find_package(Freetype REQUIRED) searches first for module packages, then for config packages. However, users can sets CMAKE_FIND_PACKAGE_PREFER_CONFIG, which would use the config package instead. Therefore, a robust approach to ensure that Freetype::Freetype is defined is to do the following:
find_package(Freetype MODULE REQUIRED)
target_link_libraries(mylib Freetype::Freetype)
However, if CMake < 3.10, this still doesn't define a Freetype::Freetype target. One option in this case is to vendor a copy of a more recent version of FindFreetype.cmake in your CMAKE_MODULE_PATH.
For even more robustness, you could even call it Find<Mylib>Freetype.cmake, in the unlikely case where your CMakeLists.txt is used as a subdirectory of another project which also modifies CMAKE_MODULE_PATH and provides an incompatible FindFreetype.cmake.

How to automatically choose minimal cmake version based on used features?

For example I want to specify c++ standard in CMakeLists.txt:
set_property(TARGET tgt PROPERTY CXX_STANDARD 98)
But this is available only since cmake 3.1.3. Unfortunately I am still able to write at the first line of CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
How can I be sure that I specify correct requirements (3.1.3 instead of 2.8)?
The command cmake_minimum_required is not meant to check whether your code can be executed from the requested CMake version. The CMake policies are set depending on the requested CMake version, cf. the documentation.
As a consequence, you have to test your code with the CMake in the version given in cmake_minimum_required and hope to catch all relevant code paths.

CMake dependencies: force recompile on external library change

I'm trying to correctly manage a dependency of a target on a externally built library, and somehow I'm not succeeding. I have read tutorials, posts and examples aplenty and yet, since I'm new to CMake, I guess I'm missing some obvious thing.
Setup is as follows. An external library built in another (CMake unsupported) language produces a libadatest.a. I've used ExternalProject_Add for this. Then, there is another regular C target that uses this lib. Everything works fine, but if I change the original lib, even if I recompile it, the C target is not recompiled. Here is a complete sample. I'm using CMake 2.8.12:
cmake_minimum_required(VERSION 2.8)
include(ExternalProject)
ExternalProject_Add(
AdaTestExternal # Not important
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
BUILD_COMMAND gprbuild -P${CMAKE_CURRENT_SOURCE_DIR}/adalibtest -XOBJ_DIR=${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY} -XLIB_DIR=${CMAKE_CURRENT_BINARY_DIR}
ALWAYS 1 # Force build, gprbuild will take care of dependencies
# BUILD_ALWAYS 1 # For 3.0 higher versions?
INSTALL_COMMAND ""
)
add_custom_target(AdaTest DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/libadatest.a)
link_directories(${CMAKE_CURRENT_BINARY_DIR}) # Needed or won't find it
add_executable(main_ada main.c)
add_dependencies(main_ada AdaTest) # We must depend on the final output lib
target_link_libraries(main_ada adatest)
What I've attempted is to create an intermediate custom target which depends on the actual library, and in turn make the main C target depend on this target.
When I remove the externally built library (libadatest.a), that's properly externally recompiled but the main executable is not re-linked. Plainly seen in that the timestamp of the library is fresher than the executable which uses it.
I've also tried this instead of the custom target, with same negative result:
add_library(AdaTest
UNKNOWN IMPORTED
IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/libadatest.a)
Found the proper solution (which was, as expected, simple) in this old post: http://www.cmake.org/pipermail/cmake/2010-November/041072.html
The gist is to use the actual file in target_link_libraries, so its timestamp is checked. So no need for intermediate or custom dependencies:
set(AdaTestLib ${CMAKE_CURRENT_BINARY_DIR}/libadatest.a)
add_executable(main_ada main.c)
add_dependencies(main_ada AdaTestExternal)
target_link_libraries(main_ada ${AdaTestLib})