ExternalData.cmake 30 KB

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