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:
12 Manage data files stored outside source tree
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.
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
34 COMMAND MyExe DATA{MyInput.png}
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.
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:
54 ExternalData_Expand_Arguments(
55 <target> # Name of data management target
56 <outVar> # Output variable
57 [args...] # Input arguments, DATA{} allowed
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
71 ExternalData_Add_Test(
72 <target> # Name of data management target
73 ... # Arguments of add_test(), DATA{} allowed
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:
92 ExternalData_Add_Target(
93 <target> # Name of data management target
94 [SHOW_PROGRESS <ON|OFF>] # Show progress during the download
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``
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``
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.
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
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.
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
272 Note that the ``<suffix>`` of a series does not include a hash-algorithm
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``
303 .. versionadded:: 3.3
304 In order to match associated files in subdirectories,
305 specify a ``RECURSE:`` option, e.g. ``DATA{MyDataDir/,RECURSE:,REGEX:.*}``.
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`:
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
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}")
392 function(ExternalData_add_target target)
393 if(NOT ExternalData_URL_TEMPLATES AND NOT ExternalData_OBJECT_STORES)
395 "Neither ExternalData_URL_TEMPLATES nor ExternalData_OBJECT_STORES is set!")
397 if(NOT ExternalData_OBJECT_STORES)
398 set(ExternalData_OBJECT_STORES ${CMAKE_BINARY_DIR}/ExternalData/Objects)
400 set(_ExternalData_CONFIG_CODE "")
402 cmake_parse_arguments(PARSE_ARGV 1 _ExternalData_add_target
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}`")
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)
418 set(_ExternalData_add_target_SHOW_PROGRESS ON)
420 elseif (_ExternalData_add_target_SHOW_PROGRESS)
421 set(_ExternalData_add_target_SHOW_PROGRESS ON)
423 set(_ExternalData_add_target_SHOW_PROGRESS OFF)
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}}\")")
437 "No ExternalData_CUSTOM_SCRIPT_${key} is not set to a full path:\n"
438 " ${ExternalData_CUSTOM_SCRIPT_${key}}")
442 "No ExternalData_CUSTOM_SCRIPT_${key} is set for URL template:\n"
447 "Bad ExternalDataCustomScript key '${key}' in URL template:\n"
449 "The key must be a valid C identifier.")
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}}\")")
466 "Bad %(algo:${key}) in URL template:\n"
468 "The transform name must be a valid C identifier.")
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}\")")
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)
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.
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}")
511 set_property(DIRECTORY PROPERTY "_ExternalData_FILE_${file}" 1)
513 COMMENT "Generating ${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}"
524 list(APPEND files "${file}")
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}")
542 set_property(DIRECTORY PROPERTY "_ExternalData_FILE_${file}" 1)
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}"
562 list(APPEND files "${file}${stamp}")
566 # Custom target to drive all update commands.
567 add_custom_target(${target} ALL DEPENDS ${files})
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[^{])+|.")
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.
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}")
591 # No replacement needed for this piece.
592 string(APPEND outArg "${piece}")
596 # No replacements needed in this argument.
599 # Re-escape in-value semicolons in resulting list.
600 string(REPLACE ";" "\\;" outArg "${outArg}")
601 list(APPEND outArgs "${outArg}")
603 set("${outArgsVar}" "${outArgs}" PARENT_SCOPE)
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)
619 message(FATAL_ERROR "Hash algorithm ${algo} unimplemented.")
623 function(_ExternalData_random var)
624 string(RANDOM LENGTH 6 random)
625 set("${var}" "${random}" PARENT_SCOPE)
628 function(_ExternalData_exact_regex regex_var string)
629 string(REGEX REPLACE "([][+.*()^])" "\\\\\\1" regex "${string}")
630 set("${regex_var}" "${regex}" PARENT_SCOPE)
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}")
640 function(_ExternalData_link_content name var_ext)
641 if("${ExternalData_LINK_CONTENT}" MATCHES "^(${_ExternalData_REGEX_ALGO})$")
642 set(algo "${ExternalData_LINK_CONTENT}")
645 "Unknown hash algorithm specified by ExternalData_LINK_CONTENT:\n"
646 " ${ExternalData_LINK_CONTENT}")
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}")
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}")
673 # Convert to full path.
674 if(IS_ABSOLUTE "${data}")
675 set(absdata "${data}")
677 set(absdata "${CMAKE_CURRENT_SOURCE_DIR}/${data}")
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}")
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"
690 "does not lie under the top-level source directory\n"
693 if(data_is_directory AND NOT IS_DIRECTORY "${top_src}/${reldata}")
694 message(FATAL_ERROR "Data directory referenced by argument\n"
696 "corresponds to source tree path\n"
698 "that does not exist as a directory!")
700 if(NOT ExternalData_BINARY_ROOT)
701 set(ExternalData_BINARY_ROOT "${CMAKE_BINARY_DIR}")
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)
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)
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}")
738 message(FATAL_ERROR "Unknown option \"${opt}\" in argument\n"
744 if(data_is_directory)
745 message(FATAL_ERROR "Series option \"${series_option}\" not allowed with directories.")
747 if(associated_files OR associated_regex)
748 message(FATAL_ERROR "Series option \"${series_option}\" not allowed with associated files.")
751 message(FATAL_ERROR "Recurse option \"${recurse_option}\" allowed only with directories.")
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()
760 message(FATAL_ERROR "Data directory referenced by argument\n"
762 "must list associated files.")
766 message(FATAL_ERROR "Recurse option \"${recurse_option}\" allowed only with directories.")
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()
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!")
781 set(msg_kind AUTHOR_WARNING)
782 set(msg "that does not exist as a file (with or without an extension)!")
784 message(${msg_kind} "Data file referenced by argument\n"
786 "corresponds to source tree path\n"
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)
799 # The whole series is in the source tree.
800 set("${var_file}" "${top_src}/${reldata}" PARENT_SCOPE)
804 macro(_ExternalData_arg_associated)
805 # Associated files lie in the same directory.
806 if(data_is_directory)
807 set(reldir "${reldata}")
809 get_filename_component(reldir "${reldata}" PATH)
812 string(APPEND reldir "/")
814 _ExternalData_exact_regex(reldir_regex "${reldir}")
816 set(glob GLOB_RECURSE)
817 string(APPEND reldir_regex "(.+/)?")
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}")
829 # Find files matching the given regular expressions.
832 foreach(regex ${associated_regex})
833 string(APPEND all "${sep}${reldir_regex}${regex}")
836 _ExternalData_arg_find_files(${glob} "${reldir}" "${all}")
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}")
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}")
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\\([^()]*\\)\\([^()]*\\)\\$$")
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"
869 set(series_parse "${ExternalData_SERIES_PARSE}")
871 set(series_parse "([0-9]*)(\\.[^./]*)$")
873 if(ExternalData_SERIES_MATCH)
874 set(series_match "${ExternalData_SERIES_MATCH}")
876 set(series_match "[_.-]?[0-9]*")
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"
885 "corresponds to path\n"
887 "that does not match regular expression\n"
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}")
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}")
909 set(relname "${entry}")
912 if("x${relname}" MATCHES "^x${regex}$" # matches
913 AND NOT "x${relname}" MATCHES "(^x|/)\\.ExternalData_" # not staged obj
915 if(IS_DIRECTORY "${top_src}/${entry}")
916 if("${relname}" STREQUAL "${reldata}")
917 set(have_original_as_dir 1)
920 set(name "${top_src}/${relname}")
921 set(file "${top_bin}/${relname}")
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}")
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}")
932 list(APPEND internal "${file}|${name}")
934 if("${relname}" STREQUAL "${reldata}")
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)
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)
954 #-----------------------------------------------------------------------------
955 # Private script mode interface
957 if(CMAKE_GENERATOR OR NOT ExternalData_ACTION)
961 if(ExternalData_CONFIG)
962 include(${ExternalData_CONFIG})
964 if(NOT ExternalData_URL_TEMPLATES AND NOT ExternalData_OBJECT_STORES)
966 "Neither ExternalData_URL_TEMPLATES nor ExternalData_OBJECT_STORES is set!")
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.
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}")
987 # Create link (falling back to copying if there's a problem).
988 file(CREATE_LINK "${tgt}" "${tmp}" RESULT result COPY_ON_ERROR SYMBOLIC)
991 file(COPY_FILE "${src}" "${tmp}" RESULT result INPUT_MAY_BE_RECENT)
994 file(REMOVE "${tmp}")
995 message(FATAL_ERROR "Failed to create:\n \"${tmp}\"\nfrom:\n \"${obj}\"\nwith error:\n ${result}")
998 # Atomically create/replace the real destination.
999 file(RENAME "${tmp}" "${dst}")
1002 function(_ExternalData_download_file url file err_var msg_var)
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}")
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)
1017 set(inactivity_timeout "")
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)
1024 set(absolute_timeout "")
1026 set(show_progress_args)
1027 if (ExternalData_SHOW_PROGRESS)
1028 list(APPEND show_progress_args SHOW_PROGRESS)
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)
1034 if("${msg}" MATCHES "HTTP response code said error" AND
1035 "${log}" MATCHES "error: 503")
1036 set(msg "temporarily unavailable")
1038 elseif("${log}" MATCHES "\nHTTP[^\n]* 503")
1040 set(msg "temporarily unavailable")
1042 if(NOT err OR NOT "${msg}" MATCHES "partial|timeout|temporarily")
1045 message(STATUS "[download terminated: ${msg}, retries left: ${retry}]")
1048 set("${err_var}" "${err}" PARENT_SCOPE)
1049 set("${msg_var}" "${msg}" PARENT_SCOPE)
1052 function(_ExternalData_custom_fetch key loc file err_var msg_var)
1053 if(NOT ExternalData_CUSTOM_SCRIPT_${key})
1055 set(msg "No ExternalData_CUSTOM_SCRIPT_${key} set!")
1056 elseif(NOT EXISTS "${ExternalData_CUSTOM_SCRIPT_${key}}")
1058 set(msg "No '${ExternalData_CUSTOM_SCRIPT_${key}}' exists!")
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)
1066 set(msg "${ExternalData_CUSTOM_ERROR}")
1072 set("${err_var}" "${err}" PARENT_SCOPE)
1073 set("${msg_var}" "${msg}" PARENT_SCOPE)
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}")
1081 message(STATUS "Found object: \"${obj}\"")
1082 set("${var_obj}" "${obj}" PARENT_SCOPE)
1083 set("${var_success}" 1 PARENT_SCOPE)
1089 function(_ExternalData_download_object name hash algo var_obj var_success var_errorMsg)
1090 # Search all object stores for an existing object.
1092 foreach(dir ${ExternalData_OBJECT_STORES})
1093 set(obj "${dir}/${algo}/${hash}")
1095 message(STATUS "Found object: \"${obj}\"")
1096 set("${var_obj}" "${obj}" PARENT_SCOPE)
1097 set("${var_success}" "${success}" PARENT_SCOPE)
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}")
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}")
1120 set(url "${lhs}${algo}${rhs}")
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)
1128 _ExternalData_download_file("${url}" "${tmp}" err errMsg)
1130 string(APPEND tried "\n ${url}")
1132 string(APPEND tried " (${errMsg})")
1134 # Verify downloaded object.
1135 _ExternalData_compute_hash(dl_hash "${algo}" "${tmp}")
1136 if("${dl_hash}" STREQUAL "${hash}")
1140 string(APPEND tried " (wrong hash ${algo}=${dl_hash})")
1141 if("$ENV{ExternalData_DEBUG_DOWNLOAD}" MATCHES ".")
1142 file(RENAME "${tmp}" "${store}/${algo}/${dl_hash}")
1146 file(REMOVE "${tmp}")
1149 get_filename_component(dir "${name}" PATH)
1150 set(staged "${dir}/.ExternalData_${algo}_${hash}")
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}")
1160 message(FATAL_ERROR "Failed to rename:\n \"${tmp}\"\nto:\n \"${obj}\"\nwith error:\n ${result}")
1162 message(STATUS "Downloaded object: \"${obj}\"")
1163 elseif(EXISTS "${staged}")
1164 set(obj "${staged}")
1165 message(STATUS "Staged object: \"${obj}\"")
1168 set(tried "\n (No ExternalData_URL_TEMPLATES given)")
1171 set("${var_errorMsg}" "Object ${algo}=${hash} not found at:${tried}" PARENT_SCOPE)
1174 set("${var_obj}" "${obj}" PARENT_SCOPE)
1175 set("${var_success}" "${success}" PARENT_SCOPE)
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!")
1185 string(REPLACE "+" ";" exts_list "${exts}")
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}")
1200 message(FATAL_ERROR "Unknown hash algorithm extension \"${ext}\"")
1203 list(APPEND hash_list ${hash})
1204 list(APPEND algo_list ${algo})
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)
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}")
1230 message(FATAL_ERROR "${errorMsg}")
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)
1244 # Touch the file to convince the build system it is up to date.
1245 file(TOUCH "${file}")
1247 _ExternalData_link_or_copy("${obj}" "${file}")
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!")
1258 _ExternalData_link_or_copy("${name}" "${file}")
1260 message(FATAL_ERROR "Unknown ExternalData_ACTION=[${ExternalData_ACTION}]")