Просмотр исходного кода

Merge topic 'add-ExternalData-module'

5484c60 Merge branch 'vs6-rule-files' into add-ExternalData-module
1fd8d01 ExternalData: Attach download rules to content links in IDEs
a6d3ffc Fix Module.ExternalData test on VS 6
aed590a Fix Module.ExternalData test on Cygwin
06e8ded Merge branch 'fix-atomic-rename-on-Windows' into add-ExternalData-module
e2e0d2e ExternalData: Collapse ../ components in DATA{} paths
ee2abfd ExternalData: Add support for SHA 1 and 2 hash algorithms
aa8b228 ExternalData: Generalize hash algo/ext handling
9e518a8 ExternalData: Allow DATA{} syntax to reference directories
175ed02 ExternalData: Allow ()-groups in series match regex
4befecc ExternalData: Add tests covering interfaces and errors
bcd2580 ExternalData: Improve series matching using an explicit syntax
c0cebcb ExternalData: Remove unused private interface
5275993 ExternalData: Cleanup stray TODO and typo in comments
7bb8344 ExternalData: Do not match directory names when resolving DATA{}
00d801f ExternalData: Remove compatibility with CMake < 2.8.5
...
Brad King 13 лет назад
Родитель
Сommit
020525845a
100 измененных файлов с 1099 добавлено и 0 удалено
  1. 761 0
      Modules/ExternalData.cmake
  2. 4 0
      Modules/ExternalData_config.cmake.in
  3. 13 0
      Tests/CMakeLists.txt
  4. 38 0
      Tests/Module/ExternalData/CMakeLists.txt
  5. 1 0
      Tests/Module/ExternalData/Data.dat.md5
  6. 52 0
      Tests/Module/ExternalData/Data1Check.cmake
  7. 1 0
      Tests/Module/ExternalData/Data2.dat.md5
  8. 11 0
      Tests/Module/ExternalData/Data2/CMakeLists.txt
  9. 12 0
      Tests/Module/ExternalData/Data2/Data2Check.cmake
  10. 1 0
      Tests/Module/ExternalData/Data2/SeriesC_1_.my.dat.md5
  11. 1 0
      Tests/Module/ExternalData/Data2/SeriesC_2_.my.dat.md5
  12. 1 0
      Tests/Module/ExternalData/Data2/SeriesC_3_.my.dat.md5
  13. 1 0
      Tests/Module/ExternalData/Data2b.dat.md5
  14. 14 0
      Tests/Module/ExternalData/Data3/CMakeLists.txt
  15. 1 0
      Tests/Module/ExternalData/Data3/Data.dat.md5
  16. 25 0
      Tests/Module/ExternalData/Data3/Data3Check.cmake
  17. 1 0
      Tests/Module/ExternalData/Data3/Other.dat.md5
  18. 1 0
      Tests/Module/ExternalData/Directory/A.dat.md5
  19. 1 0
      Tests/Module/ExternalData/Directory/B.dat.md5
  20. 1 0
      Tests/Module/ExternalData/Directory/C.dat.md5
  21. 1 0
      Tests/Module/ExternalData/MD5/.gitattributes
  22. 1 0
      Tests/Module/ExternalData/MD5/08cfcf221f76ace7b906b312284e73d7
  23. 1 0
      Tests/Module/ExternalData/MD5/30ba0acdee9096b3b9fc6c69362c6b42
  24. 1 0
      Tests/Module/ExternalData/MD5/31eff09e84fca01415f8cd9d82ec432b
  25. 1 0
      Tests/Module/ExternalData/MD5/401767f22a456b3522953722090a2c36
  26. 1 0
      Tests/Module/ExternalData/MD5/8c018830e3efa5caf3c7415028335a57
  27. 1 0
      Tests/Module/ExternalData/MD5/8f4add4581551facf27237e6577fd662
  28. 1 0
      Tests/Module/ExternalData/MD5/9d980b06c2f0fec3d4872d68175b9822
  29. 1 0
      Tests/Module/ExternalData/MD5/aaad162b85f60d1eb57ca71a23e8efd7
  30. 1 0
      Tests/Module/ExternalData/MD5/c1030719c95f3435d8abc39c0d442946
  31. 1 0
      Tests/Module/ExternalData/MD5/ce38ea6c3c1e00fa6405dd64b8bf6da0
  32. 1 0
      Tests/Module/ExternalData/MD5/ecfa1ecd417d4253af81ae04d1bd6581
  33. 1 0
      Tests/Module/ExternalData/MD5/f41c94425d01ecbbee70440b951cb058
  34. 1 0
      Tests/Module/ExternalData/MD5/f7ab5a04aae9cb9a520e70b20b9c8ed7
  35. 1 0
      Tests/Module/ExternalData/MetaA.dat.md5
  36. 1 0
      Tests/Module/ExternalData/MetaB.dat.md5
  37. 1 0
      Tests/Module/ExternalData/MetaC.dat.md5
  38. 1 0
      Tests/Module/ExternalData/MetaTop.dat.md5
  39. 1 0
      Tests/Module/ExternalData/PairedA.dat.md5
  40. 1 0
      Tests/Module/ExternalData/PairedB.dat.md5
  41. 1 0
      Tests/Module/ExternalData/SHA1/.gitattributes
  42. 1 0
      Tests/Module/ExternalData/SHA1/2af59a7022024974f3b8521b7ed8137c996a79f1
  43. 1 0
      Tests/Module/ExternalData/SHA224/.gitattributes
  44. 1 0
      Tests/Module/ExternalData/SHA224/3b679da7908562fe1cc28db47ffb89bae025f4551dceb343a5869174
  45. 1 0
      Tests/Module/ExternalData/SHA256/.gitattributes
  46. 1 0
      Tests/Module/ExternalData/SHA256/969171a0dd70d49ce096bd3e8178c7e26c711c9b20dbcaa3853d869d3871f133
  47. 1 0
      Tests/Module/ExternalData/SeriesA.dat.md5
  48. 1 0
      Tests/Module/ExternalData/SeriesA1.dat.md5
  49. 1 0
      Tests/Module/ExternalData/SeriesA2.dat.md5
  50. 1 0
      Tests/Module/ExternalData/SeriesA3.dat.md5
  51. 1 0
      Tests/Module/ExternalData/SeriesAn1.dat.md5
  52. 1 0
      Tests/Module/ExternalData/SeriesAn2.dat.md5
  53. 1 0
      Tests/Module/ExternalData/SeriesAn3.dat.md5
  54. 1 0
      Tests/Module/ExternalData/SeriesB.dat.md5
  55. 1 0
      Tests/Module/ExternalData/SeriesB_1.dat.md5
  56. 1 0
      Tests/Module/ExternalData/SeriesB_2.dat.md5
  57. 1 0
      Tests/Module/ExternalData/SeriesB_3.dat.md5
  58. 1 0
      Tests/Module/ExternalData/SeriesBn_1.dat.md5
  59. 1 0
      Tests/Module/ExternalData/SeriesBn_2.dat.md5
  60. 1 0
      Tests/Module/ExternalData/SeriesBn_3.dat.md5
  61. 1 0
      Tests/Module/ExternalData/SeriesC.1.dat.md5
  62. 1 0
      Tests/Module/ExternalData/SeriesC.2.dat.md5
  63. 1 0
      Tests/Module/ExternalData/SeriesC.3.dat.md5
  64. 1 0
      Tests/Module/ExternalData/SeriesC.dat.md5
  65. 1 0
      Tests/Module/ExternalData/SeriesCn.1.dat.md5
  66. 1 0
      Tests/Module/ExternalData/SeriesCn.2.dat.md5
  67. 1 0
      Tests/Module/ExternalData/SeriesCn.3.dat.md5
  68. 1 0
      Tests/Module/ExternalData/SeriesD-1.dat.md5
  69. 1 0
      Tests/Module/ExternalData/SeriesD-2.dat.md5
  70. 1 0
      Tests/Module/ExternalData/SeriesD-3.dat.md5
  71. 1 0
      Tests/Module/ExternalData/SeriesD.dat.md5
  72. 1 0
      Tests/Module/ExternalData/SeriesDn-1.dat.md5
  73. 1 0
      Tests/Module/ExternalData/SeriesDn-2.dat.md5
  74. 1 0
      Tests/Module/ExternalData/SeriesDn-3.dat.md5
  75. 1 0
      Tests/Module/ExternalData/SeriesMixed.1.dat.md5
  76. 1 0
      Tests/Module/ExternalData/SeriesMixed.2.dat.sha1
  77. 1 0
      Tests/Module/ExternalData/SeriesMixed.3.dat.sha224
  78. 1 0
      Tests/Module/ExternalData/SeriesMixed.4.dat.sha256
  79. 1 0
      Tests/RunCMake/CMakeLists.txt
  80. 1 0
      Tests/RunCMake/ExternalData/BadHashAlgo1-result.txt
  81. 8 0
      Tests/RunCMake/ExternalData/BadHashAlgo1-stderr.txt
  82. 3 0
      Tests/RunCMake/ExternalData/BadHashAlgo1.cmake
  83. 1 0
      Tests/RunCMake/ExternalData/BadHashAlgo1.txt
  84. 1 0
      Tests/RunCMake/ExternalData/BadOption1-result.txt
  85. 9 0
      Tests/RunCMake/ExternalData/BadOption1-stderr.txt
  86. 5 0
      Tests/RunCMake/ExternalData/BadOption1.cmake
  87. 1 0
      Tests/RunCMake/ExternalData/BadOption2-result.txt
  88. 9 0
      Tests/RunCMake/ExternalData/BadOption2-stderr.txt
  89. 5 0
      Tests/RunCMake/ExternalData/BadOption2.cmake
  90. 1 0
      Tests/RunCMake/ExternalData/BadSeries1-result.txt
  91. 19 0
      Tests/RunCMake/ExternalData/BadSeries1-stderr.txt
  92. 3 0
      Tests/RunCMake/ExternalData/BadSeries1.cmake
  93. 1 0
      Tests/RunCMake/ExternalData/BadSeries2-result.txt
  94. 16 0
      Tests/RunCMake/ExternalData/BadSeries2-stderr.txt
  95. 3 0
      Tests/RunCMake/ExternalData/BadSeries2.cmake
  96. 1 0
      Tests/RunCMake/ExternalData/BadSeries3-result.txt
  97. 6 0
      Tests/RunCMake/ExternalData/BadSeries3-stderr.txt
  98. 2 0
      Tests/RunCMake/ExternalData/BadSeries3.cmake
  99. 3 0
      Tests/RunCMake/ExternalData/CMakeLists.txt
  100. 1 0
      Tests/RunCMake/ExternalData/Data.txt.md5

+ 761 - 0
Modules/ExternalData.cmake

@@ -0,0 +1,761 @@
+# - Manage data files stored outside source tree
+# Use this module to unambiguously reference data files stored outside the
+# source tree and fetch them at build time from arbitrary local and remote
+# content-addressed locations.  Functions provided by this module recognize
+# arguments with the syntax "DATA{<name>}" as references to external data,
+# replace them with full paths to local copies of those data, and create build
+# rules to fetch and update the local copies.
+#
+# The DATA{} syntax is literal and the <name> is a full or relative path
+# within the source tree.  The source tree must contain either a real data
+# file at <name> or a "content link" at <name><ext> containing a hash of the
+# real file using a hash algorithm corresponding to <ext>.  For example, the
+# argument "DATA{img.png}" may be satisfied by either a real "img.png" file in
+# the current source directory or a "img.png.md5" file containing its MD5 sum.
+#
+# The 'ExternalData_Expand_Arguments' function evaluates DATA{} references
+# in its arguments and constructs a new list of arguments:
+#  ExternalData_Expand_Arguments(
+#    <target>   # Name of data management target
+#    <outVar>   # Output variable
+#    [args...]  # Input arguments, DATA{} allowed
+#    )
+# It replaces each DATA{} reference in an argument with the full path of a
+# real data file on disk that will exist after the <target> builds.
+#
+# The 'ExternalData_Add_Test' function wraps around the CMake add_test()
+# command but supports DATA{} references in its arguments:
+#  ExternalData_Add_Test(
+#    <target>   # Name of data management target
+#    ...        # Arguments of add_test(), DATA{} allowed
+#    )
+# It passes its arguments through ExternalData_Expand_Arguments and then
+# invokes add_test() using the results.
+#
+# The 'ExternalData_Add_Target' function creates a custom target to manage
+# local instances of data files stored externally:
+#  ExternalData_Add_Target(
+#    <target>   # Name of data management target
+#    )
+# It creates custom commands in the target as necessary to make data files
+# available for each DATA{} reference previously evaluated by other functions
+# provided by this module.  A list of URL templates must be provided in the
+# variable ExternalData_URL_TEMPLATES using the placeholders "%(algo)" and
+# "%(hash)" in each template.  Data fetch rules try each URL template in order
+# by substituting the hash algorithm name for "%(algo)" and the hash value for
+# "%(hash)".
+#
+# The following hash algorithms are supported:
+#    %(algo)     <ext>     Description
+#    -------     -----     -----------
+#    MD5         .md5      Message-Digest Algorithm 5, RFC 1321
+#    SHA1        .sha1     US Secure Hash Algorithm 1, RFC 3174
+#    SHA224      .sha224   US Secure Hash Algorithms, RFC 4634
+#    SHA256      .sha256   US Secure Hash Algorithms, RFC 4634
+#    SHA384      .sha384   US Secure Hash Algorithms, RFC 4634
+#    SHA512      .sha512   US Secure Hash Algorithms, RFC 4634
+# Note that the hashes are used only for unique data identification and
+# download verification.  This is not security software.
+#
+# Example usage:
+#   include(ExternalData)
+#   set(ExternalData_URL_TEMPLATES "file:///local/%(algo)/%(hash)"
+#                                  "http://data.org/%(algo)/%(hash)")
+#   ExternalData_Add_Test(MyData
+#     NAME MyTest
+#     COMMAND MyExe DATA{MyInput.png}
+#     )
+#   ExternalData_Add_Target(MyData)
+# When test "MyTest" runs the "DATA{MyInput.png}" argument will be replaced by
+# the full path to a real instance of the data file "MyInput.png" on disk.  If
+# the source tree contains a content link such as "MyInput.png.md5" then the
+# "MyData" target creates a real "MyInput.png" in the build tree.
+#
+# The DATA{} syntax can be told to fetch a file series using the form
+# "DATA{<name>,:}", where the ":" is literal.  If the source tree contains a
+# group of files or content links named like a series then a reference to one
+# member adds rules to fetch all of them.  Although all members of a series
+# are fetched, only the file originally named by the DATA{} argument is
+# substituted for it.  The default configuration recognizes file series names
+# ending with "#.ext", "_#.ext", ".#.ext", or "-#.ext" where "#" is a sequence
+# of decimal digits and ".ext" is any single extension.  Configure it with a
+# regex that parses <number> and <suffix> parts from the end of <name>:
+#  ExternalData_SERIES_PARSE = regex of the form (<number>)(<suffix>)$
+# For more complicated cases set:
+#  ExternalData_SERIES_PARSE = regex with at least two () groups
+#  ExternalData_SERIES_PARSE_PREFIX = <prefix> regex group number, if any
+#  ExternalData_SERIES_PARSE_NUMBER = <number> regex group number
+#  ExternalData_SERIES_PARSE_SUFFIX = <suffix> regex group number
+# Configure series number matching with a regex that matches the
+# <number> part of series members named <prefix><number><suffix>:
+#  ExternalData_SERIES_MATCH = regex matching <number> in all series members
+# Note that the <suffix> of a series does not include a hash-algorithm
+# extension.
+#
+# The DATA{} syntax can alternatively match files associated with the named
+# file and contained in the same directory.  Associated files may be specified
+# by options using the syntax DATA{<name>,<opt1>,<opt2>,...}.  Each option may
+# specify one file by name or specify a regular expression to match file names
+# using the syntax REGEX:<regex>.  For example, the arguments
+#   DATA{MyData/MyInput.mhd,MyInput.img}                   # File pair
+#   DATA{MyData/MyFrames00.png,REGEX:MyFrames[0-9]+\\.png} # Series
+# will pass MyInput.mha and MyFrames00.png on the command line but ensure
+# that the associated files are present next to them.
+#
+# The DATA{} syntax may reference a directory using a trailing slash and a
+# list of associated files.  The form DATA{<name>/,<opt1>,<opt2>,...} adds
+# rules to fetch any files in the directory that match one of the associated
+# file options.  For example, the argument DATA{MyDataDir/,REGEX:.*} will pass
+# the full path to a MyDataDir directory on the command line and ensure that
+# the directory contains files corresponding to every file or content link in
+# the MyDataDir source directory.
+#
+# The variable ExternalData_LINK_CONTENT may be set to the name of a supported
+# hash algorithm to enable automatic conversion of real data files referenced
+# by the DATA{} syntax into content links.  For each such <file> a content
+# link named "<file><ext>" is created.  The original file is renamed to the
+# form ".ExternalData_<algo>_<hash>" to stage it for future transmission to
+# one of the locations in the list of URL templates (by means outside the
+# scope of this module).  The data fetch rule created for the content link
+# will use the staged object if it cannot be found using any URL template.
+#
+# The variable ExternalData_OBJECT_STORES may be set to a list of local
+# directories that store objects using the layout <dir>/%(algo)/%(hash).
+# These directories will be searched first for a needed object.  If the object
+# is not available in any store then it will be fetched remotely using the URL
+# templates and added to the first local store listed.  If no stores are
+# specified the default is a location inside the build tree.
+#
+# The variable ExternalData_SOURCE_ROOT may be set to the highest source
+# directory containing any path named by a DATA{} reference.  The default is
+# CMAKE_SOURCE_DIR.  ExternalData_SOURCE_ROOT and CMAKE_SOURCE_DIR must refer
+# to directories within a single source distribution (e.g. they come together
+# in one tarball).
+#
+# The variable ExternalData_BINARY_ROOT may be set to the directory to hold
+# the real data files named by expanded DATA{} references.  The default is
+# CMAKE_BINARY_DIR.  The directory layout will mirror that of content links
+# under ExternalData_SOURCE_ROOT.
+#
+# Variables ExternalData_TIMEOUT_INACTIVITY and ExternalData_TIMEOUT_ABSOLUTE
+# set the download inactivity and absolute timeouts, in seconds.  The defaults
+# are 60 seconds and 300 seconds, respectively.  Set either timeout to 0
+# seconds to disable enforcement.
+
+#=============================================================================
+# Copyright 2010-2013 Kitware, Inc.
+#
+# Distributed under the OSI-approved BSD License (the "License");
+# see accompanying file Copyright.txt for details.
+#
+# This software is distributed WITHOUT ANY WARRANTY; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the License for more information.
+#=============================================================================
+# (To distribute this file outside of CMake, substitute the full
+#  License text for the above reference.)
+
+function(ExternalData_add_test target)
+  ExternalData_expand_arguments("${target}" testArgs ${ARGN})
+  add_test(${testArgs})
+endfunction()
+
+function(ExternalData_add_target target)
+  if(NOT ExternalData_URL_TEMPLATES)
+    message(FATAL_ERROR "ExternalData_URL_TEMPLATES is not set!")
+  endif()
+  if(NOT ExternalData_OBJECT_STORES)
+    set(ExternalData_OBJECT_STORES ${CMAKE_BINARY_DIR}/ExternalData/Objects)
+  endif()
+  set(config ${CMAKE_CURRENT_BINARY_DIR}/${target}_config.cmake)
+  configure_file(${_ExternalData_SELF_DIR}/ExternalData_config.cmake.in ${config} @ONLY)
+
+  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.
+
+  # Custom commands to copy or link local data.
+  get_property(data_local GLOBAL PROPERTY _ExternalData_${target}_LOCAL)
+  foreach(entry IN LISTS data_local)
+    string(REPLACE "|" ";" tuple "${entry}")
+    list(GET tuple 0 file)
+    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}"
+        )
+      list(APPEND files "${file}")
+    endif()
+  endforeach()
+
+  # Custom commands to fetch remote data.
+  get_property(data_fetch GLOBAL PROPERTY _ExternalData_${target}_FETCH)
+  foreach(entry IN LISTS data_fetch)
+    string(REPLACE "|" ";" tuple "${entry}")
+    list(GET tuple 0 file)
+    list(GET tuple 1 name)
+    list(GET tuple 2 ext)
+    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}"
+        )
+      list(APPEND files "${file}${stamp}")
+    endif()
+  endforeach()
+
+  # Custom target to drive all update commands.
+  add_custom_target(${target} ALL DEPENDS ${files})
+endfunction()
+
+function(ExternalData_expand_arguments target outArgsVar)
+  # Replace DATA{} references with real arguments.
+  set(data_regex "DATA{([^{}\r\n]*)}")
+  set(other_regex "([^D]|D[^A]|DA[^T]|DAT[^A]|DATA[^{])+|.")
+  set(outArgs "")
+  foreach(arg IN LISTS ARGN)
+    if("x${arg}" MATCHES "${data_regex}")
+      # Split argument into DATA{}-pieces and other pieces.
+      string(REGEX MATCHALL "${data_regex}|${other_regex}" pieces "${arg}")
+      # Compose output argument with DATA{}-pieces replaced.
+      set(outArg "")
+      foreach(piece IN LISTS pieces)
+        if("x${piece}" MATCHES "^x${data_regex}$")
+          # Replace this DATA{}-piece with a file path.
+          string(REGEX REPLACE "${data_regex}" "\\1" data "${piece}")
+          _ExternalData_arg("${target}" "${piece}" "${data}" file)
+          set(outArg "${outArg}${file}")
+        else()
+          # No replacement needed for this piece.
+          set(outArg "${outArg}${piece}")
+        endif()
+      endforeach()
+      list(APPEND outArgs "${outArg}")
+    else()
+      # No replacements needed in this argument.
+      list(APPEND outArgs "${arg}")
+    endif()
+  endforeach()
+  set("${outArgsVar}" "${outArgs}" PARENT_SCOPE)
+endfunction()
+
+#-----------------------------------------------------------------------------
+# Private helper interface
+
+set(_ExternalData_REGEX_ALGO "MD5|SHA1|SHA224|SHA256|SHA384|SHA512")
+set(_ExternalData_REGEX_EXT "md5|sha1|sha224|sha256|sha384|sha512")
+set(_ExternalData_SELF "${CMAKE_CURRENT_LIST_FILE}")
+get_filename_component(_ExternalData_SELF_DIR "${_ExternalData_SELF}" PATH)
+
+function(_ExternalData_compute_hash var_hash algo file)
+  if("${algo}" MATCHES "^${_ExternalData_REGEX_ALGO}$")
+    file("${algo}" "${file}" hash)
+    set("${var_hash}" "${hash}" PARENT_SCOPE)
+  else()
+    message(FATAL_ERROR "Hash algorithm ${algo} unimplemented.")
+  endif()
+endfunction()
+
+function(_ExternalData_random var)
+  string(RANDOM LENGTH 6 random)
+  set("${var}" "${random}" PARENT_SCOPE)
+endfunction()
+
+function(_ExternalData_exact_regex regex_var string)
+  string(REGEX REPLACE "([][+.*()^])" "\\\\\\1" regex "${string}")
+  set("${regex_var}" "${regex}" PARENT_SCOPE)
+endfunction()
+
+function(_ExternalData_atomic_write file content)
+  _ExternalData_random(random)
+  set(tmp "${file}.tmp${random}")
+  file(WRITE "${tmp}" "${content}")
+  file(RENAME "${tmp}" "${file}")
+endfunction()
+
+function(_ExternalData_link_content name var_ext)
+  if("${ExternalData_LINK_CONTENT}" MATCHES "^(${_ExternalData_REGEX_ALGO})$")
+    set(algo "${ExternalData_LINK_CONTENT}")
+  else()
+    message(FATAL_ERROR
+      "Unknown hash algorithm specified by ExternalData_LINK_CONTENT:\n"
+      "  ${ExternalData_LINK_CONTENT}")
+  endif()
+  _ExternalData_compute_hash(hash "${algo}" "${name}")
+  get_filename_component(dir "${name}" PATH)
+  set(staged "${dir}/.ExternalData_${algo}_${hash}")
+  string(TOLOWER ".${algo}" ext)
+  _ExternalData_atomic_write("${name}${ext}" "${hash}\n")
+  file(RENAME "${name}" "${staged}")
+  set("${var_ext}" "${ext}" PARENT_SCOPE)
+
+  file(RELATIVE_PATH relname "${ExternalData_SOURCE_ROOT}" "${name}${ext}")
+  message(STATUS "Linked ${relname} to ExternalData ${algo}/${hash}")
+endfunction()
+
+function(_ExternalData_arg target arg options var_file)
+  # Separate data path from the options.
+  string(REPLACE "," ";" options "${options}")
+  list(GET options 0 data)
+  list(REMOVE_AT options 0)
+
+  # Interpret trailing slashes as directories.
+  set(data_is_directory 0)
+  if("x${data}" MATCHES "^x(.*)([/\\])$")
+    set(data_is_directory 1)
+    set(data "${CMAKE_MATCH_1}")
+  endif()
+
+  # Convert to full path.
+  if(IS_ABSOLUTE "${data}")
+    set(absdata "${data}")
+  else()
+    set(absdata "${CMAKE_CURRENT_SOURCE_DIR}/${data}")
+  endif()
+  get_filename_component(absdata "${absdata}" ABSOLUTE)
+
+  # Convert to relative path under the source tree.
+  if(NOT ExternalData_SOURCE_ROOT)
+    set(ExternalData_SOURCE_ROOT "${CMAKE_SOURCE_DIR}")
+  endif()
+  set(top_src "${ExternalData_SOURCE_ROOT}")
+  file(RELATIVE_PATH reldata "${top_src}" "${absdata}")
+  if(IS_ABSOLUTE "${reldata}" OR "${reldata}" MATCHES "^\\.\\./")
+    message(FATAL_ERROR "Data file referenced by argument\n"
+      "  ${arg}\n"
+      "does not lie under the top-level source directory\n"
+      "  ${top_src}\n")
+  endif()
+  if(data_is_directory AND NOT IS_DIRECTORY "${top_src}/${reldata}")
+    message(FATAL_ERROR "Data directory referenced by argument\n"
+      "  ${arg}\n"
+      "corresponds to source tree path\n"
+      "  ${reldata}\n"
+      "that does not exist as a directory!")
+  endif()
+  if(NOT ExternalData_BINARY_ROOT)
+    set(ExternalData_BINARY_ROOT "${CMAKE_BINARY_DIR}")
+  endif()
+  set(top_bin "${ExternalData_BINARY_ROOT}")
+
+  # Handle in-source builds gracefully.
+  if("${top_src}" STREQUAL "${top_bin}")
+    if(ExternalData_LINK_CONTENT)
+      message(WARNING "ExternalData_LINK_CONTENT cannot be used in-source")
+      set(ExternalData_LINK_CONTENT 0)
+    endif()
+    set(top_same 1)
+  endif()
+
+  set(external "") # Entries external to the source tree.
+  set(internal "") # Entries internal to the source tree.
+  set(have_original ${data_is_directory})
+
+  # Process options.
+  set(series_option "")
+  set(associated_files "")
+  set(associated_regex "")
+  foreach(opt ${options})
+    if("x${opt}" MATCHES "^xREGEX:[^:/]+$")
+      # Regular expression to match associated files.
+      string(REGEX REPLACE "^REGEX:" "" regex "${opt}")
+      list(APPEND associated_regex "${regex}")
+    elseif("x${opt}" MATCHES "^x:$")
+      # Activate series matching.
+      set(series_option "${opt}")
+    elseif("x${opt}" MATCHES "^[^][:/*?]+$")
+      # Specific associated file.
+      list(APPEND associated_files "${opt}")
+    else()
+      message(FATAL_ERROR "Unknown option \"${opt}\" in argument\n"
+        "  ${arg}\n")
+    endif()
+  endforeach()
+
+  if(series_option)
+    if(data_is_directory)
+      message(FATAL_ERROR "Series option \"${series_option}\" not allowed with directories.")
+    endif()
+    if(associated_files OR associated_regex)
+      message(FATAL_ERROR "Series option \"${series_option}\" not allowed with associated files.")
+    endif()
+    # Load a whole file series.
+    _ExternalData_arg_series()
+  elseif(data_is_directory)
+    if(associated_files OR associated_regex)
+      # Load listed/matching associated files in the directory.
+      _ExternalData_arg_associated()
+    else()
+      message(FATAL_ERROR "Data directory referenced by argument\n"
+        "  ${arg}\n"
+        "must list associated files.")
+    endif()
+  else()
+    # Load the named data file.
+    _ExternalData_arg_single()
+    if(associated_files OR associated_regex)
+      # Load listed/matching associated files.
+      _ExternalData_arg_associated()
+    endif()
+  endif()
+
+  if(NOT have_original)
+    message(FATAL_ERROR "Data file referenced by argument\n"
+      "  ${arg}\n"
+      "corresponds to source tree path\n"
+      "  ${reldata}\n"
+      "that does not exist as a file (with or without an extension)!")
+  endif()
+
+  if(external)
+    # Make the series available in the build tree.
+    set_property(GLOBAL APPEND PROPERTY
+      _ExternalData_${target}_FETCH "${external}")
+    set_property(GLOBAL APPEND PROPERTY
+      _ExternalData_${target}_LOCAL "${internal}")
+    set("${var_file}" "${top_bin}/${reldata}" PARENT_SCOPE)
+  else()
+    # The whole series is in the source tree.
+    set("${var_file}" "${top_src}/${reldata}" PARENT_SCOPE)
+  endif()
+endfunction()
+
+macro(_ExternalData_arg_associated)
+  # Associated files lie in the same directory.
+  if(data_is_directory)
+    set(reldir "${reldata}")
+  else()
+    get_filename_component(reldir "${reldata}" PATH)
+  endif()
+  if(reldir)
+    set(reldir "${reldir}/")
+  endif()
+  _ExternalData_exact_regex(reldir_regex "${reldir}")
+
+  # Find files named explicitly.
+  foreach(file ${associated_files})
+    _ExternalData_exact_regex(file_regex "${file}")
+    _ExternalData_arg_find_files("${reldir}${file}" "${reldir_regex}${file_regex}")
+  endforeach()
+
+  # Find files matching the given regular expressions.
+  set(all "")
+  set(sep "")
+  foreach(regex ${associated_regex})
+    set(all "${all}${sep}${reldir_regex}${regex}")
+    set(sep "|")
+  endforeach()
+  _ExternalData_arg_find_files("${reldir}" "${all}")
+endmacro()
+
+macro(_ExternalData_arg_single)
+  # Match only the named data by itself.
+  _ExternalData_exact_regex(data_regex "${reldata}")
+  _ExternalData_arg_find_files("${reldata}" "${data_regex}")
+endmacro()
+
+macro(_ExternalData_arg_series)
+  # Configure series parsing and matching.
+  set(series_parse_prefix "")
+  set(series_parse_number "\\1")
+  set(series_parse_suffix "\\2")
+  if(ExternalData_SERIES_PARSE)
+    if(ExternalData_SERIES_PARSE_NUMBER AND ExternalData_SERIES_PARSE_SUFFIX)
+      if(ExternalData_SERIES_PARSE_PREFIX)
+        set(series_parse_prefix "\\${ExternalData_SERIES_PARSE_PREFIX}")
+      endif()
+      set(series_parse_number "\\${ExternalData_SERIES_PARSE_NUMBER}")
+      set(series_parse_suffix "\\${ExternalData_SERIES_PARSE_SUFFIX}")
+    elseif(NOT "x${ExternalData_SERIES_PARSE}" MATCHES "^x\\([^()]*\\)\\([^()]*\\)\\$$")
+      message(FATAL_ERROR
+        "ExternalData_SERIES_PARSE is set to\n"
+        "  ${ExternalData_SERIES_PARSE}\n"
+        "which is not of the form\n"
+        "  (<number>)(<suffix>)$\n"
+        "Fix the regular expression or set variables\n"
+        "  ExternalData_SERIES_PARSE_PREFIX = <prefix> regex group number, if any\n"
+        "  ExternalData_SERIES_PARSE_NUMBER = <number> regex group number\n"
+        "  ExternalData_SERIES_PARSE_SUFFIX = <suffix> regex group number\n"
+        )
+    endif()
+    set(series_parse "${ExternalData_SERIES_PARSE}")
+  else()
+    set(series_parse "([0-9]*)(\\.[^./]*)$")
+  endif()
+  if(ExternalData_SERIES_MATCH)
+    set(series_match "${ExternalData_SERIES_MATCH}")
+  else()
+    set(series_match "[_.-]?[0-9]*")
+  endif()
+
+  # Parse the base, number, and extension components of the series.
+  string(REGEX REPLACE "${series_parse}" "${series_parse_prefix};${series_parse_number};${series_parse_suffix}" tuple "${reldata}")
+  list(LENGTH tuple len)
+  if(NOT "${len}" EQUAL 3)
+    message(FATAL_ERROR "Data file referenced by argument\n"
+      "  ${arg}\n"
+      "corresponds to path\n"
+      "  ${reldata}\n"
+      "that does not match regular expression\n"
+      "  ${series_parse}")
+  endif()
+  list(GET tuple 0 relbase)
+  list(GET tuple 2 ext)
+
+  # Glob files that might match the series.
+  # Then match base, number, and extension.
+  _ExternalData_exact_regex(series_base "${relbase}")
+  _ExternalData_exact_regex(series_ext "${ext}")
+  _ExternalData_arg_find_files("${relbase}*${ext}"
+    "${series_base}${series_match}${series_ext}")
+endmacro()
+
+function(_ExternalData_arg_find_files pattern regex)
+  file(GLOB globbed RELATIVE "${top_src}" "${top_src}/${pattern}*")
+  foreach(entry IN LISTS globbed)
+    if("x${entry}" MATCHES "^x(.*)(\\.(${_ExternalData_REGEX_EXT}))$")
+      set(relname "${CMAKE_MATCH_1}")
+      set(alg "${CMAKE_MATCH_2}")
+    else()
+      set(relname "${entry}")
+      set(alg "")
+    endif()
+    if("x${relname}" MATCHES "^x${regex}$" AND NOT IS_DIRECTORY "${top_src}/${entry}")
+      set(name "${top_src}/${relname}")
+      set(file "${top_bin}/${relname}")
+      if(alg)
+        list(APPEND external "${file}|${name}|${alg}")
+      elseif(ExternalData_LINK_CONTENT)
+        _ExternalData_link_content("${name}" alg)
+        list(APPEND external "${file}|${name}|${alg}")
+      elseif(NOT top_same)
+        list(APPEND internal "${file}|${name}")
+      endif()
+      if("${relname}" STREQUAL "${reldata}")
+        set(have_original 1)
+      endif()
+    endif()
+  endforeach()
+  set(external "${external}" PARENT_SCOPE)
+  set(internal "${internal}" PARENT_SCOPE)
+  set(have_original "${have_original}" PARENT_SCOPE)
+endfunction()
+
+#-----------------------------------------------------------------------------
+# Private script mode interface
+
+if(CMAKE_GENERATOR OR NOT ExternalData_ACTION)
+  return()
+endif()
+
+if(ExternalData_CONFIG)
+  include(${ExternalData_CONFIG})
+endif()
+if(NOT ExternalData_URL_TEMPLATES)
+  message(FATAL_ERROR "No ExternalData_URL_TEMPLATES set!")
+endif()
+
+function(_ExternalData_link_or_copy src dst)
+  # Create a temporary file first.
+  get_filename_component(dst_dir "${dst}" PATH)
+  file(MAKE_DIRECTORY "${dst_dir}")
+  _ExternalData_random(random)
+  set(tmp "${dst}.tmp${random}")
+  if(UNIX)
+    # Create a symbolic link.
+    set(tgt "${src}")
+    if(relative_top)
+      # Use relative path if files are close enough.
+      file(RELATIVE_PATH relsrc "${relative_top}" "${src}")
+      file(RELATIVE_PATH relfile "${relative_top}" "${dst}")
+      if(NOT IS_ABSOLUTE "${relsrc}" AND NOT "${relsrc}" MATCHES "^\\.\\./" AND
+          NOT IS_ABSOLUTE "${reldst}" AND NOT "${reldst}" MATCHES "^\\.\\./")
+        file(RELATIVE_PATH tgt "${dst_dir}" "${src}")
+      endif()
+    endif()
+    execute_process(COMMAND "${CMAKE_COMMAND}" -E create_symlink "${tgt}" "${tmp}" RESULT_VARIABLE result)
+  else()
+    # Create a copy.
+    execute_process(COMMAND "${CMAKE_COMMAND}" -E copy "${src}" "${tmp}" RESULT_VARIABLE result)
+  endif()
+  if(result)
+    file(REMOVE "${tmp}")
+    message(FATAL_ERROR "Failed to create\n  ${tmp}\nfrom\n  ${obj}")
+  endif()
+
+  # Atomically create/replace the real destination.
+  file(RENAME "${tmp}" "${dst}")
+endfunction()
+
+function(_ExternalData_download_file url file err_var msg_var)
+  set(retry 3)
+  while(retry)
+    math(EXPR retry "${retry} - 1")
+    if(ExternalData_TIMEOUT_INACTIVITY)
+      set(inactivity_timeout INACTIVITY_TIMEOUT ${ExternalData_TIMEOUT_INACTIVITY})
+    elseif(NOT "${ExternalData_TIMEOUT_INACTIVITY}" EQUAL 0)
+      set(inactivity_timeout INACTIVITY_TIMEOUT 60)
+    else()
+      set(inactivity_timeout "")
+    endif()
+    if(ExternalData_TIMEOUT_ABSOLUTE)
+      set(absolute_timeout TIMEOUT ${ExternalData_TIMEOUT_ABSOLUTE})
+    elseif(NOT "${ExternalData_TIMEOUT_ABSOLUTE}" EQUAL 0)
+      set(absolute_timeout TIMEOUT 300)
+    else()
+      set(absolute_timeout "")
+    endif()
+    file(DOWNLOAD "${url}" "${file}" STATUS status LOG log ${inactivity_timeout} ${absolute_timeout} SHOW_PROGRESS)
+    list(GET status 0 err)
+    list(GET status 1 msg)
+    if(err)
+      if("${msg}" MATCHES "HTTP response code said error" AND
+          "${log}" MATCHES "error: 503")
+        set(msg "temporarily unavailable")
+      endif()
+    elseif("${log}" MATCHES "\nHTTP[^\n]* 503")
+      set(err TRUE)
+      set(msg "temporarily unavailable")
+    endif()
+    if(NOT err OR NOT "${msg}" MATCHES "partial|timeout|temporarily")
+      break()
+    elseif(retry)
+      message(STATUS "[download terminated: ${msg}, retries left: ${retry}]")
+    endif()
+  endwhile()
+  set("${err_var}" "${err}" PARENT_SCOPE)
+  set("${msg_var}" "${msg}" PARENT_SCOPE)
+endfunction()
+
+function(_ExternalData_download_object name hash algo var_obj)
+  # Search all object stores for an existing object.
+  foreach(dir ${ExternalData_OBJECT_STORES})
+    set(obj "${dir}/${algo}/${hash}")
+    if(EXISTS "${obj}")
+      message(STATUS "Found object: \"${obj}\"")
+      set("${var_obj}" "${obj}" PARENT_SCOPE)
+      return()
+    endif()
+  endforeach()
+
+  # Download object to the first store.
+  list(GET ExternalData_OBJECT_STORES 0 store)
+  set(obj "${store}/${algo}/${hash}")
+
+  _ExternalData_random(random)
+  set(tmp "${obj}.tmp${random}")
+  set(found 0)
+  set(tried "")
+  foreach(url_template IN LISTS ExternalData_URL_TEMPLATES)
+    string(REPLACE "%(hash)" "${hash}" url_tmp "${url_template}")
+    string(REPLACE "%(algo)" "${algo}" url "${url_tmp}")
+    message(STATUS "Fetching \"${url}\"")
+    _ExternalData_download_file("${url}" "${tmp}" err errMsg)
+    set(tried "${tried}\n  ${url}")
+    if(err)
+      set(tried "${tried} (${errMsg})")
+    else()
+      # Verify downloaded object.
+      _ExternalData_compute_hash(dl_hash "${algo}" "${tmp}")
+      if("${dl_hash}" STREQUAL "${hash}")
+        set(found 1)
+        break()
+      else()
+        set(tried "${tried} (wrong hash ${algo}=${dl_hash})")
+        if("$ENV{ExternalData_DEBUG_DOWNLOAD}" MATCHES ".")
+          file(RENAME "${tmp}" "${store}/${algo}/${dl_hash}")
+        endif()
+      endif()
+    endif()
+    file(REMOVE "${tmp}")
+  endforeach()
+
+  get_filename_component(dir "${name}" PATH)
+  set(staged "${dir}/.ExternalData_${algo}_${hash}")
+
+  if(found)
+    file(RENAME "${tmp}" "${obj}")
+    message(STATUS "Downloaded object: \"${obj}\"")
+  elseif(EXISTS "${staged}")
+    set(obj "${staged}")
+    message(STATUS "Staged object: \"${obj}\"")
+  else()
+    message(FATAL_ERROR "Object ${algo}=${hash} not found at:${tried}")
+  endif()
+
+  set("${var_obj}" "${obj}" PARENT_SCOPE)
+endfunction()
+
+if("${ExternalData_ACTION}" STREQUAL "fetch")
+  foreach(v ExternalData_OBJECT_STORES file name ext)
+    if(NOT DEFINED "${v}")
+      message(FATAL_ERROR "No \"-D${v}=\" value provided!")
+    endif()
+  endforeach()
+
+  file(READ "${name}${ext}" hash)
+  string(STRIP "${hash}" hash)
+
+  if("${ext}" MATCHES "^\\.(${_ExternalData_REGEX_EXT})$")
+    string(TOUPPER "${CMAKE_MATCH_1}" algo)
+  else()
+    message(FATAL_ERROR "Unknown hash algorithm extension \"${ext}\"")
+  endif()
+
+  _ExternalData_download_object("${name}" "${hash}" "${algo}" obj)
+
+  # Check if file already corresponds to the object.
+  set(stamp "${ext}-stamp")
+  set(file_up_to_date 0)
+  if(EXISTS "${file}" AND EXISTS "${file}${stamp}")
+    file(READ "${file}${stamp}" f_hash)
+    string(STRIP "${f_hash}" f_hash)
+    if("${f_hash}" STREQUAL "${hash}")
+      #message(STATUS "File already corresponds to object")
+      set(file_up_to_date 1)
+    endif()
+  endif()
+
+  if(file_up_to_date)
+    # Touch the file to convince the build system it is up to date.
+    execute_process(COMMAND "${CMAKE_COMMAND}" -E touch "${file}")
+  else()
+    _ExternalData_link_or_copy("${obj}" "${file}")
+  endif()
+
+  # Atomically update the hash/timestamp file to record the object referenced.
+  _ExternalData_atomic_write("${file}${stamp}" "${hash}\n")
+elseif("${ExternalData_ACTION}" STREQUAL "local")
+  foreach(v file name)
+    if(NOT DEFINED "${v}")
+      message(FATAL_ERROR "No \"-D${v}=\" value provided!")
+    endif()
+  endforeach()
+  _ExternalData_link_or_copy("${name}" "${file}")
+else()
+  message(FATAL_ERROR "Unknown ExternalData_ACTION=[${ExternalData_ACTION}]")
+endif()

+ 4 - 0
Modules/ExternalData_config.cmake.in

@@ -0,0 +1,4 @@
+set(ExternalData_OBJECT_STORES "@ExternalData_OBJECT_STORES@")
+set(ExternalData_URL_TEMPLATES "@ExternalData_URL_TEMPLATES@")
+set(ExternalData_TIMEOUT_INACTIVITY "@ExternalData_TIMEOUT_INACTIVITY@")
+set(ExternalData_TIMEOUT_ABSOLUTE "@ExternalData_TIMEOUT_ABSOLUTE@")

+ 13 - 0
Tests/CMakeLists.txt

@@ -312,6 +312,19 @@ if(BUILD_TESTING)
 
   ADD_TEST_MACRO(Module.CheckTypeSize CheckTypeSize)
 
+  add_test(Module.ExternalData ${CMAKE_CTEST_COMMAND}
+    --build-and-test
+    "${CMake_SOURCE_DIR}/Tests/Module/ExternalData"
+    "${CMake_BINARY_DIR}/Tests/Module/ExternalData"
+    --build-generator ${CMAKE_TEST_GENERATOR}
+    --build-project ExternalDataTest
+    --build-makeprogram ${CMAKE_TEST_MAKEPROGRAM}
+    --build-noclean
+    --force-new-ctest-process
+    --test-command ${CMAKE_CTEST_COMMAND} -C \${CTEST_CONFIGURATION_TYPE} -V
+    )
+  list(APPEND TEST_BUILD_DIRS "${CMake_BINARY_DIR}/Tests/Module/ExternalData")
+
   ADD_TEST_MACRO(Module.GenerateExportHeader GenerateExportHeader)
 
   if (APPLE OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")

+ 38 - 0
Tests/Module/ExternalData/CMakeLists.txt

@@ -0,0 +1,38 @@
+cmake_minimum_required(VERSION 2.8.10.20130115)
+project(ExternalDataTest NONE)
+
+include(CTest)
+
+include(ExternalData)
+
+if(NOT "${CMAKE_CURRENT_SOURCE_DIR}" MATCHES "^/")
+  set(slash /)
+endif()
+set(ExternalData_URL_TEMPLATES
+  "file://${slash}${CMAKE_CURRENT_SOURCE_DIR}/%(algo)/%(hash)"
+  )
+set(ExternalData_BINARY_ROOT "${CMAKE_CURRENT_BINARY_DIR}/ExternalData")
+file(REMOVE_RECURSE ${ExternalData_BINARY_ROOT}) # clean test
+
+ExternalData_Add_Test(Data1
+  NAME Data1Check
+  COMMAND ${CMAKE_COMMAND}
+    -D Data=DATA{Data.dat}
+    -D SeriesA=DATA{SeriesA.dat,:}
+    -D SeriesB=DATA{SeriesB.dat,:}
+    -D SeriesC=DATA{SeriesC.dat,:}
+    -D SeriesD=DATA{SeriesD.dat,:}
+    -D SeriesAn=DATA{SeriesAn1.dat,:}
+    -D SeriesBn=DATA{SeriesBn_1.dat,:}
+    -D SeriesCn=DATA{SeriesCn.1.dat,:}
+    -D SeriesDn=DATA{SeriesDn-1.dat,:}
+    -D SeriesMixed=DATA{SeriesMixed.1.dat,:}
+    -D Paired=DATA{PairedA.dat,PairedB.dat}
+    -D Meta=DATA{MetaTop.dat,REGEX:Meta[ABC].dat}
+    -D Directory=DATA{Directory/,A.dat,REGEX:[BC].dat}
+    -P ${CMAKE_CURRENT_SOURCE_DIR}/Data1Check.cmake
+  )
+ExternalData_Add_Target(Data1)
+
+add_subdirectory(Data2)
+add_subdirectory(Data3)

+ 1 - 0
Tests/Module/ExternalData/Data.dat.md5

@@ -0,0 +1 @@
+8c018830e3efa5caf3c7415028335a57

+ 52 - 0
Tests/Module/ExternalData/Data1Check.cmake

@@ -0,0 +1,52 @@
+file(STRINGS "${Data}" lines LIMIT_INPUT 1024)
+if(NOT "x${lines}" STREQUAL "xInput file already transformed.")
+  message(SEND_ERROR "Input file:\n  ${Data}\ndoes not have expected content, but [[${lines}]]")
+endif()
+set(SeriesAn1 "1\\.dat")
+set(SeriesBn1 "_1\\.dat")
+set(SeriesCn1 "\\.1\\.dat")
+set(SeriesDn1 "-1\\.dat")
+set(SeriesAl 1 2 3)
+set(SeriesBl _1 _2 _3)
+set(SeriesCl .1 .2 .3)
+set(SeriesDl -1 -2 -3)
+foreach(s A B C D)
+  foreach(n "" ${Series${s}l})
+    string(REGEX REPLACE "\\.dat$" "${n}.dat" file "${Series${s}}")
+    if(NOT EXISTS "${file}")
+      message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+    endif()
+  endforeach()
+endforeach()
+foreach(s A B C D)
+  foreach(n ${Series${s}l})
+    string(REGEX REPLACE "${Series${s}n1}$" "${n}.dat" file "${Series${s}n}")
+    if(NOT EXISTS "${file}")
+      message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+    endif()
+  endforeach()
+endforeach()
+foreach(n .1 .2 .3 .4)
+  string(REGEX REPLACE "\\.1\\.dat$" "${n}.dat" file "${SeriesMixed}")
+  if(NOT EXISTS "${file}")
+    message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+  endif()
+endforeach()
+foreach(n A B)
+  string(REGEX REPLACE "A\\.dat$" "${n}.dat" file "${Paired}")
+  if(NOT EXISTS "${file}")
+    message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+  endif()
+endforeach()
+foreach(n Top A B C)
+  string(REGEX REPLACE "Top\\.dat$" "${n}.dat" file "${Meta}")
+  if(NOT EXISTS "${file}")
+    message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+  endif()
+endforeach()
+foreach(n A B C)
+  set(file "${Directory}/${n}.dat")
+  if(NOT EXISTS "${file}")
+    message(SEND_ERROR "Input file:\n  ${file}\ndoes not exist!")
+  endif()
+endforeach()

+ 1 - 0
Tests/Module/ExternalData/Data2.dat.md5

@@ -0,0 +1 @@
+8c018830e3efa5caf3c7415028335a57

+ 11 - 0
Tests/Module/ExternalData/Data2/CMakeLists.txt

@@ -0,0 +1,11 @@
+set(ExternalData_SERIES_PARSE "([0-9]+)(_\\.my\\.dat)$")
+set(ExternalData_SERIES_MATCH "([0-9]+)")
+ExternalData_Add_Test(Data2
+  NAME Data2Check
+  COMMAND ${CMAKE_COMMAND}
+    -D Data2=DATA{../Data2.dat}
+    -D Data2b=DATA{${CMAKE_CURRENT_SOURCE_DIR}/../Data2b.dat}
+    -D SeriesC=DATA{SeriesC_1_.my.dat,:}
+    -P ${CMAKE_CURRENT_SOURCE_DIR}/Data2Check.cmake
+  )
+ExternalData_Add_Target(Data2)

+ 12 - 0
Tests/Module/ExternalData/Data2/Data2Check.cmake

@@ -0,0 +1,12 @@
+foreach(d "${Data2}" "${Data2b}")
+  file(STRINGS "${d}" lines LIMIT_INPUT 1024)
+  if(NOT "x${lines}" STREQUAL "xInput file already transformed.")
+    message(SEND_ERROR "Input file:\n  ${d}\ndoes not have expected content, but [[${lines}]]")
+  endif()
+endforeach()
+foreach(n 1 2 3)
+  string(REGEX REPLACE "_1_\\.my\\.dat$" "_${n}_.my.dat" SeriesCFile "${SeriesC}")
+  if(NOT EXISTS "${SeriesCFile}")
+    message(SEND_ERROR "Input file:\n  ${SeriesCFile}\ndoes not exist!")
+  endif()
+endforeach()

+ 1 - 0
Tests/Module/ExternalData/Data2/SeriesC_1_.my.dat.md5

@@ -0,0 +1 @@
+31eff09e84fca01415f8cd9d82ec432b

+ 1 - 0
Tests/Module/ExternalData/Data2/SeriesC_2_.my.dat.md5

@@ -0,0 +1 @@
+f7ab5a04aae9cb9a520e70b20b9c8ed7

+ 1 - 0
Tests/Module/ExternalData/Data2/SeriesC_3_.my.dat.md5

@@ -0,0 +1 @@
+30ba0acdee9096b3b9fc6c69362c6b42

+ 1 - 0
Tests/Module/ExternalData/Data2b.dat.md5

@@ -0,0 +1 @@
+8c018830e3efa5caf3c7415028335a57

+ 14 - 0
Tests/Module/ExternalData/Data3/CMakeLists.txt

@@ -0,0 +1,14 @@
+set(Store0 ${CMAKE_BINARY_DIR}/ExternalData/Other)
+set(Store1 ${CMAKE_BINARY_DIR}/ExternalData/Objects)
+set(ExternalData_OBJECT_STORES ${Store0} ${Store1})
+ExternalData_Add_Test(Data3
+  NAME Data3Check
+  COMMAND ${CMAKE_COMMAND}
+    -D Data=DATA{Data.dat}
+    -D Other=DATA{Other.dat}
+    -D Store0=${Store0}
+    -D Store1=${Store1}
+    -P ${CMAKE_CURRENT_SOURCE_DIR}/Data3Check.cmake
+  )
+ExternalData_Add_Target(Data3)
+add_dependencies(Data3 Data1 Data2)

+ 1 - 0
Tests/Module/ExternalData/Data3/Data.dat.md5

@@ -0,0 +1 @@
+8c018830e3efa5caf3c7415028335a57

+ 25 - 0
Tests/Module/ExternalData/Data3/Data3Check.cmake

@@ -0,0 +1,25 @@
+if(NOT EXISTS "${Data}")
+  message(SEND_ERROR "Input file:\n  ${Data}\ndoes not exist!")
+endif()
+if(NOT EXISTS "${Other}")
+  message(SEND_ERROR "Input file:\n  ${Other}\ndoes not exist!")
+endif()
+# Verify that the 'Data' object was found in the second store location left
+# from Data1 target downloads and that the 'Other' object was downloaded to
+# our first store location.  Neither object should exist in the other store.
+foreach(should_exist
+    "${Store0}/MD5/aaad162b85f60d1eb57ca71a23e8efd7"
+    "${Store1}/MD5/8c018830e3efa5caf3c7415028335a57"
+    )
+  if(NOT EXISTS ${should_exist})
+    message(SEND_ERROR "Store file:\n  ${should_exist}\nshould exist!")
+  endif()
+endforeach()
+foreach(should_not_exist
+    "${Store0}/MD5/8c018830e3efa5caf3c7415028335a57"
+    "${Store1}/MD5/aaad162b85f60d1eb57ca71a23e8efd7"
+    )
+  if(EXISTS ${should_not_exist})
+    message(SEND_ERROR "Store file:\n  ${should_not_exist}\nshould not exist!")
+  endif()
+endforeach()

+ 1 - 0
Tests/Module/ExternalData/Data3/Other.dat.md5

@@ -0,0 +1 @@
+aaad162b85f60d1eb57ca71a23e8efd7

+ 1 - 0
Tests/Module/ExternalData/Directory/A.dat.md5

@@ -0,0 +1 @@
+9d980b06c2f0fec3d4872d68175b9822

+ 1 - 0
Tests/Module/ExternalData/Directory/B.dat.md5

@@ -0,0 +1 @@
+8f4add4581551facf27237e6577fd662

+ 1 - 0
Tests/Module/ExternalData/Directory/C.dat.md5

@@ -0,0 +1 @@
+c1030719c95f3435d8abc39c0d442946

+ 1 - 0
Tests/Module/ExternalData/MD5/.gitattributes

@@ -0,0 +1 @@
+*               -crlf

+ 1 - 0
Tests/Module/ExternalData/MD5/08cfcf221f76ace7b906b312284e73d7

@@ -0,0 +1 @@
+MetaTop

+ 1 - 0
Tests/Module/ExternalData/MD5/30ba0acdee9096b3b9fc6c69362c6b42

@@ -0,0 +1 @@
+Series.3

+ 1 - 0
Tests/Module/ExternalData/MD5/31eff09e84fca01415f8cd9d82ec432b

@@ -0,0 +1 @@
+Series.1

+ 1 - 0
Tests/Module/ExternalData/MD5/401767f22a456b3522953722090a2c36

@@ -0,0 +1 @@
+PairedA

+ 1 - 0
Tests/Module/ExternalData/MD5/8c018830e3efa5caf3c7415028335a57

@@ -0,0 +1 @@
+Input file already transformed.

+ 1 - 0
Tests/Module/ExternalData/MD5/8f4add4581551facf27237e6577fd662

@@ -0,0 +1 @@
+MetaB

+ 1 - 0
Tests/Module/ExternalData/MD5/9d980b06c2f0fec3d4872d68175b9822

@@ -0,0 +1 @@
+MetaA

+ 1 - 0
Tests/Module/ExternalData/MD5/aaad162b85f60d1eb57ca71a23e8efd7

@@ -0,0 +1 @@
+Another input file already transformed.

+ 1 - 0
Tests/Module/ExternalData/MD5/c1030719c95f3435d8abc39c0d442946

@@ -0,0 +1 @@
+MetaC

+ 1 - 0
Tests/Module/ExternalData/MD5/ce38ea6c3c1e00fa6405dd64b8bf6da0

@@ -0,0 +1 @@
+SeriesMixed.1

+ 1 - 0
Tests/Module/ExternalData/MD5/ecfa1ecd417d4253af81ae04d1bd6581

@@ -0,0 +1 @@
+PairedB

+ 1 - 0
Tests/Module/ExternalData/MD5/f41c94425d01ecbbee70440b951cb058

@@ -0,0 +1 @@
+Series

+ 1 - 0
Tests/Module/ExternalData/MD5/f7ab5a04aae9cb9a520e70b20b9c8ed7

@@ -0,0 +1 @@
+Series.2

+ 1 - 0
Tests/Module/ExternalData/MetaA.dat.md5

@@ -0,0 +1 @@
+9d980b06c2f0fec3d4872d68175b9822

+ 1 - 0
Tests/Module/ExternalData/MetaB.dat.md5

@@ -0,0 +1 @@
+8f4add4581551facf27237e6577fd662

+ 1 - 0
Tests/Module/ExternalData/MetaC.dat.md5

@@ -0,0 +1 @@
+c1030719c95f3435d8abc39c0d442946

+ 1 - 0
Tests/Module/ExternalData/MetaTop.dat.md5

@@ -0,0 +1 @@
+08cfcf221f76ace7b906b312284e73d7

+ 1 - 0
Tests/Module/ExternalData/PairedA.dat.md5

@@ -0,0 +1 @@
+401767f22a456b3522953722090a2c36

+ 1 - 0
Tests/Module/ExternalData/PairedB.dat.md5

@@ -0,0 +1 @@
+ecfa1ecd417d4253af81ae04d1bd6581

+ 1 - 0
Tests/Module/ExternalData/SHA1/.gitattributes

@@ -0,0 +1 @@
+*               -crlf

+ 1 - 0
Tests/Module/ExternalData/SHA1/2af59a7022024974f3b8521b7ed8137c996a79f1

@@ -0,0 +1 @@
+SeriesMixed.2

+ 1 - 0
Tests/Module/ExternalData/SHA224/.gitattributes

@@ -0,0 +1 @@
+*               -crlf

+ 1 - 0
Tests/Module/ExternalData/SHA224/3b679da7908562fe1cc28db47ffb89bae025f4551dceb343a5869174

@@ -0,0 +1 @@
+SeriesMixed.3

+ 1 - 0
Tests/Module/ExternalData/SHA256/.gitattributes

@@ -0,0 +1 @@
+*               -crlf

+ 1 - 0
Tests/Module/ExternalData/SHA256/969171a0dd70d49ce096bd3e8178c7e26c711c9b20dbcaa3853d869d3871f133

@@ -0,0 +1 @@
+SeriesMixed.4

+ 1 - 0
Tests/Module/ExternalData/SeriesA.dat.md5

@@ -0,0 +1 @@
+f41c94425d01ecbbee70440b951cb058

+ 1 - 0
Tests/Module/ExternalData/SeriesA1.dat.md5

@@ -0,0 +1 @@
+31eff09e84fca01415f8cd9d82ec432b

+ 1 - 0
Tests/Module/ExternalData/SeriesA2.dat.md5

@@ -0,0 +1 @@
+f7ab5a04aae9cb9a520e70b20b9c8ed7

+ 1 - 0
Tests/Module/ExternalData/SeriesA3.dat.md5

@@ -0,0 +1 @@
+30ba0acdee9096b3b9fc6c69362c6b42

+ 1 - 0
Tests/Module/ExternalData/SeriesAn1.dat.md5

@@ -0,0 +1 @@
+31eff09e84fca01415f8cd9d82ec432b

+ 1 - 0
Tests/Module/ExternalData/SeriesAn2.dat.md5

@@ -0,0 +1 @@
+f7ab5a04aae9cb9a520e70b20b9c8ed7

+ 1 - 0
Tests/Module/ExternalData/SeriesAn3.dat.md5

@@ -0,0 +1 @@
+30ba0acdee9096b3b9fc6c69362c6b42

+ 1 - 0
Tests/Module/ExternalData/SeriesB.dat.md5

@@ -0,0 +1 @@
+f41c94425d01ecbbee70440b951cb058

+ 1 - 0
Tests/Module/ExternalData/SeriesB_1.dat.md5

@@ -0,0 +1 @@
+31eff09e84fca01415f8cd9d82ec432b

+ 1 - 0
Tests/Module/ExternalData/SeriesB_2.dat.md5

@@ -0,0 +1 @@
+f7ab5a04aae9cb9a520e70b20b9c8ed7

+ 1 - 0
Tests/Module/ExternalData/SeriesB_3.dat.md5

@@ -0,0 +1 @@
+30ba0acdee9096b3b9fc6c69362c6b42

+ 1 - 0
Tests/Module/ExternalData/SeriesBn_1.dat.md5

@@ -0,0 +1 @@
+31eff09e84fca01415f8cd9d82ec432b

+ 1 - 0
Tests/Module/ExternalData/SeriesBn_2.dat.md5

@@ -0,0 +1 @@
+f7ab5a04aae9cb9a520e70b20b9c8ed7

+ 1 - 0
Tests/Module/ExternalData/SeriesBn_3.dat.md5

@@ -0,0 +1 @@
+30ba0acdee9096b3b9fc6c69362c6b42

+ 1 - 0
Tests/Module/ExternalData/SeriesC.1.dat.md5

@@ -0,0 +1 @@
+31eff09e84fca01415f8cd9d82ec432b

+ 1 - 0
Tests/Module/ExternalData/SeriesC.2.dat.md5

@@ -0,0 +1 @@
+f7ab5a04aae9cb9a520e70b20b9c8ed7

+ 1 - 0
Tests/Module/ExternalData/SeriesC.3.dat.md5

@@ -0,0 +1 @@
+30ba0acdee9096b3b9fc6c69362c6b42

+ 1 - 0
Tests/Module/ExternalData/SeriesC.dat.md5

@@ -0,0 +1 @@
+f41c94425d01ecbbee70440b951cb058

+ 1 - 0
Tests/Module/ExternalData/SeriesCn.1.dat.md5

@@ -0,0 +1 @@
+31eff09e84fca01415f8cd9d82ec432b

+ 1 - 0
Tests/Module/ExternalData/SeriesCn.2.dat.md5

@@ -0,0 +1 @@
+f7ab5a04aae9cb9a520e70b20b9c8ed7

+ 1 - 0
Tests/Module/ExternalData/SeriesCn.3.dat.md5

@@ -0,0 +1 @@
+30ba0acdee9096b3b9fc6c69362c6b42

+ 1 - 0
Tests/Module/ExternalData/SeriesD-1.dat.md5

@@ -0,0 +1 @@
+31eff09e84fca01415f8cd9d82ec432b

+ 1 - 0
Tests/Module/ExternalData/SeriesD-2.dat.md5

@@ -0,0 +1 @@
+f7ab5a04aae9cb9a520e70b20b9c8ed7

+ 1 - 0
Tests/Module/ExternalData/SeriesD-3.dat.md5

@@ -0,0 +1 @@
+30ba0acdee9096b3b9fc6c69362c6b42

+ 1 - 0
Tests/Module/ExternalData/SeriesD.dat.md5

@@ -0,0 +1 @@
+f41c94425d01ecbbee70440b951cb058

+ 1 - 0
Tests/Module/ExternalData/SeriesDn-1.dat.md5

@@ -0,0 +1 @@
+31eff09e84fca01415f8cd9d82ec432b

+ 1 - 0
Tests/Module/ExternalData/SeriesDn-2.dat.md5

@@ -0,0 +1 @@
+f7ab5a04aae9cb9a520e70b20b9c8ed7

+ 1 - 0
Tests/Module/ExternalData/SeriesDn-3.dat.md5

@@ -0,0 +1 @@
+30ba0acdee9096b3b9fc6c69362c6b42

+ 1 - 0
Tests/Module/ExternalData/SeriesMixed.1.dat.md5

@@ -0,0 +1 @@
+ce38ea6c3c1e00fa6405dd64b8bf6da0

+ 1 - 0
Tests/Module/ExternalData/SeriesMixed.2.dat.sha1

@@ -0,0 +1 @@
+2af59a7022024974f3b8521b7ed8137c996a79f1

+ 1 - 0
Tests/Module/ExternalData/SeriesMixed.3.dat.sha224

@@ -0,0 +1 @@
+3b679da7908562fe1cc28db47ffb89bae025f4551dceb343a5869174

+ 1 - 0
Tests/Module/ExternalData/SeriesMixed.4.dat.sha256

@@ -0,0 +1 @@
+969171a0dd70d49ce096bd3e8178c7e26c711c9b20dbcaa3853d869d3871f133

+ 1 - 0
Tests/RunCMake/CMakeLists.txt

@@ -46,6 +46,7 @@ macro(add_RunCMake_test test)
 endmacro()
 
 add_RunCMake_test(CMP0019)
+add_RunCMake_test(ExternalData)
 add_RunCMake_test(GeneratorExpression)
 add_RunCMake_test(TargetPropertyGeneratorExpressions)
 add_RunCMake_test(Languages)

+ 1 - 0
Tests/RunCMake/ExternalData/BadHashAlgo1-result.txt

@@ -0,0 +1 @@
+1

+ 8 - 0
Tests/RunCMake/ExternalData/BadHashAlgo1-stderr.txt

@@ -0,0 +1,8 @@
+CMake Error at .*/Modules/ExternalData.cmake:[0-9]+ \(message\):
+  Unknown hash algorithm specified by ExternalData_LINK_CONTENT:
+
+    BAD
+Call Stack \(most recent call first\):
+  .*
+  BadHashAlgo1.cmake:3 \(ExternalData_Expand_Arguments\)
+  CMakeLists.txt:3 \(include\)

+ 3 - 0
Tests/RunCMake/ExternalData/BadHashAlgo1.cmake

@@ -0,0 +1,3 @@
+include(ExternalData)
+set(ExternalData_LINK_CONTENT BAD)
+ExternalData_Expand_Arguments(Data args DATA{BadHashAlgo1.txt})

+ 1 - 0
Tests/RunCMake/ExternalData/BadHashAlgo1.txt

@@ -0,0 +1 @@
+Sample input file that should not be transformed.

+ 1 - 0
Tests/RunCMake/ExternalData/BadOption1-result.txt

@@ -0,0 +1 @@
+1

+ 9 - 0
Tests/RunCMake/ExternalData/BadOption1-stderr.txt

@@ -0,0 +1,9 @@
+CMake Error at .*/Modules/ExternalData.cmake:[0-9]+ \(message\):
+  Unknown option "Bad/Option" in argument
+
+    DATA{Data.txt,Bad/Option}
+
+Call Stack \(most recent call first\):
+  .*
+  BadOption1.cmake:2 \(ExternalData_Add_Test\)
+  CMakeLists.txt:3 \(include\)

+ 5 - 0
Tests/RunCMake/ExternalData/BadOption1.cmake

@@ -0,0 +1,5 @@
+include(ExternalData)
+ExternalData_Add_Test(Data
+  NAME Test
+  COMMAND ${CMAKE_COMMAND} -E echo DATA{Data.txt,Bad/Option}
+  )

+ 1 - 0
Tests/RunCMake/ExternalData/BadOption2-result.txt

@@ -0,0 +1 @@
+1

+ 9 - 0
Tests/RunCMake/ExternalData/BadOption2-stderr.txt

@@ -0,0 +1,9 @@
+CMake Error at .*/Modules/ExternalData.cmake:[0-9]+ \(message\):
+  Unknown option "Bad:Option" in argument
+
+    DATA{Data.txt,Bad:Option}
+
+Call Stack \(most recent call first\):
+  .*
+  BadOption2.cmake:2 \(ExternalData_Add_Test\)
+  CMakeLists.txt:3 \(include\)

+ 5 - 0
Tests/RunCMake/ExternalData/BadOption2.cmake

@@ -0,0 +1,5 @@
+include(ExternalData)
+ExternalData_Add_Test(Data
+  NAME Test
+  COMMAND ${CMAKE_COMMAND} -E echo DATA{Data.txt,Bad:Option}
+  )

+ 1 - 0
Tests/RunCMake/ExternalData/BadSeries1-result.txt

@@ -0,0 +1 @@
+1

+ 19 - 0
Tests/RunCMake/ExternalData/BadSeries1-stderr.txt

@@ -0,0 +1,19 @@
+CMake Error at .*/Modules/ExternalData.cmake:[0-9]+ \(message\):
+  ExternalData_SERIES_PARSE is set to
+
+    NotASeriesRegex
+
+  which is not of the form
+
+    \(<number>\)\(<suffix>\)\$
+
+  Fix the regular expression or set variables
+
+    ExternalData_SERIES_PARSE_PREFIX = <prefix> regex group number, if any
+    ExternalData_SERIES_PARSE_NUMBER = <number> regex group number
+    ExternalData_SERIES_PARSE_SUFFIX = <suffix> regex group number
+
+Call Stack \(most recent call first\):
+  .*
+  BadSeries1.cmake:3 \(ExternalData_Expand_Arguments\)
+  CMakeLists.txt:3 \(include\)

+ 3 - 0
Tests/RunCMake/ExternalData/BadSeries1.cmake

@@ -0,0 +1,3 @@
+include(ExternalData)
+set(ExternalData_SERIES_PARSE NotASeriesRegex)
+ExternalData_Expand_Arguments(Data args DATA{Data.txt,:})

+ 1 - 0
Tests/RunCMake/ExternalData/BadSeries2-result.txt

@@ -0,0 +1 @@
+1

+ 16 - 0
Tests/RunCMake/ExternalData/BadSeries2-stderr.txt

@@ -0,0 +1,16 @@
+CMake Error at .*/Modules/ExternalData.cmake:[0-9]+ \(message\):
+  Data file referenced by argument
+
+    DATA{Data.txt,:}
+
+  corresponds to path
+
+    Data.txt
+
+  that does not match regular expression
+
+    \(x\)\(y\)\$
+Call Stack \(most recent call first\):
+  .*
+  BadSeries2.cmake:3 \(ExternalData_Expand_Arguments\)
+  CMakeLists.txt:3 \(include\)

+ 3 - 0
Tests/RunCMake/ExternalData/BadSeries2.cmake

@@ -0,0 +1,3 @@
+include(ExternalData)
+set(ExternalData_SERIES_PARSE "(x)(y)$")
+ExternalData_Expand_Arguments(Data args DATA{Data.txt,:})

+ 1 - 0
Tests/RunCMake/ExternalData/BadSeries3-result.txt

@@ -0,0 +1 @@
+1

+ 6 - 0
Tests/RunCMake/ExternalData/BadSeries3-stderr.txt

@@ -0,0 +1,6 @@
+CMake Error at .*/Modules/ExternalData.cmake:[0-9]+ \(message\):
+  Series option ":" not allowed with associated files.
+Call Stack \(most recent call first\):
+  .*
+  BadSeries3.cmake:2 \(ExternalData_Expand_Arguments\)
+  CMakeLists.txt:3 \(include\)

+ 2 - 0
Tests/RunCMake/ExternalData/BadSeries3.cmake

@@ -0,0 +1,2 @@
+include(ExternalData)
+ExternalData_Expand_Arguments(Data args DATA{PairA.txt,PairB.txt,:})

+ 3 - 0
Tests/RunCMake/ExternalData/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 2.8)
+project(${RunCMake_TEST} NONE)
+include(${RunCMake_TEST}.cmake)

+ 1 - 0
Tests/RunCMake/ExternalData/Data.txt.md5

@@ -0,0 +1 @@
+e8bb14af900b998b5a3df7e21dd07d58

Некоторые файлы не были показаны из-за большого количества измененных файлов