8

This might be an x y problem, so here's my situation.

Background


I have the following project structure:

-project
    -examples
        -example_that_uses_mylib_1
            * CMakeLists.txt
            * main.cpp
        -example_that_uses_mylib_2
            * CMakeLists.txt
            * main.cpp
    -external
        -notmylib_a
            * CMakeLists.txt
            * ... (other stuff)
        -notmylib_b
            * CMakeLists.txt
            * ... (other stuff)
    -src
        -mylib_stuff
            * file1.cpp
            * file1.h
        *CMakeLists.txt
    CMakeLists.txt

I'm attempting to make a cmake file that does the following:

  • allows mylib to depend on targets in 3rd party libraries found in external with out using add_subdirectory given that they aren't actually sub directories and that is bad practice.

  • allows example_that_uses_mylib executables that depend on mylib target with out adding it as a sub directory.

I was unsure of how to do this until I saw this project whos top level cmake does this:

add_subdirectory(lib/foo)
add_subdirectory(src/bar)
add_subdirectory(src/baz)

and bar and baz CMakeLists.txt do this:

#Bar
find_package(foo 0.1.2 CONFIG REQUIRED)

#Baz
find_package(bar CONFIG REQUIRED)

Which made me think I could do the same with my libraries. I couldn't.

Originally the single top level CMakeLists.txt built all targets, I wanted to move away from this, and split up building with add_subdirectories, starting with mylib.

Problem


Originally my top level CMakeLists.txt looked a lot like this (which previously worked):

add_subdirectory(external/notmylib_a)
add_library(mylib STATIC src/mylib_stuff/file1.cpp)
target_include_directories(mylib PUBLIC src/)
target_link_libraries(mylib PRIVATE notmylib_a::notmylib_a)

when I decided to split things up so originally I did this (which also works):

#CMakeLists.txt
add_subdirectory(external/notmylib_a)


#src/CMakeLists.txt
add_library(mylib STATIC src/mylib_stuff/file1.cpp)
target_include_directories(mylib PUBLIC src/)
target_link_libraries(mylib PRIVATE notmylib_a::notmylib_a)

Then to follow the other project I decided to do this:

#CMakeLists.txt
add_subdirectory(external/notmylib_a)


#src/CMakeLists.txt
find_package(notmylib_a CONFIG REQUIRED) #NEW LINE!!
add_library(mylib STATIC src/mylib_stuff/file1.cpp)
target_include_directories(mylib PUBLIC src/)
target_link_libraries(mylib PRIVATE notmylib_a::notmylib_a)

and I got an error in CMAKE

CMake Error at src/CMakeLists.txt:25 (find_package):
  Could not find a package configuration file provided by "notmylib_a"
  with any of the following names:

    notmylib_aConfig.cmake
    notmylib_a-config.cmake

  Add the installation prefix of "notmylib_a" to CMAKE_PREFIX_PATH or set
  "notmylib_a_DIR" to a directory containing one of the above files.  If
  "notmylib_a" provides a separate development package or SDK, be sure it
  has been installed.

How was the other project able to utilize find_package in such a way?

Community
  • 1
  • 1
Krupip
  • 4,404
  • 2
  • 32
  • 54

2 Answers2

8

How was the other project able to utilize find_package in such a way?

Config file for other foo project has these lines:

if(NOT TARGET foo::foo)
    include("${foo_CMAKE_DIR}/foo-targets.cmake")
endif()

That is, when the foo is included with add_subdirectory approach and creates foo::foo target, find_package(foo) actually ignores its configuration file.

This is noted in the foo's CMakeLists.txt:

# We also add an alias definition so that we shadown
# the export namespace when using add_subdirectory() instead.
add_library(foo::foo ALIAS foo)

In other words, with given foo package included with add_subdirectory approach, using find_package(foo) is possible, but optional: One may directly use foo or foo::foo target without any find_package().

Tsyvarev
  • 60,011
  • 17
  • 110
  • 153
3

The example project that you refer to is able to do:

#Bar
find_package(foo 0.1.2 CONFIG REQUIRED)

#Baz
find_package(bar CONFIG REQUIRED)

because both of the sub-projects lib/foo and src/bar have CMakeLists.txt files that contain CMake code that generates a CMake package configuration file that will be searched for by find_package when CONFIG mode is specified, discovery of which will constitute success for the find_package command.

In lib/foo/CMakeLists.txt, for example, such code includes:

include(CMakePackageConfigHelpers)
...
set(PROJECT_CONFIG_FILE         "${PROJECT_BINARY_DIR}/foo-config.cmake")
...
configure_package_config_file(cmake/foo-config.cmake.in
        ${PROJECT_CONFIG_FILE}
        INSTALL_DESTINATION ${INSTALL_CONFIG_DIR})

As a result, when cmake is run to generate the project build files, the package configuration file is among the files generated, like so:

$ git clone https://github.com/sunsided/cmake.git
...
$ cd cmake/
$ mkdir build
$ cd build
$ cmake ..
...
$ find -name '*-config.cmake'
./lib/foo/foo-config.cmake
./src/bar/bar-config.cmake

Those two *-config.cmake files are respectively the package configuration files for lib/foo and src/bar. The documentation for find_package CONFIG mode describes the (complex) search algorithm by which find_package will discover them. For sub-project src/bar,

find_package(foo 0.1.2 CONFIG REQUIRED)

is able to find lib/foo/foo-config.cmake because, when it runs, the build files for its dependency lib/foo have already been generated. Likewise for sub-project src/baz,

find_package(bar CONFIG REQUIRED)

succeeds because the build files for its dependency src/bar have already been generated.

The CMake error that you receive from your own attempt to use find_package in the same way:

CMake Error at src/CMakeLists.txt:25 (find_package):
  Could not find a package configuration file provided by "notmylib_a"
  with any of the following names:

    notmylib_aConfig.cmake
    notmylib_a-config.cmake
    ...

now has an obvious meaning. To fix it, you need to fill in the missing CMake code in the external/notmylib_a/CMakeLists.txt to generate notmylib_a-config.cmake.

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182