Skip to content

Instantly share code, notes, and snippets.

@socantre
Created August 24, 2015 03:45
Show Gist options
  • Save socantre/7ee63133a0a3a08f3990 to your computer and use it in GitHub Desktop.
Save socantre/7ee63133a0a3a08f3990 to your computer and use it in GitHub Desktop.
Example of using add_custom_command and add_custom_target together in CMake to handle custom build steps with minimal rebuilding: This example untars library headers for an INTERFACE library target
set(LIBFOO_TAR_HEADERS
"${CMAKE_CURRENT_BINARY_DIR}/include/foo/foo.h"
"${CMAKE_CURRENT_BINARY_DIR}/include/foo/foo_utils.h"
)
add_custom_command(OUTPUT ${LIBFOO_TAR_HEADERS}
COMMAND ${CMAKE_COMMAND} -E tar xzf "${CMAKE_CURRENT_SOURCE_DIR}/libfoo/foo.tar"
COMMAND ${CMAKE_COMMAND} -E touch ${LIBFOO_TAR_HEADERS}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/foo"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/libfoo/foo.tar"
COMMENT "Unpacking foo.tar"
VERBATIM
)
add_custom_target(libfoo_untar DEPENDS ${LIBFOO_TAR_HEADERS})
add_library(foo INTERFACE)
target_include_directories(foo INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/include/foo")
target_link_libraries(foo INTERFACE ${FOO_LIBRARIES})
@crownk1997
Copy link

@PhilippeCarphin Hi, could you please tell me why we need add_custom_target here? I think only the command add_custom_command can do what we want to. Thank you so much!

@PhilippeCarphin
Copy link

PhilippeCarphin commented Feb 16, 2020

@crownk1997

@PhilippeCarphin Hi, could you please tell me why we need add_custom_target here? I think only the command add_custom_command can do what we want to. Thank you so much!

It is to create a target whose goal in life is to just depend on the files generated by add_custom_command. You don't need the add_custom_target but you should use it.

The rule to generate the files (created with add_custom_command) is more simple, "here's how to generate these files if they are not there" and doesn't benefit from CMake's dependency resolution magic.

What the documentation says is that because these rules are dumber, if two targets depend on these files and you are building with -j ${n} where n > 1, then those targets may cause those files to be generated twice concurrently.

Having the custom target and having only dependencies on the target and not on the files gives you a protection against that kind of thing.

@PhilippeCarphin
Copy link

@crownk1997 To me `add_custom_command" is like "if the target depends on this file and it's not there, just run this command and don't ask questions" which is why you only want one target causing the "run this command and don't ask questions".

It's the custom target that asks the questions.

@frobnitzem
Copy link

I was guided here looking for a solution to just such a make -j collision because add_custom_command runs twice - and the second one throws an error if you are trying to auto-generate docs using Doxygen followed by Sphinx. (both are custom_command + custom_target combos).

Initially, it seemed like there should be an alternative to add_custom_target that doesn't run more than once. ALL seems pretty forceful, since it doesn't have a dependency to trigger it. Now I see that it was a dependency in the Sphinx add_custom_command - asking for a file directly rather than Doxygen that caused the error. Changing the file to the "Doxygen" target created by add_custom_command fixed the problem.

@gon1332
Copy link

gon1332 commented Mar 18, 2021

How could this be changed if I didn't know the header files included in the tar? Basically if I want LIBFOO_TAR_HEADERS to be populated in some more automatic way. I struggled with file(GLOB..) but didn't work.

@PhilippeCarphin
Copy link

What I would do is untar the thing, run a find command in my shell, copy-it and paste like this

set(LIB_FOO_HEADERS
# Paste here
)

I prefer this over using GLOB. You can read up on the GLOB vs no-GLOB in debate for CMake.

For me it boils down to the fact that if I add a file, I have to go write it somewhere in a CMakeLists.txt which ensures that when I run make, cmake will be re-run. This is not the case with globs.

Sorry if you're disappointed with my "copy-paste some shell output in your CMakeLists.txt" answer but that is what I do 😄

@gon1332
Copy link

gon1332 commented Mar 18, 2021

First of all thanks for your answer. Your proposal is the best/single working solution that I have up to now. 😄
Yeah, GLOB can hide a lot of traps.
I was trying to do the find and copy-paste trick you mentioned a bit more automatically via a custom command or target, but the problem was that I couldn't assign it's output to a CMake variable. 🤕

@PhilippeCarphin
Copy link

PhilippeCarphin commented Mar 18, 2021

There's a parameter to make the output of execute_process go into a variable (or to make the output of a custom command, I forget which) but it's not worth it.

Plus you would have to make sure that this automatic process is triggered when you add a new file anyway by remembering to run CMake when you add a file.

With the files written explicitely, you have to remember to add the file in a CMakeLIsts.txt somewhere but that will make sure the the necessary things get triggered.

@gon1332
Copy link

gon1332 commented Mar 18, 2021

I agree with you regarding the execute_process solution. I also like the idea of writing explicitly the files in my case now that you put it that way.

@krishna116
Copy link

It is a good example.
if the process logic is too complicate, I have a idea about wrap some logic code to script.cmake, then the code will be more clean.
for example:

CMakeList.txt

set(InputFileList "config.h" "apis.h")
set(OutputFileName "${CMAKE_CURRENT_BINARY_DIR}/SingleHeadFile.h")

add_custom_command(OUTPUT ${OutputFileName}
COMMAND ${CMAKE_COMMAND} -P "${PROJECT_SOURCE_DIR}/cmake/GenerateSingleFile.cmake" "${InputFileList}" "${OutputFileName}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
DEPENDS ${InputFileList}
COMMENT "Generate single-head-file from input-file-list."
VERBATIM
)
add_library(Foo INTERFACE ${OutputFileName})

GenerateSingleFile.cmake

-- your logic code here

@jeffersonfr
Copy link

Helps a lot. Thanks :)

@marc-hb
Copy link

marc-hb commented Sep 26, 2022

So I ended up doing this: A macro called add_something that creates a target [ combining add_custom_command + add_custom_target ]

Every time I do NOT find something like this macro in plain CMake it makes me feel like I still don't understand CMake.

But no, it's apparently CMake that does not understand the most basic feature people need.

Best page found so far:
https://samthursfield.wordpress.com/2015/11/21/cmake-dependencies-between-targets-and-files-and-custom-commands/

@pranavb-ca
Copy link

pranavb-ca commented Oct 27, 2022

Thank you for posting this. I am perplexed about one thing though. add_library and add_executable know that source files are present in ${CMAKE_CURRENT_SOURCE_DIR} so we don't need to explicitly mention it. But, add_custom_command needs the full path to a file in the current source directory. So,

add_custom_command(OUTPUT cross_compile_file.o
${CXX_CROSS_COMPILER} -c cross_compiled_file.cpp
MAIN_DEPENDENCY cross_compiled_file.cpp
VERBATIM)

results in no input files: cannot find cross_compiled_file.cpp error from the compiler. The behavior of cmake seems inconsistent here.
I guess the reasoning is that the custom_command could be anything so cmake isn't going to assume anything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment