ExternalData.cmake 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. #.rst:
  2. # ExternalData
  3. # ------------
  4. #
  5. # Manage data files stored outside source tree
  6. #
  7. # Use this module to unambiguously reference data files stored outside
  8. # the source tree and fetch them at build time from arbitrary local and
  9. # remote content-addressed locations. Functions provided by this module
  10. # recognize arguments with the syntax ``DATA{<name>}`` as references to
  11. # external data, replace them with full paths to local copies of those
  12. # data, and create build rules to fetch and update the local copies.
  13. #
  14. # The ``DATA{}`` syntax is literal and the ``<name>`` is a full or relative path
  15. # within the source tree. The source tree must contain either a real
  16. # data file at ``<name>`` or a "content link" at ``<name><ext>`` containing a
  17. # hash of the real file using a hash algorithm corresponding to ``<ext>``.
  18. # For example, the argument ``DATA{img.png}`` may be satisfied by either a
  19. # real ``img.png`` file in the current source directory or a ``img.png.md5``
  20. # file containing its MD5 sum.
  21. #
  22. # The ``ExternalData_Expand_Arguments`` function evaluates ``DATA{}``
  23. # references in its arguments and constructs a new list of arguments:
  24. #
  25. # .. code-block:: cmake
  26. #
  27. # ExternalData_Expand_Arguments(
  28. # <target> # Name of data management target
  29. # <outVar> # Output variable
  30. # [args...] # Input arguments, DATA{} allowed
  31. # )
  32. #
  33. # It replaces each ``DATA{}`` reference in an argument with the full path of
  34. # a real data file on disk that will exist after the ``<target>`` builds.
  35. #
  36. # The ``ExternalData_Add_Test`` function wraps around the CMake
  37. # :command:`add_test` command but supports ``DATA{}`` references in
  38. # its arguments:
  39. #
  40. # .. code-block:: cmake
  41. #
  42. # ExternalData_Add_Test(
  43. # <target> # Name of data management target
  44. # ... # Arguments of add_test(), DATA{} allowed
  45. # )
  46. #
  47. # It passes its arguments through ``ExternalData_Expand_Arguments`` and then
  48. # invokes the :command:`add_test` command using the results.
  49. #
  50. # The ``ExternalData_Add_Target`` function creates a custom target to
  51. # manage local instances of data files stored externally:
  52. #
  53. # .. code-block:: cmake
  54. #
  55. # ExternalData_Add_Target(
  56. # <target> # Name of data management target
  57. # )
  58. #
  59. # It creates custom commands in the target as necessary to make data
  60. # files available for each ``DATA{}`` reference previously evaluated by
  61. # other functions provided by this module. A list of URL templates may
  62. # be provided in the variable ``ExternalData_URL_TEMPLATES`` using the
  63. # placeholders ``%(algo)`` and ``%(hash)`` in each template. Data fetch
  64. # rules try each URL template in order by substituting the hash
  65. # algorithm name for ``%(algo)`` and the hash value for ``%(hash)``.
  66. #
  67. # The following hash algorithms are supported::
  68. #
  69. # %(algo) <ext> Description
  70. # ------- ----- -----------
  71. # MD5 .md5 Message-Digest Algorithm 5, RFC 1321
  72. # SHA1 .sha1 US Secure Hash Algorithm 1, RFC 3174
  73. # SHA224 .sha224 US Secure Hash Algorithms, RFC 4634
  74. # SHA256 .sha256 US Secure Hash Algorithms, RFC 4634
  75. # SHA384 .sha384 US Secure Hash Algorithms, RFC 4634
  76. # SHA512 .sha512 US Secure Hash Algorithms, RFC 4634
  77. #
  78. # Note that the hashes are used only for unique data identification and
  79. # download verification. This is not security software.
  80. #
  81. # Example usage:
  82. #
  83. # .. code-block:: cmake
  84. #
  85. # include(ExternalData)
  86. # set(ExternalData_URL_TEMPLATES "file:///local/%(algo)/%(hash)"
  87. # "file:////host/share/%(algo)/%(hash)"
  88. # "http://data.org/%(algo)/%(hash)")
  89. # ExternalData_Add_Test(MyData
  90. # NAME MyTest
  91. # COMMAND MyExe DATA{MyInput.png}
  92. # )
  93. # ExternalData_Add_Target(MyData)
  94. #
  95. # When test ``MyTest`` runs the ``DATA{MyInput.png}`` argument will be
  96. # replaced by the full path to a real instance of the data file
  97. # ``MyInput.png`` on disk. If the source tree contains a content link
  98. # such as ``MyInput.png.md5`` then the ``MyData`` target creates a real
  99. # ``MyInput.png`` in the build tree.
  100. #
  101. # The ``DATA{}`` syntax can be told to fetch a file series using the form
  102. # ``DATA{<name>,:}``, where the ``:`` is literal. If the source tree
  103. # contains a group of files or content links named like a series then a
  104. # reference to one member adds rules to fetch all of them. Although all
  105. # members of a series are fetched, only the file originally named by the
  106. # ``DATA{}`` argument is substituted for it. The default configuration
  107. # recognizes file series names ending with ``#.ext``, ``_#.ext``, ``.#.ext``,
  108. # or ``-#.ext`` where ``#`` is a sequence of decimal digits and ``.ext`` is
  109. # any single extension. Configure it with a regex that parses ``<number>``
  110. # and ``<suffix>`` parts from the end of ``<name>``::
  111. #
  112. # ExternalData_SERIES_PARSE = regex of the form (<number>)(<suffix>)$
  113. #
  114. # For more complicated cases set::
  115. #
  116. # ExternalData_SERIES_PARSE = regex with at least two () groups
  117. # ExternalData_SERIES_PARSE_PREFIX = <prefix> regex group number, if any
  118. # ExternalData_SERIES_PARSE_NUMBER = <number> regex group number
  119. # ExternalData_SERIES_PARSE_SUFFIX = <suffix> regex group number
  120. #
  121. # Configure series number matching with a regex that matches the
  122. # ``<number>`` part of series members named ``<prefix><number><suffix>``::
  123. #
  124. # ExternalData_SERIES_MATCH = regex matching <number> in all series members
  125. #
  126. # Note that the ``<suffix>`` of a series does not include a hash-algorithm
  127. # extension.
  128. #
  129. # The ``DATA{}`` syntax can alternatively match files associated with the
  130. # named file and contained in the same directory. Associated files may
  131. # be specified by options using the syntax
  132. # ``DATA{<name>,<opt1>,<opt2>,...}``. Each option may specify one file by
  133. # name or specify a regular expression to match file names using the
  134. # syntax ``REGEX:<regex>``. For example, the arguments::
  135. #
  136. # DATA{MyData/MyInput.mhd,MyInput.img} # File pair
  137. # DATA{MyData/MyFrames00.png,REGEX:MyFrames[0-9]+\\.png} # Series
  138. #
  139. # will pass ``MyInput.mha`` and ``MyFrames00.png`` on the command line but
  140. # ensure that the associated files are present next to them.
  141. #
  142. # The ``DATA{}`` syntax may reference a directory using a trailing slash and
  143. # a list of associated files. The form ``DATA{<name>/,<opt1>,<opt2>,...}``
  144. # adds rules to fetch any files in the directory that match one of the
  145. # associated file options. For example, the argument
  146. # ``DATA{MyDataDir/,REGEX:.*}`` will pass the full path to a ``MyDataDir``
  147. # directory on the command line and ensure that the directory contains
  148. # files corresponding to every file or content link in the ``MyDataDir``
  149. # source directory.
  150. #
  151. # The variable ``ExternalData_LINK_CONTENT`` may be set to the name of a
  152. # supported hash algorithm to enable automatic conversion of real data
  153. # files referenced by the ``DATA{}`` syntax into content links. For each
  154. # such ``<file>`` a content link named ``<file><ext>`` is created. The
  155. # original file is renamed to the form ``.ExternalData_<algo>_<hash>`` to
  156. # stage it for future transmission to one of the locations in the list
  157. # of URL templates (by means outside the scope of this module). The
  158. # data fetch rule created for the content link will use the staged
  159. # object if it cannot be found using any URL template.
  160. #
  161. # The variable ``ExternalData_OBJECT_STORES`` may be set to a list of local
  162. # directories that store objects using the layout ``<dir>/%(algo)/%(hash)``.
  163. # These directories will be searched first for a needed object. If the
  164. # object is not available in any store then it will be fetched remotely
  165. # using the URL templates and added to the first local store listed. If
  166. # no stores are specified the default is a location inside the build
  167. # tree.
  168. #
  169. # The variable ``ExternalData_SOURCE_ROOT`` may be set to the highest source
  170. # directory containing any path named by a ``DATA{}`` reference. The
  171. # default is ``CMAKE_SOURCE_DIR``. ``ExternalData_SOURCE_ROOT`` and
  172. # ``CMAKE_SOURCE_DIR`` must refer to directories within a single source
  173. # distribution (e.g. they come together in one tarball).
  174. #
  175. # The variable ``ExternalData_BINARY_ROOT`` may be set to the directory to
  176. # hold the real data files named by expanded ``DATA{}`` references. The
  177. # default is ``CMAKE_BINARY_DIR``. The directory layout will mirror that of
  178. # content links under ``ExternalData_SOURCE_ROOT``.
  179. #
  180. # Variables ``ExternalData_TIMEOUT_INACTIVITY`` and
  181. # ``ExternalData_TIMEOUT_ABSOLUTE`` set the download inactivity and absolute
  182. # timeouts, in seconds. The defaults are 60 seconds and 300 seconds,
  183. # respectively. Set either timeout to 0 seconds to disable enforcement.
  184. #=============================================================================
  185. # Copyright 2010-2013 Kitware, Inc.
  186. #
  187. # Distributed under the OSI-approved BSD License (the "License");
  188. # see accompanying file Copyright.txt for details.
  189. #
  190. # This software is distributed WITHOUT ANY WARRANTY; without even the
  191. # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  192. # See the License for more information.
  193. #=============================================================================
  194. # (To distribute this file outside of CMake, substitute the full
  195. # License text for the above reference.)
  196. function(ExternalData_add_test target)
  197. # Expand all arguments as a single string to preserve escaped semicolons.
  198. ExternalData_expand_arguments("${target}" testArgs "${ARGN}")
  199. add_test(${testArgs})
  200. endfunction()
  201. function(ExternalData_add_target target)
  202. if(NOT ExternalData_URL_TEMPLATES AND NOT ExternalData_OBJECT_STORES)
  203. message(FATAL_ERROR
  204. "Neither ExternalData_URL_TEMPLATES nor ExternalData_OBJECT_STORES is set!")
  205. endif()
  206. if(NOT ExternalData_OBJECT_STORES)
  207. set(ExternalData_OBJECT_STORES ${CMAKE_BINARY_DIR}/ExternalData/Objects)
  208. endif()
  209. set(config ${CMAKE_CURRENT_BINARY_DIR}/${target}_config.cmake)
  210. configure_file(${_ExternalData_SELF_DIR}/ExternalData_config.cmake.in ${config} @ONLY)
  211. set(files "")
  212. # Set "_ExternalData_FILE_${file}" for each output file to avoid duplicate
  213. # rules. Use local data first to prefer real files over content links.
  214. # Custom commands to copy or link local data.
  215. get_property(data_local GLOBAL PROPERTY _ExternalData_${target}_LOCAL)
  216. foreach(entry IN LISTS data_local)
  217. string(REPLACE "|" ";" tuple "${entry}")
  218. list(GET tuple 0 file)
  219. list(GET tuple 1 name)
  220. if(NOT DEFINED "_ExternalData_FILE_${file}")
  221. set("_ExternalData_FILE_${file}" 1)
  222. add_custom_command(
  223. COMMENT "Generating ${file}"
  224. OUTPUT "${file}"
  225. COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
  226. -Dfile=${file} -Dname=${name}
  227. -DExternalData_ACTION=local
  228. -DExternalData_CONFIG=${config}
  229. -P ${_ExternalData_SELF}
  230. MAIN_DEPENDENCY "${name}"
  231. )
  232. list(APPEND files "${file}")
  233. endif()
  234. endforeach()
  235. # Custom commands to fetch remote data.
  236. get_property(data_fetch GLOBAL PROPERTY _ExternalData_${target}_FETCH)
  237. foreach(entry IN LISTS data_fetch)
  238. string(REPLACE "|" ";" tuple "${entry}")
  239. list(GET tuple 0 file)
  240. list(GET tuple 1 name)
  241. list(GET tuple 2 ext)
  242. set(stamp "${ext}-stamp")
  243. if(NOT DEFINED "_ExternalData_FILE_${file}")
  244. set("_ExternalData_FILE_${file}" 1)
  245. add_custom_command(
  246. # Users care about the data file, so hide the hash/timestamp file.
  247. COMMENT "Generating ${file}"
  248. # The hash/timestamp file is the output from the build perspective.
  249. # List the real file as a second output in case it is a broken link.
  250. # The files must be listed in this order so CMake can hide from the
  251. # make tool that a symlink target may not be newer than the input.
  252. OUTPUT "${file}${stamp}" "${file}"
  253. # Run the data fetch/update script.
  254. COMMAND ${CMAKE_COMMAND} -Drelative_top=${CMAKE_BINARY_DIR}
  255. -Dfile=${file} -Dname=${name} -Dext=${ext}
  256. -DExternalData_ACTION=fetch
  257. -DExternalData_CONFIG=${config}
  258. -P ${_ExternalData_SELF}
  259. # Update whenever the object hash changes.
  260. MAIN_DEPENDENCY "${name}${ext}"
  261. )
  262. list(APPEND files "${file}${stamp}")
  263. endif()
  264. endforeach()
  265. # Custom target to drive all update commands.
  266. add_custom_target(${target} ALL DEPENDS ${files})
  267. endfunction()
  268. function(ExternalData_expand_arguments target outArgsVar)
  269. # Replace DATA{} references with real arguments.
  270. set(data_regex "DATA{([^;{}\r\n]*)}")
  271. set(other_regex "([^D]|D[^A]|DA[^T]|DAT[^A]|DATA[^{])+|.")
  272. set(outArgs "")
  273. # This list expansion un-escapes semicolons in list element values so we
  274. # must re-escape them below anywhere a new list expansion will occur.
  275. foreach(arg IN LISTS ARGN)
  276. if("x${arg}" MATCHES "${data_regex}")
  277. # Re-escape in-value semicolons before expansion in foreach below.
  278. string(REPLACE ";" "\\;" tmp "${arg}")
  279. # Split argument into DATA{}-pieces and other pieces.
  280. string(REGEX MATCHALL "${data_regex}|${other_regex}" pieces "${tmp}")
  281. # Compose output argument with DATA{}-pieces replaced.
  282. set(outArg "")
  283. foreach(piece IN LISTS pieces)
  284. if("x${piece}" MATCHES "^x${data_regex}$")
  285. # Replace this DATA{}-piece with a file path.
  286. string(REGEX REPLACE "${data_regex}" "\\1" data "${piece}")
  287. _ExternalData_arg("${target}" "${piece}" "${data}" file)
  288. set(outArg "${outArg}${file}")
  289. else()
  290. # No replacement needed for this piece.
  291. set(outArg "${outArg}${piece}")
  292. endif()
  293. endforeach()
  294. else()
  295. # No replacements needed in this argument.
  296. set(outArg "${arg}")
  297. endif()
  298. # Re-escape in-value semicolons in resulting list.
  299. string(REPLACE ";" "\\;" outArg "${outArg}")
  300. list(APPEND outArgs "${outArg}")
  301. endforeach()
  302. set("${outArgsVar}" "${outArgs}" PARENT_SCOPE)
  303. endfunction()
  304. #-----------------------------------------------------------------------------
  305. # Private helper interface
  306. set(_ExternalData_REGEX_ALGO "MD5|SHA1|SHA224|SHA256|SHA384|SHA512")
  307. set(_ExternalData_REGEX_EXT "md5|sha1|sha224|sha256|sha384|sha512")
  308. set(_ExternalData_SELF "${CMAKE_CURRENT_LIST_FILE}")
  309. get_filename_component(_ExternalData_SELF_DIR "${_ExternalData_SELF}" PATH)
  310. function(_ExternalData_compute_hash var_hash algo file)
  311. if("${algo}" MATCHES "^${_ExternalData_REGEX_ALGO}$")
  312. file("${algo}" "${file}" hash)
  313. set("${var_hash}" "${hash}" PARENT_SCOPE)
  314. else()
  315. message(FATAL_ERROR "Hash algorithm ${algo} unimplemented.")
  316. endif()
  317. endfunction()
  318. function(_ExternalData_random var)
  319. string(RANDOM LENGTH 6 random)
  320. set("${var}" "${random}" PARENT_SCOPE)
  321. endfunction()
  322. function(_ExternalData_exact_regex regex_var string)
  323. string(REGEX REPLACE "([][+.*()^])" "\\\\\\1" regex "${string}")
  324. set("${regex_var}" "${regex}" PARENT_SCOPE)
  325. endfunction()
  326. function(_ExternalData_atomic_write file content)
  327. _ExternalData_random(random)
  328. set(tmp "${file}.tmp${random}")
  329. file(WRITE "${tmp}" "${content}")
  330. file(RENAME "${tmp}" "${file}")
  331. endfunction()
  332. function(_ExternalData_link_content name var_ext)
  333. if("${ExternalData_LINK_CONTENT}" MATCHES "^(${_ExternalData_REGEX_ALGO})$")
  334. set(algo "${ExternalData_LINK_CONTENT}")
  335. else()
  336. message(FATAL_ERROR
  337. "Unknown hash algorithm specified by ExternalData_LINK_CONTENT:\n"
  338. " ${ExternalData_LINK_CONTENT}")
  339. endif()
  340. _ExternalData_compute_hash(hash "${algo}" "${name}")
  341. get_filename_component(dir "${name}" PATH)
  342. set(staged "${dir}/.ExternalData_${algo}_${hash}")
  343. string(TOLOWER ".${algo}" ext)
  344. _ExternalData_atomic_write("${name}${ext}" "${hash}\n")
  345. file(RENAME "${name}" "${staged}")
  346. set("${var_ext}" "${ext}" PARENT_SCOPE)
  347. file(RELATIVE_PATH relname "${ExternalData_SOURCE_ROOT}" "${name}${ext}")
  348. message(STATUS "Linked ${relname} to ExternalData ${algo}/${hash}")
  349. endfunction()
  350. function(_ExternalData_arg target arg options var_file)
  351. # Separate data path from the options.
  352. string(REPLACE "," ";" options "${options}")
  353. list(GET options 0 data)
  354. list(REMOVE_AT options 0)
  355. # Interpret trailing slashes as directories.
  356. set(data_is_directory 0)
  357. if("x${data}" MATCHES "^x(.*)([/\\])$")
  358. set(data_is_directory 1)
  359. set(data "${CMAKE_MATCH_1}")
  360. endif()
  361. # Convert to full path.
  362. if(IS_ABSOLUTE "${data}")
  363. set(absdata "${data}")
  364. else()
  365. set(absdata "${CMAKE_CURRENT_SOURCE_DIR}/${data}")
  366. endif()
  367. get_filename_component(absdata "${absdata}" ABSOLUTE)
  368. # Convert to relative path under the source tree.
  369. if(NOT ExternalData_SOURCE_ROOT)
  370. set(ExternalData_SOURCE_ROOT "${CMAKE_SOURCE_DIR}")
  371. endif()
  372. set(top_src "${ExternalData_SOURCE_ROOT}")
  373. file(RELATIVE_PATH reldata "${top_src}" "${absdata}")
  374. if(IS_ABSOLUTE "${reldata}" OR "${reldata}" MATCHES "^\\.\\./")
  375. message(FATAL_ERROR "Data file referenced by argument\n"
  376. " ${arg}\n"
  377. "does not lie under the top-level source directory\n"
  378. " ${top_src}\n")
  379. endif()
  380. if(data_is_directory AND NOT IS_DIRECTORY "${top_src}/${reldata}")
  381. message(FATAL_ERROR "Data directory referenced by argument\n"
  382. " ${arg}\n"
  383. "corresponds to source tree path\n"
  384. " ${reldata}\n"
  385. "that does not exist as a directory!")
  386. endif()
  387. if(NOT ExternalData_BINARY_ROOT)
  388. set(ExternalData_BINARY_ROOT "${CMAKE_BINARY_DIR}")
  389. endif()
  390. set(top_bin "${ExternalData_BINARY_ROOT}")
  391. # Handle in-source builds gracefully.
  392. if("${top_src}" STREQUAL "${top_bin}")
  393. if(ExternalData_LINK_CONTENT)
  394. message(WARNING "ExternalData_LINK_CONTENT cannot be used in-source")
  395. set(ExternalData_LINK_CONTENT 0)
  396. endif()
  397. set(top_same 1)
  398. endif()
  399. set(external "") # Entries external to the source tree.
  400. set(internal "") # Entries internal to the source tree.
  401. set(have_original ${data_is_directory})
  402. # Process options.
  403. set(series_option "")
  404. set(associated_files "")
  405. set(associated_regex "")
  406. foreach(opt ${options})
  407. if("x${opt}" MATCHES "^xREGEX:[^:/]+$")
  408. # Regular expression to match associated files.
  409. string(REGEX REPLACE "^REGEX:" "" regex "${opt}")
  410. list(APPEND associated_regex "${regex}")
  411. elseif(opt STREQUAL ":")
  412. # Activate series matching.
  413. set(series_option "${opt}")
  414. elseif("x${opt}" MATCHES "^[^][:/*?]+$")
  415. # Specific associated file.
  416. list(APPEND associated_files "${opt}")
  417. else()
  418. message(FATAL_ERROR "Unknown option \"${opt}\" in argument\n"
  419. " ${arg}\n")
  420. endif()
  421. endforeach()
  422. if(series_option)
  423. if(data_is_directory)
  424. message(FATAL_ERROR "Series option \"${series_option}\" not allowed with directories.")
  425. endif()
  426. if(associated_files OR associated_regex)
  427. message(FATAL_ERROR "Series option \"${series_option}\" not allowed with associated files.")
  428. endif()
  429. # Load a whole file series.
  430. _ExternalData_arg_series()
  431. elseif(data_is_directory)
  432. if(associated_files OR associated_regex)
  433. # Load listed/matching associated files in the directory.
  434. _ExternalData_arg_associated()
  435. else()
  436. message(FATAL_ERROR "Data directory referenced by argument\n"
  437. " ${arg}\n"
  438. "must list associated files.")
  439. endif()
  440. else()
  441. # Load the named data file.
  442. _ExternalData_arg_single()
  443. if(associated_files OR associated_regex)
  444. # Load listed/matching associated files.
  445. _ExternalData_arg_associated()
  446. endif()
  447. endif()
  448. if(NOT have_original)
  449. message(FATAL_ERROR "Data file referenced by argument\n"
  450. " ${arg}\n"
  451. "corresponds to source tree path\n"
  452. " ${reldata}\n"
  453. "that does not exist as a file (with or without an extension)!")
  454. endif()
  455. if(external)
  456. # Make the series available in the build tree.
  457. set_property(GLOBAL APPEND PROPERTY
  458. _ExternalData_${target}_FETCH "${external}")
  459. set_property(GLOBAL APPEND PROPERTY
  460. _ExternalData_${target}_LOCAL "${internal}")
  461. set("${var_file}" "${top_bin}/${reldata}" PARENT_SCOPE)
  462. else()
  463. # The whole series is in the source tree.
  464. set("${var_file}" "${top_src}/${reldata}" PARENT_SCOPE)
  465. endif()
  466. endfunction()
  467. macro(_ExternalData_arg_associated)
  468. # Associated files lie in the same directory.
  469. if(data_is_directory)
  470. set(reldir "${reldata}")
  471. else()
  472. get_filename_component(reldir "${reldata}" PATH)
  473. endif()
  474. if(reldir)
  475. set(reldir "${reldir}/")
  476. endif()
  477. _ExternalData_exact_regex(reldir_regex "${reldir}")
  478. # Find files named explicitly.
  479. foreach(file ${associated_files})
  480. _ExternalData_exact_regex(file_regex "${file}")
  481. _ExternalData_arg_find_files("${reldir}${file}" "${reldir_regex}${file_regex}")
  482. endforeach()
  483. # Find files matching the given regular expressions.
  484. set(all "")
  485. set(sep "")
  486. foreach(regex ${associated_regex})
  487. set(all "${all}${sep}${reldir_regex}${regex}")
  488. set(sep "|")
  489. endforeach()
  490. _ExternalData_arg_find_files("${reldir}" "${all}")
  491. endmacro()
  492. macro(_ExternalData_arg_single)
  493. # Match only the named data by itself.
  494. _ExternalData_exact_regex(data_regex "${reldata}")
  495. _ExternalData_arg_find_files("${reldata}" "${data_regex}")
  496. endmacro()
  497. macro(_ExternalData_arg_series)
  498. # Configure series parsing and matching.
  499. set(series_parse_prefix "")
  500. set(series_parse_number "\\1")
  501. set(series_parse_suffix "\\2")
  502. if(ExternalData_SERIES_PARSE)
  503. if(ExternalData_SERIES_PARSE_NUMBER AND ExternalData_SERIES_PARSE_SUFFIX)
  504. if(ExternalData_SERIES_PARSE_PREFIX)
  505. set(series_parse_prefix "\\${ExternalData_SERIES_PARSE_PREFIX}")
  506. endif()
  507. set(series_parse_number "\\${ExternalData_SERIES_PARSE_NUMBER}")
  508. set(series_parse_suffix "\\${ExternalData_SERIES_PARSE_SUFFIX}")
  509. elseif(NOT "x${ExternalData_SERIES_PARSE}" MATCHES "^x\\([^()]*\\)\\([^()]*\\)\\$$")
  510. message(FATAL_ERROR
  511. "ExternalData_SERIES_PARSE is set to\n"
  512. " ${ExternalData_SERIES_PARSE}\n"
  513. "which is not of the form\n"
  514. " (<number>)(<suffix>)$\n"
  515. "Fix the regular expression or set variables\n"
  516. " ExternalData_SERIES_PARSE_PREFIX = <prefix> regex group number, if any\n"
  517. " ExternalData_SERIES_PARSE_NUMBER = <number> regex group number\n"
  518. " ExternalData_SERIES_PARSE_SUFFIX = <suffix> regex group number\n"
  519. )
  520. endif()
  521. set(series_parse "${ExternalData_SERIES_PARSE}")
  522. else()
  523. set(series_parse "([0-9]*)(\\.[^./]*)$")
  524. endif()
  525. if(ExternalData_SERIES_MATCH)
  526. set(series_match "${ExternalData_SERIES_MATCH}")
  527. else()
  528. set(series_match "[_.-]?[0-9]*")
  529. endif()
  530. # Parse the base, number, and extension components of the series.
  531. string(REGEX REPLACE "${series_parse}" "${series_parse_prefix};${series_parse_number};${series_parse_suffix}" tuple "${reldata}")
  532. list(LENGTH tuple len)
  533. if(NOT "${len}" EQUAL 3)
  534. message(FATAL_ERROR "Data file referenced by argument\n"
  535. " ${arg}\n"
  536. "corresponds to path\n"
  537. " ${reldata}\n"
  538. "that does not match regular expression\n"
  539. " ${series_parse}")
  540. endif()
  541. list(GET tuple 0 relbase)
  542. list(GET tuple 2 ext)
  543. # Glob files that might match the series.
  544. # Then match base, number, and extension.
  545. _ExternalData_exact_regex(series_base "${relbase}")
  546. _ExternalData_exact_regex(series_ext "${ext}")
  547. _ExternalData_arg_find_files("${relbase}*${ext}"
  548. "${series_base}${series_match}${series_ext}")
  549. endmacro()
  550. function(_ExternalData_arg_find_files pattern regex)
  551. file(GLOB globbed RELATIVE "${top_src}" "${top_src}/${pattern}*")
  552. foreach(entry IN LISTS globbed)
  553. if("x${entry}" MATCHES "^x(.*)(\\.(${_ExternalData_REGEX_EXT}))$")
  554. set(relname "${CMAKE_MATCH_1}")
  555. set(alg "${CMAKE_MATCH_2}")
  556. else()
  557. set(relname "${entry}")
  558. set(alg "")
  559. endif()
  560. if("x${relname}" MATCHES "^x${regex}$" # matches
  561. AND NOT IS_DIRECTORY "${top_src}/${entry}" # not a directory
  562. AND NOT "x${relname}" MATCHES "(^x|/)\\.ExternalData_" # not staged obj
  563. )
  564. set(name "${top_src}/${relname}")
  565. set(file "${top_bin}/${relname}")
  566. if(alg)
  567. list(APPEND external "${file}|${name}|${alg}")
  568. elseif(ExternalData_LINK_CONTENT)
  569. _ExternalData_link_content("${name}" alg)
  570. list(APPEND external "${file}|${name}|${alg}")
  571. elseif(NOT top_same)
  572. list(APPEND internal "${file}|${name}")
  573. endif()
  574. if("${relname}" STREQUAL "${reldata}")
  575. set(have_original 1)
  576. endif()
  577. endif()
  578. endforeach()
  579. set(external "${external}" PARENT_SCOPE)
  580. set(internal "${internal}" PARENT_SCOPE)
  581. set(have_original "${have_original}" PARENT_SCOPE)
  582. endfunction()
  583. #-----------------------------------------------------------------------------
  584. # Private script mode interface
  585. if(CMAKE_GENERATOR OR NOT ExternalData_ACTION)
  586. return()
  587. endif()
  588. if(ExternalData_CONFIG)
  589. include(${ExternalData_CONFIG})
  590. endif()
  591. if(NOT ExternalData_URL_TEMPLATES AND NOT ExternalData_OBJECT_STORES)
  592. message(FATAL_ERROR
  593. "Neither ExternalData_URL_TEMPLATES nor ExternalData_OBJECT_STORES is set!")
  594. endif()
  595. function(_ExternalData_link_or_copy src dst)
  596. # Create a temporary file first.
  597. get_filename_component(dst_dir "${dst}" PATH)
  598. file(MAKE_DIRECTORY "${dst_dir}")
  599. _ExternalData_random(random)
  600. set(tmp "${dst}.tmp${random}")
  601. if(UNIX)
  602. # Create a symbolic link.
  603. set(tgt "${src}")
  604. if(relative_top)
  605. # Use relative path if files are close enough.
  606. file(RELATIVE_PATH relsrc "${relative_top}" "${src}")
  607. file(RELATIVE_PATH relfile "${relative_top}" "${dst}")
  608. if(NOT IS_ABSOLUTE "${relsrc}" AND NOT "${relsrc}" MATCHES "^\\.\\./" AND
  609. NOT IS_ABSOLUTE "${reldst}" AND NOT "${reldst}" MATCHES "^\\.\\./")
  610. file(RELATIVE_PATH tgt "${dst_dir}" "${src}")
  611. endif()
  612. endif()
  613. execute_process(COMMAND "${CMAKE_COMMAND}" -E create_symlink "${tgt}" "${tmp}" RESULT_VARIABLE result)
  614. else()
  615. # Create a copy.
  616. execute_process(COMMAND "${CMAKE_COMMAND}" -E copy "${src}" "${tmp}" RESULT_VARIABLE result)
  617. endif()
  618. if(result)
  619. file(REMOVE "${tmp}")
  620. message(FATAL_ERROR "Failed to create\n ${tmp}\nfrom\n ${obj}")
  621. endif()
  622. # Atomically create/replace the real destination.
  623. file(RENAME "${tmp}" "${dst}")
  624. endfunction()
  625. function(_ExternalData_download_file url file err_var msg_var)
  626. set(retry 3)
  627. while(retry)
  628. math(EXPR retry "${retry} - 1")
  629. if(ExternalData_TIMEOUT_INACTIVITY)
  630. set(inactivity_timeout INACTIVITY_TIMEOUT ${ExternalData_TIMEOUT_INACTIVITY})
  631. elseif(NOT "${ExternalData_TIMEOUT_INACTIVITY}" EQUAL 0)
  632. set(inactivity_timeout INACTIVITY_TIMEOUT 60)
  633. else()
  634. set(inactivity_timeout "")
  635. endif()
  636. if(ExternalData_TIMEOUT_ABSOLUTE)
  637. set(absolute_timeout TIMEOUT ${ExternalData_TIMEOUT_ABSOLUTE})
  638. elseif(NOT "${ExternalData_TIMEOUT_ABSOLUTE}" EQUAL 0)
  639. set(absolute_timeout TIMEOUT 300)
  640. else()
  641. set(absolute_timeout "")
  642. endif()
  643. file(DOWNLOAD "${url}" "${file}" STATUS status LOG log ${inactivity_timeout} ${absolute_timeout} SHOW_PROGRESS)
  644. list(GET status 0 err)
  645. list(GET status 1 msg)
  646. if(err)
  647. if("${msg}" MATCHES "HTTP response code said error" AND
  648. "${log}" MATCHES "error: 503")
  649. set(msg "temporarily unavailable")
  650. endif()
  651. elseif("${log}" MATCHES "\nHTTP[^\n]* 503")
  652. set(err TRUE)
  653. set(msg "temporarily unavailable")
  654. endif()
  655. if(NOT err OR NOT "${msg}" MATCHES "partial|timeout|temporarily")
  656. break()
  657. elseif(retry)
  658. message(STATUS "[download terminated: ${msg}, retries left: ${retry}]")
  659. endif()
  660. endwhile()
  661. set("${err_var}" "${err}" PARENT_SCOPE)
  662. set("${msg_var}" "${msg}" PARENT_SCOPE)
  663. endfunction()
  664. function(_ExternalData_download_object name hash algo var_obj)
  665. # Search all object stores for an existing object.
  666. foreach(dir ${ExternalData_OBJECT_STORES})
  667. set(obj "${dir}/${algo}/${hash}")
  668. if(EXISTS "${obj}")
  669. message(STATUS "Found object: \"${obj}\"")
  670. set("${var_obj}" "${obj}" PARENT_SCOPE)
  671. return()
  672. endif()
  673. endforeach()
  674. # Download object to the first store.
  675. list(GET ExternalData_OBJECT_STORES 0 store)
  676. set(obj "${store}/${algo}/${hash}")
  677. _ExternalData_random(random)
  678. set(tmp "${obj}.tmp${random}")
  679. set(found 0)
  680. set(tried "")
  681. foreach(url_template IN LISTS ExternalData_URL_TEMPLATES)
  682. string(REPLACE "%(hash)" "${hash}" url_tmp "${url_template}")
  683. string(REPLACE "%(algo)" "${algo}" url "${url_tmp}")
  684. message(STATUS "Fetching \"${url}\"")
  685. _ExternalData_download_file("${url}" "${tmp}" err errMsg)
  686. set(tried "${tried}\n ${url}")
  687. if(err)
  688. set(tried "${tried} (${errMsg})")
  689. else()
  690. # Verify downloaded object.
  691. _ExternalData_compute_hash(dl_hash "${algo}" "${tmp}")
  692. if("${dl_hash}" STREQUAL "${hash}")
  693. set(found 1)
  694. break()
  695. else()
  696. set(tried "${tried} (wrong hash ${algo}=${dl_hash})")
  697. if("$ENV{ExternalData_DEBUG_DOWNLOAD}" MATCHES ".")
  698. file(RENAME "${tmp}" "${store}/${algo}/${dl_hash}")
  699. endif()
  700. endif()
  701. endif()
  702. file(REMOVE "${tmp}")
  703. endforeach()
  704. get_filename_component(dir "${name}" PATH)
  705. set(staged "${dir}/.ExternalData_${algo}_${hash}")
  706. if(found)
  707. file(RENAME "${tmp}" "${obj}")
  708. message(STATUS "Downloaded object: \"${obj}\"")
  709. elseif(EXISTS "${staged}")
  710. set(obj "${staged}")
  711. message(STATUS "Staged object: \"${obj}\"")
  712. else()
  713. if(NOT tried)
  714. set(tried "\n (No ExternalData_URL_TEMPLATES given)")
  715. endif()
  716. message(FATAL_ERROR "Object ${algo}=${hash} not found at:${tried}")
  717. endif()
  718. set("${var_obj}" "${obj}" PARENT_SCOPE)
  719. endfunction()
  720. if("${ExternalData_ACTION}" STREQUAL "fetch")
  721. foreach(v ExternalData_OBJECT_STORES file name ext)
  722. if(NOT DEFINED "${v}")
  723. message(FATAL_ERROR "No \"-D${v}=\" value provided!")
  724. endif()
  725. endforeach()
  726. file(READ "${name}${ext}" hash)
  727. string(STRIP "${hash}" hash)
  728. if("${ext}" MATCHES "^\\.(${_ExternalData_REGEX_EXT})$")
  729. string(TOUPPER "${CMAKE_MATCH_1}" algo)
  730. else()
  731. message(FATAL_ERROR "Unknown hash algorithm extension \"${ext}\"")
  732. endif()
  733. _ExternalData_download_object("${name}" "${hash}" "${algo}" obj)
  734. # Check if file already corresponds to the object.
  735. set(stamp "${ext}-stamp")
  736. set(file_up_to_date 0)
  737. if(EXISTS "${file}" AND EXISTS "${file}${stamp}")
  738. file(READ "${file}${stamp}" f_hash)
  739. string(STRIP "${f_hash}" f_hash)
  740. if("${f_hash}" STREQUAL "${hash}")
  741. #message(STATUS "File already corresponds to object")
  742. set(file_up_to_date 1)
  743. endif()
  744. endif()
  745. if(file_up_to_date)
  746. # Touch the file to convince the build system it is up to date.
  747. execute_process(COMMAND "${CMAKE_COMMAND}" -E touch "${file}")
  748. else()
  749. _ExternalData_link_or_copy("${obj}" "${file}")
  750. endif()
  751. # Atomically update the hash/timestamp file to record the object referenced.
  752. _ExternalData_atomic_write("${file}${stamp}" "${hash}\n")
  753. elseif("${ExternalData_ACTION}" STREQUAL "local")
  754. foreach(v file name)
  755. if(NOT DEFINED "${v}")
  756. message(FATAL_ERROR "No \"-D${v}=\" value provided!")
  757. endif()
  758. endforeach()
  759. _ExternalData_link_or_copy("${name}" "${file}")
  760. else()
  761. message(FATAL_ERROR "Unknown ExternalData_ACTION=[${ExternalData_ACTION}]")
  762. endif()