~ubuntu-branches/debian/sid/simpleitk/sid

« back to all changes in this revision

Viewing changes to CMake/ExternalData.cmake

  • Committer: Package Import Robot
  • Author(s): Ghislain Antony Vaillant
  • Date: 2017-11-02 08:49:18 UTC
  • Revision ID: package-import@ubuntu.com-20171102084918-7hs09ih668xq87ej
Tags: upstream-1.0.1
ImportĀ upstreamĀ versionĀ 1.0.1

Show diffs side-by-side

added added

removed removed

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