CMakeIOSInstallCombined.cmake 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. # Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. # file Copyright.txt or https://cmake.org/licensing for details.
  3. cmake_policy(PUSH)
  4. cmake_policy(SET CMP0057 NEW) # if IN_LIST
  5. # Function to print messages of this module
  6. function(_ios_install_combined_message)
  7. message(STATUS "[iOS combined] " ${ARGN})
  8. endfunction()
  9. # Get build settings for the current target/config/SDK by running
  10. # `xcodebuild -sdk ... -showBuildSettings` and parsing it's output
  11. function(_ios_install_combined_get_build_setting sdk variable resultvar)
  12. if("${sdk}" STREQUAL "")
  13. message(FATAL_ERROR "`sdk` is empty")
  14. endif()
  15. if("${variable}" STREQUAL "")
  16. message(FATAL_ERROR "`variable` is empty")
  17. endif()
  18. if("${resultvar}" STREQUAL "")
  19. message(FATAL_ERROR "`resultvar` is empty")
  20. endif()
  21. set(
  22. cmd
  23. xcodebuild -showBuildSettings
  24. -sdk "${sdk}"
  25. -target "${CURRENT_TARGET}"
  26. -config "${CURRENT_CONFIG}"
  27. )
  28. execute_process(
  29. COMMAND ${cmd}
  30. WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
  31. RESULT_VARIABLE result
  32. OUTPUT_VARIABLE output
  33. )
  34. if(NOT result EQUAL 0)
  35. message(FATAL_ERROR "Command failed (${result}): ${cmd}")
  36. endif()
  37. if(NOT output MATCHES " ${variable} = ([^\n]*)")
  38. if("${variable}" STREQUAL "VALID_ARCHS")
  39. # VALID_ARCHS may be unset by user for given SDK
  40. # (e.g. for build without simulator).
  41. set("${resultvar}" "" PARENT_SCOPE)
  42. return()
  43. else()
  44. message(FATAL_ERROR "${variable} not found.")
  45. endif()
  46. endif()
  47. set("${resultvar}" "${CMAKE_MATCH_1}" PARENT_SCOPE)
  48. endfunction()
  49. # Get architectures of given SDK (iphonesimulator/iphoneos)
  50. function(_ios_install_combined_get_valid_archs sdk resultvar)
  51. if("${resultvar}" STREQUAL "")
  52. message(FATAL_ERROR "`resultvar` is empty")
  53. endif()
  54. _ios_install_combined_get_build_setting("${sdk}" "VALID_ARCHS" valid_archs)
  55. separate_arguments(valid_archs)
  56. list(REMOVE_ITEM valid_archs "") # remove empty elements
  57. list(REMOVE_DUPLICATES valid_archs)
  58. string(REPLACE ";" " " printable "${valid_archs}")
  59. _ios_install_combined_message("Architectures (${sdk}): ${printable}")
  60. set("${resultvar}" "${valid_archs}" PARENT_SCOPE)
  61. endfunction()
  62. # Make both arch lists a disjoint set by preferring the current SDK
  63. # (starting with Xcode 12 arm64 is available as device and simulator arch on iOS)
  64. function(_ios_install_combined_prune_common_archs corr_sdk corr_archs_var this_archs_var)
  65. list(REMOVE_ITEM ${corr_archs_var} ${${this_archs_var}})
  66. string(REPLACE ";" " " printable "${${corr_archs_var}}")
  67. _ios_install_combined_message("Architectures (${corr_sdk}) after pruning: ${printable}")
  68. set("${corr_archs_var}" "${${corr_archs_var}}" PARENT_SCOPE)
  69. endfunction()
  70. # Final target can contain more architectures that specified by SDK. This
  71. # function will run 'lipo -info' and parse output. Result will be returned
  72. # as a CMake list.
  73. function(_ios_install_combined_get_real_archs filename resultvar)
  74. set(cmd "${_lipo_path}" -info "${filename}")
  75. execute_process(
  76. COMMAND ${cmd}
  77. RESULT_VARIABLE result
  78. OUTPUT_VARIABLE output
  79. ERROR_VARIABLE output
  80. OUTPUT_STRIP_TRAILING_WHITESPACE
  81. ERROR_STRIP_TRAILING_WHITESPACE
  82. )
  83. if(NOT result EQUAL 0)
  84. message(
  85. FATAL_ERROR "Command failed (${result}): ${cmd}\n\nOutput:\n${output}"
  86. )
  87. endif()
  88. if(NOT output MATCHES "(Architectures in the fat file: [^\n]+ are|Non-fat file: [^\n]+ is architecture): ([^\n]*)")
  89. message(FATAL_ERROR "Could not detect architecture from: ${output}")
  90. endif()
  91. separate_arguments(CMAKE_MATCH_2)
  92. set(${resultvar} ${CMAKE_MATCH_2} PARENT_SCOPE)
  93. endfunction()
  94. # Run build command for the given SDK
  95. function(_ios_install_combined_build sdk)
  96. if("${sdk}" STREQUAL "")
  97. message(FATAL_ERROR "`sdk` is empty")
  98. endif()
  99. _ios_install_combined_message("Build `${CURRENT_TARGET}` for `${sdk}`")
  100. execute_process(
  101. COMMAND
  102. "${CMAKE_COMMAND}"
  103. --build
  104. .
  105. --target "${CURRENT_TARGET}"
  106. --config ${CURRENT_CONFIG}
  107. --
  108. -sdk "${sdk}"
  109. WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
  110. RESULT_VARIABLE result
  111. )
  112. if(NOT result EQUAL 0)
  113. message(FATAL_ERROR "Build failed")
  114. endif()
  115. endfunction()
  116. # Remove given architecture from file. This step needed only in rare cases
  117. # when target was built in "unusual" way. Emit warning message.
  118. function(_ios_install_combined_remove_arch lib arch)
  119. _ios_install_combined_message(
  120. "Warning! Unexpected architecture `${arch}` detected and will be removed "
  121. "from file `${lib}`")
  122. set(cmd "${_lipo_path}" -remove ${arch} -output ${lib} ${lib})
  123. execute_process(
  124. COMMAND ${cmd}
  125. RESULT_VARIABLE result
  126. OUTPUT_VARIABLE output
  127. ERROR_VARIABLE output
  128. OUTPUT_STRIP_TRAILING_WHITESPACE
  129. ERROR_STRIP_TRAILING_WHITESPACE
  130. )
  131. if(NOT result EQUAL 0)
  132. message(
  133. FATAL_ERROR "Command failed (${result}): ${cmd}\n\nOutput:\n${output}"
  134. )
  135. endif()
  136. endfunction()
  137. # Check that 'lib' contains only 'archs' architectures (remove others).
  138. function(_ios_install_combined_keep_archs lib archs)
  139. _ios_install_combined_get_real_archs("${lib}" real_archs)
  140. set(archs_to_remove ${real_archs})
  141. list(REMOVE_ITEM archs_to_remove ${archs})
  142. foreach(x ${archs_to_remove})
  143. _ios_install_combined_remove_arch("${lib}" "${x}")
  144. endforeach()
  145. endfunction()
  146. function(_ios_install_combined_detect_associated_sdk corr_sdk_var)
  147. if("${PLATFORM_NAME}" STREQUAL "")
  148. message(FATAL_ERROR "PLATFORM_NAME should not be empty")
  149. endif()
  150. set(all_platforms "$ENV{SUPPORTED_PLATFORMS}")
  151. if("${SUPPORTED_PLATFORMS}" STREQUAL "")
  152. _ios_install_combined_get_build_setting(
  153. ${PLATFORM_NAME} SUPPORTED_PLATFORMS all_platforms)
  154. if("${all_platforms}" STREQUAL "")
  155. message(FATAL_ERROR
  156. "SUPPORTED_PLATFORMS not set as an environment variable nor "
  157. "able to be determined from project")
  158. endif()
  159. endif()
  160. separate_arguments(all_platforms)
  161. if(NOT PLATFORM_NAME IN_LIST all_platforms)
  162. message(FATAL_ERROR "`${PLATFORM_NAME}` not found in `${all_platforms}`")
  163. endif()
  164. list(REMOVE_ITEM all_platforms "" "${PLATFORM_NAME}")
  165. list(LENGTH all_platforms all_platforms_length)
  166. if(NOT all_platforms_length EQUAL 1)
  167. message(FATAL_ERROR "Expected one element: ${all_platforms}")
  168. endif()
  169. set(${corr_sdk_var} "${all_platforms}" PARENT_SCOPE)
  170. endfunction()
  171. # Create combined binary for the given target.
  172. #
  173. # Preconditions:
  174. # * Target already installed at ${destination}
  175. # for the ${PLATFORM_NAME} platform
  176. #
  177. # This function will:
  178. # * Run build for the lacking platform, i.e. opposite to the ${PLATFORM_NAME}
  179. # * Fuse both libraries by running lipo
  180. function(ios_install_combined target destination)
  181. if("${target}" STREQUAL "")
  182. message(FATAL_ERROR "`target` is empty")
  183. endif()
  184. if("${destination}" STREQUAL "")
  185. message(FATAL_ERROR "`destination` is empty")
  186. endif()
  187. if(NOT IS_ABSOLUTE "${destination}")
  188. message(FATAL_ERROR "`destination` is not absolute: ${destination}")
  189. endif()
  190. if(IS_DIRECTORY "${destination}" OR IS_SYMLINK "${destination}")
  191. message(FATAL_ERROR "`destination` is no regular file: ${destination}")
  192. endif()
  193. if("${CMAKE_BINARY_DIR}" STREQUAL "")
  194. message(FATAL_ERROR "`CMAKE_BINARY_DIR` is empty")
  195. endif()
  196. if(NOT IS_DIRECTORY "${CMAKE_BINARY_DIR}")
  197. message(FATAL_ERROR "Is not a directory: ${CMAKE_BINARY_DIR}")
  198. endif()
  199. if("${CMAKE_INSTALL_CONFIG_NAME}" STREQUAL "")
  200. message(FATAL_ERROR "CMAKE_INSTALL_CONFIG_NAME is empty")
  201. endif()
  202. set(cmd xcrun -f lipo)
  203. # Do not merge OUTPUT_VARIABLE and ERROR_VARIABLE since latter may contain
  204. # some diagnostic information even for the successful run.
  205. execute_process(
  206. COMMAND ${cmd}
  207. RESULT_VARIABLE result
  208. OUTPUT_VARIABLE output
  209. ERROR_VARIABLE error_output
  210. OUTPUT_STRIP_TRAILING_WHITESPACE
  211. ERROR_STRIP_TRAILING_WHITESPACE
  212. )
  213. if(NOT result EQUAL 0)
  214. message(
  215. FATAL_ERROR "Command failed (${result}): ${cmd}\n\nOutput:\n${output}\nOutput(error):\n${error_output}"
  216. )
  217. endif()
  218. set(_lipo_path ${output})
  219. list(LENGTH _lipo_path len)
  220. if(NOT len EQUAL 1)
  221. message(FATAL_ERROR "Unexpected xcrun output: ${_lipo_path}")
  222. endif()
  223. if(NOT EXISTS "${_lipo_path}")
  224. message(FATAL_ERROR "File not found: ${_lipo_path}")
  225. endif()
  226. set(CURRENT_CONFIG "${CMAKE_INSTALL_CONFIG_NAME}")
  227. set(CURRENT_TARGET "${target}")
  228. _ios_install_combined_message("Target: ${CURRENT_TARGET}")
  229. _ios_install_combined_message("Config: ${CURRENT_CONFIG}")
  230. _ios_install_combined_message("Destination: ${destination}")
  231. # Get SDKs
  232. _ios_install_combined_detect_associated_sdk(corr_sdk)
  233. # Get architectures of the target
  234. _ios_install_combined_get_valid_archs("${PLATFORM_NAME}" this_valid_archs)
  235. _ios_install_combined_get_valid_archs("${corr_sdk}" corr_valid_archs)
  236. _ios_install_combined_prune_common_archs("${corr_sdk}" corr_valid_archs this_valid_archs)
  237. # Return if there are no valid architectures for the SDK.
  238. # (note that library already installed)
  239. if("${corr_valid_archs}" STREQUAL "")
  240. _ios_install_combined_message(
  241. "No architectures detected for `${corr_sdk}` (skip)"
  242. )
  243. return()
  244. endif()
  245. # Trigger build of corresponding target
  246. _ios_install_combined_build("${corr_sdk}")
  247. # Get location of the library in build directory
  248. _ios_install_combined_get_build_setting(
  249. "${corr_sdk}" "CONFIGURATION_BUILD_DIR" corr_build_dir)
  250. _ios_install_combined_get_build_setting(
  251. "${corr_sdk}" "EXECUTABLE_PATH" corr_executable_path)
  252. set(corr "${corr_build_dir}/${corr_executable_path}")
  253. _ios_install_combined_keep_archs("${corr}" "${corr_valid_archs}")
  254. _ios_install_combined_keep_archs("${destination}" "${this_valid_archs}")
  255. _ios_install_combined_message("Current: ${destination}")
  256. _ios_install_combined_message("Corresponding: ${corr}")
  257. set(cmd "${_lipo_path}" -create ${corr} ${destination} -output ${destination})
  258. execute_process(
  259. COMMAND ${cmd}
  260. WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
  261. RESULT_VARIABLE result
  262. )
  263. if(NOT result EQUAL 0)
  264. message(FATAL_ERROR "Command failed: ${cmd}")
  265. endif()
  266. _ios_install_combined_message("Install done: ${destination}")
  267. endfunction()
  268. cmake_policy(POP)