Pulling in ExternalProject_Add from other subproject in add_subdirectory

Greetings,

I have a cmake project that combines some external dependencies with subdirectories.

The first dependency is pulled in via FetchContent_Dependency / FetchContent_MakeAvailable. This dependency in its own CMakeLists.txt pulls in libceres as prebuilt binary via ExternalProject_Add.

After the first dependency’s FetchContent_MakeAvailable call, a subdirectory is added via add_subdirectory on top level. The files in this directory could also need libceres which I try to pull in from the first dependency via find_package ( ceres .

Problem: The ExternalProject_Add in the first dependency does not download libceres before the build process of the add_subdirectory files starts although FetchContent_MakeAvailable comes before the latter in the top level CMakeLists.txt.

Any idea why?

Regards

You’re trying to mix projects in a way they aren’t really designed to be. If a dependency forces using ExternalProject_Add() to bring in its own dependencies, I don’t recommend you pull it into your project with FetchContent. Pretty much once any dependency uses ExternalProject, it doesn’t play nice with any other project wanting to use it as a dependency.

In your situation, I would not add your immediate dependency with FetchContent. Find a way to have that dependency pre-built and bring it in as a binary package using find_package(). You’ll have to find a way to do that which fits within your project’s constraints. There are different solutions depending on what the dependency is and what rules you have to follow for your project. In an ideal scenario, it’s a commonly used dependency and you’re allowed to use a package manager like Conan to bring it into your build.

Yeah I feared something like that.

Just out of curiosity: Can you tell me which command actually triggers the download and deployment of an ExternalProject_Add dependency?

Regards

The download part is an internal detail of ExternalProject_Add(). The actual command(s) called depend on the download method used. It might use file(DOWNLOAD), or it might call a command line tool like git. These are private details that the calling project shouldn’t care about nor try to interfere with.

As for the deployment part, that’s the INSTALL step of ExternalProject_Add(). For a CMake-based external project, this will default to building the install target. For non-CMake-based external projects, the default assumes a Makefiles-based project and does a make install. Projects can override these defaults and specify their own install command with INSTALL_COMMAND.

And the install step of the ExternalProject_Add is not finished before FetchContent_MakeAvailable of the parent?

No part of ExternalProject_Add() executes during the configure step. All parts of that only happen at build time. The FetchContent_MakeAvailable() command, on the other hand, executes entirely at configure time. If you bring in a dependency “A” via FetchContent, and that dependency brings in another dependency “B” by ExternalProject, no part of “B” will be available when FetchContent_MakeAvailable(A) returns. Not even the configure step for “B” will have been executed, that only happens at build time.

But the build complains about lacking includes from ceres while it builds the add_subdirectory. Shouldn’t ExternalProject_Add finish before this build step is started?

You are confusing terms. When you run CMake, that’s the configure and generation phase. The purpose of those is to generate the files that the build tool needs (eg. Makefile, build.ninja, and so on). The build hasn’t started yet. You invoke the build tool after configure and generation to perform the build step. All the FetchContent stuff happens during configure. All the ExternalProject stuff happens during the build. There is no overlap other than the ExternalProject_Add() command is called during configure, but that only defines build rules. It doesn’t do any of the external project steps at that time.

Yeah ExternalProject_Add creates a target and add_subdirectory goes into the main target of the top CMakeLists.txt where the fetch is yes? Shouldn’t the targets created by the ExternalProject_Add be processed before the final one of the main target?

The targets are, but nothing has executed them yet. Nothing from libceres will exist until cmake --build is performed.

Yeah but the cmake --build of ceres should be executed before the cmake --build of the main target, right? So why does the main target compile complain about lacking ceres headers when ceres hasn’t even downloaded yet?

Can you share the error message you’re getting?

It complains that it doesn’t find a header of ceres during the build of the main target. The reason is that ceres has not been downloaded at the time the main target is built although it should I think.

Something is missing a dependency on ceres completing. It’s hard to say what, exactly, without the project, but basically anything directly linking to the results of building the ceres project should get add_dependencies(uses-ceres ceres).