206

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?

John
  • 1,927
  • 6
  • 18
MarcDefiant
  • 6,321
  • 5
  • 26
  • 47
  • That pattern seems very weird to me. Libraries using CMake are not supposed to expose their 'find' module this way. How did you come up with such a way to find that "SomeLib" ? And which lib is it ? – SirDarius Dec 23 '13 at 16:08
  • 2
    Something similar is done in http://www.cmake.org/Wiki/CMake:How_To_Find_Libraries#Using_external_libraries_that_CMake_doesn.27t_yet_have_modules_for . And it's OGRE. – MarcDefiant Dec 23 '13 at 16:33
  • 3
    The section you link to mentions this: "Since CMake (currently) doesn't ship it, you'll have to ship it within your project." This is what I have done in flvmeta to find LibYAML (see https://github.com/noirotm/flvmeta/tree/master/cmake/modules). The module path points to this directory, inside my project. – SirDarius Dec 24 '13 at 10:32
  • 3
    I usually copy FindXXX modules to my project and set CMAKE_MODULE_PATH (if those modules not present in CMake of course), I've also seen this pattern many times in other projects – szx Dec 25 '13 at 23:19

4 Answers4

276

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

Community
  • 1
  • 1
  • 1
    Your answer is great. However, the example at github is more complex that it can be IMO. In the common case where a subdirectory (module) exports a single artifact, lets say a lib along with the headers, you don't need to generate custom *Config.cmake. As a result the configuration can be cut down significantly. I think I'll make a similar example myself. – Dimitrios Menounos Jan 21 '15 at 08:58
  • 2
    @Dimitris Yes, it can be simplified a little bit. I've updated github example so now it doesn't use `configure_package_config_file`. By the way if you have any other suggestions you can send me pull request. –  Feb 03 '15 at 23:18
  • 1
    @rusio Here is my [example](https://github.com/scumm/foobar). It supports monolithic build (all modules from the root folder) or autonomous builds (each module separately, requires install). – Dimitrios Menounos Apr 28 '15 at 00:14
  • 1
    @Dimitris Okay, now I see. Usually the file that you "optimize away" serve for loading extra stuff like [find_dependency](http://www.cmake.org/cmake/help/v3.2/module/CMakeFindDependencyMacro.html). I think it's a good template to start so I will keep it even it's not used in fact. The rest of the code looks more simplier because you're missing some functionality like version, export for dll, layout with `bin/lib` (try to install executable and run it on windows). And namespaces look very pretty, so I will keep them too :) Also I've added `monolithic` build. –  Apr 30 '15 at 15:10
  • 1
    Each of your examples were very helpful to me. Thank you both! – zmb Aug 24 '15 at 20:43
  • 1
    @Dimitris `Yes, it can be simplified a little bit. I've updated github example so now it doesn't use configure_package_config_file` just for your information I'm [back](https://github.com/forexample/package-example/commit/cf2ea1d6a209fb9eca2ab83fdd0ac15fe4d3e807) to using this macro since it has a important module `check_required_components`. It can be used to detect misprints in `find_package`, like `find_package(Foo REQUIRE error string)`. CMake think that `error` and `string` is a components and without `check_required_componets` will just ignore the error. –  Sep 21 '15 at 16:20
  • @ruslo Thanks for the answer. It helped me clear my confusion. I still have one follow up however, how to understand "By default, `CMAKE_MODULE_PATH` is empty." on the [official documentation](https://cmake.org/cmake/help/latest/variable/CMAKE_MODULE_PATH.html#variable:CMAKE_MODULE_PATH). How did you figure out that in `Module` mode, the default module search path will be under directory "/cmake/"? Thanks! – Dreamer Nov 28 '17 at 22:13
  • Great answer. Though, I wish by default Module mode was the fallback to Config mode, not the other way around. If you use find_package(Foo REQUIRED) it searches for a module then config. If you use find_package(Foo [CONFIG|MODULE] REQUIRED), it will only search the one you selected. See my [question about this](https://stackoverflow.com/questions/50978724/how-can-i-make-find-package-search-with-config-mode-and-fallback-on-module-mode/50983667#50983667) – johnb003 Jun 22 '18 at 16:31
5

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.

Ryan Feeley
  • 499
  • 6
  • 12
2

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.

einpoklum
  • 102,731
  • 48
  • 279
  • 553
1

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.

zjm555
  • 741
  • 5
  • 14
  • 12
    `their location is in the default CMAKE_MODULE_PATH` by default `CMAKE_MODULE_PATH` is empty –  Dec 31 '13 at 12:02
  • Can confirm @user2288008's comment in 2018. `CMAKE_MODULE_PATH` is empty on Windows. – Jeroen May 12 '18 at 14:37
  • 1
    It is a project specific variable, for modules shipping with your project. "By default it is empty, it is intended to be set by the project." https://cmake.org/cmake/help/latest/variable/CMAKE_MODULE_PATH.html – Farway Jun 27 '19 at 09:29