MarcDefiant
MarcDefiant

Reputation: 6889

What use is find_package() when you need to specify CMAKE_MODULE_PATH?

I'm trying to get a cross-plattform build system working using CMake. Now the software has a few dependencies. I compiled them myself and installed them on my system.

Some example files which got installed:

-- Installing: /usr/local/share/SomeLib/SomeDir/somefile
-- Installing: /usr/local/share/SomeLib/SomeDir/someotherfile
-- Installing: /usr/local/lib/SomeLib/somesharedlibrary
-- Installing: /usr/local/lib/SomeLib/cmake/FindSomeLib.cmake
-- Installing: /usr/local/lib/SomeLib/cmake/HelperFile.cmake

Now CMake has a find_package() which opens a Find*.cmake file and searches after the library on the system and defines some variables like SomeLib_FOUND etc.

My CMakeLists.txt contains something like this:

set(CMAKE_MODULE_PATH "/usr/local/lib/SomeLib/cmake/;${CMAKE_MODULE_PATH}")
find_package(SomeLib REQUIRED)

The first command defines where CMake searches after the Find*.cmake and I added the directory of SomeLib where the FindSomeLib.cmake can be found, so find_package() works as expected.

But this is kind of weird because one of the reasons why find_package() exists is to get away from non-cross-plattform hard coded paths.

How is this usually done? Should I copy the cmake/ directory of SomeLib into my project and set the CMAKE_MODULE_PATH relatively?

Upvotes: 247

Views: 357316

Answers (5)

jpr42
jpr42

Reputation: 1860

Fundamentally find_package is a mechanism to find a library, it's header files, etc. all in 1 convenient method.

As is somewhat implied by the name find_package works best with package managers. Either system package managers or language package managers.

  • system package managers: APT, yum, etc.
  • language package managers: vcpkg, conan, etc.

find_package streamlines using a library from a package manager.

Here is a step by step example using a very popular c++ library (https://github.com/fmtlib/fmt)

1.) Install the library

# Note I'm using APT but it doesn't really matter
sudo apt install libfmt-dev

2.) Find the library in your CMakeLists.txt

project(example_project)

add_executable(foobar PRIVATE main.cpp)

# Find your library and link to it.
find_package(fmt CONFIG REQUIRED)
target_link_libraries(foobar PRIVATE fmt::fmt)

You should now be good to go. If you are using a system package manager.

That's because find_* calls in CMake check default system locations.

To see how CMake found your package you can use --debug-find-pkg.

cmake -S . -B build --debug-find-pkg=fmt --fresh

This will show you all the places CMake is looking for a file called fmt-config.cmake.

On my machine it was found here:

/usr/lib/x86_64-linux-gnu/cmake/fmt/fmt-config.cmake

--debug-find is a VERY useful tool for figuring out how CMake is looking for things.

What about non-system package managers?

There are other ways to describe where packages are located. A very common one is modifying CMAKE_PREFIX_PATH.

From the documentation

CMAKE_PREFIX_PATH is a semicolon-separated list of directories specifying installation prefixes to be searched by the find_package(), find_program(), find_library(), find_file(), and find_path() commands. Each command will add appropriate subdirectories (like bin, lib, or include) as specified in its own documentation. By default CMAKE_PREFIX_PATH is empty. It is intended to be set by the project.

Package managers like vcpkg handle this automatically for you. Making it as seamless as if you were using a system package manger.

Config vs module searching

Now as your original question alludes to there is some confusion in finding things.

There are 2 main modes for finding things.

  • Module mode

In this mode, CMake searches for a file called Find<PackageName>.cmake, looking first in the locations listed in the CMAKE_MODULE_PATH, then among the Find Modules provided by the CMake installation.

  • Config mode

In this mode, CMake searches for a file called -config.cmake or Config.cmake.

Now as you may notice are above example used Config mode.

Config mode is generally the desirable solution IF available. Since it's basically requires no work from you. Module mode requires a CMake module be written to help you find things.

CMake actually has a few built in Module mode helpers.

https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html#find-modules

Like the FindPython3 module. Here is a tiny example

find_package(Python3 REQUIRED)
message(STATUS "${Python3_EXECUTABLE}")

As shown in this python example, find_package is more generically speaking a way to find multiple files that belong together in an easy to consume way.

NOTE:

Because these modules are built in you don't need to modify your CMAKE_MODULE_PATH. They are already provided for you. Of course you can make your own if you need to.

Final Notes

I'm hoping my answer is more illuminating than confusing and helped you better understand find_package.

For more details on finding behavior in CMake I recommend Craig Scott's book Professional CMake.

https://crascit.com/professional-cmake/

Upvotes: 7

user2288008
user2288008

Reputation:

Command find_package has two modes: Module mode and Config mode. You are trying to use Module mode when you actually need Config mode.

Module mode

Find<package>.cmake file located within your project. Something like this:

CMakeLists.txt
cmake/FindFoo.cmake
cmake/FindBoo.cmake

CMakeLists.txt content:

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
find_package(Foo REQUIRED) # FOO_INCLUDE_DIR, FOO_LIBRARIES
find_package(Boo REQUIRED) # BOO_INCLUDE_DIR, BOO_LIBRARIES

include_directories("${FOO_INCLUDE_DIR}")
include_directories("${BOO_INCLUDE_DIR}")
add_executable(Bar Bar.hpp Bar.cpp)
target_link_libraries(Bar ${FOO_LIBRARIES} ${BOO_LIBRARIES})

Note that CMAKE_MODULE_PATH has high priority and may be usefull when you need to rewrite standard Find<package>.cmake file.

Config mode (install)

<package>Config.cmake file located outside and produced by install command of other project (Foo for example).

foo library:

> cat CMakeLists.txt 
cmake_minimum_required(VERSION 2.8)
project(Foo)

add_library(foo Foo.hpp Foo.cpp)
install(FILES Foo.hpp DESTINATION include)
install(TARGETS foo DESTINATION lib)
install(FILES FooConfig.cmake DESTINATION lib/cmake/Foo)

Simplified version of config file:

> cat FooConfig.cmake 
add_library(foo STATIC IMPORTED)
find_library(FOO_LIBRARY_PATH foo HINTS "${CMAKE_CURRENT_LIST_DIR}/../../")
set_target_properties(foo PROPERTIES IMPORTED_LOCATION "${FOO_LIBRARY_PATH}")

By default project installed in CMAKE_INSTALL_PREFIX directory:

> cmake -H. -B_builds
> cmake --build _builds --target install
-- Install configuration: ""
-- Installing: /usr/local/include/Foo.hpp
-- Installing: /usr/local/lib/libfoo.a
-- Installing: /usr/local/lib/cmake/Foo/FooConfig.cmake

Config mode (use)

Use find_package(... CONFIG) to include FooConfig.cmake with imported target foo:

> cat CMakeLists.txt 
cmake_minimum_required(VERSION 2.8)
project(Boo)

# import library target `foo`
find_package(Foo CONFIG REQUIRED)

add_executable(boo Boo.cpp Boo.hpp)
target_link_libraries(boo foo)
> cmake -H. -B_builds -DCMAKE_VERBOSE_MAKEFILE=ON
> cmake --build _builds
Linking CXX executable Boo
/usr/bin/c++ ... -o Boo /usr/local/lib/libfoo.a

Note that imported target is highly configurable. See my answer.

Update

Upvotes: 334

einpoklum
einpoklum

Reputation: 132138

How is this usually done? Should I copy the cmake/ directory of SomeLib into my project and set the CMAKE_MODULE_PATH relatively?

If you don't trust CMake to have that module, then - yes, do that - sort of: Copy the find_SomeLib.cmake and its dependencies into your cmake/ directory. That's what I do as a fallback. It's an ugly solution though.

Note that the FindFoo.cmake modules are each a sort of a bridge between platform-dependence and platform-independence - they look in various platform-specific places to obtain paths in variables whose names is platform-independent.

Upvotes: 3

Ryan Feeley
Ryan Feeley

Reputation: 667

If you are running cmake to generate SomeLib yourself (say as part of a superbuild), consider using the User Package Registry. This requires no hard-coded paths and is cross-platform. On Windows (including mingw64) it works via the registry. If you examine how the list of installation prefixes is constructed by the CONFIG mode of the find_packages() command, you'll see that the User Package Registry is one of elements.

Brief how-to

Associate the targets of SomeLib that you need outside of that external project by adding them to an export set in the CMakeLists.txt files where they are created:

add_library(thingInSomeLib ...)
install(TARGETS thingInSomeLib Export SomeLib-export DESTINATION lib)

Create a XXXConfig.cmake file for SomeLib in its ${CMAKE_CURRENT_BUILD_DIR} and store this location in the User Package Registry by adding two calls to export() to the CMakeLists.txt associated with SomeLib:

export(EXPORT SomeLib-export NAMESPACE SomeLib:: FILE SomeLibConfig.cmake) # Create SomeLibConfig.cmake
export(PACKAGE SomeLib)                                                    # Store location of SomeLibConfig.cmake

Issue your find_package(SomeLib REQUIRED) commmand in the CMakeLists.txt file of the project that depends on SomeLib without the "non-cross-platform hard coded paths" tinkering with the CMAKE_MODULE_PATH.

When it might be the right approach

This approach is probably best suited for situations where you'll never use your software downstream of the build directory (e.g., you're cross-compiling and never install anything on your machine, or you're building the software just to run tests in the build directory), since it creates a link to a .cmake file in your "build" output, which may be temporary.

But if you're never actually installing SomeLib in your workflow, calling EXPORT(PACKAGE <name>) allows you to avoid the hard-coded path. And, of course, if you are installing SomeLib, you probably know your platform, CMAKE_MODULE_PATH, etc, so @user2288008's excellent answer will have you covered.

Upvotes: 7

zjm555
zjm555

Reputation: 861

You don't need to specify the module path per se. CMake ships with its own set of built-in find_package scripts, and their location is in the default CMAKE_MODULE_PATH.

The more normal use case for dependent projects that have been CMakeified would be to use CMake's external_project command and then include the Use[Project].cmake file from the subproject. If you just need the Find[Project].cmake script, copy it out of the subproject and into your own project's source code, and then you won't need to augment the CMAKE_MODULE_PATH in order to find the subproject at the system level.

Upvotes: 2

Related Questions