ExternalData: Tolerate files duplicated across multiple targets
If multiple ExternalData_Target_Add calls generate the same output file then we need to avoid calling add_custom_command multiple times with that output. This was already done within a single target by setting a variable in the local function scope. This will not be visible in other calls though so we need to use a directory property instead to prevent adding a custom command multiple times for one output in a directory. Normally it is not safe to have multiple custom commands that produce the same output file across multiple independent targets, but since we use atomic replacement of outputs the resulting races should not be a problem. For the convenience of projects, tolerate this instead of diagnosing it. In particular, we previously allowed up to two copies of the custom command in one directory because CMake has a fallback from MAIN_DEPENDENCY to an `<output>.rule` file. While at it, add a note to the documentation that typically only one external data target should be needed for a project. Reported-by: David Manthey <david.manthey@kitware.com>
This commit is contained in:
parent
024eecd910
commit
f9973166e8
|
@ -86,6 +86,10 @@ Module Functions
|
||||||
in one of the paths specified in the ``ExternalData_OBJECT_STORES``
|
in one of the paths specified in the ``ExternalData_OBJECT_STORES``
|
||||||
variable.
|
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
|
Module Variables
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -394,8 +398,14 @@ function(ExternalData_add_target target)
|
||||||
|
|
||||||
set(files "")
|
set(files "")
|
||||||
|
|
||||||
# Set "_ExternalData_FILE_${file}" for each output file to avoid duplicate
|
# Set a "_ExternalData_FILE_${file}" variable for each output file to avoid
|
||||||
# rules. Use local data first to prefer real files over content links.
|
# 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.
|
# Custom commands to copy or link local data.
|
||||||
get_property(data_local GLOBAL PROPERTY _ExternalData_${target}_LOCAL)
|
get_property(data_local GLOBAL PROPERTY _ExternalData_${target}_LOCAL)
|
||||||
|
@ -405,16 +415,20 @@ function(ExternalData_add_target target)
|
||||||
list(GET tuple 1 name)
|
list(GET tuple 1 name)
|
||||||
if(NOT DEFINED "_ExternalData_FILE_${file}")
|
if(NOT DEFINED "_ExternalData_FILE_${file}")
|
||||||
set("_ExternalData_FILE_${file}" 1)
|
set("_ExternalData_FILE_${file}" 1)
|
||||||
add_custom_command(
|
get_property(added DIRECTORY PROPERTY "_ExternalData_FILE_${file}")
|
||||||
COMMENT "Generating ${file}"
|
if(NOT added)
|
||||||
OUTPUT "${file}"
|
set_property(DIRECTORY PROPERTY "_ExternalData_FILE_${file}" 1)
|
||||||
COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
|
add_custom_command(
|
||||||
-Dfile=${file} -Dname=${name}
|
COMMENT "Generating ${file}"
|
||||||
-DExternalData_ACTION=local
|
OUTPUT "${file}"
|
||||||
-DExternalData_CONFIG=${config}
|
COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
|
||||||
-P ${_ExternalData_SELF}
|
-Dfile=${file} -Dname=${name}
|
||||||
MAIN_DEPENDENCY "${name}"
|
-DExternalData_ACTION=local
|
||||||
)
|
-DExternalData_CONFIG=${config}
|
||||||
|
-P ${_ExternalData_SELF}
|
||||||
|
MAIN_DEPENDENCY "${name}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
list(APPEND files "${file}")
|
list(APPEND files "${file}")
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
@ -429,23 +443,27 @@ function(ExternalData_add_target target)
|
||||||
set(stamp "${ext}-stamp")
|
set(stamp "${ext}-stamp")
|
||||||
if(NOT DEFINED "_ExternalData_FILE_${file}")
|
if(NOT DEFINED "_ExternalData_FILE_${file}")
|
||||||
set("_ExternalData_FILE_${file}" 1)
|
set("_ExternalData_FILE_${file}" 1)
|
||||||
add_custom_command(
|
get_property(added DIRECTORY PROPERTY "_ExternalData_FILE_${file}")
|
||||||
# Users care about the data file, so hide the hash/timestamp file.
|
if(NOT added)
|
||||||
COMMENT "Generating ${file}"
|
set_property(DIRECTORY PROPERTY "_ExternalData_FILE_${file}" 1)
|
||||||
# The hash/timestamp file is the output from the build perspective.
|
add_custom_command(
|
||||||
# List the real file as a second output in case it is a broken link.
|
# Users care about the data file, so hide the hash/timestamp file.
|
||||||
# The files must be listed in this order so CMake can hide from the
|
COMMENT "Generating ${file}"
|
||||||
# make tool that a symlink target may not be newer than the input.
|
# The hash/timestamp file is the output from the build perspective.
|
||||||
OUTPUT "${file}${stamp}" "${file}"
|
# List the real file as a second output in case it is a broken link.
|
||||||
# Run the data fetch/update script.
|
# The files must be listed in this order so CMake can hide from the
|
||||||
COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
|
# make tool that a symlink target may not be newer than the input.
|
||||||
-Dfile=${file} -Dname=${name} -Dext=${ext}
|
OUTPUT "${file}${stamp}" "${file}"
|
||||||
-DExternalData_ACTION=fetch
|
# Run the data fetch/update script.
|
||||||
-DExternalData_CONFIG=${config}
|
COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
|
||||||
-P ${_ExternalData_SELF}
|
-Dfile=${file} -Dname=${name} -Dext=${ext}
|
||||||
# Update whenever the object hash changes.
|
-DExternalData_ACTION=fetch
|
||||||
MAIN_DEPENDENCY "${name}${ext}"
|
-DExternalData_CONFIG=${config}
|
||||||
)
|
-P ${_ExternalData_SELF}
|
||||||
|
# Update whenever the object hash changes.
|
||||||
|
MAIN_DEPENDENCY "${name}${ext}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
list(APPEND files "${file}${stamp}")
|
list(APPEND files "${file}${stamp}")
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
|
@ -53,4 +53,5 @@ ExternalData_Add_Target(Data1)
|
||||||
add_subdirectory(Data2)
|
add_subdirectory(Data2)
|
||||||
add_subdirectory(Data3)
|
add_subdirectory(Data3)
|
||||||
add_subdirectory(Data4)
|
add_subdirectory(Data4)
|
||||||
|
add_subdirectory(Data5)
|
||||||
add_subdirectory(DataNoSymlinks)
|
add_subdirectory(DataNoSymlinks)
|
||||||
|
|
|
@ -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)
|
|
@ -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()
|
Loading…
Reference in New Issue