CheckSubmodules.cmake 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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(SKIP_SUBMODULES "foo;bar")
  11. #
  12. # Or via command line:
  13. # cmake -DSKIP_SUBMODULES=foo;bar
  14. #
  15. # Copyright (c) 2017, 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. # Try and use the specified shallow clone on submodules, if supported
  22. SET(DEPTH_VALUE 100)
  23. # Number of times git commands will retry before failing
  24. SET(MAX_ATTEMPTS 2)
  25. MESSAGE("\nValidating submodules...")
  26. FILE(READ "${CMAKE_SOURCE_DIR}/.gitmodules" SUBMODULE_DATA)
  27. # Force English locale
  28. SET(LC_ALL_BACKUP "$ENV{LC_ALL}")
  29. SET(LANG_BACKUP "$ENV{LANG}")
  30. SET(ENV{LC_ALL} "C")
  31. SET(ENV{LANG} "en_US")
  32. # Assume alpha-numeric paths
  33. STRING(REGEX MATCHALL "path = [-0-9A-Za-z/]+" SUBMODULE_LIST ${SUBMODULE_DATA})
  34. STRING(REGEX MATCHALL "url = [.:%-0-9A-Za-z/]+" SUBMODULE_URL_LIST ${SUBMODULE_DATA})
  35. FOREACH(_part ${SUBMODULE_LIST})
  36. STRING(REPLACE "path = " "" SUBMODULE_PATH ${_part})
  37. LIST(FIND SUBMODULE_LIST ${_part} SUBMODULE_INDEX)
  38. LIST(GET SUBMODULE_URL_LIST ${SUBMODULE_INDEX} _url)
  39. STRING(REPLACE "url = " "" SUBMODULE_URL ${_url})
  40. # Remove submodules from validation as specified in -DSKIP_SUBMODULES=foo;bar
  41. SET(SKIP false)
  42. IF(SKIP_SUBMODULES)
  43. FOREACH(_skip ${SKIP_SUBMODULES})
  44. IF(${SUBMODULE_PATH} MATCHES ${_skip})
  45. MESSAGE("-- Skipping ${SUBMODULE_PATH} matches \"${_skip}\"")
  46. SET(SKIP true)
  47. ENDIF()
  48. ENDFOREACH()
  49. ENDIF()
  50. IF(NOT SKIP)
  51. LIST(INSERT SUBMODULE_LIST ${SUBMODULE_INDEX} ${SUBMODULE_PATH})
  52. LIST(INSERT SUBMODULE_URL_LIST ${SUBMODULE_INDEX} ${SUBMODULE_URL})
  53. ENDIF()
  54. LIST(REMOVE_ITEM SUBMODULE_LIST ${_part})
  55. LIST(REMOVE_ITEM SUBMODULE_URL_LIST ${_url})
  56. ENDFOREACH()
  57. # Once called, status is stored in GIT_RESULT respectively.
  58. # Note: Git likes to write to stderr. Don't assume stderr is error; Check GIT_RESULT instead.
  59. MACRO(GIT_SUBMODULE SUBMODULE_PATH FORCE_DEINIT FORCE_REMOTE)
  60. FIND_PACKAGE(Git REQUIRED)
  61. # Handle missing commits
  62. SET(FORCE_REMOTE_FLAG "${FORCE_REMOTE}")
  63. IF(FORCE_REMOTE_FLAG)
  64. MESSAGE("-- Adding remote submodulefix to ${SUBMODULE_PATH}")
  65. EXECUTE_PROCESS(
  66. COMMAND ${GIT_EXECUTABLE} remote rm submodulefix
  67. COMMAND ${GIT_EXECUTABLE} remote add submodulefix ${FORCE_REMOTE}
  68. COMMAND ${GIT_EXECUTABLE} fetch submodulefix
  69. WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/${SUBMODULE_PATH}
  70. OUTPUT_QUIET ERROR_QUIET
  71. )
  72. # Recurse
  73. GIT_SUBMODULE(${SUBMODULE_PATH} false false)
  74. ELSEIF(${FORCE_DEINIT})
  75. MESSAGE("-- Resetting ${SUBMODULE_PATH}")
  76. EXECUTE_PROCESS(
  77. COMMAND ${GIT_EXECUTABLE} submodule deinit -f ${CMAKE_SOURCE_DIR}/${SUBMODULE_PATH}
  78. WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  79. OUTPUT_QUIET
  80. )
  81. # Recurse
  82. GIT_SUBMODULE(${SUBMODULE_PATH} false false)
  83. ELSE()
  84. # Try to use the depth switch
  85. SET(DEPTH_CMD "")
  86. MESSAGE("-- Fetching ${SUBMODULE_PATH}")
  87. IF(DEPTH_VALUE)
  88. SET(DEPTH_CMD "--depth" )
  89. MESSAGE("-- Fetching ${SUBMODULE_PATH} @ --depth ${DEPTH_VALUE}")
  90. ENDIF()
  91. EXECUTE_PROCESS(
  92. COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive ${DEPTH_CMD} ${DEPTH_VALUE} ${CMAKE_SOURCE_DIR}/${SUBMODULE_PATH}
  93. WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  94. RESULT_VARIABLE GIT_RESULT
  95. OUTPUT_VARIABLE GIT_STDOUT
  96. ERROR_VARIABLE GIT_STDERR
  97. )
  98. SET(GIT_MESSAGE "${GIT_STDOUT}${GIT_STDERR}")
  99. MESSAGE("${GIT_MESSAGE}")
  100. ENDIF()
  101. ENDMACRO()
  102. SET(MISSING_COMMIT_PHRASES "no such remote ref;reference is not a tree;unadvertised object")
  103. SET(RETRY_PHRASES "Failed to recurse;cannot create directory;already exists;${MISSING_COMMIT_PHRASES}")
  104. # Attempt to do lazy clone
  105. FOREACH(_submodule ${SUBMODULE_LIST})
  106. STRING(REPLACE "/" ";" PATH_PARTS ${_submodule})
  107. LIST(REVERSE PATH_PARTS)
  108. LIST(GET PATH_PARTS 0 SUBMODULE_NAME)
  109. MESSAGE("-- Checking ${SUBMODULE_NAME}...")
  110. SET(CRUMB_FOUND false)
  111. FOREACH(_crumb ${VALID_CRUMBS})
  112. IF(EXISTS "${CMAKE_SOURCE_DIR}/${_submodule}/${_crumb}")
  113. SET(CRUMB_FOUND true)
  114. MESSAGE("-- Found ${_submodule}/${_crumb}")
  115. BREAK()
  116. ENDIF()
  117. ENDFOREACH()
  118. IF(NOT CRUMB_FOUND)
  119. GIT_SUBMODULE(${_submodule} false false)
  120. SET(COUNTED 0)
  121. SET(COUNTING "")
  122. # Handle edge-cases where submodule didn't clone properly or re-uses a non-empty directory
  123. WHILE(NOT GIT_RESULT EQUAL 0 AND COUNTED LESS MAX_ATTEMPTS)
  124. LIST(APPEND COUNTING "x")
  125. LIST(LENGTH COUNTING COUNTED)
  126. SET(MISSING_COMMIT false)
  127. FOREACH(_phrase ${MISSING_COMMIT_PHRASES})
  128. IF("${GIT_MESSAGE}" MATCHES "${_phrase}")
  129. SET(MISSING_COMMIT true)
  130. BREAK()
  131. ENDIF()
  132. ENDFOREACH()
  133. FOREACH(_phrase ${RETRY_PHRASES})
  134. IF(${MISSING_COMMIT})
  135. LIST(FIND SUBMODULE_LIST ${_submodule} SUBMODULE_INDEX)
  136. LIST(GET SUBMODULE_URL_LIST ${SUBMODULE_INDEX} SUBMODULE_URL)
  137. MESSAGE("-- Retrying ${_submodule} using 'remote add submodulefix' (attempt ${COUNTED} of ${MAX_ATTEMPTS})...")
  138. GIT_SUBMODULE(${_submodule} false "${SUBMODULE_URL}")
  139. BREAK()
  140. ELSEIF("${GIT_MESSAGE}" MATCHES "${_phrase}")
  141. MESSAGE("-- Retrying ${_submodule} using 'deinit' (attempt ${COUNTED} of ${MAX_ATTEMPTS})...")
  142. # Shallow submodules were introduced in 1.8.4
  143. # Shallow commits can fail to clone from non-default branches, only try once
  144. IF(GIT_VERSION_STRING VERSION_GREATER "1.8.3" AND COUNTED LESS 2)
  145. # Try a shallow submodule clone
  146. ELSE()
  147. UNSET(DEPTH_VALUE)
  148. ENDIF()
  149. GIT_SUBMODULE(${_submodule} true false)
  150. BREAK()
  151. ENDIF()
  152. ENDFOREACH()
  153. ENDWHILE()
  154. IF(NOT GIT_RESULT EQUAL 0)
  155. MESSAGE(FATAL_ERROR "${GIT_EXECUTABLE} exited with status of ${GIT_RESULT}")
  156. ENDIF()
  157. ENDIF()
  158. ENDFOREACH()
  159. MESSAGE("-- Done validating submodules.\n")
  160. # Reset locale
  161. SET(ENV{LC_ALL} "${LC_ALL_BACKUP}")
  162. SET(ENV{LANG} "${LANG_BACKUP}")