cocoapods-core 0.30.0 → 1.15.2

Sign up to get free protection for your applications and to get access to all the features.
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