Class: Pod::Downloader::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/cocoapods/downloader/cache.rb

Overview

The class responsible for managing Pod downloads, transparently caching them in a cache directory.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root) ⇒ Cache

Initialize a new instance

Parameters:

  • root (Pathname, String)

    see #root



20
21
22
23
# File 'lib/cocoapods/downloader/cache.rb', line 20

def initialize(root)
  @root = Pathname(root)
  ensure_matching_version
end

Instance Attribute Details

#rootPathname (readonly)

Returns The root directory where this cache store its downloads.

Returns:

  • (Pathname)

    The root directory where this cache store its downloads.



13
14
15
# File 'lib/cocoapods/downloader/cache.rb', line 13

def root
  @root
end

Class Method Details

.lock(location, lock_type) ⇒ void

This method returns an undefined value.

Creates a .lock file at location, aquires a lock of type lock_type, checks that it is valid, and executes passed block while holding on to that lock. Afterwards, the .lock file is deleted, which is why validation of the lock is necessary, as you might have a lock on a file that doesn't exist on the filesystem anymore.

Parameters:

  • location (Pathname)

    the path to require a lock for.

  • lock_type (locking_constant)

    the type of lock, either exclusive (File::LOCK_EX) or shared (File::LOCK_SH).

Raises:

  • (ArgumentError)


117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/cocoapods/downloader/cache.rb', line 117

def self.lock(location, lock_type)
  raise ArgumentError, 'no block given' unless block_given?
  lockfile = "#{location}.lock"
  f = nil
  loop do
    f.close if f
    f = File.open(lockfile, File::CREAT, 0o644)
    f.flock(lock_type)
    break if Cache.valid_lock?(f, lockfile)
  end
  begin
    yield location
  ensure
    if lock_type == File::LOCK_SH
      f.flock(File::LOCK_EX)
      File.delete(lockfile) if Cache.valid_lock?(f, lockfile)
    else
      File.delete(lockfile)
    end
    f.close
  end
end

.read_lock(location, &block) ⇒ void

This method returns an undefined value.

Convenience method for acquiring a shared lock to safely read from the cache. See Cache.lock for more details.

Parameters:

  • location (Pathname)

    the path to require a lock for.

  • &block (block)

    the block to execute inside the lock.



83
84
85
# File 'lib/cocoapods/downloader/cache.rb', line 83

def self.read_lock(location, &block)
  Cache.lock(location, File::LOCK_SH, &block)
end

.valid_lock?(file, filename) ⇒ Boolean

Checks that the lock is on a file that still exists on the filesystem.

Parameters:

  • file (File)

    the actual file that we have a lock for.

  • filename (String)

    the filename of the file that we have a lock for.

Returns:

  • (Boolean)

    true if filename still exists and is the same file as file



151
152
153
154
155
# File 'lib/cocoapods/downloader/cache.rb', line 151

def self.valid_lock?(file, filename)
  file.stat.ino == File.stat(filename).ino
rescue Errno::ENOENT
  false
end

.write_lock(location, &block) ⇒ void

This method returns an undefined value.

Convenience method for acquiring an exclusive lock to safely write to the cache. See Cache.lock for more details.

Parameters:

  • location (Pathname)

    the path to require a lock for.

  • &block (block)

    the block to execute inside the lock.



98
99
100
# File 'lib/cocoapods/downloader/cache.rb', line 98

def self.write_lock(location, &block)
  Cache.lock(location, File::LOCK_EX, &block)
end

Instance Method Details

#cache_descriptors_per_podHash<String, Hash<Symbol, String>>

Returns A hash whose keys are the pod name And values are a hash with the following keys: :spec_file : path to the spec file :name : name of the pod :version : pod version :release : boolean to tell if that's a release pod :slug : the slug path where the pod cache is located.

Returns:

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

    A hash whose keys are the pod name And values are a hash with the following keys: :spec_file : path to the spec file :name : name of the pod :version : pod version :release : boolean to tell if that's a release pod :slug : the slug path where the pod cache is located



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/cocoapods/downloader/cache.rb', line 50

def cache_descriptors_per_pod
  specs_dir = root + 'Specs'
  release_specs_dir = specs_dir + 'Release'
  return {} unless specs_dir.exist?

  spec_paths = specs_dir.find.select { |f| f.fnmatch('*.podspec.json') }
  spec_paths.reduce({}) do |hash, spec_path|
    spec = Specification.from_file(spec_path)
    hash[spec.name] ||= []
    is_release = spec_path.to_s.start_with?(release_specs_dir.to_s)
    request = Downloader::Request.new(:spec => spec, :released => is_release)
    hash[spec.name] << {
      :spec_file => spec_path,
      :name => spec.name,
      :version => spec.version,
      :release => is_release,
      :slug => root + request.slug,
    }
    hash
  end
end

#cached_pod(request) ⇒ Response (private)

Returns The download response for the given request that was found in the download cache.

Parameters:

  • request (Request)

    the request to be downloaded.

Returns:

  • (Response)

    The download response for the given request that was found in the download cache.



209
210
211
212
213
214
215
216
# File 'lib/cocoapods/downloader/cache.rb', line 209

def cached_pod(request)
  cached_spec = cached_spec(request)
  path = path_for_pod(request)

  return unless cached_spec && path.directory?
  spec = request.spec || cached_spec
  Response.new(path, spec, request.params)
end

#cached_spec(request) ⇒ Specification (private)

Returns The cached specification for the given request.

Parameters:

  • request (Request)

    the request to be downloaded.

Returns:

  • (Specification)

    The cached specification for the given request.



224
225
226
227
228
229
# File 'lib/cocoapods/downloader/cache.rb', line 224

def cached_spec(request)
  path = path_for_spec(request)
  path.file? && Specification.from_file(path)
rescue JSON::ParserError
  nil
end

#copy_and_clean(source, destination, spec) ⇒ Void (private)

Copies the source directory to destination, cleaning the directory of any files unused by spec.

Parameters:

Returns:

  • (Void)


282
283
284
285
286
287
288
289
290
291
# File 'lib/cocoapods/downloader/cache.rb', line 282

def copy_and_clean(source, destination, spec)
  specs_by_platform = group_subspecs_by_platform(spec)
  destination.parent.mkpath
  Cache.write_lock(destination) do
    FileUtils.rm_rf(destination)
    FileUtils.cp_r(source, destination)
    Pod::Installer::PodSourcePreparer.new(spec, destination).prepare!
    Sandbox::PodDirCleaner.new(destination, specs_by_platform).clean!
  end
end

#download(request, target) ⇒ Object (private)



255
256
257
# File 'lib/cocoapods/downloader/cache.rb', line 255

def download(request, target)
  Downloader.download_request(request, target)
end

#download_pod(request) ⇒ Response

Downloads the Pod from the given request

Parameters:

  • request (Request)

    the request to be downloaded.

Returns:

  • (Response)

    the response from downloading request



32
33
34
35
36
37
38
39
# File 'lib/cocoapods/downloader/cache.rb', line 32

def download_pod(request)
  cached_pod(request) || uncached_pod(request)
rescue Informative
  raise
rescue
  UI.puts("\n[!] Error installing #{request.name}".red)
  raise
end

#ensure_matching_versionVoid (private)

Ensures the cache on disk was created with the same CocoaPods version as is currently running.

Returns:

  • (Void)


164
165
166
167
168
169
170
171
172
# File 'lib/cocoapods/downloader/cache.rb', line 164

def ensure_matching_version
  version_file = root + 'VERSION'
  version = version_file.read.strip if version_file.file?

  root.rmtree if version != Pod::VERSION && root.exist?
  root.mkpath

  version_file.open('w') { |f| f << Pod::VERSION }
end

#group_subspecs_by_platform(spec) ⇒ Object (private)



293
294
295
296
297
298
299
300
301
302
# File 'lib/cocoapods/downloader/cache.rb', line 293

def group_subspecs_by_platform(spec)
  specs_by_platform = {}
  [spec, *spec.recursive_subspecs].each do |ss|
    ss.available_platforms.each do |platform|
      specs_by_platform[platform] ||= []
      specs_by_platform[platform] << ss
    end
  end
  specs_by_platform
end

#in_tmpdir(&blk) ⇒ Object (private)

Performs the given block inside a temporary directory, which is removed at the end of the block's scope.

Returns:

  • (Object)

    The return value of the given block



264
265
266
267
268
269
# File 'lib/cocoapods/downloader/cache.rb', line 264

def in_tmpdir(&blk)
  tmpdir = Pathname(Dir.mktmpdir)
  blk.call(tmpdir)
ensure
  FileUtils.remove_entry(tmpdir, :force => true) if tmpdir && tmpdir.exist?
end

#path_for_pod(request, slug_opts = {}) ⇒ Pathname (private)

Returns The path for the Pod downloaded from the given request.

Parameters:

  • request (Request)

    the request to be downloaded.

  • slug_opts (Hash<Symbol,String>) (defaults to: {})

    the download options that should be used in constructing the cache slug for this request.

Returns:

  • (Pathname)

    The path for the Pod downloaded from the given request.



184
185
186
# File 'lib/cocoapods/downloader/cache.rb', line 184

def path_for_pod(request, slug_opts = {})
  root + request.slug(**slug_opts)
end

#path_for_spec(request, slug_opts = {}) ⇒ Pathname (private)

Returns The path for the podspec downloaded from the given request.

Parameters:

  • request (Request)

    the request to be downloaded.

  • slug_opts (Hash<Symbol,String>) (defaults to: {})

    the download options that should be used in constructing the cache slug for this request.

Returns:

  • (Pathname)

    The path for the podspec downloaded from the given request.



198
199
200
201
# File 'lib/cocoapods/downloader/cache.rb', line 198

def path_for_spec(request, slug_opts = {})
  path = root + 'Specs' + request.slug(**slug_opts)
  Pathname.new(path.to_path + '.podspec.json')
end

#uncached_pod(request) ⇒ Response (private)

Returns The download response for the given request that was not found in the download cache.

Parameters:

  • request (Request)

    the request to be downloaded.

Returns:

  • (Response)

    The download response for the given request that was not found in the download cache.



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/cocoapods/downloader/cache.rb', line 237

def uncached_pod(request)
  in_tmpdir do |target|
    result, podspecs = download(request, target)
    result.location = nil

    podspecs.each do |name, spec|
      destination = path_for_pod(request, :name => name, :params => result.checkout_options)
      copy_and_clean(target, destination, spec)
      write_spec(spec, path_for_spec(request, :name => name, :params => result.checkout_options))
      if request.name == name
        result.location = destination
      end
    end

    result
  end
end

#write_spec(spec, path) ⇒ Void (private)

Writes the given spec to the given path.

Parameters:

  • spec (Specification)

    the specification to be written.

  • path (Pathname)

    the path the specification is to be written to.

Returns:

  • (Void)


314
315
316
317
318
319
# File 'lib/cocoapods/downloader/cache.rb', line 314

def write_spec(spec, path)
  path.dirname.mkpath
  Cache.write_lock(path) do
    path.open('w') { |f| f.write spec.to_pretty_json }
  end
end