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
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