Class: Pod::Lockfile

Inherits:
Object
  • Object
show all
Defined in:
lib/cocoapods-core/lockfile.rb

Overview

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.

Constant Summary collapse

HASH_KEY_ORDER =

Returns The order in which the hash keys should appear in a serialized Lockfile.

Returns:

  • (Array<String>)

    The order in which the hash keys should appear in a serialized Lockfile.

[
  'PODS',
  'DEPENDENCIES',
  'SPEC REPOS',
  'EXTERNAL SOURCES',
  'CHECKOUT OPTIONS',
  'SPEC CHECKSUMS',
  'PODFILE CHECKSUM',
  'COCOAPODS',
].map(&:freeze).freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hash) ⇒ Lockfile

Returns a new instance of Lockfile.

Parameters:

  • hash (Hash)

    a hash representation of the Lockfile.



23
24
25
# File 'lib/cocoapods-core/lockfile.rb', line 23

def initialize(hash)
  @internal_data = hash
end

Instance Attribute Details

#defined_in_fileString

Returns the file where the Lockfile is serialized.

Returns:

  • (String)

    the file where the Lockfile is serialized.



51
52
53
# File 'lib/cocoapods-core/lockfile.rb', line 51

def defined_in_file
  @defined_in_file
end

#internal_dataString (readonly)

Returns the hash used to initialize the Lockfile.

Returns:

  • (String)

    the hash used to initialize the Lockfile.



18
19
20
# File 'lib/cocoapods-core/lockfile.rb', line 18

def internal_data
  @internal_data
end

Class Method Details

.from_file(path) ⇒ Lockfile

Note:

This method returns nil if the given path doesn't exists.

Loads a lockfile form the given path.

Parameters:

  • path (Pathname)

    the path where the lockfile is serialized.

Returns:

Raises:

  • If there is a syntax error loading the YAML data.



38
39
40
41
42
43
44
45
46
47
# 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 = {}) ⇒ Lockfile

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

Parameters:

  • podfile (Podfile)

    the podfile that should be used to generate the lockfile.

  • specs (Array<Specification>)

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

Returns:



421
422
423
424
425
426
427
428
429
430
431
432
433
# 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

.generate_checksums(specs) ⇒ Hash (private)

Generates the relative to the checksum of the specifications.

Examples:

Output

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

Returns:

  • (Hash)

    a hash where the keys are the names of the root specifications and the values are the SHA1 digest of the podspec file.



540
541
542
543
544
545
546
# 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) ⇒ Array (private)

Generates the list of the dependencies of the Podfile.

Examples:

Output

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

Returns:

  • (Array)

    the generated data.



478
479
480
# 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) ⇒ Hash (private)

Generates the information of the external sources.

Examples:

Output

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

Returns:

  • (Hash)

    a hash where the keys are the names of the pods and the values store the external source hashes of each dependency.



520
521
522
523
524
525
526
# 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) ⇒ Array<Hash,String> (private)

TODO:

Specifications should be stored per platform, otherwise they list dependencies which actually might not be used.

Note:

The dependencies of iOS and OS X version of the same pod are merged.

Generates the list of the installed Pods and their dependencies.

Examples:

Output

[ {"BananaLib (1.0)"=>["monkey (< 1.0.9, ~> 1.0.1)"]},
"monkey (1.0.8)" ]

Returns:

  • (Array<Hash,String>)

    the generated data.



456
457
458
459
460
461
462
463
464
465
466
467
468
# 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) ⇒ Object (private)

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

Examples:

Output

{ "https://github.com/cocoapods/cocoapods.git" => ["Alamofire", "Moya"] }


487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
# 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

Instance Method Details

#==(other) ⇒ Boolean

Returns Whether the Podfiles are equal.

Returns:

  • (Boolean)

    Whether the Podfiles are equal.



55
56
57
# File 'lib/cocoapods-core/lockfile.rb', line 55

def ==(other)
  other && to_hash == other.to_hash
end

#checkout_options_dataHash{String => Hash} (private)

Returns a hash where the name of the pods are the keys and the values are a hash of specific checkout options.

Returns:

  • (Hash{String => Hash})

    a hash where the name of the pods are the keys and the values are a hash of specific checkout options.



231
232
233
# File 'lib/cocoapods-core/lockfile.rb', line 231

def checkout_options_data
  @checkout_options_data ||= internal_data['CHECKOUT OPTIONS'] || {}
end

#checkout_options_for_pod_named(name) ⇒ Hash

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

Examples:

Output

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

Parameters:

  • name (String)

    the name of the Pod.

Returns:

  • (Hash)

    a hash of the checkout options for the external source of the pod with the given name.



188
189
190
# File 'lib/cocoapods-core/lockfile.rb', line 188

def checkout_options_for_pod_named(name)
  checkout_options_data[name]
end

#checksum(name) ⇒ String, Nil

Returns the checksum for the given Pod.

Parameters:

  • name (String)

    The name of the Pod (root name of the specification).

Returns:

  • (String)

    The checksum of the specification for the given Pod.

  • (Nil)

    If there is no checksum stored for the given name.



115
116
117
# File 'lib/cocoapods-core/lockfile.rb', line 115

def checksum(name)
  checksum_data[name]
end

#checksum_dataHash{String => Version} (private)

Returns A Hash containing the checksums of the specification by the name of their root.

Returns:

  • (Hash{String => Version})

    A Hash containing the checksums of the specification by the name of their root.



247
248
249
# File 'lib/cocoapods-core/lockfile.rb', line 247

def checksum_data
  internal_data['SPEC CHECKSUMS'] || {}
end

#cocoapods_versionVersion

Returns The version of CocoaPods which generated this lockfile.

Returns:

  • (Version)

    The version of CocoaPods which generated this lockfile.



194
195
196
# File 'lib/cocoapods-core/lockfile.rb', line 194

def cocoapods_version
  Version.new(internal_data['COCOAPODS'])
end

#dependenciesArray<Dependency>

Note:

It includes only the dependencies explicitly required in the podfile and not those triggered by the Resolver.

Returns the dependencies of the Podfile used for the last installation.

Returns:

  • (Array<Dependency>)

    the dependencies of the Podfile used for the last installation.



124
125
126
127
128
129
130
131
132
133
134
# 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) ⇒ Array<Dependency>

Note:

The generated dependencies used are by the Resolver from upgrading a Pod during an installation.

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

Parameters:

  • name (String)

    the name of the Pod

Returns:

  • (Array<Dependency>)

    the generated dependency.

Raises:

  • If there is no version stored for the given name.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
# 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) ⇒ Hash{Symbol=>Array[Strings]}

TODO:

Why do we look for compatibility instead of just comparing if the two dependencies are equal?

Analyzes the Pod::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.

Parameters:

  • podfile (Podfile)

    the podfile that should be analyzed.

Returns:

  • (Hash{Symbol=>Array[Strings]})

    a hash where pods are grouped by the state in which they are.



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# 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

#external_sources_dataHash{String => Hash} (private)

Returns 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.

Returns:

  • (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.



224
225
226
# File 'lib/cocoapods-core/lockfile.rb', line 224

def external_sources_data
  @external_sources_data ||= internal_data['EXTERNAL SOURCES'] || {}
end

#generate_pod_names_and_versionsArray<String, Hash{String => Array[String]}> (private)

Returns the pods installed and their dependencies.

Returns:

  • (Array<String, Hash{String => Array[String]}>)

    the pods installed and their dependencies.



207
208
209
210
211
212
213
214
215
216
217
218
# 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

#inspectString

Returns a string representation suitable for debugging.

Returns:

  • (String)

    a string representation suitable for debugging.



61
62
63
# File 'lib/cocoapods-core/lockfile.rb', line 61

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

#pod_namesArray<String>

Returns the names of the installed Pods.

Returns:

  • (Array<String>)

    the names of the installed Pods.



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

def pod_names
  generate_pod_names_and_versions unless @pod_names
  @pod_names
end

#pod_versionsHash{String => Version} (private)

Returns a Hash containing the name of the root specification of the installed Pods as the keys and their corresponding Version as the values.

Returns:

  • (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.



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

def pod_versions
  generate_pod_names_and_versions unless @pod_versions
  @pod_versions
end

#pods_by_spec_repoHash<String, Array<String>>

Note:

It does not include pods that come from "external sources".

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

Returns:

  • (Hash<String, Array<String>>)

    A hash, where the keys are spec repo source URLs (or names), and the values are arrays of pod names.



143
144
145
# 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) ⇒ String, Nil

Returns the source of the given Pod.

Parameters:

  • pod_name (String)

    The name of the Pod (root name of the specification).

Returns:

  • (String)

    The source of the pod.

  • (Nil)

    If there is no source stored for the given name.



103
104
105
# File 'lib/cocoapods-core/lockfile.rb', line 103

def spec_repo(pod_name)
  spec_repos_by_pod[pod_name]
end

#spec_repos_by_podHash{String => String} (private)

Returns A hash containing the spec repo used for the specification by the name of the root spec.

Returns:

  • (Hash{String => String})

    A hash containing the spec repo used for the specification by the name of the root spec.



254
255
256
257
258
259
260
# 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

#to_hashHash{String=>Array,Hash,String}

Returns a hash representation of the Lockfile.

Examples:

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"
}

Returns:

  • (Hash{String=>Array,Hash,String})

    a hash representation of the Lockfile.



368
369
370
371
372
373
374
# 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_yamlString

Note:

Empty root keys are discarded.

Note:

The YAML string is prettified.

Returns the YAML representation of the Lockfile, used for serialization.

Returns:

  • (String)

    the YAML representation of the Lockfile, used for serialization.



397
398
399
# 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) ⇒ Version, Nil

Returns the version of the given Pod.

Parameters:

  • pod_name (String)

    The name of the Pod (root name of the specification).

Returns:

  • (Version)

    The version of the pod.

  • (Nil)

    If there is no version stored for the given name.



86
87
88
89
90
91
92
93
# 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) ⇒ void

This method returns an undefined value.

Writes the Lockfile to the given path.

Parameters:

  • path (Pathname)

    the path where the lockfile should be saved.



334
335
336
337
338
339
340
341
342
343
344
345
346
347
# 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