cocoapods-core 0.30.0 → 1.15.2

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 (50) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +7 -10
  3. data/lib/cocoapods-core/build_type.rb +121 -0
  4. data/lib/cocoapods-core/cdn_source.rb +501 -0
  5. data/lib/cocoapods-core/core_ui.rb +4 -3
  6. data/lib/cocoapods-core/dependency.rb +100 -73
  7. data/lib/cocoapods-core/gem_version.rb +1 -2
  8. data/lib/cocoapods-core/github.rb +32 -5
  9. data/lib/cocoapods-core/http.rb +86 -0
  10. data/lib/cocoapods-core/lockfile.rb +161 -56
  11. data/lib/cocoapods-core/metrics.rb +47 -0
  12. data/lib/cocoapods-core/platform.rb +99 -11
  13. data/lib/cocoapods-core/podfile/dsl.rb +623 -124
  14. data/lib/cocoapods-core/podfile/target_definition.rb +662 -109
  15. data/lib/cocoapods-core/podfile.rb +138 -65
  16. data/lib/cocoapods-core/requirement.rb +37 -8
  17. data/lib/cocoapods-core/source/acceptor.rb +16 -13
  18. data/lib/cocoapods-core/source/aggregate.rb +79 -103
  19. data/lib/cocoapods-core/source/health_reporter.rb +9 -18
  20. data/lib/cocoapods-core/source/manager.rb +488 -0
  21. data/lib/cocoapods-core/source/metadata.rb +79 -0
  22. data/lib/cocoapods-core/source.rb +241 -70
  23. data/lib/cocoapods-core/specification/consumer.rb +187 -47
  24. data/lib/cocoapods-core/specification/dsl/attribute.rb +49 -85
  25. data/lib/cocoapods-core/specification/dsl/attribute_support.rb +6 -8
  26. data/lib/cocoapods-core/specification/dsl/deprecations.rb +9 -126
  27. data/lib/cocoapods-core/specification/dsl/platform_proxy.rb +30 -20
  28. data/lib/cocoapods-core/specification/dsl.rb +943 -296
  29. data/lib/cocoapods-core/specification/json.rb +64 -23
  30. data/lib/cocoapods-core/specification/linter/analyzer.rb +218 -0
  31. data/lib/cocoapods-core/specification/linter/result.rb +128 -0
  32. data/lib/cocoapods-core/specification/linter.rb +310 -309
  33. data/lib/cocoapods-core/specification/root_attribute_accessors.rb +90 -39
  34. data/lib/cocoapods-core/specification/set/presenter.rb +35 -71
  35. data/lib/cocoapods-core/specification/set.rb +42 -96
  36. data/lib/cocoapods-core/specification.rb +368 -130
  37. data/lib/cocoapods-core/standard_error.rb +45 -24
  38. data/lib/cocoapods-core/trunk_source.rb +14 -0
  39. data/lib/cocoapods-core/vendor/requirement.rb +133 -53
  40. data/lib/cocoapods-core/vendor/version.rb +197 -156
  41. data/lib/cocoapods-core/vendor.rb +1 -5
  42. data/lib/cocoapods-core/version.rb +137 -42
  43. data/lib/cocoapods-core/yaml_helper.rb +334 -0
  44. data/lib/cocoapods-core.rb +10 -4
  45. metadata +100 -27
  46. data/lib/cocoapods-core/source/abstract_data_provider.rb +0 -71
  47. data/lib/cocoapods-core/source/file_system_data_provider.rb +0 -150
  48. data/lib/cocoapods-core/source/github_data_provider.rb +0 -143
  49. data/lib/cocoapods-core/specification/set/statistics.rb +0 -266
  50. data/lib/cocoapods-core/yaml_converter.rb +0 -192
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/string/strip.rb'
2
+
1
3
  require 'cocoapods-core/specification/consumer'
2
4
  require 'cocoapods-core/specification/dsl'
3
5
  require 'cocoapods-core/specification/linter'
@@ -6,7 +8,6 @@ require 'cocoapods-core/specification/set'
6
8
  require 'cocoapods-core/specification/json'
7
9
 
8
10
  module Pod
9
-
10
11
  # The Specification provides a DSL to describe a Pod. A pod is defined as a
11
12
  # library originating from a source. A specification can support detailed
12
13
  # attributes for modules of code through subspecs.
@@ -14,7 +15,6 @@ module Pod
14
15
  # Usually it is stored in files with `podspec` extension.
15
16
  #
16
17
  class Specification
17
-
18
18
  include Pod::Specification::DSL
19
19
  include Pod::Specification::DSL::Deprecations
20
20
  include Pod::Specification::RootAttributesAccessors
@@ -25,21 +25,46 @@ module Pod
25
25
  #
26
26
  attr_reader :parent
27
27
 
28
+ # @return [Integer] the cached hash value for this spec.
29
+ #
30
+ attr_reader :hash_value
31
+
28
32
  # @param [Specification] parent @see parent
29
33
  #
30
34
  # @param [String] name
31
35
  # the name of the specification.
32
36
  #
33
- def initialize(parent = nil, name = nil)
37
+ # @param [Boolean] test_specification
38
+ # Whether the specification is a test specification
39
+ #
40
+ # @param [Boolean] app_specification
41
+ # Whether the specification is an app specification
42
+ #
43
+ def initialize(parent = nil, name = nil, test_specification = false, app_specification: false)
44
+ raise StandardError, "#{self} can not be both an app and test specification." if test_specification && app_specification
34
45
  @attributes_hash = {}
35
46
  @subspecs = []
36
47
  @consumers = {}
37
48
  @parent = parent
49
+ @hash_value = nil
50
+ @test_specification = test_specification
51
+ @app_specification = app_specification
38
52
  attributes_hash['name'] = name
53
+ attributes_hash['test_type'] = :unit if test_specification
39
54
 
40
55
  yield self if block_given?
41
56
  end
42
57
 
58
+ def initialize_copy(other)
59
+ super
60
+
61
+ @subspecs = @subspecs.map do |subspec|
62
+ subspec = subspec.dup
63
+ subspec.instance_variable_set :@parent, self
64
+ subspec
65
+ end
66
+ end
67
+
43
68
  # @return [Hash] the hash that stores the information of the attributes of
44
69
  # the specification.
45
70
  #
@@ -49,6 +74,16 @@ module Pod
49
74
  #
50
75
  attr_accessor :subspecs
51
76
 
77
+ # @return [Boolean] If this specification is a test specification.
78
+ #
79
+ attr_accessor :test_specification
80
+ alias_method :test_specification?, :test_specification
81
+
82
+ # @return [Boolean] If this specification is an app specification.
83
+ #
84
+ attr_accessor :app_specification
85
+ alias_method :app_specification?, :app_specification
86
+
52
87
  # Checks if a specification is equal to the given one according its name
53
88
  # and to its version.
54
89
  #
@@ -59,23 +94,15 @@ module Pod
59
94
  # go. This is used by the installer to group specifications by root
60
95
  # spec.
61
96
  #
62
- # @return [Bool] Whether the specifications are equal.
97
+ # @return [Boolean] Whether the specifications are equal.
63
98
  #
64
99
  def ==(other)
65
- # TODO
66
- # self.class === other &&
67
- # attributes_hash == other.attributes_hash &&
68
- # subspecs == other.subspecs &&
69
- # pre_install_callback == other.pre_install_callback &&
70
- # post_install_callback == other.post_install_callback
71
- to_s == other.to_s
100
+ other.is_a?(self.class) &&
101
+ name == other.name &&
102
+ version == other.version
72
103
  end
73
104
 
74
- # @see ==
75
- #
76
- def eql?(other)
77
- self == other
78
- end
105
+ alias_method :eql?, :==
79
106
 
80
107
  # Return the hash value for this specification according to its attributes
81
108
  # hash.
@@ -88,19 +115,23 @@ module Pod
88
115
  # @return [Fixnum] The hash value.
89
116
  #
90
117
  def hash
91
- to_s.hash
118
+ if @hash_value.nil?
119
+ @hash_value = (name.hash * 53) ^ version.hash
120
+ end
121
+ @hash_value
92
122
  end
93
123
 
94
124
  # @return [String] A string suitable for representing the specification in
95
125
  # clients.
96
126
  #
97
127
  def to_s
98
- if name && !version.version.empty?
99
- "#{name} (#{version})"
128
+ specified_version = raw_version || ''
129
+ if name && !specified_version.empty?
130
+ "#{name} (#{specified_version})"
100
131
  elsif name
101
132
  name
102
133
  else
103
- "No-name"
134
+ 'No-name'
104
135
  end
105
136
  end
106
137
 
@@ -117,19 +148,18 @@ module Pod
117
148
  # @example Input examples
118
149
  #
119
150
  # "libPusher (1.0)"
120
- # "libPusher (HEAD based on 1.0)"
121
151
  # "RestKit/JSON (1.0)"
122
152
  #
123
153
  # @return [Array<String, Version>] the name and the version of a
124
154
  # pod.
125
155
  #
126
156
  def self.name_and_version_from_string(string_representation)
127
- match_data = string_representation.match(/(\S*) \((.*)\)/)
157
+ match_data = string_representation.match(/\A((?:\s?[^\s(])+)(?: \((.+)\))?\Z/)
128
158
  unless match_data
129
- raise Informative, "Invalid string representation for a " \
130
- "Specification: `#{string_representation}`." \
131
- "String representation should include the name and " \
132
- "the version of a pod."
159
+ raise Informative, 'Invalid string representation for a ' \
160
+ "specification: `#{string_representation}`. " \
161
+ 'The string representation should include the name and ' \
162
+ 'optionally the version of the Pod.'
133
163
  end
134
164
  name = match_data[1]
135
165
  vers = Version.new(match_data[2])
@@ -143,7 +173,46 @@ module Pod
143
173
  # @return [String] the root name
144
174
  #
145
175
  def self.root_name(full_name)
146
- full_name.split('/').first
176
+ if index = full_name.index('/')
177
+ full_name.slice(0, index)
178
+ else
179
+ full_name
180
+ end
181
+ end
182
+
183
+ # Returns the module name of a specification
184
+ #
185
+ # @return [String] the module name
186
+ #
187
+ def module_name
188
+ attributes_hash['module_name'] ||
189
+ c99ext_identifier(attributes_hash['header_dir']) ||
190
+ c99ext_identifier(attributes_hash['name'])
191
+ end
192
+
193
+ private
194
+
195
+ # Transforms the given string into a valid +identifier+ after C99ext
196
+ # standard, so that it can be used in source code where escaping of
197
+ # ambiguous characters is not applicable.
198
+ #
199
+ # @param [String] name
200
+ # any name, which may contain leading numbers, spaces or invalid
201
+ # characters.
202
+ #
203
+ # @return [String]
204
+ #
205
+ def c99ext_identifier(name)
206
+ return nil if name.nil?
207
+ I18n.transliterate(name).gsub(/^([0-9])/, '_\1').
208
+ gsub(/[^a-zA-Z0-9_]/, '_').gsub(/_+/, '_')
209
+ end
210
+
211
+ # @return [Object, Nil]
212
+ # the raw value specified for the version attribute, or nil
213
+ #
214
+ def raw_version
215
+ root.attributes_hash['version']
147
216
  end
148
217
 
149
218
  #-------------------------------------------------------------------------#
@@ -158,13 +227,13 @@ module Pod
158
227
  parent ? parent.root : self
159
228
  end
160
229
 
161
- # @return [Bool] whether the specification is root.
230
+ # @return [Boolean] whether the specification is root.
162
231
  #
163
232
  def root?
164
233
  parent.nil?
165
234
  end
166
235
 
167
- # @return [Bool] whether the specification is a subspec.
236
+ # @return [Boolean] whether the specification is a subspec.
168
237
  #
169
238
  def subspec?
170
239
  !parent.nil?
@@ -174,9 +243,63 @@ module Pod
174
243
 
175
244
  public
176
245
 
246
+ # @return [Symbol] Spec type of the current spec.
247
+ #
248
+ # @note see Attribute#SUPPORTED_SPEC_TYPES for the list of available spec_types.
249
+ #
250
+ def spec_type
251
+ return :app if app_specification?
252
+ return :test if test_specification?
253
+
254
+ :library
255
+ end
256
+
177
257
  # @!group Dependencies & Subspecs
178
258
 
179
- # @return [Array<Specifications>] the recursive list of all the subspecs of
259
+ # @return [Boolean] If this specification is a library specification.
260
+ #
261
+ # @note a library specification is a specification that is not of type app or test.
262
+ #
263
+ def library_specification?
264
+ !app_specification? && !test_specification?
265
+ end
266
+
267
+ # @return [Boolean] If this specification is not a library specification.
268
+ #
269
+ # @note see #library_specification?
270
+ #
271
+ def non_library_specification?
272
+ !library_specification?
273
+ end
274
+
275
+ # @return [Symbol] the test type supported if this is a test specification.
276
+ #
277
+ def test_type
278
+ attributes_hash['test_type'].to_sym
279
+ end
280
+
281
+ # @return [Array<Specification>] the list of all the test subspecs of
282
+ # a specification.
283
+ #
284
+ def test_specs
285
+ subspecs.select(&:test_specification?)
286
+ end
287
+
288
+ # @return [Array<Specification>] the list of all the app subspecs of
289
+ # a specification.
290
+ #
291
+ def app_specs
292
+ subspecs.select(&:app_specification?)
293
+ end
294
+
295
+ # @return [Array<Specification>] the list of all the non libary (app or test) subspecs of
296
+ # a specification.
297
+ #
298
+ def non_library_specs
299
+ subspecs.select(&:non_library_specification?)
300
+ end
301
+
302
+ # @return [Array<Specification>] the recursive list of all the subspecs of
180
303
  # a specification.
181
304
  #
182
305
  def recursive_subspecs
@@ -195,58 +318,89 @@ module Pod
195
318
  # the relative name of the subspecs starting from the receiver
196
319
  # including the name of the receiver.
197
320
  #
321
+ # @param [Boolean] raise_if_missing
322
+ # whether an exception should be raised if no specification named
323
+ # `relative_name` is found.
324
+ #
198
325
  # @example Retrieving a subspec
199
326
  #
200
327
  # s.subspec_by_name('Pod/subspec').name #=> 'subspec'
201
328
  #
202
329
  # @return [Specification] the subspec with the given name or self.
203
330
  #
204
- def subspec_by_name(relative_name)
331
+ def subspec_by_name(relative_name, raise_if_missing = true, include_non_library_specifications = false)
205
332
  if relative_name.nil? || relative_name == base_name
206
333
  self
334
+ elsif base_name.nil?
335
+ if raise_if_missing
336
+ raise Informative, "Trying to access a `#{relative_name}` " \
337
+ "specification from `#{defined_in_file}`, which has no contents."
338
+ else
339
+ return nil
340
+ end
341
+ elsif relative_name.downcase == base_name.downcase
342
+ raise Informative, "Trying to access a `#{relative_name}` " \
343
+ "specification from `#{base_name}`, which has a different case."
207
344
  else
208
345
  remainder = relative_name[base_name.size + 1..-1]
209
346
  subspec_name = remainder.split('/').shift
210
- subspec = subspecs.find { |s| s.name == "#{name}/#{subspec_name}" }
347
+ subspec = subspecs.find { |s| s.base_name == subspec_name && (include_non_library_specifications || !s.non_library_specification?) }
211
348
  unless subspec
212
- raise Informative, "Unable to find a specification named " \
213
- "`#{relative_name}` in `#{name} (#{version})`."
349
+ if raise_if_missing
350
+ raise Informative, 'Unable to find a specification named ' \
351
+ "`#{relative_name}` in `#{name} (#{version})`."
352
+ else
353
+ return nil
354
+ end
214
355
  end
215
- subspec.subspec_by_name(remainder)
356
+ subspec.subspec_by_name(remainder, raise_if_missing, include_non_library_specifications)
216
357
  end
217
358
  end
218
359
 
219
- # @return [String] the name of the default subspec if provided.
360
+ # @return [Array<String>, Symbol] the name(s) of the default subspecs if provided or :none for no default subspecs.
220
361
  #
221
- def default_subspec
222
- attributes_hash["default_subspec"]
362
+ def default_subspecs
363
+ # TODO: remove singular form and update the JSON specs.
364
+ value = Array(attributes_hash['default_subspecs'] || attributes_hash['default_subspec'])
365
+ first = value.first
366
+ if first == :none || first == 'none'
367
+ first.to_sym
368
+ else
369
+ value
370
+ end
223
371
  end
224
372
 
225
373
  # Returns the dependencies on subspecs.
226
374
  #
227
375
  # @note A specification has a dependency on either the
228
- # {#default_subspec} or each of its children subspecs that are
376
+ # {#default_subspecs} or each of its children subspecs that are
229
377
  # compatible with its platform.
230
378
  #
379
+ # @param [Platform] platform
380
+ # return only dependencies supported on the given platform.
381
+ #
231
382
  # @return [Array<Dependency>] the dependencies on subspecs.
232
383
  #
233
384
  def subspec_dependencies(platform = nil)
234
- if default_subspec
235
- specs = [subspec_by_name("#{name}/#{default_subspec}")]
236
- else
237
- specs = subspecs.compact
238
- end
385
+ specs = if default_subspecs.empty?
386
+ subspecs.compact.reject(&:non_library_specification?)
387
+ elsif default_subspecs == :none
388
+ []
389
+ else
390
+ default_subspecs.map do |subspec_name|
391
+ root.subspec_by_name("#{name}/#{subspec_name}")
392
+ end
393
+ end
239
394
  if platform
240
395
  specs = specs.select { |s| s.supported_on_platform?(platform) }
241
396
  end
242
- specs.map { |s| Dependency.new(s.name) }
397
+ specs.map { |s| Dependency.new(s.name, version) }
243
398
  end
244
399
 
245
400
  # Returns the dependencies on other Pods or subspecs of other Pods.
246
401
  #
247
- # @param [Bool] all_platforms
248
- # whether the dependencies should be returned for all platforms
249
- # instead of the active one.
402
+ # @param [Platform] platform
403
+ # return only dependencies supported on the given platform.
250
404
  #
251
405
  # @note External dependencies are inherited by subspecs
252
406
  #
@@ -268,10 +422,29 @@ module Pod
268
422
  dependencies(platform) + subspec_dependencies(platform)
269
423
  end
270
424
 
425
+ # Returns whether a dependency is whitelisted for the given configuration.
426
+ #
427
+ # @param [Pod::Dependency] dependency
428
+ # the dependency verify.
429
+ #
430
+ # @param [Symbol, String] configuration
431
+ # the configuration to check against.
432
+ #
433
+ # @return [Boolean] whether the dependency is whitelisted or not.
434
+ #
435
+ def dependency_whitelisted_for_configuration?(dependency, configuration)
436
+ inherited = -> { root? ? true : parent.dependency_whitelisted_for_configuration?(dependency, configuration) }
437
+
438
+ return inherited[] unless configuration_whitelist = attributes_hash['configuration_pod_whitelist']
439
+ return inherited[] unless whitelist_for_pod = configuration_whitelist[dependency.name]
440
+
441
+ whitelist_for_pod.include?(configuration.to_s.downcase)
442
+ end
443
+
271
444
  # Returns a consumer to access the multi-platform attributes.
272
445
  #
273
446
  # @param [String, Symbol, Platform] platform
274
- # he platform of the consumer
447
+ # the platform of the consumer
275
448
  #
276
449
  # @return [Specification::Consumer] the consumer for the given platform
277
450
  #
@@ -280,20 +453,61 @@ module Pod
280
453
  @consumers[platform] ||= Consumer.new(self, platform)
281
454
  end
282
455
 
456
+ # @return [Bool, String] The prefix_header_file value.
457
+ #
458
+ def prefix_header_file
459
+ attributes_hash['prefix_header_file']
460
+ end
461
+
462
+ # @return [Array<Hash{Symbol=>String}>] The script_phases value.
463
+ #
464
+ def script_phases
465
+ script_phases = attributes_hash['script_phases'] || []
466
+ script_phases.map do |script_phase|
467
+ phase = Specification.convert_keys_to_symbol(script_phase)
468
+ phase[:execution_position] = if phase.key?(:execution_position)
469
+ phase[:execution_position].to_sym
470
+ else
471
+ :any
472
+ end
473
+ phase
474
+ end
475
+ end
476
+
477
+ # @return [Hash] The on demand resources value.
478
+ #
479
+ def on_demand_resources
480
+ attributes_hash['on_demand_resources'] || {}
481
+ end
482
+
483
+ # @return [Hash] The scheme value.
484
+ #
485
+ def scheme
486
+ value = attributes_hash['scheme'] || {}
487
+ Specification.convert_keys_to_symbol(value, :recursive => false)
488
+ end
489
+
490
+ # @return [Hash] The Info.plist value.
491
+ #
492
+ def info_plist
493
+ attributes_hash['info_plist'] || {}
494
+ end
495
+
283
496
  #-------------------------------------------------------------------------#
284
497
 
285
498
  public
286
499
 
287
500
  # @!group DSL helpers
288
501
 
289
- # @return [Bool] whether the specification should use a directory as it
502
+ # @return [Boolean] whether the specification should use a directory as its
290
503
  # source.
291
504
  #
292
505
  def local?
293
- !(source[:path] || source[:local]).nil? rescue false
506
+ return true if source[:path]
507
+ false
294
508
  end
295
509
 
296
- # @return [Bool] whether the specification is supported in the given
510
+ # @return [Boolean] whether the specification is supported in the given
297
511
  # platform.
298
512
  #
299
513
  # @overload supported_on_platform?(platform)
@@ -303,11 +517,6 @@ module Pod
303
517
  #
304
518
  # @overload supported_on_platform?(symbolic_name, deployment_target)
305
519
  #
306
- # @param [Symbol] symbolic_name
307
- # the name of the platform which is checked for support.
308
- #
309
- # @param [String] deployment_target
310
- # the deployment target which is checked for support.
311
520
  #
312
521
  def supported_on_platform?(*platform)
313
522
  platform = Platform.new(*platform)
@@ -359,7 +568,7 @@ module Pod
359
568
  # information.
360
569
  #
361
570
  def platform_hash
362
- case value = attributes_hash["platforms"]
571
+ case value = attributes_hash['platforms']
363
572
  when String
364
573
  { value => nil }
365
574
  when Array
@@ -377,55 +586,8 @@ module Pod
377
586
 
378
587
  public
379
588
 
380
- # @!group Deprecated Hooks support
381
589
  #-------------------------------------------------------------------------#
382
590
 
383
- # @return [Proc] the pre install callback if defined.
384
- #
385
- attr_reader :pre_install_callback
386
-
387
- # @return [Proc] the post install callback if defined.
388
- #
389
- attr_reader :post_install_callback
390
-
391
- # Calls the pre install callback if defined.
392
- #
393
- # @param [Pod::LocalPod] pod
394
- # the local pod instance that manages the files described by this
395
- # specification.
396
- #
397
- # @param [Podfile::TargetDefinition] target_definition
398
- # the target definition that required this specification as a
399
- # dependency.
400
- #
401
- # @return [Bool] whether a pre install callback was specified and it was
402
- # called.
403
- #
404
- def pre_install!(pod, target_definition)
405
- return false unless @pre_install_callback
406
- @pre_install_callback.call(pod, target_definition)
407
- true
408
- end
409
-
410
- # Calls the post install callback if defined.
411
- #
412
- # @param [Pod::TargetInstaller] target_installer
413
- # the target installer that is performing the installation of the
414
- # pod.
415
- #
416
- # @return [Bool] whether a post install callback was specified and it was
417
- # called.
418
- #
419
- def post_install!(target_installer)
420
- return false unless @post_install_callback
421
- @post_install_callback.call(target_installer)
422
- true
423
- end
424
-
425
- #-------------------------------------------------------------------------#
426
-
427
- public
428
-
429
591
  # @!group DSL attribute writers
430
592
 
431
593
  # Sets the value for the attribute with the given name.
@@ -436,7 +598,7 @@ module Pod
436
598
  # @param [Object] value
437
599
  # the value to store.
438
600
  #
439
- # @param [Symbol] platform.
601
+ # @param [Symbol] platform_name
440
602
  # If provided the attribute is stored only for the given platform.
441
603
  #
442
604
  # @note If the provides value is Hash the keys are converted to a string.
@@ -445,7 +607,8 @@ module Pod
445
607
  #
446
608
  def store_attribute(name, value, platform_name = nil)
447
609
  name = name.to_s
448
- value = convert_keys_to_string(value) if value.is_a?(Hash)
610
+ value = Specification.convert_keys_to_string(value) if value.is_a?(Hash)
611
+ value = value.strip_heredoc.strip if value.respond_to?(:strip_heredoc)
449
612
  if platform_name
450
613
  platform_name = platform_name.to_s
451
614
  attributes_hash[platform_name] ||= {}
@@ -468,25 +631,46 @@ module Pod
468
631
  end
469
632
  end
470
633
 
471
- private
472
-
473
634
  # Converts the keys of the given hash to a string.
474
635
  #
475
636
  # @param [Object] value
476
637
  # the value that needs to be stripped from the Symbols.
477
638
  #
478
- # @return [Hash] the hash with the strings instead of the keys.
639
+ # @param [Boolean] recursive
640
+ # whether to convert keys of nested hashes.
479
641
  #
480
- def convert_keys_to_string(value)
642
+ # @return [Hash] the hash with the keys as strings instead of symbols.
643
+ #
644
+ def self.convert_keys_to_string(value, recursive: true)
481
645
  return unless value
482
646
  result = {}
483
647
  value.each do |key, subvalue|
484
- subvalue = convert_keys_to_string(subvalue) if subvalue.is_a?(Hash)
648
+ subvalue = Specification.convert_keys_to_string(subvalue) if recursive && subvalue.is_a?(Hash)
485
649
  result[key.to_s] = subvalue
486
650
  end
487
651
  result
488
652
  end
489
653
 
654
+ # Converts the keys of the given hash to a symbol.
655
+ #
656
+ # @param [Object] value
657
+ # the value that needs to be stripped from the Strings.
658
+ #
659
+ # @param [Boolean] recursive
660
+ # whether to convert keys of nested hashes.
661
+ #
662
+ # @return [Hash] the hash with the keys as symbols instead of strings.
663
+ #
664
+ def self.convert_keys_to_symbol(value, recursive: true)
665
+ return unless value
666
+ result = {}
667
+ value.each do |key, subvalue|
668
+ subvalue = Specification.convert_keys_to_symbol(subvalue) if recursive && subvalue.is_a?(Hash)
669
+ result[key.to_sym] = subvalue
670
+ end
671
+ result
672
+ end
673
+
490
674
  #-------------------------------------------------------------------------#
491
675
 
492
676
  public
@@ -499,11 +683,17 @@ module Pod
499
683
  # @return [Nil] If the specification is not defined in a file.
500
684
  #
501
685
  def checksum
502
- require 'digest'
503
- unless defined_in_file.nil?
504
- checksum = Digest::SHA1.hexdigest(File.read(defined_in_file))
505
- checksum = checksum.encode('UTF-8') if checksum.respond_to?(:encode)
506
- checksum
686
+ @checksum ||= begin
687
+ if root?
688
+ unless defined_in_file.nil?
689
+ require 'digest'
690
+ checksum = Digest::SHA1.hexdigest(File.read(defined_in_file))
691
+ checksum = checksum.encode('UTF-8') if checksum.respond_to?(:encode)
692
+ checksum
693
+ end
694
+ else
695
+ root.checksum
696
+ end
507
697
  end
508
698
  end
509
699
 
@@ -534,9 +724,9 @@ module Pod
534
724
  raise Informative, "No podspec exists at path `#{path}`."
535
725
  end
536
726
 
537
- string = File.open(path, 'r:utf-8') { |f| f.read }
727
+ string = File.open(path, 'r:utf-8', &:read)
538
728
  # Work around for Rubinius incomplete encoding in 1.9 mode
539
- if string.respond_to?(:encoding) && string.encoding.name != "UTF-8"
729
+ if string.respond_to?(:encoding) && string.encoding.name != 'UTF-8'
540
730
  string.encode!('UTF-8')
541
731
  end
542
732
 
@@ -544,6 +734,7 @@ module Pod
544
734
  end
545
735
 
546
736
  # Loads a specification with the given string.
737
+ # The specification is evaluated in the context of `path`.
547
738
  #
548
739
  # @param [String] spec_contents
549
740
  # A string describing a specification.
@@ -554,21 +745,24 @@ module Pod
554
745
  # @return [Specification] the specification
555
746
  #
556
747
  def self.from_string(spec_contents, path, subspec_name = nil)
557
- path = Pathname.new(path)
748
+ path = Pathname.new(path).expand_path
749
+ spec = nil
558
750
  case path.extname
559
751
  when '.podspec'
560
- spec = ::Pod._eval_podspec(spec_contents, path)
561
- unless spec.is_a?(Specification)
562
- raise Informative, "Invalid podspec file at path `#{path}`."
752
+ Dir.chdir(path.parent.directory? ? path.parent : Dir.pwd) do
753
+ spec = ::Pod._eval_podspec(spec_contents, path)
754
+ unless spec.is_a?(Specification)
755
+ raise Informative, "Invalid podspec file at path `#{path}`."
756
+ end
563
757
  end
564
758
  when '.json'
565
- spec = Specification.from_json(spec_contents)
759
+ spec = Specification.from_json(spec_contents, path)
566
760
  else
567
- raise Informative, "Unsupported specification format `#{path.extname}`."
761
+ raise Informative, "Unsupported specification format `#{path.extname}` for spec at `#{path}`."
568
762
  end
569
763
 
570
764
  spec.defined_in_file = path
571
- spec.subspec_by_name(subspec_name)
765
+ spec.subspec_by_name(subspec_name, true)
572
766
  end
573
767
 
574
768
  # Sets the path of the `podspec` file used to load the specification.
@@ -582,10 +776,51 @@ module Pod
582
776
  #
583
777
  def defined_in_file=(file)
584
778
  unless root?
585
- raise StandardError, "Defined in file can be set only for root specs."
779
+ raise StandardError, 'Defined in file can be set only for root specs.'
586
780
  end
587
781
  @defined_in_file = file
588
782
  end
783
+
784
+ # Sets the name of the `podspec`.
785
+ #
786
+ # @param [String] name
787
+ # the `podspec` name.
788
+ #
789
+ # @return [void]
790
+ #
791
+ # @visibility private
792
+ #
793
+ def name=(name)
794
+ @hash_value = nil
795
+ attributes_hash['name'] = name
796
+ end
797
+
798
+ # Sets the version of the `podspec`.
799
+ #
800
+ # @param [String] version
801
+ # the `podspec` version.
802
+ #
803
+ # @return [void]
804
+ #
805
+ # @visibility private
806
+ #
807
+ def version=(version)
808
+ @hash_value = nil
809
+ store_attribute(:version, version)
810
+ @version = nil
811
+ end
812
+
813
+ # @!group Validation
814
+
815
+ # Validates the cocoapods_version in the specification against the current version of Core.
816
+ # It will raise an Informative error if the version is not satisfied.
817
+ #
818
+ def validate_cocoapods_version
819
+ unless cocoapods_version.satisfied_by?(Version.create(CORE_VERSION))
820
+ raise Informative, "`#{name}` requires CocoaPods version `#{cocoapods_version}`, " \
821
+ "which is not satisfied by your current version, `#{CORE_VERSION}`."
822
+ end
823
+ end
589
824
  end
590
825
 
591
826
  #---------------------------------------------------------------------------#
@@ -606,11 +841,14 @@ module Pod
606
841
  #
607
842
  #
608
843
  def self._eval_podspec(string, path)
609
- # rubocop:disable Eval
844
+ # rubocop:disable Security/Eval
610
845
  eval(string, nil, path.to_s)
611
- # rubocop:enable Eval
846
+ # rubocop:enable Security/Eval
847
+
848
+ # rubocop:disable Lint/RescueException
612
849
  rescue Exception => e
850
+ # rubocop:enable Lint/RescueException
613
851
  message = "Invalid `#{path.basename}` file: #{e.message}"
614
- raise DSLError.new(message, path, e.backtrace)
852
+ raise DSLError.new(message, path, e, string)
615
853
  end
616
854
  end