BASIS  r3148
ExternalData.cmake
Go to the documentation of this file.
00001 ##############################################################################
00002 # @file  ExternalData.cmake
00003 # @brief Manage data files stored outside the source tree.
00004 #
00005 #
00006 # Copyright 2010-2011 Kitware, Inc. All rights reserved.
00007 # File modified by the SBIA Group.
00008 #
00009 # Contact: SBIA Group <sbia-software at uphs.upenn.edu>
00010 #
00011 # @ingroup CMakeTools
00012 ##############################################################################
00013 
00014 # - Manage data files stored outside source tree
00015 # Use this module to unambiguously reference data files stored outside the
00016 # source tree and fetch them at build time from arbitrary local and remote
00017 # content-addressed locations.  Functions provided by this module recognize
00018 # arguments with the syntax "DATA{<name>}" as references to external data,
00019 # replace them with full paths to local copies of those data, and create build
00020 # rules to fetch and update the local copies.
00021 #
00022 # The DATA{} syntax is literal and the <name> is a full or relative path
00023 # within the source tree.  The source tree must contain either a real data
00024 # file at <name> or a "content link" at <name><ext> containing a hash of the
00025 # real file using a hash algorithm corresponding to <ext>.  For example, the
00026 # argument "DATA{img.png}" may be satisfied by either a real "img.png" file in
00027 # the current source directory or a "img.png.md5" file containing its MD5 sum.
00028 #
00029 # The 'ExternalData_Expand_Arguments' function evaluates DATA{} references
00030 # in its arguments and constructs a new list of arguments:
00031 #  ExternalData_Expand_Arguments(
00032 #    <target>   # Name of data management target
00033 #    <outVar>   # Output variable
00034 #    [args...]  # Input arguments, DATA{} allowed
00035 #    )
00036 # It replaces each DATA{} reference argument with the full path of a real
00037 # data file on disk that will exist after the <target> builds.
00038 #
00039 # The 'ExternalData_Add_Test' function wraps around the CMake add_test()
00040 # command but supports DATA{} reference arguments:
00041 #  ExternalData_Add_Test(
00042 #    <target>   # Name of data management target
00043 #    ...        # Arguments of add_test(), DATA{} allowed
00044 #    )
00045 # It passes its arguments through ExternalData_Expand_Arguments and then
00046 # invokes add_test() using the results.
00047 #
00048 # The 'ExternalData_Add_Target' function creates a custom target to manage
00049 # local instances of data files stored externally:
00050 #  ExternalData_Add_Target(
00051 #    <target>   # Name of data management target
00052 #    )
00053 # It creates custom commands in the target as necessary to make data files
00054 # available for each DATA{} reference previously evaluated by other functions
00055 # provided by this module.  A list of URL templates must be provided in the
00056 # variable ExternalData_URL_TEMPLATES using the placeholders "%(algo)" and
00057 # "%(hash)" in each template.  Data fetch rules try each URL template in order
00058 # by substituting the hash algorithm name for "%(algo)" and the hash value for
00059 # "%(hash)".
00060 #
00061 # The following hash algorithms are supported:
00062 #    %(algo)     <ext>     Description
00063 #    -------     -----     -----------
00064 #    MD5         .md5      Message-Digest Algorithm 5, RFC 1321
00065 # Note that the hashes are used only for unique data identification and
00066 # download verification.  This is not security software.
00067 #
00068 # Example usage:
00069 #   include(ExternalData)
00070 #   set(ExternalData_URL_TEMPLATES "file:///local/%(algo)/%(hash)"
00071 #                                  "http://data.org/%(algo)/%(hash)")
00072 #   ExternalData_Add_Test(MyData
00073 #     NAME MyTest
00074 #     COMMAND MyExe DATA{MyInput.png}
00075 #     )
00076 #   ExternalData_Add_Target(MyData)
00077 # When test "MyTest" runs the "DATA{MyInput.png}" argument will be replaced by
00078 # the full path to a real instance of the data file "MyInput.png" on disk.  If
00079 # the source tree contains a content link such as "MyInput.png.md5" then the
00080 # "MyData" target creates a real "MyInput.png" in the build tree.
00081 #
00082 # The DATA{} syntax can automatically recognize and fetch a file series.  If
00083 # the source tree contains a group of files or content links named like a
00084 # series then a DATA{} reference to one member adds rules to fetch all of
00085 # them.  Although all members of a series are fetched, only the file
00086 # originally named by the DATA{} argument is substituted for it.  Two
00087 # variables configure recognition of a series from DATA{<name>}.  First,
00088 # ExternalData_SERIES_PARSE is a regex of the form "^(...)(...)(...)$" to
00089 # parse <prefix>, <number>, and <suffix> parts from <name>.  Second,
00090 # ExternalData_SERIES_MATCH is a regex matching the <number> part of series
00091 # members named <prefix><number><suffix>.  Note that the <suffix> of a series
00092 # does not include a hash-algorithm extension.  Both series configuration
00093 # variables have default values that work well for common cases.
00094 #
00095 # The variable ExternalData_LINK_CONTENT may be set to the name of a supported
00096 # hash algorithm to enable automatic conversion of real data files referenced
00097 # by the DATA{} syntax into content links.  For each such <file> a content
00098 # link named "<file><ext>" is created.  The original file is renamed to the
00099 # form ".ExternalData_<algo>_<hash>" to stage it for future transmission to
00100 # one of the locations in the list of URL templates (by means outside the
00101 # scope of this module).  The data fetch rule created for the content link
00102 # will use the staged object if it cannot be found using any URL template.
00103 #
00104 # The variable ExternalData_SOURCE_ROOT may be set to the highest source
00105 # directory containing any path named by a DATA{} reference.  The default is
00106 # CMAKE_SOURCE_DIR.  ExternalData_SOURCE_ROOT and CMAKE_SOURCE_DIR must refer
00107 # to directories within a single source distribution (e.g. they come together
00108 # in one tarball).
00109 
00110 #=============================================================================
00111 # Copyright 2010-2011 Kitware, Inc.
00112 # All rights reserved.
00113 #
00114 # Redistribution and use in source and binary forms, with or without
00115 # modification, are permitted provided that the following conditions
00116 # are met:
00117 #
00118 # * Redistributions of source code must retain the above copyright
00119 #   notice, this list of conditions and the following disclaimer.
00120 #
00121 # * Redistributions in binary form must reproduce the above copyright
00122 #   notice, this list of conditions and the following disclaimer in the
00123 #   documentation and/or other materials provided with the distribution.
00124 #
00125 # * Neither the names of Kitware, Inc., the Insight Software Consortium,
00126 #   nor the names of their contributors may be used to endorse or promote
00127 #   products derived from this software without specific prior written
00128 #   permission.
00129 #
00130 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00131 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00132 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
00133 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
00134 # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00135 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
00136 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00137 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
00138 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00139 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
00140 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00141 #=============================================================================
00142 
00143 ##############################################################################
00144 # @brief @todo Document function.
00145 #
00146 # @param [in] target Name of the test.
00147 
00148 function(ExternalData_add_test target)
00149   ExternalData_expand_arguments("${target}" testArgs ${ARGN})
00150   add_test(${testArgs})
00151 endfunction()
00152 
00153 ##############################################################################
00154 # @brief @todo Document funtion.
00155 #
00156 # @param [in] target Name of the external data target.
00157 
00158 function(ExternalData_add_target target)
00159   if(NOT ExternalData_URL_TEMPLATES)
00160     message(FATAL_ERROR "ExternalData_URL_TEMPLATES is not set!")
00161   endif()
00162   set(config ${CMAKE_CURRENT_BINARY_DIR}/${target}_config.cmake)
00163   configure_file(${_ExternalData_SELF_DIR}/ExternalData_config.cmake.in ${config} @ONLY)
00164 
00165   set(files "")
00166 
00167   # Set "_ExternalData_FILE_${file}" for each output file to avoid duplicate
00168   # rules.  Use local data first to prefer real files over content links.
00169 
00170   # Custom commands to copy or link local data.
00171   get_property(data_local GLOBAL PROPERTY _ExternalData_${target}_LOCAL)
00172   foreach(entry IN LISTS data_local)
00173     string(REPLACE "|" ";" tuple "${entry}")
00174     list(GET tuple 0 file)
00175     list(GET tuple 1 name)
00176     if(NOT DEFINED "_ExternalData_FILE_${file}")
00177       set("_ExternalData_FILE_${file}" 1)
00178       add_custom_command(
00179         COMMENT "Generating ${file}"
00180         OUTPUT "${file}"
00181         COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
00182                                  -Dfile=${file} -Dname=${name}
00183                                  -DExternalData_ACTION=local
00184                                  -DExternalData_CONFIG=${config}
00185                                  -P ${_ExternalData_SELF}
00186         DEPENDS "${name}"
00187         )
00188       list(APPEND files "${file}")
00189     endif()
00190   endforeach()
00191 
00192   # Custom commands to fetch remote data.
00193   get_property(data_fetch GLOBAL PROPERTY _ExternalData_${target}_FETCH)
00194   foreach(entry IN LISTS data_fetch)
00195     string(REPLACE "|" ";" tuple "${entry}")
00196     list(GET tuple 0 file)
00197     list(GET tuple 1 name)
00198     list(GET tuple 2 ext)
00199     if(NOT DEFINED "_ExternalData_FILE_${file}")
00200       set("_ExternalData_FILE_${file}" 1)
00201       add_custom_command(
00202         # Users care about the data file, so hide the hash/timestamp file.
00203         COMMENT "Generating ${file}"
00204         # The hash/timestamp file is the output from the build perspective.
00205         # List the real file as a second output in case it is a broken link.
00206         # The files must be listed in this order so CMake can hide from the
00207         # make tool that a symlink target may not be newer than the input.
00208         OUTPUT "${file}${ext}" "${file}"
00209         # Run the data fetch/update script.
00210         COMMAND ${CMAKE_COMMAND} -DExternalData_OBJECT_DIR=${CMAKE_BINARY_DIR}/ExternalData/Objects
00211                                  -Drelative_top=${CMAKE_BINARY_DIR}
00212                                  -Dfile=${file} -Dname=${name} -Dext=${ext}
00213                                  -DExternalData_ACTION=fetch
00214                                  -DExternalData_CONFIG=${config}
00215                                  -P ${_ExternalData_SELF}
00216         # Update whenever the object hash changes.
00217         DEPENDS "${name}${ext}"
00218         )
00219       list(APPEND files "${file}${ext}")
00220     endif()
00221   endforeach()
00222 
00223   # Custom target to drive all update commands.
00224   add_custom_target(${target} ALL DEPENDS ${files})
00225 endfunction()
00226 
00227 ##############################################################################
00228 # @brief Replace DATA{} references with real arguments.
00229 #
00230 # @param [in]  target     Name of the external data target.
00231 # @param [out] outArgsVar List of expanded arguments.
00232 
00233 function(ExternalData_expand_arguments target outArgsVar)
00234   # Replace DATA{} references with real arguments.
00235   set(data_regex "^xDATA{([^{}\r\n]*)}$")
00236   set(outArgs "")
00237   foreach(arg IN LISTS ARGN)
00238     if("x${arg}" MATCHES "${data_regex}")
00239       string(REGEX REPLACE "${data_regex}" "\\1" data "x${arg}")
00240       _ExternalData_arg("${target}" "${arg}" "${data}" file)
00241       list(APPEND outArgs "${file}")
00242     else()
00243       list(APPEND outArgs "${arg}")
00244     endif()
00245   endforeach()
00246   set("${outArgsVar}" "${outArgs}" PARENT_SCOPE)
00247 endfunction()
00248 
00249 #-----------------------------------------------------------------------------
00250 # Private helper interface
00251 
00252 set(_ExternalData_SELF "${CMAKE_CURRENT_LIST_FILE}")
00253 get_filename_component(_ExternalData_SELF_DIR "${_ExternalData_SELF}" PATH)
00254 
00255 function(_ExternalData_compute_hash var_hash algo file)
00256   if("${algo}" STREQUAL "MD5")
00257     # TODO: Errors
00258     execute_process(COMMAND "${CMAKE_COMMAND}" -E md5sum "${file}"
00259       OUTPUT_VARIABLE output)
00260     string(SUBSTRING ${output} 0 32 hash)
00261     set("${var_hash}" "${hash}" PARENT_SCOPE)
00262   else()
00263     # TODO: Other hashes.
00264     message(FATAL_ERROR "Hash algorithm ${algo} unimplemented.")
00265   endif()
00266 endfunction()
00267 
00268 function(_ExternalData_atomic_write file content)
00269   string(RANDOM LENGTH 6 random)
00270   set(tmp "${file}.tmp${random}")
00271   file(WRITE "${tmp}" "${content}")
00272   file(RENAME "${tmp}" "${file}")
00273 endfunction()
00274 
00275 function(_ExternalData_link_content name var_ext)
00276   if("${ExternalData_LINK_CONTENT}" MATCHES "^(MD5)$")
00277     set(algo "${ExternalData_LINK_CONTENT}")
00278   else()
00279     message(FATAL_ERROR
00280       "Unknown hash algorithm specified by ExternalData_LINK_CONTENT:\n"
00281       "  ${ExternalData_LINK_CONTENT}")
00282   endif()
00283   _ExternalData_compute_hash(hash "${algo}" "${name}")
00284   get_filename_component(dir "${name}" PATH)
00285   set(staged "${dir}/.ExternalData_${algo}_${hash}")
00286   set(ext ".md5")
00287   _ExternalData_atomic_write("${name}${ext}" "${hash}\n")
00288   file(RENAME "${name}" "${staged}")
00289   set("${var_ext}" "${ext}" PARENT_SCOPE)
00290 
00291   file(RELATIVE_PATH relname "${ExternalData_SOURCE_ROOT}" "${name}${ext}")
00292   message(STATUS "Linked ${relname} to ExternalData ${algo}/${hash}")
00293 endfunction()
00294 
00295 function(_ExternalData_arg target arg data var_file)
00296   # Convert to full path.
00297   if(IS_ABSOLUTE "${data}")
00298     set(absdata "${data}")
00299   else()
00300     # TODO: If ${data} does not start in "./" or "../" then use search path?
00301     get_filename_component(absdata "${CMAKE_CURRENT_SOURCE_DIR}/${data}" ABSOLUTE)
00302   endif()
00303 
00304   # Convert to relative path under the source tree.
00305   if(NOT ExternalData_SOURCE_ROOT)
00306     set(ExternalData_SOURCE_ROOT "${CMAKE_SOURCE_DIR}")
00307   endif()
00308   set(top_src "${ExternalData_SOURCE_ROOT}")
00309   file(RELATIVE_PATH reldata "${top_src}" "${absdata}")
00310   if(IS_ABSOLUTE "${reldata}" OR "${reldata}" MATCHES "^\\.\\./")
00311     message(FATAL_ERROR "Data file referenced by argument\n"
00312       "  ${arg}\n"
00313       "does not lie under the top-level source directory\n"
00314       "  ${top_src}\n")
00315   endif()
00316   set(top_bin "${CMAKE_BINARY_DIR}/ExternalData") # TODO: .../${target} ?
00317 
00318   # Configure series parsing and matching.
00319   if(ExternalData_SERIES_PARSE)
00320     if(NOT "${ExternalData_SERIES_PARSE}" MATCHES
00321         "^\\^\\([^()]*\\)\\([^()]*\\)\\([^()]*\\)\\$$")
00322       message(FATAL_ERROR
00323         "ExternalData_SERIES_PARSE is set to\n"
00324         "  ${ExternalData_SERIES_PARSE}\n"
00325         "which is not of the form\n"
00326         "  ^(...)(...)(...)$\n")
00327     endif()
00328     set(series_parse "${ExternalData_SERIES_PARSE}")
00329   else()
00330     set(series_parse "^(.*[A-Za-z_.-])([0-9]*)(\\.[^.]*)$")
00331   endif()
00332   if(ExternalData_SERIES_MATCH)
00333     set(series_match "${ExternalData_SERIES_MATCH}")
00334   else()
00335     set(series_match "[_.]?[0-9]*")
00336   endif()
00337 
00338   # Parse the base, number, and extension components of the series.
00339   string(REGEX REPLACE "${series_parse}" "\\1;\\2;\\3" tuple "${reldata}")
00340   list(LENGTH tuple len)
00341   if(NOT "${len}" EQUAL 3)
00342     message(FATAL_ERROR "Data file referenced by argument\n"
00343       "  ${arg}\n"
00344       "corresponds to path\n"
00345       "  ${reldata}\n"
00346       "that does not match regular expression\n"
00347       "  ${series_parse}")
00348   endif()
00349 
00350   # Glob files that might match the series.
00351   list(GET tuple 0 relbase)
00352   list(GET tuple 2 ext)
00353   set(pattern "${relbase}*${ext}*")
00354   file(GLOB globbed RELATIVE "${top_src}" "${top_src}/${pattern}")
00355 
00356   # Match base, number, and extension perhaps followed by a hash ext.
00357   string(REGEX REPLACE "([][+.*()^])" "\\\\\\1" series_base "${relbase}")
00358   string(REGEX REPLACE "([][+.*()^])" "\\\\\\1" series_ext "${ext}")
00359   set(series_regex "^(${series_base}${series_match}${series_ext})(\\.[^.]*|)$")
00360   set(external "") # Entries external to the source tree.
00361   set(internal "") # Entries internal to the source tree.
00362   set(have_original 0)
00363   foreach(entry IN LISTS globbed)
00364     string(REGEX REPLACE "${series_regex}" "\\1;\\2" tuple "${entry}")
00365     list(LENGTH tuple len)
00366     if("${len}" EQUAL 2)
00367       list(GET tuple 0 relname)
00368       list(GET tuple 1 alg)
00369       set(name "${top_src}/${relname}")
00370       set(file "${top_bin}/${relname}")
00371       if(alg)
00372         list(APPEND external "${file}|${name}|${alg}")
00373       elseif(ExternalData_LINK_CONTENT)
00374         _ExternalData_link_content("${name}" alg)
00375         list(APPEND external "${file}|${name}|${alg}")
00376       else()
00377         list(APPEND internal "${file}|${name}")
00378       endif()
00379       if("${relname}" STREQUAL "${reldata}")
00380         set(have_original 1)
00381       endif()
00382     endif()
00383   endforeach()
00384 
00385   if(NOT have_original)
00386     message(FATAL_ERROR "Data file referenced by argument\n"
00387       "  ${arg}\n"
00388       "corresponds to source tree path\n"
00389       "  ${reldata}\n"
00390       "that does not exist (with or without an extension)!")
00391   endif()
00392 
00393   if(external)
00394     # Make the series available in the build tree.
00395     set_property(GLOBAL APPEND PROPERTY
00396       _ExternalData_${target}_FETCH "${external}")
00397     set_property(GLOBAL APPEND PROPERTY
00398       _ExternalData_${target}_LOCAL "${internal}")
00399     set("${var_file}" "${top_bin}/${reldata}" PARENT_SCOPE)
00400   else()
00401     # The whole series is in the source tree.
00402     set("${var_file}" "${top_src}/${reldata}" PARENT_SCOPE)
00403   endif()
00404 endfunction()
00405 
00406 #-----------------------------------------------------------------------------
00407 # Private script mode interface
00408 
00409 if(CMAKE_GENERATOR OR NOT ExternalData_ACTION)
00410   return()
00411 endif()
00412 
00413 if(ExternalData_CONFIG)
00414   include(${ExternalData_CONFIG})
00415 endif()
00416 if(NOT ExternalData_URL_TEMPLATES)
00417   message(FATAL_ERROR "No ExternalData_URL_TEMPLATES set!")
00418 endif()
00419 
00420 function(_ExternalData_link_or_copy src dst)
00421   # Create a temporary file first.
00422   get_filename_component(dst_dir "${dst}" PATH)
00423   file(MAKE_DIRECTORY "${dst_dir}")
00424   string(RANDOM LENGTH 6 random)
00425   set(tmp "${dst}.tmp${random}")
00426   if(UNIX)
00427     # Create a symbolic link.
00428     set(tgt "${src}")
00429     if(relative_top)
00430       # Use relative path if files are close enough.
00431       file(RELATIVE_PATH relsrc "${relative_top}" "${src}")
00432       file(RELATIVE_PATH relfile "${relative_top}" "${dst}")
00433       if(NOT IS_ABSOLUTE "${relsrc}" AND NOT "${relsrc}" MATCHES "^\\.\\./" AND
00434           NOT IS_ABSOLUTE "${reldst}" AND NOT "${reldst}" MATCHES "^\\.\\./")
00435         file(RELATIVE_PATH tgt "${dst_dir}" "${src}")
00436       endif()
00437     endif()
00438     execute_process(COMMAND "${CMAKE_COMMAND}" -E create_symlink "${tgt}" "${tmp}" RESULT_VARIABLE result)
00439   else()
00440     # Create a copy.
00441     execute_process(COMMAND "${CMAKE_COMMAND}" -E copy "${src}" "${tmp}" RESULT_VARIABLE result)
00442   endif()
00443   if(result)
00444     file(REMOVE "${tmp}")
00445     message(FATAL_ERROR "Failed to create\n  ${tmp}\nfrom\n  ${obj}")
00446   endif()
00447 
00448   # Atomically create/replace the real destination.
00449   file(RENAME "${tmp}" "${dst}")
00450 endfunction()
00451 
00452 function(_ExternalData_download_object name hash algo var_obj)
00453   set(obj "${ExternalData_OBJECT_DIR}/${algo}/${hash}")
00454   if(EXISTS "${obj}")
00455     message(STATUS "Found object: \"${obj}\"")
00456     set("${var_obj}" "${obj}" PARENT_SCOPE)
00457     return()
00458   endif()
00459 
00460   string(RANDOM LENGTH 6 random)
00461   set(tmp "${obj}.tmp${random}")
00462   set(found 0)
00463   set(tried "")
00464   foreach(url_template IN LISTS ExternalData_URL_TEMPLATES)
00465     string(REPLACE "%(hash)" "${hash}" url_tmp "${url_template}")
00466     string(REPLACE "%(algo)" "${algo}" url "${url_tmp}")
00467     message(STATUS "Fetching \"${url}\"")
00468     file(DOWNLOAD "${url}" "${tmp}" STATUS status SHOW_PROGRESS) # TODO: timeout
00469     set(tried "${tried}\n  ${url}")
00470     list(GET status 0 err)
00471     if(err)
00472       list(GET status 1 errMsg)
00473       set(tried "${tried} (${errMsg})")
00474     else()
00475       # Verify downloaded object.
00476       _ExternalData_compute_hash(dl_hash "${algo}" "${tmp}")
00477       if("${dl_hash}" STREQUAL "${hash}")
00478         set(found 1)
00479         break()
00480       else()
00481         set(tried "${tried} (wrong hash ${algo}=${dl_hash})")
00482       endif()
00483     endif()
00484     file(REMOVE "${tmp}")
00485   endforeach()
00486 
00487   get_filename_component(dir "${name}" PATH)
00488   set(staged "${dir}/.ExternalData_${algo}_${hash}")
00489 
00490   if(found)
00491     file(RENAME "${tmp}" "${obj}")
00492     message(STATUS "Downloaded object: \"${obj}\"")
00493   elseif(EXISTS "${staged}")
00494     set(obj "${staged}")
00495     message(STATUS "Staged object: \"${obj}\"")
00496   else()
00497     message(FATAL_ERROR "Object ${algo}=${hash} not found at:${tried}")
00498   endif()
00499 
00500   set("${var_obj}" "${obj}" PARENT_SCOPE)
00501 endfunction()
00502 
00503 if("${ExternalData_ACTION}" STREQUAL "fetch")
00504   foreach(v ExternalData_OBJECT_DIR file name ext)
00505     if(NOT DEFINED "${v}")
00506       message(FATAL_ERROR "No \"-D${v}=\" value provided!")
00507     endif()
00508   endforeach()
00509 
00510   file(READ "${name}${ext}" hash)
00511   string(STRIP "${hash}" hash)
00512 
00513   if("${ext}" STREQUAL ".md5")
00514     set(algo "MD5")
00515   else()
00516     message(FATAL_ERROR "Unknown hash algorithm extension \"${ext}\"")
00517   endif()
00518 
00519   _ExternalData_download_object("${name}" "${hash}" "${algo}" obj)
00520 
00521   # Check if file already corresponds to the object.
00522   set(file_up_to_date 0)
00523   if(EXISTS "${file}" AND EXISTS "${file}${ext}")
00524     file(READ "${file}${ext}" f_hash)
00525     string(STRIP "${f_hash}" f_hash)
00526     if("${f_hash}" STREQUAL "${hash}")
00527       #message(STATUS "File already corresponds to object")
00528       set(file_up_to_date 1)
00529     endif()
00530   endif()
00531 
00532   if(file_up_to_date)
00533     # Touch the file to convince the build system it is up to date.
00534     execute_process(COMMAND "${CMAKE_COMMAND}" -E touch "${file}")
00535   else()
00536     _ExternalData_link_or_copy("${obj}" "${file}")
00537   endif()
00538 
00539   # Atomically update the hash/timestamp file to record the object referenced.
00540   _ExternalData_atomic_write("${file}${ext}" "${hash}\n")
00541 elseif("${ExternalData_ACTION}" STREQUAL "local")
00542   foreach(v file name)
00543     if(NOT DEFINED "${v}")
00544       message(FATAL_ERROR "No \"-D${v}=\" value provided!")
00545     endif()
00546   endforeach()
00547   _ExternalData_link_or_copy("${name}" "${file}")
00548 elseif("${ExternalData_ACTION}" STREQUAL "store")
00549   foreach(v dir file)
00550     if(NOT DEFINED "${v}")
00551       message(FATAL_ERROR "No \"-D${v}=\" value provided!")
00552     endif()
00553   endforeach()
00554   if(NOT DEFINED algo)
00555     set(algo "MD5")
00556   endif()
00557   _ExternalData_compute_hash(hash "${algo}" "${file}")
00558 else()
00559   message(FATAL_ERROR "Unknnown ExternalData_ACTION=[${ExternalData_ACTION}]")
00560 endif()