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
ExternalProject_Add() to invoke the code generator subdirectory’s
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
DEPENDS keyword referenced the
OUTPUT file. I’d then have the code generation targets depend on this convenience target. But, that won’t work for
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:
add_library(SomeProjLib STATIC IMPORTED)
set_property(TARGET SomeProjLib PROPERTY IMPORTED_LOCATION ".../libSomeProj.a")
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:
set_property(TARGET SomeProjExe PROPERTY IMPORTED_LOCATION ".../SomeProj/AnExe")
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.