class Pod::Lockfile

The Lockfile stores information about the pods that were installed by CocoaPods.

It is used in combination with the Podfile to resolve the exact version of the Pods that should be installed (i.e. to prevent ‘pod install` from upgrading dependencies).

Moreover it is used as a manifest of an installation to detect which Pods need to be installed or removed.

Constants

HASH_KEY_ORDER

@return [Array<String>] The order in which the hash keys should appear in

a serialized Lockfile.

Attributes

defined_in_file[RW]

@return [String] the file where the Lockfile is serialized.

internal_data[R]

@return [String] the hash used to initialize the Lockfile.

Public Class Methods

from_file(path) click to toggle source

Loads a lockfile form the given path.

@note This method returns nil if the given path doesn’t exists.

@raise If there is a syntax error loading the YAML data.

@param [Pathname] path

the path where the lockfile is serialized.

@return [Lockfile] a new lockfile.

# File lib/cocoapods-core/lockfile.rb, line 38
def self.from_file(path)
  return nil unless path.exist?
  hash = YAMLHelper.load_file(path)
  unless hash && hash.is_a?(Hash)
    raise Informative, "Invalid Lockfile in `#{path}`"
  end
  lockfile = Lockfile.new(hash)
  lockfile.defined_in_file = path
  lockfile
end
generate(podfile, specs, checkout_options, spec_repos = {}) click to toggle source

Generates a hash representation of the Lockfile generated from a given Podfile and the list of resolved Specifications. This representation is suitable for serialization.

@param [Podfile] podfile

the podfile that should be used to generate the lockfile.

@param [Array<Specification>] specs

an array containing the podspec that were generated by
resolving the given podfile.

@return [Lockfile] a new lockfile.

# File lib/cocoapods-core/lockfile.rb, line 421
def generate(podfile, specs, checkout_options, spec_repos = {})
  hash = {
    'PODS'             => generate_pods_data(specs),
    'DEPENDENCIES'     => generate_dependencies_data(podfile),
    'SPEC REPOS'       => generate_spec_repos(spec_repos),
    'EXTERNAL SOURCES' => generate_external_sources_data(podfile),
    'CHECKOUT OPTIONS' => checkout_options,
    'SPEC CHECKSUMS'   => generate_checksums(specs),
    'PODFILE CHECKSUM' => podfile.checksum,
    'COCOAPODS'        => CORE_VERSION,
  }
  Lockfile.new(hash)
end
new(hash) click to toggle source

@param [Hash] hash

a hash representation of the Lockfile.
# File lib/cocoapods-core/lockfile.rb, line 23
def initialize(hash)
  @internal_data = hash
end

Private Class Methods

generate_checksums(specs) click to toggle source

Generates the relative to the checksum of the specifications.

@example Output

{
  "BananaLib"=>"9906b267592664126923875ce2c8d03824372c79",
  "JSONKit"=>"92ae5f71b77c8dec0cd8d0744adab79d38560949"
}

@return [Hash] a hash where the keys are the names of the root

specifications and the values are the SHA1 digest of the
podspec file.
# File lib/cocoapods-core/lockfile.rb, line 540
def generate_checksums(specs)
  checksums = {}
  specs.select(&:defined_in_file).each do |spec|
    checksums[spec.root.name] = spec.checksum
  end
  checksums
end
generate_dependencies_data(podfile) click to toggle source

Generates the list of the dependencies of the Podfile.

@example Output

[ "BananaLib (~> 1.0)",
  "JSONKit (from `path/JSONKit.podspec')" ]

@return [Array] the generated data.

# File lib/cocoapods-core/lockfile.rb, line 478
def generate_dependencies_data(podfile)
  YAMLHelper.sorted_array(podfile.dependencies.map(&:to_s))
end
generate_external_sources_data(podfile) click to toggle source

Generates the information of the external sources.

@example Output

{ "JSONKit"=>{:podspec=>"path/JSONKit.podspec"} }

@return [Hash] a hash where the keys are the names of the pods and

the values store the external source hashes of each
dependency.
# File lib/cocoapods-core/lockfile.rb, line 520
def generate_external_sources_data(podfile)
  deps = podfile.dependencies.select(&:external?)
  deps = deps.sort { |d, other| d.name <=> other.name }
  sources = {}
  deps.each { |d| sources[d.root_name] = d.external_source }
  sources
end
generate_pods_data(specs) click to toggle source

Generates the list of the installed Pods and their dependencies.

@note The dependencies of iOS and OS X version of the same pod are

merged.

@todo Specifications should be stored per platform, otherwise they

list dependencies which actually might not be used.

@return [Array<Hash,String>] the generated data.

@example Output

[ {"BananaLib (1.0)"=>["monkey (< 1.0.9, ~> 1.0.1)"]},
"monkey (1.0.8)" ]
# File lib/cocoapods-core/lockfile.rb, line 456
def generate_pods_data(specs)
  pods_and_deps_merged = specs.reduce({}) do |result, spec|
    name = spec.to_s
    result[name] ||= []
    result[name].concat(spec.all_dependencies.map(&:to_s))
    result
  end

  pod_and_deps = pods_and_deps_merged.map do |name, deps|
    deps.empty? ? name : { name => YAMLHelper.sorted_array(deps.uniq) }
  end
  YAMLHelper.sorted_array(pod_and_deps)
end
generate_spec_repos(spec_repos) click to toggle source

Generates the hash of spec repo sources used in the Podfile.

@example Output

{ "https://github.com/cocoapods/cocoapods.git" => ["Alamofire", "Moya"] }
# File lib/cocoapods-core/lockfile.rb, line 487
def generate_spec_repos(spec_repos)
  output = Hash.new {|h, k| h[k] = Array.new(0)}
  spec_repos.each do |source, specs|
    next unless source
    next if specs.empty?
    key = source.url || source.name

    # save `trunk` as 'trunk' so that the URL itself can be changed without lockfile churn
    key = Pod::TrunkSource::TRUNK_REPO_NAME if source.name == Pod::TrunkSource::TRUNK_REPO_NAME

    value = specs.map { |s| s.root.name }

    if output.has_key?(key)
      value = value + output[key]
    end

    if value.length > 0
      output[key] = YAMLHelper.sorted_array(value.uniq)
    end
  end

  output.compact
end

Public Instance Methods

==(other) click to toggle source

@return [Boolean] Whether the Podfiles are equal.

# File lib/cocoapods-core/lockfile.rb, line 55
def ==(other)
  other && to_hash == other.to_hash
end
checkout_options_for_pod_named(name) click to toggle source

Returns the specific checkout options for the external source of the pod with the given name.

@example Output

{:commit => "919903db28535c3f387c4bbaa6a3feae4428e993"
 :git => "https://github.com/luisdelarosa/AFRaptureXMLRequestOperation.git"}

@return [Hash] a hash of the checkout options for the external source of

the pod with the given name.

@param [String] name

the name of the Pod.
# File lib/cocoapods-core/lockfile.rb, line 188
def checkout_options_for_pod_named(name)
  checkout_options_data[name]
end
checksum(name) click to toggle source

Returns the checksum for the given Pod.

@param [String] name The name of the Pod (root name of the specification).

@return [String] The checksum of the specification for the given Pod.

@return [Nil] If there is no checksum stored for the given name.

# File lib/cocoapods-core/lockfile.rb, line 115
def checksum(name)
  checksum_data[name]
end
cocoapods_version() click to toggle source

@return [Version] The version of CocoaPods which generated this lockfile.

# File lib/cocoapods-core/lockfile.rb, line 194
def cocoapods_version
  Version.new(internal_data['COCOAPODS'])
end
dependencies() click to toggle source

@return [Array<Dependency>] the dependencies of the Podfile used for the

last installation.

@note It includes only the dependencies explicitly required in the

podfile and not those triggered by the Resolver.
# File lib/cocoapods-core/lockfile.rb, line 124
def dependencies
  unless @dependencies
    data = internal_data['DEPENDENCIES'] || []
    @dependencies = data.map do |string|
      dep = Dependency.from_string(string)
      dep.external_source = external_sources_data[dep.root_name]
      dep
    end
  end
  @dependencies
end
dependencies_to_lock_pod_named(name) click to toggle source

Generates a dependency that requires the exact version of the Pod with the given name.

@param [String] name

the name of the Pod

@note The generated dependencies used are by the Resolver from

upgrading a Pod during an installation.

@raise If there is no version stored for the given name.

@return [Array<Dependency>] the generated dependency.

# File lib/cocoapods-core/lockfile.rb, line 160
def dependencies_to_lock_pod_named(name)
  deps = dependencies.select { |d| d.root_name == name }
  if deps.empty?
    raise StandardError, "Attempt to lock the `#{name}` Pod without a " \
      'known dependency.'
  end

  deps.map do |dep|
    version = version(dep.name)
    locked_dependency = dep.dup
    locked_dependency.specific_version = version
    locked_dependency
  end
end
detect_changes_with_podfile(podfile) click to toggle source

Analyzes the {Lockfile} and detects any changes applied to the {Podfile} since the last installation.

For each Pod, it detects one state among the following:

  • added: Pods that weren’t present in the Podfile.

  • changed: Pods that were present in the Podfile but changed:

    • Pods whose version is not compatible anymore with Podfile,

    • Pods that changed their external options.

  • removed: Pods that were removed form the Podfile.

  • unchanged: Pods that are still compatible with Podfile.

@param [Podfile] podfile

the podfile that should be analyzed.

@return [Hash{Symbol=>Array}] a hash where pods are grouped

by the state in which they are.

@todo Why do we look for compatibility instead of just comparing if the

two dependencies are equal?
# File lib/cocoapods-core/lockfile.rb, line 289
def detect_changes_with_podfile(podfile)
  result = {}
  [:added, :changed, :removed, :unchanged].each { |k| result[k] = [] }

  installed_deps = {}
  dependencies.each do |dep|
    name = dep.root_name
    installed_deps[name] ||= dependencies_to_lock_pod_named(name)
  end

  installed_deps = installed_deps.values.flatten(1).group_by(&:name)

  podfile_dependencies = podfile.dependencies
  podfile_dependencies_by_name = podfile_dependencies.group_by(&:name)

  all_dep_names = (dependencies + podfile_dependencies).map(&:name).uniq
  all_dep_names.each do |name|
    installed_dep   = installed_deps[name]
    installed_dep &&= installed_dep.first
    podfile_dep     = podfile_dependencies_by_name[name]
    podfile_dep   &&= podfile_dep.first

    if installed_dep.nil?  then key = :added
    elsif podfile_dep.nil? then key = :removed
    elsif podfile_dep.compatible?(installed_dep) then key = :unchanged
    else key = :changed
    end
    result[key] << name
  end
  result
end
inspect() click to toggle source

@return [String] a string representation suitable for debugging.

# File lib/cocoapods-core/lockfile.rb, line 61
def inspect
  "#<#{self.class}>"
end
pod_names() click to toggle source

@return [Array<String>] the names of the installed Pods.

# File lib/cocoapods-core/lockfile.rb, line 73
def pod_names
  generate_pod_names_and_versions unless @pod_names
  @pod_names
end
pods_by_spec_repo() click to toggle source

Returns pod names grouped by the spec repo they were sourced from.

@return [Hash<String, Array<String>>] A hash, where the keys are spec

repo source URLs (or names), and the values are arrays of pod names.

@note It does not include pods that come from “external sources”.

# File lib/cocoapods-core/lockfile.rb, line 143
def pods_by_spec_repo
  @pods_by_spec_repo ||= internal_data['SPEC REPOS'] || {}
end
spec_repo(pod_name) click to toggle source

Returns the source of the given Pod.

@param [String] pod_name The name of the Pod (root name of the specification).

@return [String] The source of the pod.

@return [Nil] If there is no source stored for the given name.

# File lib/cocoapods-core/lockfile.rb, line 103
def spec_repo(pod_name)
  spec_repos_by_pod[pod_name]
end
to_hash() click to toggle source

@return [Hash{String=>Array,Hash,String}] a hash representation of the

Lockfile.

@example Output

{
  'PODS'             => [ { BananaLib (1.0) => [monkey (< 1.0.9, ~> 1.0.1)] },
                          "JSONKit (1.4)",
                          "monkey (1.0.8)"]
  'DEPENDENCIES'     => [ "BananaLib (~> 1.0)",
                          "JSONKit (from `path/JSONKit.podspec`)" ],
  'EXTERNAL SOURCES' => { "JSONKit" => { :podspec => path/JSONKit.podspec } },
  'SPEC CHECKSUMS'   => { "BananaLib" => "439d9f683377ecf4a27de43e8cf3bce6be4df97b",
                          "JSONKit", "92ae5f71b77c8dec0cd8d0744adab79d38560949" },
  'PODFILE CHECKSUM' => "439d9f683377ecf4a27de43e8cf3bce6be4df97b",
  'COCOAPODS'        => "0.17.0"
}
# File lib/cocoapods-core/lockfile.rb, line 368
def to_hash
  hash = {}
  internal_data.each do |key, value|
    hash[key] = value unless value.nil? || value.empty?
  end
  hash
end
to_yaml() click to toggle source

@return [String] the YAML representation of the Lockfile, used for

serialization.

@note Empty root keys are discarded.

@note The YAML string is prettified.

# File lib/cocoapods-core/lockfile.rb, line 397
def to_yaml
  YAMLHelper.convert_hash(to_hash, HASH_KEY_ORDER, "\n\n")
end
version(pod_name) click to toggle source

Returns the version of the given Pod.

@param [String] pod_name The name of the Pod (root name of the specification).

@return [Version] The version of the pod.

@return [Nil] If there is no version stored for the given name.

# File lib/cocoapods-core/lockfile.rb, line 86
def version(pod_name)
  version = pod_versions[pod_name]
  return version if version
  root_name = pod_versions.keys.find do |name|
    Specification.root_name(name) == pod_name
  end
  pod_versions[root_name]
end
write_to_disk(path) click to toggle source

Writes the Lockfile to the given path.

@param [Pathname] path

the path where the lockfile should be saved.

@return [void]

# File lib/cocoapods-core/lockfile.rb, line 334
def write_to_disk(path)
  path.dirname.mkpath unless path.dirname.exist?
  self.defined_in_file = path
  # rubocop:disable Lint/RescueException
  # rubocop:disable Lint/HandleExceptions
  begin
    existing = Lockfile.from_file(path)
    return if existing == self
  rescue Exception
  end
  path.open('w') { |f| f.write(to_yaml) }
  # rubocop:enable Lint/HandleExceptions
  # rubocop:enable Lint/RescueException
end

Private Instance Methods

checkout_options_data() click to toggle source

@return [Hash{String => Hash}] a hash where the name of the pods are the

keys and the values are a hash of specific checkout options.
# File lib/cocoapods-core/lockfile.rb, line 231
def checkout_options_data
  @checkout_options_data ||= internal_data['CHECKOUT OPTIONS'] || {}
end
checksum_data() click to toggle source

@return [Hash{String => Version}] A Hash containing the checksums of the

specification by the name of their root.
# File lib/cocoapods-core/lockfile.rb, line 247
def checksum_data
  internal_data['SPEC CHECKSUMS'] || {}
end
external_sources_data() click to toggle source

@return [Hash{String => Hash}] a hash where the name of the pods are the

keys and the values are the external source hash the dependency
that required the pod.
# File lib/cocoapods-core/lockfile.rb, line 224
def external_sources_data
  @external_sources_data ||= internal_data['EXTERNAL SOURCES'] || {}
end
generate_pod_names_and_versions() click to toggle source

@return [Array<String, Hash{String => Array}>] the pods installed

and their dependencies.
# File lib/cocoapods-core/lockfile.rb, line 207
def generate_pod_names_and_versions
  @pod_names    = []
  @pod_versions = {}

  return unless pods = internal_data['PODS']
  pods.each do |pod|
    pod = pod.keys.first unless pod.is_a?(String)
    name, version = Spec.name_and_version_from_string(pod)
    @pod_names << name
    @pod_versions[name] = version
  end
end
pod_versions() click to toggle source

@return [Hash{String => Version}] a Hash containing the name of the root

specification of the installed Pods as the keys and their
corresponding {Version} as the values.
# File lib/cocoapods-core/lockfile.rb, line 239
def pod_versions
  generate_pod_names_and_versions unless @pod_versions
  @pod_versions
end
spec_repos_by_pod() click to toggle source

@return [Hash{String => String}] A hash containing the spec repo used for the specification

by the name of the root spec.
# File lib/cocoapods-core/lockfile.rb, line 254
def spec_repos_by_pod
  @spec_repos_by_pod ||= pods_by_spec_repo.each_with_object({}) do |(spec_repo, pods), spec_repos_by_pod|
    pods.each do |pod|
      spec_repos_by_pod[pod] = spec_repo
    end
  end
end