-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
aggregate_target.rb
558 lines (494 loc) · 21.2 KB
/
aggregate_target.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
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
require 'cocoapods/xcode/framework_paths'
require 'cocoapods/xcode/xcframework'
module Pod
# Stores the information relative to the target used to cluster the targets
# of the single Pods. The client targets will then depend on this one.
#
class AggregateTarget < Target
# Product types where the product's frameworks must be embedded in a host target
#
EMBED_FRAMEWORKS_IN_HOST_TARGET_TYPES = [:app_extension, :framework, :static_library, :messages_extension,
:watch_extension, :xpc_service].freeze
# @return [TargetDefinition] the target definition of the Podfile that
# generated this target.
#
attr_reader :target_definition
# @return [Pathname] the folder where the client is stored used for
# computing the relative paths. If integrating it should be the
# folder where the user project is stored, otherwise it should
# be the installation root.
#
attr_reader :client_root
# @return [Xcodeproj::Project] the user project that this target will
# integrate as identified by the analyzer.
#
attr_reader :user_project
# @return [Array<String>] the list of the UUIDs of the user targets that
# will be integrated by this target as identified by the analyzer.
#
# @note The target instances are not stored to prevent editing different
# instances.
#
attr_reader :user_target_uuids
# @return [Hash<String, Xcodeproj::Config>] Map from configuration name to
# configuration file for the target
#
# @note The configurations are generated by the {TargetInstaller} and
# used by {UserProjectIntegrator} to check for any overridden
# values.
#
attr_reader :xcconfigs
# @return [Array<PodTarget>] The dependencies for this target.
#
attr_reader :pod_targets
# @return [Array<AggregateTarget>] The aggregate targets whose pods this
# target must be able to import, but will not directly link against.
#
attr_reader :search_paths_aggregate_targets
# Initialize a new instance
#
# @param [Sandbox] sandbox @see Target#sandbox
# @param [BuildType] build_type @see Target#build_type
# @param [Hash{String=>Symbol}] user_build_configurations @see Target#user_build_configurations
# @param [Array<String>] archs @see Target#archs
# @param [Platform] platform @see #Target#platform
# @param [TargetDefinition] target_definition @see #target_definition
# @param [Pathname] client_root @see #client_root
# @param [Xcodeproj::Project] user_project @see #user_project
# @param [Array<String>] user_target_uuids @see #user_target_uuids
# @param [Hash{String=>Array<PodTarget>}] pod_targets_for_build_configuration @see #pod_targets_for_build_configuration
#
def initialize(sandbox, build_type, user_build_configurations, archs, platform, target_definition, client_root,
user_project, user_target_uuids, pod_targets_for_build_configuration)
super(sandbox, build_type, user_build_configurations, archs, platform)
raise "Can't initialize an AggregateTarget without a TargetDefinition!" if target_definition.nil?
raise "Can't initialize an AggregateTarget with an abstract TargetDefinition!" if target_definition.abstract?
@target_definition = target_definition
@client_root = client_root
@user_project = user_project
@user_target_uuids = user_target_uuids
@pod_targets_for_build_configuration = pod_targets_for_build_configuration
@pod_targets = pod_targets_for_build_configuration.values.flatten.uniq
@search_paths_aggregate_targets = []
@xcconfigs = {}
end
# Merges this aggregate target with additional pod targets that are part of embedded aggregate targets.
#
# @param [Hash{String=>Array<PodTarget>}] embedded_pod_targets_for_build_configuration
# The pod targets to merge with.
#
# @return [AggregateTarget] a new instance of this aggregate target with additional pod targets to be used from
# pod targets of embedded aggregate targets.
#
def merge_embedded_pod_targets(embedded_pod_targets_for_build_configuration)
merged = @pod_targets_for_build_configuration.merge(embedded_pod_targets_for_build_configuration) do |_, before, after|
(before + after).uniq
end
AggregateTarget.new(sandbox, build_type, user_build_configurations, archs, platform,
target_definition, client_root, user_project, user_target_uuids, merged).tap do |aggregate_target|
aggregate_target.search_paths_aggregate_targets.concat(search_paths_aggregate_targets).freeze
aggregate_target.mark_application_extension_api_only if application_extension_api_only
aggregate_target.mark_build_library_for_distribution if build_library_for_distribution
end
end
def build_settings(configuration_name = nil)
if configuration_name
@build_settings[configuration_name] ||
raise(ArgumentError, "#{self} does not contain a build setting for the #{configuration_name.inspect} configuration, only #{@build_settings.keys.inspect}")
else
@build_settings.each_value.first ||
raise(ArgumentError, "#{self} does not contain any build settings")
end
end
# @return [Boolean] True if the user_target refers to a
# library (framework, static or dynamic lib).
#
def library?
# Without a user_project, we can't say for sure
# that this is a library
return false if user_project.nil?
symbol_types = user_targets.map(&:symbol_type).uniq
unless symbol_types.count == 1
raise ArgumentError, "Expected single kind of user_target for #{name}. Found #{symbol_types.join(', ')}."
end
[:framework, :dynamic_library, :static_library].include? symbol_types.first
end
# @return [Boolean] True if the user_target's pods are
# for an extension and must be embedded in a host,
# target, otherwise false.
#
def requires_host_target?
# If we don't have a user_project, then we can't
# glean any info about how this target is going to
# be integrated, so return false since we can't know
# for sure that this target refers to an extension
# target that would require a host target
return false if user_project.nil?
symbol_types = user_targets.map(&:symbol_type).uniq
unless symbol_types.count == 1
raise ArgumentError, "Expected single kind of user_target for #{name}. Found #{symbol_types.join(', ')}."
end
EMBED_FRAMEWORKS_IN_HOST_TARGET_TYPES.include?(symbol_types[0])
end
# @return [String] the label for the target.
#
def label
target_definition.label.to_s
end
# @return [Podfile] The podfile which declares the dependency
#
def podfile
target_definition.podfile
end
# @return [Pathname] the path of the user project that this target will
# integrate as identified by the analyzer.
#
def user_project_path
user_project.path if user_project
end
# List all user targets that will be integrated by this #target.
#
# @return [Array<PBXNativeTarget>]
#
def user_targets
return [] unless user_project
user_target_uuids.map do |uuid|
native_target = user_project.objects_by_uuid[uuid]
unless native_target
raise Informative, '[Bug] Unable to find the target with ' \
"the `#{uuid}` UUID for the `#{self}` integration library"
end
native_target
end
end
# @param [String] build_configuration The build configuration for which the
# the pod targets should be returned.
#
# @return [Array<PodTarget>] the pod targets for the given build
# configuration.
#
def pod_targets_for_build_configuration(build_configuration)
@pod_targets_for_build_configuration[build_configuration] || []
end
# @return [Array<Specification>] The specifications used by this aggregate target.
#
def specs
pod_targets.flat_map(&:specs)
end
# @return [Hash{Symbol => Array<Specification>}] The pod targets for each
# build configuration.
#
def specs_by_build_configuration
result = {}
user_build_configurations.each_key do |build_configuration|
result[build_configuration] = pod_targets_for_build_configuration(build_configuration).
flat_map(&:specs)
end
result
end
# @return [Array<Specification::Consumer>] The consumers of the Pod.
#
def spec_consumers
specs.map { |spec| spec.consumer(platform) }
end
# @return [Boolean] Whether the target uses Swift code
#
def uses_swift?
pod_targets.any?(&:uses_swift?)
end
# @return [Boolean] Whether the target contains any resources
#
def includes_resources?
!resource_paths_by_config.each_value.all?(&:empty?)
end
# @return [Boolean] Whether the target contains any on demand resources
#
def includes_on_demand_resources?
!on_demand_resources.empty?
end
# @return [Boolean] Whether the target contains frameworks to be embedded into
# the user target
#
def includes_frameworks?
!framework_paths_by_config.each_value.all?(&:empty?)
end
# @return [Boolean] Whether the target contains xcframeworks to be embedded into
# the user target
#
def includes_xcframeworks?
!xcframeworks_by_config.each_value.all?(&:empty?)
end
# @return [Hash{String => Array<FrameworkPaths>}] The vendored dynamic artifacts and framework target
# input and output paths grouped by config
#
def framework_paths_by_config
@framework_paths_by_config ||= begin
framework_paths_by_config = {}
user_build_configurations.each_key do |config|
relevant_pod_targets = pod_targets_for_build_configuration(config)
framework_paths_by_config[config] = relevant_pod_targets.flat_map do |pod_target|
library_specs = pod_target.library_specs.map(&:name)
pod_target.framework_paths.values_at(*library_specs).flatten.compact.uniq
end
end
framework_paths_by_config
end
end
# @return [Hash{String => Array<Xcode::XCFramework>}] The vendored dynamic artifacts and framework target
# input and output paths grouped by config
#
def xcframeworks_by_config
@xcframeworks_by_config ||= begin
xcframeworks_by_config = {}
user_build_configurations.each_key do |config|
relevant_pod_targets = pod_targets_for_build_configuration(config)
xcframeworks_by_config[config] = relevant_pod_targets.flat_map do |pod_target|
library_specs = pod_target.library_specs.map(&:name)
pod_target.xcframeworks.values_at(*library_specs).flatten.compact.uniq
end
end
xcframeworks_by_config
end
end
# @return [Array<Pathname>] Uniqued On Demand Resources for this target.
#
# @note On Demand Resources are not separated by config as they are integrated directly into the users target via
# the resources build phase.
#
def on_demand_resources
@on_demand_resources ||= begin
pod_targets.flat_map do |pod_target|
library_file_accessors = pod_target.file_accessors.select { |fa| fa.spec.library_specification? }
library_file_accessors.flat_map(&:on_demand_resources_files)
end.uniq
end
end
# @return [Hash{String => Array<String>}] Uniqued Resources grouped by config
#
def resource_paths_by_config
@resource_paths_by_config ||= begin
relevant_pod_targets = pod_targets.reject do |pod_target|
pod_target.should_build? && pod_target.build_as_dynamic_framework?
end
user_build_configurations.each_key.each_with_object({}) do |config, resources_by_config|
targets = relevant_pod_targets & pod_targets_for_build_configuration(config)
resources_by_config[config] = targets.flat_map do |pod_target|
library_specs = pod_target.library_specs.map(&:name)
resource_paths = pod_target.resource_paths.values_at(*library_specs).flatten
if pod_target.build_as_static_framework?
built_product_dir = Pathname.new(pod_target.build_product_path('${BUILT_PRODUCTS_DIR}'))
resource_paths = resource_paths.map do |resource_path|
extname = File.extname(resource_path)
if self.class.resource_extension_compilable?(extname)
output_extname = self.class.output_extension_for_resource(extname)
output_path_components = Pathname(resource_path).each_filename.select { |component| File.extname(component) == '.lproj' }
output_path_components << File.basename(resource_path)
built_product_dir.join(*output_path_components).sub_ext(output_extname).to_s
else
resource_path
end
end
end
resource_paths << bridge_support_file
resource_paths.compact.uniq
end
end
end
end
# @return [Pathname] the path of the bridge support file relative to the
# sandbox or `nil` if bridge support is disabled.
#
def bridge_support_file
bridge_support_path.relative_path_from(sandbox.root) if podfile.generate_bridge_support?
end
#-------------------------------------------------------------------------#
# @!group Support files
# @return [Pathname] The absolute path of acknowledgements file.
#
# @note The acknowledgements generators add the extension according to
# the file type.
#
def acknowledgements_basepath
support_files_dir + "#{label}-acknowledgements"
end
# @return [Pathname] The absolute path of the copy resources script.
#
def copy_resources_script_path
support_files_dir + "#{label}-resources.sh"
end
# @return [Pathname] The absolute path of the embed frameworks script.
#
def embed_frameworks_script_path
support_files_dir + "#{label}-frameworks.sh"
end
# @param [String] configuration the configuration this path is for.
#
# @return [Pathname] The absolute path of the copy resources script input file list.
#
def copy_resources_script_input_files_path(configuration)
support_files_dir + "#{label}-resources-#{configuration}-input-files.xcfilelist"
end
# @param [String] configuration the configuration this path is for.
#
# @return [Pathname] The absolute path of the copy resources script output file list.
#
def copy_resources_script_output_files_path(configuration)
support_files_dir + "#{label}-resources-#{configuration}-output-files.xcfilelist"
end
# @param [String] configuration the configuration this path is for.
#
# @return [Pathname] The absolute path of the embed frameworks script input file list.
#
def embed_frameworks_script_input_files_path(configuration)
support_files_dir + "#{label}-frameworks-#{configuration}-input-files.xcfilelist"
end
# @param [String] configuration the configuration this path is for.
#
# @return [Pathname] The absolute path of the embed frameworks script output file list.
#
def embed_frameworks_script_output_files_path(configuration)
support_files_dir + "#{label}-frameworks-#{configuration}-output-files.xcfilelist"
end
# @param [String] configuration the configuration this path is for.
#
# @return [Pathname] The absolute path of the prepare artifacts script input file list.
#
# @deprecated
#
# @todo Remove in 2.0
#
def prepare_artifacts_script_input_files_path(configuration)
support_files_dir + "#{label}-artifacts-#{configuration}-input-files.xcfilelist"
end
# @param [String] configuration the configuration this path is for.
#
# @return [Pathname] The absolute path of the prepare artifacts script output file list.
#
# @deprecated
#
# @todo Remove in 2.0
#
def prepare_artifacts_script_output_files_path(configuration)
support_files_dir + "#{label}-artifacts-#{configuration}-output-files.xcfilelist"
end
# @return [String] The output file path fo the check manifest lock script.
#
def check_manifest_lock_script_output_file_path
"$(DERIVED_FILE_DIR)/#{label}-checkManifestLockResult.txt"
end
# @return [Pathname] The relative path of the Pods directory from user project's directory.
#
def relative_pods_root_path
sandbox.root.relative_path_from(client_root)
end
# @return [String] The xcconfig path of the root from the `$(SRCROOT)`
# variable of the user's project.
#
def relative_pods_root
"${SRCROOT}/#{relative_pods_root_path}"
end
# @return [String] The path of the Podfile directory relative to the
# root of the user project.
#
def podfile_dir_relative_path
podfile_path = target_definition.podfile.defined_in_file
return "${SRCROOT}/#{podfile_path.relative_path_from(client_root).dirname}" unless podfile_path.nil?
# Fallback to the standard path if the Podfile is not represented by a file.
'${PODS_ROOT}/..'
end
# @param [String] config_name The build configuration name to get the xcconfig for
# @return [String] The path of the xcconfig file relative to the root of
# the user project.
#
def xcconfig_relative_path(config_name)
xcconfig_path(config_name).relative_path_from(client_root).to_s
end
# @return [String] The path of the copy resources script relative to the
# root of the Pods project.
#
def copy_resources_script_relative_path
"${PODS_ROOT}/#{relative_to_pods_root(copy_resources_script_path)}"
end
# @return [String] The path of the copy resources script input file list
# relative to the root of the Pods project.
#
def copy_resources_script_input_files_relative_path
"${PODS_ROOT}/#{relative_to_pods_root(copy_resources_script_input_files_path('${CONFIGURATION}'))}"
end
# @return [String] The path of the copy resources script output file list
# relative to the root of the Pods project.
#
def copy_resources_script_output_files_relative_path
"${PODS_ROOT}/#{relative_to_pods_root(copy_resources_script_output_files_path('${CONFIGURATION}'))}"
end
# @return [String] The path of the embed frameworks relative to the
# root of the Pods project.
#
def embed_frameworks_script_relative_path
"${PODS_ROOT}/#{relative_to_pods_root(embed_frameworks_script_path)}"
end
# @return [String] The path of the embed frameworks script input file list
# relative to the root of the Pods project.
#
def embed_frameworks_script_input_files_relative_path
"${PODS_ROOT}/#{relative_to_pods_root(embed_frameworks_script_input_files_path('${CONFIGURATION}'))}"
end
# @return [String] The path of the embed frameworks script output file list
# relative to the root of the Pods project.
#
def embed_frameworks_script_output_files_relative_path
"${PODS_ROOT}/#{relative_to_pods_root(embed_frameworks_script_output_files_path('${CONFIGURATION}'))}"
end
# @return [String] The path of the prepare artifacts script relative to the
# root of the Pods project.
#
# @deprecated
#
# @todo Remove in 2.0
#
def prepare_artifacts_script_relative_path
"${PODS_ROOT}/#{relative_to_pods_root(prepare_artifacts_script_path)}"
end
# @return [String] The path of the prepare artifacts script input file list
# relative to the root of the Pods project.
#
# @deprecated
#
# @todo Remove in 2.0
#
def prepare_artifacts_script_input_files_relative_path
"${PODS_ROOT}/#{relative_to_pods_root(prepare_artifacts_script_input_files_path('${CONFIGURATION}'))}"
end
# @return [String] The path of the prepare artifacts script output file list
# relative to the root of the Pods project.
#
# @deprecated
#
# @todo Remove in 2.0
#
def prepare_artifacts_script_output_files_relative_path
"${PODS_ROOT}/#{relative_to_pods_root(prepare_artifacts_script_output_files_path('${CONFIGURATION}'))}"
end
private
# @!group Private Helpers
#-------------------------------------------------------------------------#
# Computes the relative path of a sandboxed file from the `$(PODS_ROOT)`
# variable of the Pods's project.
#
# @param [Pathname] path
# A relative path from the root of the sandbox.
#
# @return [String] The computed path.
#
def relative_to_pods_root(path)
path.relative_path_from(sandbox.root).to_s
end
def create_build_settings
settings = {}
user_build_configurations.each do |configuration_name, configuration|
settings[configuration_name] = BuildSettings::AggregateTargetSettings.new(self, configuration_name, :configuration => configuration)
end
settings
end
end
end