Merge topic 'cuda_add_12.8_new_sm_support'
[kiteware-cmake.git] / Modules / ExternalData.cmake
blob70b547be691820dc28c5a605e78f52b2f4e9a7e8
1 # Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2 # file Copyright.txt or https://cmake.org/licensing for details.
4 #[=======================================================================[.rst:
5 ExternalData
6 ------------
8 .. only:: html
10    .. contents::
12 Manage data files stored outside source tree
14 Introduction
15 ^^^^^^^^^^^^
17 Use this module to unambiguously reference data files stored outside
18 the source tree and fetch them at build time from arbitrary local and
19 remote content-addressed locations.  Functions provided by this module
20 recognize arguments with the syntax ``DATA{<name>}`` as references to
21 external data, replace them with full paths to local copies of those
22 data, and create build rules to fetch and update the local copies.
24 For example:
26 .. code-block:: cmake
28  include(ExternalData)
29  set(ExternalData_URL_TEMPLATES "file:///local/%(algo)/%(hash)"
30                                 "file:////host/share/%(algo)/%(hash)"
31                                 "http://data.org/%(algo)/%(hash)")
32  ExternalData_Add_Test(MyData
33    NAME MyTest
34    COMMAND MyExe DATA{MyInput.png}
35    )
36  ExternalData_Add_Target(MyData)
38 When test ``MyTest`` runs the ``DATA{MyInput.png}`` argument will be
39 replaced by the full path to a real instance of the data file
40 ``MyInput.png`` on disk.  If the source tree contains a content link
41 such as ``MyInput.png.md5`` then the ``MyData`` target creates a real
42 ``MyInput.png`` in the build tree.
44 Module Functions
45 ^^^^^^^^^^^^^^^^
47 .. command:: ExternalData_Expand_Arguments
49   The ``ExternalData_Expand_Arguments`` function evaluates ``DATA{}``
50   references in its arguments and constructs a new list of arguments:
52   .. code-block:: cmake
54     ExternalData_Expand_Arguments(
55       <target>   # Name of data management target
56       <outVar>   # Output variable
57       [args...]  # Input arguments, DATA{} allowed
58       )
60   It replaces each ``DATA{}`` reference in an argument with the full path of
61   a real data file on disk that will exist after the ``<target>`` builds.
63 .. command:: ExternalData_Add_Test
65   The ``ExternalData_Add_Test`` function wraps around the CMake
66   :command:`add_test` command but supports ``DATA{}`` references in
67   its arguments:
69   .. code-block:: cmake
71     ExternalData_Add_Test(
72       <target>   # Name of data management target
73       ...        # Arguments of add_test(), DATA{} allowed
74       )
76   It passes its arguments through ``ExternalData_Expand_Arguments`` and then
77   invokes the :command:`add_test` command using the results.
79   .. versionchanged:: 3.31
80     If the arguments after ``<target>`` define a test with an executable
81     that is a CMake target, empty values in the :prop_tgt:`TEST_LAUNCHER`
82     and :prop_tgt:`CROSSCOMPILING_EMULATOR` properties of that target are
83     preserved.  See policy :policy:`CMP0178`.
85 .. command:: ExternalData_Add_Target
87   The ``ExternalData_Add_Target`` function creates a custom target to
88   manage local instances of data files stored externally:
90   .. code-block:: cmake
92     ExternalData_Add_Target(
93       <target>                  # Name of data management target
94       [SHOW_PROGRESS <ON|OFF>]  # Show progress during the download
95       )
97   It creates custom commands in the target as necessary to make data
98   files available for each ``DATA{}`` reference previously evaluated by
99   other functions provided by this module.
100   Data files may be fetched from one of the URL templates specified in
101   the ``ExternalData_URL_TEMPLATES`` variable, or may be found locally
102   in one of the paths specified in the ``ExternalData_OBJECT_STORES``
103   variable.
105   .. versionadded:: 3.20
106     The ``SHOW_PROGRESS`` argument may be passed to suppress progress information
107     during the download of objects. If not provided, it defaults to ``OFF`` for
108     :generator:`Ninja` and :generator:`Ninja Multi-Config` generators and ``ON``
109     otherwise.
111   Typically only one target is needed to manage all external data within
112   a project.  Call this function once at the end of configuration after
113   all data references have been processed.
115 Module Variables
116 ^^^^^^^^^^^^^^^^
118 The following variables configure behavior.  They should be set before
119 calling any of the functions provided by this module.
121 .. variable:: ExternalData_BINARY_ROOT
123   The ``ExternalData_BINARY_ROOT`` variable may be set to the directory to
124   hold the real data files named by expanded ``DATA{}`` references.  The
125   default is ``CMAKE_BINARY_DIR``.  The directory layout will mirror that of
126   content links under ``ExternalData_SOURCE_ROOT``.
128 .. variable:: ExternalData_CUSTOM_SCRIPT_<key>
130   .. versionadded:: 3.2
132   Specify a full path to a ``.cmake`` custom fetch script identified by
133   ``<key>`` in entries of the ``ExternalData_URL_TEMPLATES`` list.
134   See `Custom Fetch Scripts`_.
136 .. variable:: ExternalData_HTTPHEADERS
138   .. versionadded:: 4.0
140   The ``ExternalData_HTTPHEADERS`` variable may be used to supply a list of
141   headers, each element containing one header with the form ``Key: Value``.
142   See the :command:`file(DOWNLOAD)` command's ``HTTPHEADER`` option.
144 .. variable:: ExternalData_LINK_CONTENT
146   The ``ExternalData_LINK_CONTENT`` variable may be set to the name of a
147   supported hash algorithm to enable automatic conversion of real data
148   files referenced by the ``DATA{}`` syntax into content links.  For each
149   such ``<file>`` a content link named ``<file><ext>`` is created.  The
150   original file is renamed to the form ``.ExternalData_<algo>_<hash>`` to
151   stage it for future transmission to one of the locations in the list
152   of URL templates (by means outside the scope of this module).  The
153   data fetch rule created for the content link will use the staged
154   object if it cannot be found using any URL template.
156 .. variable:: ExternalData_NO_SYMLINKS
158   .. versionadded:: 3.3
160   The real data files named by expanded ``DATA{}`` references may be made
161   available under ``ExternalData_BINARY_ROOT`` using symbolic links on
162   some platforms.  The ``ExternalData_NO_SYMLINKS`` variable may be set
163   to disable use of symbolic links and enable use of copies instead.
165 .. variable:: ExternalData_OBJECT_STORES
167   The ``ExternalData_OBJECT_STORES`` variable may be set to a list of local
168   directories that store objects using the layout ``<dir>/%(algo)/%(hash)``.
169   These directories will be searched first for a needed object.  If the
170   object is not available in any store then it will be fetched remotely
171   using the URL templates and added to the first local store listed.  If
172   no stores are specified the default is a location inside the build
173   tree.
175 .. variable:: ExternalData_SERIES_PARSE
176               ExternalData_SERIES_PARSE_PREFIX
177               ExternalData_SERIES_PARSE_NUMBER
178               ExternalData_SERIES_PARSE_SUFFIX
179               ExternalData_SERIES_MATCH
181   See `Referencing File Series`_.
183 .. variable:: ExternalData_SOURCE_ROOT
185   The ``ExternalData_SOURCE_ROOT`` variable may be set to the highest source
186   directory containing any path named by a ``DATA{}`` reference.  The
187   default is ``CMAKE_SOURCE_DIR``.  ``ExternalData_SOURCE_ROOT`` and
188   ``CMAKE_SOURCE_DIR`` must refer to directories within a single source
189   distribution (e.g.  they come together in one tarball).
191 .. variable:: ExternalData_TIMEOUT_ABSOLUTE
193   The ``ExternalData_TIMEOUT_ABSOLUTE`` variable sets the download
194   absolute timeout, in seconds, with a default of ``300`` seconds.
195   Set to ``0`` to disable enforcement.
197 .. variable:: ExternalData_TIMEOUT_INACTIVITY
199   The ``ExternalData_TIMEOUT_INACTIVITY`` variable sets the download
200   inactivity timeout, in seconds, with a default of ``60`` seconds.
201   Set to ``0`` to disable enforcement.
203 .. variable:: ExternalData_URL_ALGO_<algo>_<key>
205   .. versionadded:: 3.3
207   Specify a custom URL component to be substituted for URL template
208   placeholders of the form ``%(algo:<key>)``, where ``<key>`` is a
209   valid C identifier, when fetching an object referenced via hash
210   algorithm ``<algo>``.  If not defined, the default URL component
211   is just ``<algo>`` for any ``<key>``.
213 .. variable:: ExternalData_URL_TEMPLATES
215   The ``ExternalData_URL_TEMPLATES`` may be set to provide a list
216   of URL templates using the placeholders ``%(algo)`` and ``%(hash)``
217   in each template.  Data fetch rules try each URL template in order
218   by substituting the hash algorithm name for ``%(algo)`` and the hash
219   value for ``%(hash)``.  Alternatively one may use ``%(algo:<key>)``
220   with ``ExternalData_URL_ALGO_<algo>_<key>`` variables to gain more
221   flexibility in remote URLs.
223 Referencing Files
224 ^^^^^^^^^^^^^^^^^
226 Referencing Single Files
227 """"""""""""""""""""""""
229 The ``DATA{}`` syntax is literal and the ``<name>`` is a full or relative path
230 within the source tree.  The source tree must contain either a real
231 data file at ``<name>`` or a "content link" at ``<name><ext>`` containing a
232 hash of the real file using a hash algorithm corresponding to ``<ext>``.
233 For example, the argument ``DATA{img.png}`` may be satisfied by either a
234 real ``img.png`` file in the current source directory or a ``img.png.md5``
235 file containing its MD5 sum.
237 .. versionadded:: 3.8
238   Multiple content links of the same name with different hash algorithms
239   are supported (e.g. ``img.png.sha256`` and ``img.png.sha1``) so long as
240   they all correspond to the same real file.  This allows objects to be
241   fetched from sources indexed by different hash algorithms.
243 Referencing File Series
244 """""""""""""""""""""""
246 The ``DATA{}`` syntax can be told to fetch a file series using the form
247 ``DATA{<name>,:}``, where the ``:`` is literal.  If the source tree
248 contains a group of files or content links named like a series then a
249 reference to one member adds rules to fetch all of them.  Although all
250 members of a series are fetched, only the file originally named by the
251 ``DATA{}`` argument is substituted for it.  The default configuration
252 recognizes file series names ending with ``#.ext``, ``_#.ext``, ``.#.ext``,
253 or ``-#.ext`` where ``#`` is a sequence of decimal digits and ``.ext`` is
254 any single extension.  Configure it with a regex that parses ``<number>``
255 and ``<suffix>`` parts from the end of ``<name>``:
257   ``ExternalData_SERIES_PARSE`` - regex of the form ``(<number>)(<suffix>)$``.
259 For more complicated cases set:
261 * ``ExternalData_SERIES_PARSE`` - regex with at least two ``()`` groups.
262 * ``ExternalData_SERIES_PARSE_PREFIX`` - regex group number of the ``<prefix>``, if any.
263 * ``ExternalData_SERIES_PARSE_NUMBER`` - regex group number of the ``<number>``.
264 * ``ExternalData_SERIES_PARSE_SUFFIX`` - regex group number of the ``<suffix>``.
266 Configure series number matching with a regex that matches the
267 ``<number>`` part of series members named ``<prefix><number><suffix>``:
269   ``ExternalData_SERIES_MATCH`` - regex matching ``<number>`` in all series
270   members
272 Note that the ``<suffix>`` of a series does not include a hash-algorithm
273 extension.
275 Referencing Associated Files
276 """"""""""""""""""""""""""""
278 The ``DATA{}`` syntax can alternatively match files associated with the
279 named file and contained in the same directory.  Associated files may
280 be specified by options using the syntax
281 ``DATA{<name>,<opt1>,<opt2>,...}``.  Each option may specify one file by
282 name or specify a regular expression to match file names using the
283 syntax ``REGEX:<regex>``.  For example, the arguments::
285  DATA{MyData/MyInput.mhd,MyInput.img}                   # File pair
286  DATA{MyData/MyFrames00.png,REGEX:MyFrames[0-9]+\\.png} # Series
288 will pass ``MyInput.mha`` and ``MyFrames00.png`` on the command line but
289 ensure that the associated files are present next to them.
291 Referencing Directories
292 """""""""""""""""""""""
294 The ``DATA{}`` syntax may reference a directory using a trailing slash and
295 a list of associated files.  The form ``DATA{<name>/,<opt1>,<opt2>,...}``
296 adds rules to fetch any files in the directory that match one of the
297 associated file options.  For example, the argument
298 ``DATA{MyDataDir/,REGEX:.*}`` will pass the full path to a ``MyDataDir``
299 directory on the command line and ensure that the directory contains
300 files corresponding to every file or content link in the ``MyDataDir``
301 source directory.
303 .. versionadded:: 3.3
304   In order to match associated files in subdirectories,
305   specify a ``RECURSE:`` option, e.g. ``DATA{MyDataDir/,RECURSE:,REGEX:.*}``.
307 Hash Algorithms
308 ^^^^^^^^^^^^^^^
310 The following hash algorithms are supported:
312  ============ ============= ============
313  %(algo)      <ext>         Description
314  ============ ============= ============
315  ``MD5``      ``.md5``      Message-Digest Algorithm 5, RFC 1321
316  ``SHA1``     ``.sha1``     US Secure Hash Algorithm 1, RFC 3174
317  ``SHA224``   ``.sha224``   US Secure Hash Algorithms, RFC 4634
318  ``SHA256``   ``.sha256``   US Secure Hash Algorithms, RFC 4634
319  ``SHA384``   ``.sha384``   US Secure Hash Algorithms, RFC 4634
320  ``SHA512``   ``.sha512``   US Secure Hash Algorithms, RFC 4634
321  ``SHA3_224`` ``.sha3-224`` Keccak SHA-3
322  ``SHA3_256`` ``.sha3-256`` Keccak SHA-3
323  ``SHA3_384`` ``.sha3-384`` Keccak SHA-3
324  ``SHA3_512`` ``.sha3-512`` Keccak SHA-3
325  ============ ============= ============
327 .. versionadded:: 3.8
328   Added the ``SHA3_*`` hash algorithms.
330 Note that the hashes are used only for unique data identification and
331 download verification.
333 .. _`ExternalData Custom Fetch Scripts`:
335 Custom Fetch Scripts
336 ^^^^^^^^^^^^^^^^^^^^
338 .. versionadded:: 3.2
340 When a data file must be fetched from one of the URL templates
341 specified in the ``ExternalData_URL_TEMPLATES`` variable, it is
342 normally downloaded using the :command:`file(DOWNLOAD)` command.
343 One may specify usage of a custom fetch script by using a URL
344 template of the form ``ExternalDataCustomScript://<key>/<loc>``.
345 The ``<key>`` must be a C identifier, and the ``<loc>`` must
346 contain the ``%(algo)`` and ``%(hash)`` placeholders.
347 A variable corresponding to the key, ``ExternalData_CUSTOM_SCRIPT_<key>``,
348 must be set to the full path to a ``.cmake`` script file.  The script
349 will be included to perform the actual fetch, and provided with
350 the following variables:
352 .. variable:: ExternalData_CUSTOM_LOCATION
354   When a custom fetch script is loaded, this variable is set to the
355   location part of the URL, which will contain the substituted hash
356   algorithm name and content hash value.
358 .. variable:: ExternalData_CUSTOM_FILE
360   When a custom fetch script is loaded, this variable is set to the
361   full path to a file in which the script must store the fetched
362   content.  The name of the file is unspecified and should not be
363   interpreted in any way.
365 The custom fetch script is expected to store fetched content in the
366 file or set a variable:
368 .. variable:: ExternalData_CUSTOM_ERROR
370   When a custom fetch script fails to fetch the requested content,
371   it must set this variable to a short one-line message describing
372   the reason for failure.
374 #]=======================================================================]
376 function(ExternalData_add_test target)
377   # Expand all arguments as a single string to preserve escaped semicolons.
378   ExternalData_expand_arguments("${target}" testArgs "${ARGN}")
380   # We need the caller's CMP0178 policy setting to apply here
381   cmake_policy(GET CMP0178 cmp0178
382     PARENT_SCOPE  # undocumented, do not use outside of CMake
383   )
385   # ExternalData_expand_arguments() escapes semicolons, so we should still be
386   # preserving empty elements from ARGN here. But CMP0178 is still important
387   # for correctly handling TEST_LAUNCHER and CROSSCOMPILING_EMULATOR target
388   # properties that contain empty elements.
389   add_test(${testArgs} __CMP0178 "${cmp0178}")
390 endfunction()
392 function(ExternalData_add_target target)
393   if(NOT ExternalData_URL_TEMPLATES AND NOT ExternalData_OBJECT_STORES)
394     message(FATAL_ERROR
395       "Neither ExternalData_URL_TEMPLATES nor ExternalData_OBJECT_STORES is set!")
396   endif()
397   if(NOT ExternalData_OBJECT_STORES)
398     set(ExternalData_OBJECT_STORES ${CMAKE_BINARY_DIR}/ExternalData/Objects)
399   endif()
400   set(_ExternalData_CONFIG_CODE "")
402   cmake_parse_arguments(PARSE_ARGV 1 _ExternalData_add_target
403     ""
404     "SHOW_PROGRESS"
405     "")
406   if (_ExternalData_add_target_UNPARSED_ARGUMENTS)
407     message(AUTHOR_WARNING
408       "Ignoring unrecognized arguments passed to ExternalData_add_target: "
409       "`${_ExternalData_add_target_UNPARSED_ARGUMENTS}`")
410   endif ()
412   # Turn `SHOW_PROGRESS` into a boolean
413   if (NOT DEFINED _ExternalData_add_target_SHOW_PROGRESS)
414     # The default setting
415     if (CMAKE_GENERATOR MATCHES "Ninja")
416       set(_ExternalData_add_target_SHOW_PROGRESS OFF)
417     else ()
418       set(_ExternalData_add_target_SHOW_PROGRESS ON)
419     endif ()
420   elseif (_ExternalData_add_target_SHOW_PROGRESS)
421     set(_ExternalData_add_target_SHOW_PROGRESS ON)
422   else ()
423     set(_ExternalData_add_target_SHOW_PROGRESS OFF)
424   endif ()
426   # Store custom script configuration.
427   foreach(url_template IN LISTS ExternalData_URL_TEMPLATES)
428     if("${url_template}" MATCHES "^ExternalDataCustomScript://([^/]*)/(.*)$")
429       set(key "${CMAKE_MATCH_1}")
430       if(key MATCHES "^[A-Za-z_][A-Za-z0-9_]*$")
431         if(ExternalData_CUSTOM_SCRIPT_${key})
432           if(IS_ABSOLUTE "${ExternalData_CUSTOM_SCRIPT_${key}}")
433             string(CONCAT _ExternalData_CONFIG_CODE "${_ExternalData_CONFIG_CODE}\n"
434               "set(ExternalData_CUSTOM_SCRIPT_${key} \"${ExternalData_CUSTOM_SCRIPT_${key}}\")")
435           else()
436             message(FATAL_ERROR
437               "No ExternalData_CUSTOM_SCRIPT_${key} is not set to a full path:\n"
438               " ${ExternalData_CUSTOM_SCRIPT_${key}}")
439           endif()
440         else()
441           message(FATAL_ERROR
442             "No ExternalData_CUSTOM_SCRIPT_${key} is set for URL template:\n"
443             " ${url_template}")
444         endif()
445       else()
446         message(FATAL_ERROR
447           "Bad ExternalDataCustomScript key '${key}' in URL template:\n"
448           " ${url_template}\n"
449           "The key must be a valid C identifier.")
450       endif()
451     endif()
453     # Store custom algorithm name to URL component maps.
454     if("${url_template}" MATCHES "%\\(algo:([^)]*)\\)")
455       set(key "${CMAKE_MATCH_1}")
456       if(key MATCHES "^[A-Za-z_][A-Za-z0-9_]*$")
457         string(REPLACE "|" ";" _algos "${_ExternalData_REGEX_ALGO}")
458         foreach(algo ${_algos})
459           if(DEFINED ExternalData_URL_ALGO_${algo}_${key})
460             string(CONCAT _ExternalData_CONFIG_CODE "${_ExternalData_CONFIG_CODE}\n"
461               "set(ExternalData_URL_ALGO_${algo}_${key} \"${ExternalData_URL_ALGO_${algo}_${key}}\")")
462           endif()
463         endforeach()
464       else()
465         message(FATAL_ERROR
466           "Bad %(algo:${key}) in URL template:\n"
467           " ${url_template}\n"
468           "The transform name must be a valid C identifier.")
469       endif()
470     endif()
471   endforeach()
473   # Store http headers.
474   if(ExternalData_HTTPHEADERS)
475     message(STATUS "${CMAKE_CURRENT_BINARY_DIR}/${target}_config.cmake")
476     string(CONCAT _ExternalData_CONFIG_CODE "${_ExternalData_CONFIG_CODE}\n"
477       "set(ExternalData_HTTPHEADERS)")
478     foreach(h IN LISTS ExternalData_HTTPHEADERS)
479       string(REPLACE "\\" "\\\\" tmp "${h}")
480       string(REPLACE "\"" "\\\"" h "${tmp}")
481       string(CONCAT _ExternalData_CONFIG_CODE "${_ExternalData_CONFIG_CODE}\n"
482         "list(APPEND ExternalData_HTTPHEADERS \"${h}\")")
483     endforeach()
484   endif()
486   # Store configuration for use by build-time script.
487   set(config ${CMAKE_CURRENT_BINARY_DIR}/${target}_config.cmake)
488   configure_file(${_ExternalData_SELF_DIR}/ExternalData_config.cmake.in ${config} @ONLY)
490   set(files "")
492   # Set a "_ExternalData_FILE_${file}" variable for each output file to avoid
493   # duplicate entries within this target.  Set a directory property of the same
494   # name to avoid repeating custom commands with the same output in this directory.
495   # Repeating custom commands with the same output across directories or across
496   # targets in the same directory may be a race, but this is likely okay because
497   # we use atomic replacement of output files.
498   #
499   # Use local data first to prefer real files over content links.
501   # Custom commands to copy or link local data.
502   get_property(data_local GLOBAL PROPERTY _ExternalData_${target}_LOCAL)
503   foreach(entry IN LISTS data_local)
504     string(REPLACE "|" ";" tuple "${entry}")
505     list(GET tuple 0 file)
506     list(GET tuple 1 name)
507     if(NOT DEFINED "_ExternalData_FILE_${file}")
508       set("_ExternalData_FILE_${file}" 1)
509       get_property(added DIRECTORY PROPERTY "_ExternalData_FILE_${file}")
510       if(NOT added)
511         set_property(DIRECTORY PROPERTY "_ExternalData_FILE_${file}" 1)
512         add_custom_command(
513           COMMENT "Generating ${file}"
514           OUTPUT "${file}"
515           COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
516                                    -Dfile=${file} -Dname=${name}
517                                    -DExternalData_ACTION=local
518                                    -DExternalData_SHOW_PROGRESS=${_ExternalData_add_target_SHOW_PROGRESS}
519                                    -DExternalData_CONFIG=${config}
520                                    -P ${_ExternalData_SELF}
521           MAIN_DEPENDENCY "${name}"
522           )
523       endif()
524       list(APPEND files "${file}")
525     endif()
526   endforeach()
528   # Custom commands to fetch remote data.
529   get_property(data_fetch GLOBAL PROPERTY _ExternalData_${target}_FETCH)
530   foreach(entry IN LISTS data_fetch)
531     string(REPLACE "|" ";" tuple "${entry}")
532     list(GET tuple 0 file)
533     list(GET tuple 1 name)
534     list(GET tuple 2 exts)
535     string(REPLACE "+" ";" exts_list "${exts}")
536     list(GET exts_list 0 first_ext)
537     set(stamp "-hash-stamp")
538     if(NOT DEFINED "_ExternalData_FILE_${file}")
539       set("_ExternalData_FILE_${file}" 1)
540       get_property(added DIRECTORY PROPERTY "_ExternalData_FILE_${file}")
541       if(NOT added)
542         set_property(DIRECTORY PROPERTY "_ExternalData_FILE_${file}" 1)
543         add_custom_command(
544           # Users care about the data file, so hide the hash/timestamp file.
545           COMMENT "Generating ${file}"
546           # The hash/timestamp file is the output from the build perspective.
547           # List the real file as a second output in case it is a broken link.
548           # The files must be listed in this order so CMake can hide from the
549           # make tool that a symlink target may not be newer than the input.
550           OUTPUT "${file}${stamp}" "${file}"
551           # Run the data fetch/update script.
552           COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
553                                    -Dfile=${file} -Dname=${name} -Dexts=${exts}
554                                    -DExternalData_ACTION=fetch
555                                    -DExternalData_SHOW_PROGRESS=${_ExternalData_add_target_SHOW_PROGRESS}
556                                    -DExternalData_CONFIG=${config}
557                                    -P ${_ExternalData_SELF}
558           # Update whenever the object hash changes.
559           MAIN_DEPENDENCY "${name}${first_ext}"
560           )
561       endif()
562       list(APPEND files "${file}${stamp}")
563     endif()
564   endforeach()
566   # Custom target to drive all update commands.
567   add_custom_target(${target} ALL DEPENDS ${files})
568 endfunction()
570 function(ExternalData_expand_arguments target outArgsVar)
571   # Replace DATA{} references with real arguments.
572   set(data_regex "DATA{([^;{}\r\n]*)}")
573   set(other_regex "([^D]|D[^A]|DA[^T]|DAT[^A]|DATA[^{])+|.")
574   set(outArgs "")
575   # This list expansion un-escapes semicolons in list element values so we
576   # must re-escape them below anywhere a new list expansion will occur.
577   foreach(arg IN LISTS ARGN)
578     if("x${arg}" MATCHES "${data_regex}")
579       # Re-escape in-value semicolons before expansion in foreach below.
580       string(REPLACE ";" "\\;" tmp "${arg}")
581       # Split argument into DATA{}-pieces and other pieces.
582       string(REGEX MATCHALL "${data_regex}|${other_regex}" pieces "${tmp}")
583       # Compose output argument with DATA{}-pieces replaced.
584       set(outArg "")
585       foreach(piece IN LISTS pieces)
586         if("x${piece}" MATCHES "^x${data_regex}$")
587           # Replace this DATA{}-piece with a file path.
588           _ExternalData_arg("${target}" "${piece}" "${CMAKE_MATCH_1}" file)
589           string(APPEND outArg "${file}")
590         else()
591           # No replacement needed for this piece.
592           string(APPEND outArg "${piece}")
593         endif()
594       endforeach()
595     else()
596       # No replacements needed in this argument.
597       set(outArg "${arg}")
598     endif()
599     # Re-escape in-value semicolons in resulting list.
600     string(REPLACE ";" "\\;" outArg "${outArg}")
601     list(APPEND outArgs "${outArg}")
602   endforeach()
603   set("${outArgsVar}" "${outArgs}" PARENT_SCOPE)
604 endfunction()
606 #-----------------------------------------------------------------------------
607 # Private helper interface
609 set(_ExternalData_REGEX_ALGO "MD5|SHA1|SHA224|SHA256|SHA384|SHA512|SHA3_224|SHA3_256|SHA3_384|SHA3_512")
610 set(_ExternalData_REGEX_EXT "md5|sha1|sha224|sha256|sha384|sha512|sha3-224|sha3-256|sha3-384|sha3-512")
611 set(_ExternalData_SELF "${CMAKE_CURRENT_LIST_FILE}")
612 get_filename_component(_ExternalData_SELF_DIR "${_ExternalData_SELF}" PATH)
614 function(_ExternalData_compute_hash var_hash algo file)
615   if("${algo}" MATCHES "^${_ExternalData_REGEX_ALGO}$")
616     file("${algo}" "${file}" hash)
617     set("${var_hash}" "${hash}" PARENT_SCOPE)
618   else()
619     message(FATAL_ERROR "Hash algorithm ${algo} unimplemented.")
620   endif()
621 endfunction()
623 function(_ExternalData_random var)
624   string(RANDOM LENGTH 6 random)
625   set("${var}" "${random}" PARENT_SCOPE)
626 endfunction()
628 function(_ExternalData_exact_regex regex_var string)
629   string(REGEX REPLACE "([][+.*()^])" "\\\\\\1" regex "${string}")
630   set("${regex_var}" "${regex}" PARENT_SCOPE)
631 endfunction()
633 function(_ExternalData_atomic_write file content)
634   _ExternalData_random(random)
635   set(tmp "${file}.tmp${random}")
636   file(WRITE "${tmp}" "${content}")
637   file(RENAME "${tmp}" "${file}")
638 endfunction()
640 function(_ExternalData_link_content name var_ext)
641   if("${ExternalData_LINK_CONTENT}" MATCHES "^(${_ExternalData_REGEX_ALGO})$")
642     set(algo "${ExternalData_LINK_CONTENT}")
643   else()
644     message(FATAL_ERROR
645       "Unknown hash algorithm specified by ExternalData_LINK_CONTENT:\n"
646       "  ${ExternalData_LINK_CONTENT}")
647   endif()
648   _ExternalData_compute_hash(hash "${algo}" "${name}")
649   get_filename_component(dir "${name}" PATH)
650   set(staged "${dir}/.ExternalData_${algo}_${hash}")
651   string(TOLOWER ".${algo}" ext)
652   _ExternalData_atomic_write("${name}${ext}" "${hash}\n")
653   file(RENAME "${name}" "${staged}")
654   set("${var_ext}" "${ext}" PARENT_SCOPE)
656   file(RELATIVE_PATH relname "${ExternalData_SOURCE_ROOT}" "${name}${ext}")
657   message(STATUS "Linked ${relname} to ExternalData ${algo}/${hash}")
658 endfunction()
660 function(_ExternalData_arg target arg options var_file)
661   # Separate data path from the options.
662   string(REPLACE "," ";" options "${options}")
663   list(GET options 0 data)
664   list(REMOVE_AT options 0)
666   # Interpret trailing slashes as directories.
667   set(data_is_directory 0)
668   if("x${data}" MATCHES "^x(.*)([/\\])$")
669     set(data_is_directory 1)
670     set(data "${CMAKE_MATCH_1}")
671   endif()
673   # Convert to full path.
674   if(IS_ABSOLUTE "${data}")
675     set(absdata "${data}")
676   else()
677     set(absdata "${CMAKE_CURRENT_SOURCE_DIR}/${data}")
678   endif()
679   get_filename_component(absdata "${absdata}" ABSOLUTE)
681   # Convert to relative path under the source tree.
682   if(NOT ExternalData_SOURCE_ROOT)
683     set(ExternalData_SOURCE_ROOT "${CMAKE_SOURCE_DIR}")
684   endif()
685   set(top_src "${ExternalData_SOURCE_ROOT}")
686   file(RELATIVE_PATH reldata "${top_src}" "${absdata}")
687   if(IS_ABSOLUTE "${reldata}" OR "${reldata}" MATCHES "^\\.\\./")
688     message(FATAL_ERROR "Data file referenced by argument\n"
689       "  ${arg}\n"
690       "does not lie under the top-level source directory\n"
691       "  ${top_src}\n")
692   endif()
693   if(data_is_directory AND NOT IS_DIRECTORY "${top_src}/${reldata}")
694     message(FATAL_ERROR "Data directory referenced by argument\n"
695       "  ${arg}\n"
696       "corresponds to source tree path\n"
697       "  ${reldata}\n"
698       "that does not exist as a directory!")
699   endif()
700   if(NOT ExternalData_BINARY_ROOT)
701     set(ExternalData_BINARY_ROOT "${CMAKE_BINARY_DIR}")
702   endif()
703   set(top_bin "${ExternalData_BINARY_ROOT}")
705   # Handle in-source builds gracefully.
706   if("${top_src}" STREQUAL "${top_bin}")
707     if(ExternalData_LINK_CONTENT)
708       message(WARNING "ExternalData_LINK_CONTENT cannot be used in-source")
709       set(ExternalData_LINK_CONTENT 0)
710     endif()
711     set(top_same 1)
712   endif()
714   set(external "") # Entries external to the source tree.
715   set(internal "") # Entries internal to the source tree.
716   set(have_original ${data_is_directory})
717   set(have_original_as_dir 0)
719   # Process options.
720   set(series_option "")
721   set(recurse_option "")
722   set(associated_files "")
723   set(associated_regex "")
724   foreach(opt ${options})
725     # Regular expression to match associated files.
726     if("x${opt}" MATCHES "^xREGEX:([^:/]+)$")
727       list(APPEND associated_regex "${CMAKE_MATCH_1}")
728     elseif(opt STREQUAL ":")
729       # Activate series matching.
730       set(series_option "${opt}")
731     elseif(opt STREQUAL "RECURSE:")
732       # Activate recursive matching in directories.
733       set(recurse_option "${opt}")
734     elseif("x${opt}" MATCHES "^[^][:/*?]+$")
735       # Specific associated file.
736       list(APPEND associated_files "${opt}")
737     else()
738       message(FATAL_ERROR "Unknown option \"${opt}\" in argument\n"
739         "  ${arg}\n")
740     endif()
741   endforeach()
743   if(series_option)
744     if(data_is_directory)
745       message(FATAL_ERROR "Series option \"${series_option}\" not allowed with directories.")
746     endif()
747     if(associated_files OR associated_regex)
748       message(FATAL_ERROR "Series option \"${series_option}\" not allowed with associated files.")
749     endif()
750     if(recurse_option)
751       message(FATAL_ERROR "Recurse option \"${recurse_option}\" allowed only with directories.")
752     endif()
753     # Load a whole file series.
754     _ExternalData_arg_series()
755   elseif(data_is_directory)
756     if(associated_files OR associated_regex)
757       # Load listed/matching associated files in the directory.
758       _ExternalData_arg_associated()
759     else()
760       message(FATAL_ERROR "Data directory referenced by argument\n"
761         "  ${arg}\n"
762         "must list associated files.")
763     endif()
764   else()
765     if(recurse_option)
766       message(FATAL_ERROR "Recurse option \"${recurse_option}\" allowed only with directories.")
767     endif()
768     # Load the named data file.
769     _ExternalData_arg_single()
770     if(associated_files OR associated_regex)
771       # Load listed/matching associated files.
772       _ExternalData_arg_associated()
773     endif()
774   endif()
776   if(NOT have_original)
777     if(have_original_as_dir)
778       set(msg_kind FATAL_ERROR)
779       set(msg "that is directory instead of a file!")
780     else()
781       set(msg_kind AUTHOR_WARNING)
782       set(msg "that does not exist as a file (with or without an extension)!")
783     endif()
784     message(${msg_kind} "Data file referenced by argument\n"
785       "  ${arg}\n"
786       "corresponds to source tree path\n"
787       "  ${reldata}\n"
788       "${msg}")
789   endif()
791   if(external)
792     # Make the series available in the build tree.
793     set_property(GLOBAL APPEND PROPERTY
794       _ExternalData_${target}_FETCH "${external}")
795     set_property(GLOBAL APPEND PROPERTY
796       _ExternalData_${target}_LOCAL "${internal}")
797     set("${var_file}" "${top_bin}/${reldata}" PARENT_SCOPE)
798   else()
799     # The whole series is in the source tree.
800     set("${var_file}" "${top_src}/${reldata}" PARENT_SCOPE)
801   endif()
802 endfunction()
804 macro(_ExternalData_arg_associated)
805   # Associated files lie in the same directory.
806   if(data_is_directory)
807     set(reldir "${reldata}")
808   else()
809     get_filename_component(reldir "${reldata}" PATH)
810   endif()
811   if(reldir)
812     string(APPEND reldir "/")
813   endif()
814   _ExternalData_exact_regex(reldir_regex "${reldir}")
815   if(recurse_option)
816     set(glob GLOB_RECURSE)
817     string(APPEND reldir_regex "(.+/)?")
818   else()
819     set(glob GLOB)
820   endif()
822   # Find files named explicitly.
823   foreach(file ${associated_files})
824     _ExternalData_exact_regex(file_regex "${file}")
825     _ExternalData_arg_find_files(${glob} "${reldir}${file}"
826       "${reldir_regex}${file_regex}")
827   endforeach()
829   # Find files matching the given regular expressions.
830   set(all "")
831   set(sep "")
832   foreach(regex ${associated_regex})
833     string(APPEND all "${sep}${reldir_regex}${regex}")
834     set(sep "|")
835   endforeach()
836   _ExternalData_arg_find_files(${glob} "${reldir}" "${all}")
837 endmacro()
839 macro(_ExternalData_arg_single)
840   # Match only the named data by itself.
841   _ExternalData_exact_regex(data_regex "${reldata}")
842   _ExternalData_arg_find_files(GLOB "${reldata}" "${data_regex}")
843 endmacro()
845 macro(_ExternalData_arg_series)
846   # Configure series parsing and matching.
847   set(series_parse_prefix "")
848   set(series_parse_number "\\1")
849   set(series_parse_suffix "\\2")
850   if(ExternalData_SERIES_PARSE)
851     if(ExternalData_SERIES_PARSE_NUMBER AND ExternalData_SERIES_PARSE_SUFFIX)
852       if(ExternalData_SERIES_PARSE_PREFIX)
853         set(series_parse_prefix "\\${ExternalData_SERIES_PARSE_PREFIX}")
854       endif()
855       set(series_parse_number "\\${ExternalData_SERIES_PARSE_NUMBER}")
856       set(series_parse_suffix "\\${ExternalData_SERIES_PARSE_SUFFIX}")
857     elseif(NOT "x${ExternalData_SERIES_PARSE}" MATCHES "^x\\([^()]*\\)\\([^()]*\\)\\$$")
858       message(FATAL_ERROR
859         "ExternalData_SERIES_PARSE is set to\n"
860         "  ${ExternalData_SERIES_PARSE}\n"
861         "which is not of the form\n"
862         "  (<number>)(<suffix>)$\n"
863         "Fix the regular expression or set variables\n"
864         "  ExternalData_SERIES_PARSE_PREFIX = <prefix> regex group number, if any\n"
865         "  ExternalData_SERIES_PARSE_NUMBER = <number> regex group number\n"
866         "  ExternalData_SERIES_PARSE_SUFFIX = <suffix> regex group number\n"
867         )
868     endif()
869     set(series_parse "${ExternalData_SERIES_PARSE}")
870   else()
871     set(series_parse "([0-9]*)(\\.[^./]*)$")
872   endif()
873   if(ExternalData_SERIES_MATCH)
874     set(series_match "${ExternalData_SERIES_MATCH}")
875   else()
876     set(series_match "[_.-]?[0-9]*")
877   endif()
879   # Parse the base, number, and extension components of the series.
880   string(REGEX REPLACE "${series_parse}" "${series_parse_prefix};${series_parse_number};${series_parse_suffix}" tuple "${reldata}")
881   list(LENGTH tuple len)
882   if(NOT "${len}" EQUAL 3)
883     message(FATAL_ERROR "Data file referenced by argument\n"
884       "  ${arg}\n"
885       "corresponds to path\n"
886       "  ${reldata}\n"
887       "that does not match regular expression\n"
888       "  ${series_parse}")
889   endif()
890   list(GET tuple 0 relbase)
891   list(GET tuple 2 ext)
893   # Glob files that might match the series.
894   # Then match base, number, and extension.
895   _ExternalData_exact_regex(series_base "${relbase}")
896   _ExternalData_exact_regex(series_ext "${ext}")
897   _ExternalData_arg_find_files(GLOB "${relbase}*${ext}"
898     "${series_base}${series_match}${series_ext}")
899 endmacro()
901 function(_ExternalData_arg_find_files glob pattern regex)
902   file(${glob} globbed RELATIVE "${top_src}" "${top_src}/${pattern}*")
903   set(externals_count -1)
904   foreach(entry IN LISTS globbed)
905     if("x${entry}" MATCHES "^x(.*)(\\.(${_ExternalData_REGEX_EXT}))$")
906       set(relname "${CMAKE_MATCH_1}")
907       set(alg "${CMAKE_MATCH_2}")
908     else()
909       set(relname "${entry}")
910       set(alg "")
911     endif()
912     if("x${relname}" MATCHES "^x${regex}$" # matches
913         AND NOT "x${relname}" MATCHES "(^x|/)\\.ExternalData_" # not staged obj
914         )
915       if(IS_DIRECTORY "${top_src}/${entry}")
916         if("${relname}" STREQUAL "${reldata}")
917           set(have_original_as_dir 1)
918         endif()
919       else()
920         set(name "${top_src}/${relname}")
921         set(file "${top_bin}/${relname}")
922         if(alg)
923           if(NOT "${external_${externals_count}_file_name}" STREQUAL "${file}|${name}")
924             math(EXPR externals_count "${externals_count} + 1")
925             set(external_${externals_count}_file_name "${file}|${name}")
926           endif()
927           list(APPEND external_${externals_count}_algs "${alg}")
928         elseif(ExternalData_LINK_CONTENT)
929           _ExternalData_link_content("${name}" alg)
930           list(APPEND external "${file}|${name}|${alg}")
931         elseif(NOT top_same)
932           list(APPEND internal "${file}|${name}")
933         endif()
934         if("${relname}" STREQUAL "${reldata}")
935           set(have_original 1)
936         endif()
937       endif()
938     endif()
939   endforeach()
940   if(${externals_count} GREATER -1)
941     foreach(ii RANGE ${externals_count})
942       string(REPLACE ";" "+" algs_delim "${external_${ii}_algs}")
943       list(APPEND external "${external_${ii}_file_name}|${algs_delim}")
944       unset(external_${ii}_algs)
945       unset(external_${ii}_file_name)
946     endforeach()
947   endif()
948   set(external "${external}" PARENT_SCOPE)
949   set(internal "${internal}" PARENT_SCOPE)
950   set(have_original "${have_original}" PARENT_SCOPE)
951   set(have_original_as_dir "${have_original_as_dir}" PARENT_SCOPE)
952 endfunction()
954 #-----------------------------------------------------------------------------
955 # Private script mode interface
957 if(CMAKE_GENERATOR OR NOT ExternalData_ACTION)
958   return()
959 endif()
961 if(ExternalData_CONFIG)
962   include(${ExternalData_CONFIG})
963 endif()
964 if(NOT ExternalData_URL_TEMPLATES AND NOT ExternalData_OBJECT_STORES)
965   message(FATAL_ERROR
966     "Neither ExternalData_URL_TEMPLATES nor ExternalData_OBJECT_STORES is set!")
967 endif()
969 function(_ExternalData_link_or_copy src dst)
970   # Create a temporary file first.
971   get_filename_component(dst_dir "${dst}" PATH)
972   file(MAKE_DIRECTORY "${dst_dir}")
973   _ExternalData_random(random)
974   set(tmp "${dst}.tmp${random}")
975   if(UNIX AND NOT ExternalData_NO_SYMLINKS)
976     # Create a symbolic link.
977     set(tgt "${src}")
978     if(relative_top)
979       # Use relative path if files are close enough.
980       file(RELATIVE_PATH relsrc "${relative_top}" "${src}")
981       file(RELATIVE_PATH relfile "${relative_top}" "${dst}")
982       if(NOT IS_ABSOLUTE "${relsrc}" AND NOT "${relsrc}" MATCHES "^\\.\\./" AND
983           NOT IS_ABSOLUTE "${reldst}" AND NOT "${reldst}" MATCHES "^\\.\\./")
984         file(RELATIVE_PATH tgt "${dst_dir}" "${src}")
985       endif()
986     endif()
987     # Create link (falling back to copying if there's a problem).
988     file(CREATE_LINK "${tgt}" "${tmp}" RESULT result COPY_ON_ERROR SYMBOLIC)
989   else()
990     # Create a copy.
991     file(COPY_FILE "${src}" "${tmp}" RESULT result INPUT_MAY_BE_RECENT)
992   endif()
993   if(result)
994     file(REMOVE "${tmp}")
995     message(FATAL_ERROR "Failed to create:\n  \"${tmp}\"\nfrom:\n  \"${obj}\"\nwith error:\n  ${result}")
996   endif()
998   # Atomically create/replace the real destination.
999   file(RENAME "${tmp}" "${dst}")
1000 endfunction()
1002 function(_ExternalData_download_file url file err_var msg_var)
1003   set(retry 3)
1004   while(retry)
1005     math(EXPR retry "${retry} - 1")
1006     set(httpheader_args)
1007     if (ExternalData_HTTPHEADERS)
1008       foreach(h IN LISTS ExternalData_HTTPHEADERS)
1009         list(APPEND httpheader_args HTTPHEADER "${h}")
1010       endforeach()
1011     endif()
1012     if(ExternalData_TIMEOUT_INACTIVITY)
1013       set(inactivity_timeout INACTIVITY_TIMEOUT ${ExternalData_TIMEOUT_INACTIVITY})
1014     elseif(NOT "${ExternalData_TIMEOUT_INACTIVITY}" EQUAL 0)
1015       set(inactivity_timeout INACTIVITY_TIMEOUT 60)
1016     else()
1017       set(inactivity_timeout "")
1018     endif()
1019     if(ExternalData_TIMEOUT_ABSOLUTE)
1020       set(absolute_timeout TIMEOUT ${ExternalData_TIMEOUT_ABSOLUTE})
1021     elseif(NOT "${ExternalData_TIMEOUT_ABSOLUTE}" EQUAL 0)
1022       set(absolute_timeout TIMEOUT 300)
1023     else()
1024       set(absolute_timeout "")
1025     endif()
1026     set(show_progress_args)
1027     if (ExternalData_SHOW_PROGRESS)
1028       list(APPEND show_progress_args SHOW_PROGRESS)
1029     endif ()
1030     file(DOWNLOAD "${url}" "${file}" STATUS status LOG log ${httpheader_args} ${inactivity_timeout} ${absolute_timeout} ${show_progress_args})
1031     list(GET status 0 err)
1032     list(GET status 1 msg)
1033     if(err)
1034       if("${msg}" MATCHES "HTTP response code said error" AND
1035           "${log}" MATCHES "error: 503")
1036         set(msg "temporarily unavailable")
1037       endif()
1038     elseif("${log}" MATCHES "\nHTTP[^\n]* 503")
1039       set(err TRUE)
1040       set(msg "temporarily unavailable")
1041     endif()
1042     if(NOT err OR NOT "${msg}" MATCHES "partial|timeout|temporarily")
1043       break()
1044     elseif(retry)
1045       message(STATUS "[download terminated: ${msg}, retries left: ${retry}]")
1046     endif()
1047   endwhile()
1048   set("${err_var}" "${err}" PARENT_SCOPE)
1049   set("${msg_var}" "${msg}" PARENT_SCOPE)
1050 endfunction()
1052 function(_ExternalData_custom_fetch key loc file err_var msg_var)
1053   if(NOT ExternalData_CUSTOM_SCRIPT_${key})
1054     set(err 1)
1055     set(msg "No ExternalData_CUSTOM_SCRIPT_${key} set!")
1056   elseif(NOT EXISTS "${ExternalData_CUSTOM_SCRIPT_${key}}")
1057     set(err 1)
1058     set(msg "No '${ExternalData_CUSTOM_SCRIPT_${key}}' exists!")
1059   else()
1060     set(ExternalData_CUSTOM_LOCATION "${loc}")
1061     set(ExternalData_CUSTOM_FILE "${file}")
1062     unset(ExternalData_CUSTOM_ERROR)
1063     include("${ExternalData_CUSTOM_SCRIPT_${key}}")
1064     if(DEFINED ExternalData_CUSTOM_ERROR)
1065       set(err 1)
1066       set(msg "${ExternalData_CUSTOM_ERROR}")
1067     else()
1068       set(err 0)
1069       set(msg "no error")
1070     endif()
1071   endif()
1072   set("${err_var}" "${err}" PARENT_SCOPE)
1073   set("${msg_var}" "${msg}" PARENT_SCOPE)
1074 endfunction()
1076 function(_ExternalData_get_from_object_store hash algo var_obj var_success)
1077   # Search all object stores for an existing object.
1078   foreach(dir ${ExternalData_OBJECT_STORES})
1079     set(obj "${dir}/${algo}/${hash}")
1080     if(EXISTS "${obj}")
1081       message(STATUS "Found object: \"${obj}\"")
1082       set("${var_obj}" "${obj}" PARENT_SCOPE)
1083       set("${var_success}" 1 PARENT_SCOPE)
1084       return()
1085     endif()
1086   endforeach()
1087 endfunction()
1089 function(_ExternalData_download_object name hash algo var_obj var_success var_errorMsg)
1090   # Search all object stores for an existing object.
1091   set(success 1)
1092   foreach(dir ${ExternalData_OBJECT_STORES})
1093     set(obj "${dir}/${algo}/${hash}")
1094     if(EXISTS "${obj}")
1095       message(STATUS "Found object: \"${obj}\"")
1096       set("${var_obj}" "${obj}" PARENT_SCOPE)
1097       set("${var_success}" "${success}" PARENT_SCOPE)
1098       return()
1099     endif()
1100   endforeach()
1102   # Download object to the first store.
1103   list(GET ExternalData_OBJECT_STORES 0 store)
1104   set(obj "${store}/${algo}/${hash}")
1106   _ExternalData_random(random)
1107   set(tmp "${obj}.tmp${random}")
1108   set(found 0)
1109   set(tried "")
1110   foreach(url_template IN LISTS ExternalData_URL_TEMPLATES)
1111     string(REPLACE "%(hash)" "${hash}" url_tmp "${url_template}")
1112     string(REPLACE "%(algo)" "${algo}" url "${url_tmp}")
1113     if(url MATCHES "^(.*)%\\(algo:([A-Za-z_][A-Za-z0-9_]*)\\)(.*)$")
1114       set(lhs "${CMAKE_MATCH_1}")
1115       set(key "${CMAKE_MATCH_2}")
1116       set(rhs "${CMAKE_MATCH_3}")
1117       if(DEFINED ExternalData_URL_ALGO_${algo}_${key})
1118         set(url "${lhs}${ExternalData_URL_ALGO_${algo}_${key}}${rhs}")
1119       else()
1120         set(url "${lhs}${algo}${rhs}")
1121       endif()
1122     endif()
1123     string(REGEX REPLACE "((https?|ftp)://)([^@]+@)?(.*)" "\\1\\4" secured_url "${url}")
1124     message(STATUS "Fetching \"${secured_url}\"")
1125     if(url MATCHES "^ExternalDataCustomScript://([A-Za-z_][A-Za-z0-9_]*)/(.*)$")
1126       _ExternalData_custom_fetch("${CMAKE_MATCH_1}" "${CMAKE_MATCH_2}" "${tmp}" err errMsg)
1127     else()
1128       _ExternalData_download_file("${url}" "${tmp}" err errMsg)
1129     endif()
1130     string(APPEND tried "\n  ${url}")
1131     if(err)
1132       string(APPEND tried " (${errMsg})")
1133     else()
1134       # Verify downloaded object.
1135       _ExternalData_compute_hash(dl_hash "${algo}" "${tmp}")
1136       if("${dl_hash}" STREQUAL "${hash}")
1137         set(found 1)
1138         break()
1139       else()
1140         string(APPEND tried " (wrong hash ${algo}=${dl_hash})")
1141         if("$ENV{ExternalData_DEBUG_DOWNLOAD}" MATCHES ".")
1142           file(RENAME "${tmp}" "${store}/${algo}/${dl_hash}")
1143         endif()
1144       endif()
1145     endif()
1146     file(REMOVE "${tmp}")
1147   endforeach()
1149   get_filename_component(dir "${name}" PATH)
1150   set(staged "${dir}/.ExternalData_${algo}_${hash}")
1152   set(success 1)
1153   if(found)
1154     # Atomically create the object.  If we lose a race with another process,
1155     # do not replace it.  Content-addressing ensures it has what we expect.
1156     file(RENAME "${tmp}" "${obj}" NO_REPLACE RESULT result)
1157     if (result STREQUAL "NO_REPLACE")
1158       file(REMOVE "${tmp}")
1159     elseif (result)
1160       message(FATAL_ERROR "Failed to rename:\n  \"${tmp}\"\nto:\n  \"${obj}\"\nwith error:\n  ${result}")
1161     endif()
1162     message(STATUS "Downloaded object: \"${obj}\"")
1163   elseif(EXISTS "${staged}")
1164     set(obj "${staged}")
1165     message(STATUS "Staged object: \"${obj}\"")
1166   else()
1167     if(NOT tried)
1168       set(tried "\n  (No ExternalData_URL_TEMPLATES given)")
1169     endif()
1170     set(success 0)
1171     set("${var_errorMsg}" "Object ${algo}=${hash} not found at:${tried}" PARENT_SCOPE)
1172   endif()
1174   set("${var_obj}" "${obj}" PARENT_SCOPE)
1175   set("${var_success}" "${success}" PARENT_SCOPE)
1176 endfunction()
1178 if("${ExternalData_ACTION}" STREQUAL "fetch")
1179   foreach(v ExternalData_OBJECT_STORES file name exts)
1180     if(NOT DEFINED "${v}")
1181       message(FATAL_ERROR "No \"-D${v}=\" value provided!")
1182     endif()
1183   endforeach()
1185   string(REPLACE "+" ";" exts_list "${exts}")
1186   set(succeeded 0)
1187   set(errorMsg "")
1188   set(hash_list )
1189   set(algo_list )
1190   set(hash )
1191   set(algo )
1192   foreach(ext ${exts_list})
1193     file(READ "${name}${ext}" hash)
1194     string(STRIP "${hash}" hash)
1196     if("${ext}" MATCHES "^\\.(${_ExternalData_REGEX_EXT})$")
1197       string(TOUPPER "${CMAKE_MATCH_1}" algo)
1198       string(REPLACE "-" "_" algo "${algo}")
1199     else()
1200       message(FATAL_ERROR "Unknown hash algorithm extension \"${ext}\"")
1201     endif()
1203     list(APPEND hash_list ${hash})
1204     list(APPEND algo_list ${algo})
1205   endforeach()
1207   list(LENGTH exts_list num_extensions)
1208   math(EXPR exts_range "${num_extensions} - 1")
1209   foreach(ii RANGE 0 ${exts_range})
1210     list(GET hash_list ${ii} hash)
1211     list(GET algo_list ${ii} algo)
1212     _ExternalData_get_from_object_store("${hash}" "${algo}" obj succeeded)
1213     if(succeeded)
1214       break()
1215     endif()
1216   endforeach()
1217   if(NOT succeeded)
1218     foreach(ii RANGE 0 ${exts_range})
1219       list(GET hash_list ${ii} hash)
1220       list(GET algo_list ${ii} algo)
1221       _ExternalData_download_object("${name}" "${hash}" "${algo}"
1222         obj succeeded algoErrorMsg)
1223       string(APPEND errorMsg "\n${algoErrorMsg}")
1224       if(succeeded)
1225         break()
1226       endif()
1227     endforeach()
1228   endif()
1229   if(NOT succeeded)
1230     message(FATAL_ERROR "${errorMsg}")
1231   endif()
1232   # Check if file already corresponds to the object.
1233   set(stamp "-hash-stamp")
1234   set(file_up_to_date 0)
1235   if(EXISTS "${file}" AND EXISTS "${file}${stamp}")
1236     file(READ "${file}${stamp}" f_hash)
1237     string(STRIP "${f_hash}" f_hash)
1238     if("${f_hash}" STREQUAL "${hash}")
1239       set(file_up_to_date 1)
1240     endif()
1241   endif()
1243   if(file_up_to_date)
1244     # Touch the file to convince the build system it is up to date.
1245     file(TOUCH "${file}")
1246   else()
1247     _ExternalData_link_or_copy("${obj}" "${file}")
1248   endif()
1250   # Atomically update the hash/timestamp file to record the object referenced.
1251   _ExternalData_atomic_write("${file}${stamp}" "${hash}\n")
1252 elseif("${ExternalData_ACTION}" STREQUAL "local")
1253   foreach(v file name)
1254     if(NOT DEFINED "${v}")
1255       message(FATAL_ERROR "No \"-D${v}=\" value provided!")
1256     endif()
1257   endforeach()
1258   _ExternalData_link_or_copy("${name}" "${file}")
1259 else()
1260   message(FATAL_ERROR "Unknown ExternalData_ACTION=[${ExternalData_ACTION}]")
1261 endif()