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.
- checksums.yaml +5 -5
- data/README.md +7 -10
- data/lib/cocoapods-core/build_type.rb +121 -0
- data/lib/cocoapods-core/cdn_source.rb +501 -0
- data/lib/cocoapods-core/core_ui.rb +4 -3
- data/lib/cocoapods-core/dependency.rb +100 -73
- data/lib/cocoapods-core/gem_version.rb +1 -2
- data/lib/cocoapods-core/github.rb +32 -5
- data/lib/cocoapods-core/http.rb +86 -0
- data/lib/cocoapods-core/lockfile.rb +161 -56
- data/lib/cocoapods-core/metrics.rb +47 -0
- data/lib/cocoapods-core/platform.rb +99 -11
- data/lib/cocoapods-core/podfile/dsl.rb +623 -124
- data/lib/cocoapods-core/podfile/target_definition.rb +662 -109
- data/lib/cocoapods-core/podfile.rb +138 -65
- data/lib/cocoapods-core/requirement.rb +37 -8
- data/lib/cocoapods-core/source/acceptor.rb +16 -13
- data/lib/cocoapods-core/source/aggregate.rb +79 -103
- data/lib/cocoapods-core/source/health_reporter.rb +9 -18
- data/lib/cocoapods-core/source/manager.rb +488 -0
- data/lib/cocoapods-core/source/metadata.rb +79 -0
- data/lib/cocoapods-core/source.rb +241 -70
- data/lib/cocoapods-core/specification/consumer.rb +187 -47
- data/lib/cocoapods-core/specification/dsl/attribute.rb +49 -85
- data/lib/cocoapods-core/specification/dsl/attribute_support.rb +6 -8
- data/lib/cocoapods-core/specification/dsl/deprecations.rb +9 -126
- data/lib/cocoapods-core/specification/dsl/platform_proxy.rb +30 -20
- data/lib/cocoapods-core/specification/dsl.rb +943 -296
- data/lib/cocoapods-core/specification/json.rb +64 -23
- data/lib/cocoapods-core/specification/linter/analyzer.rb +218 -0
- data/lib/cocoapods-core/specification/linter/result.rb +128 -0
- data/lib/cocoapods-core/specification/linter.rb +310 -309
- data/lib/cocoapods-core/specification/root_attribute_accessors.rb +90 -39
- data/lib/cocoapods-core/specification/set/presenter.rb +35 -71
- data/lib/cocoapods-core/specification/set.rb +42 -96
- data/lib/cocoapods-core/specification.rb +368 -130
- data/lib/cocoapods-core/standard_error.rb +45 -24
- data/lib/cocoapods-core/trunk_source.rb +14 -0
- data/lib/cocoapods-core/vendor/requirement.rb +133 -53
- data/lib/cocoapods-core/vendor/version.rb +197 -156
- data/lib/cocoapods-core/vendor.rb +1 -5
- data/lib/cocoapods-core/version.rb +137 -42
- data/lib/cocoapods-core/yaml_helper.rb +334 -0
- data/lib/cocoapods-core.rb +10 -4
- metadata +100 -27
- data/lib/cocoapods-core/source/abstract_data_provider.rb +0 -71
- data/lib/cocoapods-core/source/file_system_data_provider.rb +0 -150
- data/lib/cocoapods-core/source/github_data_provider.rb +0 -143
- data/lib/cocoapods-core/specification/set/statistics.rb +0 -266
- data/lib/cocoapods-core/yaml_converter.rb +0 -192
@@ -0,0 +1,488 @@
|
|
1
|
+
require 'public_suffix'
|
2
|
+
|
3
|
+
module Pod
|
4
|
+
class Source
|
5
|
+
class Manager
|
6
|
+
# @return [Pathname] The directory that contains the source repo
|
7
|
+
# directories.
|
8
|
+
#
|
9
|
+
attr_reader :repos_dir
|
10
|
+
|
11
|
+
def initialize(repos_dir)
|
12
|
+
@repos_dir = Pathname(repos_dir).expand_path
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Array<Pathname>] The source repo directories.
|
16
|
+
#
|
17
|
+
def source_repos
|
18
|
+
return [] unless repos_dir.exist?
|
19
|
+
repos_dir.children.select(&:directory?).sort_by { |d| d.basename.to_s.downcase }
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Source::Aggregate] The aggregate of all the sources with the
|
23
|
+
# known Pods.
|
24
|
+
#
|
25
|
+
def aggregate
|
26
|
+
aggregate_with_repos(source_repos)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Source::Aggregate] The aggregate of the sources from repos.
|
30
|
+
#
|
31
|
+
# @param [Dependency] dependency
|
32
|
+
# The dependency for which to find or build the appropriate.
|
33
|
+
# aggregate. If the dependency specifies a source podspec repo
|
34
|
+
# then only that source will be used, otherwise all sources
|
35
|
+
# will be used.
|
36
|
+
#
|
37
|
+
def aggregate_for_dependency(dependency)
|
38
|
+
return aggregate if dependency.podspec_repo.nil?
|
39
|
+
|
40
|
+
source = source_with_url(dependency.podspec_repo) || source_with_name(dependency.podspec_repo)
|
41
|
+
return aggregate if source.nil?
|
42
|
+
|
43
|
+
aggregate_with_repos([source.repo])
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Array<Source>] The list of the sources with the given names.
|
47
|
+
#
|
48
|
+
# @param [Array<#to_s>] names
|
49
|
+
# The names of the sources.
|
50
|
+
#
|
51
|
+
def sources(names)
|
52
|
+
dirs = names.map { |name| source_dir(name) }
|
53
|
+
dirs.map { |repo| source_from_path(repo) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Array<Source>] The list of all the sources known to this
|
57
|
+
# installation of CocoaPods.
|
58
|
+
#
|
59
|
+
def all
|
60
|
+
aggregate.sources
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Array<Source>] The list of all the non-indexable sources known to this
|
64
|
+
# installation of CocoaPods.
|
65
|
+
#
|
66
|
+
def all_non_indexable
|
67
|
+
aggregate.sources.reject(&:indexable?)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Array<Source>] The CocoaPods Master Repo source.
|
71
|
+
#
|
72
|
+
def master
|
73
|
+
sources([Pod::TrunkSource::TRUNK_REPO_NAME]).select { |s| s.repo.directory? }
|
74
|
+
end
|
75
|
+
|
76
|
+
# @!group Master repo
|
77
|
+
|
78
|
+
# @return [Pathname] The path of the master repo.
|
79
|
+
#
|
80
|
+
def master_repo_dir
|
81
|
+
source_dir(Pod::TrunkSource::TRUNK_REPO_NAME)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Boolean] Checks if the master repo is usable.
|
85
|
+
#
|
86
|
+
# @note Note this is used to automatically setup the master repo if
|
87
|
+
# needed.
|
88
|
+
#
|
89
|
+
def master_repo_functional?
|
90
|
+
return false unless master_repo = master.first
|
91
|
+
master_repo.metadata.compatible?(CORE_VERSION)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Search the appropriate sources to match the set for the given dependency.
|
95
|
+
#
|
96
|
+
# @return [Set, nil] a set for a given dependency including all the
|
97
|
+
# {Source} that contain the Pod. If no sources containing the
|
98
|
+
# Pod where found it returns nil.
|
99
|
+
#
|
100
|
+
# @raise If no source can be found that includes the dependency.
|
101
|
+
#
|
102
|
+
def search(dependency)
|
103
|
+
aggregate_for_dependency(dependency).search(dependency)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Search all the sources with the given search term.
|
107
|
+
#
|
108
|
+
# @param [String] query
|
109
|
+
# The search term.
|
110
|
+
#
|
111
|
+
# @param [Boolean] full_text_search
|
112
|
+
# Whether the search should be limited to the name of the Pod or
|
113
|
+
# should include also the author, the summary, and the
|
114
|
+
# description.
|
115
|
+
#
|
116
|
+
# @raise If no source including the set can be found.
|
117
|
+
#
|
118
|
+
# @return [Array<Set>] The sets that contain the search term.
|
119
|
+
#
|
120
|
+
def search_by_name(query, full_text_search = false)
|
121
|
+
query_word_regexps = query.split.map { |word| /#{word}/i }
|
122
|
+
if full_text_search
|
123
|
+
query_word_results_hash = {}
|
124
|
+
updated_search_index.each_value do |word_spec_hash|
|
125
|
+
word_spec_hash.each_pair do |word, spec_names|
|
126
|
+
query_word_regexps.each do |query_word_regexp|
|
127
|
+
set = (query_word_results_hash[query_word_regexp] ||= Set.new)
|
128
|
+
set.merge(spec_names) if word =~ query_word_regexp
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
found_set_names = query_word_results_hash.values.reduce(:&)
|
133
|
+
found_set_names ||= []
|
134
|
+
|
135
|
+
sets_from_non_indexable = all_non_indexable.map { |s| s.search_by_name(query, true) }.flatten
|
136
|
+
|
137
|
+
found_set_names += sets_from_non_indexable.map(&:name).flatten.uniq
|
138
|
+
|
139
|
+
sets = found_set_names.map do |name|
|
140
|
+
aggregate.representative_set(name)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Remove nil values because representative_set return nil if no pod is found in any of the sources.
|
144
|
+
sets.compact!
|
145
|
+
else
|
146
|
+
sets = aggregate.search_by_name(query, false)
|
147
|
+
end
|
148
|
+
if sets.empty?
|
149
|
+
extra = ', author, summary, or description' if full_text_search
|
150
|
+
raise Informative, "Unable to find a pod with name#{extra} " \
|
151
|
+
"matching `#{query}`"
|
152
|
+
end
|
153
|
+
sorted_sets(sets, query_word_regexps)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns given set array by sorting it in-place.
|
157
|
+
#
|
158
|
+
# @param [Array<Set>] sets
|
159
|
+
# Array of sets to be sorted.
|
160
|
+
#
|
161
|
+
# @param [Array<Regexp>] query_word_regexps
|
162
|
+
# Array of regexp objects for user query.
|
163
|
+
#
|
164
|
+
# @return [Array<Set>] Given sets parameter itself after sorting it in-place.
|
165
|
+
#
|
166
|
+
def sorted_sets(sets, query_word_regexps)
|
167
|
+
sets.sort_by! do |set|
|
168
|
+
pre_match_length = nil
|
169
|
+
found_query_index = nil
|
170
|
+
found_query_count = 0
|
171
|
+
query_word_regexps.each_with_index do |q, idx|
|
172
|
+
if (m = set.name.match(/#{q}/i))
|
173
|
+
pre_match_length ||= m.pre_match.length
|
174
|
+
found_query_index ||= idx
|
175
|
+
found_query_count += 1
|
176
|
+
end
|
177
|
+
end
|
178
|
+
pre_match_length ||= 1000
|
179
|
+
found_query_index ||= 1000
|
180
|
+
[-found_query_count, pre_match_length, found_query_index, set.name.downcase]
|
181
|
+
end
|
182
|
+
sets
|
183
|
+
end
|
184
|
+
|
185
|
+
# Returns the search data. If a saved search data exists, retrieves it from file and returns it.
|
186
|
+
# Else, creates the search data from scratch, saves it to file system, and returns it.
|
187
|
+
# Search data is grouped by source repos. For each source, it contains a hash where keys are words
|
188
|
+
# and values are the pod names containing corresponding word.
|
189
|
+
#
|
190
|
+
# For each source, list of unique words are generated from the following spec information.
|
191
|
+
# - version
|
192
|
+
# - summary
|
193
|
+
# - description
|
194
|
+
# - authors
|
195
|
+
#
|
196
|
+
# @return [Hash{String => Hash{String => Array<String>}}] The up to date search data.
|
197
|
+
#
|
198
|
+
def updated_search_index
|
199
|
+
index = stored_search_index || {}
|
200
|
+
indexable_sources.each do |source|
|
201
|
+
source_name = source.name
|
202
|
+
unless index[source_name]
|
203
|
+
CoreUI.print "Creating search index for spec repo '#{source_name}'.."
|
204
|
+
index[source_name] = aggregate.generate_search_index_for_source(source)
|
205
|
+
CoreUI.puts ' Done!'
|
206
|
+
end
|
207
|
+
end
|
208
|
+
save_search_index(index)
|
209
|
+
index
|
210
|
+
end
|
211
|
+
|
212
|
+
# Updates the stored search index if there are changes in spec repos while updating them.
|
213
|
+
# Update is performed incrementally. Only the changed pods' search data is re-generated and updated.
|
214
|
+
# @param [Hash{Source => Array<String>}] changed_spec_paths
|
215
|
+
# A hash containing changed specification paths for each source.
|
216
|
+
#
|
217
|
+
def update_search_index_if_needed(changed_spec_paths)
|
218
|
+
search_index = stored_search_index
|
219
|
+
return unless search_index
|
220
|
+
changed_spec_paths.each_pair do |source, spec_paths|
|
221
|
+
next unless source.indexable?
|
222
|
+
index_for_source = search_index[source.name]
|
223
|
+
next unless index_for_source && !spec_paths.empty?
|
224
|
+
updated_pods = source.pods_for_specification_paths(spec_paths)
|
225
|
+
|
226
|
+
new_index = aggregate.generate_search_index_for_changes_in_source(source, spec_paths)
|
227
|
+
# First traverse search_index and update existing words
|
228
|
+
# Remove traversed words from new_index after adding to search_index,
|
229
|
+
# so that only non existing words will remain in new_index after enumeration completes.
|
230
|
+
index_for_source.each_pair do |word, _|
|
231
|
+
if new_index[word]
|
232
|
+
index_for_source[word] |= new_index[word]
|
233
|
+
new_index.delete(word)
|
234
|
+
else
|
235
|
+
index_for_source[word] -= updated_pods
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Now add non existing words remained in new_index to search_index
|
240
|
+
index_for_source.merge!(new_index)
|
241
|
+
end
|
242
|
+
save_search_index(search_index)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Updates search index for changed pods in background
|
246
|
+
# @param [Hash{Source => Array<String>}] changed_spec_paths
|
247
|
+
# A hash containing changed specification paths for each source.
|
248
|
+
#
|
249
|
+
def update_search_index_if_needed_in_background(changed_spec_paths)
|
250
|
+
if Gem.win_platform?
|
251
|
+
update_search_index_if_needed(changed_spec_paths)
|
252
|
+
return
|
253
|
+
end
|
254
|
+
Process.fork do
|
255
|
+
Process.daemon
|
256
|
+
update_search_index_if_needed(changed_spec_paths)
|
257
|
+
exit
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Returns the search data stored in the file system.
|
262
|
+
# If existing data in the file system is not valid, returns nil.
|
263
|
+
#
|
264
|
+
def stored_search_index
|
265
|
+
@updated_search_index ||= begin
|
266
|
+
if search_index_path.exist?
|
267
|
+
require 'json'
|
268
|
+
begin
|
269
|
+
index = JSON.parse(search_index_path.read)
|
270
|
+
unless index # JSON.parse("null") => nil
|
271
|
+
search_index_path.delete
|
272
|
+
return nil
|
273
|
+
end
|
274
|
+
|
275
|
+
index if index.is_a?(Hash) # TODO: should we also check if hash has correct hierarchy?
|
276
|
+
rescue JSON::ParserError
|
277
|
+
search_index_path.delete
|
278
|
+
nil
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Stores given search data in the file system.
|
285
|
+
# @param [Hash] index
|
286
|
+
# Index to be saved in file system
|
287
|
+
#
|
288
|
+
def save_search_index(index)
|
289
|
+
require 'json'
|
290
|
+
@updated_search_index = index
|
291
|
+
search_index_path.open('w') do |io|
|
292
|
+
io.write(@updated_search_index.to_json)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Allows to clear the search index.
|
297
|
+
#
|
298
|
+
attr_writer :updated_search_index
|
299
|
+
|
300
|
+
# @return [Pathname] The path where the search index should be stored.
|
301
|
+
#
|
302
|
+
attr_accessor :search_index_path
|
303
|
+
|
304
|
+
private
|
305
|
+
|
306
|
+
# @return [Source] The Source at a given path.
|
307
|
+
#
|
308
|
+
# @param [Pathname] path
|
309
|
+
# The local file path to one podspec repo.
|
310
|
+
#
|
311
|
+
def source_from_path(path)
|
312
|
+
@sources_by_path ||= Hash.new do |hash, key|
|
313
|
+
hash[key] = case
|
314
|
+
when key.basename.to_s == Pod::TrunkSource::TRUNK_REPO_NAME
|
315
|
+
TrunkSource.new(key)
|
316
|
+
when (key + '.url').exist?
|
317
|
+
CDNSource.new(key)
|
318
|
+
else
|
319
|
+
Source.new(key)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
@sources_by_path[path]
|
323
|
+
end
|
324
|
+
|
325
|
+
# @return [Source::Aggregate] The aggregate of the sources from repos.
|
326
|
+
#
|
327
|
+
# @param [Array<Pathname>] repos
|
328
|
+
# The local file paths to one or more podspec repo caches.
|
329
|
+
#
|
330
|
+
def aggregate_with_repos(repos)
|
331
|
+
sources = repos.map { |path| source_from_path(path) }
|
332
|
+
@aggregates_by_repos ||= {}
|
333
|
+
@aggregates_by_repos[repos] ||= Source::Aggregate.new(sources)
|
334
|
+
end
|
335
|
+
|
336
|
+
# @return [Source] The source with the given name.
|
337
|
+
#
|
338
|
+
# @param [String] name
|
339
|
+
# The name of the source.
|
340
|
+
#
|
341
|
+
def source_with_name(name)
|
342
|
+
source = sources([name]).first
|
343
|
+
return nil unless source.repo.exist?
|
344
|
+
source
|
345
|
+
end
|
346
|
+
|
347
|
+
# @return [Source] The updateable source with the given name. If no updateable source
|
348
|
+
# with given name is found it raises.
|
349
|
+
#
|
350
|
+
# @param [String] name
|
351
|
+
# The name of the source.
|
352
|
+
#
|
353
|
+
def updateable_source_named(name)
|
354
|
+
specified_source = source_with_name(name)
|
355
|
+
unless specified_source
|
356
|
+
raise Informative, "Unable to find the `#{name}` repo."
|
357
|
+
end
|
358
|
+
unless specified_source.updateable?
|
359
|
+
raise Informative, "The `#{name}` repo is not a updateable repo."
|
360
|
+
end
|
361
|
+
specified_source
|
362
|
+
end
|
363
|
+
|
364
|
+
# @return [Source] The list of the updateable sources.
|
365
|
+
#
|
366
|
+
def updateable_sources
|
367
|
+
all.select(&:updateable?)
|
368
|
+
end
|
369
|
+
|
370
|
+
# @return [Source] The list of the indexable sources.
|
371
|
+
#
|
372
|
+
def indexable_sources
|
373
|
+
all.select(&:indexable?)
|
374
|
+
end
|
375
|
+
|
376
|
+
# @return [Pathname] The path of the source with the given name.
|
377
|
+
#
|
378
|
+
# @param [String] name
|
379
|
+
# The name of the source.
|
380
|
+
#
|
381
|
+
def source_dir(name)
|
382
|
+
repos_dir + name
|
383
|
+
end
|
384
|
+
|
385
|
+
# @return [Source] The source whose {Source#url} is equal to `url`.
|
386
|
+
#
|
387
|
+
# @param [String] url
|
388
|
+
# The URL of the source.
|
389
|
+
#
|
390
|
+
def source_with_url(url)
|
391
|
+
url = canonic_url(url)
|
392
|
+
url = 'https://github.com/cocoapods/specs' if url =~ %r{github.com[:/]+cocoapods/specs}
|
393
|
+
all.find do |source|
|
394
|
+
source.url && canonic_url(source.url) == url
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
def canonic_url(url)
|
399
|
+
url.downcase.gsub(/\.git$/, '').gsub(%r{\/$}, '')
|
400
|
+
end
|
401
|
+
|
402
|
+
# Returns a suitable repository name for `url`.
|
403
|
+
#
|
404
|
+
# @example A GitHub.com URL
|
405
|
+
#
|
406
|
+
# name_for_url('https://github.com/Artsy/Specs.git')
|
407
|
+
# # "artsy"
|
408
|
+
# name_for_url('https://github.com/Artsy/Specs.git')
|
409
|
+
# # "artsy-1"
|
410
|
+
#
|
411
|
+
# @example A non-Github.com URL
|
412
|
+
#
|
413
|
+
# name_for_url('https://sourceforge.org/Artsy/Specs.git')
|
414
|
+
# # sourceforge-artsy
|
415
|
+
#
|
416
|
+
# @example A file URL
|
417
|
+
#
|
418
|
+
# name_for_url('file:///Artsy/Specs.git')
|
419
|
+
# # artsy
|
420
|
+
#
|
421
|
+
# @param [#to_s] url
|
422
|
+
# The URL of the source.
|
423
|
+
#
|
424
|
+
# @return [String] A suitable repository name for `url`.
|
425
|
+
#
|
426
|
+
def name_for_url(url)
|
427
|
+
base_from_host_and_path = lambda do |host, path|
|
428
|
+
if host && !host.empty?
|
429
|
+
domain = PublicSuffix.parse(host) rescue nil
|
430
|
+
base = [domain&.sld || host]
|
431
|
+
base = [] if base == %w(github)
|
432
|
+
else
|
433
|
+
base = []
|
434
|
+
end
|
435
|
+
|
436
|
+
path = path.gsub(/.git$/, '').gsub(%r{^/}, '').split('/')
|
437
|
+
path.pop if path.last == 'specs'
|
438
|
+
|
439
|
+
(base + path).join('-')
|
440
|
+
end
|
441
|
+
|
442
|
+
valid_url = lambda do |url|
|
443
|
+
url =~ URI.regexp && (URI(url) rescue false)
|
444
|
+
end
|
445
|
+
|
446
|
+
valid_scp_url = lambda do |url|
|
447
|
+
valid_url['scp://' + url]
|
448
|
+
end
|
449
|
+
|
450
|
+
url = url.to_s.downcase
|
451
|
+
|
452
|
+
case url
|
453
|
+
when %r{https://#{Regexp.quote(trunk_repo_hostname)}}i
|
454
|
+
# Main CDN repo
|
455
|
+
base = Pod::TrunkSource::TRUNK_REPO_NAME
|
456
|
+
when valid_url
|
457
|
+
# HTTPS URL or something similar
|
458
|
+
url = valid_url[url]
|
459
|
+
base = base_from_host_and_path[url.host, url.path]
|
460
|
+
when valid_scp_url
|
461
|
+
# SCP-style URLs for private git repos
|
462
|
+
url = valid_scp_url[url]
|
463
|
+
base = base_from_host_and_path[url.host, url.path]
|
464
|
+
when %r{(?:git|ssh|https?|[a-z0-9_-]+@([-\w.]+)):(\/\/)?(.*?)(\.git)?(\/?|\#[-\d\w._]+?)$}i
|
465
|
+
# Additional SCP-style URLs for private git repos
|
466
|
+
host, _, path = Regexp.last_match.captures
|
467
|
+
base = base_from_host_and_path[host, path]
|
468
|
+
else
|
469
|
+
# This is nearly impossible, with all the previous cases
|
470
|
+
raise Informative, "Couldn't determine repo name for URL: #{url}"
|
471
|
+
end
|
472
|
+
|
473
|
+
name = base
|
474
|
+
(1..).each do |i|
|
475
|
+
break unless source_dir(name).exist?
|
476
|
+
name = "#{base}-#{i}"
|
477
|
+
end
|
478
|
+
name
|
479
|
+
end
|
480
|
+
|
481
|
+
# Returns hostname for for `trunk` URL.
|
482
|
+
#
|
483
|
+
def trunk_repo_hostname
|
484
|
+
URI.parse(TrunkSource::TRUNK_REPO_URL).host.downcase.freeze
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
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
|
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.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 [Boolean] 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
|