cocoapods 0.39.0 → 1.0.0.beta.1

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +261 -12
  3. data/lib/cocoapods.rb +1 -0
  4. data/lib/cocoapods/command.rb +1 -0
  5. data/lib/cocoapods/command/env.rb +66 -0
  6. data/lib/cocoapods/command/init.rb +1 -1
  7. data/lib/cocoapods/command/lib.rb +1 -1
  8. data/lib/cocoapods/command/project.rb +0 -4
  9. data/lib/cocoapods/command/repo/lint.rb +7 -6
  10. data/lib/cocoapods/command/repo/push.rb +22 -1
  11. data/lib/cocoapods/command/setup.rb +0 -24
  12. data/lib/cocoapods/command/spec/create.rb +3 -1
  13. data/lib/cocoapods/command/spec/edit.rb +14 -21
  14. data/lib/cocoapods/command/spec/env_spec.rb +53 -0
  15. data/lib/cocoapods/command/spec/lint.rb +1 -1
  16. data/lib/cocoapods/config.rb +1 -34
  17. data/lib/cocoapods/downloader.rb +9 -4
  18. data/lib/cocoapods/external_sources.rb +0 -4
  19. data/lib/cocoapods/external_sources/abstract_external_source.rb +38 -11
  20. data/lib/cocoapods/external_sources/path_source.rb +2 -2
  21. data/lib/cocoapods/gem_version.rb +2 -2
  22. data/lib/cocoapods/generator/acknowledgements.rb +1 -1
  23. data/lib/cocoapods/generator/acknowledgements/plist.rb +1 -1
  24. data/lib/cocoapods/generator/copy_resources_script.rb +28 -21
  25. data/lib/cocoapods/generator/info_plist_file.rb +34 -8
  26. data/lib/cocoapods/generator/module_map.rb +3 -18
  27. data/lib/cocoapods/generator/xcconfig/aggregate_xcconfig.rb +22 -10
  28. data/lib/cocoapods/generator/xcconfig/pod_xcconfig.rb +2 -1
  29. data/lib/cocoapods/generator/xcconfig/xcconfig_helper.rb +2 -1
  30. data/lib/cocoapods/hooks_manager.rb +3 -11
  31. data/lib/cocoapods/installer.rb +45 -25
  32. data/lib/cocoapods/installer/analyzer.rb +53 -25
  33. data/lib/cocoapods/installer/analyzer/sandbox_analyzer.rb +2 -13
  34. data/lib/cocoapods/installer/analyzer/target_inspection_result.rb +4 -0
  35. data/lib/cocoapods/installer/analyzer/target_inspector.rb +22 -19
  36. data/lib/cocoapods/installer/file_references_installer.rb +53 -6
  37. data/lib/cocoapods/installer/installation_options.rb +156 -0
  38. data/lib/cocoapods/installer/migrator.rb +1 -56
  39. data/lib/cocoapods/installer/pod_source_installer.rb +10 -8
  40. data/lib/cocoapods/installer/podfile_validator.rb +42 -1
  41. data/lib/cocoapods/installer/post_install_hooks_context.rb +19 -2
  42. data/lib/cocoapods/installer/target_installer.rb +6 -2
  43. data/lib/cocoapods/installer/target_installer/aggregate_target_installer.rb +6 -5
  44. data/lib/cocoapods/installer/target_installer/pod_target_installer.rb +82 -14
  45. data/lib/cocoapods/installer/user_project_integrator.rb +37 -16
  46. data/lib/cocoapods/installer/user_project_integrator/target_integrator.rb +14 -136
  47. data/lib/cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb +15 -22
  48. data/lib/cocoapods/project.rb +109 -19
  49. data/lib/cocoapods/resolver.rb +17 -15
  50. data/lib/cocoapods/resolver/lazy_specification.rb +4 -0
  51. data/lib/cocoapods/sandbox.rb +0 -32
  52. data/lib/cocoapods/sandbox/headers_store.rb +2 -2
  53. data/lib/cocoapods/sandbox/podspec_finder.rb +1 -1
  54. data/lib/cocoapods/sources_manager.rb +181 -50
  55. data/lib/cocoapods/target/aggregate_target.rb +17 -11
  56. data/lib/cocoapods/target/pod_target.rb +31 -4
  57. data/lib/cocoapods/user_interface.rb +32 -3
  58. data/lib/cocoapods/user_interface/error_report.rb +46 -36
  59. data/lib/cocoapods/validator.rb +132 -43
  60. metadata +164 -79
@@ -5,6 +5,9 @@ module Pod
5
5
  #
6
6
  class Analyzer
7
7
  include Config::Mixin
8
+ include InstallationOptions::Mixin
9
+
10
+ delegate_installation_options { podfile }
8
11
 
9
12
  autoload :AnalysisResult, 'cocoapods/installer/analyzer/analysis_result'
10
13
  autoload :SandboxAnalyzer, 'cocoapods/installer/analyzer/sandbox_analyzer'
@@ -57,7 +60,7 @@ module Pod
57
60
  validate_podfile!
58
61
  validate_lockfile_version!
59
62
  @result = AnalysisResult.new
60
- if config.integrate_targets?
63
+ if installation_options.integrate_targets?
61
64
  @result.target_inspections = inspect_targets_to_integrate
62
65
  else
63
66
  verify_platforms_specified!
@@ -157,6 +160,7 @@ module Pod
157
160
  unless validator.valid?
158
161
  raise Informative, validator.message
159
162
  end
163
+ validator.warnings.uniq.each { |w| UI.warn(w) }
160
164
  end
161
165
 
162
166
  # @!group Analysis steps
@@ -225,9 +229,14 @@ module Pod
225
229
  #
226
230
  def generate_targets
227
231
  pod_targets = generate_pod_targets(result.specs_by_target)
228
- result.specs_by_target.map do |target_definition, _|
232
+ aggregate_targets = result.specs_by_target.keys.reject(&:abstract?).map do |target_definition|
229
233
  generate_target(target_definition, pod_targets)
230
234
  end
235
+ aggregate_targets.each do |target|
236
+ target.search_paths_aggregate_targets = aggregate_targets.select do |aggregate_target|
237
+ target.target_definition.targets_to_inherit_search_paths.include?(aggregate_target.target_definition)
238
+ end
239
+ end
231
240
  end
232
241
 
233
242
  # Setup the aggregate target for a single user target
@@ -244,15 +253,16 @@ module Pod
244
253
  target = AggregateTarget.new(target_definition, sandbox)
245
254
  target.host_requires_frameworks |= target_definition.uses_frameworks?
246
255
 
247
- if config.integrate_targets?
256
+ if installation_options.integrate_targets?
248
257
  target_inspection = result.target_inspections[target_definition]
249
- target.user_project_path = target_inspection.project_path
250
- target.client_root = target.user_project_path.dirname
258
+ raise "missing inspection: #{target_definition.name}" unless target_inspection
259
+ target.user_project = target_inspection.project
260
+ target.client_root = target.user_project_path.dirname.realpath
251
261
  target.user_target_uuids = target_inspection.project_target_uuids
252
262
  target.user_build_configurations = target_inspection.build_configurations
253
263
  target.archs = target_inspection.archs
254
264
  else
255
- target.client_root = config.installation_root
265
+ target.client_root = config.installation_root.realpath
256
266
  target.user_target_uuids = []
257
267
  target.user_build_configurations = target_definition.build_configurations || { 'Release' => :release, 'Debug' => :debug }
258
268
  if target_definition.platform && target_definition.platform.name == :osx
@@ -276,7 +286,9 @@ module Pod
276
286
  # @return [Array<PodTarget>]
277
287
  #
278
288
  def generate_pod_targets(specs_by_target)
279
- if config.deduplicate_targets?
289
+ if installation_options.deduplicate_targets?
290
+ dedupe_cache = {}
291
+
280
292
  all_specs = specs_by_target.flat_map do |target_definition, dependent_specs|
281
293
  dependent_specs.group_by(&:root).map do |root_spec, specs|
282
294
  [root_spec, specs, target_definition]
@@ -294,7 +306,7 @@ module Pod
294
306
  # There are different sets of subspecs or the spec is used across different platforms
295
307
  targets_by_distinctors.flat_map do |distinctor, target_definitions|
296
308
  specs, = *distinctor
297
- generate_pod_target(target_definitions, specs).scoped
309
+ generate_pod_target(target_definitions, specs).scoped(dedupe_cache)
298
310
  end
299
311
  else
300
312
  (specs, _), target_definitions = targets_by_distinctors.first
@@ -308,7 +320,7 @@ module Pod
308
320
  dependent_targets = transitive_dependencies_for_pod_target(target, pod_targets)
309
321
  target.dependent_targets = dependent_targets
310
322
  if dependent_targets.any?(&:scoped?)
311
- target.scoped
323
+ target.scoped(dedupe_cache)
312
324
  else
313
325
  target
314
326
  end
@@ -317,7 +329,7 @@ module Pod
317
329
  pod_targets = specs_by_target.flat_map do |target_definition, specs|
318
330
  grouped_specs = specs.group_by.group_by(&:root).values.uniq
319
331
  grouped_specs.flat_map do |pod_specs|
320
- generate_pod_target([target_definition], pod_specs).scoped
332
+ generate_pod_target([target_definition], pod_specs).scoped(dedupe_cache)
321
333
  end
322
334
  end
323
335
  pod_targets.each do |target|
@@ -369,7 +381,7 @@ module Pod
369
381
  def generate_pod_target(target_definitions, pod_specs)
370
382
  pod_target = PodTarget.new(pod_specs, target_definitions, sandbox)
371
383
 
372
- if config.integrate_targets?
384
+ if installation_options.integrate_targets?
373
385
  target_inspections = result.target_inspections.select { |t, _| target_definitions.include?(t) }.values
374
386
  pod_target.user_build_configurations = target_inspections.map(&:build_configurations).reduce({}, &:merge)
375
387
  pod_target.archs = target_inspections.flat_map(&:archs).compact.uniq.sort
@@ -454,6 +466,7 @@ module Pod
454
466
  else
455
467
  source = ExternalSources.from_dependency(dependency, podfile.defined_in_file)
456
468
  end
469
+ source.can_cache = installation_options.clean?
457
470
  source.fetch(sandbox)
458
471
  end
459
472
 
@@ -469,7 +482,6 @@ module Pod
469
482
  deps_to_fetch_if_needed = deps_with_external_source.select { |dep| result.podfile_state.unchanged.include?(dep.name) }
470
483
  deps_to_fetch += deps_to_fetch_if_needed.select do |dep|
471
484
  sandbox.specification(dep.name).nil? ||
472
- !dep.external_source[:local].nil? ||
473
485
  !dep.external_source[:path].nil? ||
474
486
  !sandbox.pod_dir(dep.root_name).directory? ||
475
487
  checkout_requires_update?(dep)
@@ -612,13 +624,21 @@ module Pod
612
624
  def sources
613
625
  @sources ||= begin
614
626
  sources = podfile.sources
615
- if sources.empty?
616
- url = 'https://github.com/CocoaPods/Specs.git'
617
- [SourcesManager.find_or_create_source_with_url(url)]
627
+
628
+ # Add any sources specified using the :source flag on individual dependencies.
629
+ dependency_sources = podfile.dependencies.map(&:podspec_repo).compact
630
+
631
+ all_dependencies_have_sources = dependency_sources.count == podfile.dependencies.count
632
+ if all_dependencies_have_sources
633
+ sources = dependency_sources
634
+ elsif sources.empty?
635
+ sources = ['https://github.com/CocoaPods/Specs.git']
618
636
  else
619
- sources.map do |source_url|
620
- SourcesManager.find_or_create_source_with_url(source_url)
621
- end
637
+ sources += dependency_sources
638
+ end
639
+
640
+ sources.uniq.map do |source_url|
641
+ SourcesManager.find_or_create_source_with_url(source_url)
622
642
  end
623
643
  end
624
644
  end
@@ -634,7 +654,7 @@ module Pod
634
654
  # @return [void]
635
655
  #
636
656
  def verify_platforms_specified!
637
- unless config.integrate_targets?
657
+ unless installation_options.integrate_targets?
638
658
  podfile.target_definition_list.each do |target_definition|
639
659
  if !target_definition.empty? && target_definition.platform.nil?
640
660
  raise Informative, 'It is necessary to specify the platform in the Podfile if not integrating.'
@@ -654,12 +674,20 @@ module Pod
654
674
  def inspect_targets_to_integrate
655
675
  inspection_result = {}
656
676
  UI.section 'Inspecting targets to integrate' do
657
- podfile.target_definition_list.each do |target_definition|
658
- inspector = TargetInspector.new(target_definition, config.installation_root)
659
- results = inspector.compute_results
660
- inspection_result[target_definition] = results
661
- UI.message('Using `ARCHS` setting to build architectures of ' \
662
- "target `#{target_definition.label}`: (`#{results.archs.join('`, `')}`)")
677
+ inspectors = podfile.target_definition_list.map do |target_definition|
678
+ next if target_definition.abstract?
679
+ TargetInspector.new(target_definition, config.installation_root)
680
+ end.compact
681
+ inspectors.group_by(&:compute_project_path).each do |project_path, target_inspectors|
682
+ project = Xcodeproj::Project.open(project_path)
683
+ target_inspectors.each do |inspector|
684
+ target_definition = inspector.target_definition
685
+ inspector.user_project = project
686
+ results = inspector.compute_results
687
+ inspection_result[target_definition] = results
688
+ UI.message('Using `ARCHS` setting to build architectures of ' \
689
+ "target `#{target_definition.label}`: (`#{results.archs.join('`, `')}`)")
690
+ end
663
691
  end
664
692
  end
665
693
  inspection_result
@@ -14,7 +14,7 @@ module Pod
14
14
  # - The version of the Pod changed.
15
15
  # - The SHA of the specification file changed.
16
16
  # - The specific installed (sub)specs of the same Pod changed.
17
- # - The specification is in head mode or from an external source and the
17
+ # - The specification is from an external source and the
18
18
  # installation process is in update mode.
19
19
  # - The directory of the Pod is empty.
20
20
  # - The Pod has been pre-downloaded.
@@ -135,7 +135,7 @@ module Pod
135
135
  # changed and thus should be reinstalled.
136
136
  #
137
137
  # @note In update mode, as there is no way to know if a remote source
138
- # hash changed the Pods in head mode and the ones from external
138
+ # hash changed the Pods from external
139
139
  # sources are always marked as changed.
140
140
  #
141
141
  # @note A Pod whose folder is empty is considered changed.
@@ -152,10 +152,6 @@ module Pod
152
152
  return true if resolved_spec_names(pod) != sandbox_spec_names(pod)
153
153
  return true if sandbox.predownloaded?(pod)
154
154
  return true if folder_empty?(pod)
155
- return true if sandbox.head_pod?(pod) != sandbox_head_version?(pod)
156
- if update_mode
157
- return true if sandbox.head_pod?(pod)
158
- end
159
155
  false
160
156
  end
161
157
 
@@ -238,13 +234,6 @@ module Pod
238
234
  sandbox_manifest.checksum(pod)
239
235
  end
240
236
 
241
- # @return [Bool] Wether the Pod is installed in the sandbox is in head
242
- # mode.
243
- #
244
- def sandbox_head_version?(pod)
245
- sandbox_version(pod).head? == true
246
- end
247
-
248
237
  #--------------------------------------#
249
238
 
250
239
  def folder_exist?(pod)
@@ -35,6 +35,10 @@ module Pod
35
35
  # due to the presence of Swift source in the user's targets
36
36
  #
37
37
  attr_accessor :recommends_frameworks
38
+
39
+ # @return [Xcodeproj::Project] the user's Xcode project
40
+ #
41
+ attr_accessor :project
38
42
  end
39
43
  end
40
44
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/array/conversions'
2
+
1
3
  module Pod
2
4
  class Installer
3
5
  class Analyzer
@@ -25,27 +27,26 @@ module Pod
25
27
 
26
28
  # Inspect the #target_definition
27
29
  #
30
+ # @raise If no `user_project` is set
31
+ #
28
32
  # @return [TargetInspectionResult]
29
33
  #
30
34
  def compute_results
31
- project_path = compute_project_path
32
- user_project = Xcodeproj::Project.open(project_path)
35
+ raise ArgumentError, 'Cannot compute results without a user project set' unless user_project
36
+
33
37
  targets = compute_targets(user_project)
34
38
 
35
39
  result = TargetInspectionResult.new
36
40
  result.target_definition = target_definition
37
- result.project_path = project_path
41
+ result.project_path = user_project.path
38
42
  result.project_target_uuids = targets.map(&:uuid)
39
43
  result.build_configurations = compute_build_configurations(targets)
40
44
  result.platform = compute_platform(targets)
41
45
  result.archs = compute_archs(targets)
46
+ result.project = user_project
42
47
  result
43
48
  end
44
49
 
45
- #-----------------------------------------------------------------------#
46
-
47
- private
48
-
49
50
  # Returns the path of the user project that the #target_definition
50
51
  # should integrate.
51
52
  #
@@ -77,6 +78,15 @@ module Pod
77
78
  path
78
79
  end
79
80
 
81
+ # @return [Xcodeproj::Project] the user's Xcode project, used for target
82
+ # inspection
83
+ #
84
+ attr_accessor :user_project
85
+
86
+ #-----------------------------------------------------------------------#
87
+
88
+ private
89
+
80
90
  # Returns a list of the targets from the project of #target_definition
81
91
  # that needs to be integrated.
82
92
  #
@@ -94,19 +104,12 @@ module Pod
94
104
  #
95
105
  def compute_targets(user_project)
96
106
  native_targets = user_project.native_targets
97
- if link_with = target_definition.link_with
98
- targets = native_targets.select { |t| link_with.include?(t.name) }
99
- raise Informative, "Unable to find the targets named #{link_with.map { |x| "`#{x}`" }.to_sentence}" \
100
- "to link with target definition `#{target_definition.name}`" if targets.empty?
101
- elsif target_definition.link_with_first_target?
102
- targets = [native_targets.first].compact
103
- raise Informative, 'Unable to find a target' if targets.empty?
104
- else
105
- target = native_targets.find { |t| t.name == target_definition.name.to_s }
106
- targets = [target].compact
107
- raise Informative, "Unable to find a target named `#{target_definition.name}`" if targets.empty?
107
+ target = native_targets.find { |t| t.name == target_definition.name.to_s }
108
+ unless target
109
+ found = native_targets.map { |t| "`#{t.name}`" }.to_sentence
110
+ raise Informative, "Unable to find a target named `#{target_definition.name}`, did find #{found}."
108
111
  end
109
- targets
112
+ [target]
110
113
  end
111
114
 
112
115
  # @param [Array<PBXNativeTarget] the user's targets of the project of
@@ -117,21 +117,19 @@ module Pod
117
117
  pod_target.file_accessors.each do |file_accessor|
118
118
  framework_exp = /\.framework\//
119
119
  headers_sandbox = Pathname.new(file_accessor.spec.root.name)
120
- pod_target.build_headers.add_search_path(headers_sandbox, pod_target.platform)
121
120
 
122
121
  # When integrating Pod as frameworks, built Pods are built into
123
122
  # frameworks, whose headers are included inside the built
124
123
  # framework. Those headers do not need to be linked from the
125
124
  # sandbox.
126
125
  unless pod_target.requires_frameworks? && pod_target.should_build?
126
+ pod_target.build_headers.add_search_path(headers_sandbox, pod_target.platform)
127
127
  sandbox.public_headers.add_search_path(headers_sandbox, pod_target.platform)
128
- end
129
128
 
130
- header_mappings(headers_sandbox, file_accessor, file_accessor.headers).each do |namespaced_path, files|
131
- pod_target.build_headers.add_files(namespaced_path, files.reject { |f| f.to_path =~ framework_exp })
132
- end
129
+ header_mappings(headers_sandbox, file_accessor, file_accessor.headers).each do |namespaced_path, files|
130
+ pod_target.build_headers.add_files(namespaced_path, files.reject { |f| f.to_path =~ framework_exp })
131
+ end
133
132
 
134
- unless pod_target.requires_frameworks? && pod_target.should_build?
135
133
  header_mappings(headers_sandbox, file_accessor, file_accessor.public_headers).each do |namespaced_path, files|
136
134
  sandbox.public_headers.add_files(namespaced_path, files.reject { |f| f.to_path =~ framework_exp })
137
135
  end
@@ -179,6 +177,7 @@ module Pod
179
177
  pod_name = file_accessor.spec.name
180
178
  local = sandbox.local?(pod_name)
181
179
  paths = file_accessor.send(file_accessor_key)
180
+ paths = allowable_project_paths(paths)
182
181
  paths.each do |path|
183
182
  group = pods_project.group_for_spec(file_accessor.spec.name, group_key)
184
183
  pods_project.add_file_reference(path, group, local && reflect_file_system_structure_for_development)
@@ -186,6 +185,54 @@ module Pod
186
185
  end
187
186
  end
188
187
 
188
+ # Filters a list of paths down to those paths which can be added to
189
+ # the Xcode project. Some paths are intermediates and only their children
190
+ # should be added, while some paths are treated as bundles and their
191
+ # children should not be added directly.
192
+ #
193
+ # @param [Array<Pathname>] paths
194
+ # The paths to files or directories on disk.
195
+ #
196
+ # @return [Array<Pathname>] The paths which can be added to the Xcode project
197
+ #
198
+ def allowable_project_paths(paths)
199
+ lproj_paths = Set.new
200
+ lproj_paths_with_files = Set.new
201
+ allowable_paths = paths.select do |path|
202
+ path_str = path.to_s.downcase
203
+
204
+ # We add the directory for a Core Data model, but not the items in it.
205
+ next if path_str =~ /.*\.xcdatamodeld\/.+/
206
+
207
+ # We add the directory for an asset catalog, but not the items in it.
208
+ next if path_str =~ /.*\.xcassets\/.+/
209
+
210
+ if path_str =~ /\.lproj(\/|$)/
211
+ # If the element is an .lproj directory then save it and potentially
212
+ # add it later if we don't find any contained items.
213
+ if path_str.end_with?('.lproj') && path.directory?
214
+ lproj_paths << path_str
215
+ next
216
+ end
217
+
218
+ # Collect the paths for the .lproj directories that contain files.
219
+ lproj_path = /(^.*\.lproj)\/.*/.match(path_str)[1]
220
+ lproj_paths_with_files << lproj_path
221
+
222
+ # Directories nested within an .lproj directory are added as file
223
+ # system references so their contained items are not added directly.
224
+ next if path.dirname.dirname.to_s.downcase == lproj_path
225
+ end
226
+
227
+ true
228
+ end
229
+
230
+ # Only add the path for the .lproj directories that do not have anything
231
+ # within them added as well. This generally happens if the glob within the
232
+ # resources directory was not a recursive glob.
233
+ allowable_paths + lproj_paths.subtract(lproj_paths_with_files).to_a
234
+ end
235
+
189
236
  # Computes the destination sub-directory in the sandbox
190
237
  #
191
238
  # @param [Pathname] headers_sandbox
@@ -0,0 +1,156 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+
3
+ module Pod
4
+ class Installer
5
+ # Represents the installation options the user can customize via a
6
+ # `Podfile`.
7
+ #
8
+ class InstallationOptions
9
+ # Parses installation options from a podfile.
10
+ #
11
+ # @param [Podfile] podfile the podfile to parse installation options
12
+ # from.
13
+ #
14
+ # @raise [Informative] if `podfile` does not specify a `CocoaPods`
15
+ # install.
16
+ #
17
+ # @return [Self]
18
+ #
19
+ def self.from_podfile(podfile)
20
+ name, options = podfile.installation_method
21
+ unless name.downcase == 'cocoapods'
22
+ raise Informative, "Currently need to specify a `cocoapods` install, you chose `#{name}`."
23
+ end
24
+ new(options)
25
+ end
26
+
27
+ # Defines a new installation option.
28
+ #
29
+ # @param [#to_s] name the name of the option.
30
+ #
31
+ # @param default the default value for the option.
32
+ #
33
+ # @param [Boolean] boolean whether the option has a boolean value.
34
+ #
35
+ # @return [void]
36
+ #
37
+ # @!macro [attach] option
38
+ #
39
+ # @note this option defaults to $2.
40
+ #
41
+ # @return the $1 $0 for installation.
42
+ #
43
+ def self.option(name, default, boolean: true)
44
+ name = name.to_s
45
+ raise ArgumentError, "The `#{name}` option is already defined" if defaults.key?(name)
46
+ defaults[name] = default
47
+ attr_accessor name
48
+ alias_method "#{name}?", name if boolean
49
+ end
50
+
51
+ # @return [Hash<Symbol,Object>] all known installation options and their
52
+ # default values.
53
+ #
54
+ def self.defaults
55
+ @defaults ||= {}
56
+ end
57
+
58
+ # @return [Array<Symbol>] the names of all known installation options.
59
+ #
60
+ def self.all_options
61
+ defaults.keys
62
+ end
63
+
64
+ # Initializes the installation options with a hash of options from a
65
+ # Podfile.
66
+ #
67
+ # @param [Hash] options the options to parse.
68
+ #
69
+ # @raise [Informative] if `options` contains any unknown keys.
70
+ #
71
+ def initialize(options = {})
72
+ options = ActiveSupport::HashWithIndifferentAccess.new(options)
73
+ unknown_keys = options.keys - self.class.all_options.map(&:to_s)
74
+ raise Informative, "Unknown installation options: #{unknown_keys.to_sentence}." unless unknown_keys.empty?
75
+ self.class.defaults.each do |key, default|
76
+ value = options.fetch(key, default)
77
+ send("#{key}=", value)
78
+ end
79
+ end
80
+
81
+ # @param [Boolean] include_defaults whether values that match the default
82
+ # for their option should be included. Defaults to `true`.
83
+ #
84
+ # @return [Hash] the options, keyed by option name.
85
+ #
86
+ def to_h(include_defaults: true)
87
+ self.class.defaults.reduce(ActiveSupport::HashWithIndifferentAccess.new) do |hash, (option, default)|
88
+ value = send(option)
89
+ hash[option] = value if include_defaults || value != default
90
+ hash
91
+ end
92
+ end
93
+
94
+ def ==(other)
95
+ other.is_a?(self.class) && to_h == other.to_h
96
+ end
97
+
98
+ alias_method :eql, :==
99
+
100
+ def hash
101
+ to_h.hash
102
+ end
103
+
104
+ option :clean, true
105
+ option :deduplicate_targets, true
106
+ option :deterministic_uuids, true
107
+ option :integrate_targets, true
108
+ option :lock_pod_sources, true
109
+
110
+ module Mixin
111
+ module ClassMethods
112
+ # Delegates the creation of {#installation_options} to the `Podfile`
113
+ # returned by the given block.
114
+ #
115
+ # @param blk a block that returns the `Podfile` to create
116
+ # installation options from.
117
+ #
118
+ # @return [Void]
119
+ #
120
+ def delegate_installation_options(&blk)
121
+ define_method(:installation_options) do
122
+ @installation_options ||= InstallationOptions.from_podfile(instance_eval(&blk))
123
+ end
124
+ end
125
+
126
+ # Delegates the installation options attributes directly to
127
+ # {#installation_options}.
128
+ #
129
+ # @return [Void]
130
+ #
131
+ def delegate_installation_option_attributes!
132
+ define_method(:respond_to_missing?) do |name, *args|
133
+ installation_options.respond_to?(name, *args) || super
134
+ end
135
+
136
+ define_method(:method_missing) do |name, *args, &blk|
137
+ if installation_options.respond_to?(name)
138
+ installation_options.send(name, *args, &blk)
139
+ else
140
+ super
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ # @return [InstallationOptions] The installation options.
147
+ #
148
+ attr_accessor :installation_options
149
+
150
+ def self.included(mod)
151
+ mod.extend(ClassMethods)
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end