diff --git a/Modules/ExternalData.cmake b/Modules/ExternalData.cmake index a0bffe771..e7f8408d4 100644 --- a/Modules/ExternalData.cmake +++ b/Modules/ExternalData.cmake @@ -86,6 +86,10 @@ Module Functions in one of the paths specified in the ``ExternalData_OBJECT_STORES`` variable. + Typically only one target is needed to manage all external data within + a project. Call this function once at the end of configuration after + all data references have been processed. + Module Variables ^^^^^^^^^^^^^^^^ @@ -394,8 +398,14 @@ function(ExternalData_add_target target) set(files "") - # Set "_ExternalData_FILE_${file}" for each output file to avoid duplicate - # rules. Use local data first to prefer real files over content links. + # Set a "_ExternalData_FILE_${file}" variable for each output file to avoid + # duplicate entries within this target. Set a directory property of the same + # name to avoid repeating custom commands with the same output in this directory. + # Repeating custom commands with the same output across directories or across + # targets in the same directory may be a race, but this is likely okay because + # we use atomic replacement of output files. + # + # Use local data first to prefer real files over content links. # Custom commands to copy or link local data. get_property(data_local GLOBAL PROPERTY _ExternalData_${target}_LOCAL) @@ -405,16 +415,20 @@ function(ExternalData_add_target target) list(GET tuple 1 name) if(NOT DEFINED "_ExternalData_FILE_${file}") set("_ExternalData_FILE_${file}" 1) - add_custom_command( - COMMENT "Generating ${file}" - OUTPUT "${file}" - COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR} - -Dfile=${file} -Dname=${name} - -DExternalData_ACTION=local - -DExternalData_CONFIG=${config} - -P ${_ExternalData_SELF} - MAIN_DEPENDENCY "${name}" - ) + get_property(added DIRECTORY PROPERTY "_ExternalData_FILE_${file}") + if(NOT added) + set_property(DIRECTORY PROPERTY "_ExternalData_FILE_${file}" 1) + add_custom_command( + COMMENT "Generating ${file}" + OUTPUT "${file}" + COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR} + -Dfile=${file} -Dname=${name} + -DExternalData_ACTION=local + -DExternalData_CONFIG=${config} + -P ${_ExternalData_SELF} + MAIN_DEPENDENCY "${name}" + ) + endif() list(APPEND files "${file}") endif() endforeach() @@ -429,23 +443,27 @@ function(ExternalData_add_target target) set(stamp "${ext}-stamp") if(NOT DEFINED "_ExternalData_FILE_${file}") set("_ExternalData_FILE_${file}" 1) - add_custom_command( - # Users care about the data file, so hide the hash/timestamp file. - COMMENT "Generating ${file}" - # The hash/timestamp file is the output from the build perspective. - # List the real file as a second output in case it is a broken link. - # The files must be listed in this order so CMake can hide from the - # make tool that a symlink target may not be newer than the input. - OUTPUT "${file}${stamp}" "${file}" - # Run the data fetch/update script. - COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR} - -Dfile=${file} -Dname=${name} -Dext=${ext} - -DExternalData_ACTION=fetch - -DExternalData_CONFIG=${config} - -P ${_ExternalData_SELF} - # Update whenever the object hash changes. - MAIN_DEPENDENCY "${name}${ext}" - ) + get_property(added DIRECTORY PROPERTY "_ExternalData_FILE_${file}") + if(NOT added) + set_property(DIRECTORY PROPERTY "_ExternalData_FILE_${file}" 1) + add_custom_command( + # Users care about the data file, so hide the hash/timestamp file. + COMMENT "Generating ${file}" + # The hash/timestamp file is the output from the build perspective. + # List the real file as a second output in case it is a broken link. + # The files must be listed in this order so CMake can hide from the + # make tool that a symlink target may not be newer than the input. + OUTPUT "${file}${stamp}" "${file}" + # Run the data fetch/update script. + COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR} + -Dfile=${file} -Dname=${name} -Dext=${ext} + -DExternalData_ACTION=fetch + -DExternalData_CONFIG=${config} + -P ${_ExternalData_SELF} + # Update whenever the object hash changes. + MAIN_DEPENDENCY "${name}${ext}" + ) + endif() list(APPEND files "${file}${stamp}") endif() endforeach() diff --git a/Tests/Module/ExternalData/CMakeLists.txt b/Tests/Module/ExternalData/CMakeLists.txt index f07ab7197..1018dd87d 100644 --- a/Tests/Module/ExternalData/CMakeLists.txt +++ b/Tests/Module/ExternalData/CMakeLists.txt @@ -53,4 +53,5 @@ ExternalData_Add_Target(Data1) add_subdirectory(Data2) add_subdirectory(Data3) add_subdirectory(Data4) +add_subdirectory(Data5) add_subdirectory(DataNoSymlinks) diff --git a/Tests/Module/ExternalData/Data5/CMakeLists.txt b/Tests/Module/ExternalData/Data5/CMakeLists.txt new file mode 100644 index 000000000..13c7fabed --- /dev/null +++ b/Tests/Module/ExternalData/Data5/CMakeLists.txt @@ -0,0 +1,22 @@ +# Test adding the same data file in multiple tests +ExternalData_Add_Test(Data5.A + NAME Data5Check.A + COMMAND ${CMAKE_COMMAND} + -D Data5=DATA{../Data.dat} + -P ${CMAKE_CURRENT_SOURCE_DIR}/Data5Check.cmake + ) +ExternalData_Add_Target(Data5.A) +ExternalData_Add_Test(Data5.B + NAME Data5Check.B + COMMAND ${CMAKE_COMMAND} + -D Data5=DATA{../Data.dat} + -P ${CMAKE_CURRENT_SOURCE_DIR}/Data5Check.cmake + ) +ExternalData_Add_Target(Data5.B) +ExternalData_Add_Test(Data5.C + NAME Data5Check.C + COMMAND ${CMAKE_COMMAND} + -D Data5=DATA{../Data.dat} + -P ${CMAKE_CURRENT_SOURCE_DIR}/Data5Check.cmake + ) +ExternalData_Add_Target(Data5.C) diff --git a/Tests/Module/ExternalData/Data5/Data5Check.cmake b/Tests/Module/ExternalData/Data5/Data5Check.cmake new file mode 100644 index 000000000..4dea9a487 --- /dev/null +++ b/Tests/Module/ExternalData/Data5/Data5Check.cmake @@ -0,0 +1,4 @@ +file(STRINGS "${Data5}" lines LIMIT_INPUT 1024) +if(NOT "x${lines}" STREQUAL "xInput file already transformed.") + message(SEND_ERROR "Input file:\n ${Data5}\ndoes not have expected content, but [[${lines}]]") +endif()