-
Notifications
You must be signed in to change notification settings - Fork 1
/
remove_unsupported_libraries.rb
449 lines (360 loc) · 14.8 KB
/
remove_unsupported_libraries.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# Source: https://gist.github.com/fermoya/f9be855ad040d5545ae3cb254ed201e4
###### CONSISTENCY BETWEEN MACOS AND IOS #####
#
# In order to use the same PodFile with MacOS, we need to unlink the libraries that do not support Catalyst, filter
# files in native targets build phases, filter dependencies and make sure the unsupported frameworks along with their
# their bundle resources are not included in the final archive. For that, we use `platform_filter` to specify 'ios' and
# 'OTHER_LDFLAGS[sdk=iphone*]' to link those libraries for iPhone and iPad. Besides, we modify "*frameworks.sh" and
# "*resrouces.sh" to skip installation for architecture x86_64.
#
# *Notice*: 'sdk=iphone*' excludes macOS, even though Catalyst is compiled with iOS SDK.
#
# ADDING A NEW LIBRARY
#
# Pass the name of the new library to the script
#
###### RESOURCES #######
#
# https://www.bitbuildr.com/tech-blog/mac-catalyst-porting-an-app-using-crashlytics-firebase - Article that inspired this script
# https://github.com/CocoaPods/Xcodeproj - Xcode configuration using ruby. This Framework is already included on cocoapods environment
# https://www.rubydoc.info/gems/xcodeproj/Xcodeproj/Project/Object/AbstractTarget Wiki for Xcodeproj
#
include Xcodeproj::Project::Object
include Pod
$verbose = false
def loggs string
if $verbose
puts string
end
return
end
# EXTENSIONS
class String
def filter_lines
lines = []
each_line do |line|
if yield line
lines = lines + [line]
end
end
return lines
end
end
class PBXNativeTarget
###### STEP 4 ######
# In "Pods-" targets, modify "*frameworks.sh" to not install unsupported frameworks for platform architectures
def uninstall_frameworks frameworks, platform, configurations
uninstall frameworks, "#{name}-frameworks.sh", platform.architectures, configurations
end
###### STEP 5 ######
# In "Pods-" targets, modify "*resources.sh" to not install unsupported frameworks for platform architectures
def uninstall_resources resources, platform, configurations
uninstall resources, "#{name}-resources.sh", platform.architectures, configurations
end
def support_files_folder
build_configurations.filter do |config| not config.base_configuration_reference.nil? end.first.base_configuration_reference.real_path.parent
end
@private
def uninstall keys, file_name, architectures, configurations=nil
configurations = configurations.nil? ? build_configurations.map { |b| b.name } : configurations
keys = keys.to_set.to_a
loggs "\t\t\tUninstalling for configurations: #{configurations}"
if support_files_folder.nil?
loggs "\t\t\tNothing to uninstall"
return
end
script_path = support_files_folder.join file_name
if !script_path.exist?
loggs "\t\t\tNothing to uninstall"
return
end
script = File.read(script_path)
snippets = script.scan(/if \[\[ \"\$CONFIGURATION\" [\S\s]*?(?=fi\n)fi/)
condition = architectures.map do |arch| "[ \"$ARCHS\" != \"#{arch}\" ]" end.reduce("") do |total, piece| total.empty? ? piece : total + " || " + piece end
changed = false
snippets.filter do |snippet|
configurations.map do |string| snippet.include? string end.reduce(false) do |total, condition| total = total || condition end
end.each do |snippet|
new_snippet = snippet.clone
keys.each do |key|
lines_to_replace = snippet.filter_lines do |line| line.include? "#{key}" end.to_set.to_a
unless lines_to_replace.empty?
changed = true
lines_to_replace.each do |line|
new_snippet.gsub! line, "\tif #{condition}; then \n\t#{line}\tfi\n"
end
end
end
script.gsub! snippet, new_snippet
end
if changed
File.open(script_path, "w") { |file| file << script }
end
loggs "\t\t\t#{changed ? "Succeded" : "Nothing to uninstall"}"
end
###### STEP 1 ######
# In native target's build phases, add platform filter to:
# - Resources
# - Compile Sources
# - Frameworks
# - Headers
def add_platform_filter_to_build_phases platform
loggs "\t\t- Filtering resources"
resources_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end
loggs "\t\t- Filtering compile sources"
source_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end
loggs "\t\t- Filtering frameworks"
frameworks_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end
loggs "\t\t- Filtering headers"
headers_build_phase.files.to_a.map do |build_file| build_file.platform_filter = platform.name end
end
end
class PodTarget
def module_name
string = name.clone.gsub! /-iOS[0-9]+(\.[0-9])+$/, ''
return string.nil? ? name : string
end
def resources
return file_accessors.flat_map do |accessor| accessor.resources end.map do |path| "#{path.basename}" end
end
def vendor_products
return file_accessors.flat_map do |accessor|
accessor.vendored_frameworks + accessor.vendored_libraries
end.map do |s| s.basename
end.map do |s|
name = "#{s}"
if name.include? "framework"
PodDependency.newFramework name.sub(".framework", "")
else
PodDependency.newLibrary name.sub("lib", "").sub(".a", "")
end
end
end
def frameworks
return file_accessors.flat_map do |accessor|
accessor.spec_consumer.frameworks.map do |name| PodDependency.newFramework name end + accessor.spec_consumer.libraries.map do |name| PodDependency.newLibrary name end
end
end
end
class PBXTargetDependency
def module_name
string = name.clone.gsub! /-iOS[0-9]+(\.[0-9])+$/, ''
return string.nil? ? name : string
end
end
class AbstractTarget
def module_name
string = name.clone.gsub! /-iOS[0-9]+(\.[0-9])+$/, ''
return string.nil? ? name : string
end
###### STEP 2 ######
# In all targets (aggregates + native), filter dependencies
def add_platform_filter_to_dependencies platform
loggs "\t\t- Filtering dependencies"
dependencies.each do |dependency|
dependency.platform_filter = platform.name
end
end
###### STEP 3 ######
# If any unsupported library, then flag as platform-dependant for every build configuration
def flag_libraries libraries, platform
loggs "\tTarget: #{name}"
build_configurations.filter do |config| not config.base_configuration_reference.nil?
end.each do |config|
loggs "\t\tScheme: #{config.name}"
xcconfig_path = config.base_configuration_reference.real_path
xcconfig = File.read(xcconfig_path)
changed = false
libraries.each do |framework|
if xcconfig.include? framework
xcconfig.gsub!(framework, '')
unless xcconfig.include? "OTHER_LDFLAGS[sdk=#{platform.sdk}]"
changed = true
xcconfig += "OTHER_LDFLAGS[sdk=#{platform.sdk}] = $(inherited) -ObjC "
end
xcconfig += framework + ' '
end
end
File.open(xcconfig_path, "w") { |file| file << xcconfig }
loggs "\t\t\t#{changed ? "Succeded" : "Nothing to flag"}"
end
end
def to_dependency
# We return both as we don't know if build as library or framework
return [PodDependency.newFramework(module_name), PodDependency.newLibrary(module_name)]
end
# Dependencies contained in Other Linker Flags
def other_linker_flags_dependencies
frameworks = Array.new
libraries = Array.new
config = build_configurations.filter do |config| not config.base_configuration_reference.nil? end.first
xcconfig_path = config.base_configuration_reference.real_path
xcconfig = File.read(xcconfig_path)
xcconfig.gsub!(/\r\n?/, "\n")
xcconfig.each_line do |line|
if line.start_with? 'OTHER_LDFLAGS'
frameworks = frameworks + line.split("-framework").map do |s|
s.strip.delete("\n") end.filter do |s|
s.strip.start_with? '"' end
libraries = libraries + line.split("-l").filter do |s| s.strip.start_with? '"' end.map do |s| s.strip.split(' ').first end
end
end
libraries = libraries.map do |name| PodDependency.newLibrary(name.gsub!("\"", "")) end
frameworks = frameworks.map do |name| PodDependency.newFramework(name.gsub!("\"", "")) end
return OtherLinkerFlagsDependencies.new libraries, frameworks
end
end
# HELPER CLASSES
class PodDependency
attr_reader :name
attr_reader :type
def link
if library?
return "-l\"#{name}\""
else
return "-framework \"#{name}\""
end
end
def library?
return type == "library"
end
def framework?
return type == "framework"
end
def self.newFramework name
return PodDependency.new(name, "framework")
end
def self.newLibrary name
return PodDependency.new(name, "library")
end
def ==(other)
name == other.name && type == other.type
end
def eql?(other)
name == other.name
end
private
def initialize(name, type)
@name = name
@type = type
end
end
class OtherLinkerFlagsDependencies
attr_reader :libraries
attr_reader :frameworks
def initialize(libraries = [], frameworks = [])
@libraries = libraries
@frameworks = frameworks
end
def combine dependencies
frameworks = (dependencies.frameworks + @frameworks).to_set.to_a
libraries = (dependencies.libraries + @libraries).to_set.to_a
return OtherLinkerFlagsDependencies.new libraries, frameworks
end
def dependencies
libraries + frameworks
end
end
class OSPlatform
attr_reader :sdk
attr_reader :name
attr_reader :architectures
def self.ios
OSPlatform.new 'ios', 'iphone*', ['arm64', 'armv7s', 'armv7']
end
def self.macos
OSPlatform.new 'macos', 'macosx*', ['x86_64']
end
def self.wtachos
OSPlatform.new 'watchos', 'watchos*', ['arm64_32', 'armv7k']
end
def self.tvos
OSPlatform.new 'tvos', 'appletvos*', ['arm64']
end
private
def initialize(name, sdk, architectures)
@name = name
@sdk = sdk
@architectures = architectures
end
end
# SCRIPT
class Installer
def configure_support_catalyst pod_names_to_keep, pod_names_to_remove, configurations=nil
###### Variable definition ######
targets = pods_project.targets
pod_names_to_remove = pod_names_to_remove.map do |name| name.sub('/', '') end
pod_names_to_keep = pod_names_to_keep.map do |name| name.sub('/', '') end
pod_names_to_keep = recursive_dependencies pod_names_to_keep
pod_names_to_remove = recursive_dependencies(pod_names_to_remove).filter do |name| !pod_names_to_keep.include? name end
pod_targets_to_keep = pod_targets.filter do |pod| pod_names_to_keep.include? pod.module_name end # PodTarget
pod_targets_to_remove = pod_targets.filter do |pod| pod_names_to_remove.include? pod.module_name end # PodTarget
loggs "\n#### Unsupported Libraries ####\n#{pod_names_to_remove}\n"
targets_to_remove = targets.filter do |target| pod_names_to_remove.include?(target.module_name) end # AbstractTarget
pods_targets = targets.filter do |target| target.name.start_with? "Pods-" end # AbstractTarget
cross_platform_targets = targets.filter do |target| !targets_to_remove.include?(target) && !pods_targets.include?(target) end # AbstractTarget
###### Determine which dependencies should be removed ######
dependencies_to_keep = cross_platform_targets.reduce(OtherLinkerFlagsDependencies.new) do |dependencies, target|
dependencies.combine target.other_linker_flags_dependencies
end.dependencies
# [PodDependency]
dependencies_to_keep = dependencies_to_keep + cross_platform_targets.flat_map do |target| target.to_dependency end + pod_targets_to_keep.flat_map do |pod| pod.vendor_products + pod.frameworks end
dependencies_to_remove = targets_to_remove.reduce(OtherLinkerFlagsDependencies.new) do |dependencies, target|
dependencies.combine target.other_linker_flags_dependencies
end.dependencies
# [PodDependency]
dependencies_to_remove = dependencies_to_remove + targets_to_remove.flat_map do |target| target.to_dependency end + pod_targets_to_remove.flat_map do |pod| pod.vendor_products + pod.frameworks end
dependencies_to_remove = dependencies_to_remove.filter do |d| !dependencies_to_keep.include? d end
###### CATALYST NOT SUPPORTED LINKS ######
unsupported_links = dependencies_to_remove.map do |d| d.link end.to_set.to_a
loggs "#### Unsupported dependencies ####\n"
loggs "#{dependencies_to_remove.map do |d| d.name end.to_set.to_a }\n\n"
###### CATALYST NOT SUPPORTED FRAMEWORKS AND RESOURCES
frameworks_to_uninstall = dependencies_to_remove.filter do |d| d.framework? end.map do |d| "#{d.name}.framework" end.to_set.to_a
resources_to_uninstall = pod_targets_to_remove.flat_map do |pod| pod.resources end.to_set.to_a
loggs "#### Frameworks not to be included in the Archive ####\n"
loggs "#{frameworks_to_uninstall}\n\n"
loggs "#### Resources not to be included in the Archive ####\n"
loggs "#{resources_to_uninstall}\n\n"
###### OTHER LINKER FLAGS -> to iphone* ######
loggs "#### Flagging unsupported libraries ####"
targets.each do |target| target.flag_libraries unsupported_links, OSPlatform.ios end
###### BUILD_PHASES AND DEPENDENCIES -> PLATFORM_FILTER 'ios' ######
loggs "\n#### Filtering build phases ####"
targets_to_remove.filter do |target|
pods_project.native_targets.include? target
end.each do |target|
loggs "\tTarget: #{target.name}"
target.add_platform_filter_to_build_phases OSPlatform.ios
target.add_platform_filter_to_dependencies OSPlatform.ios
end
loggs "\n#### Filtering dependencies ####"
targets_to_remove.filter do |target|
!pods_project.native_targets.include? target
end.each do |target|
loggs "\tTarget: #{target.name}"
target.add_platform_filter_to_dependencies OSPlatform.ios
end
###### FRAMEWORKS AND RESOURCES SCRIPT -> if [ "$ARCHS" != "x86_64" ]; then #######
loggs "\n#### Chagings frameworks and resources script ####"
pods_targets.each do |target|
loggs "\tTarget: #{target.name}"
loggs "\t\t-Uninstalling frameworks"
target.uninstall_frameworks frameworks_to_uninstall, OSPlatform.macos, configurations
loggs "\t\t-Uninstalling resources"
target.uninstall_resources resources_to_uninstall, OSPlatform.macos, configurations
end
end
@private
def recursive_dependencies to_filter_names
targets = pods_project.targets
targets_to_remove = targets.filter do |target| to_filter_names.include? target.module_name end
dependencies = targets_to_remove.flat_map do |target| target.dependencies end
dependencies_names = dependencies.map do |d| d.module_name end
if dependencies.empty?
return to_filter_names + dependencies_names
else
return to_filter_names + recursive_dependencies(dependencies_names)
end
end
end