cocoapods-core 1.0.0.beta.6 → 1.0.0.beta.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b116f2a8b2c3c090b6aada3e046db9d3515e538a
4
- data.tar.gz: e4f97aed43c5b3ddc85381673e863a414796def1
3
+ metadata.gz: 4c385f0112647c0300a73d55e8c6d160f98fec9d
4
+ data.tar.gz: 1d87fa015bcf769c785e181c394aa1fdc7584ead
5
5
  SHA512:
6
- metadata.gz: b06b2f5053a53c500861559f776f65864128c2144b1e5a7bff34d40c78811fa952b70ee112e3a2f18475962049a1e3258da1b0d32c78aa0f62ab961d4a24fe3c
7
- data.tar.gz: 123a0a4674f478085c4abc1ba8d63b155c20b3fbc16d93b399bac5ebd0ff2d6914bb49baf8c730093a086881c61868f0829010737a5226c7a5a61ee5afbf5344
6
+ metadata.gz: 51e7cb1f3134fdd9d08f15427489033a43b76616429d7158d2534ca6fe461fde55da4c459ef98030d9f1e4bd53259b6d64c171edebc4ba4428725f1073259e6d
7
+ data.tar.gz: 7c0aaacccb38fbd1f89fdb950731eb72c58ecf3462258468a9b6703746fe2d0fb9d1ad5e98b652a317ab1bae6643c114f19748c097da1342052c8b56fa81512f
@@ -6,6 +6,10 @@ module Pod
6
6
  STDOUT.puts message
7
7
  end
8
8
 
9
+ def self.print(message)
10
+ STDOUT.print(message)
11
+ end
12
+
9
13
  def self.warn(message)
10
14
  STDERR.puts message
11
15
  end
@@ -1,5 +1,5 @@
1
1
  module Pod
2
2
  # The version of the cocoapods-core.
3
3
  #
4
- CORE_VERSION = '1.0.0.beta.6'.freeze unless defined? Pod::CORE_VERSION
4
+ CORE_VERSION = '1.0.0.beta.7'.freeze unless defined? Pod::CORE_VERSION
5
5
  end
@@ -123,21 +123,22 @@ module Pod
123
123
  # your Podfile to use future versions of CocoaPods. Anyway a clear and
124
124
  # simple upgrade path will be provided.
125
125
  #
126
- # By default dependencies are installed on all the build configurations
126
+ # By default dependencies are installed in all the build configurations
127
127
  # of the target. For debug purposes or for other reasons, they can be
128
- # enabled only on a given list of build configuration names.
128
+ # only enabled on a list of build configurations.
129
129
  #
130
- # pod 'PonyDebugger', :configurations => ['Release', 'App Store']
130
+ # pod 'PonyDebugger', :configurations => ['Debug', 'Beta']
131
131
  #
132
- # Alternatively you can white-list only a single build configuration.
132
+ # Alternatively, you specify to have it included on a single build
133
+ # configuration.
133
134
  #
134
- # pod 'PonyDebugger', :configuration => ['Release']
135
+ # pod 'PonyDebugger', :configuration => 'Debug'
135
136
  #
136
137
  # ------
137
138
  #
138
139
  # ### Subspecs
139
140
  #
140
- # When installing a Pod via it's name, it will install all of the
141
+ # When installing a Pod via its name, it will install all of the
141
142
  # default subspecs defined in the podspec.
142
143
  #
143
144
  # You may install a specific subspec using the following:
@@ -295,12 +296,12 @@ module Pod
295
296
  # target 'ShowsApp' do
296
297
  # pod 'ShowsKit'
297
298
  #
298
- # # Has it's own copy of ShowsKit + ShowTVAuth
299
+ # # Has its own copy of ShowsKit + ShowTVAuth
299
300
  # target 'ShowsTV' do
300
301
  # pod 'ShowTVAuth'
301
302
  # end
302
303
  #
303
- # # Has it's own copy of Specta + Expecta
304
+ # # Has its own copy of Specta + Expecta
304
305
  # # and has access to ShowsKit via the app
305
306
  # # that the test target is bundled into
306
307
  #
@@ -344,23 +345,24 @@ module Pod
344
345
  #
345
346
  # @example Defining an abstract_target wrapping Pods to multiple targets
346
347
  #
347
- # # There are no targets called "Shows" in any Xcode projects
348
+ # # Note: There are no targets called "Shows" in any of this workspace's Xcode projects
348
349
  # abstract_target 'Shows' do
349
350
  # pod 'ShowsKit'
350
351
  #
351
- # # Has it's own copy of ShowsKit + ShowWebAuth
352
+ # # The target ShowsiOS has its own copy of ShowsKit (inherited) + ShowWebAuth (added here)
352
353
  # target 'ShowsiOS' do
353
354
  # pod 'ShowWebAuth'
354
355
  # end
355
356
  #
356
- # # Has it's own copy of ShowsKit + ShowTVAuth
357
+ # # The target ShowsTV has its own copy of ShowsKit (inherited) + ShowTVAuth (added here)
357
358
  # target 'ShowsTV' do
358
359
  # pod 'ShowTVAuth'
359
360
  # end
360
361
  #
361
- # # Has it's own copy of Specta + Expecta
362
- # # and has access to ShowsKit via the app
363
- # # that the test target is bundled into
362
+ # # Our tests target has its own copy of
363
+ # # our testing frameworks, and has access
364
+ # # to ShowsKit as well because it is
365
+ # # a child of the abstract target 'Shows'
364
366
  #
365
367
  # target 'ShowsTests' do
366
368
  # inherit! :search_paths
@@ -10,6 +10,7 @@ module Pod
10
10
  # @param [Array<Source>] repos_dirs @see Sources
11
11
  #
12
12
  def initialize(sources)
13
+ raise "Cannot initialize an aggregate with a nil source: (#{sources})" if sources.include?(nil)
13
14
  @sources = sources
14
15
  end
15
16
 
@@ -164,7 +165,7 @@ module Pod
164
165
  result = {}
165
166
  sets.each do |set|
166
167
  word_list_from_set(set).each do |w|
167
- (result[w] ||= []).push(set.name.to_sym)
168
+ (result[w] ||= []).push(set.name)
168
169
  end
169
170
  end
170
171
  result
@@ -0,0 +1,412 @@
1
+ module Pod
2
+ class Source
3
+ class Manager
4
+ # @return [Pathname] The directory that contains the source repo
5
+ # directories.
6
+ #
7
+ attr_reader :repos_dir
8
+
9
+ def initialize(repos_dir)
10
+ @repos_dir = Pathname(repos_dir).expand_path
11
+ end
12
+
13
+ # @return [Array<Pathname>] The source repo directories.
14
+ #
15
+ def source_repos
16
+ return [] unless repos_dir.exist?
17
+ repos_dir.children.select(&:directory?).sort_by { |d| d.basename.to_s.downcase }
18
+ end
19
+
20
+ # @return [Source::Aggregate] The aggregate of all the sources with the
21
+ # known Pods.
22
+ #
23
+ def aggregate
24
+ aggregate_with_repos(source_repos)
25
+ end
26
+
27
+ # @return [Source::Aggregate] The aggregate of the sources from repos.
28
+ #
29
+ # @param [Dependency] dependency
30
+ # The dependency for which to find or build the appropriate.
31
+ # aggregate. If the dependency specifies a source podspec repo
32
+ # then only that source will be used, otherwise all sources
33
+ # will be used.
34
+ #
35
+ def aggregate_for_dependency(dependency)
36
+ if dependency.podspec_repo
37
+ source = source_with_url(dependency.podspec_repo)
38
+ raise StandardError, '[Bug] Failed to find known source with the URL ' \
39
+ "#{dependency.podspec_repo.inspect}" if source.nil?
40
+
41
+ aggregate_with_repos([source.repo])
42
+ else
43
+ aggregate
44
+ end
45
+ end
46
+
47
+ # @return [Array<Source>] The list of the sources with the given names.
48
+ #
49
+ # @param [Array<#to_s>] names
50
+ # The names of the sources.
51
+ #
52
+ def sources(names)
53
+ dirs = names.map { |name| source_dir(name) }
54
+ dirs.map { |repo| source_from_path(repo) }
55
+ end
56
+
57
+ # @return [Array<Source>] The list of all the sources known to this
58
+ # installation of CocoaPods.
59
+ #
60
+ def all
61
+ aggregate.sources
62
+ end
63
+
64
+ # @return [Array<Source>] The CocoaPods Master Repo source.
65
+ #
66
+ def master
67
+ sources(['master']).select { |s| s.repo.directory? }
68
+ end
69
+
70
+ # @!group Master repo
71
+
72
+ # @return [Pathname] The path of the master repo.
73
+ #
74
+ def master_repo_dir
75
+ source_dir('master')
76
+ end
77
+
78
+ # @return [Bool] Checks if the master repo is usable.
79
+ #
80
+ # @note Note this is used to automatically setup the master repo if
81
+ # needed.
82
+ #
83
+ def master_repo_functional?
84
+ return false unless master_repo = master.first
85
+ master_repo.metadata.compatible?(CORE_VERSION)
86
+ end
87
+
88
+ # Search the appropriate sources to match the set for the given dependency.
89
+ #
90
+ # @return [Set, nil] a set for a given dependency including all the
91
+ # {Source} that contain the Pod. If no sources containing the
92
+ # Pod where found it returns nil.
93
+ #
94
+ # @raise If no source can be found that includes the dependency.
95
+ #
96
+ def search(dependency)
97
+ aggregate_for_dependency(dependency).search(dependency)
98
+ end
99
+
100
+ # Search all the sources with the given search term.
101
+ #
102
+ # @param [String] query
103
+ # The search term.
104
+ #
105
+ # @param [Bool] full_text_search
106
+ # Whether the search should be limited to the name of the Pod or
107
+ # should include also the author, the summary, and the
108
+ # description.
109
+ #
110
+ # @raise If no source including the set can be found.
111
+ #
112
+ # @return [Array<Set>] The sets that contain the search term.
113
+ #
114
+ def search_by_name(query, full_text_search = false)
115
+ query_word_regexps = query.split.map { |word| /#{word}/i }
116
+ if full_text_search
117
+ query_word_results_hash = {}
118
+ updated_search_index.each_value do |word_spec_hash|
119
+ word_spec_hash.each_pair do |word, spec_names|
120
+ query_word_regexps.each do |query_word_regexp|
121
+ set = (query_word_results_hash[query_word_regexp] ||= Set.new)
122
+ set.merge(spec_names) if word =~ query_word_regexp
123
+ end
124
+ end
125
+ end
126
+ found_set_names = query_word_results_hash.values.reduce(:&)
127
+ found_set_names ||= []
128
+ sets = found_set_names.map do |name|
129
+ aggregate.representative_set(name)
130
+ end
131
+ # Remove nil values because representative_set return nil if no pod is found in any of the sources.
132
+ sets.compact!
133
+ else
134
+ sets = aggregate.search_by_name(query, false)
135
+ end
136
+ if sets.empty?
137
+ extra = ', author, summary, or description' if full_text_search
138
+ raise Informative, "Unable to find a pod with name#{extra}" \
139
+ "matching `#{query}`"
140
+ end
141
+ sorted_sets(sets, query_word_regexps)
142
+ end
143
+
144
+ # Returns given set array by sorting it in-place.
145
+ #
146
+ # @param [Array<Set>] sets
147
+ # Array of sets to be sorted.
148
+ #
149
+ # @param [Array<Regexp>] query_word_regexps
150
+ # Array of regexp objects for user query.
151
+ #
152
+ # @return [Array<Set>] Given sets parameter itself after sorting it in-place.
153
+ #
154
+ def sorted_sets(sets, query_word_regexps)
155
+ sets.sort_by! do |set|
156
+ pre_match_length = nil
157
+ found_query_index = nil
158
+ found_query_count = 0
159
+ query_word_regexps.each_with_index do |q, idx|
160
+ if (m = set.name.match(/#{q}/i))
161
+ pre_match_length ||= m.pre_match.length
162
+ found_query_index ||= idx
163
+ found_query_count += 1
164
+ end
165
+ end
166
+ pre_match_length ||= 1000
167
+ found_query_index ||= 1000
168
+ [-found_query_count, pre_match_length, found_query_index, set.name.downcase]
169
+ end
170
+ sets
171
+ end
172
+
173
+ # Returns the search data. If a saved search data exists, retrieves it from file and returns it.
174
+ # Else, creates the search data from scratch, saves it to file system, and returns it.
175
+ # Search data is grouped by source repos. For each source, it contains a hash where keys are words
176
+ # and values are the pod names containing corresponding word.
177
+ #
178
+ # For each source, list of unique words are generated from the following spec information.
179
+ # - version
180
+ # - summary
181
+ # - description
182
+ # - authors
183
+ #
184
+ # @return [Hash{String => Hash{String => Array<String>}}] The up to date search data.
185
+ #
186
+ def updated_search_index
187
+ index = stored_search_index || {}
188
+ all.each do |source|
189
+ source_name = source.name
190
+ unless index[source_name]
191
+ CoreUI.print "Creating search index for spec repo '#{source_name}'.."
192
+ index[source_name] = aggregate.generate_search_index_for_source(source)
193
+ CoreUI.puts ' Done!'
194
+ end
195
+ end
196
+ save_search_index(index)
197
+ index
198
+ end
199
+
200
+ # Updates the stored search index if there are changes in spec repos while updating them.
201
+ # Update is performed incrementally. Only the changed pods' search data is re-generated and updated.
202
+ # @param [Hash{Source => Array<String>}] changed_spec_paths
203
+ # A hash containing changed specification paths for each source.
204
+ #
205
+ def update_search_index_if_needed(changed_spec_paths)
206
+ search_index = stored_search_index
207
+ return unless search_index
208
+ changed_spec_paths.each_pair do |source, spec_paths|
209
+ index_for_source = search_index[source.name]
210
+ next unless index_for_source && !spec_paths.empty?
211
+ updated_pods = source.pods_for_specification_paths(spec_paths)
212
+
213
+ new_index = aggregate.generate_search_index_for_changes_in_source(source, spec_paths)
214
+ # First traverse search_index and update existing words
215
+ # Remove traversed words from new_index after adding to search_index,
216
+ # so that only non existing words will remain in new_index after enumeration completes.
217
+ index_for_source.each_pair do |word, _|
218
+ if new_index[word]
219
+ index_for_source[word] |= new_index[word]
220
+ new_index.delete(word)
221
+ else
222
+ index_for_source[word] -= updated_pods
223
+ end
224
+ end
225
+
226
+ # Now add non existing words remained in new_index to search_index
227
+ index_for_source.merge!(new_index)
228
+ end
229
+ save_search_index(search_index)
230
+ end
231
+
232
+ # Updates search index for changed pods in background
233
+ # @param [Hash{Source => Array<String>}] changed_spec_paths
234
+ # A hash containing changed specification paths for each source.
235
+ #
236
+ def update_search_index_if_needed_in_background(changed_spec_paths)
237
+ Process.fork do
238
+ Process.daemon
239
+ update_search_index_if_needed(changed_spec_paths)
240
+ exit
241
+ end
242
+ end
243
+
244
+ # Returns the search data stored in the file system.
245
+ # If existing data in the file system is not valid, returns nil.
246
+ #
247
+ def stored_search_index
248
+ @updated_search_index ||= begin
249
+ if search_index_path.exist?
250
+ require 'json'
251
+ index = JSON.parse(search_index_path.read)
252
+ index if index.is_a?(Hash) # TODO: should we also check if hash has correct hierarchy?
253
+ end
254
+ end
255
+ end
256
+
257
+ # Stores given search data in the file system.
258
+ # @param [Hash] index
259
+ # Index to be saved in file system
260
+ #
261
+ def save_search_index(index)
262
+ require 'json'
263
+ @updated_search_index = index
264
+ search_index_path.open('w') do |io|
265
+ io.write(@updated_search_index.to_json)
266
+ end
267
+ end
268
+
269
+ # Allows to clear the search index.
270
+ #
271
+ attr_writer :updated_search_index
272
+
273
+ # @return [Pathname] The path where the search index should be stored.
274
+ #
275
+ attr_accessor :search_index_path
276
+
277
+ private
278
+
279
+ # @return [Source] The Source at a given path.
280
+ #
281
+ # @param [Pathname] path
282
+ # The local file path to one podspec repo.
283
+ #
284
+ def source_from_path(path)
285
+ @sources_by_path ||= Hash.new do |hash, key|
286
+ hash[key] = if key.basename.to_s == 'master'
287
+ MasterSource.new(key)
288
+ else
289
+ Source.new(key)
290
+ end
291
+ end
292
+ @sources_by_path[path]
293
+ end
294
+
295
+ # @return [Source::Aggregate] The aggregate of the sources from repos.
296
+ #
297
+ # @param [Array<Pathname>] repos
298
+ # The local file paths to one or more podspec repo caches.
299
+ #
300
+ def aggregate_with_repos(repos)
301
+ sources = repos.map { |path| source_from_path(path) }
302
+ @aggregates_by_repos ||= {}
303
+ @aggregates_by_repos[repos] ||= Source::Aggregate.new(sources)
304
+ end
305
+
306
+ # @return [Source] The git source with the given name. If no git source
307
+ # with given name is found it raises.
308
+ #
309
+ # @param [String] name
310
+ # The name of the source.
311
+ #
312
+ def git_source_named(name)
313
+ specified_source = sources([name]).first
314
+ unless specified_source
315
+ raise Informative, "Unable to find the `#{name}` repo."
316
+ end
317
+ unless specified_source.git?
318
+ raise Informative, "The `#{name}` repo is not a git repo."
319
+ end
320
+ specified_source
321
+ end
322
+
323
+ # @return [Source] The list of the git sources.
324
+ #
325
+ def git_sources
326
+ all.select(&:git?)
327
+ end
328
+
329
+ # @return [Pathname] The path of the source with the given name.
330
+ #
331
+ # @param [String] name
332
+ # The name of the source.
333
+ #
334
+ def source_dir(name)
335
+ repos_dir + name
336
+ end
337
+
338
+ # @return [Source] The source whose {Source#url} is equal to `url`.
339
+ #
340
+ # @param [String] url
341
+ # The URL of the source.
342
+ #
343
+ def source_with_url(url)
344
+ url = url.downcase.gsub(/.git$/, '')
345
+ url = 'https://github.com/cocoapods/specs' if url =~ %r{github.com[:/]+cocoapods/specs}
346
+ all.find do |source|
347
+ source.url && source.url.downcase.gsub(/.git$/, '') == url
348
+ end
349
+ end
350
+
351
+ # Returns a suitable repository name for `url`.
352
+ #
353
+ # @example A GitHub.com URL
354
+ #
355
+ # name_for_url('https://github.com/Artsy/Specs.git')
356
+ # # "artsy"
357
+ # name_for_url('https://github.com/Artsy/Specs.git')
358
+ # # "artsy-1"
359
+ #
360
+ # @example A non-Github.com URL
361
+ #
362
+ # name_for_url('https://sourceforge.org/Artsy/Specs.git')
363
+ # # sourceforge-artsy-specs
364
+ #
365
+ # @example A file URL
366
+ #
367
+ # name_for_url('file:///Artsy/Specs.git')
368
+ # # artsy-specs
369
+ #
370
+ # @param [#to_s] url
371
+ # The URL of the source.
372
+ #
373
+ # @return [String] A suitable repository name for `url`.
374
+ #
375
+ def name_for_url(url)
376
+ base_from_host_and_path = lambda do |host, path|
377
+ if host
378
+ base = host.split('.')[-2] || host
379
+ base += '-'
380
+ else
381
+ base = ''
382
+ end
383
+
384
+ base + path.gsub(/.git$/, '').gsub(%r{^/}, '').split('/').join('-')
385
+ end
386
+
387
+ case url.to_s.downcase
388
+ when %r{github.com[:/]+cocoapods/specs}
389
+ base = 'master'
390
+ when %r{github.com[:/]+(.+)/(.+)}
391
+ base = Regexp.last_match[1]
392
+ when %r{^\S+@(\S+)[:/]+(.+)$}
393
+ host, path = Regexp.last_match.captures
394
+ base = base_from_host_and_path[host, path]
395
+ when URI.regexp
396
+ url = URI(url.downcase)
397
+ base = base_from_host_and_path[url.host, url.path]
398
+ else
399
+ base = url.to_s.downcase
400
+ end
401
+
402
+ name = base
403
+ infinity = 1.0 / 0
404
+ (1..infinity).each do |i|
405
+ break unless source_dir(name).exist?
406
+ name = "#{base}-#{i}"
407
+ end
408
+ name
409
+ end
410
+ end
411
+ end
412
+ end
@@ -0,0 +1,79 @@
1
+ autoload :Digest, 'digest/md5'
2
+ require 'active_support/hash_with_indifferent_access'
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+
5
+ module Pod
6
+ class Source
7
+ class Metadata
8
+ attr_reader :minimum_cocoapods_version
9
+ attr_reader :maximum_cocoapods_version
10
+ attr_reader :latest_cocoapods_version
11
+ attr_reader :prefix_lengths
12
+ attr_reader :last_compatible_versions
13
+
14
+ def initialize(hash = {})
15
+ hash = hash.with_indifferent_access
16
+ @minimum_cocoapods_version = hash['min']
17
+ @minimum_cocoapods_version &&= Pod::Version.new(@minimum_cocoapods_version)
18
+ @maximum_cocoapods_version = hash['max']
19
+ @maximum_cocoapods_version &&= Pod::Version.new(@maximum_cocoapods_version)
20
+ @latest_cocoapods_version = hash['last']
21
+ @latest_cocoapods_version &&= Pod::Version.new(@latest_cocoapods_version)
22
+ @prefix_lengths = Array(hash['prefix_lengths']).map!(&:to_i)
23
+ @last_compatible_versions = Array(hash['last_compatible_versions']).map(&Pod::Version.method(:new)).sort
24
+ end
25
+
26
+ def self.from_file(file)
27
+ hash = file.file? ? YAMLHelper.load_file(file) : {}
28
+ new(hash)
29
+ end
30
+
31
+ def to_hash
32
+ hash = ActiveSupport::HashWithIndifferentAccess.new
33
+ hash['min'] = @minimum_cocoapods_version.to_s if @minimum_cocoapods_version
34
+ hash['max'] = @maximum_cocoapods_version.to_s if @maximum_cocoapods_version
35
+ hash['last'] = @latest_cocoapods_version.to_s if @latest_cocoapods_version
36
+ hash['prefix_lengths'] = @prefix_lengths unless @prefix_lengths.empty?
37
+ hash['last_compatible_versions'] = @last_compatible_versions.map(&:to_s) unless @last_compatible_versions.empty?
38
+ hash
39
+ end
40
+
41
+ def path_fragment(pod_name, version = nil)
42
+ prefixes = if prefix_lengths.empty?
43
+ []
44
+ else
45
+ hashed = Digest::MD5.hexdigest(pod_name)
46
+ prefix_lengths.map do |length|
47
+ hashed.slice!(0, length)
48
+ end
49
+ end
50
+ prefixes.concat([pod_name, version]).compact.join(File::SEPARATOR)
51
+ end
52
+
53
+ def last_compatible_version(target_version)
54
+ return unless minimum_cocoapods_version
55
+ return if minimum_cocoapods_version <= target_version
56
+ @last_compatible_versions.reverse_each.bsearch { |v| v <= target_version }.tap do |version|
57
+ raise Informative, 'Unable to find compatible version' unless version
58
+ end
59
+ end
60
+
61
+ # Returns whether a source is compatible with the current version of
62
+ # CocoaPods.
63
+ #
64
+ # @param [Pathname] dir
65
+ # The directory where the source is stored.
66
+ #
67
+ # @return [Bool] whether the source is compatible.
68
+ #
69
+ def compatible?(version)
70
+ bin_version = Gem::Version.new(version)
71
+ supports_min = !minimum_cocoapods_version ||
72
+ (bin_version >= Gem::Version.new(minimum_cocoapods_version))
73
+ supports_max = !maximum_cocoapods_version ||
74
+ (bin_version <= Gem::Version.new(maximum_cocoapods_version))
75
+ supports_min && supports_max
76
+ end
77
+ end
78
+ end
79
+ end
@@ -1,6 +1,8 @@
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/manager'
5
+ require 'cocoapods-core/source/metadata'
4
6
 
5
7
  module Pod
6
8
  # The Source class is responsible to manage a collection of podspecs.
@@ -14,14 +16,15 @@ module Pod
14
16
  # "#{SPEC_NAME}/#{VERSION}/#{SPEC_NAME}.podspec"
15
17
  #
16
18
  class Source
17
- # @return [Pathname] The path where the source is stored.
19
+ # @return [Pod::Source::Metadata] The metadata for this source.
18
20
  #
19
- attr_reader :repo
21
+ attr_reader :metadata
20
22
 
21
23
  # @param [Pathname, String] repo @see #repo.
22
24
  #
23
25
  def initialize(repo)
24
26
  @repo = Pathname(repo).expand_path
27
+ refresh_metadata
25
28
  end
26
29
 
27
30
  # @return [String] The name of the source.
@@ -74,9 +77,49 @@ module Pod
74
77
  "#<#{self.class} name:#{name} type:#{type}>"
75
78
  end
76
79
 
80
+ # @!group Paths
81
+ #-------------------------------------------------------------------------#
82
+
83
+ # @return [Pathname] The path where the source is stored.
84
+ #
85
+ attr_reader :repo
86
+
87
+ # @return [Pathname] The directory where the specs are stored.
88
+ #
89
+ # @note In previous versions of CocoaPods they used to be stored in
90
+ # the root of the repo. This lead to issues, especially with
91
+ # the GitHub interface and now they are stored in a dedicated
92
+ # folder.
93
+ #
94
+ def specs_dir
95
+ @specs_dir ||= begin
96
+ specs_sub_dir = repo + 'Specs'
97
+ if specs_sub_dir.exist?
98
+ specs_sub_dir
99
+ elsif repo.exist?
100
+ repo
101
+ end
102
+ end
103
+ end
104
+
105
+ # @param [String] name The name of the pod.
106
+ #
107
+ # @return [Pathname] The path at which the specs for the given pod are
108
+ # stored.
109
+ #
110
+ def pod_path(name)
111
+ specs_dir.join(*metadata.path_fragment(name))
112
+ end
113
+
114
+ # @return [Pathname] The path at which source metadata is stored.
115
+ #
116
+ def metadata_path
117
+ repo + 'CocoaPods-version.yml'
118
+ end
119
+
77
120
  public
78
121
 
79
- # @!group Queering the source
122
+ # @!group Querying the source
80
123
  #-------------------------------------------------------------------------#
81
124
 
82
125
  # @return [Array<String>] the list of the name of all the Pods.
@@ -86,10 +129,10 @@ module Pod
86
129
  unless specs_dir
87
130
  raise Informative, "Unable to find a source named: `#{name}`"
88
131
  end
89
- specs_dir_as_string = specs_dir.to_s
90
- Dir.entries(specs_dir).select do |entry|
91
- valid_name = entry[0, 1] != '.'
92
- valid_name && File.directory?(File.join(specs_dir_as_string, entry))
132
+ glob = specs_dir.join('*/' * metadata.prefix_lengths.size, '*')
133
+ Pathname.glob(glob).reduce([]) do |pods, entry|
134
+ pods << entry.basename.to_s if entry.directory?
135
+ pods
93
136
  end.sort
94
137
  end
95
138
 
@@ -118,7 +161,7 @@ module Pod
118
161
  def versions(name)
119
162
  return nil unless specs_dir
120
163
  raise ArgumentError, 'No name' unless name
121
- pod_dir = specs_dir + name
164
+ pod_dir = pod_path(name)
122
165
  return unless pod_dir.exist?
123
166
  pod_dir.children.map do |v|
124
167
  basename = v.basename.to_s
@@ -153,7 +196,7 @@ module Pod
153
196
  def specification_path(name, version)
154
197
  raise ArgumentError, 'No name' unless name
155
198
  raise ArgumentError, 'No version' unless version
156
- path = specs_dir + name + version.to_s
199
+ path = pod_path(name) + version.to_s
157
200
  specification_path = path + "#{name}.podspec.json"
158
201
  unless specification_path.exist?
159
202
  specification_path = path + "#{name}.podspec"
@@ -169,15 +212,17 @@ module Pod
169
212
  # source.
170
213
  #
171
214
  def all_specs
172
- specs = pods.map do |name|
215
+ glob = specs_dir.join('*/' * metadata.prefix_lengths.size, '*', '*', '*.podspec{.json,}')
216
+ specs = Pathname.glob(glob).map do |path|
173
217
  begin
174
- versions(name).map { |version| specification(name, version) }
218
+ Specification.from_file(path)
175
219
  rescue
176
- CoreUI.warn "Skipping `#{name}` because the podspec contains errors."
220
+ CoreUI.warn "Skipping `#{path.relative_path_from(repo)}` because the " \
221
+ 'podspec contains errors.'
177
222
  next
178
223
  end
179
224
  end
180
- specs.flatten.compact
225
+ specs.compact
181
226
  end
182
227
 
183
228
  # Returns the set for the Pod with the given name.
@@ -217,8 +262,10 @@ module Pod
217
262
  if query.is_a?(Dependency)
218
263
  query = query.root_name
219
264
  end
220
- if specs_dir.children.select(&:directory?).map(&:basename).map(&:to_s).include?(query.to_s)
221
- set(query)
265
+ found = Pathname.glob(pod_path(query)).map { |path| path.basename.to_s }
266
+ if [query] == found
267
+ set = set(query)
268
+ set if set.specification.name == query
222
269
  end
223
270
  end
224
271
 
@@ -290,11 +337,37 @@ module Pod
290
337
  Dir.chdir(repo) do
291
338
  prev_commit_hash = git_commit_hash
292
339
  update_git_repo(show_output)
340
+ refresh_metadata
341
+ if version = metadata.last_compatible_version(Version.new(CORE_VERSION))
342
+ tag = "v#{version}"
343
+ CoreUI.warn "Using the `#{tag}` tag of the `#{name}` source because " \
344
+ "it is the last version compatible with CocoaPods #{CORE_VERSION}."
345
+ git(['checkout', tag])
346
+ end
293
347
  changed_spec_paths = diff_until_commit_hash(prev_commit_hash)
294
348
  end
295
349
  changed_spec_paths
296
350
  end
297
351
 
352
+ def git?
353
+ Dir.chdir(repo) do
354
+ !git(%w(rev-parse HEAD)).empty?
355
+ end
356
+ end
357
+
358
+ def verify_compatibility!
359
+ return if metadata.compatible?(CORE_VERSION)
360
+
361
+ version_msg = if metadata.minimum_cocoapods_version == metadata.maximum_cocoapods_version
362
+ metadata.minimum_cocoapods_version
363
+ else
364
+ "#{metadata.minimum_cocoapods_version} - #{metadata.maximum_cocoapods_version}"
365
+ end
366
+ raise Informative, "The `#{name}` repo requires " \
367
+ "CocoaPods #{version_msg} (currently using #{CORE_VERSION})\n" \
368
+ 'Update CocoaPods, or checkout the appropriate tag in the repo.'
369
+ end
370
+
298
371
  public
299
372
 
300
373
  # @!group Representations
@@ -326,8 +399,9 @@ module Pod
326
399
  #-------------------------------------------------------------------------#
327
400
 
328
401
  def ensure_in_repo!
329
- return if Pathname.pwd == repo
330
- raise 'Must be in the root of the repo'
402
+ return if Pathname.pwd.realpath == repo.realpath
403
+ raise StandardError, "Must be in the root of the repo (#{repo}), " \
404
+ "instead in #{Pathname.pwd}."
331
405
  end
332
406
 
333
407
  # Loads the specification for the given Pod gracefully.
@@ -349,22 +423,8 @@ module Pod
349
423
  nil
350
424
  end
351
425
 
352
- # @return [Pathname] The directory where the specs are stored.
353
- #
354
- # @note In previous versions of CocoaPods they used to be stored in
355
- # the root of the repo. This lead to issues, especially with
356
- # the GitHub interface and now they are stored in a dedicated
357
- # folder.
358
- #
359
- def specs_dir
360
- @specs_dir ||= begin
361
- specs_sub_dir = repo + 'Specs'
362
- if specs_sub_dir.exist?
363
- specs_sub_dir
364
- elsif repo.exist?
365
- repo
366
- end
367
- end
426
+ def refresh_metadata
427
+ @metadata = Metadata.from_file(metadata_path)
368
428
  end
369
429
 
370
430
  def git_commit_hash
@@ -374,10 +434,16 @@ module Pod
374
434
 
375
435
  def update_git_repo(show_output = false)
376
436
  ensure_in_repo!
437
+ git(['checkout', git_tracking_branch])
377
438
  output = git(%w(pull --ff-only), :include_error => true)
378
439
  CoreUI.puts output if show_output
379
440
  end
380
441
 
442
+ def git_tracking_branch
443
+ path = repo.join('.git', 'cocoapods_branch')
444
+ path.file? ? path.read.strip : 'master'
445
+ end
446
+
381
447
  def diff_until_commit_hash(commit_hash)
382
448
  ensure_in_repo!
383
449
  git(%W(diff --name-only #{commit_hash}..HEAD)).split("\n")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocoapods-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta.6
4
+ version: 1.0.0.beta.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eloy Duran
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-03-14 00:00:00.000000000 Z
12
+ date: 2016-04-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -98,6 +98,8 @@ files:
98
98
  - lib/cocoapods-core/source/acceptor.rb
99
99
  - lib/cocoapods-core/source/aggregate.rb
100
100
  - lib/cocoapods-core/source/health_reporter.rb
101
+ - lib/cocoapods-core/source/manager.rb
102
+ - lib/cocoapods-core/source/metadata.rb
101
103
  - lib/cocoapods-core/specification.rb
102
104
  - lib/cocoapods-core/specification/consumer.rb
103
105
  - lib/cocoapods-core/specification/dsl.rb
@@ -138,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
140
  version: '0'
139
141
  requirements: []
140
142
  rubyforge_project:
141
- rubygems_version: 2.6.2
143
+ rubygems_version: 2.6.3
142
144
  signing_key:
143
145
  specification_version: 3
144
146
  summary: The models of CocoaPods