Class: Pod::Source

Inherits:
Object
  • Object
show all
Defined in:
lib/cocoapods-core/source.rb,
lib/cocoapods-core/source/manager.rb,
lib/cocoapods-core/source/acceptor.rb,
lib/cocoapods-core/source/metadata.rb,
lib/cocoapods-core/source/aggregate.rb,
lib/cocoapods-core/source/health_reporter.rb

Overview

The Source class is responsible to manage a collection of podspecs.

The backing store of the podspecs collection is an implementation detail abstracted from the rest of CocoaPods.

The default implementation uses a git repo as a backing store, where the podspecs are namespaced as:

"#{SPEC_NAME}/#{VERSION}/#{SPEC_NAME}.podspec"

Direct Known Subclasses

CDNSource

Defined Under Namespace

Classes: Acceptor, Aggregate, HealthReporter, Manager, Metadata

Constant Summary collapse

DEFAULT_SPECS_BRANCH =

The default branch in which the specs are stored

'master'.freeze

Paths collapse

Instance Attribute Summary collapse

Paths collapse

Querying the source collapse

Searching the source collapse

Updating the source collapse

Representations collapse

Private Helpers collapse

Instance Method Summary collapse

Constructor Details

#initialize(repo) ⇒ Source

Returns a new instance of Source.

Parameters:

  • repo (Pathname, String)

    @see #repo.


28
29
30
31
32
# File 'lib/cocoapods-core/source.rb', line 28

def initialize(repo)
  @repo = Pathname(repo).expand_path
  @versions_by_name = {}
  
end

Instance Attribute Details

#metadataPod::Source::Metadata (readonly)

Returns The metadata for this source.

Returns:


24
25
26
# File 'lib/cocoapods-core/source.rb', line 24

def 
  @metadata
end

#repoPathname (readonly)

Returns The path where the source is stored.

Returns:

  • (Pathname)

    The path where the source is stored.


88
89
90
# File 'lib/cocoapods-core/source.rb', line 88

def repo
  @repo
end

Instance Method Details

#<=>(other) ⇒ Integer

Note:

Source are compared by the alphabetical order of their name, and this convention should be used in any case where sources need to be disambiguated.

Returns compares a source with another one for sorting purposes.

Returns:

  • (Integer)

    compares a source with another one for sorting purposes.


73
74
75
# File 'lib/cocoapods-core/source.rb', line 73

def <=>(other)
  name <=> other.name
end

#all_specsArray<Specification>

Returns all the specifications contained by the source.

Returns:

  • (Array<Specification>)

    all the specifications contained by the source.


219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/cocoapods-core/source.rb', line 219

def all_specs
  glob = specs_dir.join('*/' * .prefix_lengths.size, '*', '*', '*.podspec{.json,}')
  specs = Pathname.glob(glob).map do |path|
    begin
      Specification.from_file(path)
    rescue
      CoreUI.warn "Skipping `#{path.relative_path_from(repo)}` because the " \
                  'podspec contains errors.'
      next
    end
  end
  specs.compact
end

#diff_until_commit_hash(commit_hash) ⇒ Object (private)


459
460
461
# File 'lib/cocoapods-core/source.rb', line 459

def diff_until_commit_hash(commit_hash)
  repo_git(%W(diff --name-only #{commit_hash}..HEAD)).split("\n")
end

#fuzzy_search(query) ⇒ Set, Nil

Returns the set of the Pod whose name fuzzily matches the given query.

Parameters:

  • query (String)

    The query to search for.

Returns:

  • (Set)

    The name of the Pod.

  • (Nil)

    If no Pod with a suitable name was found.


333
334
335
336
337
338
339
# File 'lib/cocoapods-core/source.rb', line 333

def fuzzy_search(query)
  require 'fuzzy_match'
  pod_name = FuzzyMatch.new(pods).find(query)
  if pod_name
    search(pod_name)
  end
end

#git?Boolean

Returns:

  • (Boolean)

370
371
372
# File 'lib/cocoapods-core/source.rb', line 370

def git?
  repo.join('.git').exist? && !repo_git(%w(rev-parse HEAD)).empty?
end

#git_commit_hashObject (private)


444
445
446
# File 'lib/cocoapods-core/source.rb', line 444

def git_commit_hash
  repo_git(%w(rev-parse HEAD))
end

#git_tracking_branchObject (private)


454
455
456
457
# File 'lib/cocoapods-core/source.rb', line 454

def git_tracking_branch
  path = repo.join('.git', 'cocoapods_branch')
  path.file? ? path.read.strip : DEFAULT_SPECS_BRANCH
end

#indexable?Boolean

Returns:

  • (Boolean)

374
375
376
# File 'lib/cocoapods-core/source.rb', line 374

def indexable?
  true
end

#inspectString

Returns A description suitable for debugging.

Returns:

  • (String)

    A description suitable for debugging.


79
80
81
# File 'lib/cocoapods-core/source.rb', line 79

def inspect
  "#<#{self.class} name:#{name} type:#{type}>"
end

#load_spec_gracefully(name) ⇒ Specification, Nil (private)

Loads the specification for the given Pod gracefully.

Parameters:

  • name (String)

    the name of the Pod.

Returns:

  • (Specification)

    The specification for the last version of the Pod.

  • (Nil)

    If the spec could not be loaded.


430
431
432
433
434
435
436
437
438
# File 'lib/cocoapods-core/source.rb', line 430

def load_spec_gracefully(name)
  versions = versions(name)
  version = versions.sort.last if versions
  specification(name, version) if version
rescue Informative
  Pod::CoreUI.warn "Skipping `#{name}` because the podspec " \
    'contains errors.'
  nil
end

#metadata_pathPathname

Returns The path at which source metadata is stored.

Returns:

  • (Pathname)

    The path at which source metadata is stored.


119
120
121
# File 'lib/cocoapods-core/source.rb', line 119

def 
  repo + 'CocoaPods-version.yml'
end

#nameString Also known as: to_s

Returns The name of the source.

Returns:

  • (String)

    The name of the source.


36
37
38
# File 'lib/cocoapods-core/source.rb', line 36

def name
  repo.basename.to_s
end

#pod_path(name) ⇒ Pathname

Returns The path at which the specs for the given pod are stored.

Parameters:

  • name (String)

    The name of the pod.

Returns:

  • (Pathname)

    The path at which the specs for the given pod are stored.


113
114
115
# File 'lib/cocoapods-core/source.rb', line 113

def pod_path(name)
  specs_dir.join(*.path_fragment(name))
end

#pod_setsArray<Sets>

Returns the sets of all the Pods.

Returns:

  • (Array<Sets>)

    the sets of all the Pods.


246
247
248
# File 'lib/cocoapods-core/source.rb', line 246

def pod_sets
  pods.map { |pod_name| set(pod_name) }
end

#podsArray<String>

Returns the list of the name of all the Pods.

Returns:

  • (Array<String>)

    the list of the name of all the Pods.


131
132
133
134
135
136
137
138
139
140
# File 'lib/cocoapods-core/source.rb', line 131

def pods
  unless specs_dir
    raise Informative, "Unable to find a source named: `#{name}`"
  end
  glob = specs_dir.join('*/' * .prefix_lengths.size, '*')
  Pathname.glob(glob).reduce([]) do |pods, entry|
    pods << entry.basename.to_s if entry.directory?
    pods
  end.sort
end

#pods_for_specification_paths(spec_paths) ⇒ Array<String>

Returns pod names for given array of specification paths.

Parameters:

  • spec_paths (Array<String>)

    Array of file path names for specifications. Path strings should be relative to the source path.

Returns:

  • (Array<String>)

    the list of the name of Pods corresponding to specification paths.


149
150
151
152
153
154
155
156
# File 'lib/cocoapods-core/source.rb', line 149

def pods_for_specification_paths(spec_paths)
  spec_paths.map do |path|
    absolute_path = repo + path
    relative_path = absolute_path.relative_path_from(specs_dir)
    # The first file name returned by 'each_filename' is the pod name
    relative_path.each_filename.first
  end
end

#refresh_metadataObject (private)


440
441
442
# File 'lib/cocoapods-core/source.rb', line 440

def 
  @metadata = Metadata.from_file()
end

#repo_git(args, include_error: false) ⇒ Object (private)


463
464
465
466
467
# File 'lib/cocoapods-core/source.rb', line 463

def repo_git(args, include_error: false)
  command = "env -u GIT_CONFIG git -C \"#{repo}\" " << args.join(' ')
  command << ' 2>&1' if include_error
  (`#{command}` || '').strip
end

#search(query) ⇒ Set

TODO:

Rename to #load_set

Note:

This method is optimized for fast lookups by name, i.e. it does not require iterating through #pod_sets

Returns a set for a given dependency. The set is identified by the name of the dependency and takes into account subspecs.

Returns:

  • (Set)

    a set for a given dependency. The set is identified by the name of the dependency and takes into account subspecs.


263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/cocoapods-core/source.rb', line 263

def search(query)
  unless specs_dir
    raise Informative, "Unable to find a source named: `#{name}`"
  end
  if query.is_a?(Dependency)
    query = query.root_name
  end

  if (versions = @versions_by_name[query]) && !versions.empty?
    set = set(query)
    return set if set.specification_name == query
  end

  found = []
  Pathname.glob(pod_path(query)) do |path|
    next unless Dir.foreach(path).any? { |child| child != '.' && child != '..' }
    found << path.basename.to_s
  end

  if [query] == found
    set = set(query)
    set if set.specification_name == query
  end
end

#search_by_name(query, full_text_search = false) ⇒ Array<Set>

TODO:

Rename to #search

Note:

full text search requires to load the specification for each pod, hence is considerably slower.

Returns The list of the sets that contain the search term.

Parameters:

  • query (String)

    the search term. Can be a regular expression.

  • full_text_search (Bool) (defaults to: false)

    whether the search should be limited to the name of the Pod or should include also the author, the summary, and the description.

Returns:

  • (Array<Set>)

    The list of the sets that contain the search term.


302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/cocoapods-core/source.rb', line 302

def search_by_name(query, full_text_search = false)
  regexp_query = /#{query}/i
  if full_text_search
    pod_sets.reject do |set|
      texts = []
      begin
        s = set.specification
        texts << s.name
        texts += s.authors.keys
        texts << s.summary
        texts << s.description
      rescue
        CoreUI.warn "Skipping `#{set.name}` because the podspec " \
          'contains errors.'
      end
      texts.grep(regexp_query).empty?
    end
  else
    names = pods.grep(regexp_query)
    names.map { |pod_name| set(pod_name) }
  end
end

#set(pod_name) ⇒ Sets

Returns the set for the Pod with the given name.

Parameters:

  • pod_name (String)

    The name of the Pod.

Returns:

  • (Sets)

    the set.


240
241
242
# File 'lib/cocoapods-core/source.rb', line 240

def set(pod_name)
  Specification::Set.new(pod_name, self)
end

#specification(name, version) ⇒ Specification

Returns the specification for a given version of Pod.

Parameters:

  • @see

    specification_path

Returns:

  • (Specification)

    the specification for a given version of Pod.


187
188
189
# File 'lib/cocoapods-core/source.rb', line 187

def specification(name, version)
  Specification.from_file(specification_path(name, version))
end

#specification_path(name, version) ⇒ Pathname

Returns the path of the specification with the given name and version.

Parameters:

  • name (String)

    the name of the Pod.

  • version (Version, String)

    the version for the specification.

Returns:

  • (Pathname)

    The path of the specification.

Raises:

  • (ArgumentError)

201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/cocoapods-core/source.rb', line 201

def specification_path(name, version)
  raise ArgumentError, 'No name' unless name
  raise ArgumentError, 'No version' unless version
  path = pod_path(name) + version.to_s
  specification_path = path + "#{name}.podspec.json"
  unless specification_path.exist?
    specification_path = path + "#{name}.podspec"
  end
  unless specification_path.exist?
    raise StandardError, "Unable to find the specification #{name} " \
      "(#{version}) in the #{self.name} source."
  end
  specification_path
end

#specs_dirPathname

Note:

In previous versions of CocoaPods they used to be stored in the root of the repo. This lead to issues, especially with the GitHub interface and now they are stored in a dedicated folder.

Returns The directory where the specs are stored.

Returns:

  • (Pathname)

    The directory where the specs are stored.


97
98
99
100
101
102
103
104
105
106
# File 'lib/cocoapods-core/source.rb', line 97

def specs_dir
  @specs_dir ||= begin
    specs_sub_dir = repo + 'Specs'
    if specs_sub_dir.exist?
      specs_sub_dir
    elsif repo.exist?
      repo
    end
  end
end

#to_hashHash{String=>{String=>Specification}}

Returns the static representation of all the specifications grouped first by name and then by version.

Returns:

  • (Hash{String=>{String=>Specification}})

    the static representation of all the specifications grouped first by name and then by version.


400
401
402
403
404
405
406
407
# File 'lib/cocoapods-core/source.rb', line 400

def to_hash
  hash = {}
  all_specs.each do |spec|
    hash[spec.name] ||= {}
    hash[spec.name][spec.version.version] = spec.to_hash
  end
  hash
end

#to_yamlString

Returns the YAML encoded #to_hash representation.

Returns:

  • (String)

    the YAML encoded #to_hash representation.


411
412
413
414
# File 'lib/cocoapods-core/source.rb', line 411

def to_yaml
  require 'yaml'
  to_hash.to_yaml
end

#typeString

Returns The type of the source.

Returns:

  • (String)

    The type of the source.


60
61
62
# File 'lib/cocoapods-core/source.rb', line 60

def type
  git? ? 'git' : 'file system'
end

#unchanged_github_repo?Boolean (private)

Returns:

  • (Boolean)

469
470
471
472
# File 'lib/cocoapods-core/source.rb', line 469

def unchanged_github_repo?
  return unless url =~ /github.com/
  !GitHub.modified_since_commit(url, git_commit_hash)
end

#update(show_output) ⇒ Array<String>

Updates the local clone of the source repo.

Parameters:

  • show_output (Bool)

Returns:

  • (Array<String>)

    changed_spec_paths Returns the list of changed spec paths.


351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/cocoapods-core/source.rb', line 351

def update(show_output)
  return [] if unchanged_github_repo?
  prev_commit_hash = git_commit_hash
  update_git_repo(show_output)
  @versions_by_name.clear
  
  if version = .last_compatible_version(Version.new(CORE_VERSION))
    tag = "v#{version}"
    CoreUI.warn "Using the `#{tag}` tag of the `#{name}` source because " \
      "it is the last version compatible with CocoaPods #{CORE_VERSION}."
    repo_git(['checkout', tag])
  end
  diff_until_commit_hash(prev_commit_hash)
end

#update_git_repo(show_output = false) ⇒ Object (private)


448
449
450
451
452
# File 'lib/cocoapods-core/source.rb', line 448

def update_git_repo(show_output = false)
  repo_git(['checkout', git_tracking_branch])
  output = repo_git(%w(pull --ff-only), :include_error => true)
  CoreUI.puts output if show_output
end

#updateable?Boolean

Returns:

  • (Boolean)

366
367
368
# File 'lib/cocoapods-core/source.rb', line 366

def updateable?
  git?
end

#urlString

Note:

In the past we had used git ls-remote --get-url, but this could lead to an issue when finding a source based on its URL when git is configured to rewrite URLs with the url.<base>.insteadOf option. See https://github.com/CocoaPods/CocoaPods/issues/2724.

Returns The URL of the source.

Returns:

  • (String)

    The URL of the source.


47
48
49
50
51
52
53
54
55
56
# File 'lib/cocoapods-core/source.rb', line 47

def url
  @url ||= begin
    remote = repo_git(%w(config --get remote.origin.url))
    if !remote.empty?
      remote
    elsif (repo + '.git').exist?
      "file://#{repo}/.git"
    end
  end
end

#verify_compatibility!Object

Raises:


378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/cocoapods-core/source.rb', line 378

def verify_compatibility!
  return if .compatible?(CORE_VERSION)

  version_msg = if .minimum_cocoapods_version == .maximum_cocoapods_version
                  .minimum_cocoapods_version
                else
                  "#{.minimum_cocoapods_version} - #{.maximum_cocoapods_version}"
                end
  raise Informative, "The `#{name}` repo requires " \
    "CocoaPods #{version_msg} (currently using #{CORE_VERSION})\n" \
    'Update CocoaPods, or checkout the appropriate tag in the repo.'
end

#versions(name) ⇒ Array<Version>

Returns all the available versions for the Pod, sorted from highest to lowest.

Parameters:

  • name (String)

    the name of the Pod.

Returns:

  • (Array<Version>)

    all the available versions for the Pod, sorted from highest to lowest.

Raises:

  • (ArgumentError)

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/cocoapods-core/source.rb', line 164

def versions(name)
  return nil unless specs_dir
  raise ArgumentError, 'No name' unless name
  pod_dir = pod_path(name)
  return unless pod_dir.exist?
  @versions_by_name[name] ||= pod_dir.children.map do |v|
    next nil unless v.directory?
    basename = v.basename.to_s
    next unless basename[0, 1] != '.'
    begin
      Version.new(basename)
    rescue ArgumentError
      raise Informative, 'An unexpected version directory ' \
       "`#{basename}` was encountered for the " \
       "`#{pod_dir}` Pod in the `#{name}` repository."
    end
  end.compact.sort.reverse
end