cocoapods-dykit 0.5.2 → 0.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 (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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 16bd383324c6991bf7c697138264ec8286b4be63
4
- data.tar.gz: 4aeb969c00319d04cd1400d448c2195625eaa33e
3
+ metadata.gz: 9960e4f98da76b9263ffa9f64053a4252444f9d3
4
+ data.tar.gz: 95db61d9669d6c6f96531da70be6ec5740096ba9
5
5
  SHA512:
6
- metadata.gz: 2dc160a91674b14aa4db6a8d4f68ebc064d5f760b6e39ff8eba872bd6984cab9202831132ff7f5f3c57d3ff04be72b543f105d0332409f1fbabe00ef67782b59
7
- data.tar.gz: 3d178c43188b461944a947ec0261205533034d79de2a45c0ccdaf8024c937676b88d946accdb13416c7f00b51f02d03afc4bc8048a1af24fbe473ec129aeed16
6
+ metadata.gz: 7480c952b1b8eea11abd9528840c39dbe822359456ac11fe1406f288499a4382c778eb8e955a583b3721a2f63e99a6162d570d79f04b9356c3a72e7b4b9381e1
7
+ data.tar.gz: 646805ab6fbc89e624fb3d96c6485d4d329cb38acff9c4b26727f3e8ad0efd131651ff2e203d2a69335b7988b4b81830ebef6cec8b152a5d2de3a54c4effc63f
data/lib/pod/command.rb CHANGED
@@ -2,3 +2,5 @@ require 'pod/command/lib/dylint'
2
2
  require 'pod/command/repo/dypush'
3
3
  require 'pod/command/fmwk/create'
4
4
  require 'pod/command/fmwk/build'
5
+ require 'pod/command/dyinstall'
6
+ require 'pod/command/dyupdate'
@@ -0,0 +1,51 @@
1
+ # require 'cocoapods/installer'
2
+ require File.expand_path('../../installer.rb', __FILE__)
3
+ module Pod
4
+ class Command
5
+ class DyInstall < Command
6
+ include RepoUpdate
7
+ include ProjectDirectory
8
+
9
+ self.summary = 'Install project dependencies according to versions from a Podfile.lock'
10
+
11
+ self.description = <<-DESC
12
+ Downloads all dependencies defined in `Podfile` and creates an Xcode
13
+ Pods library project in `./Pods`.
14
+
15
+ The Xcode project file should be specified in your `Podfile` like this:
16
+
17
+ project 'path/to/XcodeProject.xcodeproj'
18
+
19
+ If no project is specified, then a search for an Xcode project will
20
+ be made. If more than one Xcode project is found, the command will
21
+ raise an error.
22
+
23
+ This will configure the project to reference the Pods static library,
24
+ add a build configuration file, and add a post build script to copy
25
+ Pod resources.
26
+
27
+ This may return one of several error codes if it encounters problems.
28
+ * `1` Generic error code
29
+ * `31` Spec not found (i.e out-of-date source repos, mistyped Pod name etc...)
30
+ DESC
31
+
32
+ def self.options
33
+ [
34
+ ['--repo-update', 'Force running `pod repo update` before install'],
35
+ ].concat(super).reject { |(name, _)| name == '--no-repo-update' }
36
+ end
37
+
38
+ def run
39
+ verify_podfile_exists!
40
+ installer = installer_for_config
41
+ installer.repo_update = repo_update?(:default => false)
42
+ installer.update = false
43
+ installer.install!
44
+ end
45
+
46
+ def installer_for_config
47
+ DyInstaller.new(config.sandbox, config.podfile, config.lockfile)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,106 @@
1
+ require File.expand_path('../../installer.rb', __FILE__)
2
+ module Pod
3
+ class Command
4
+ class DyUpdate < Command
5
+ include RepoUpdate
6
+ include ProjectDirectory
7
+
8
+ self.summary = 'Update outdated project dependencies and create new ' \
9
+ 'Podfile.lock'
10
+
11
+ self.description = <<-DESC
12
+ Updates the Pods identified by the specified `POD_NAMES`, which is a
13
+ space-delimited list of pod names. If no `POD_NAMES` are specified, it
14
+ updates all the Pods, ignoring the contents of the Podfile.lock. This
15
+ command is reserved for the update of dependencies; pod install should
16
+ be used to install changes to the Podfile.
17
+ DESC
18
+
19
+ self.arguments = [
20
+ CLAide::Argument.new('POD_NAMES', false, true),
21
+ ]
22
+
23
+ def self.options
24
+ [
25
+ ['--sources=https://github.com/artsy/Specs,master', 'The sources from which to update dependent pods. ' \
26
+ 'Multiple sources must be comma-delimited. The master repo will not be included by default with this option.'],
27
+ ['--exclude-pods=podName', 'Pods to exclude during update. Multiple pods must be comma-delimited.'],
28
+ ].concat(super)
29
+ end
30
+
31
+ def initialize(argv)
32
+ @pods = argv.arguments! unless argv.arguments.empty?
33
+
34
+ source_urls = argv.option('sources', '').split(',')
35
+ excluded_pods = argv.option('exclude-pods', '').split(',')
36
+ unless source_urls.empty?
37
+ source_pods = source_urls.flat_map { |url| config.sources_manager.source_with_name_or_url(url).pods }
38
+ unless source_pods.empty?
39
+ source_pods = source_pods.select { |pod| config.lockfile.pod_names.include?(pod) }
40
+ if @pods
41
+ @pods += source_pods
42
+ else
43
+ @pods = source_pods unless source_pods.empty?
44
+ end
45
+ end
46
+ end
47
+
48
+ unless excluded_pods.empty?
49
+ @pods ||= config.lockfile.pod_names.dup
50
+
51
+ non_installed_pods = (excluded_pods - @pods)
52
+ unless non_installed_pods.empty?
53
+ pluralized_words = non_installed_pods.length > 1 ? %w(Pods are) : %w(Pod is)
54
+ message = "Trying to skip `#{non_installed_pods.join('`, `')}` #{pluralized_words.first} " \
55
+ "which #{pluralized_words.last} not installed"
56
+ raise Informative, message
57
+ end
58
+
59
+ @pods.delete_if { |pod| excluded_pods.include?(pod) }
60
+ end
61
+
62
+ super
63
+ end
64
+
65
+ # Check if all given pods are installed
66
+ #
67
+ def verify_pods_are_installed!
68
+ lockfile_roots = config.lockfile.pod_names.map { |p| Specification.root_name(p) }
69
+ missing_pods = @pods.map { |p| Specification.root_name(p) }.select do |pod|
70
+ !lockfile_roots.include?(pod)
71
+ end
72
+
73
+ unless missing_pods.empty?
74
+ message = if missing_pods.length > 1
75
+ "Pods `#{missing_pods.join('`, `')}` are not " \
76
+ 'installed and cannot be updated'
77
+ else
78
+ "The `#{missing_pods.first}` Pod is not installed " \
79
+ 'and cannot be updated'
80
+ end
81
+ raise Informative, message
82
+ end
83
+ end
84
+
85
+ def run
86
+ verify_podfile_exists!
87
+
88
+ installer = installer_for_config
89
+ installer.repo_update = repo_update?(:default => true)
90
+ if @pods
91
+ verify_lockfile_exists!
92
+ verify_pods_are_installed!
93
+ installer.update = { :pods => @pods }
94
+ else
95
+ UI.puts 'Update all pods'.yellow
96
+ installer.update = true
97
+ end
98
+ installer.install!
99
+ end
100
+
101
+ def installer_for_config
102
+ DyInstaller.new(config.sandbox, config.podfile, config.lockfile)
103
+ end
104
+ end
105
+ end
106
+ end
@@ -6,6 +6,10 @@ module Pod
6
6
  class Fmwk < Command
7
7
  self.abstract_command = true
8
8
  self.summary = 'Develop pods'
9
+
10
+ def run
11
+ puts File.expand_path('installer/analyzer')
12
+ end
9
13
  end
10
14
  end
11
15
  end
@@ -69,6 +69,7 @@ module Pod
69
69
  end
70
70
 
71
71
  def run
72
+ UI.puts File.expand_path('installer/analyzer')
72
73
  UI.puts
73
74
  podspecs_to_lint.each do |podspec|
74
75
  validator = Pod::DyValidator.new(podspec, @source_urls)
@@ -1,3 +1,3 @@
1
1
  module CocoapodsDylint
2
- VERSION = "0.5.2"
2
+ VERSION = "0.5.3"
3
3
  end
@@ -0,0 +1,715 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+ require 'fileutils'
3
+
4
+ module Pod
5
+ # The Installer is responsible of taking a Podfile and transform it in the
6
+ # Pods libraries. It also integrates the user project so the Pods
7
+ # libraries can be used out of the box.
8
+ #
9
+ # The Installer is capable of doing incremental updates to an existing Pod
10
+ # installation.
11
+ #
12
+ # The Installer gets the information that it needs mainly from 3 files:
13
+ #
14
+ # - Podfile: The specification written by the user that contains
15
+ # information about targets and Pods.
16
+ # - Podfile.lock: Contains information about the pods that were previously
17
+ # installed and in concert with the Podfile provides information about
18
+ # which specific version of a Pod should be installed. This file is
19
+ # ignored in update mode.
20
+ # - Manifest.lock: A file contained in the Pods folder that keeps track of
21
+ # the pods installed in the local machine. This files is used once the
22
+ # exact versions of the Pods has been computed to detect if that version
23
+ # is already installed. This file is not intended to be kept under source
24
+ # control and is a copy of the Podfile.lock.
25
+ #
26
+ # The Installer is designed to work in environments where the Podfile folder
27
+ # is under source control and environments where it is not. The rest of the
28
+ # files, like the user project and the workspace are assumed to be under
29
+ # source control.
30
+ #
31
+ class DyInstaller
32
+ # autoload :Analyzer, 'cocoapods/installer/analyzer'
33
+ # autoload :InstallationOptions, 'cocoapods/installer/installation_options'
34
+ # autoload :PostInstallHooksContext, 'cocoapods/installer/post_install_hooks_context'
35
+ # autoload :PreInstallHooksContext, 'cocoapods/installer/pre_install_hooks_context'
36
+ # autoload :SourceProviderHooksContext, 'cocoapods/installer/source_provider_hooks_context'
37
+ # autoload :PodfileValidator, 'cocoapods/installer/podfile_validator'
38
+ # autoload :PodSourceInstaller, 'cocoapods/installer/pod_source_installer'
39
+ # autoload :PodSourcePreparer, 'cocoapods/installer/pod_source_preparer'
40
+ # autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
41
+ # autoload :Xcode, 'cocoapods/installer/xcode'
42
+ autoload :Analyzer, File.expand_path('../installer/analyzer', __FILE__)
43
+ autoload :InstallationOptions, File.expand_path('../installer/installation_options', __FILE__)
44
+ autoload :PostInstallHooksContext, File.expand_path('../installer/post_install_hooks_context', __FILE__)
45
+ autoload :PreInstallHooksContext, File.expand_path('../installer/pre_install_hooks_context', __FILE__)
46
+ autoload :SourceProviderHooksContext, File.expand_path('../installer/source_provider_hooks_context', __FILE__)
47
+ autoload :PodfileValidator, File.expand_path('../installer/podfile_validator', __FILE__)
48
+ autoload :PodSourceInstaller, File.expand_path('../installer/pod_source_installer', __FILE__)
49
+ autoload :PodSourcePreparer, File.expand_path('../installer/pod_source_preparer', __FILE__)
50
+ autoload :UserProjectIntegrator, File.expand_path('../installer/user_project_integrator', __FILE__)
51
+ autoload :Xcode, File.expand_path('../installer/xcode', __FILE__)
52
+
53
+ include Config::Mixin
54
+ include InstallationOptions::Mixin
55
+
56
+ delegate_installation_options { podfile }
57
+
58
+ # @return [Sandbox] The sandbox where the Pods should be installed.
59
+ #
60
+ attr_reader :sandbox
61
+
62
+ # @return [Podfile] The Podfile specification that contains the information
63
+ # of the Pods that should be installed.
64
+ #
65
+ attr_reader :podfile
66
+
67
+ # @return [Lockfile] The Lockfile that stores the information about the
68
+ # Pods previously installed on any machine.
69
+ #
70
+ attr_reader :lockfile
71
+
72
+ # Initialize a new instance
73
+ #
74
+ # @param [Sandbox] sandbox @see sandbox
75
+ # @param [Podfile] podfile @see podfile
76
+ # @param [Lockfile] lockfile @see lockfile
77
+ #
78
+ def initialize(sandbox, podfile, lockfile = nil)
79
+ @sandbox = sandbox
80
+ @podfile = podfile
81
+ @lockfile = lockfile
82
+
83
+ @use_default_plugins = true
84
+ @has_dependencies = true
85
+ end
86
+
87
+ # @return [Hash, Boolean, nil] Pods that have been requested to be
88
+ # updated or true if all Pods should be updated.
89
+ # If all Pods should been updated the contents of the Lockfile are
90
+ # not taken into account for deciding what Pods to install.
91
+ #
92
+ attr_accessor :update
93
+
94
+ # @return [Boolean] Whether it has dependencies. Defaults to true.
95
+ #
96
+ attr_accessor :has_dependencies
97
+ alias_method :has_dependencies?, :has_dependencies
98
+
99
+ # @return [Boolean] Whether the spec repos should be updated.
100
+ #
101
+ attr_accessor :repo_update
102
+ alias_method :repo_update?, :repo_update
103
+
104
+ # @return [Boolean] Whether default plugins should be used during
105
+ # installation. Defaults to true.
106
+ #
107
+ attr_accessor :use_default_plugins
108
+ alias_method :use_default_plugins?, :use_default_plugins
109
+
110
+ # Installs the Pods.
111
+ #
112
+ # The installation process is mostly linear with a few minor complications
113
+ # to keep in mind:
114
+ #
115
+ # - The stored podspecs need to be cleaned before the resolution step
116
+ # otherwise the sandbox might return an old podspec and not download
117
+ # the new one from an external source.
118
+ # - The resolver might trigger the download of Pods from external sources
119
+ # necessary to retrieve their podspec (unless it is instructed not to
120
+ # do it).
121
+ #
122
+ # @return [void]
123
+ #
124
+ def install!
125
+ before_prepare = Time.now
126
+ prepare
127
+ before_resolve = Time.now
128
+ resolve_dependencies
129
+ before_download = Time.now
130
+ download_dependencies
131
+ before_validate_targets = Time.now
132
+ validate_targets
133
+ before_generate = Time.now
134
+ generate_pods_project
135
+ before_integreate = Time.now
136
+ if installation_options.integrate_targets?
137
+ integrate_user_project
138
+ else
139
+ UI.section 'Skipping User Project Integration'
140
+ end
141
+ before_post = Time.now
142
+ perform_post_install_actions
143
+ after_post = Time.now
144
+ puts('Time result:')
145
+ puts(" 【total: #{after_post - before_prepare}】")
146
+ puts(" 【prepare: #{before_resolve - before_prepare}】")
147
+ puts(" 【resolve_dependencies: #{before_download - before_resolve}】")
148
+ puts(" 【download_dependencies: #{before_validate_targets - before_download}】")
149
+ puts(" 【validate_targets: #{before_generate - before_validate_targets}】")
150
+ puts(" 【generate_pods_project: #{before_integreate - before_generate}】")
151
+ puts(" 【integrate_user_project: #{before_post - before_integreate}】")
152
+ puts(" 【perform_post_install_actions: #{after_post - before_post}】")
153
+ end
154
+
155
+ def prepare
156
+ # Raise if pwd is inside Pods
157
+ if Dir.pwd.start_with?(sandbox.root.to_path)
158
+ message = 'Command should be run from a directory outside Pods directory.'
159
+ message << "\n\n\tCurrent directory is #{UI.path(Pathname.pwd)}\n"
160
+ raise Informative, message
161
+ end
162
+ UI.message 'Preparing' do
163
+ deintegrate_if_different_major_version
164
+ sandbox.prepare
165
+ ensure_plugins_are_installed!
166
+ run_plugins_pre_install_hooks
167
+ end
168
+ end
169
+
170
+ # @return [Analyzer] The analyzer used to resolve dependencies
171
+ #
172
+ def resolve_dependencies
173
+ plugin_sources = run_source_provider_hooks
174
+ analyzer = create_analyzer(plugin_sources)
175
+
176
+ UI.section 'Updating local specs repositories' do
177
+ analyzer.update_repositories
178
+ end if repo_update?
179
+
180
+ UI.section 'Analyzing dependencies' do
181
+ analyze(analyzer)
182
+ validate_build_configurations
183
+ clean_sandbox
184
+ end
185
+ analyzer
186
+ end
187
+
188
+ def download_dependencies
189
+ UI.section 'Downloading dependencies' do
190
+ create_file_accessors
191
+ install_pod_sources
192
+ run_podfile_pre_install_hooks
193
+ clean_pod_sources
194
+ end
195
+ end
196
+
197
+ #-------------------------------------------------------------------------#
198
+
199
+ # @!group Pods Project Generation
200
+
201
+ private
202
+
203
+ def create_generator
204
+ Xcode::PodsProjectGenerator.new(aggregate_targets, sandbox, pod_targets, analysis_result, installation_options, config)
205
+ end
206
+
207
+ # Generate the 'Pods/Pods.xcodeproj' project.
208
+ #
209
+ def generate_pods_project(generator = create_generator)
210
+ UI.section 'Generating Pods project' do
211
+ generator.generate!
212
+ @pods_project = generator.project
213
+ run_podfile_post_install_hooks
214
+ generator.write
215
+ generator.share_development_pod_schemes
216
+ write_lockfiles
217
+ end
218
+ end
219
+
220
+ #-------------------------------------------------------------------------#
221
+
222
+ public
223
+
224
+ # @!group Installation results
225
+
226
+ # @return [Analyzer] the analyzer which provides the information about what
227
+ # needs to be installed.
228
+ #
229
+ attr_reader :analysis_result
230
+
231
+ # @return [Pod::Project] the `Pods/Pods.xcodeproj` project.
232
+ #
233
+ attr_reader :pods_project
234
+
235
+ # @return [Array<String>] The Pods that should be installed.
236
+ #
237
+ attr_reader :names_of_pods_to_install
238
+
239
+ # @return [Array<AggregateTarget>] The model representations of an
240
+ # aggregation of pod targets generated for a target definition
241
+ # in the Podfile as result of the analyzer.
242
+ #
243
+ attr_reader :aggregate_targets
244
+
245
+ # @return [Array<PodTarget>] The model representations of pod targets
246
+ # generated as result of the analyzer.
247
+ #
248
+ def pod_targets
249
+ aggregate_target_pod_targets = aggregate_targets.flat_map(&:pod_targets)
250
+ test_dependent_targets = aggregate_target_pod_targets.flat_map(&:test_dependent_targets)
251
+ (aggregate_target_pod_targets + test_dependent_targets).uniq
252
+ end
253
+
254
+ # @return [Array<Specification>] The specifications that where installed.
255
+ #
256
+ attr_accessor :installed_specs
257
+
258
+ #-------------------------------------------------------------------------#
259
+
260
+ private
261
+
262
+ # @!group Installation steps
263
+
264
+ # Performs the analysis.
265
+ #
266
+ # @return [void]
267
+ #
268
+ def analyze(analyzer = create_analyzer)
269
+ analyzer.update = update
270
+ @analysis_result = analyzer.analyze
271
+ @aggregate_targets = analyzer.result.targets
272
+ end
273
+
274
+ def create_analyzer(plugin_sources = nil)
275
+ Analyzer.new(sandbox, podfile, lockfile, plugin_sources).tap do |analyzer|
276
+ analyzer.installation_options = installation_options
277
+ analyzer.has_dependencies = has_dependencies?
278
+ end
279
+ end
280
+
281
+ # Ensures that the white-listed build configurations are known to prevent
282
+ # silent typos.
283
+ #
284
+ # @raise If an unknown user configuration is found.
285
+ #
286
+ def validate_build_configurations
287
+ whitelisted_configs = pod_targets.
288
+ flat_map(&:target_definitions).
289
+ flat_map(&:all_whitelisted_configurations).
290
+ map(&:downcase).
291
+ uniq
292
+ all_user_configurations = analysis_result.all_user_build_configurations.keys.map(&:downcase)
293
+
294
+ remainder = whitelisted_configs - all_user_configurations
295
+ unless remainder.empty?
296
+ raise Informative,
297
+ "Unknown #{'configuration'.pluralize(remainder.size)} whitelisted: #{remainder.sort.to_sentence}. " \
298
+ "CocoaPods found #{all_user_configurations.sort.to_sentence}, did you mean one of these?"
299
+ end
300
+ end
301
+
302
+ # @return [void] In this step we clean all the folders that will be
303
+ # regenerated from scratch and any file which might not be
304
+ # overwritten.
305
+ #
306
+ # @todo [#247] Clean the headers of only the pods to install.
307
+ #
308
+ def clean_sandbox
309
+ sandbox.public_headers.implode!
310
+ target_support_dirs = sandbox.target_support_files_root.children.select(&:directory?)
311
+ pod_targets.each do |pod_target|
312
+ pod_target.build_headers.implode!
313
+ target_support_dirs.delete(pod_target.support_files_dir)
314
+ end
315
+
316
+ aggregate_targets.each do |aggregate_target|
317
+ target_support_dirs.delete(aggregate_target.support_files_dir)
318
+ end
319
+
320
+ target_support_dirs.each { |dir| FileUtils.rm_rf(dir) }
321
+
322
+ unless sandbox_state.deleted.empty?
323
+ title_options = { :verbose_prefix => '-> '.red }
324
+ sandbox_state.deleted.each do |pod_name|
325
+ UI.titled_section("Removing #{pod_name}".red, title_options) do
326
+ sandbox.clean_pod(pod_name)
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ # @return [void] In this step we create the file accessors for the pod
333
+ # targets.
334
+ #
335
+ def create_file_accessors
336
+ sandbox.create_file_accessors(pod_targets)
337
+ end
338
+
339
+ # Downloads, installs the documentation and cleans the sources of the Pods
340
+ # which need to be installed.
341
+ #
342
+ # @return [void]
343
+ #
344
+ def install_pod_sources
345
+ @installed_specs = []
346
+ pods_to_install = sandbox_state.added | sandbox_state.changed
347
+ title_options = { :verbose_prefix => '-> '.green }
348
+ root_specs.sort_by(&:name).each do |spec|
349
+ if pods_to_install.include?(spec.name)
350
+ if sandbox_state.changed.include?(spec.name) && sandbox.manifest
351
+ current_version = spec.version
352
+ previous_version = sandbox.manifest.version(spec.name)
353
+ has_changed_version = current_version != previous_version
354
+ current_repo = analysis_result.specs_by_source.detect { |key, values| break key if values.map(&:name).include?(spec.name) }
355
+ current_repo &&= current_repo.url || current_repo.name
356
+ previous_spec_repo = sandbox.manifest.spec_repo(spec.name)
357
+ has_changed_repo = !previous_spec_repo.nil? && current_repo && (current_repo != previous_spec_repo)
358
+ title = "Installing #{spec.name} #{spec.version}"
359
+ title << " (was #{previous_version} and source changed to `#{current_repo}` from `#{previous_spec_repo}`)" if has_changed_version && has_changed_repo
360
+ title << " (was #{previous_version})" if has_changed_version && !has_changed_repo
361
+ title << " (source changed to `#{current_repo}` from `#{previous_spec_repo}`)" if !has_changed_version && has_changed_repo
362
+ else
363
+ title = "Installing #{spec}"
364
+ end
365
+ UI.titled_section(title.green, title_options) do
366
+ install_source_of_pod(spec.name)
367
+ end
368
+ else
369
+ UI.titled_section("Using #{spec}", title_options) do
370
+ create_pod_installer(spec.name)
371
+ end
372
+ end
373
+ end
374
+ end
375
+
376
+ def create_pod_installer(pod_name)
377
+ specs_by_platform = {}
378
+ pod_targets.each do |pod_target|
379
+ if pod_target.root_spec.name == pod_name
380
+ specs_by_platform[pod_target.platform] ||= []
381
+ specs_by_platform[pod_target.platform].concat(pod_target.specs)
382
+ end
383
+ end
384
+
385
+ raise Informative, "Could not install '#{pod_name}' pod. There is no target that supports it." if specs_by_platform.empty?
386
+
387
+ @pod_installers ||= []
388
+ pod_installer = PodSourceInstaller.new(sandbox, specs_by_platform, :can_cache => installation_options.clean?)
389
+ @pod_installers << pod_installer
390
+ pod_installer
391
+ end
392
+
393
+ # Install the Pods. If the resolver indicated that a Pod should be
394
+ # installed and it exits, it is removed and then reinstalled. In any case if
395
+ # the Pod doesn't exits it is installed.
396
+ #
397
+ # @return [void]
398
+ #
399
+ def install_source_of_pod(pod_name)
400
+ pod_installer = create_pod_installer(pod_name)
401
+ pod_installer.install!
402
+ @installed_specs.concat(pod_installer.specs_by_platform.values.flatten.uniq)
403
+ end
404
+
405
+ # Cleans the sources of the Pods if the config instructs to do so.
406
+ #
407
+ # @todo Why the @pod_installers might be empty?
408
+ #
409
+ def clean_pod_sources
410
+ return unless installation_options.clean?
411
+ return unless @pod_installers
412
+ @pod_installers.each(&:clean!)
413
+ end
414
+
415
+ # Unlocks the sources of the Pods.
416
+ #
417
+ # @todo Why the @pod_installers might be empty?
418
+ #
419
+ def unlock_pod_sources
420
+ return unless @pod_installers
421
+ @pod_installers.each do |installer|
422
+ pod_target = pod_targets.find { |target| target.pod_name == installer.name }
423
+ installer.unlock_files!(pod_target.file_accessors)
424
+ end
425
+ end
426
+
427
+ # Locks the sources of the Pods if the config instructs to do so.
428
+ #
429
+ # @todo Why the @pod_installers might be empty?
430
+ #
431
+ def lock_pod_sources
432
+ return unless installation_options.lock_pod_sources?
433
+ return unless @pod_installers
434
+ @pod_installers.each do |installer|
435
+ pod_target = pod_targets.find { |target| target.pod_name == installer.name }
436
+ installer.lock_files!(pod_target.file_accessors)
437
+ end
438
+ end
439
+
440
+ def validate_targets
441
+ validator = Xcode::TargetValidator.new(aggregate_targets, pod_targets)
442
+ validator.validate!
443
+ end
444
+
445
+ # Runs the registered callbacks for the plugins pre install hooks.
446
+ #
447
+ # @return [void]
448
+ #
449
+ def run_plugins_pre_install_hooks
450
+ context = PreInstallHooksContext.generate(sandbox, podfile, lockfile)
451
+ HooksManager.run(:pre_install, context, plugins)
452
+ end
453
+
454
+ # Performs any post-installation actions
455
+ #
456
+ # @return [void]
457
+ #
458
+ def perform_post_install_actions
459
+ unlock_pod_sources
460
+ run_plugins_post_install_hooks
461
+ warn_for_deprecations
462
+ warn_for_installed_script_phases
463
+ lock_pod_sources
464
+ print_post_install_message
465
+ end
466
+
467
+ def print_post_install_message
468
+ podfile_dependencies = analysis_result.podfile_dependency_cache.podfile_dependencies.size
469
+ pods_installed = root_specs.size
470
+ title_options = { :verbose_prefix => '-> '.green }
471
+ UI.titled_section('Pod installation complete! ' \
472
+ "There #{podfile_dependencies == 1 ? 'is' : 'are'} #{podfile_dependencies} " \
473
+ "#{'dependency'.pluralize(podfile_dependencies)} from the Podfile " \
474
+ "and #{pods_installed} total #{'pod'.pluralize(pods_installed)} installed.".green,
475
+ title_options)
476
+ end
477
+
478
+ # Runs the registered callbacks for the plugins post install hooks.
479
+ #
480
+ def run_plugins_post_install_hooks
481
+ context = PostInstallHooksContext.generate(sandbox, aggregate_targets)
482
+ HooksManager.run(:post_install, context, plugins)
483
+ end
484
+
485
+ # Runs the registered callbacks for the source provider plugin hooks.
486
+ #
487
+ # @return [void]
488
+ #
489
+ def run_source_provider_hooks
490
+ context = SourceProviderHooksContext.generate
491
+ HooksManager.run(:source_provider, context, plugins)
492
+ context.sources
493
+ end
494
+
495
+ # Run the deintegrator against all projects in the installation root if the
496
+ # current CocoaPods major version part is different than the one in the
497
+ # lockfile.
498
+ #
499
+ # @return [void]
500
+ #
501
+ def deintegrate_if_different_major_version
502
+ return unless lockfile
503
+ return if lockfile.cocoapods_version.major == Version.create(VERSION).major
504
+ UI.section('Re-creating CocoaPods due to major version update.') do
505
+ projects = Pathname.glob(config.installation_root + '*.xcodeproj').map { |path| Xcodeproj::Project.open(path) }
506
+ deintegrator = Deintegrator.new
507
+ projects.each do |project|
508
+ config.with_changes(:silent => true) { deintegrator.deintegrate_project(project) }
509
+ project.save if project.dirty?
510
+ end
511
+ end
512
+ end
513
+
514
+ # Ensures that all plugins specified in the {#podfile} are loaded.
515
+ #
516
+ # @return [void]
517
+ #
518
+ def ensure_plugins_are_installed!
519
+ require 'claide/command/plugin_manager'
520
+
521
+ loaded_plugins = Command::PluginManager.specifications.map(&:name)
522
+
523
+ podfile.plugins.keys.each do |plugin|
524
+ unless loaded_plugins.include? plugin
525
+ raise Informative, "Your Podfile requires that the plugin `#{plugin}` be installed. Please install it and try installation again."
526
+ end
527
+ end
528
+ end
529
+
530
+ DEFAULT_PLUGINS = { 'cocoapods-stats' => {} }
531
+
532
+ # Returns the plugins that should be run, as indicated by the default
533
+ # plugins and the podfile's plugins
534
+ #
535
+ # @return [Hash<String, Hash>] The plugins to be used
536
+ #
537
+ def plugins
538
+ if use_default_plugins?
539
+ DEFAULT_PLUGINS.merge(podfile.plugins)
540
+ else
541
+ podfile.plugins
542
+ end
543
+ end
544
+
545
+ # Prints a warning for any pods that are deprecated
546
+ #
547
+ # @return [void]
548
+ #
549
+ def warn_for_deprecations
550
+ deprecated_pods = root_specs.select do |spec|
551
+ spec.deprecated || spec.deprecated_in_favor_of
552
+ end
553
+ deprecated_pods.each do |spec|
554
+ if spec.deprecated_in_favor_of
555
+ UI.warn "#{spec.name} has been deprecated in " \
556
+ "favor of #{spec.deprecated_in_favor_of}"
557
+ else
558
+ UI.warn "#{spec.name} has been deprecated"
559
+ end
560
+ end
561
+ end
562
+
563
+ # Prints a warning for any pods that included script phases
564
+ #
565
+ # @return [void]
566
+ #
567
+ def warn_for_installed_script_phases
568
+ pods_to_install = sandbox_state.added | sandbox_state.changed
569
+ pod_targets.group_by(&:pod_name).each do |name, pod_targets|
570
+ if pods_to_install.include?(name)
571
+ script_phase_count = pod_targets.inject(0) { |sum, target| sum + target.script_phases.count }
572
+ unless script_phase_count.zero?
573
+ UI.warn "#{name} has added #{script_phase_count} #{'script phase'.pluralize(script_phase_count)}. " \
574
+ 'Please inspect before executing a build. See `https://guides.cocoapods.org/syntax/podspec.html#script_phases` for more information.'
575
+ end
576
+ end
577
+ end
578
+ end
579
+
580
+ # Writes the Podfile and the lock files.
581
+ #
582
+ # @todo Pass the checkout options to the Lockfile.
583
+ #
584
+ # @return [void]
585
+ #
586
+ def write_lockfiles
587
+ external_source_pods = analysis_result.podfile_dependency_cache.podfile_dependencies.select(&:external_source).map(&:root_name).uniq
588
+ checkout_options = sandbox.checkout_sources.select { |root_name, _| external_source_pods.include? root_name }
589
+ @lockfile = Lockfile.generate(podfile, analysis_result.specifications, checkout_options, analysis_result.specs_by_source)
590
+
591
+ UI.message "- Writing Lockfile in #{UI.path config.lockfile_path}" do
592
+ @lockfile.write_to_disk(config.lockfile_path)
593
+ end
594
+
595
+ UI.message "- Writing Manifest in #{UI.path sandbox.manifest_path}" do
596
+ sandbox.manifest_path.open('w') do |f|
597
+ f.write config.lockfile_path.read
598
+ end
599
+ end
600
+ end
601
+
602
+ # Integrates the user projects adding the dependencies on the CocoaPods
603
+ # libraries, setting them up to use the xcconfigs and performing other
604
+ # actions. This step is also responsible of creating the workspace if
605
+ # needed.
606
+ #
607
+ # @return [void]
608
+ #
609
+ # @todo [#397] The libraries should be cleaned and the re-added on every
610
+ # installation. Maybe a clean_user_project phase should be added.
611
+ # In any case it appears to be a good idea store target definition
612
+ # information in the lockfile.
613
+ #
614
+ def integrate_user_project
615
+ UI.section "Integrating client #{'project'.pluralize(aggregate_targets.map(&:user_project_path).uniq.count)}" do
616
+ installation_root = config.installation_root
617
+ integrator = UserProjectIntegrator.new(podfile, sandbox, installation_root, aggregate_targets)
618
+ integrator.integrate!
619
+ end
620
+ end
621
+
622
+ #-------------------------------------------------------------------------#
623
+
624
+ private
625
+
626
+ # @!group Hooks
627
+
628
+ # Runs the pre install hooks of the installed specs and of the Podfile.
629
+ #
630
+ # @return [void]
631
+ #
632
+ def run_podfile_pre_install_hooks
633
+ UI.message '- Running pre install hooks' do
634
+ executed = run_podfile_pre_install_hook
635
+ UI.message '- Podfile' if executed
636
+ end
637
+ end
638
+
639
+ # Runs the pre install hook of the Podfile
640
+ #
641
+ # @raise Raises an informative if the hooks raises.
642
+ #
643
+ # @return [Boolean] Whether the hook was run.
644
+ #
645
+ def run_podfile_pre_install_hook
646
+ podfile.pre_install!(self)
647
+ rescue => e
648
+ raise Informative, 'An error occurred while processing the pre-install ' \
649
+ 'hook of the Podfile.' \
650
+ "\n\n#{e.message}\n\n#{e.backtrace * "\n"}"
651
+ end
652
+
653
+ # Runs the post install hooks of the installed specs and of the Podfile.
654
+ #
655
+ # @note Post install hooks run _before_ saving of project, so that they
656
+ # can alter it before it is written to the disk.
657
+ #
658
+ # @return [void]
659
+ #
660
+ def run_podfile_post_install_hooks
661
+ UI.message '- Running post install hooks' do
662
+ executed = run_podfile_post_install_hook
663
+ UI.message '- Podfile' if executed
664
+ end
665
+ end
666
+
667
+ # Runs the post install hook of the Podfile
668
+ #
669
+ # @raise Raises an informative if the hooks raises.
670
+ #
671
+ # @return [Boolean] Whether the hook was run.
672
+ #
673
+ def run_podfile_post_install_hook
674
+ podfile.post_install!(self)
675
+ rescue => e
676
+ raise Informative, 'An error occurred while processing the post-install ' \
677
+ 'hook of the Podfile.' \
678
+ "\n\n#{e.message}\n\n#{e.backtrace * "\n"}"
679
+ end
680
+
681
+ #-------------------------------------------------------------------------#
682
+
683
+ public
684
+
685
+ # @return [Array<PodTarget>] The targets of the development pods generated by
686
+ # the installation process. This can be used as a convenience method for external scripts.
687
+ #
688
+ def development_pod_targets
689
+ pod_targets.select do |pod_target|
690
+ sandbox.local?(pod_target.pod_name)
691
+ end
692
+ end
693
+
694
+ #-------------------------------------------------------------------------#
695
+
696
+ private
697
+
698
+ # @!group Private helpers
699
+
700
+ # @return [Array<Specification>] All the root specifications of the
701
+ # installation.
702
+ #
703
+ def root_specs
704
+ analysis_result.specifications.map(&:root).uniq
705
+ end
706
+
707
+ # @return [SpecsState] The state of the sandbox returned by the analyzer.
708
+ #
709
+ def sandbox_state
710
+ analysis_result.sandbox_state
711
+ end
712
+
713
+ #-------------------------------------------------------------------------#
714
+ end
715
+ end