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,12 +1,10 @@
1
1
  require 'cocoapods-core/source/acceptor'
2
2
  require 'cocoapods-core/source/aggregate'
3
3
  require 'cocoapods-core/source/health_reporter'
4
- require 'cocoapods-core/source/abstract_data_provider'
5
- require 'cocoapods-core/source/file_system_data_provider'
6
- require 'cocoapods-core/source/github_data_provider'
4
+ require 'cocoapods-core/source/manager'
5
+ require 'cocoapods-core/source/metadata'
7
6
 
8
7
  module Pod
9
-
10
8
  # The Source class is responsible to manage a collection of podspecs.
11
9
  #
12
10
  # The backing store of the podspecs collection is an implementation detail
@@ -18,32 +16,49 @@ module Pod
18
16
  # "#{SPEC_NAME}/#{VERSION}/#{SPEC_NAME}.podspec"
19
17
  #
20
18
  class Source
19
+ # The default branch in which the specs are stored
20
+ DEFAULT_SPECS_BRANCH = 'master'.freeze
21
21
 
22
- # @return [AbstractDataProvider] the data provider for this source.
22
+ # @return [Pod::Source::Metadata] The metadata for this source.
23
23
  #
24
- attr_accessor :data_provider
24
+ attr_reader :metadata
25
25
 
26
26
  # @param [Pathname, String] repo @see #repo.
27
27
  #
28
- def initialize(repo = nil)
29
- # TODO: Temporary solution to aide the transition
30
- if repo.is_a?(String) || repo.is_a?(Pathname)
31
- @data_provider = FileSystemDataProvider.new(repo)
32
- else
33
- @data_provider = repo
34
- end
28
+ def initialize(repo)
29
+ @repo = Pathname(repo).expand_path
30
+ @versions_by_name = {}
31
+ refresh_metadata
35
32
  end
36
33
 
37
34
  # @return [String] The name of the source.
38
35
  #
39
36
  def name
40
- data_provider.name
37
+ repo.basename.to_s
38
+ end
39
+
40
+ # @return [String] The URL of the source.
41
+ #
42
+ # @note In the past we had used `git ls-remote --get-url`, but this could
43
+ # lead to an issue when finding a source based on its URL when `git`
44
+ # is configured to rewrite URLs with the `url.<base>.insteadOf`
45
+ # option. See https://github.com/CocoaPods/CocoaPods/issues/2724.
46
+ #
47
+ def url
48
+ @url ||= begin
49
+ remote = repo_git(%w(config --get remote.origin.url))
50
+ if !remote.empty?
51
+ remote
52
+ elsif (repo + '.git').exist?
53
+ "file://#{repo}/.git"
54
+ end
55
+ end
41
56
  end
42
57
 
43
58
  # @return [String] The type of the source.
44
59
  #
45
60
  def type
46
- data_provider.type
61
+ git? ? 'git' : 'file system'
47
62
  end
48
63
 
49
64
  alias_method :to_s, :name
@@ -65,21 +80,79 @@ module Pod
65
80
  "#<#{self.class} name:#{name} type:#{type}>"
66
81
  end
67
82
 
83
+ # @!group Paths
84
+ #-------------------------------------------------------------------------#
85
+
86
+ # @return [Pathname] The path where the source is stored.
87
+ #
88
+ attr_reader :repo
89
+
90
+ # @return [Pathname] The directory where the specs are stored.
91
+ #
92
+ # @note In previous versions of CocoaPods they used to be stored in
93
+ # the root of the repo. This lead to issues, especially with
94
+ # the GitHub interface and now they are stored in a dedicated
95
+ # folder.
96
+ #
97
+ def specs_dir
98
+ @specs_dir ||= begin
99
+ specs_sub_dir = repo + 'Specs'
100
+ if specs_sub_dir.exist?
101
+ specs_sub_dir
102
+ elsif repo.exist?
103
+ repo
104
+ end
105
+ end
106
+ end
107
+
108
+ # @param [String] name The name of the pod.
109
+ #
110
+ # @return [Pathname] The path at which the specs for the given pod are
111
+ # stored.
112
+ #
113
+ def pod_path(name)
114
+ specs_dir.join(*metadata.path_fragment(name))
115
+ end
116
+
117
+ # @return [Pathname] The path at which source metadata is stored.
118
+ #
119
+ def metadata_path
120
+ repo + 'CocoaPods-version.yml'
121
+ end
122
+
68
123
  public
69
124
 
70
- # @!group Queering the source
125
+ # @!group Querying the source
71
126
  #-------------------------------------------------------------------------#
72
127
 
73
128
  # @return [Array<String>] the list of the name of all the Pods.
74
129
  #
75
130
  #
76
131
  def pods
77
- pods = data_provider.pods
78
- unless pods
79
- raise Informative, "Unable to find the #{data_provider.type} source " \
80
- "named: `#{data_provider.name}`"
132
+ unless specs_dir
133
+ raise Informative, "Unable to find a source named: `#{name}`"
134
+ end
135
+ glob = specs_dir.join('*/' * metadata.prefix_lengths.size, '*')
136
+ Pathname.glob(glob).reduce([]) do |pods, entry|
137
+ pods << entry.basename.to_s if entry.directory?
138
+ pods
139
+ end.sort
140
+ end
141
+
142
+ # Returns pod names for given array of specification paths.
143
+ #
144
+ # @param [Array<String>] spec_paths
145
+ # Array of file path names for specifications. Path strings should be relative to the source path.
146
+ #
147
+ # @return [Array<String>] the list of the name of Pods corresponding to specification paths.
148
+ #
149
+ def pods_for_specification_paths(spec_paths)
150
+ spec_paths.map do |path|
151
+ absolute_path = repo + path
152
+ relative_path = absolute_path.relative_path_from(specs_dir)
153
+ # The first file name returned by 'each_filename' is the pod name
154
+ relative_path.each_filename.first
81
155
  end
82
- pods
83
156
  end
84
157
 
85
158
  # @return [Array<Version>] all the available versions for the Pod, sorted
@@ -89,8 +162,22 @@ module Pod
89
162
  # the name of the Pod.
90
163
  #
91
164
  def versions(name)
92
- versions = data_provider.versions(name)
93
- versions.map { |version| Version.new(version) } if versions
165
+ return nil unless specs_dir
166
+ raise ArgumentError, 'No name' unless name
167
+ pod_dir = pod_path(name)
168
+ return unless pod_dir.exist?
169
+ @versions_by_name[name] ||= pod_dir.children.map do |v|
170
+ next nil unless v.directory?
171
+ basename = v.basename.to_s
172
+ next unless basename[0, 1] != '.'
173
+ begin
174
+ Version.new(basename)
175
+ rescue ArgumentError
176
+ raise Informative, 'An unexpected version directory ' \
177
+ "`#{basename}` was encountered for the " \
178
+ "`#{pod_dir}` Pod in the `#{name}` repository."
179
+ end
180
+ end.compact.sort.reverse
94
181
  end
95
182
 
96
183
  # @return [Specification] the specification for a given version of Pod.
@@ -112,31 +199,35 @@ module Pod
112
199
  # @return [Pathname] The path of the specification.
113
200
  #
114
201
  def specification_path(name, version)
115
- path = specs_dir + name + version.to_s
202
+ raise ArgumentError, 'No name' unless name
203
+ raise ArgumentError, 'No version' unless version
204
+ path = pod_path(name) + version.to_s
116
205
  specification_path = path + "#{name}.podspec.json"
117
206
  unless specification_path.exist?
118
207
  specification_path = path + "#{name}.podspec"
119
208
  end
120
209
  unless specification_path.exist?
121
210
  raise StandardError, "Unable to find the specification #{name} " \
122
- "(#{version}) in the #{name} source."
211
+ "(#{version}) in the #{self.name} source."
123
212
  end
124
- spec
213
+ specification_path
125
214
  end
126
215
 
127
216
  # @return [Array<Specification>] all the specifications contained by the
128
217
  # source.
129
218
  #
130
219
  def all_specs
131
- specs = pods.map do |name|
220
+ glob = specs_dir.join('*/' * metadata.prefix_lengths.size, '*', '*', '*.podspec{.json,}')
221
+ specs = Pathname.glob(glob).map do |path|
132
222
  begin
133
- versions(name).map { |version| specification(name, version) }
223
+ Specification.from_file(path)
134
224
  rescue
135
- CoreUI.warn "Skipping `#{name}` because the podspec contains errors."
225
+ CoreUI.warn "Skipping `#{path.relative_path_from(repo)}` because the " \
226
+ 'podspec contains errors.'
136
227
  next
137
228
  end
138
229
  end
139
- specs.flatten.compact
230
+ specs.compact
140
231
  end
141
232
 
142
233
  # Returns the set for the Pod with the given name.
@@ -156,22 +247,6 @@ module Pod
156
247
  pods.map { |pod_name| set(pod_name) }
157
248
  end
158
249
 
159
- # Returns the path of the specification with the given name and version.
160
- #
161
- # @param [String] name
162
- # the name of the Pod.
163
- #
164
- # @param [Version,String] version
165
- # the version for the specification.
166
- #
167
- # @return [Pathname] The path of the specification.
168
- #
169
- # @todo Remove.
170
- #
171
- def specification_path(name, version)
172
- data_provider.specification_path(name, version)
173
- end
174
-
175
250
  public
176
251
 
177
252
  # @!group Searching the source
@@ -180,16 +255,33 @@ module Pod
180
255
  # @return [Set] a set for a given dependency. The set is identified by the
181
256
  # name of the dependency and takes into account subspecs.
182
257
  #
258
+ # @note This method is optimized for fast lookups by name, i.e. it does
259
+ # *not* require iterating through {#pod_sets}
260
+ #
183
261
  # @todo Rename to #load_set
184
262
  #
185
263
  def search(query)
264
+ unless specs_dir
265
+ raise Informative, "Unable to find a source named: `#{name}`"
266
+ end
186
267
  if query.is_a?(Dependency)
187
- name = query.root_name
188
- else
189
- name = query
268
+ query = query.root_name
269
+ end
270
+
271
+ if (versions = @versions_by_name[query]) && !versions.empty?
272
+ set = set(query)
273
+ return set if set.specification_name == query
274
+ end
275
+
276
+ found = []
277
+ Pathname.glob(pod_path(query)) do |path|
278
+ next unless Dir.foreach(path).any? { |child| child != '.' && child != '..' }
279
+ found << path.basename.to_s
190
280
  end
191
- pod_sets.find do |set|
192
- set.name == name
281
+
282
+ if [query] == found
283
+ set = set(query)
284
+ set if set.specification_name == query
193
285
  end
194
286
  end
195
287
 
@@ -198,7 +290,7 @@ module Pod
198
290
  # @param [String] query
199
291
  # the search term. Can be a regular expression.
200
292
  #
201
- # @param [Bool] full_text_search
293
+ # @param [Boolean] full_text_search
202
294
  # whether the search should be limited to the name of the Pod or
203
295
  # should include also the author, the summary, and the description.
204
296
  #
@@ -210,23 +302,19 @@ module Pod
210
302
  def search_by_name(query, full_text_search = false)
211
303
  regexp_query = /#{query}/i
212
304
  if full_text_search
213
- if data_provider.is_a?(FileSystemDataProvider)
214
- pod_sets.reject do |set|
215
- texts = []
216
- begin
217
- s = set.specification
218
- texts << s.name
219
- texts += s.authors.keys
220
- texts << s.summary
221
- texts << s.description
222
- rescue
223
- CoreUI.warn "Skipping `#{set.name}` because the podspec " \
224
- "contains errors."
225
- end
226
- texts.grep(regexp_query).empty?
305
+ pod_sets.reject do |set|
306
+ texts = []
307
+ begin
308
+ s = set.specification
309
+ texts << s.name
310
+ texts += s.authors.keys
311
+ texts << s.summary
312
+ texts << s.description
313
+ rescue
314
+ CoreUI.warn "Skipping `#{set.name}` because the podspec " \
315
+ 'contains errors.'
227
316
  end
228
- else
229
- []
317
+ texts.grep(regexp_query).empty?
230
318
  end
231
319
  else
232
320
  names = pods.grep(regexp_query)
@@ -250,6 +338,56 @@ module Pod
250
338
  end
251
339
  end
252
340
 
341
+ # @!group Updating the source
342
+ #-------------------------------------------------------------------------#
343
+
344
+ # Updates the local clone of the source repo.
345
+ #
346
+ # @param [Boolean] show_output
347
+ #
348
+ # @return [Array<String>] changed_spec_paths
349
+ # Returns the list of changed spec paths.
350
+ #
351
+ def update(show_output)
352
+ return [] if unchanged_github_repo?
353
+ prev_commit_hash = git_commit_hash
354
+ update_git_repo(show_output)
355
+ @versions_by_name.clear
356
+ refresh_metadata
357
+ if version = metadata.last_compatible_version(Version.new(CORE_VERSION))
358
+ tag = "v#{version}"
359
+ CoreUI.warn "Using the `#{tag}` tag of the `#{name}` source because " \
360
+ "it is the last version compatible with CocoaPods #{CORE_VERSION}."
361
+ repo_git(['checkout', tag])
362
+ end
363
+ diff_until_commit_hash(prev_commit_hash)
364
+ end
365
+
366
+ def updateable?
367
+ git?
368
+ end
369
+
370
+ def git?
371
+ repo.join('.git').exist? && !repo_git(%w(rev-parse HEAD)).empty?
372
+ end
373
+
374
+ def indexable?
375
+ true
376
+ end
377
+
378
+ def verify_compatibility!
379
+ return if metadata.compatible?(CORE_VERSION)
380
+
381
+ version_msg = if metadata.minimum_cocoapods_version == metadata.maximum_cocoapods_version
382
+ metadata.minimum_cocoapods_version
383
+ else
384
+ "#{metadata.minimum_cocoapods_version} - #{metadata.maximum_cocoapods_version}"
385
+ end
386
+ raise Informative, "The `#{name}` repo requires " \
387
+ "CocoaPods #{version_msg} (currently using #{CORE_VERSION})\n" \
388
+ 'Update CocoaPods, or checkout the appropriate tag in the repo.'
389
+ end
390
+
253
391
  public
254
392
 
255
393
  # @!group Representations
@@ -295,11 +433,44 @@ module Pod
295
433
  specification(name, version) if version
296
434
  rescue Informative
297
435
  Pod::CoreUI.warn "Skipping `#{name}` because the podspec " \
298
- "contains errors."
436
+ 'contains errors.'
299
437
  nil
300
438
  end
301
439
 
302
- #-------------------------------------------------------------------------#
440
+ def refresh_metadata
441
+ @metadata = Metadata.from_file(metadata_path)
442
+ end
303
443
 
444
+ def git_commit_hash
445
+ repo_git(%w(rev-parse HEAD))
446
+ end
447
+
448
+ def update_git_repo(show_output = false)
449
+ repo_git(['checkout', git_tracking_branch])
450
+ output = repo_git(%w(pull --ff-only), :include_error => true)
451
+ CoreUI.puts output if show_output
452
+ end
453
+
454
+ def git_tracking_branch
455
+ path = repo.join('.git', 'cocoapods_branch')
456
+ path.file? ? path.read.strip : DEFAULT_SPECS_BRANCH
457
+ end
458
+
459
+ def diff_until_commit_hash(commit_hash)
460
+ repo_git(%W(diff --name-only #{commit_hash}..HEAD)).split("\n")
461
+ end
462
+
463
+ def repo_git(args, include_error: false)
464
+ command = "env -u GIT_CONFIG git -C \"#{repo}\" " << args.join(' ')
465
+ command << ' 2>&1' if include_error
466
+ (`#{command}` || '').strip
467
+ end
468
+
469
+ def unchanged_github_repo?
470
+ return unless url =~ /github.com/
471
+ !GitHub.modified_since_commit(url, git_commit_hash)
472
+ end
473
+
474
+ #-------------------------------------------------------------------------#
304
475
  end
305
476
  end