2

I am troubleshooting a build on a C++ codebase which uses cmake. The codebase has several popular 3rd party packages which are embedded directly in the source tree.

For one package (say foo) a find_package call fails as it could not find the FooConfig.cmake (or foo-config.cmake). However the foo project is already being added in one of the CMakeLists.txt using add_subdirectory.

  1. Do I need to use find_package if I am using add_subdirectory?
  2. If I am using add_subdirectory, what is the correct way to ensure that all dependents of foo are able to locate the headers and libraries of foo?
Agnel Kurian
  • 57,975
  • 43
  • 146
  • 217
  • 3
    "Do I need to use `find_package` if I am using `add_subdirectory`" - No, `find_package` is not needed when use `add_subdirectory`. "If I am using `add_subdirectory`, what is the correct way to ensure that all dependents of foo are able to locate the headers and libraries of foo?" - It depends from the project you use. Usually, linking with one of its targets is sufficient for provide both libraries and headers. In case of GTest it is linking with `GTest::gtest` or `GTest::gtest_main`. – Tsyvarev Jun 29 '22 at 07:19
  • 1
    `find_package` and `add_subdirectory` do completely different things. `find_package` tries import a library installed on system (or by Conan), which is already built. `add_subdirectory` add other `CMakeLists.txt` from that directory for further processing, which will for example build some library from source or build tests. – Marek R Jun 30 '22 at 11:53

2 Answers2

2

find_package will not automatically succeed if the project was added using add_subdirectory.

If one of your third-party libraries relies on a find_package call to find its dependencies, you will likely have to provide some help in the surrounding build script to ensure it will succeed.

Here is what happens in detail: When a project is pulled in via add_subdirectory, its targets will become visible to the enclosing projects. That is, if subproject A does add_library(a ...), then subproject B can do target_link_libraries(b PUBLIC a) to make use of library a as a dependency for its own executable b.

However, if both libraries are usually don't being built together as part of an enclosing project (which is almost always true for third party code), this is probably not what B will be doing. Instead B will probably do a find_package(A) call and then use as a dependency whatever that find script provides. If you're lucky, the find script will provide an imported target, so you will find a call of the form target_link_libraries(b PUBLIC a) somewhere in B's CMakeLists.txt. If it's an older find script, it may instead provide a bunch of variables and you will find calls like target_link_libraries(b ${A_LIBRARIES}) (and others for include directories and whatever else) instead.

So making this work really all depends on how B's build scripts look like. If you're not at liberty to patch the build script, you might even end up in a situation where what you are asking for cannot be done.

Now, for the particular problem of the find_package call: Find package is used to find a library that has already been built. That is, at the time that CMake runs, either a CMake config script or the library binaries are already on disk. In your scenario, neither of them have been generated, as you are still in the process of configuring the project, and those files won't get generated until you start building. So the find_package call will almost certainly fail out of the box.

You have two options how to potentially resolve this:

  • Have the enclosing project manually set all of the cache variables that the find_package call is trying to fill in. That way any find_* calls inside B's package find script will turn into no-ops, simply reusing the values that you set.
  • Provide your own find script and ensure that B's find_package call will find that one instead of the one it would use in a standalone build.

Again, the specifics are highly project dependent, neither of these is guaranteed to be easy or to work at all. Also, ensuring that a build structured in this way remains portable across generators can be very challenging.


Bottom line: Unless you really have strong reasons for using add_subdirectory, don't do this.

Instead have the enclosing project use a bunch of execute_process calls to build each of the dependencies in isolation1. This will always work and is much easier to set up correctly. Main downside is that all the dependency building happens during the initial CMake configure run of the enclosing project and changes to the dependency's sources will not be tracked and trigger an automatic rebuild.


1: That is: First call cmake and cmake --build (and probably cmake --install) on project A; only then do the same for project B. When configuring B, provide the location information required for finding A on the configure command line: cmake -DA_ROOT=...

ComicSansMS
  • 51,484
  • 14
  • 155
  • 166
0

It's just how @ComicSansMS said. Regarding additional practical solutions. If you look what GoogleTest is doing, you'd see they have two different usage examples, one for add_subdirectory usage, and another for find_package usage. In the former you then use the internal target (without GTest:: in front of it).

Import GoogleTest by using find_package (or pkg_check_modules). For example, if find_package(GTest CONFIG REQUIRED) succeeds, you can use the libraries as GTest::gtest, GTest::gmock.

and the alternative is

include(FetchContent)
FetchContent_Declare(
  googletest
  # Specify the commit you depend on and update it regularly.
  URL ... some url.tgz here ...
)

# ...

FetchContent_MakeAvailable(googletest)

# Now simply link against gtest or gtest_main as needed. Eg
add_executable(example example.cpp)
target_link_libraries(example gtest_main)

What you can do is to add an library alias to unify this, like this issue suggests (duplicate issue 1, duplicate issue 2)

add_library(GTest::Main ALIAS gtest_main)

And, GoogleTest actually implemented this suggestion in CMake: Add namespaced ALIAS library. While I would not much rely on GoogleTest authors for their CMake knowledge, this actually seems like the right thing to do!

user7610
  • 25,267
  • 15
  • 124
  • 150