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