cocoapods-core 0.17.0.rc1

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