Rebuild external project's dependent targets when the external project changes?

I’m working on a cross-compilation project with a top-level CMakeLists.txt file generating Makefiles. Some of the cross-compiled source code is generated by a host-based code generation tool; since that tool needs a different toolchain, the top-level CMakeLists.txt uses ExternalProject_Add() to invoke the code generator subdirectory’s CMakeLists.txt, with BUILD_ALWAYS set to TRUE. The code generation targets depend on the code generator target, so during the initial build the code generator is built, and then the code is generated, and then the whole shebang is compiled into the final result. (To be clear: there’s no downloading involved; all of the external project code is local.)

If I change the source of the code generator then the code generator executable will be properly rebuilt on the next make. Problem: the dependent code generation targets will NOT be rebuilt. There seems to be no way with an ExternalProject_Add() target to indicate when/if any dependencies of the target should be rebuilt.

For instance, if my code generator target were configured using Add_Custom_Command(), then I’d use its OUTPUT keyword to specify the resulting executable file. I’d then create a convenience target using Add_Custom_Target() whose DEPENDS keyword referenced the Add_Custom_Command()'s OUTPUT file. I’d then have the code generation targets depend on this convenience target. But, that won’t work for ExternalProject_Add().

Is there a way to configure an ExternalProject_Add()-based target so that dependent targets will be automatically rebuilt when the main target’s output changes?

P.S. This is similar to the following post: Generate dependency on external projects .However, in my case nothing is being downloaded, and the concern is not the rebuilding of the code generator when its source changes (that works), but the rebuilding of the code generation targets when the code generator changes (that doesn’t).

The artifacts of the external project are not available at the time you’re configuring the main project. You need to tell CMake about them by creating imported targets. For example:

ExternalProject_Add(SomeProj ...)

add_library(SomeProjLib STATIC IMPORTED)
set_property(TARGET SomeProjLib  PROPERTY IMPORTED_LOCATION ".../libSomeProj.a")
add_dependencies(SomeProjLib SomeProj)

add_executable(main ...)
target_link_libraries(main PRIVATE SomeProjLib)

That will give main a dependency on the SomeProj external target to ensure that the artifact is available before main is built.

Of course this requires carefully coordinating the external project’s configuration and the imported target’s IMPORTED_LOCATION to know exactly where the file will be, and its name can be per-platform.

Another approach is to use a separate “superbuild” project that doesn’t compile anything directly but instead uses ExternalProject_Add for each external dependency and again for your main project. That way your main project can be written to assume its dependencies are already available, and can use normal exporting and importing as documented here and here. That avoids needing to hand-import anything.

Excellent: your imported target example works. Many thanks.

I ended up hard-coding the location of the resulting generator executable in the top-level CMakeLists.txt, partly because the target export example seemed to require installing the executable somewhere, when I only need it during the build process and don’t want to clutter up my dev system.

Is there a way to export an executable target without actually installing it somewhere?

Further down in that same page is an example of exporting from a build tree.

Or, you can use the same hand-imported approach as above, but for an executable:

add_executable(SomeProjExe IMPORTED)
set_property(TARGET SomeProjExe PROPERTY IMPORTED_LOCATION ".../SomeProj/AnExe")
add_dependencies(SomeProjExe SomeProj)

Interesting, but unfortunately the exported *.cmake file won’t exist until the code generator’s CMakeLists.txt has generated its output, which will be after the top level CMakeLists.txt has generated its own Makefiles. So, if I hand-trigger the building of the code generator then the main build will work, and will work every time after that, but I can’t do it as a single step from a clean build.

I tried adding a add_subdirectory(codegen) line higher up in the top CMakeLists.txt, hoping that that would generate the exported *.cmake file, but it still threw an error that the *.cmake wasn’t there. I’m guessing that it never got to the generation phase.

Eh; it’s far better than it used to be, and thanks for the help.

Dan