ExternalData: Add support for custom download scripts

Add support for a special URL template to map the fetch operation
to a project-specified .cmake script insead of using file(DOWNLOAD).

Extend the Module.ExternalData test to cover the behavior.
Extend the RunCMake.ExternalData test to cover error cases.
This commit is contained in:
Brad King 2015-01-13 10:03:56 -05:00
parent 945571db74
commit 0fe4d8bb3b
19 changed files with 179 additions and 1 deletions

View File

@ -99,6 +99,12 @@ calling any of the functions provided by this module.
default is ``CMAKE_BINARY_DIR``. The directory layout will mirror that of default is ``CMAKE_BINARY_DIR``. The directory layout will mirror that of
content links under ``ExternalData_SOURCE_ROOT``. content links under ``ExternalData_SOURCE_ROOT``.
.. variable:: ExternalData_CUSTOM_SCRIPT_<key>
Specify a full path to a ``.cmake`` custom fetch script identified by
``<key>`` in entries of the ``ExternalData_URL_TEMPLATES`` list.
See `Custom Fetch Scripts`_.
.. variable:: ExternalData_LINK_CONTENT .. variable:: ExternalData_LINK_CONTENT
The ``ExternalData_LINK_CONTENT`` variable may be set to the name of a The ``ExternalData_LINK_CONTENT`` variable may be set to the name of a
@ -246,6 +252,44 @@ The following hash algorithms are supported::
Note that the hashes are used only for unique data identification and Note that the hashes are used only for unique data identification and
download verification. download verification.
Custom Fetch Scripts
^^^^^^^^^^^^^^^^^^^^
When a data file must be fetched from one of the URL templates
specified in the ``ExternalData_URL_TEMPLATES`` variable, it is
normally downloaded using the :command:`file(DOWNLOAD)` command.
One may specify usage of a custom fetch script by using a URL
template of the form ``ExternalDataCustomScript://<key>/<loc>``.
The ``<key>`` must be a C identifier, and the ``<loc>`` must
contain the ``%(algo)`` and ``%(hash)`` placeholders.
A variable corresponding to the key, ``ExternalData_CUSTOM_SCRIPT_<key>``,
must be set to the full path to a ``.cmake`` script file. The script
will be included to perform the actual fetch, and provided with
the following variables:
.. variable:: ExternalData_CUSTOM_LOCATION
When a custom fetch script is loaded, this variable is set to the
location part of the URL, which will contain the substituted hash
algorithm name and content hash value.
.. variable:: ExternalData_CUSTOM_FILE
When a custom fetch script is loaded, this variable is set to the
full path to a file in which the script must store the fetched
content. The name of the file is unspecified and should not be
interpreted in any way.
The custom fetch script is expected to store fetched content in the
file or set a variable:
.. variable:: ExternalData_CUSTOM_ERROR
When a custom fetch script fails to fetch the requested content,
it must set this variable to a short one-line message describing
the reason for failure.
#]=======================================================================] #]=======================================================================]
#============================================================================= #=============================================================================
@ -275,6 +319,37 @@ function(ExternalData_add_target target)
if(NOT ExternalData_OBJECT_STORES) if(NOT ExternalData_OBJECT_STORES)
set(ExternalData_OBJECT_STORES ${CMAKE_BINARY_DIR}/ExternalData/Objects) set(ExternalData_OBJECT_STORES ${CMAKE_BINARY_DIR}/ExternalData/Objects)
endif() endif()
set(_ExternalData_CONFIG_CODE "")
# Store custom script configuration.
foreach(url_template IN LISTS ExternalData_URL_TEMPLATES)
if("${url_template}" MATCHES "^ExternalDataCustomScript://([^/]*)/(.*)$")
set(key "${CMAKE_MATCH_1}")
if(key MATCHES "^[A-Za-z_][A-Za-z0-9_]*$")
if(ExternalData_CUSTOM_SCRIPT_${key})
if(IS_ABSOLUTE "${ExternalData_CUSTOM_SCRIPT_${key}}")
string(CONCAT _ExternalData_CONFIG_CODE "${_ExternalData_CONFIG_CODE}\n"
"set(ExternalData_CUSTOM_SCRIPT_${key} \"${ExternalData_CUSTOM_SCRIPT_${key}}\")")
else()
message(FATAL_ERROR
"No ExternalData_CUSTOM_SCRIPT_${key} is not set to a full path:\n"
" ${ExternalData_CUSTOM_SCRIPT_${key}}")
endif()
else()
message(FATAL_ERROR
"No ExternalData_CUSTOM_SCRIPT_${key} is set for URL template:\n"
" ${url_template}")
endif()
else()
message(FATAL_ERROR
"Bad ExternalDataCustomScript key '${key}' in URL template:\n"
" ${url_template}\n"
"The key must be a valid C identifier.")
endif()
endif()
endforeach()
# Store configuration for use by build-time script.
set(config ${CMAKE_CURRENT_BINARY_DIR}/${target}_config.cmake) set(config ${CMAKE_CURRENT_BINARY_DIR}/${target}_config.cmake)
configure_file(${_ExternalData_SELF_DIR}/ExternalData_config.cmake.in ${config} @ONLY) configure_file(${_ExternalData_SELF_DIR}/ExternalData_config.cmake.in ${config} @ONLY)
@ -781,6 +856,30 @@ function(_ExternalData_download_file url file err_var msg_var)
set("${msg_var}" "${msg}" PARENT_SCOPE) set("${msg_var}" "${msg}" PARENT_SCOPE)
endfunction() endfunction()
function(_ExternalData_custom_fetch key loc file err_var msg_var)
if(NOT ExternalData_CUSTOM_SCRIPT_${key})
set(err 1)
set(msg "No ExternalData_CUSTOM_SCRIPT_${key} set!")
elseif(NOT EXISTS "${ExternalData_CUSTOM_SCRIPT_${key}}")
set(err 1)
set(msg "No '${ExternalData_CUSTOM_SCRIPT_${key}}' exists!")
else()
set(ExternalData_CUSTOM_LOCATION "${loc}")
set(ExternalData_CUSTOM_FILE "${file}")
unset(ExternalData_CUSTOM_ERROR)
include("${ExternalData_CUSTOM_SCRIPT_${key}}")
if(DEFINED ExternalData_CUSTOM_ERROR)
set(err 1)
set(msg "${ExternalData_CUSTOM_ERROR}")
else()
set(err 0)
set(msg "no error")
endif()
endif()
set("${err_var}" "${err}" PARENT_SCOPE)
set("${msg_var}" "${msg}" PARENT_SCOPE)
endfunction()
function(_ExternalData_download_object name hash algo var_obj) function(_ExternalData_download_object name hash algo var_obj)
# Search all object stores for an existing object. # Search all object stores for an existing object.
foreach(dir ${ExternalData_OBJECT_STORES}) foreach(dir ${ExternalData_OBJECT_STORES})
@ -804,7 +903,11 @@ function(_ExternalData_download_object name hash algo var_obj)
string(REPLACE "%(hash)" "${hash}" url_tmp "${url_template}") string(REPLACE "%(hash)" "${hash}" url_tmp "${url_template}")
string(REPLACE "%(algo)" "${algo}" url "${url_tmp}") string(REPLACE "%(algo)" "${algo}" url "${url_tmp}")
message(STATUS "Fetching \"${url}\"") message(STATUS "Fetching \"${url}\"")
_ExternalData_download_file("${url}" "${tmp}" err errMsg) if(url MATCHES "^ExternalDataCustomScript://([A-Za-z_][A-Za-z0-9_]*)/(.*)$")
_ExternalData_custom_fetch("${CMAKE_MATCH_1}" "${CMAKE_MATCH_2}" "${tmp}" err errMsg)
else()
_ExternalData_download_file("${url}" "${tmp}" err errMsg)
endif()
set(tried "${tried}\n ${url}") set(tried "${tried}\n ${url}")
if(err) if(err)
set(tried "${tried} (${errMsg})") set(tried "${tried} (${errMsg})")

View File

@ -2,3 +2,4 @@ set(ExternalData_OBJECT_STORES "@ExternalData_OBJECT_STORES@")
set(ExternalData_URL_TEMPLATES "@ExternalData_URL_TEMPLATES@") set(ExternalData_URL_TEMPLATES "@ExternalData_URL_TEMPLATES@")
set(ExternalData_TIMEOUT_INACTIVITY "@ExternalData_TIMEOUT_INACTIVITY@") set(ExternalData_TIMEOUT_INACTIVITY "@ExternalData_TIMEOUT_INACTIVITY@")
set(ExternalData_TIMEOUT_ABSOLUTE "@ExternalData_TIMEOUT_ABSOLUTE@") set(ExternalData_TIMEOUT_ABSOLUTE "@ExternalData_TIMEOUT_ABSOLUTE@")
@_ExternalData_CONFIG_CODE@

View File

@ -10,7 +10,9 @@ if(NOT "${CMAKE_CURRENT_SOURCE_DIR}" MATCHES "^/")
endif() endif()
set(ExternalData_URL_TEMPLATES set(ExternalData_URL_TEMPLATES
"file://${slash}${CMAKE_CURRENT_SOURCE_DIR}/%(algo)/%(hash)" "file://${slash}${CMAKE_CURRENT_SOURCE_DIR}/%(algo)/%(hash)"
"ExternalDataCustomScript://MyScript1/%(algo)/%(hash)"
) )
set(ExternalData_CUSTOM_SCRIPT_MyScript1 "${CMAKE_CURRENT_SOURCE_DIR}/MyScript1.cmake")
set(ExternalData_BINARY_ROOT "${CMAKE_CURRENT_BINARY_DIR}/ExternalData") set(ExternalData_BINARY_ROOT "${CMAKE_CURRENT_BINARY_DIR}/ExternalData")
file(REMOVE_RECURSE ${ExternalData_BINARY_ROOT}) # clean test file(REMOVE_RECURSE ${ExternalData_BINARY_ROOT}) # clean test
@ -23,6 +25,7 @@ ExternalData_Add_Test(Data1
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
-D Data=DATA{Data.dat} -D Data=DATA{Data.dat}
${Data1CheckSpaces} ${Data1CheckSpaces}
-D DataScript=DATA{DataScript.dat}
-D DataMissing=DATA{DataMissing.dat} -D DataMissing=DATA{DataMissing.dat}
-D DataMissingWithAssociated=DATA{DataMissing.dat,Data.dat} -D DataMissingWithAssociated=DATA{DataMissing.dat,Data.dat}
-D SeriesA=DATA{SeriesA.dat,:} -D SeriesA=DATA{SeriesA.dat,:}

View File

@ -8,6 +8,10 @@ if(DEFINED DataSpace)
message(SEND_ERROR "Input file:\n ${DataSpace}\ndoes not have expected content, but [[${lines}]]") message(SEND_ERROR "Input file:\n ${DataSpace}\ndoes not have expected content, but [[${lines}]]")
endif() endif()
endif() endif()
file(STRINGS "${DataScript}" lines LIMIT_INPUT 1024)
if(NOT "x${lines}" STREQUAL "xDataScript")
message(SEND_ERROR "Input file:\n ${DataScript}\ndoes not have expected content, but [[${lines}]]")
endif()
if(DataMissing) if(DataMissing)
if(EXISTS "${DataMissing}") if(EXISTS "${DataMissing}")
message(SEND_ERROR message(SEND_ERROR

View File

@ -0,0 +1 @@
fd95c03719e8626c0d10a818f9996dc5

View File

@ -0,0 +1,5 @@
if(ExternalData_CUSTOM_LOCATION STREQUAL "MD5/fd95c03719e8626c0d10a818f9996dc5")
file(WRITE "${ExternalData_CUSTOM_FILE}" "DataScript")
else()
set(ExternalData_CUSTOM_ERROR "no ${ExternalData_CUSTOM_LOCATION} known")
endif()

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,9 @@
^CMake Error at .*/Modules/ExternalData.cmake:[0-9]+ \(message\):
Bad ExternalDataCustomScript key '0BadKey' in URL template:
ExternalDataCustomScript://0BadKey/%\(algo\)/%\(hash\)
The key must be a valid C identifier.
Call Stack \(most recent call first\):
BadCustom1.cmake:[0-9]+ \(ExternalData_Add_Target\)
CMakeLists.txt:[0-9]+ \(include\)$

View File

@ -0,0 +1,5 @@
include(ExternalData)
set(ExternalData_URL_TEMPLATES
"ExternalDataCustomScript://0BadKey/%(algo)/%(hash)"
)
ExternalData_Add_Target(Data)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,9 @@
^CMake Error at .*/Modules/ExternalData.cmake:[0-9]+ \(message\):
Bad ExternalDataCustomScript key '' in URL template:
ExternalDataCustomScript:///%\(algo\)/%\(hash\)
The key must be a valid C identifier.
Call Stack \(most recent call first\):
BadCustom2.cmake:[0-9]+ \(ExternalData_Add_Target\)
CMakeLists.txt:[0-9]+ \(include\)$

View File

@ -0,0 +1,5 @@
include(ExternalData)
set(ExternalData_URL_TEMPLATES
"ExternalDataCustomScript:///%(algo)/%(hash)"
)
ExternalData_Add_Target(Data)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,7 @@
^CMake Error at .*/Modules/ExternalData.cmake:[0-9]+ \(message\):
No ExternalData_CUSTOM_SCRIPT_MissingKey is set for URL template:
ExternalDataCustomScript://MissingKey/%\(algo\)/%\(hash\)
Call Stack \(most recent call first\):
BadCustom3.cmake:[0-9]+ \(ExternalData_Add_Target\)
CMakeLists.txt:[0-9]+ \(include\)$

View File

@ -0,0 +1,5 @@
include(ExternalData)
set(ExternalData_URL_TEMPLATES
"ExternalDataCustomScript://MissingKey/%(algo)/%(hash)"
)
ExternalData_Add_Target(Data)

View File

@ -0,0 +1 @@
1

View File

@ -0,0 +1,7 @@
^CMake Error at .*/Modules/ExternalData.cmake:[0-9]+ \(message\):
No ExternalData_CUSTOM_SCRIPT_RelPathKey is not set to a full path:
RelPathScript.cmake
Call Stack \(most recent call first\):
BadCustom4.cmake:[0-9]+ \(ExternalData_Add_Target\)
CMakeLists.txt:[0-9]+ \(include\)$

View File

@ -0,0 +1,6 @@
include(ExternalData)
set(ExternalData_URL_TEMPLATES
"ExternalDataCustomScript://RelPathKey/%(algo)/%(hash)"
)
set(ExternalData_CUSTOM_SCRIPT_RelPathKey "RelPathScript.cmake")
ExternalData_Add_Target(Data)

View File

@ -1,5 +1,9 @@
include(RunCMake) include(RunCMake)
run_cmake(BadCustom1)
run_cmake(BadCustom2)
run_cmake(BadCustom3)
run_cmake(BadCustom4)
run_cmake(BadHashAlgo1) run_cmake(BadHashAlgo1)
run_cmake(BadOption1) run_cmake(BadOption1)
run_cmake(BadOption2) run_cmake(BadOption2)