xcocoapods 1.5.3

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