cocoapods-core 0.17.0.rc1

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 (36) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +36 -0
  4. data/lib/cocoapods-core/core_ui.rb +19 -0
  5. data/lib/cocoapods-core/dependency.rb +295 -0
  6. data/lib/cocoapods-core/gem_version.rb +6 -0
  7. data/lib/cocoapods-core/lockfile.rb +440 -0
  8. data/lib/cocoapods-core/platform.rb +171 -0
  9. data/lib/cocoapods-core/podfile/dsl.rb +459 -0
  10. data/lib/cocoapods-core/podfile/target_definition.rb +503 -0
  11. data/lib/cocoapods-core/podfile.rb +345 -0
  12. data/lib/cocoapods-core/requirement.rb +15 -0
  13. data/lib/cocoapods-core/source/validator.rb +183 -0
  14. data/lib/cocoapods-core/source.rb +284 -0
  15. data/lib/cocoapods-core/specification/consumer.rb +356 -0
  16. data/lib/cocoapods-core/specification/dsl/attribute.rb +245 -0
  17. data/lib/cocoapods-core/specification/dsl/attribute_support.rb +76 -0
  18. data/lib/cocoapods-core/specification/dsl/deprecations.rb +47 -0
  19. data/lib/cocoapods-core/specification/dsl/platform_proxy.rb +67 -0
  20. data/lib/cocoapods-core/specification/dsl.rb +1110 -0
  21. data/lib/cocoapods-core/specification/linter.rb +436 -0
  22. data/lib/cocoapods-core/specification/root_attribute_accessors.rb +152 -0
  23. data/lib/cocoapods-core/specification/set/presenter.rb +229 -0
  24. data/lib/cocoapods-core/specification/set/statistics.rb +277 -0
  25. data/lib/cocoapods-core/specification/set.rb +171 -0
  26. data/lib/cocoapods-core/specification/yaml.rb +60 -0
  27. data/lib/cocoapods-core/specification.rb +592 -0
  28. data/lib/cocoapods-core/standard_error.rb +84 -0
  29. data/lib/cocoapods-core/vendor/dependency.rb +264 -0
  30. data/lib/cocoapods-core/vendor/requirement.rb +208 -0
  31. data/lib/cocoapods-core/vendor/version.rb +333 -0
  32. data/lib/cocoapods-core/vendor.rb +56 -0
  33. data/lib/cocoapods-core/version.rb +99 -0
  34. data/lib/cocoapods-core/yaml_converter.rb +202 -0
  35. data/lib/cocoapods-core.rb +23 -0
  36. metadata +154 -0
@@ -0,0 +1,440 @@
1
+ module Pod
2
+
3
+ # The Lockfile stores information about the pods that were installed by
4
+ # CocoaPods.
5
+ #
6
+ # It is used in combination with the Podfile to resolve the exact version of
7
+ # the Pods that should be installed (i.e. to prevent `pod install` from
8
+ # upgrading dependencies).
9
+ #
10
+ # Moreover it is used as a manifest of an installation to detect which Pods
11
+ # need to be installed or removed.
12
+ #
13
+ class Lockfile
14
+
15
+ # TODO The symbols should be converted to a String and back to symbol
16
+ # when reading (EXTERNAL SOURCES Download options)
17
+
18
+ # @return [String] the hash used to initialize the Lockfile.
19
+ #
20
+ attr_reader :internal_data
21
+
22
+ # @param [Hash] hash
23
+ # a hash representation of the Lockfile.
24
+ #
25
+ def initialize(hash)
26
+ @internal_data = hash
27
+ end
28
+
29
+ # Loads a lockfile form the given path.
30
+ #
31
+ # @note This method returns nil if the given path doesn't exists.
32
+ #
33
+ # @raise If there is a syntax error loading the YAML data.
34
+ #
35
+ # @param [Pathname] path
36
+ # the path where the lockfile is serialized.
37
+ #
38
+ # @return [Lockfile] a new lockfile.
39
+ #
40
+ def self.from_file(path)
41
+ return nil unless path.exist?
42
+ begin
43
+ hash = YAML.load(File.open(path))
44
+ rescue Exception => e
45
+ raise StandardError, "Podfile.lock syntax error: #{e.inspect}"
46
+ end
47
+ lockfile = Lockfile.new(hash)
48
+ lockfile.defined_in_file = path
49
+ lockfile
50
+ end
51
+
52
+ # @return [String] the file where the Lockfile is serialized.
53
+ #
54
+ attr_accessor :defined_in_file
55
+
56
+ # @return [String] a string representation suitable for UI output.
57
+ #
58
+ def to_s
59
+ "Podfile.lock"
60
+ end
61
+
62
+ # @return [String] a string representation suitable for debugging.
63
+ #
64
+ def inspect
65
+ "#<#{self.class}>"
66
+ end
67
+
68
+ #-------------------------------------------------------------------------#
69
+
70
+ # !@group Accessing the Data
71
+
72
+ public
73
+
74
+ # @return [Array<String>] the names of the installed Pods.
75
+ #
76
+ def pod_names
77
+ generate_pod_names_and_versions unless @pod_names
78
+ @pod_names
79
+ end
80
+
81
+ # Returns the version of the given Pod.
82
+ #
83
+ # @param [name] The name of the Pod (root name of the specification).
84
+ #
85
+ # @return [Version] The version of the pod.
86
+ #
87
+ # @return [Nil] If there is no version stored for the given name.
88
+ #
89
+ def version(pod_name)
90
+ version = pod_versions[pod_name]
91
+ return version if version
92
+ pod_name = pod_versions.keys.find { |name| Specification.root_name(name) == pod_name }
93
+ pod_versions[pod_name]
94
+ end
95
+
96
+ # Returns the checksum for the given Pod.
97
+ #
98
+ # @param [name] The name of the Pod (root name of the specification).
99
+ #
100
+ # @return [String] The checksum of the specification for the given Pod.
101
+ #
102
+ # @return [Nil] If there is no checksum stored for the given name.
103
+ #
104
+ def checksum(name)
105
+ checksum_data[name]
106
+ end
107
+
108
+ # @return [Array<Dependency>] the dependencies of the Podfile used for the
109
+ # last installation.
110
+ #
111
+ # @note It includes only the dependencies explicitly required in the
112
+ # podfile and not those triggered by the Resolver.
113
+ #
114
+ def dependencies
115
+ unless @dependencies
116
+ data = internal_data['DEPENDENCIES'] || []
117
+ @dependencies = data.map do |string|
118
+ dep = Dependency.from_string(string)
119
+ dep.external_source = external_sources_data[dep.root_name]
120
+ dep
121
+ end
122
+ end
123
+ @dependencies
124
+ end
125
+
126
+ # Generates a dependency that requires the exact version of the Pod with the
127
+ # given name.
128
+ #
129
+ # @param [String] name
130
+ # the name of the Pod
131
+ #
132
+ # @note The generated dependencies are by the Installer to prevent the
133
+ # Resolver from upgrading a Pod during an installation.
134
+ #
135
+ # @raise If there is no version stored for the given name.
136
+ #
137
+ # @return [Dependency] the generated dependency.
138
+ #
139
+ def dependency_to_lock_pod_named(name)
140
+ dep = dependencies.find { |d| d.name == name || d.root_name == name }
141
+ version = version(name)
142
+
143
+ unless dep
144
+ raise StandardError, "Attempt to lock the `#{name}` Pod without an known dependency."
145
+ end
146
+
147
+ unless version
148
+ raise StandardError, "Attempt to lock the `#{name}` Pod without an known version."
149
+ end
150
+
151
+ locked_dependency = dep.dup
152
+ locked_dependency.specific_version = version
153
+ locked_dependency
154
+ end
155
+
156
+ #--------------------------------------#
157
+
158
+ # !@group Accessing the internal data.
159
+
160
+ private
161
+
162
+ # @return [Array<String, Hash{String => Array[String]}>] the pods installed
163
+ # and their dependencies.
164
+ #
165
+ def generate_pod_names_and_versions
166
+ @pod_names = []
167
+ @pod_versions = {}
168
+
169
+ return unless pods = internal_data['PODS']
170
+ pods.each do |pod|
171
+ pod = pod.keys.first unless pod.is_a?(String)
172
+ name, version = Spec.name_and_version_from_string(pod)
173
+ @pod_names << name
174
+ @pod_versions[name] = version
175
+ end
176
+ end
177
+
178
+ # @return [Hash{String => Hash}] a hash where the name of the pods are the
179
+ # keys and the values are the external source hash the dependency
180
+ # that required the pod.
181
+ #
182
+ def external_sources_data
183
+ @external_sources_data ||= internal_data["EXTERNAL SOURCES"] || {}
184
+ end
185
+
186
+ # @return [Hash{String => Version}] a Hash containing the name of the root
187
+ # specification of the installed Pods as the keys and their
188
+ # corresponding {Version} as the values.
189
+ #
190
+ def pod_versions
191
+ generate_pod_names_and_versions unless @pod_versions
192
+ @pod_versions
193
+ end
194
+
195
+ # @return [Hash{String => Version}] A Hash containing the checksums of the
196
+ # specification by the name of their root.
197
+ #
198
+ def checksum_data
199
+ internal_data['SPEC CHECKSUMS'] || {}
200
+ end
201
+
202
+
203
+ #-------------------------------------------------------------------------#
204
+
205
+ # !@group Comparison with a Podfile
206
+
207
+ public
208
+
209
+ # Analyzes the {Lockfile} and detects any changes applied to the {Podfile}
210
+ # since the last installation.
211
+ #
212
+ # For each Pod, it detects one state among the following:
213
+ #
214
+ # - added: Pods that weren't present in the Podfile.
215
+ # - changed: Pods that were present in the Podfile but changed:
216
+ # - Pods whose version is not compatible anymore with Podfile,
217
+ # - Pods that changed their head or external options.
218
+ # - removed: Pods that were removed form the Podfile.
219
+ # - unchanged: Pods that are still compatible with Podfile.
220
+ #
221
+ # @param [Podfile] podfile
222
+ # the podfile that should be analyzed.
223
+ #
224
+ # @return [Hash{Symbol=>Array[Strings]}] a hash where pods are grouped
225
+ # by the state in which they are.
226
+ #
227
+ # @todo Why do we look for compatibility instead of just comparing if the
228
+ # two dependencies are equal?
229
+ #
230
+ def detect_changes_with_podfile(podfile)
231
+ result = {}
232
+ [ :added, :changed, :removed, :unchanged ].each { |k| result[k] = [] }
233
+
234
+ installed_deps = dependencies.map { |d| dependency_to_lock_pod_named(d.name) }
235
+ all_dep_names = (dependencies + podfile.dependencies).map(&:name).uniq
236
+ all_dep_names.each do |name|
237
+ installed_dep = installed_deps.find { |d| d.name == name }
238
+ podfile_dep = podfile.dependencies.find { |d| d.name == name }
239
+
240
+ if installed_dep.nil? then key = :added
241
+ elsif podfile_dep.nil? then key = :removed
242
+ elsif podfile_dep.compatible?(installed_dep ) then key = :unchanged
243
+ else key = :changed
244
+ end
245
+ result[key] << name
246
+ end
247
+ result
248
+ end
249
+
250
+ #-------------------------------------------------------------------------#
251
+
252
+ # !@group Serialization
253
+
254
+ public
255
+
256
+ # Writes the Lockfile to the given path.
257
+ #
258
+ # @param [Pathname] path
259
+ # the path where the lockfile should be saved.
260
+ #
261
+ # @return [void]
262
+ #
263
+ def write_to_disk(path)
264
+ path.dirname.mkpath unless path.dirname.exist?
265
+ File.open(path, 'w') {|f| f.write(to_yaml) }
266
+ self.defined_in_file = path
267
+ end
268
+
269
+ # @return [Hash{String=>Array,Hash,String}] a hash reppresentation of the
270
+ # Lockfile.
271
+ #
272
+ # @example Output
273
+ #
274
+ # {
275
+ # 'PODS' => [ { BananaLib (1.0) => [monkey (< 1.0.9, ~> 1.0.1)] },
276
+ # "JSONKit (1.4)",
277
+ # "monkey (1.0.8)"]
278
+ # 'DEPENDENCIES' => [ "BananaLib (~> 1.0)",
279
+ # "JSONKit (from `path/JSONKit.podspec`)" ],
280
+ # 'EXTERNAL SOURCES' => { "JSONKit" => { :podspec => path/JSONKit.podspec } },
281
+ # 'SPEC CHECKSUMS' => { "BananaLib" => "439d9f683377ecf4a27de43e8cf3bce6be4df97b",
282
+ # "JSONKit", "92ae5f71b77c8dec0cd8d0744adab79d38560949" },
283
+ # 'COCOAPODS' => "0.17.0"
284
+ # }
285
+ #
286
+ #
287
+ def to_hash
288
+ hash = {}
289
+ internal_data.each do |key, value|
290
+ hash[key] = value unless value.empty?
291
+ end
292
+ hash
293
+ end
294
+
295
+ # @return [String] the YAML representation of the Lockfile, used for
296
+ # serialization.
297
+ #
298
+ # @note Empty root keys are discarded.
299
+ #
300
+ # @note The YAML string is prettified.
301
+ #
302
+ def to_yaml
303
+ keys_hint = [
304
+ "PODS",
305
+ "DEPENDENCIES",
306
+ "EXTERNAL SOURCES",
307
+ "SPEC CHECKSUMS",
308
+ "COCOAPODS"
309
+ ]
310
+ YAMLConverter.convert_hash(to_hash, keys_hint, "\n\n")
311
+ end
312
+
313
+ #-------------------------------------------------------------------------#
314
+
315
+ class << self
316
+
317
+ # !@group Generation
318
+
319
+ public
320
+
321
+ # Generates a hash representation of the Lockfile generated from a given
322
+ # Podfile and the list of resolved Specifications. This representation is
323
+ # suitable for serialization.
324
+ #
325
+ # @param [Podfile] podfile
326
+ # the podfile that should be used to generate the lockfile.
327
+ #
328
+ # @param [Array<Specification>] specs
329
+ # an array containing the podspec that were generated by
330
+ # resolving the given podfile.
331
+ #
332
+ # @return [Lockfile] a new lockfile.
333
+ #
334
+ def generate(podfile, specs)
335
+ hash = {
336
+ 'PODS' => generate_pods_data(podfile, specs),
337
+ 'DEPENDENCIES' => generate_dependencies_data(podfile),
338
+ 'EXTERNAL SOURCES' => generate_external_sources_data(podfile),
339
+ 'SPEC CHECKSUMS' => generate_checksums(specs),
340
+ 'COCOAPODS' => CORE_VERSION
341
+ }
342
+ Lockfile.new(hash)
343
+ end
344
+
345
+ #--------------------------------------#
346
+
347
+ private
348
+
349
+ # !@group Private helpers
350
+
351
+ # Generates the list of the installed Pods and their dependencies.
352
+ #
353
+ # @note The dependencies of iOS and OS X version of the same pod are
354
+ # merged.
355
+ #
356
+ # @todo Specifications should be stored per platform, otherwise they
357
+ # list dependencies which actually might not be used.
358
+ #
359
+ # @return [Array<Hash,String>] the generated data.
360
+ #
361
+ # @example Output
362
+ # [ {"BananaLib (1.0)"=>["monkey (< 1.0.9, ~> 1.0.1)"]},
363
+ # "monkey (1.0.8)" ]
364
+ #
365
+ #
366
+ def generate_pods_data(podfile, specs)
367
+ pod_and_deps = specs.map do |spec|
368
+ [spec.to_s, spec.all_dependencies.map(&:to_s).sort]
369
+ end.uniq
370
+
371
+ tmp = {}
372
+ pod_and_deps.each do |name, deps|
373
+ if tmp[name]
374
+ tmp[name].concat(deps).uniq!
375
+ else
376
+ tmp[name] = deps
377
+ end
378
+ end
379
+ pod_and_deps = tmp.sort_by(&:first).map do |name, deps|
380
+ deps.empty? ? name : { name => deps }
381
+ end
382
+ pod_and_deps
383
+ end
384
+
385
+ # Generates the list of the dependencies of the Podfile.
386
+ #
387
+ # @example Output
388
+ # [ "BananaLib (~> 1.0)",
389
+ # "JSONKit (from `path/JSONKit.podspec')" ]
390
+ #
391
+ # @return [Array] the generated data.
392
+ #
393
+ def generate_dependencies_data(podfile)
394
+ podfile.dependencies.map{ |d| d.to_s }.sort
395
+ end
396
+
397
+ # Generates the information of the external sources.
398
+ #
399
+ # @example Output
400
+ # { "JSONKit"=>{:podspec=>"path/JSONKit.podspec"} }
401
+ #
402
+ # @return [Hash] a hash where the keys are the names of the pods and
403
+ # the values store the external source hashes of each
404
+ # dependency.
405
+ #
406
+ # @todo The downloader should generate an external source hash that
407
+ # should be store for dependencies in head mode and for those
408
+ # with external source.
409
+ #
410
+ def generate_external_sources_data(podfile)
411
+ deps = podfile.dependencies.select(&:external?)
412
+ deps = deps.sort { |d, other| d.name <=> other.name}
413
+ sources = {}
414
+ deps.each { |d| sources[d.root_name] = d.external_source }
415
+ sources
416
+ end
417
+
418
+ # Generates the relative to the checksum of the specifications.
419
+ #
420
+ # @example Output
421
+ # {
422
+ # "BananaLib"=>"9906b267592664126923875ce2c8d03824372c79",
423
+ # "JSONKit"=>"92ae5f71b77c8dec0cd8d0744adab79d38560949"
424
+ # }
425
+ #
426
+ # @return [Hash] a hash where the keys are the names of the root
427
+ # specifications and the values are the SHA1 digest of the
428
+ # podspec file.
429
+ #
430
+ def generate_checksums(specs)
431
+ checksums = {}
432
+ specs.select { |spec| !spec.defined_in_file.nil? }.each do |spec|
433
+ checksums[spec.root.name] = spec.checksum
434
+ end
435
+ checksums
436
+ end
437
+ end
438
+ end
439
+ end
440
+
@@ -0,0 +1,171 @@
1
+ module Pod
2
+
3
+ # A Platform describes an SDK name and deployment target.
4
+ #
5
+ class Platform
6
+
7
+ # @return [Symbol, String] the name of the SDK represented by the platform.
8
+ #
9
+ def name
10
+ @symbolic_name
11
+ end
12
+
13
+ # @return [Version] the deployment target of the platform.
14
+ #
15
+ attr_reader :deployment_target
16
+
17
+ # Constructs a platform from either another platform or by
18
+ # specifying the symbolic name and optionally the deployment target.
19
+ #
20
+ # @overload initialize(name, deployment_target)
21
+ #
22
+ # @param [Symbol, String] name
23
+ # the name of platform.
24
+ #
25
+ # @param [String, Version] deployment_target
26
+ # the optional deployment.
27
+ #
28
+ # @note If the deployment target is not provided a default deployment
29
+ # target will not be assigned.
30
+ #
31
+ # @example Initialization with symbol
32
+ #
33
+ # Platform.new(:ios)
34
+ # Platform.new(:ios, '4.3')
35
+ #
36
+ # @overload initialize(platform)
37
+ #
38
+ # @param [Platform] platform
39
+ # Another {Platform}.
40
+ #
41
+ # @example Initialization with another platform
42
+ #
43
+ # platform = Platform.new(:ios)
44
+ # Platform.new(platform)
45
+ #
46
+ def initialize(input, target = nil)
47
+ if input.is_a? Platform
48
+ @symbolic_name = input.name
49
+ @deployment_target = input.deployment_target
50
+ else
51
+ @symbolic_name = input.to_sym
52
+ target = target[:deployment_target] if target.is_a?(Hash)
53
+ @deployment_target = Version.create(target)
54
+ end
55
+ end
56
+
57
+ # Convenience method to initialize an iOS platform.
58
+ #
59
+ # @return [Platform] an iOS platform.
60
+ #
61
+ def self.ios
62
+ new :ios
63
+ end
64
+
65
+ # Convenience method to initialize an OS X platform.
66
+ #
67
+ # @return [Platform] an OS X platform.
68
+ #
69
+ def self.osx
70
+ new :osx
71
+ end
72
+
73
+ # Checks if a platform is equivalent to another one or to a symbol
74
+ # representation.
75
+ #
76
+ # @param [Platform, Symbol] other
77
+ # the other platform to check.
78
+ #
79
+ # @note If a symbol is passed the comparison does not take into account
80
+ # the deployment target.
81
+ #
82
+ # @return [Boolean] whether two platforms are the equivalent.
83
+ #
84
+ def ==(other)
85
+ if other.is_a?(Symbol)
86
+ @symbolic_name == other
87
+ else
88
+ (name == other.name) && (deployment_target == other.deployment_target)
89
+ end
90
+ end
91
+
92
+ # Checks whether a platform supports another one.
93
+ #
94
+ # In the context of operating system SDKs, a platform supports another
95
+ # one if they have the same name and the other platform has a minor or
96
+ # equal deployment target.
97
+ #
98
+ # @return [Bool] whether the platform supports another platform.
99
+ #
100
+ def supports?(other)
101
+ other = Platform.new(other)
102
+ if other.deployment_target && deployment_target
103
+ (other.name == name) && (other.deployment_target <= deployment_target)
104
+ else
105
+ other.name == name
106
+ end
107
+ end
108
+
109
+ # @return [String] a string representation that includes the deployment
110
+ # target.
111
+ #
112
+ def to_s
113
+ s = self.class.string_name(@symbolic_name)
114
+ s << " #{deployment_target}" if deployment_target
115
+ s
116
+ end
117
+
118
+ # @return [String] the debug representation.
119
+ #
120
+ def inspect
121
+ "#<#{self.class.name} name=#{name.inspect} " \
122
+ "deployment_target=#{deployment_target.inspect}>"
123
+ end
124
+
125
+ # @return [Symbol] a symbol representing the name of the platform.
126
+ #
127
+ def to_sym
128
+ name
129
+ end
130
+
131
+ # Compares the platform first by name and the by deployment_target for
132
+ # sorting.
133
+ #
134
+ # @param [Platform] other
135
+ # The other platform to compare.
136
+ #
137
+ # @return [Fixnum] -1, 0, or +1 depending on whether the receiver is less
138
+ # than, equal to, or greater than other.
139
+ #
140
+ def <=> other
141
+ name_sort = self.name.to_s <=> other.name.to_s
142
+ if name_sort.zero?
143
+ self.deployment_target <=> other.deployment_target
144
+ else
145
+ name_sort
146
+ end
147
+ end
148
+
149
+ # @return [Bool] whether the platform requires legacy architectures for
150
+ # iOS.
151
+ #
152
+ def requires_legacy_ios_archs?
153
+ (name == :ios) && deployment_target && (deployment_target < Version.new("4.3"))
154
+ end
155
+
156
+ # Converts the symbolic name of a platform to a string name suitable to be
157
+ # presented to the user.
158
+ #
159
+ # @param [Symbol] symbolic_name
160
+ # the symbolic name of a platform.
161
+ #
162
+ # @return [String] The string that describes the name of the given symbol.
163
+ #
164
+ def self.string_name(symbolic_name)
165
+ case symbolic_name
166
+ when :ios then 'iOS'
167
+ when :osx then 'OS X'
168
+ else symbolic_name end
169
+ end
170
+ end
171
+ end