cocoapods-dykit 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pod/command.rb +2 -0
  3. data/lib/pod/command/dyinstall.rb +51 -0
  4. data/lib/pod/command/dyupdate.rb +106 -0
  5. data/lib/pod/command/fmwk.rb +4 -0
  6. data/lib/pod/command/lib/dylint.rb +1 -0
  7. data/lib/pod/gem_version.rb +1 -1
  8. data/lib/pod/installer.rb +715 -0
  9. data/lib/pod/installer/analyzer.rb +934 -0
  10. data/lib/pod/installer/analyzer/analysis_result.rb +57 -0
  11. data/lib/pod/installer/analyzer/locking_dependency_analyzer.rb +95 -0
  12. data/lib/pod/installer/analyzer/pod_variant.rb +68 -0
  13. data/lib/pod/installer/analyzer/pod_variant_set.rb +157 -0
  14. data/lib/pod/installer/analyzer/podfile_dependency_cache.rb +54 -0
  15. data/lib/pod/installer/analyzer/sandbox_analyzer.rb +251 -0
  16. data/lib/pod/installer/analyzer/specs_state.rb +84 -0
  17. data/lib/pod/installer/analyzer/target_inspection_result.rb +45 -0
  18. data/lib/pod/installer/analyzer/target_inspector.rb +254 -0
  19. data/lib/pod/installer/installation_options.rb +158 -0
  20. data/lib/pod/installer/pod_source_installer.rb +214 -0
  21. data/lib/pod/installer/pod_source_preparer.rb +77 -0
  22. data/lib/pod/installer/podfile_validator.rb +139 -0
  23. data/lib/pod/installer/post_install_hooks_context.rb +107 -0
  24. data/lib/pod/installer/pre_install_hooks_context.rb +42 -0
  25. data/lib/pod/installer/source_provider_hooks_context.rb +32 -0
  26. data/lib/pod/installer/user_project_integrator.rb +253 -0
  27. data/lib/pod/installer/user_project_integrator/target_integrator.rb +462 -0
  28. data/lib/pod/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb +146 -0
  29. data/lib/pod/installer/xcode.rb +8 -0
  30. data/lib/pod/installer/xcode/pods_project_generator.rb +353 -0
  31. data/lib/pod/installer/xcode/pods_project_generator/aggregate_target_installer.rb +172 -0
  32. data/lib/pod/installer/xcode/pods_project_generator/file_references_installer.rb +367 -0
  33. data/lib/pod/installer/xcode/pods_project_generator/pod_target_installer.rb +718 -0
  34. data/lib/pod/installer/xcode/pods_project_generator/pod_target_integrator.rb +111 -0
  35. data/lib/pod/installer/xcode/pods_project_generator/target_installer.rb +265 -0
  36. data/lib/pod/installer/xcode/target_validator.rb +141 -0
  37. data/lib/pod/resolver.rb +632 -0
  38. metadata +34 -2
@@ -0,0 +1,934 @@
1
+ require File.expand_path('../../resolver', __FILE__)
2
+ module Pod
3
+ class DyInstaller
4
+ # Analyzes the Podfile, the Lockfile, and the sandbox manifest to generate
5
+ # the information relative to a CocoaPods installation.
6
+ #
7
+ class Analyzer
8
+ include Config::Mixin
9
+ include InstallationOptions::Mixin
10
+
11
+ delegate_installation_options { podfile }
12
+
13
+ autoload :AnalysisResult, File.expand_path('../analyzer/analysis_result', __FILE__)
14
+ autoload :LockingDependencyAnalyzer, File.expand_path('../analyzer/locking_dependency_analyzer', __FILE__)
15
+ autoload :PodfileDependencyCache, File.expand_path('../analyzer/podfile_dependency_cache', __FILE__)
16
+ autoload :PodVariant, File.expand_path('../analyzer/pod_variant', __FILE__)
17
+ autoload :PodVariantSet, File.expand_path('../analyzer/pod_variant_set', __FILE__)
18
+ autoload :SandboxAnalyzer, File.expand_path('../analyzer/sandbox_analyzer', __FILE__)
19
+ autoload :SpecsState, File.expand_path('../analyzer/specs_state', __FILE__)
20
+ autoload :TargetInspectionResult, File.expand_path('../analyzer/target_inspection_result', __FILE__)
21
+ autoload :TargetInspector, File.expand_path('../analyzer/target_inspector', __FILE__)
22
+ # autoload :DyResolver, File.expand_path('../../resolver', __FILE__)
23
+
24
+ # @return [Sandbox] The sandbox where the Pods should be installed.
25
+ #
26
+ attr_reader :sandbox
27
+
28
+ # @return [Podfile] The Podfile specification that contains the
29
+ # information of the Pods that should be installed.
30
+ #
31
+ attr_reader :podfile
32
+
33
+ # @return [Lockfile] The Lockfile that stores the information about the
34
+ # Pods previously installed on any machine.
35
+ #
36
+ attr_reader :lockfile
37
+
38
+ # @return [Array<Source>] Sources provided by plugins
39
+ #
40
+ attr_reader :plugin_sources
41
+
42
+ # Initialize a new instance
43
+ #
44
+ # @param [Sandbox] sandbox @see sandbox
45
+ # @param [Podfile] podfile @see podfile
46
+ # @param [Lockfile] lockfile @see lockfile
47
+ # @param [Array<Source>] plugin_sources @see plugin_sources
48
+ #
49
+ def initialize(sandbox, podfile, lockfile = nil, plugin_sources = nil)
50
+ @sandbox = sandbox
51
+ @podfile = podfile
52
+ @lockfile = lockfile
53
+ @plugin_sources = plugin_sources
54
+
55
+ @update = false
56
+ @allow_pre_downloads = true
57
+ @has_dependencies = true
58
+ @test_pod_target_analyzer_cache = {}
59
+ @test_pod_target_key = Struct.new(:name, :pod_targets)
60
+ @podfile_dependency_cache = PodfileDependencyCache.from_podfile(podfile)
61
+ end
62
+
63
+ # Performs the analysis.
64
+ #
65
+ # The Podfile and the Lockfile provide the information necessary to
66
+ # compute which specification should be installed. The manifest of the
67
+ # sandbox returns which specifications are installed.
68
+ #
69
+ # @param [Bool] allow_fetches
70
+ # whether external sources may be fetched
71
+ #
72
+ # @return [AnalysisResult]
73
+ #
74
+ def analyze(allow_fetches = true)
75
+ validate_podfile!
76
+ validate_lockfile_version!
77
+ @result = AnalysisResult.new
78
+ @result.podfile_dependency_cache = @podfile_dependency_cache
79
+ if installation_options.integrate_targets?
80
+ @result.target_inspections = inspect_targets_to_integrate
81
+ else
82
+ verify_platforms_specified!
83
+ end
84
+ @result.podfile_state = generate_podfile_state
85
+
86
+ store_existing_checkout_options
87
+ fetch_external_sources if allow_fetches
88
+
89
+ @locked_dependencies = generate_version_locking_dependencies
90
+ resolver_specs_by_target = resolve_dependencies
91
+ validate_platforms(resolver_specs_by_target)
92
+ @result.specifications = generate_specifications(resolver_specs_by_target)
93
+ @result.targets = generate_targets(resolver_specs_by_target)
94
+ @result.sandbox_state = generate_sandbox_state
95
+ @result.specs_by_target = resolver_specs_by_target.each_with_object({}) do |rspecs_by_target, hash|
96
+ hash[rspecs_by_target[0]] = rspecs_by_target[1].map(&:spec)
97
+ end
98
+ @result.specs_by_source = Hash[resolver_specs_by_target.values.flatten(1).group_by(&:source).map { |source, specs| [source, specs.map(&:spec).uniq] }]
99
+ sources.each { |s| @result.specs_by_source[s] ||= [] }
100
+ @result
101
+ end
102
+
103
+ attr_accessor :result
104
+
105
+ # @return [Bool] Whether an installation should be performed or this
106
+ # CocoaPods project is already up to date.
107
+ #
108
+ def needs_install?
109
+ analysis_result = analyze(false)
110
+ podfile_needs_install?(analysis_result) || sandbox_needs_install?(analysis_result)
111
+ end
112
+
113
+ # @param [AnalysisResult] analysis_result
114
+ # the analysis result to check for changes
115
+ #
116
+ # @return [Bool] Whether the podfile has changes respect to the lockfile.
117
+ #
118
+ def podfile_needs_install?(analysis_result)
119
+ state = analysis_result.podfile_state
120
+ needing_install = state.added + state.changed + state.deleted
121
+ !needing_install.empty?
122
+ end
123
+
124
+ # @param [AnalysisResult] analysis_result
125
+ # the analysis result to check for changes
126
+ #
127
+ # @return [Bool] Whether the sandbox is in synch with the lockfile.
128
+ #
129
+ def sandbox_needs_install?(analysis_result)
130
+ state = analysis_result.sandbox_state
131
+ needing_install = state.added + state.changed + state.deleted
132
+ !needing_install.empty?
133
+ end
134
+
135
+ #-----------------------------------------------------------------------#
136
+
137
+ # @!group Configuration
138
+
139
+ # @return [Hash, Boolean, nil] Pods that have been requested to be
140
+ # updated or true if all Pods should be updated
141
+ #
142
+ attr_accessor :update
143
+
144
+ # @return [Bool] Whether the version of the dependencies which did not
145
+ # change in the Podfile should be locked.
146
+ #
147
+ def update_mode?
148
+ update != nil
149
+ end
150
+
151
+ # @return [Symbol] Whether and how the dependencies in the Podfile
152
+ # should be updated.
153
+ #
154
+ def update_mode
155
+ if !update
156
+ :none
157
+ elsif update == true
158
+ :all
159
+ elsif !update[:pods].nil?
160
+ :selected
161
+ end
162
+ end
163
+
164
+ # @return [Bool] Whether the analysis allows pre-downloads and thus
165
+ # modifications to the sandbox.
166
+ #
167
+ # @note This flag should not be used in installations.
168
+ #
169
+ # @note This is used by the `pod outdated` command to prevent
170
+ # modification of the sandbox in the resolution process.
171
+ #
172
+ attr_accessor :allow_pre_downloads
173
+ alias_method :allow_pre_downloads?, :allow_pre_downloads
174
+
175
+ # @return [Bool] Whether the analysis has dependencies and thus
176
+ # sources must be configured.
177
+ #
178
+ # @note This is used by the `pod lib lint` command to prevent
179
+ # update of specs when not needed.
180
+ #
181
+ attr_accessor :has_dependencies
182
+ alias_method :has_dependencies?, :has_dependencies
183
+
184
+ #-----------------------------------------------------------------------#
185
+
186
+ private
187
+
188
+ # @return [Bool] Whether the analysis has updated sources repositories.
189
+ #
190
+ attr_accessor :specs_updated
191
+ alias_method :specs_updated?, :specs_updated
192
+
193
+ def validate_podfile!
194
+ validator = Installer::PodfileValidator.new(podfile, @podfile_dependency_cache)
195
+ validator.validate
196
+
197
+ unless validator.valid?
198
+ raise Informative, validator.message
199
+ end
200
+ validator.warnings.uniq.each { |w| UI.warn(w) }
201
+ end
202
+
203
+ # @!group Analysis steps
204
+
205
+ # @note The warning about the version of the Lockfile doesn't use the
206
+ # `UI.warn` method because it prints the output only at the end
207
+ # of the installation. At that time CocoaPods could have crashed.
208
+ #
209
+ def validate_lockfile_version!
210
+ if lockfile && lockfile.cocoapods_version > Version.new(VERSION)
211
+ STDERR.puts '[!] The version of CocoaPods used to generate ' \
212
+ "the lockfile (#{lockfile.cocoapods_version}) is "\
213
+ "higher than the version of the current executable (#{VERSION}). " \
214
+ 'Incompatibility issues may arise.'.yellow
215
+ end
216
+ end
217
+
218
+ # Compares the {Podfile} with the {Lockfile} in order to detect which
219
+ # dependencies should be locked.
220
+ #
221
+ # @return [SpecsState] the states of the Podfile specs.
222
+ #
223
+ # @note As the target definitions share the same sandbox they should have
224
+ # the same version of a Pod. For this reason this method returns
225
+ # the name of the Pod (root name of the dependencies) and doesn't
226
+ # group them by target definition.
227
+ #
228
+ # @todo [CocoaPods > 0.18] If there isn't a Lockfile all the Pods should
229
+ # be marked as added.
230
+ #
231
+ def generate_podfile_state
232
+ if lockfile
233
+ pods_state = nil
234
+ UI.section 'Finding Podfile changes' do
235
+ pods_by_state = lockfile.detect_changes_with_podfile(podfile)
236
+ pods_state = SpecsState.new(pods_by_state)
237
+ pods_state.print
238
+ end
239
+ pods_state
240
+ else
241
+ state = SpecsState.new
242
+ state.added.merge(@podfile_dependency_cache.podfile_dependencies.map(&:root_name))
243
+ state
244
+ end
245
+ end
246
+
247
+ public
248
+
249
+ # Updates the git source repositories.
250
+ #
251
+ def update_repositories
252
+ sources.each do |source|
253
+ if source.git?
254
+ config.sources_manager.update(source.name, true)
255
+ else
256
+ UI.message "Skipping `#{source.name}` update because the repository is not a git source repository."
257
+ end
258
+ end
259
+ @specs_updated = true
260
+ end
261
+
262
+ private
263
+
264
+ # Copies the pod_targets of any of the app embedded aggregate targets into
265
+ # their potential host aggregate target, if that potential host aggregate target's
266
+ # user_target hosts any of the app embedded aggregate targets' user_targets
267
+ #
268
+ # @param [AggregateTarget] aggregate_target the aggregate target whose user_target
269
+ # might host one or more of the embedded aggregate targets' user_targets
270
+ #
271
+ # @param [Array<AggregateTarget>] embedded_aggregate_targets the aggregate targets
272
+ # representing the embedded targets to be integrated
273
+ #
274
+ # @param [Boolean] libraries_only if true, only library-type embedded
275
+ # targets are considered, otherwise, all other types are have
276
+ # their pods copied to their host targets as well (extensions, etc.)
277
+ #
278
+ def copy_embedded_target_pod_targets_to_host(aggregate_target, embedded_aggregate_targets, libraries_only)
279
+ return if aggregate_target.requires_host_target?
280
+ pod_target_names = Set.new(aggregate_target.pod_targets.map(&:name))
281
+ aggregate_user_target_uuids = Set.new(aggregate_target.user_targets.map(&:uuid))
282
+ embedded_aggregate_targets.each do |embedded_aggregate_target|
283
+ # Skip non libraries in library-only mode
284
+ next if libraries_only && !embedded_aggregate_target.library?
285
+ next unless embedded_aggregate_target.user_targets.any? do |embedded_user_target|
286
+ # You have to ask the host target's project for the host targets of
287
+ # the embedded target, as opposed to asking user_project for the
288
+ # embedded targets of the host target. The latter doesn't work when
289
+ # the embedded target lives in a sub-project. The lines below get
290
+ # the host target uuids for the embedded target and checks to see if
291
+ # those match to any of the user_target uuids in the aggregate_target.
292
+ host_target_uuids = Set.new(aggregate_target.user_project.host_targets_for_embedded_target(embedded_user_target).map(&:uuid))
293
+ !aggregate_user_target_uuids.intersection(host_target_uuids).empty?
294
+ end
295
+ # This embedded target is hosted by the aggregate target's user_target; copy over the non-duplicate pod_targets
296
+ aggregate_target.pod_targets = aggregate_target.pod_targets + embedded_aggregate_target.pod_targets.select do |pod_target|
297
+ !pod_target_names.include? pod_target.name
298
+ end
299
+ end
300
+ end
301
+
302
+ # Raises an error if there are embedded targets in the Podfile, but
303
+ # their host targets have not been declared in the Podfile. As it
304
+ # finds host targets, it collection information on host target types.
305
+ #
306
+ # @param [Array<AggregateTarget>] aggregate_targets the generated
307
+ # aggregate targets
308
+ #
309
+ # @param [Array<AggregateTarget>] embedded_aggregate_targets the aggregate targets
310
+ # representing the embedded targets to be integrated
311
+ #
312
+ def analyze_host_targets_in_podfile(aggregate_targets, embedded_aggregate_targets)
313
+ target_definitions_by_uuid = {}
314
+ # Collect aggregate target definitions by uuid to later lookup host target
315
+ # definitions and verify their compatiblity with their embedded targets
316
+ aggregate_targets.each do |target|
317
+ target.user_targets.map(&:uuid).each do |uuid|
318
+ target_definitions_by_uuid[uuid] = target.target_definition
319
+ end
320
+ end
321
+ aggregate_target_user_projects = aggregate_targets.map(&:user_project)
322
+ embedded_targets_missing_hosts = []
323
+ host_uuid_to_embedded_target_definitions = {}
324
+ # Search all of the known user projects for each embedded target's hosts
325
+ embedded_aggregate_targets.each do |target|
326
+ host_uuids = []
327
+ aggregate_target_user_projects.product(target.user_targets).each do |user_project, user_target|
328
+ host_uuids += user_project.host_targets_for_embedded_target(user_target).map(&:uuid)
329
+ end
330
+ # For each host, keep track of its embedded target definitions
331
+ # to later verify each embedded target's compatiblity with its host,
332
+ # ignoring the hosts that aren't known to CocoaPods (no target
333
+ # definitions in the Podfile)
334
+ host_uuids.each do |uuid|
335
+ (host_uuid_to_embedded_target_definitions[uuid] ||= []) << target.target_definition if target_definitions_by_uuid.key? uuid
336
+ end
337
+ # If none of the hosts are known to CocoaPods (no target definitions
338
+ # in the Podfile), add it to the list of targets missing hosts
339
+ embedded_targets_missing_hosts << target unless host_uuids.any? do |uuid|
340
+ target_definitions_by_uuid.key? uuid
341
+ end
342
+ end
343
+
344
+ unless embedded_targets_missing_hosts.empty?
345
+ embedded_targets_missing_hosts_product_types = Set.new embedded_targets_missing_hosts.flat_map(&:user_targets).map(&:symbol_type)
346
+ target_names = embedded_targets_missing_hosts.map do |target|
347
+ target.name.sub('Pods-', '') # Make the target names more recognizable to the user
348
+ end.join ', '
349
+ # If the targets missing hosts are only frameworks, then this is likely
350
+ # a project for doing framework development. In that case, just warn that
351
+ # the frameworks that these targets depend on won't be integrated anywhere
352
+ if embedded_targets_missing_hosts_product_types.subset?(Set.new([:framework, :static_library]))
353
+ UI.warn "The Podfile contains framework or static library targets (#{target_names}), for which the Podfile does not contain host targets (targets which embed the framework)." \
354
+ "\n" \
355
+ 'If this project is for doing framework development, you can ignore this message. Otherwise, add a target to the Podfile that embeds these frameworks to make this message go away (e.g. a test target).'
356
+ else
357
+ raise Informative, "Unable to find host target(s) for #{target_names}. Please add the host targets for the embedded targets to the Podfile." \
358
+ "\n" \
359
+ 'Certain kinds of targets require a host target. A host target is a "parent" target which embeds a "child" target. These are example types of targets that need a host target:' \
360
+ "\n- Framework" \
361
+ "\n- App Extension" \
362
+ "\n- Watch OS 1 Extension" \
363
+ "\n- Messages Extension (except when used with a Messages Application)"
364
+ end
365
+ end
366
+
367
+ target_mismatches = []
368
+ host_uuid_to_embedded_target_definitions.each do |uuid, target_definitions|
369
+ host_target_definition = target_definitions_by_uuid[uuid]
370
+ target_definitions.each do |target_definition|
371
+ unless host_target_definition.uses_frameworks? == target_definition.uses_frameworks?
372
+ target_mismatches << "- #{host_target_definition.name} (#{host_target_definition.uses_frameworks?}) and #{target_definition.name} (#{target_definition.uses_frameworks?}) do not both set use_frameworks!."
373
+ end
374
+ end
375
+ end
376
+
377
+ unless target_mismatches.empty?
378
+ heading = 'Unable to integrate the following embedded targets with their respective host targets (a host target is a "parent" target which embeds a "child" target like a framework or extension):'
379
+ raise Informative, heading + "\n\n" + target_mismatches.sort.uniq.join("\n")
380
+ end
381
+ end
382
+
383
+ # Creates the models that represent the targets generated by CocoaPods.
384
+ #
385
+ # @param [Hash{Podfile::TargetDefinition => Array<ResolvedSpecification>}] resolver_specs_by_target
386
+ # mapping of targets to resolved specs (containing information about test usage)
387
+ # aggregate targets
388
+ #
389
+ # @return [Array<AggregateTarget>] the list of aggregate targets generated.
390
+ #
391
+ def generate_targets(resolver_specs_by_target)
392
+ resolver_specs_by_target = resolver_specs_by_target.reject { |td, _| td.abstract? }
393
+ pod_targets = generate_pod_targets(resolver_specs_by_target)
394
+ aggregate_targets = resolver_specs_by_target.keys.map do |target_definition|
395
+ generate_target(target_definition, pod_targets, resolver_specs_by_target)
396
+ end
397
+ if installation_options.integrate_targets?
398
+ # Copy embedded target pods that cannot have their pods embedded as frameworks to
399
+ # their host targets, and ensure we properly link library pods to their host targets
400
+ embedded_targets = aggregate_targets.select(&:requires_host_target?)
401
+ analyze_host_targets_in_podfile(aggregate_targets, embedded_targets)
402
+
403
+ use_frameworks_embedded_targets, non_use_frameworks_embedded_targets = embedded_targets.partition(&:requires_frameworks?)
404
+ aggregate_targets.each do |target|
405
+ # For targets that require frameworks, we always have to copy their pods to their
406
+ # host targets because those frameworks will all be loaded from the host target's bundle
407
+ copy_embedded_target_pod_targets_to_host(target, use_frameworks_embedded_targets, false)
408
+
409
+ # For targets that don't require frameworks, we only have to consider library-type
410
+ # targets because their host targets will still need to link their pods
411
+ copy_embedded_target_pod_targets_to_host(target, non_use_frameworks_embedded_targets, true)
412
+ end
413
+ end
414
+ aggregate_targets.each do |target|
415
+ target.search_paths_aggregate_targets.concat(aggregate_targets.select do |aggregate_target|
416
+ target.target_definition.targets_to_inherit_search_paths.include?(aggregate_target.target_definition)
417
+ end).freeze
418
+ end
419
+ end
420
+
421
+ # Setup the aggregate target for a single user target
422
+ #
423
+ # @param [TargetDefinition] target_definition
424
+ # the target definition for the user target.
425
+ #
426
+ # @param [Array<PodTarget>] pod_targets
427
+ # the pod targets, which were generated.
428
+ #
429
+ # @param [Hash{Podfile::TargetDefinition => Array<ResolvedSpecification>}] resolver_specs_by_target
430
+ # the resolved specifications grouped by target.
431
+ #
432
+ # @return [AggregateTarget]
433
+ #
434
+ def generate_target(target_definition, pod_targets, resolver_specs_by_target)
435
+ target = AggregateTarget.new(target_definition, sandbox)
436
+ target.host_requires_frameworks |= target_definition.uses_frameworks?
437
+
438
+ if installation_options.integrate_targets?
439
+ target_inspection = result.target_inspections[target_definition]
440
+ raise "missing inspection: #{target_definition.name}" unless target_inspection
441
+ target.user_project = target_inspection.project
442
+ target.client_root = target.user_project_path.dirname.realpath
443
+ target.user_target_uuids = target_inspection.project_target_uuids
444
+ target.user_build_configurations = target_inspection.build_configurations
445
+ target.archs = target_inspection.archs
446
+ else
447
+ target.client_root = config.installation_root.realpath
448
+ target.user_target_uuids = []
449
+ target.user_build_configurations = target_definition.build_configurations || { 'Release' => :release, 'Debug' => :debug }
450
+ if target_definition.platform && target_definition.platform.name == :osx
451
+ target.archs = '$(ARCHS_STANDARD_64_BIT)'
452
+ end
453
+ end
454
+
455
+ target.pod_targets = filter_pod_targets_for_target_definition(target_definition, pod_targets, resolver_specs_by_target)
456
+
457
+ target
458
+ end
459
+
460
+ # Returns a filtered list of pod targets that should or should not be part of the target definition. Pod targets
461
+ # used by tests only are filtered.
462
+ #
463
+ # @param [TargetDefinition] target_definition
464
+ # the target definition to use as the base for filtering
465
+ #
466
+ # @param [Array<PodTarget>] pod_targets
467
+ # the array of pod targets to check against
468
+ #
469
+ # @param [Hash{Podfile::TargetDefinition => Array<ResolvedSpecification>}] resolver_specs_by_target
470
+ # the resolved specifications grouped by target.
471
+ #
472
+ # @return [Array<PodTarget>] the filtered list of pod targets.
473
+ #
474
+ def filter_pod_targets_for_target_definition(target_definition, pod_targets, resolver_specs_by_target)
475
+ pod_targets.select do |pod_target|
476
+ included_in_target_definition = pod_target.target_definitions.include?(target_definition)
477
+ used_by_tests_only = resolver_specs_by_target[target_definition].select { |resolver_spec| pod_target.specs.include?(resolver_spec.spec) }.all?(&:used_by_tests_only?)
478
+ included_in_target_definition && !used_by_tests_only
479
+ end
480
+ end
481
+
482
+ # Setup the pod targets for an aggregate target. Deduplicates resulting
483
+ # targets by grouping by platform and subspec by their root
484
+ # to create a {PodTarget} for each spec.
485
+ #
486
+ # @param [Hash{Podfile::TargetDefinition => Array<ResolvedSpecification>}] resolver_specs_by_target
487
+ # the resolved specifications grouped by target.
488
+ #
489
+ # @return [Array<PodTarget>]
490
+ #
491
+ def generate_pod_targets(resolver_specs_by_target)
492
+ if installation_options.deduplicate_targets?
493
+ distinct_targets = resolver_specs_by_target.each_with_object({}) do |dependency, hash|
494
+ target_definition, dependent_specs = *dependency
495
+ dependent_specs.group_by(&:root).each do |root_spec, resolver_specs|
496
+ all_specs = resolver_specs.map(&:spec)
497
+ test_specs, specs = all_specs.partition(&:test_specification?)
498
+ pod_variant = PodVariant.new(specs, test_specs, target_definition.platform, target_definition.uses_frameworks?)
499
+ hash[root_spec] ||= {}
500
+ (hash[root_spec][pod_variant] ||= []) << target_definition
501
+ hash[root_spec].keys.find { |k| k == pod_variant }.test_specs.concat(test_specs).uniq!
502
+ end
503
+ end
504
+
505
+ pod_targets = distinct_targets.flat_map do |_root, target_definitions_by_variant|
506
+ suffixes = PodVariantSet.new(target_definitions_by_variant.keys).scope_suffixes
507
+ target_definitions_by_variant.flat_map do |variant, target_definitions|
508
+ generate_pod_target(target_definitions, variant.specs + variant.test_specs, :scope_suffix => suffixes[variant])
509
+ end
510
+ end
511
+
512
+ all_resolver_specs = resolver_specs_by_target.values.flatten.map(&:spec).uniq
513
+ pod_targets_by_name = pod_targets.group_by(&:pod_name).each_with_object({}) do |(name, values), hash|
514
+ # Sort the target by the number of activated subspecs, so that
515
+ # we prefer a minimal target as transitive dependency.
516
+ hash[name] = values.sort_by { |pt| pt.specs.count }
517
+ end
518
+ pod_targets.each do |target|
519
+ all_specs = all_resolver_specs.to_set
520
+ dependencies = transitive_dependencies_for_specs(target.non_test_specs.to_set, target.platform, all_specs).group_by(&:root)
521
+ test_dependencies = transitive_dependencies_for_specs(target.test_specs.to_set, target.platform, all_specs).group_by(&:root)
522
+ test_dependencies.delete_if { |k| dependencies.key? k }
523
+ target.dependent_targets = filter_dependencies(dependencies, pod_targets_by_name, target)
524
+ target.test_dependent_targets = filter_dependencies(test_dependencies, pod_targets_by_name, target)
525
+ end
526
+ else
527
+ dedupe_cache = {}
528
+ resolver_specs_by_target.flat_map do |target_definition, specs|
529
+ grouped_specs = specs.group_by(&:root).values.uniq
530
+ pod_targets = grouped_specs.flat_map do |pod_specs|
531
+ generate_pod_target([target_definition], pod_specs.map(&:spec)).scoped(dedupe_cache)
532
+ end
533
+
534
+ pod_targets.each do |target|
535
+ all_specs = specs.map(&:spec).to_set
536
+ dependencies = transitive_dependencies_for_specs(target.non_test_specs.to_set, target.platform, all_specs).group_by(&:root)
537
+ test_dependencies = transitive_dependencies_for_specs(target.test_specs.to_set, target.platform, all_specs).group_by(&:root)
538
+ test_dependencies.delete_if { |k| dependencies.key? k }
539
+ target.dependent_targets = pod_targets.reject { |t| dependencies[t.root_spec].nil? }
540
+ target.test_dependent_targets = pod_targets.reject { |t| test_dependencies[t.root_spec].nil? }
541
+ end
542
+ end
543
+ end
544
+ end
545
+
546
+ def filter_dependencies(dependencies, pod_targets_by_name, target)
547
+ dependencies.map do |root_spec, deps|
548
+ pod_targets_by_name[root_spec.name].find do |t|
549
+ next false if t.platform.symbolic_name != target.platform.symbolic_name ||
550
+ t.requires_frameworks? != target.requires_frameworks?
551
+ spec_names = t.specs.map(&:name)
552
+ deps.all? { |dep| spec_names.include?(dep.name) }
553
+ end
554
+ end
555
+ end
556
+
557
+ # Returns the specs upon which the given specs _transitively_ depend.
558
+ #
559
+ # @note: This is implemented in the analyzer, because we don't have to
560
+ # care about the requirements after dependency resolution.
561
+ #
562
+ # @param [Array<Specification>] specs
563
+ # The specs, whose dependencies should be returned.
564
+ #
565
+ # @param [Platform] platform
566
+ # The platform for which the dependencies should be returned.
567
+ #
568
+ # @param [Array<Specification>] all_specs
569
+ # All specifications which are installed alongside.
570
+ #
571
+ # @return [Array<Specification>]
572
+ #
573
+ def transitive_dependencies_for_specs(specs, platform, all_specs)
574
+ return [] if specs.empty? || all_specs.empty?
575
+
576
+ dependent_specs = Set.new
577
+ specs.each do |spec|
578
+ spec.consumer(platform).dependencies.each do |dependency|
579
+ match = all_specs.find do |s|
580
+ next false unless s.name == dependency.name
581
+ next false if specs.include?(s)
582
+ true
583
+ end
584
+ dependent_specs << match if match
585
+ end
586
+ end
587
+
588
+ remaining_specs = all_specs - dependent_specs
589
+
590
+ dependent_specs.union transitive_dependencies_for_specs(dependent_specs, platform, remaining_specs)
591
+ end
592
+
593
+ # Create a target for each spec group
594
+ #
595
+ # @param [TargetDefinitions] target_definitions
596
+ # the aggregate target
597
+ #
598
+ # @param [Array<Specification>] pod_specs
599
+ # the specifications of an equal root.
600
+ #
601
+ # @param [String] scope_suffix
602
+ # @see PodTarget#scope_suffix
603
+ #
604
+ # @return [PodTarget]
605
+ #
606
+ def generate_pod_target(target_definitions, pod_specs, scope_suffix: nil)
607
+ pod_target = PodTarget.new(pod_specs, target_definitions, sandbox, scope_suffix)
608
+ pod_target.host_requires_frameworks = target_definitions.any?(&:uses_frameworks?)
609
+
610
+ if installation_options.integrate_targets?
611
+ target_inspections = result.target_inspections.select { |t, _| target_definitions.include?(t) }.values
612
+ pod_target.user_build_configurations = target_inspections.map(&:build_configurations).reduce({}, &:merge)
613
+ pod_target.archs = target_inspections.flat_map(&:archs).compact.uniq.sort
614
+ else
615
+ pod_target.user_build_configurations = {}
616
+ if target_definitions.first.platform.name == :osx
617
+ pod_target.archs = '$(ARCHS_STANDARD_64_BIT)'
618
+ end
619
+ end
620
+
621
+ pod_target
622
+ end
623
+
624
+ # Generates dependencies that require the specific version of the Pods
625
+ # that haven't changed in the {Lockfile}.
626
+ #
627
+ # These dependencies are passed to the {Resolver}, unless the installer
628
+ # is in update mode, to prevent it from upgrading the Pods that weren't
629
+ # changed in the {Podfile}.
630
+ #
631
+ # @return [Molinillo::DependencyGraph<Dependency>] the dependencies
632
+ # generated by the lockfile that prevent the resolver to update
633
+ # a Pod.
634
+ #
635
+ def generate_version_locking_dependencies
636
+ if update_mode == :all || !lockfile
637
+ LockingDependencyAnalyzer.unlocked_dependency_graph
638
+ else
639
+ pods_to_update = result.podfile_state.changed + result.podfile_state.deleted
640
+ pods_to_update += update[:pods] if update_mode == :selected
641
+ local_pod_names = @podfile_dependency_cache.podfile_dependencies.select(&:local?).map(&:root_name)
642
+ pods_to_unlock = local_pod_names.reject do |pod_name|
643
+ sandbox.specification(pod_name).checksum == lockfile.checksum(pod_name)
644
+ end
645
+ LockingDependencyAnalyzer.generate_version_locking_dependencies(lockfile, pods_to_update, pods_to_unlock)
646
+ end
647
+ end
648
+
649
+ # Fetches the podspecs of external sources if modifications to the
650
+ # sandbox are allowed.
651
+ #
652
+ # @note In update mode all the external sources are refreshed while in
653
+ # normal mode they are refreshed only if added or changed in the
654
+ # Podfile. Moreover, in normal specifications for unchanged Pods
655
+ # which are missing or are generated from an local source are
656
+ # fetched as well.
657
+ #
658
+ # @note It is possible to perform this step before the resolution
659
+ # process because external sources identify a single specific
660
+ # version (checkout). If the other dependencies are not
661
+ # compatible with the version reported by the podspec of the
662
+ # external source the resolver will raise.
663
+ #
664
+ # @return [void]
665
+ #
666
+ # TODO: Specs
667
+ #
668
+ def fetch_external_sources
669
+ return unless allow_pre_downloads?
670
+
671
+ verify_no_pods_with_different_sources!
672
+ unless dependencies_to_fetch.empty?
673
+ UI.section 'Fetching external sources' do
674
+ dependencies_to_fetch.sort.each do |dependency|
675
+ fetch_external_source(dependency, !pods_to_fetch.include?(dependency.root_name))
676
+ end
677
+ end
678
+ end
679
+ end
680
+
681
+ def verify_no_pods_with_different_sources!
682
+ deps_with_different_sources = @podfile_dependency_cache.podfile_dependencies.group_by(&:root_name).
683
+ select { |_root_name, dependencies| dependencies.map(&:external_source).uniq.count > 1 }
684
+ deps_with_different_sources.each do |root_name, dependencies|
685
+ raise Informative, 'There are multiple dependencies with different ' \
686
+ "sources for `#{root_name}` in #{UI.path podfile.defined_in_file}:" \
687
+ "\n\n- #{dependencies.map(&:to_s).join("\n- ")}"
688
+ end
689
+ end
690
+
691
+ def fetch_external_source(dependency, use_lockfile_options)
692
+ checkout_options = lockfile.checkout_options_for_pod_named(dependency.root_name) if lockfile
693
+ source = if checkout_options && use_lockfile_options
694
+ ExternalSources.from_params(checkout_options, dependency, podfile.defined_in_file)
695
+ else
696
+ ExternalSources.from_dependency(dependency, podfile.defined_in_file)
697
+ end
698
+ source.can_cache = installation_options.clean?
699
+ source.fetch(sandbox)
700
+ end
701
+
702
+ def dependencies_to_fetch
703
+ @deps_to_fetch ||= begin
704
+ deps_to_fetch = []
705
+ deps_with_external_source = @podfile_dependency_cache.podfile_dependencies.select(&:external_source)
706
+
707
+ if update_mode == :all
708
+ deps_to_fetch = deps_with_external_source
709
+ else
710
+ deps_to_fetch = deps_with_external_source.select { |dep| pods_to_fetch.include?(dep.root_name) }
711
+ deps_to_fetch_if_needed = deps_with_external_source.select { |dep| result.podfile_state.unchanged.include?(dep.root_name) }
712
+ deps_to_fetch += deps_to_fetch_if_needed.select do |dep|
713
+ sandbox.specification(dep.root_name).nil? ||
714
+ !dep.external_source[:path].nil? ||
715
+ !sandbox.pod_dir(dep.root_name).directory? ||
716
+ checkout_requires_update?(dep)
717
+ end
718
+ end
719
+ deps_to_fetch.uniq(&:root_name)
720
+ end
721
+ end
722
+
723
+ def checkout_requires_update?(dependency)
724
+ return true unless lockfile && sandbox.manifest
725
+ locked_checkout_options = lockfile.checkout_options_for_pod_named(dependency.root_name)
726
+ sandbox_checkout_options = sandbox.manifest.checkout_options_for_pod_named(dependency.root_name)
727
+ locked_checkout_options != sandbox_checkout_options
728
+ end
729
+
730
+ def pods_to_fetch
731
+ @pods_to_fetch ||= begin
732
+ pods_to_fetch = result.podfile_state.added + result.podfile_state.changed
733
+ if update_mode == :selected
734
+ pods_to_fetch += update[:pods]
735
+ elsif update_mode == :all
736
+ pods_to_fetch += result.podfile_state.unchanged + result.podfile_state.deleted
737
+ end
738
+ pods_to_fetch += @podfile_dependency_cache.podfile_dependencies.
739
+ select { |dep| Hash(dep.external_source).key?(:podspec) && sandbox.specification_path(dep.root_name).nil? }.
740
+ map(&:root_name)
741
+ pods_to_fetch
742
+ end
743
+ end
744
+
745
+ def store_existing_checkout_options
746
+ @podfile_dependency_cache.podfile_dependencies.select(&:external_source).each do |dep|
747
+ if checkout_options = lockfile && lockfile.checkout_options_for_pod_named(dep.root_name)
748
+ sandbox.store_checkout_source(dep.root_name, checkout_options)
749
+ end
750
+ end
751
+ end
752
+
753
+ # Converts the Podfile in a list of specifications grouped by target.
754
+ #
755
+ # @note As some dependencies might have external sources the resolver
756
+ # is aware of the {Sandbox} and interacts with it to download the
757
+ # podspecs of the external sources. This is necessary because the
758
+ # resolver needs their specifications to analyze their
759
+ # dependencies.
760
+ #
761
+ # @note The specifications of the external sources which are added,
762
+ # modified or removed need to deleted from the sandbox before the
763
+ # resolution process. Otherwise the resolver might use an
764
+ # incorrect specification instead of pre-downloading it.
765
+ #
766
+ # @note In update mode the resolver is set to always update the specs
767
+ # from external sources.
768
+ #
769
+ # @return [Hash{TargetDefinition => Array<Spec>}] the specifications
770
+ # grouped by target.
771
+ #
772
+ def resolve_dependencies
773
+ duplicate_dependencies = @podfile_dependency_cache.podfile_dependencies.group_by(&:name).
774
+ select { |_name, dependencies| dependencies.count > 1 }
775
+ duplicate_dependencies.each do |name, dependencies|
776
+ UI.warn "There are duplicate dependencies on `#{name}` in #{UI.path podfile.defined_in_file}:\n\n" \
777
+ "- #{dependencies.map(&:to_s).join("\n- ")}"
778
+ end
779
+
780
+ resolver_specs_by_target = nil
781
+ UI.section "Resolving dependencies of #{UI.path(podfile.defined_in_file) || 'Podfile'}" do
782
+ resolver = DyResolver.new(sandbox, podfile, locked_dependencies, sources, specs_updated?)
783
+ resolver_specs_by_target = resolver.resolve
784
+ resolver_specs_by_target.values.flatten(1).map(&:spec).each(&:validate_cocoapods_version)
785
+ end
786
+ resolver_specs_by_target
787
+ end
788
+
789
+ # Warns for any specification that is incompatible with its target.
790
+ #
791
+ # @param [Hash{TargetDefinition => Array<Spec>}] resolver_specs_by_target
792
+ # the specifications grouped by target.
793
+ #
794
+ # @return [Hash{TargetDefinition => Array<Spec>}] the specifications
795
+ # grouped by target.
796
+ #
797
+ def validate_platforms(resolver_specs_by_target)
798
+ resolver_specs_by_target.each do |target, specs|
799
+ specs.map(&:spec).each do |spec|
800
+ next unless target_platform = target.platform
801
+ unless spec.available_platforms.any? { |p| target_platform.supports?(p) }
802
+ UI.warn "The platform of the target `#{target.name}` " \
803
+ "(#{target.platform}) may not be compatible with `#{spec}` which has " \
804
+ "a minimum requirement of #{spec.available_platforms.join(' - ')}."
805
+ end
806
+ end
807
+ end
808
+ end
809
+
810
+ # Returns the list of all the resolved specifications.
811
+ #
812
+ # @return [Array<Specification>] the list of the specifications.
813
+ #
814
+ def generate_specifications(resolver_specs_by_target)
815
+ resolver_specs_by_target.values.flatten.map(&:spec).uniq
816
+ end
817
+
818
+ # Computes the state of the sandbox respect to the resolved
819
+ # specifications.
820
+ #
821
+ # @return [SpecsState] the representation of the state of the manifest
822
+ # specifications.
823
+ #
824
+ def generate_sandbox_state
825
+ sandbox_state = nil
826
+ UI.section 'Comparing resolved specification to the sandbox manifest' do
827
+ sandbox_analyzer = SandboxAnalyzer.new(sandbox, result.specifications, update_mode?, lockfile)
828
+ sandbox_state = sandbox_analyzer.analyze
829
+ sandbox_state.print
830
+ end
831
+ sandbox_state
832
+ end
833
+
834
+ #-----------------------------------------------------------------------#
835
+
836
+ # @!group Analysis internal products
837
+
838
+ # @return [Molinillo::DependencyGraph<Dependency>] the dependencies
839
+ # generated by the lockfile that prevent the resolver to update a
840
+ # Pod.
841
+ #
842
+ attr_reader :locked_dependencies
843
+
844
+ #-----------------------------------------------------------------------#
845
+
846
+ public
847
+
848
+ # Returns the sources used to query for specifications
849
+ #
850
+ # When no explicit Podfile sources or plugin sources are defined, this
851
+ # defaults to the master spec repository.
852
+ # available sources ({config.sources_manager.all}).
853
+ #
854
+ # @return [Array<Source>] the sources to be used in finding
855
+ # specifications, as specified by the {#podfile} or all sources.
856
+ #
857
+ def sources
858
+ @sources ||= begin
859
+ sources = podfile.sources
860
+ plugin_sources = @plugin_sources || []
861
+
862
+ # Add any sources specified using the :source flag on individual dependencies.
863
+ dependency_sources = @podfile_dependency_cache.podfile_dependencies.map(&:podspec_repo).compact
864
+ all_dependencies_have_sources = dependency_sources.count == @podfile_dependency_cache.podfile_dependencies.count
865
+
866
+ if all_dependencies_have_sources
867
+ sources = dependency_sources
868
+ elsif has_dependencies? && sources.empty? && plugin_sources.empty?
869
+ sources = ['https://github.com/CocoaPods/Specs.git']
870
+ else
871
+ sources += dependency_sources
872
+ end
873
+
874
+ result = sources.uniq.map do |source_url|
875
+ config.sources_manager.find_or_create_source_with_url(source_url)
876
+ end
877
+ unless plugin_sources.empty?
878
+ result.insert(0, *plugin_sources)
879
+ end
880
+ result
881
+ end
882
+ end
883
+
884
+ #-----------------------------------------------------------------------#
885
+
886
+ private
887
+
888
+ # @!group Analysis sub-steps
889
+
890
+ # Checks whether the platform is specified if not integrating
891
+ #
892
+ # @return [void]
893
+ #
894
+ def verify_platforms_specified!
895
+ unless installation_options.integrate_targets?
896
+ @podfile_dependency_cache.target_definition_list.each do |target_definition|
897
+ if !target_definition.empty? && target_definition.platform.nil?
898
+ raise Informative, 'It is necessary to specify the platform in the Podfile if not integrating.'
899
+ end
900
+ end
901
+ end
902
+ end
903
+
904
+ # Precompute information for each target_definition in the Podfile
905
+ #
906
+ # @note The platforms are computed and added to each target_definition
907
+ # because it might be necessary to infer the platform from the
908
+ # user targets.
909
+ #
910
+ # @return [Hash{TargetDefinition => TargetInspectionResult}]
911
+ #
912
+ def inspect_targets_to_integrate
913
+ inspection_result = {}
914
+ UI.section 'Inspecting targets to integrate' do
915
+ inspectors = @podfile_dependency_cache.target_definition_list.map do |target_definition|
916
+ next if target_definition.abstract?
917
+ TargetInspector.new(target_definition, config.installation_root)
918
+ end.compact
919
+ inspectors.group_by(&:compute_project_path).each do |project_path, target_inspectors|
920
+ project = Xcodeproj::Project.open(project_path)
921
+ target_inspectors.each do |inspector|
922
+ target_definition = inspector.target_definition
923
+ results = inspector.compute_results(project)
924
+ inspection_result[target_definition] = results
925
+ UI.message('Using `ARCHS` setting to build architectures of ' \
926
+ "target `#{target_definition.label}`: (`#{results.archs.join('`, `')}`)")
927
+ end
928
+ end
929
+ end
930
+ inspection_result
931
+ end
932
+ end
933
+ end
934
+ end