CMakeFindExternalProject.cmake 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. #[[
  2. 文件名: CMakeFindExternalProject.cmake
  3. 用于定位一个外部项目
  4. 若外部项目不存在则构建该项目
  5. ]]
  6. # 保存 CMAKE_CURRENT_LIST_DIR
  7. # 因为 CMake 函数是动态作用域的
  8. set(CMakeFindExternalProject_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "CMAKE_CURRENT_LIST_DIR" FORCE)
  9. macro(_cfep_build_found_inline _found)
  10. set(${name}_CFEP_FOUND ${_found} CACHE INTERNAL "${name} found by CMakeFindExternalProject" FORCE)
  11. if (${name}_CFEP_FOUND)
  12. set(${name}_CFEP_BUILD_DIR ${_build_dir} CACHE INTERNAL "${name} install dirs" FORCE)
  13. set(${name}_CFEP_INSTALL ${_install_dir} CACHE INTERNAL "${name} install dirs" FORCE)
  14. set(${name}_CFEP_INSTALL_TYPE ${cfp_INSTALL_DIR} CACHE INTERNAL "${name} install dirs type" FORCE)
  15. else()
  16. set(${name}_CFEP_BUILD_DIR "" CACHE INTERNAL "${name} install dirs" FORCE)
  17. set(${name}_CFEP_INSTALL "" CACHE INTERNAL "${name} install dirs" FORCE)
  18. set(${name}_CFEP_INSTALL_TYPE "" CACHE INTERNAL "${name} install dirs type" FORCE)
  19. endif()
  20. endmacro()
  21. function(_cfep_build_inline name)
  22. set(options NOT_INFO FORCE)
  23. set(oneValueArgs
  24. BUILD_DIR
  25. SOURCE_DIR # 指定源文件的位置, 指定后不可再设定DOWNLOAD_COMMAND
  26. INSTALL_DIR # 有三个选择: base, deps, binary
  27. TIMEOUT)
  28. set(multiValueArgs
  29. DOWNLOAD_COMMAND
  30. CMAKE_ARGS
  31. BUILD_CMAKE_ARGS
  32. BUILD_CMAKE_CACHE_ARGS
  33. BUILD_CMAKE_CACHE_DEFAULT_ARGS)
  34. cmake_parse_arguments(cfp
  35. "${options}"
  36. "${oneValueArgs}"
  37. "${multiValueArgs}"
  38. ${ARGN})
  39. if (NOT cfp_BUILD_DIR)
  40. set(_build_dir "${CMAKE_BINARY_DIR}/deps/${name}")
  41. else()
  42. set(_build_dir "${CMAKE_BINARY_DIR}/deps/${cfp_BUILD_DIR}")
  43. endif()
  44. set(_force ${cfp_FORCE})
  45. if (cfp_NOT_INFO)
  46. set(_info false)
  47. else()
  48. set(_info true)
  49. endif()
  50. set(_cmake_dir ${_build_dir}/cmake.dir)
  51. set(_binary_dir ${_build_dir}/binary)
  52. set(_timeout ${cfp_TIMEOUT})
  53. if (cfp_SOURCE_DIR)
  54. if(EXISTS ${cfp_SOURCE_DIR}) # 若指定的SOURCE_DIR存在
  55. if (cfp_DOWNLOAD_COMMAND) # 若仍有DOWNLOAD_COMMAND
  56. if (_info)
  57. message(WARNING "DOWNLOAD_COMMAND will be ignore.")
  58. endif()
  59. unset(cfp_DOWNLOAD_COMMAND) # 清空cfp_DOWNLOAD_COMMAND
  60. endif()
  61. set(_source_dir ${cfp_SOURCE_DIR})
  62. else() # 若指定的SOURCE_DIR不存在
  63. if (_info)
  64. message(WARNING "SOURCE_DIR will be ignore.")
  65. endif()
  66. set(_source_dir ${_build_dir}/source)
  67. endif()
  68. else() # 若没有SOURCE_DIR则按默认_source_dir
  69. set(_source_dir ${_build_dir}/source)
  70. endif()
  71. if (cfp_INSTALL_DIR STREQUAL "base")
  72. set(_install_dir ${CMAKE_BINARY_DIR}/${name})
  73. elseif(cfp_INSTALL_DIR STREQUAL "binary")
  74. set(_install_dir ${CMAKE_BINARY_DIR}/${name})
  75. else()
  76. set(_install_dir ${_build_dir}/install)
  77. endif()
  78. if (_info)
  79. message(STATUS "Project ${name} will be build.")
  80. message(STATUS "Project ${name} source at ${_source_dir}")
  81. message(STATUS "Project ${name} install to ${_install_dir}")
  82. message(STATUS "Project ${name} build at ${_binary_dir}")
  83. message(STATUS "Project ${name} other info at ${_build_dir}")
  84. endif()
  85. if(NOT _force)
  86. if ((${_name}_CFEP_FOUND) AND (EXISTS ${_cmake_dir})) # 若 _cmake_dir 被删除则重新构建项目
  87. return()
  88. endif()
  89. endif()
  90. foreach(dir cmake install source binary install) # 构建目录
  91. execute_process(
  92. COMMAND "${CMAKE_COMMAND}" -E make_directory "${_${dir}_dir}"
  93. RESULT_VARIABLE re)
  94. if (re)
  95. if (_info)
  96. message(WARNING "cmake make directory ${_${dir}_dir} fail(${re}): ${_${dir}_dir}")
  97. endif()
  98. _cfep_build_found_inline(FALSE)
  99. return()
  100. endif()
  101. unset(re)
  102. unset(_stderr)
  103. unset(_stdout)
  104. endforeach()
  105. set(_download ${cfp_DOWNLOAD_COMMAND})
  106. set(_cmake_args ${cfp_CMAKE_ARGS})
  107. set(_build_cmake_args
  108. ${cfp_BUILD_CMAKE_ARGS}
  109. "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
  110. set(_build_cmake_cache
  111. ${cfp_BUILD_CMAKE_CACHE_ARGS}
  112. \"-DCMAKE_INSTALL_PREFIX:PATH=${_install_dir}\") # ${_install_dir}不需要分号
  113. set(_build_cmake_default_cache ${BUILD_CMAKE_CACHE_DEFAULT_ARGS})
  114. set(CEFP_CMAKE_VERSION ${CMAKE_VERSION})
  115. set(CFEP_NAME ${name})
  116. set(SHOW_INFO ${_info})
  117. set(CEFP_BUILD_PREFIX ${_build_dir})
  118. set(CEFP_SOURCE_DIR ${_source_dir})
  119. set(CEFP_BINARY_DIR ${_binary_dir})
  120. set(CEFP_INSTALL_DIR ${_install_dir})
  121. set(_CEFP_COMMAND
  122. TIMEOUT ${_timeout}
  123. ${_download}
  124. CMAKE_COMMAND \"${CMAKE_COMMAND}\" # "需要转义, 作为字符串的一部分内容
  125. CMAKE_GENERATOR \"${CMAKE_GENERATOR}\"
  126. CMAKE_GENERATOR_PLATFORM \"${CMAKE_GENERATOR_PLATFORM}\"
  127. CMAKE_GENERATOR_TOOLSET \"${CMAKE_GENERATOR_TOOLSET}\"
  128. CMAKE_GENERATOR_INSTANCE \"${CMAKE_GENERATOR_INSTANCE}\"
  129. CMAKE_ARGS ${_build_cmake_args}
  130. CMAKE_CACHE_ARGS ${_build_cmake_cache}
  131. CMAKE_CACHE_DEFAULT_ARGS ${_build_cmake_default_cache}
  132. )
  133. string(REPLACE ";" " \n" CEFP_COMMAND "${_CEFP_COMMAND}") # 把;替换为空格
  134. unset(_CEFP_COMMAND)
  135. set(config_file TRUE) # 生成文件
  136. if ((NOT _force) AND (EXISTS ${_cmake_dir}/CMakeLists.txt)) # 非强制模式, 文件已存在
  137. if (CFEP_${_cmake_dir}_CMAKE_FILE) # CFEP_${_cmake_dir}_CMAKE_FILE记录文件的md5值
  138. file(MD5 ${_cmake_dir}/CMakeLists.txt _md5) # 计算md5
  139. if (_md5 STREQUAL CFEP_${_cmake_dir}_CMAKE_FILE)
  140. set(config_file FALSE) # 不需要构建文件
  141. endif()
  142. unset(_md5)
  143. endif()
  144. endif()
  145. if (config_file)
  146. configure_file(${CMakeFindExternalProject_DIR}/CMakeLists.txt.in
  147. ${_cmake_dir}/CMakeLists.txt
  148. @ONLY)
  149. file(MD5 ${_cmake_dir}/CMakeLists.txt _md5) # 计算md5
  150. set(CFEP_${_cmake_dir}_CMAKE_FILE ${_md5} CACHE INTERNAL "md5" FORCE)
  151. unset(_md5)
  152. endif()
  153. unset(CEFP_CMAKE_VERSION)
  154. unset(CFEP_NAME)
  155. unset(SHOW_INFO)
  156. unset(CEFP_BUILD_PREFIX)
  157. unset(CEFP_SOURCE_DIR)
  158. unset(CEFP_BINARY_DIR)
  159. unset(CEFP_INSTALL_DIR)
  160. unset(CEFP_COMMAND)
  161. # 定义项目
  162. if (_info)
  163. message(STATUS "CMake Config ${name}...(Please Wait)")
  164. endif()
  165. execute_process(
  166. COMMAND "${CMAKE_COMMAND}" . -B ./build -G "${CMAKE_GENERATOR}" ${_cmake_args}
  167. WORKING_DIRECTORY "${_cmake_dir}"
  168. RESULT_VARIABLE re
  169. OUTPUT_VARIABLE _stdout # stdout的输出内容
  170. ERROR_VARIABLE _stderr # stderr的输出内容
  171. OUTPUT_STRIP_TRAILING_WHITESPACE
  172. ERROR_STRIP_TRAILING_WHITESPACE
  173. )
  174. if(re)
  175. _cfep_build_found_inline(FALSE)
  176. if (_info)
  177. message(WARNING "CMake config ${name} fail(${re}): \n${_stderr}")
  178. endif()
  179. return()
  180. endif()
  181. unset(re)
  182. unset(_stderr)
  183. unset(_stdout)
  184. # 构建项目
  185. if (_info)
  186. message(STATUS "CMake Build ${name}...(Please Wait)")
  187. endif()
  188. execute_process(
  189. COMMAND "${CMAKE_COMMAND}" --build .
  190. WORKING_DIRECTORY "${_cmake_dir}/build"
  191. RESULT_VARIABLE re
  192. OUTPUT_VARIABLE _stdout # stdout的输出内容
  193. ERROR_VARIABLE _stderr # stderr的输出内容
  194. OUTPUT_STRIP_TRAILING_WHITESPACE
  195. ERROR_STRIP_TRAILING_WHITESPACE
  196. )
  197. if(re)
  198. _cfep_build_found_inline(FALSE)
  199. if (_info)
  200. message(WARNING "CMake build ${name} fail(${re}): \n${_stderr}")
  201. endif()
  202. return()
  203. endif()
  204. unset(re)
  205. unset(_stderr)
  206. unset(_stdout)
  207. _cfep_build_found_inline(TRUE)
  208. endfunction()
  209. function(_cfep_check_build_inline name var)
  210. set(options)
  211. set(oneValueArgs
  212. INSTALL_DIR # 有三个选择: base, deps, binary
  213. BUILD_DIR)
  214. cmake_parse_arguments(cfp "" "${oneValueArgs}" "" ${ARGN})
  215. if (NOT cfp_BUILD_DIR)
  216. set(_build_dir "${CMAKE_BINARY_DIR}/deps/${name}")
  217. else()
  218. set(_build_dir "${CMAKE_BINARY_DIR}/deps/${cfp_BUILD_DIR}")
  219. endif()
  220. if (cfp_INSTALL_DIR STREQUAL "base")
  221. set(_install_dir ${CMAKE_BINARY_DIR}/${name})
  222. elseif(cfp_INSTALL_DIR STREQUAL "binary")
  223. set(_install_dir ${CMAKE_BINARY_DIR}/${name})
  224. else()
  225. set(_install_dir ${_build_dir}/install)
  226. endif()
  227. if (EXISTS ${_install_dir})
  228. set(${var} ${_install_dir} PARENT_SCOPE)
  229. _cfep_build_found_inline(TRUE)
  230. else()
  231. set(${var} ${var}-NOFOUND PARENT_SCOPE)
  232. _cfep_build_found_inline(FALSE)
  233. endif()
  234. endfunction()
  235. # 从url下载程序
  236. function(_cfep_build_url_inline name dir git git_tag url)
  237. _cfep_build_inline(${name} DOWNLOAD_COMMAND URL ${url} ${ARGN})
  238. if (${name}_CFEP_FOUND)
  239. set(${name}_BUILD TRUE PARENT_SCOPE)
  240. else()
  241. set(${name}_BUILD FALSE PARENT_SCOPE)
  242. endif()
  243. endfunction()
  244. # 从git下载程序
  245. function(_cfep_build_git_inline name dir git git_tag url)
  246. _cfep_build_inline(${name}
  247. DOWNLOAD_COMMAND
  248. GIT_REPOSITORY ${git}
  249. GIT_TAG ${git_tag}
  250. GIT_PROGRESS 0
  251. GIT_REMOTE_UPDATE_STRATEGY REBASE_CHECKOUT
  252. ${ARGN})
  253. if (${name}_CFEP_FOUND)
  254. set(${name}_BUILD TRUE PARENT_SCOPE)
  255. else()
  256. set(${name}_BUILD FALSE PARENT_SCOPE)
  257. endif()
  258. endfunction()
  259. # 指定源文件下载程序
  260. function(_cfep_build_dir_inline name dir git git_tag url)
  261. _cfep_build_inline(${name} SOURCE_DIR ${dir} ${ARGN})
  262. if (${name}_CFEP_FOUND)
  263. set(${name}_BUILD TRUE PARENT_SCOPE)
  264. else()
  265. set(${name}_BUILD FALSE PARENT_SCOPE)
  266. endif()
  267. endfunction()
  268. macro(_cfep_found_inline _found)
  269. set(__found ${_found})
  270. if (NOT ${__found})
  271. set(${_name}_FOUND ${_name}-NOTFOUND)
  272. if (_required)
  273. message(FATAL_ERROR "Package not found: ${_name}")
  274. endif()
  275. if (NOT _quiet)
  276. message(WARNING "Package not found: ${_name}")
  277. endif()
  278. endif()
  279. unset(__found)
  280. endmacro()
  281. macro(_cfep_first_find_inline name _cmake)
  282. if (NOT ${name}_MUST_BUILD) # _MUST_BUILD则先不构建, 而是寻找该库
  283. find_package(${name} QUIET ${_find_args}) # 尝试搜索
  284. else()
  285. set(${name}_MUST_BUILD FALSE CACHE BOOL "" FORCE)
  286. endif()
  287. if (NOT ${name}_FOUND)
  288. _cfep_check_build_inline(${name} re ${_external_args})
  289. if (re)
  290. if (_cmake)
  291. set(${name}_DIR "${re}/${_cmake}" CACHE PATH "" FORCE)
  292. elseif(WIN32 OR CYGWIN)
  293. set(${name}_DIR "${re}/cmake" CACHE PATH "" FORCE)
  294. else()
  295. set(${name}_DIR "${re}/share/cmake/${name}" CACHE PATH "" FORCE)
  296. endif()
  297. find_package(${name} QUIET ${_find_args}) # 尝试搜索
  298. endif()
  299. endif()
  300. endmacro()
  301. macro(_cfep_find_xxx_inline name func)
  302. while (1) # 宏无法处理return, 所以使用while+break来模拟return
  303. set(options REQUIRED QUIET MODULE)
  304. set(oneValueArgs SOURCE_DIR URL GIT GIT_TAG CMAKE_DIR)
  305. set(multiValueArgs
  306. PACKAGE
  307. EXTERNAL)
  308. cmake_parse_arguments(cfpu
  309. "${options}"
  310. "${oneValueArgs}"
  311. "${multiValueArgs}"
  312. ${ARGN})
  313. set(_name ${name})
  314. set(_func ${func})
  315. set(_dir ${cfpu_SOURCE_DIR})
  316. set(_git ${cfpu_GIT})
  317. set(_git_tag ${cfpu_GIT_TAG})
  318. set(_url ${cfpu_URL})
  319. set(_cmake ${cfpu_CMAKE_DIR})
  320. set(_find_args ${cfpu_PACKAGE})
  321. set(_external_args ${cfpu_EXTERNAL})
  322. set(_required ${cfpu_REQUIRED})
  323. set(_quiet ${cfpu_QUIET})
  324. set(_module ${cfpu_MODULE})
  325. set(_CMAKE_MODULE_PATH_bak ${CMAKE_MODULE_PATH})
  326. if (_module)
  327. list(APPEND CMAKE_MODULE_PATH ${_cmake}) # 此时 _cmake 必须是绝对路径
  328. endif()
  329. _cfep_first_find_inline("${name}" "${_cmake}")
  330. if (${name}_FOUND)
  331. _cfep_found_inline(TRUE)
  332. break()
  333. endif()
  334. cmake_language(CALL "${_func}" "${_name}" "${_dir}" "${_git}" "${_git_tag}" "${_url}" "${_external_args}")
  335. if (NOT ${name}_BUILD) # 未构建
  336. _cfep_found_inline(FALSE)
  337. break()
  338. endif()
  339. set(${name}_ROOT "${${name}_CFEP_INSTALL}" CACHE PATH "" FORCE)
  340. if (NOT _module)
  341. if (_cmake)
  342. set(${name}_DIR "${${name}_CFEP_INSTALL}/${_cmake}" CACHE PATH "" FORCE)
  343. elseif(WIN32 OR CYGWIN)
  344. set(${name}_DIR "${${name}_CFEP_INSTALL}/cmake" CACHE PATH "" FORCE)
  345. else()
  346. set(${name}_DIR "${${name}_CFEP_INSTALL}/share/cmake/${name}" CACHE PATH "" FORCE)
  347. endif()
  348. list(APPEND CMAKE_MODULE_PATH ${${name}_DIR}) # 追加到MODULE_PATH中应对Module模式
  349. endif()
  350. find_package(${name} QUIET ${_find_args}) # 最后搜索
  351. if (${name}_FOUND)
  352. _cfep_found_inline(TRUE)
  353. break()
  354. endif()
  355. _cfep_found_inline(FALSE)
  356. break()
  357. endwhile()
  358. unset(_name)
  359. unset(_dir)
  360. unset(_git)
  361. unset(_git_tag)
  362. unset(_url)
  363. unset(_cmake)
  364. unset(_find_args)
  365. unset(_external_args)
  366. unset(_required)
  367. unset(_quiet)
  368. unset(cfpu_SOURCE_DIR)
  369. unset(cfpu_CMAKE_DIR)
  370. unset(cfpu_PACKAGE)
  371. unset(cfpu_EXTERNAL)
  372. unset(cfpu_REQUIRED)
  373. unset(cfpu_QUIET)
  374. set(CMAKE_MODULE_PATH ${_CMAKE_MODULE_PATH_bak})
  375. unset(_CMAKE_MODULE_PATH_bak)
  376. endmacro()
  377. # 从url下载文件
  378. macro(cfep_find_url name)
  379. _cfep_find_xxx_inline(${name} _cfep_build_url_inline ${ARGN})
  380. endmacro()
  381. # 从git下载文件
  382. macro(cfep_find_git name)
  383. _cfep_find_xxx_inline(${name} _cfep_build_git_inline ${ARGN})
  384. endmacro()
  385. # 从dir下载文件
  386. macro(cfep_find_dir name)
  387. _cfep_find_xxx_inline(${name} _cfep_build_dir_inline "${name}" ${ARGN})
  388. endmacro()
  389. # 安装程序
  390. function(cfep_install name)
  391. cmake_parse_arguments(ci "NOT_QUIET" "PREFIX" "" ${ARGN})
  392. if (NOT ci_PREFIX)
  393. set(prefix ${CMAKE_INSTALL_PREFIX})
  394. else()
  395. set(prefix ${ci_PREFIX})
  396. endif()
  397. if (NOT ${name}_CFEP_FOUND)
  398. if (ci_NOT_QUIET)
  399. message(WARNING "Cannot install ${name}.")
  400. endif()
  401. set(${name}_CFEP_INSTALL_SUCCESS FALSE PARENT_SCOPE)
  402. return()
  403. endif()
  404. if (NOT prefix)
  405. set(prefix ${CMAKE_INSTALL_PREFIX})
  406. endif()
  407. install(DIRECTORY "${${name}_CFEP_INSTALL}/" # /表示把${${name}_CFEP_INSTALL}的内容安装到${prefix}
  408. DESTINATION ${prefix}
  409. USE_SOURCE_PERMISSIONS)
  410. set(${name}_CFEP_INSTALL_SUCCESS TRUE PARENT_SCOPE)
  411. endfunction()
  412. function(cfep_copy_install name)
  413. cmake_parse_arguments(cci "NOT_QUIET" "DEST" "" ${ARGN})
  414. if (NOT cci_DEST)
  415. set(dest ${CMAKE_BINARY_DIR})
  416. else()
  417. set(dest ${cci_DEST})
  418. endif()
  419. if (NOT ${name}_CFEP_FOUND)
  420. if (cci_NOT_QUIET)
  421. message(WARNING "Cannot copy install ${name}.")
  422. endif()
  423. set(${name}_CFEP_COPY_SUCCESS FALSE PARENT_SCOPE)
  424. return()
  425. endif()
  426. execute_process(COMMAND "${CMAKE_COMMAND}" -E copy_directory ${${name}_CFEP_INSTALL} ${dest}
  427. RESULT_VARIABLE re)
  428. if(re)
  429. if (cci_NOT_QUIET)
  430. message(WARNING "Cannot copy install ${name}.")
  431. endif()
  432. set(${name}_CFEP_COPY_SUCCESS TRUE PARENT_SCOPE)
  433. endif()
  434. endfunction()