Search code examples
cmakedependenciescross-compiling

How can I have the dependencies on an external project target be automatically rebuilt 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.

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 the code generator target were configured using Add_Custom_Command(), then I'd use the OUTPUT keyword to specify the resulting executable file, and any dependent targets could be configured to automatically rebuild when the code generator executable's timestamp was updated.

(Edit for clarity: after specifying an OUTPUT file for Add_Custom_Command(), 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, AFAIK, that won't work for me.)

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?


Solution

  • I needed to IMPORT a target based on the code generator executable. In my top-level CMakeLists.txt:

    # Run next-gen code generation
    # Use ExternalProject_Add() because we are building this for the host
    # We DO NOT want cross-compile toolchain applied here
    set(CODEGEN_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/codegen/_build/codegen)
    ExternalProject_Add(codegenExecutable
      SOURCE_DIR        ${CMAKE_CURRENT_LIST_DIR}/codegen
      # The two "--unset" options tell it to use the host defaults rather than the configured ones.
      CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env --unset=CC --unset=CXX ${CMAKE_COMMAND} -H. -B_build
      INSTALL_COMMAND   ""
      BUILD_IN_SOURCE   TRUE
      EXCLUDE_FROM_ALL  TRUE
      BUILD_ALWAYS      TRUE
    )
    
    # Imported target, so that any change in the code generator will result in rerunning the
    # code generation.
    add_executable(codegen IMPORTED)
    set_property(
        TARGET codegen
        PROPERTY
            IMPORTED_LOCATION "${CODEGEN_EXECUTABLE}"
    )
    add_dependencies(codegen codegenExecutable)
    

    Any of the code generation targets are set to depend on codegen. The first time I build:

    1. The codegenExecutable external project is built, creating the ${CODEGEN_EXECUTABLE} executable
    2. The codegen target's timestamp is set by the executable's timestamp
    3. All of the code generation tasks that depend on codegen are rebuilt

    With no changes, subsequent builds do the following:

    1. The codegenExecutable external project is built, but since none of its source has changed the executable isn't rebuilt and doesn't change its timestamp
    2. The codegen target's timestamp doesn't change
    3. The code generation tasks that depend on codegen aren't rebuilt

    If I touch any codegen source and build, then:

    1. The codegenExecutable external project is built, creating a new ${CODEGEN_EXECUTABLE} executable with an updated timestamp
    2. The codegen target's timestamp is updated by the executable's timestamp
    3. The code generation tasks that depend on codegen are rebuilt

    I believe that if I set up an EXPORTed target from the codegen's CMakeLists.txt then I wouldn't have to hard-code the executable path. A task for the future...