1
0

ExternalData.cmake 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  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.
  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. _ExternalData_arg("${target}" "${piece}" "${CMAKE_MATCH_1}" file)
  287. set(outArg "${outArg}${file}")
  288. else()
  289. # No replacement needed for this piece.
  290. set(outArg "${outArg}${piece}")
  291. endif()
  292. endforeach()
  293. else()
  294. # No replacements needed in this argument.
  295. set(outArg "${arg}")
  296. endif()
  297. # Re-escape in-value semicolons in resulting list.
  298. string(REPLACE ";" "\\;" outArg "${outArg}")
  299. list(APPEND outArgs "${outArg}")
  300. endforeach()
  301. set("${outArgsVar}" "${outArgs}" PARENT_SCOPE)
  302. endfunction()
  303. #-----------------------------------------------------------------------------
  304. # Private helper interface
  305. set(_ExternalData_REGEX_ALGO "MD5|SHA1|SHA224|SHA256|SHA384|SHA512")
  306. set(_ExternalData_REGEX_EXT "md5|sha1|sha224|sha256|sha384|sha512")
  307. set(_ExternalData_SELF "${CMAKE_CURRENT_LIST_FILE}")
  308. get_filename_component(_ExternalData_SELF_DIR "${_ExternalData_SELF}" PATH)
  309. function(_ExternalData_compute_hash var_hash algo file)
  310. if("${algo}" MATCHES "^${_ExternalData_REGEX_ALGO}$")
  311. file("${algo}" "${file}" hash)
  312. set("${var_hash}" "${hash}" PARENT_SCOPE)
  313. else()
  314. message(FATAL_ERROR "Hash algorithm ${algo} unimplemented.")
  315. endif()
  316. endfunction()
  317. function(_ExternalData_random var)
  318. string(RANDOM LENGTH 6 random)
  319. set("${var}" "${random}" PARENT_SCOPE)
  320. endfunction()
  321. function(_ExternalData_exact_regex regex_var string)
  322. string(REGEX REPLACE "([][+.*()^])" "\\\\\\1" regex "${string}")
  323. set("${regex_var}" "${regex}" PARENT_SCOPE)
  324. endfunction()
  325. function(_ExternalData_atomic_write file content)
  326. _ExternalData_random(random)
  327. set(tmp "${file}.tmp${random}")
  328. file(WRITE "${tmp}" "${content}")
  329. file(RENAME "${tmp}" "${file}")
  330. endfunction()
  331. function(_ExternalData_link_content name var_ext)
  332. if("${ExternalData_LINK_CONTENT}" MATCHES "^(${_ExternalData_REGEX_ALGO})$")
  333. set(algo "${ExternalData_LINK_CONTENT}")
  334. else()
  335. message(FATAL_ERROR
  336. "Unknown hash algorithm specified by ExternalData_LINK_CONTENT:\n"
  337. " ${ExternalData_LINK_CONTENT}")
  338. endif()
  339. _ExternalData_compute_hash(hash "${algo}" "${name}")
  340. get_filename_component(dir "${name}" PATH)
  341. set(staged "${dir}/.ExternalData_${algo}_${hash}")
  342. string(TOLOWER ".${algo}" ext)
  343. _ExternalData_atomic_write("${name}${ext}" "${hash}\n")
  344. file(RENAME "${name}" "${staged}")
  345. set("${var_ext}" "${ext}" PARENT_SCOPE)
  346. file(RELATIVE_PATH relname "${ExternalData_SOURCE_ROOT}" "${name}${ext}")
  347. message(STATUS "Linked ${relname} to ExternalData ${algo}/${hash}")
  348. endfunction()
  349. function(_ExternalData_arg target arg options var_file)
  350. # Separate data path from the options.
  351. string(REPLACE "," ";" options "${options}")
  352. list(GET options 0 data)
  353. list(REMOVE_AT options 0)
  354. # Interpret trailing slashes as directories.
  355. set(data_is_directory 0)
  356. if("x${data}" MATCHES "^x(.*)([/\\])$")
  357. set(data_is_directory 1)
  358. set(data "${CMAKE_MATCH_1}")
  359. endif()
  360. # Convert to full path.
  361. if(IS_ABSOLUTE "${data}")
  362. set(absdata "${data}")
  363. else()
  364. set(absdata "${CMAKE_CURRENT_SOURCE_DIR}/${data}")
  365. endif()
  366. get_filename_component(absdata "${absdata}" ABSOLUTE)
  367. # Convert to relative path under the source tree.
  368. if(NOT ExternalData_SOURCE_ROOT)
  369. set(ExternalData_SOURCE_ROOT "${CMAKE_SOURCE_DIR}")
  370. endif()
  371. set(top_src "${ExternalData_SOURCE_ROOT}")
  372. file(RELATIVE_PATH reldata "${top_src}" "${absdata}")
  373. if(IS_ABSOLUTE "${reldata}" OR "${reldata}" MATCHES "^\\.\\./")
  374. message(FATAL_ERROR "Data file referenced by argument\n"
  375. " ${arg}\n"
  376. "does not lie under the top-level source directory\n"
  377. " ${top_src}\n")
  378. endif()
  379. if(data_is_directory AND NOT IS_DIRECTORY "${top_src}/${reldata}")
  380. message(FATAL_ERROR "Data directory referenced by argument\n"
  381. " ${arg}\n"
  382. "corresponds to source tree path\n"
  383. " ${reldata}\n"
  384. "that does not exist as a directory!")
  385. endif()
  386. if(NOT ExternalData_BINARY_ROOT)
  387. set(ExternalData_BINARY_ROOT "${CMAKE_BINARY_DIR}")
  388. endif()
  389. set(top_bin "${ExternalData_BINARY_ROOT}")
  390. # Handle in-source builds gracefully.
  391. if("${top_src}" STREQUAL "${top_bin}")
  392. if(ExternalData_LINK_CONTENT)
  393. message(WARNING "ExternalData_LINK_CONTENT cannot be used in-source")
  394. set(ExternalData_LINK_CONTENT 0)
  395. endif()
  396. set(top_same 1)
  397. endif()
  398. set(external "") # Entries external to the source tree.
  399. set(internal "") # Entries internal to the source tree.
  400. set(have_original ${data_is_directory})
  401. set(have_original_as_dir 0)
  402. # Process options.
  403. set(series_option "")
  404. set(associated_files "")
  405. set(associated_regex "")
  406. foreach(opt ${options})
  407. # Regular expression to match associated files.
  408. if("x${opt}" MATCHES "^xREGEX:([^:/]+)$")
  409. list(APPEND associated_regex "${CMAKE_MATCH_1}")
  410. elseif(opt STREQUAL ":")
  411. # Activate series matching.
  412. set(series_option "${opt}")
  413. elseif("x${opt}" MATCHES "^[^][:/*?]+$")
  414. # Specific associated file.
  415. list(APPEND associated_files "${opt}")
  416. else()
  417. message(FATAL_ERROR "Unknown option \"${opt}\" in argument\n"
  418. " ${arg}\n")
  419. endif()
  420. endforeach()
  421. if(series_option)
  422. if(data_is_directory)
  423. message(FATAL_ERROR "Series option \"${series_option}\" not allowed with directories.")
  424. endif()
  425. if(associated_files OR associated_regex)
  426. message(FATAL_ERROR "Series option \"${series_option}\" not allowed with associated files.")
  427. endif()
  428. # Load a whole file series.
  429. _ExternalData_arg_series()
  430. elseif(data_is_directory)
  431. if(associated_files OR associated_regex)
  432. # Load listed/matching associated files in the directory.
  433. _ExternalData_arg_associated()
  434. else()
  435. message(FATAL_ERROR "Data directory referenced by argument\n"
  436. " ${arg}\n"
  437. "must list associated files.")
  438. endif()
  439. else()
  440. # Load the named data file.
  441. _ExternalData_arg_single()
  442. if(associated_files OR associated_regex)
  443. # Load listed/matching associated files.
  444. _ExternalData_arg_associated()
  445. endif()
  446. endif()
  447. if(NOT have_original)
  448. if(have_original_as_dir)
  449. set(msg_kind FATAL_ERROR)
  450. set(msg "that is directory instead of a file!")
  451. else()
  452. set(msg_kind AUTHOR_WARNING)
  453. set(msg "that does not exist as a file (with or without an extension)!")
  454. endif()
  455. message(${msg_kind} "Data file referenced by argument\n"
  456. " ${arg}\n"
  457. "corresponds to source tree path\n"
  458. " ${reldata}\n"
  459. "${msg}")
  460. endif()
  461. if(external)
  462. # Make the series available in the build tree.
  463. set_property(GLOBAL APPEND PROPERTY
  464. _ExternalData_${target}_FETCH "${external}")
  465. set_property(GLOBAL APPEND PROPERTY
  466. _ExternalData_${target}_LOCAL "${internal}")
  467. set("${var_file}" "${top_bin}/${reldata}" PARENT_SCOPE)
  468. else()
  469. # The whole series is in the source tree.
  470. set("${var_file}" "${top_src}/${reldata}" PARENT_SCOPE)
  471. endif()
  472. endfunction()
  473. macro(_ExternalData_arg_associated)
  474. # Associated files lie in the same directory.
  475. if(data_is_directory)
  476. set(reldir "${reldata}")
  477. else()
  478. get_filename_component(reldir "${reldata}" PATH)
  479. endif()
  480. if(reldir)
  481. set(reldir "${reldir}/")
  482. endif()
  483. _ExternalData_exact_regex(reldir_regex "${reldir}")
  484. # Find files named explicitly.
  485. foreach(file ${associated_files})
  486. _ExternalData_exact_regex(file_regex "${file}")
  487. _ExternalData_arg_find_files("${reldir}${file}" "${reldir_regex}${file_regex}")
  488. endforeach()
  489. # Find files matching the given regular expressions.
  490. set(all "")
  491. set(sep "")
  492. foreach(regex ${associated_regex})
  493. set(all "${all}${sep}${reldir_regex}${regex}")
  494. set(sep "|")
  495. endforeach()
  496. _ExternalData_arg_find_files("${reldir}" "${all}")
  497. endmacro()
  498. macro(_ExternalData_arg_single)
  499. # Match only the named data by itself.
  500. _ExternalData_exact_regex(data_regex "${reldata}")
  501. _ExternalData_arg_find_files("${reldata}" "${data_regex}")
  502. endmacro()
  503. macro(_ExternalData_arg_series)
  504. # Configure series parsing and matching.
  505. set(series_parse_prefix "")
  506. set(series_parse_number "\\1")
  507. set(series_parse_suffix "\\2")
  508. if(ExternalData_SERIES_PARSE)
  509. if(ExternalData_SERIES_PARSE_NUMBER AND ExternalData_SERIES_PARSE_SUFFIX)
  510. if(ExternalData_SERIES_PARSE_PREFIX)
  511. set(series_parse_prefix "\\${ExternalData_SERIES_PARSE_PREFIX}")
  512. endif()
  513. set(series_parse_number "\\${ExternalData_SERIES_PARSE_NUMBER}")
  514. set(series_parse_suffix "\\${ExternalData_SERIES_PARSE_SUFFIX}")
  515. elseif(NOT "x${ExternalData_SERIES_PARSE}" MATCHES "^x\\([^()]*\\)\\([^()]*\\)\\$$")
  516. message(FATAL_ERROR
  517. "ExternalData_SERIES_PARSE is set to\n"
  518. " ${ExternalData_SERIES_PARSE}\n"
  519. "which is not of the form\n"
  520. " (<number>)(<suffix>)$\n"
  521. "Fix the regular expression or set variables\n"
  522. " ExternalData_SERIES_PARSE_PREFIX = <prefix> regex group number, if any\n"
  523. " ExternalData_SERIES_PARSE_NUMBER = <number> regex group number\n"
  524. " ExternalData_SERIES_PARSE_SUFFIX = <suffix> regex group number\n"
  525. )
  526. endif()
  527. set(series_parse "${ExternalData_SERIES_PARSE}")
  528. else()
  529. set(series_parse "([0-9]*)(\\.[^./]*)$")
  530. endif()
  531. if(ExternalData_SERIES_MATCH)
  532. set(series_match "${ExternalData_SERIES_MATCH}")
  533. else()
  534. set(series_match "[_.-]?[0-9]*")
  535. endif()
  536. # Parse the base, number, and extension components of the series.
  537. string(REGEX REPLACE "${series_parse}" "${series_parse_prefix};${series_parse_number};${series_parse_suffix}" tuple "${reldata}")
  538. list(LENGTH tuple len)
  539. if(NOT "${len}" EQUAL 3)
  540. message(FATAL_ERROR "Data file referenced by argument\n"
  541. " ${arg}\n"
  542. "corresponds to path\n"
  543. " ${reldata}\n"
  544. "that does not match regular expression\n"
  545. " ${series_parse}")
  546. endif()
  547. list(GET tuple 0 relbase)
  548. list(GET tuple 2 ext)
  549. # Glob files that might match the series.
  550. # Then match base, number, and extension.
  551. _ExternalData_exact_regex(series_base "${relbase}")
  552. _ExternalData_exact_regex(series_ext "${ext}")
  553. _ExternalData_arg_find_files("${relbase}*${ext}"
  554. "${series_base}${series_match}${series_ext}")
  555. endmacro()
  556. function(_ExternalData_arg_find_files pattern regex)
  557. file(GLOB globbed RELATIVE "${top_src}" "${top_src}/${pattern}*")
  558. foreach(entry IN LISTS globbed)
  559. if("x${entry}" MATCHES "^x(.*)(\\.(${_ExternalData_REGEX_EXT}))$")
  560. set(relname "${CMAKE_MATCH_1}")
  561. set(alg "${CMAKE_MATCH_2}")
  562. else()
  563. set(relname "${entry}")
  564. set(alg "")
  565. endif()
  566. if("x${relname}" MATCHES "^x${regex}$" # matches
  567. AND NOT "x${relname}" MATCHES "(^x|/)\\.ExternalData_" # not staged obj
  568. )
  569. if(IS_DIRECTORY "${top_src}/${entry}")
  570. if("${relname}" STREQUAL "${reldata}")
  571. set(have_original_as_dir 1)
  572. endif()
  573. else()
  574. set(name "${top_src}/${relname}")
  575. set(file "${top_bin}/${relname}")
  576. if(alg)
  577. list(APPEND external "${file}|${name}|${alg}")
  578. elseif(ExternalData_LINK_CONTENT)
  579. _ExternalData_link_content("${name}" alg)
  580. list(APPEND external "${file}|${name}|${alg}")
  581. elseif(NOT top_same)
  582. list(APPEND internal "${file}|${name}")
  583. endif()
  584. if("${relname}" STREQUAL "${reldata}")
  585. set(have_original 1)
  586. endif()
  587. endif()
  588. endif()
  589. endforeach()
  590. set(external "${external}" PARENT_SCOPE)
  591. set(internal "${internal}" PARENT_SCOPE)
  592. set(have_original "${have_original}" PARENT_SCOPE)
  593. set(have_original_as_dir "${have_original_as_dir}" PARENT_SCOPE)
  594. endfunction()
  595. #-----------------------------------------------------------------------------
  596. # Private script mode interface
  597. if(CMAKE_GENERATOR OR NOT ExternalData_ACTION)
  598. return()
  599. endif()
  600. if(ExternalData_CONFIG)
  601. include(${ExternalData_CONFIG})
  602. endif()
  603. if(NOT ExternalData_URL_TEMPLATES AND NOT ExternalData_OBJECT_STORES)
  604. message(FATAL_ERROR
  605. "Neither ExternalData_URL_TEMPLATES nor ExternalData_OBJECT_STORES is set!")
  606. endif()
  607. function(_ExternalData_link_or_copy src dst)
  608. # Create a temporary file first.
  609. get_filename_component(dst_dir "${dst}" PATH)
  610. file(MAKE_DIRECTORY "${dst_dir}")
  611. _ExternalData_random(random)
  612. set(tmp "${dst}.tmp${random}")
  613. if(UNIX)
  614. # Create a symbolic link.
  615. set(tgt "${src}")
  616. if(relative_top)
  617. # Use relative path if files are close enough.
  618. file(RELATIVE_PATH relsrc "${relative_top}" "${src}")
  619. file(RELATIVE_PATH relfile "${relative_top}" "${dst}")
  620. if(NOT IS_ABSOLUTE "${relsrc}" AND NOT "${relsrc}" MATCHES "^\\.\\./" AND
  621. NOT IS_ABSOLUTE "${reldst}" AND NOT "${reldst}" MATCHES "^\\.\\./")
  622. file(RELATIVE_PATH tgt "${dst_dir}" "${src}")
  623. endif()
  624. endif()
  625. execute_process(COMMAND "${CMAKE_COMMAND}" -E create_symlink "${tgt}" "${tmp}" RESULT_VARIABLE result)
  626. else()
  627. # Create a copy.
  628. execute_process(COMMAND "${CMAKE_COMMAND}" -E copy "${src}" "${tmp}" RESULT_VARIABLE result)
  629. endif()
  630. if(result)
  631. file(REMOVE "${tmp}")
  632. message(FATAL_ERROR "Failed to create\n ${tmp}\nfrom\n ${obj}")
  633. endif()
  634. # Atomically create/replace the real destination.
  635. file(RENAME "${tmp}" "${dst}")
  636. endfunction()
  637. function(_ExternalData_download_file url file err_var msg_var)
  638. set(retry 3)
  639. while(retry)
  640. math(EXPR retry "${retry} - 1")
  641. if(ExternalData_TIMEOUT_INACTIVITY)
  642. set(inactivity_timeout INACTIVITY_TIMEOUT ${ExternalData_TIMEOUT_INACTIVITY})
  643. elseif(NOT "${ExternalData_TIMEOUT_INACTIVITY}" EQUAL 0)
  644. set(inactivity_timeout INACTIVITY_TIMEOUT 60)
  645. else()
  646. set(inactivity_timeout "")
  647. endif()
  648. if(ExternalData_TIMEOUT_ABSOLUTE)
  649. set(absolute_timeout TIMEOUT ${ExternalData_TIMEOUT_ABSOLUTE})
  650. elseif(NOT "${ExternalData_TIMEOUT_ABSOLUTE}" EQUAL 0)
  651. set(absolute_timeout TIMEOUT 300)
  652. else()
  653. set(absolute_timeout "")
  654. endif()
  655. file(DOWNLOAD "${url}" "${file}" STATUS status LOG log ${inactivity_timeout} ${absolute_timeout} SHOW_PROGRESS)
  656. list(GET status 0 err)
  657. list(GET status 1 msg)
  658. if(err)
  659. if("${msg}" MATCHES "HTTP response code said error" AND
  660. "${log}" MATCHES "error: 503")
  661. set(msg "temporarily unavailable")
  662. endif()
  663. elseif("${log}" MATCHES "\nHTTP[^\n]* 503")
  664. set(err TRUE)
  665. set(msg "temporarily unavailable")
  666. endif()
  667. if(NOT err OR NOT "${msg}" MATCHES "partial|timeout|temporarily")
  668. break()
  669. elseif(retry)
  670. message(STATUS "[download terminated: ${msg}, retries left: ${retry}]")
  671. endif()
  672. endwhile()
  673. set("${err_var}" "${err}" PARENT_SCOPE)
  674. set("${msg_var}" "${msg}" PARENT_SCOPE)
  675. endfunction()
  676. function(_ExternalData_download_object name hash algo var_obj)
  677. # Search all object stores for an existing object.
  678. foreach(dir ${ExternalData_OBJECT_STORES})
  679. set(obj "${dir}/${algo}/${hash}")
  680. if(EXISTS "${obj}")
  681. message(STATUS "Found object: \"${obj}\"")
  682. set("${var_obj}" "${obj}" PARENT_SCOPE)
  683. return()
  684. endif()
  685. endforeach()
  686. # Download object to the first store.
  687. list(GET ExternalData_OBJECT_STORES 0 store)
  688. set(obj "${store}/${algo}/${hash}")
  689. _ExternalData_random(random)
  690. set(tmp "${obj}.tmp${random}")
  691. set(found 0)
  692. set(tried "")
  693. foreach(url_template IN LISTS ExternalData_URL_TEMPLATES)
  694. string(REPLACE "%(hash)" "${hash}" url_tmp "${url_template}")
  695. string(REPLACE "%(algo)" "${algo}" url "${url_tmp}")
  696. message(STATUS "Fetching \"${url}\"")
  697. _ExternalData_download_file("${url}" "${tmp}" err errMsg)
  698. set(tried "${tried}\n ${url}")
  699. if(err)
  700. set(tried "${tried} (${errMsg})")
  701. else()
  702. # Verify downloaded object.
  703. _ExternalData_compute_hash(dl_hash "${algo}" "${tmp}")
  704. if("${dl_hash}" STREQUAL "${hash}")
  705. set(found 1)
  706. break()
  707. else()
  708. set(tried "${tried} (wrong hash ${algo}=${dl_hash})")
  709. if("$ENV{ExternalData_DEBUG_DOWNLOAD}" MATCHES ".")
  710. file(RENAME "${tmp}" "${store}/${algo}/${dl_hash}")
  711. endif()
  712. endif()
  713. endif()
  714. file(REMOVE "${tmp}")
  715. endforeach()
  716. get_filename_component(dir "${name}" PATH)
  717. set(staged "${dir}/.ExternalData_${algo}_${hash}")
  718. if(found)
  719. file(RENAME "${tmp}" "${obj}")
  720. message(STATUS "Downloaded object: \"${obj}\"")
  721. elseif(EXISTS "${staged}")
  722. set(obj "${staged}")
  723. message(STATUS "Staged object: \"${obj}\"")
  724. else()
  725. if(NOT tried)
  726. set(tried "\n (No ExternalData_URL_TEMPLATES given)")
  727. endif()
  728. message(FATAL_ERROR "Object ${algo}=${hash} not found at:${tried}")
  729. endif()
  730. set("${var_obj}" "${obj}" PARENT_SCOPE)
  731. endfunction()
  732. if("${ExternalData_ACTION}" STREQUAL "fetch")
  733. foreach(v ExternalData_OBJECT_STORES file name ext)
  734. if(NOT DEFINED "${v}")
  735. message(FATAL_ERROR "No \"-D${v}=\" value provided!")
  736. endif()
  737. endforeach()
  738. file(READ "${name}${ext}" hash)
  739. string(STRIP "${hash}" hash)
  740. if("${ext}" MATCHES "^\\.(${_ExternalData_REGEX_EXT})$")
  741. string(TOUPPER "${CMAKE_MATCH_1}" algo)
  742. else()
  743. message(FATAL_ERROR "Unknown hash algorithm extension \"${ext}\"")
  744. endif()
  745. _ExternalData_download_object("${name}" "${hash}" "${algo}" obj)
  746. # Check if file already corresponds to the object.
  747. set(stamp "${ext}-stamp")
  748. set(file_up_to_date 0)
  749. if(EXISTS "${file}" AND EXISTS "${file}${stamp}")
  750. file(READ "${file}${stamp}" f_hash)
  751. string(STRIP "${f_hash}" f_hash)
  752. if("${f_hash}" STREQUAL "${hash}")
  753. #message(STATUS "File already corresponds to object")
  754. set(file_up_to_date 1)
  755. endif()
  756. endif()
  757. if(file_up_to_date)
  758. # Touch the file to convince the build system it is up to date.
  759. execute_process(COMMAND "${CMAKE_COMMAND}" -E touch "${file}")
  760. else()
  761. _ExternalData_link_or_copy("${obj}" "${file}")
  762. endif()
  763. # Atomically update the hash/timestamp file to record the object referenced.
  764. _ExternalData_atomic_write("${file}${stamp}" "${hash}\n")
  765. elseif("${ExternalData_ACTION}" STREQUAL "local")
  766. foreach(v file name)
  767. if(NOT DEFINED "${v}")
  768. message(FATAL_ERROR "No \"-D${v}=\" value provided!")
  769. endif()
  770. endforeach()
  771. _ExternalData_link_or_copy("${name}" "${file}")
  772. else()
  773. message(FATAL_ERROR "Unknown ExternalData_ACTION=[${ExternalData_ACTION}]")
  774. endif()