blob: b022217598adbb930c3b4bdccfacf8c1a6d754c9 [file] [log] [blame]
Ryan Prichard7aea7e92022-01-13 17:30:17 -08001# 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)