CheckSubmodules.cmake 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. # Utility for validating and, if needed, cloning all submodules
  2. #
  3. # Looks for a .gitmodules in the root project folder
  4. # Loops over all modules looking well-known configure/build scripts
  5. #
  6. # Usage:
  7. # INCLUDE(CheckSubmodules)
  8. #
  9. # Options:
  10. # SET(PLUGIN_LIST "zynaddsubfx;...") # skips submodules for plugins not explicitely listed
  11. #
  12. # Or via command line:
  13. # cmake -PLUGIN_LIST=foo;bar
  14. #
  15. # Copyright (c) 2019, Tres Finocchiaro, <tres.finocchiaro@gmail.com>
  16. #
  17. # Redistribution and use is allowed according to the terms of the BSD license.
  18. # For details see the accompanying COPYING-CMAKE-SCRIPTS file.
  19. # Files which confirm a successful clone
  20. SET(VALID_CRUMBS "CMakeLists.txt;Makefile;Makefile.in;Makefile.am;configure.ac;configure.py;autogen.sh;.gitignore;LICENSE;Home.md")
  21. OPTION(NO_SHALLOW_CLONE "Disable shallow cloning of submodules" OFF)
  22. # Try and use the specified shallow clone on submodules, if supported
  23. SET(DEPTH_VALUE 100)
  24. # Number of times git commands will retry before failing
  25. SET(MAX_ATTEMPTS 2)
  26. MESSAGE("\nChecking submodules...")
  27. IF(NOT EXISTS "${CMAKE_SOURCE_DIR}/.gitmodules")
  28. MESSAGE("Skipping the check because .gitmodules not detected."
  29. "Please make sure you have all submodules in the source tree!"
  30. )
  31. RETURN()
  32. ENDIF()
  33. FILE(READ "${CMAKE_SOURCE_DIR}/.gitmodules" SUBMODULE_DATA)
  34. # Force English locale
  35. SET(LC_ALL_BACKUP "$ENV{LC_ALL}")
  36. SET(LANG_BACKUP "$ENV{LANG}")
  37. SET(ENV{LC_ALL} "C")
  38. SET(ENV{LANG} "en_US")
  39. # Submodule list pairs, unparsed (WARNING: Assumes alpha-numeric paths)
  40. STRING(REGEX MATCHALL "path = [-0-9A-Za-z/]+" SUBMODULE_LIST_RAW ${SUBMODULE_DATA})
  41. STRING(REGEX MATCHALL "url = [.:%-0-9A-Za-z/]+" SUBMODULE_URL_RAW ${SUBMODULE_DATA})
  42. # Submodule list pairs, parsed
  43. SET(SUBMODULE_LIST "")
  44. SET(SUBMODULE_URL "")
  45. FOREACH(_path ${SUBMODULE_LIST_RAW})
  46. # Parse SUBMODULE_PATH
  47. STRING(REPLACE "path = " "" SUBMODULE_PATH "${_path}")
  48. # Grab index for matching SUBMODULE_URL
  49. LIST(FIND SUBMODULE_LIST_RAW "${_path}" SUBMODULE_INDEX)
  50. LIST(GET SUBMODULE_URL_RAW ${SUBMODULE_INDEX} _url)
  51. # Parse SUBMODULE_URL
  52. STRING(REPLACE "url = " "" SUBMODULE_URL "${_url}")
  53. SET(SKIP false)
  54. # Loop over skipped plugins, add to SKIP_SUBMODULES (e.g. -DPLUGIN_LIST=foo;bar)
  55. IF(${SUBMODULE_PATH} MATCHES "^plugins/")
  56. SET(REMOVE_PLUGIN true)
  57. FOREACH(_plugin ${PLUGIN_LIST})
  58. IF(_plugin STREQUAL "")
  59. CONTINUE()
  60. ENDIF()
  61. IF(${SUBMODULE_PATH} MATCHES "${_plugin}")
  62. SET(REMOVE_PLUGIN false)
  63. ENDIF()
  64. ENDFOREACH()
  65. IF(REMOVE_PLUGIN)
  66. LIST(APPEND SKIP_SUBMODULES "${SUBMODULE_PATH}")
  67. ENDIF()
  68. ENDIF()
  69. # Finally, loop and mark "SKIP" on match
  70. IF(SKIP_SUBMODULES)
  71. FOREACH(_skip ${SKIP_SUBMODULES})
  72. IF("${SUBMODULE_PATH}" MATCHES "${_skip}")
  73. MESSAGE("-- Skipping ${SUBMODULE_PATH} matches \"${_skip}\" (absent in PLUGIN_LIST)")
  74. SET(SKIP true)
  75. BREAK()
  76. ENDIF()
  77. ENDFOREACH()
  78. ENDIF()
  79. IF(NOT SKIP)
  80. LIST(APPEND SUBMODULE_LIST "${SUBMODULE_PATH}")
  81. LIST(APPEND SUBMODULE_URL "${SUBMODULE_URL}")
  82. ENDIF()
  83. ENDFOREACH()
  84. # Once called, status is stored in GIT_RESULT respectively.
  85. # Note: Git likes to write to stderr. Don't assume stderr is error; Check GIT_RESULT instead.
  86. MACRO(GIT_SUBMODULE SUBMODULE_PATH FORCE_DEINIT FORCE_REMOTE NO_DEPTH)
  87. FIND_PACKAGE(Git REQUIRED)
  88. # Handle missing commits
  89. SET(FORCE_REMOTE_FLAG "${FORCE_REMOTE}")
  90. SET(NO_DEPTH_FLAG "${NO_DEPTH}")
  91. IF(FORCE_REMOTE_FLAG)
  92. MESSAGE("-- Adding remote submodulefix to ${SUBMODULE_PATH}")
  93. EXECUTE_PROCESS(
  94. COMMAND "${GIT_EXECUTABLE}" remote rm submodulefix
  95. COMMAND "${GIT_EXECUTABLE}" remote add submodulefix ${FORCE_REMOTE}
  96. COMMAND "${GIT_EXECUTABLE}" fetch submodulefix
  97. WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/${SUBMODULE_PATH}"
  98. OUTPUT_QUIET ERROR_QUIET
  99. )
  100. # Recurse
  101. GIT_SUBMODULE(${SUBMODULE_PATH} false false ${NO_DEPTH_FLAG})
  102. ELSEIF(${FORCE_DEINIT})
  103. MESSAGE("-- Resetting ${SUBMODULE_PATH}")
  104. EXECUTE_PROCESS(
  105. COMMAND "${GIT_EXECUTABLE}" submodule deinit -f "${CMAKE_SOURCE_DIR}/${SUBMODULE_PATH}"
  106. WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
  107. OUTPUT_QUIET
  108. )
  109. MESSAGE("-- Deleting ${CMAKE_SOURCE_DIR}/.git/${SUBMODULE_PATH}")
  110. FILE(REMOVE_RECURSE "${CMAKE_SOURCE_DIR}/.git/modules/${SUBMODULE_PATH}")
  111. # Recurse without depth
  112. GIT_SUBMODULE(${SUBMODULE_PATH} false false true)
  113. ELSE()
  114. # Try to use the depth switch
  115. IF(NO_SHALLOW_CLONE OR GIT_VERSION_STRING VERSION_LESS "1.8.4" OR NO_DEPTH_FLAG)
  116. # Shallow submodules were introduced in 1.8.4
  117. MESSAGE("-- Fetching ${SUBMODULE_PATH}")
  118. SET(DEPTH_CMD "")
  119. SET(DEPTH_VAL "")
  120. ELSE()
  121. MESSAGE("-- Fetching ${SUBMODULE_PATH} @ --depth ${DEPTH_VALUE}")
  122. SET(DEPTH_CMD "--depth")
  123. SET(DEPTH_VAL "${DEPTH_VALUE}")
  124. ENDIF()
  125. EXECUTE_PROCESS(
  126. COMMAND "${GIT_EXECUTABLE}" submodule update --init --recursive ${DEPTH_CMD} ${DEPTH_VAL} "${CMAKE_SOURCE_DIR}/${SUBMODULE_PATH}"
  127. WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
  128. RESULT_VARIABLE GIT_RESULT
  129. OUTPUT_VARIABLE GIT_STDOUT
  130. ERROR_VARIABLE GIT_STDERR
  131. )
  132. SET(GIT_MESSAGE "${GIT_STDOUT}${GIT_STDERR}")
  133. MESSAGE("${GIT_MESSAGE}")
  134. ENDIF()
  135. ENDMACRO()
  136. SET(MISSING_COMMIT_PHRASES "no such remote ref;reference is not a tree;unadvertised object")
  137. SET(RETRY_PHRASES "Failed to recurse;cannot create directory;already exists;${MISSING_COMMIT_PHRASES}")
  138. # Attempt to do lazy clone
  139. FOREACH(_submodule ${SUBMODULE_LIST})
  140. STRING(REPLACE "/" ";" PATH_PARTS "${_submodule}")
  141. LIST(REVERSE PATH_PARTS)
  142. LIST(GET PATH_PARTS 0 SUBMODULE_NAME)
  143. MESSAGE("-- Checking ${SUBMODULE_NAME}...")
  144. SET(CRUMB_FOUND false)
  145. FOREACH(_crumb ${VALID_CRUMBS})
  146. IF(EXISTS "${CMAKE_SOURCE_DIR}/${_submodule}/${_crumb}")
  147. SET(CRUMB_FOUND true)
  148. MESSAGE("-- Found ${_submodule}/${_crumb}")
  149. BREAK()
  150. ENDIF()
  151. ENDFOREACH()
  152. IF(NOT CRUMB_FOUND)
  153. GIT_SUBMODULE("${_submodule}" false false false)
  154. SET(COUNTED 0)
  155. # Handle edge-cases where submodule didn't clone properly or re-uses a non-empty directory
  156. WHILE(NOT GIT_RESULT EQUAL 0 AND COUNTED LESS MAX_ATTEMPTS)
  157. MATH(EXPR COUNTED "${COUNTED}+1")
  158. SET(MISSING_COMMIT false)
  159. FOREACH(_phrase ${MISSING_COMMIT_PHRASES})
  160. IF("${GIT_MESSAGE}" MATCHES "${_phrase}")
  161. SET(MISSING_COMMIT true)
  162. BREAK()
  163. ENDIF()
  164. ENDFOREACH()
  165. FOREACH(_phrase ${RETRY_PHRASES})
  166. IF(${MISSING_COMMIT} AND COUNTED LESS 2)
  167. LIST(FIND SUBMODULE_LIST ${_submodule} SUBMODULE_INDEX)
  168. LIST(GET SUBMODULE_URL_LIST ${SUBMODULE_INDEX} SUBMODULE_URL)
  169. MESSAGE("-- Retrying ${_submodule} using 'remote add submodulefix' (attempt ${COUNTED} of ${MAX_ATTEMPTS})...")
  170. GIT_SUBMODULE("${_submodule}" false "${SUBMODULE_URL}" false)
  171. BREAK()
  172. ELSEIF("${GIT_MESSAGE}" MATCHES "${_phrase}")
  173. MESSAGE("-- Retrying ${_submodule} using 'deinit' (attempt ${COUNTED} of ${MAX_ATTEMPTS})...")
  174. GIT_SUBMODULE("${_submodule}" true false false)
  175. BREAK()
  176. ENDIF()
  177. ENDFOREACH()
  178. ENDWHILE()
  179. IF(NOT GIT_RESULT EQUAL 0)
  180. MESSAGE(FATAL_ERROR "${GIT_EXECUTABLE} exited with status of ${GIT_RESULT}")
  181. ENDIF()
  182. ENDIF()
  183. ENDFOREACH()
  184. MESSAGE("-- Done validating submodules.\n")
  185. # Reset locale
  186. SET(ENV{LC_ALL} "${LC_ALL_BACKUP}")
  187. SET(ENV{LANG} "${LANG_BACKUP}")