AddExternalProject.cmake 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. # Requires CVS CMake for 'function' and '-E touch' and '--build'
  2. find_package(CVS)
  3. find_package(Subversion)
  4. function(_aep_parse_arguments f name ns args)
  5. # Transfer the arguments to this function into target properties for the
  6. # new custom target we just added so that we can set up all the build steps
  7. # correctly based on target properties.
  8. #
  9. # We loop through ARGN and consider the namespace starting with an
  10. # upper-case letter followed by at least two more upper-case letters
  11. # or underscores to be keywords.
  12. set(key)
  13. foreach(arg IN LISTS args)
  14. if(arg MATCHES "^[A-Z][A-Z_][A-Z_]+$" AND
  15. NOT ((arg STREQUAL "${key}") AND (key STREQUAL "COMMAND")) AND
  16. NOT arg MATCHES "^(TRUE|FALSE)$")
  17. # Keyword
  18. set(key "${arg}")
  19. if(_aep_keywords_${f} AND NOT key MATCHES "${_aep_keywords_${f}}")
  20. message(AUTHOR_WARNING "unknown ${f} keyword: ${key}")
  21. endif()
  22. elseif(key)
  23. # Value
  24. if(NOT arg STREQUAL "")
  25. set_property(TARGET ${name} APPEND PROPERTY ${ns}${key} "${arg}")
  26. else()
  27. get_property(have_key TARGET ${name} PROPERTY ${ns}${key} SET)
  28. if(have_key)
  29. get_property(value TARGET ${name} PROPERTY ${ns}${key})
  30. set_property(TARGET ${name} PROPERTY ${ns}${key} "${value};${arg}")
  31. else()
  32. set_property(TARGET ${name} PROPERTY ${ns}${key} "${arg}")
  33. endif()
  34. endif()
  35. else()
  36. # Missing Keyword
  37. message(AUTHOR_WARNING "value with no keyword in ${f}")
  38. endif()
  39. endforeach()
  40. endfunction(_aep_parse_arguments)
  41. function(get_external_project_directories base_dir_var build_dir_var downloads_dir_var install_dir_var sentinels_dir_var source_dir_var tmp_dir_var)
  42. set(base "${CMAKE_BINARY_DIR}/CMakeExternals")
  43. set(${base_dir_var} "${base}" PARENT_SCOPE)
  44. set(${build_dir_var} "${base}/Build" PARENT_SCOPE)
  45. set(${downloads_dir_var} "${base}/Downloads" PARENT_SCOPE)
  46. set(${install_dir_var} "${base}/Install" PARENT_SCOPE)
  47. set(${sentinels_dir_var} "${base}/Sentinels" PARENT_SCOPE)
  48. set(${source_dir_var} "${base}/Source" PARENT_SCOPE)
  49. set(${tmp_dir_var} "${base}/tmp" PARENT_SCOPE)
  50. endfunction(get_external_project_directories)
  51. function(get_configure_build_working_dir name working_dir_var)
  52. get_external_project_directories(base_dir build_dir downloads_dir install_dir
  53. sentinels_dir source_dir tmp_dir)
  54. get_target_property(dir ${name} AEP_CONFIGURE_DIR)
  55. if(dir)
  56. if (IS_ABSOLUTE "${dir}")
  57. set(working_dir "${dir}")
  58. else()
  59. set(working_dir "${source_dir}/${name}/${dir}")
  60. endif()
  61. else()
  62. set(working_dir "${build_dir}/${name}")
  63. endif()
  64. set(${working_dir_var} "${working_dir}" PARENT_SCOPE)
  65. endfunction(get_configure_build_working_dir)
  66. function(get_configure_command_id name cfg_cmd_id_var)
  67. get_target_property(cmd ${name} AEP_CONFIGURE_COMMAND)
  68. if(cmd STREQUAL "")
  69. # Explicit empty string means no configure step for this project
  70. set(${cfg_cmd_id_var} "none" PARENT_SCOPE)
  71. else()
  72. if(NOT cmd)
  73. # Default is "use cmake":
  74. set(${cfg_cmd_id_var} "cmake" PARENT_SCOPE)
  75. else()
  76. # Otherwise we have to analyze the value:
  77. if(cmd MATCHES "/configure$")
  78. set(${cfg_cmd_id_var} "configure" PARENT_SCOPE)
  79. else()
  80. if(cmd MATCHES "cmake")
  81. set(${cfg_cmd_id_var} "cmake" PARENT_SCOPE)
  82. else()
  83. if(cmd MATCHES "config")
  84. set(${cfg_cmd_id_var} "configure" PARENT_SCOPE)
  85. else()
  86. set(${cfg_cmd_id_var} "unknown:${cmd}" PARENT_SCOPE)
  87. endif()
  88. endif()
  89. endif()
  90. endif()
  91. endif()
  92. endfunction(get_configure_command_id)
  93. function(_aep_get_build_command name step cmd_var)
  94. set(cmd "${${cmd_var}}")
  95. if(NOT cmd)
  96. set(args)
  97. get_configure_command_id(${name} cfg_cmd_id)
  98. if(cfg_cmd_id STREQUAL "cmake")
  99. # CMake project. Select build command based on generator.
  100. get_target_property(cmake_generator ${name} AEP_CMAKE_GENERATOR)
  101. if("${cmake_generator}" MATCHES "Make" AND
  102. "${cmake_generator}" STREQUAL "${CMAKE_GENERATOR}")
  103. # The project uses the same Makefile generator. Use recursive make.
  104. set(cmd "$(MAKE)")
  105. if(step STREQUAL "INSTALL")
  106. set(args install)
  107. endif()
  108. else()
  109. # Drive the project with "cmake --build".
  110. get_target_property(cmake_command ${name} AEP_CMAKE_COMMAND)
  111. if(cmake_command)
  112. set(cmd "${cmake_command}")
  113. else()
  114. set(cmd "${CMAKE_COMMAND}")
  115. endif()
  116. set(args --build ${working_dir} --config ${CMAKE_CFG_INTDIR})
  117. if(step STREQUAL "INSTALL")
  118. list(APPEND args --target install)
  119. endif()
  120. endif()
  121. else() # if(cfg_cmd_id STREQUAL "configure")
  122. # Non-CMake project. Guess "make" and "make install".
  123. set(cmd "make")
  124. if(step STREQUAL "INSTALL")
  125. set(args install)
  126. endif()
  127. endif()
  128. # Use user-specified arguments instead of default arguments, if any.
  129. get_property(have_args TARGET ${name} PROPERTY AEP_${step}_ARGS SET)
  130. if(have_args)
  131. get_target_property(args ${name} AEP_${step}_ARGS)
  132. endif()
  133. list(APPEND cmd ${args})
  134. endif()
  135. set(${cmd_var} "${cmd}" PARENT_SCOPE)
  136. endfunction(_aep_get_build_command)
  137. function(mkdir d)
  138. file(MAKE_DIRECTORY "${d}")
  139. #message(STATUS "mkdir d='${d}'")
  140. if(NOT EXISTS "${d}")
  141. message(FATAL_ERROR "error: dir '${d}' does not exist after file(MAKE_DIRECTORY call...")
  142. endif()
  143. endfunction(mkdir)
  144. # Pre-compute a regex to match known keywords.
  145. set(_aep_keyword_regex "^(")
  146. set(_aep_keyword_sep)
  147. foreach(key IN ITEMS
  148. COMMAND
  149. COMMENT
  150. DEPENDEES
  151. DEPENDERS
  152. DEPENDS
  153. SYMBOLIC
  154. WORKING_DIRECTORY
  155. )
  156. set(_aep_keyword_regex "${_aep_keyword_regex}${_aep_keyword_sep}${key}")
  157. set(_aep_keyword_sep "|")
  158. endforeach(key)
  159. set(_aep_keyword_regex "${_aep_keyword_regex})$")
  160. set(_aep_keyword_sep)
  161. set(_aep_keywords_add_external_project_step "${_aep_keyword_regex}")
  162. function(add_external_project_step name step)
  163. get_external_project_directories(base_dir build_dir downloads_dir install_dir
  164. sentinels_dir source_dir tmp_dir)
  165. add_custom_command(APPEND
  166. OUTPUT ${sentinels_dir}/${name}-complete
  167. DEPENDS ${sentinels_dir}/${name}-${step}
  168. )
  169. _aep_parse_arguments(add_external_project_step
  170. ${name} AEP_${step}_ "${ARGN}")
  171. # Steps depending on this step.
  172. get_property(dependers TARGET ${name} PROPERTY AEP_${step}_DEPENDERS)
  173. foreach(depender IN LISTS dependers)
  174. add_custom_command(APPEND
  175. OUTPUT ${sentinels_dir}/${name}-${depender}
  176. DEPENDS ${sentinels_dir}/${name}-${step}
  177. )
  178. endforeach()
  179. # Dependencies on files.
  180. get_property(depends TARGET ${name} PROPERTY AEP_${step}_DEPENDS)
  181. # Dependencies on steps.
  182. get_property(dependees TARGET ${name} PROPERTY AEP_${step}_DEPENDEES)
  183. foreach(dependee IN LISTS dependees)
  184. list(APPEND depends ${sentinels_dir}/${name}-${dependee})
  185. endforeach()
  186. # The command to run.
  187. get_property(command TARGET ${name} PROPERTY AEP_${step}_COMMAND)
  188. if(command)
  189. set(comment "Performing ${step} step for '${name}'")
  190. else()
  191. set(comment "No ${step} step for '${name}'")
  192. endif()
  193. get_property(work_dir TARGET ${name} PROPERTY AEP_${step}_WORKING_DIRECTORY)
  194. # Custom comment?
  195. get_property(comment_set TARGET ${name} PROPERTY AEP_${step}_COMMENT SET)
  196. if(comment_set)
  197. get_property(comment TARGET ${name} PROPERTY AEP_${step}_COMMENT)
  198. endif()
  199. # Run every time?
  200. get_property(symbolic TARGET ${name} PROPERTY AEP_${step}_SYMBOLIC)
  201. if(symbolic)
  202. set_property(SOURCE ${sentinels_dir}/${name}-${step} PROPERTY SYMBOLIC 1)
  203. set(touch)
  204. else()
  205. set(touch ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-${step})
  206. endif()
  207. add_custom_command(
  208. OUTPUT ${sentinels_dir}/${name}-${step}
  209. COMMENT ${comment}
  210. COMMAND ${command}
  211. COMMAND ${touch}
  212. DEPENDS ${depends}
  213. WORKING_DIRECTORY ${work_dir}
  214. VERBATIM
  215. )
  216. endfunction(add_external_project_step)
  217. function(add_external_project_download_command name)
  218. get_external_project_directories(base_dir build_dir downloads_dir install_dir
  219. sentinels_dir source_dir tmp_dir)
  220. get_property(cmd_set TARGET ${name} PROPERTY AEP_DOWNLOAD_COMMAND SET)
  221. get_property(cmd TARGET ${name} PROPERTY AEP_DOWNLOAD_COMMAND)
  222. get_property(cvs_repository TARGET ${name} PROPERTY AEP_CVS_REPOSITORY)
  223. get_property(svn_repository TARGET ${name} PROPERTY AEP_SVN_REPOSITORY)
  224. get_property(dir TARGET ${name} PROPERTY AEP_DIR)
  225. get_property(tar TARGET ${name} PROPERTY AEP_TAR)
  226. get_property(tgz TARGET ${name} PROPERTY AEP_TGZ)
  227. get_property(tgz_url TARGET ${name} PROPERTY AEP_TGZ_URL)
  228. get_property(tar_url TARGET ${name} PROPERTY AEP_TAR_URL)
  229. set(depends ${sentinels_dir}/CMakeExternals-directories)
  230. set(comment)
  231. set(work_dir)
  232. if(cmd_set)
  233. set(work_dir ${downloads_dir})
  234. elseif(cvs_repository)
  235. if(NOT CVS_EXECUTABLE)
  236. message(FATAL_ERROR "error: could not find cvs for checkout of ${name}")
  237. endif()
  238. get_target_property(cvs_module ${name} AEP_CVS_MODULE)
  239. if(NOT cvs_module)
  240. message(FATAL_ERROR "error: no CVS_MODULE")
  241. endif()
  242. get_property(cvs_tag TARGET ${name} PROPERTY AEP_CVS_TAG)
  243. set(repository ${cvs_repository})
  244. set(module ${cvs_module})
  245. set(tag ${cvs_tag})
  246. configure_file(
  247. "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
  248. "${sentinels_dir}/${name}-cvsinfo.txt"
  249. @ONLY
  250. )
  251. mkdir("${source_dir}/${name}")
  252. set(work_dir ${source_dir})
  253. set(comment "Performing download step (CVS checkout) for '${name}'")
  254. set(cmd ${CVS_EXECUTABLE} -d ${cvs_repository} -q co ${cvs_tag} -d ${name} ${cvs_module})
  255. list(APPEND depends ${sentinels_dir}/${name}-cvsinfo.txt)
  256. elseif(svn_repository)
  257. if(NOT Subversion_SVN_EXECUTABLE)
  258. message(FATAL_ERROR "error: could not find svn for checkout of ${name}")
  259. endif()
  260. get_property(svn_tag TARGET ${name} PROPERTY AEP_SVN_TAG)
  261. set(repository ${svn_repository})
  262. set(module)
  263. set(tag ${svn_tag})
  264. configure_file(
  265. "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
  266. "${sentinels_dir}/${name}-svninfo.txt"
  267. @ONLY
  268. )
  269. mkdir("${source_dir}/${name}")
  270. set(work_dir ${source_dir})
  271. set(comment "Performing download step (SVN checkout) for '${name}'")
  272. set(cmd ${Subversion_SVN_EXECUTABLE} co ${svn_repository} ${svn_tag} ${name})
  273. list(APPEND depends ${sentinels_dir}/${name}-svninfo.txt)
  274. elseif(dir)
  275. get_filename_component(abs_dir "${dir}" ABSOLUTE)
  276. set(repository "add_external_project DIR")
  277. set(module "${abs_dir}")
  278. set(tag "")
  279. configure_file(
  280. "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
  281. "${sentinels_dir}/${name}-dirinfo.txt"
  282. @ONLY
  283. )
  284. mkdir("${source_dir}/${name}")
  285. set(work_dir ${source_dir})
  286. set(comment "Performing download step (DIR copy) for '${name}'")
  287. set(cmd ${CMAKE_COMMAND} -E remove_directory ${source_dir}/${name}
  288. COMMAND ${CMAKE_COMMAND} -E copy_directory ${abs_dir} ${source_dir}/${name})
  289. list(APPEND depends ${sentinels_dir}/${name}-dirinfo.txt)
  290. elseif(tar)
  291. mkdir("${source_dir}/${name}")
  292. set(work_dir ${source_dir})
  293. set(comment "Performing download step (TAR untar) for '${name}'")
  294. set(cmd ${CMAKE_COMMAND} -Dfilename=${tar} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
  295. list(APPEND depends ${tar})
  296. elseif(tgz)
  297. mkdir("${source_dir}/${name}")
  298. set(work_dir ${source_dir})
  299. set(comment "Performing download step (TGZ untar) for '${name}'")
  300. set(cmd ${CMAKE_COMMAND} -Dfilename=${tgz} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
  301. list(APPEND depends ${tgz})
  302. elseif(tgz_url)
  303. set(repository "add_external_project TGZ_URL")
  304. set(module "${tgz_url}")
  305. set(tag "")
  306. configure_file(
  307. "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
  308. "${sentinels_dir}/${name}-urlinfo.txt"
  309. @ONLY
  310. )
  311. mkdir("${source_dir}/${name}")
  312. set(work_dir ${source_dir})
  313. set(comment "Performing download step (TGZ_URL download and untar) for '${name}'")
  314. set(cmd ${CMAKE_COMMAND} -Dremote=${tgz_url} -Dlocal=${downloads_dir}/${name}.tgz -P ${CMAKE_ROOT}/Modules/DownloadFile.cmake
  315. COMMAND ${CMAKE_COMMAND} -Dfilename=${downloads_dir}/${name} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
  316. list(APPEND depends ${sentinels_dir}/${name}-urlinfo.txt)
  317. elseif(tar_url)
  318. set(repository "add_external_project TAR_URL")
  319. set(module "${tar_url}")
  320. set(tag "")
  321. configure_file(
  322. "${CMAKE_ROOT}/Modules/RepositoryInfo.txt.in"
  323. "${sentinels_dir}/${name}-urlinfo.txt"
  324. @ONLY
  325. )
  326. mkdir("${source_dir}/${name}")
  327. set(work_dir ${source_dir})
  328. set(comment "Performing download step (TAR_URL download and untar) for '${name}'")
  329. set(cmd ${CMAKE_COMMAND} -Dremote=${tar_url} -Dlocal=${downloads_dir}/${name}.tar -P ${CMAKE_ROOT}/Modules/DownloadFile.cmake
  330. COMMAND ${CMAKE_COMMAND} -Dfilename=${downloads_dir}/${name} -Dtmp=${tmp_dir}/${name} -Ddirectory=${source_dir}/${name} -P ${CMAKE_ROOT}/Modules/UntarFile.cmake)
  331. list(APPEND depends ${sentinels_dir}/${name}-urlinfo.txt)
  332. else()
  333. message(SEND_ERROR "error: no download info for '${name}'")
  334. endif()
  335. add_external_project_step(${name} download
  336. COMMENT ${comment}
  337. COMMAND ${cmd}
  338. WORKING_DIRECTORY ${source_dir}
  339. DEPENDS ${depends}
  340. )
  341. endfunction(add_external_project_download_command)
  342. function(add_external_project_update_command name)
  343. get_external_project_directories(base_dir build_dir downloads_dir install_dir
  344. sentinels_dir source_dir tmp_dir)
  345. get_target_property(cmd ${name} AEP_UPDATE_COMMAND)
  346. if(cmd STREQUAL "")
  347. # Explicit empty string means no update step for this project
  348. add_custom_command(
  349. OUTPUT ${sentinels_dir}/${name}-update
  350. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-update
  351. WORKING_DIRECTORY ${sentinels_dir}
  352. COMMENT "No update step for '${name}'"
  353. DEPENDS ${sentinels_dir}/${name}-download
  354. )
  355. return()
  356. else()
  357. if(cmd)
  358. add_custom_command(
  359. OUTPUT ${sentinels_dir}/${name}-update
  360. COMMAND ${cmd}
  361. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-update
  362. WORKING_DIRECTORY ${source_dir}/${name}
  363. COMMENT "Performing update step for '${name}'"
  364. DEPENDS ${sentinels_dir}/${name}-download
  365. VERBATIM
  366. )
  367. return()
  368. else()
  369. # No explicit UPDATE_COMMAND property. Look for other properties
  370. # indicating which update method to use in the logic below...
  371. endif()
  372. endif()
  373. get_target_property(cvs_repository ${name} AEP_CVS_REPOSITORY)
  374. if(cvs_repository)
  375. if(NOT CVS_EXECUTABLE)
  376. message(FATAL_ERROR "error: could not find cvs for update of ${name}")
  377. endif()
  378. get_property(cvs_tag TARGET ${name} PROPERTY AEP_CVS_TAG)
  379. set(args -d ${cvs_repository} -q up -dP ${cvs_tag})
  380. mkdir("${source_dir}/${name}")
  381. add_custom_command(
  382. OUTPUT ${sentinels_dir}/${name}-update
  383. COMMAND ${CVS_EXECUTABLE} ${args}
  384. WORKING_DIRECTORY ${source_dir}/${name}
  385. COMMENT "Performing update step (CVS update) for '${name}'"
  386. DEPENDS ${sentinels_dir}/${name}-download
  387. VERBATIM
  388. )
  389. # Since the update sentinel is not actually written:
  390. set_property(SOURCE ${sentinels_dir}/${name}-update
  391. PROPERTY SYMBOLIC 1)
  392. return()
  393. endif()
  394. get_target_property(svn_repository ${name} AEP_SVN_REPOSITORY)
  395. if(svn_repository)
  396. if(NOT Subversion_SVN_EXECUTABLE)
  397. message(FATAL_ERROR "error: could not find svn for update of ${name}")
  398. endif()
  399. get_property(svn_tag TARGET ${name} PROPERTY AEP_SVN_TAG)
  400. set(args up ${svn_tag})
  401. mkdir("${source_dir}/${name}")
  402. add_custom_command(
  403. OUTPUT ${sentinels_dir}/${name}-update
  404. COMMAND ${Subversion_SVN_EXECUTABLE} ${args}
  405. WORKING_DIRECTORY ${source_dir}/${name}
  406. COMMENT "Performing update step (SVN update) for '${name}'"
  407. DEPENDS ${sentinels_dir}/${name}-download
  408. VERBATIM
  409. )
  410. # Since the update sentinel is not actually written:
  411. set_property(SOURCE ${sentinels_dir}/${name}-update
  412. PROPERTY SYMBOLIC 1)
  413. return()
  414. endif()
  415. add_custom_command(
  416. OUTPUT ${sentinels_dir}/${name}-update
  417. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-update
  418. WORKING_DIRECTORY ${sentinels_dir}
  419. COMMENT "No update step for '${name}'"
  420. DEPENDS ${sentinels_dir}/${name}-download
  421. VERBATIM
  422. )
  423. endfunction(add_external_project_update_command)
  424. function(add_external_project_patch_command name)
  425. get_external_project_directories(base_dir build_dir downloads_dir install_dir
  426. sentinels_dir source_dir tmp_dir)
  427. get_target_property(cmd ${name} AEP_PATCH_COMMAND)
  428. if(cmd)
  429. add_custom_command(
  430. OUTPUT ${sentinels_dir}/${name}-patch
  431. COMMAND ${cmd}
  432. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-patch
  433. WORKING_DIRECTORY ${source_dir}/${name}
  434. COMMENT "Performing patch step for '${name}'"
  435. DEPENDS ${sentinels_dir}/${name}-download
  436. VERBATIM
  437. )
  438. return()
  439. endif()
  440. add_custom_command(
  441. OUTPUT ${sentinels_dir}/${name}-patch
  442. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-patch
  443. WORKING_DIRECTORY ${sentinels_dir}
  444. COMMENT "No patch step for '${name}'"
  445. DEPENDS ${sentinels_dir}/${name}-download
  446. VERBATIM
  447. )
  448. endfunction(add_external_project_patch_command)
  449. # TODO: Make sure external projects use the proper compiler
  450. function(add_external_project_configure_command name)
  451. get_external_project_directories(base_dir build_dir downloads_dir install_dir
  452. sentinels_dir source_dir tmp_dir)
  453. get_configure_build_working_dir(${name} working_dir)
  454. # Depend on other external projects (file-level).
  455. set(file_deps)
  456. get_property(deps TARGET ${name} PROPERTY AEP_DEPENDS)
  457. foreach(arg IN LISTS deps)
  458. list(APPEND file_deps ${sentinels_dir}/${arg}-done)
  459. endforeach()
  460. #message(STATUS "info: name='${name}' file_deps='${file_deps}'")
  461. # Create the working_dir for configure, build and install steps:
  462. #
  463. mkdir("${working_dir}")
  464. add_custom_command(
  465. OUTPUT ${sentinels_dir}/${name}-working_dir
  466. COMMAND ${CMAKE_COMMAND} -E make_directory ${working_dir}
  467. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-working_dir
  468. DEPENDS ${sentinels_dir}/${name}-update
  469. ${sentinels_dir}/${name}-patch
  470. ${file_deps}
  471. VERBATIM
  472. )
  473. get_target_property(cmd ${name} AEP_CONFIGURE_COMMAND)
  474. if(cmd STREQUAL "")
  475. # Explicit empty string means no configure step for this project
  476. add_custom_command(
  477. OUTPUT ${sentinels_dir}/${name}-configure
  478. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-configure
  479. WORKING_DIRECTORY ${working_dir}
  480. COMMENT "No configure step for '${name}'"
  481. DEPENDS ${sentinels_dir}/${name}-working_dir
  482. VERBATIM
  483. )
  484. else()
  485. if(NOT cmd)
  486. get_target_property(cmake_command ${name} AEP_CMAKE_COMMAND)
  487. if(cmake_command)
  488. set(cmd "${cmake_command}")
  489. else()
  490. set(cmd "${CMAKE_COMMAND}")
  491. endif()
  492. get_property(cmake_args TARGET ${name} PROPERTY AEP_CMAKE_ARGS)
  493. list(APPEND cmd ${cmake_args})
  494. get_target_property(cmake_generator ${name} AEP_CMAKE_GENERATOR)
  495. if(cmake_generator)
  496. list(APPEND cmd "-G${cmake_generator}" "${source_dir}/${name}")
  497. endif()
  498. endif()
  499. add_custom_command(
  500. OUTPUT ${sentinels_dir}/${name}-configure
  501. COMMAND ${cmd}
  502. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-configure
  503. WORKING_DIRECTORY ${working_dir}
  504. COMMENT "Performing configure step for '${name}'"
  505. DEPENDS ${sentinels_dir}/${name}-working_dir
  506. VERBATIM
  507. )
  508. endif()
  509. endfunction(add_external_project_configure_command)
  510. function(add_external_project_build_command name)
  511. get_external_project_directories(base_dir build_dir downloads_dir install_dir
  512. sentinels_dir source_dir tmp_dir)
  513. get_configure_build_working_dir(${name} working_dir)
  514. get_target_property(cmd ${name} AEP_BUILD_COMMAND)
  515. if(cmd STREQUAL "")
  516. # Explicit empty string means no build step for this project
  517. add_custom_command(
  518. OUTPUT ${sentinels_dir}/${name}-build
  519. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-build
  520. WORKING_DIRECTORY ${working_dir}
  521. COMMENT "No build step for '${name}'"
  522. DEPENDS ${sentinels_dir}/${name}-configure
  523. VERBATIM
  524. )
  525. else()
  526. _aep_get_build_command(${name} BUILD cmd)
  527. add_custom_command(
  528. OUTPUT ${sentinels_dir}/${name}-build
  529. COMMAND ${cmd}
  530. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-build
  531. WORKING_DIRECTORY ${working_dir}
  532. COMMENT "Performing build step for '${name}'"
  533. DEPENDS ${sentinels_dir}/${name}-configure
  534. VERBATIM
  535. )
  536. endif()
  537. endfunction(add_external_project_build_command)
  538. function(add_external_project_install_command name)
  539. get_external_project_directories(base_dir build_dir downloads_dir install_dir
  540. sentinels_dir source_dir tmp_dir)
  541. get_configure_build_working_dir(${name} working_dir)
  542. get_target_property(cmd ${name} AEP_INSTALL_COMMAND)
  543. if(cmd STREQUAL "")
  544. # Explicit empty string means no install step for this project
  545. add_custom_command(
  546. OUTPUT ${sentinels_dir}/${name}-install
  547. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-install
  548. WORKING_DIRECTORY ${working_dir}
  549. COMMENT "No install step for '${name}'"
  550. DEPENDS ${sentinels_dir}/${name}-build
  551. VERBATIM
  552. )
  553. else()
  554. _aep_get_build_command(${name} INSTALL cmd)
  555. add_custom_command(
  556. OUTPUT ${sentinels_dir}/${name}-install
  557. COMMAND ${cmd}
  558. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-install
  559. WORKING_DIRECTORY ${working_dir}
  560. COMMENT "Performing install step for '${name}'"
  561. DEPENDS ${sentinels_dir}/${name}-build
  562. VERBATIM
  563. )
  564. endif()
  565. endfunction(add_external_project_install_command)
  566. function(add_CMakeExternals_target)
  567. if(NOT TARGET CMakeExternals)
  568. get_external_project_directories(base_dir build_dir downloads_dir install_dir
  569. sentinels_dir source_dir tmp_dir)
  570. # Make the directories at CMake configure time *and* add a custom command
  571. # to make them at build time. They need to exist at makefile generation
  572. # time for Borland make and wmake so that CMake may generate makefiles
  573. # with "cd C:\short\paths\with\no\spaces" commands in them.
  574. #
  575. # Additionally, the add_custom_command is still used in case somebody
  576. # removes one of the necessary directories and tries to rebuild without
  577. # re-running cmake.
  578. #
  579. mkdir("${build_dir}")
  580. mkdir("${downloads_dir}")
  581. mkdir("${install_dir}")
  582. mkdir("${sentinels_dir}")
  583. mkdir("${source_dir}")
  584. mkdir("${tmp_dir}")
  585. add_custom_command(
  586. OUTPUT ${sentinels_dir}/CMakeExternals-directories
  587. COMMAND ${CMAKE_COMMAND} -E make_directory ${build_dir}
  588. COMMAND ${CMAKE_COMMAND} -E make_directory ${downloads_dir}
  589. COMMAND ${CMAKE_COMMAND} -E make_directory ${install_dir}
  590. COMMAND ${CMAKE_COMMAND} -E make_directory ${sentinels_dir}
  591. COMMAND ${CMAKE_COMMAND} -E make_directory ${source_dir}
  592. COMMAND ${CMAKE_COMMAND} -E make_directory ${tmp_dir}
  593. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/CMakeExternals-directories
  594. COMMENT "Creating CMakeExternals directories"
  595. VERBATIM
  596. )
  597. add_custom_target(CMakeExternals ALL
  598. DEPENDS ${sentinels_dir}/CMakeExternals-directories
  599. )
  600. endif()
  601. endfunction(add_CMakeExternals_target)
  602. # Pre-compute a regex to match known keywords.
  603. set(_aep_keyword_regex "^(")
  604. set(_aep_keyword_sep)
  605. foreach(key IN ITEMS
  606. BUILD_ARGS
  607. BUILD_COMMAND
  608. CMAKE_ARGS
  609. CMAKE_COMMAND
  610. CMAKE_GENERATOR
  611. CONFIGURE_COMMAND
  612. CONFIGURE_DIR
  613. CVS_MODULE
  614. CVS_REPOSITORY
  615. CVS_TAG
  616. DEPENDS
  617. DIR
  618. DOWNLOAD_COMMAND
  619. INSTALL_ARGS
  620. INSTALL_COMMAND
  621. PATCH_COMMAND
  622. SVN_REPOSITORY
  623. SVN_TAG
  624. TAR
  625. TAR_URL
  626. TGZ
  627. TGZ_URL
  628. UPDATE_COMMAND
  629. )
  630. set(_aep_keyword_regex "${_aep_keyword_regex}${_aep_keyword_sep}${key}")
  631. set(_aep_keyword_sep "|")
  632. endforeach(key)
  633. set(_aep_keyword_regex "${_aep_keyword_regex})$")
  634. set(_aep_keyword_sep)
  635. set(_aep_keywords_add_external_project "${_aep_keyword_regex}")
  636. function(add_external_project name)
  637. get_external_project_directories(base_dir build_dir downloads_dir install_dir
  638. sentinels_dir source_dir tmp_dir)
  639. # Ensure root CMakeExternals target and directories are created.
  640. # All external projects will depend on this root CMakeExternals target.
  641. #
  642. add_CMakeExternals_target()
  643. # Add a custom target for the external project. The 'complete' step
  644. # depends on all other steps and creates a 'done' mark. A dependent
  645. # external project's 'configure' step depends on the 'done' mark so
  646. # that it rebuilds when this project rebuilds. It is important that
  647. # 'done' is not the output of any custom command so that CMake does
  648. # not propagate build rules to other external project targets.
  649. add_custom_target(${name} ALL DEPENDS ${sentinels_dir}/${name}-complete)
  650. add_custom_command(
  651. OUTPUT ${sentinels_dir}/${name}-complete
  652. COMMENT "Completed '${name}'"
  653. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-complete
  654. COMMAND ${CMAKE_COMMAND} -E touch ${sentinels_dir}/${name}-done
  655. DEPENDS ${sentinels_dir}/${name}-install
  656. VERBATIM
  657. )
  658. set_target_properties(${name} PROPERTIES AEP_IS_EXTERNAL_PROJECT 1)
  659. add_dependencies(${name} CMakeExternals)
  660. _aep_parse_arguments(add_external_project ${name} AEP_ "${ARGN}")
  661. # Depend on other external projects (target-level).
  662. get_property(deps TARGET ${name} PROPERTY AEP_DEPENDS)
  663. foreach(arg IN LISTS deps)
  664. add_dependencies(${name} ${arg})
  665. endforeach()
  666. # Set up custom build steps based on the target properties.
  667. # Each step depends on the previous one.
  668. #
  669. # The target depends on the output of the final step.
  670. # (Already set up above in the DEPENDS of the add_custom_target command.)
  671. #
  672. add_external_project_download_command(${name})
  673. add_external_project_update_command(${name})
  674. add_external_project_patch_command(${name})
  675. add_external_project_configure_command(${name})
  676. add_external_project_build_command(${name})
  677. add_external_project_install_command(${name})
  678. endfunction(add_external_project)