Run custom command before some project build in Superbuild

Hi,

The schema of my project is something like:

MyProject |
          FetchContent_Populate(MyDep) |
                                        ExternalProject_Add(ExternalDep_1)
                                        ExternalProject_Add(ExternalDep_2)
                                        ExternalProject_Add(...)

That means that my project MyProject fetches other project MyDep and that other project uses superbuild to to build all its dependencies via ExternalProject_Add.

I need to run some command (probably by using add_custom_command) when MyDep is ready to be built (all ExternalProject_Add are built). I guess this is similar to run the command while MyDep buildtime (but when all its deps are already built).

Is there a way to achieve that?

I think using ExternalProject_add_step would be the way to do this (with DEPENDEES build).

Thank you for reply,

I’ve just read the documentation on ExternalProject_add_step and it seems that this command only adds the steps for targets that are created via ExternalProject_Add.

In my case my project depends on MyDep project that uses add_library() command to create target and to build its dependencies it uses ExternalProject_Add().

I need to run the command after all ExternalProject_Add() dependencies of MyDep are built but before MyDep starts building.

I’m sorry if I confused you

EDIT:
I just understood that probably add_custom_command second use signature may do what I need. Probably I should try that

add_custom_command(
  TARGET MySubDep PRE_BUILD
  COMMAND 
    python -m editcpp 
    --src-file=src.cxx
    --dest-file=out.cxx
    --oldfile-keep 
    -std=c++17
    \"$<$<BOOL:$<TARGET_PROPERTY:INCLUDE_DIRECTORIES>>:-I$<JOIN:$<TARGET_PROPERTY:INCLUDE_DIRECTORIES>, -I>>\"
  VERBATIM)

but I get error:

No TARGET 'MySubDep' has been created in this directory.

MySubDep is a dependency of MyDep that is created via add_library.
Is there a way to tell the cmake that somewhere in subfolder there is a target MySubDep and it should trust me? :slight_smile:

Yeah, I don’t think you can attach additional {PRE,POST}_BUILD commands to “remote” targets. You should be able to add your own target that depends on the dependencies and is then depended on by MySubDep using add_dependencies though.

Thank you for the hints but it seems that this doesn’t work (Cannot add target-level dependencies to non-existent target "MySubDep".)…

# Somwhere within external project:
add_library(MySubDep somefile.cxx)
target_link_libraries(MySubDep lib_0 lib_1)

#---------------another CMakeLists.txt-------------------#
# My top level project:
# I add `FakeTarget` that depends on `lib_0` and `lib_1` and 
# at the same time `MySubDep` should depend on `FakeTarget`
add_custom_target(FakeTarget 
  COMMAND 
    editcpp 
    --src-file=src.cxx
    --dest-file=out.cxx
    --oldfile-keep 
    -std=c++17
    \"-I$<JOIN:$<TARGET_PROPERTY:MySubDep ,INCLUDE_DIRECTORIES>, -I>\"
    VERBATIM)
add_dependencies(FakeTarget lib_0 lib_1)
add_dependencies(MySubDep FakeTarget)

If I misunderstood you could you please explain a little more?

I think this looks fine except that why MySubDep is not around is odd. Does it pass an if (TARGET MySubDep) conditional?

Thank you for response,

You were right, there is no if (TARGET MySubDep) but there is another condition that checks if superbuild is ON then it doesn’t add add_subdirectory() with the MySubDep target…
I need to discover how those libs are added in superbuild mode.

I will report back when I have progress

The system works in the following manner:

  1. MyProject uses FetchContent_Populate(MyDep) ;
  2. MyDep uses superbuild to build its dependencies as well as itself (i.e. uses ExternalProject_Add(MyDep) with -DUSE_SUPERBUILD:BOOL=OFF);
  3. MyDep creates MySubDep via add_library(MySubDep mysubdep.cpp);
  4. MySubDep has its dependencies that are added via target_link_libraries(MySubDep lib1 lib2 ...);

My task is to modify source code of mysubdep.cpp (I need to change the implementation of some functions there) before MySubDep is built. To do that (to parse .cpp files) I use my tool that uses clang and I need to get all dirs that are linked to MySubDep (that is why I use something like -I$<JOIN:$<TARGET_PROPERTY:MySubDep ,INCLUDE_DIRECTORIES>, -I>)

I hope now my task is clearer and I’m sorry that it took so long to explain.

For now I think the simplest solution would be modify the CMakeLists.txt where MySubDep target is created by appending custom command to it:

set(cmake_file "~/MyProject/MyDep/MySubDep/CMakeLists.txt")
file(READ ${cmake_file} MySubDepFile)

string(FIND ${MySubDepFile} 
  "EDITED BY ME"
  isMySubDepFileEdited)

if(isMySubDepFileEdited LESS 0)
  # write the command to a variable
  set(MY_COMMAND
  "
  # EDITED BY ME

  add_custom_command(
    TARGET MySubDep PRE_BUILD
    COMMAND 
      editcpp 
      --src-file=mysubdep.cpp
      --dest-file=~/MyProject/MyDep/MySubDep/mysubdep.cpp
      --oldfile-keep 
      -std=c++17
      \"-I$<JOIN:$<TARGET_PROPERTY:MySubDep,INCLUDE_DIRECTORIES>, -I>\"
    VERBATIM)
  "
  )

  # append my command to `CMakeLists.txt` where `MySubDep` is added as a target
  file(APPEND  
    ${cmake_file}
    ${MY_COMMAND}
  )
endif()

but I feel that this is not the best way to solve the problem…

FetchContent uses ExternalProject under the hood. You can use PATCH_COMMAND to patch the source.

I think you gave me all the necessary info I need to know to solve my problem, I appreciate that.
Now I need some time to understand and to test that.
Thank you very much!

Is there a way to add to variable ${VAR} without substitution value of a VAR?
For example I need to prepare variable and write it as is to a file:

  set(MY_COMMAND
  "
  get_target_property(tgt_deps MySubDep LINK_LIBRARIES)

  add_custom_command(
    TARGET MySubDep PRE_BUILD
    COMMAND 
      editcpp 
      --src-file=mysubdep.cpp
      --dest-file=~/MyProject/MyDep/MySubDep/mysubdep.cpp
      --oldfile-keep 
      -std=c++17
      \"-I$<JOIN:$<TARGET_PROPERTY:MySubDep,INCLUDE_DIRECTORIES>, -I>\"
    DEPENDS \"${tgt_deps}\"
    VERBATIM)
  "
  )

file(APPEND ~/CMakeLists.txt ${MY_COMMAND})

I would like to see that tgt_depsis written in modified CMakeLists without substitution its value (like ${tgt_deps}).
I’ve seen that configure_file is able to do something similar with @ONLY parameter set, and also file(CONFIGURE ...) but that is not an exact solution to what I would do I think.

You can escale the $:

\${tgt_deps}

Also consider using bracket syntax instead of double-quotes. Bracket syntax doesn’t require you to escape the contents and does not dereference variables. E.g.

set(MY_COMMAND [==[
  get_target_property(tgt_deps MySubDep LINK_LIBRARIES)

  add_custom_command(
    TARGET MySubDep PRE_BUILD
    COMMAND 
      editcpp 
      --src-file=mysubdep.cpp
      --dest-file=~/MyProject/MyDep/MySubDep/mysubdep.cpp
      --oldfile-keep 
      -std=c++17
      "-I$<JOIN:$<TARGET_PROPERTY:MySubDep,INCLUDE_DIRECTORIES>, -I>"
    DEPENDS "${tgt_deps}"
    VERBATIM)
]==])

Thank you, I didn’t know that