Commit 3cb4781b authored by Samuel E. Giddins's avatar Samuel E. Giddins

Merge pull request #3236 from CocoaPods/seg-cache

Add Pod download caching
parents 0f562c02 afc06710
......@@ -4,6 +4,9 @@ inherit_from:
#- CocoaPods ------------------------------------------------------------------
ParameterLists:
CountKeywordArgs: false
AllCops:
Exclude:
- spec/fixtures/**/*.rb
......
......@@ -22,6 +22,12 @@ To install release candidates run `[sudo] gem install cocoapods --pre`
[Samuel Giddins](https://github.com/segiddins)
[#1241](https://github.com/CocoaPods/CocoaPods/issues/1241)
* Add download caching for pods, which speeds up `pod install` and linting,
potentially by several oders of magnitude.
[Samuel Giddins](https://github.com/segiddins)
[#2863](https://github.com/CocoaPods/CocoaPods/issues/2863)
[#3172](https://github.com/CocoaPods/CocoaPods/issues/3172)
##### Bug Fixes
* Do not pass code-sign arguments to xcodebuild when linting OS X targets.
......
......@@ -15,6 +15,7 @@ module Pod
:verbose => false,
:silent => false,
:skip_repo_update => false,
:skip_download_cache => !ENV['COCOAPODS_SKIP_CACHE'].nil?,
:clean => true,
:integrate_targets => true,
......@@ -66,6 +67,11 @@ module Pod
attr_accessor :skip_repo_update
alias_method :skip_repo_update?, :skip_repo_update
# @return [Bool] Whether the installer should skip the download cache.
#
attr_accessor :skip_download_cache
alias_method :skip_download_cache?, :skip_download_cache
public
#-------------------------------------------------------------------------#
......
require 'cocoapods-downloader'
require 'claide/informative_error'
require 'fileutils'
require 'tmpdir'
module Pod
module Downloader
require 'cocoapods/downloader/cache'
require 'cocoapods/downloader/request'
require 'cocoapods/downloader/response'
# Downloads a pod from the given `request` to the given `target` location.
#
# @return [Response] The download response for this download.
#
# @param [Request] request
# the request that describes this pod download.
#
# @param [Pathname,Nil] target
# the location to which this pod should be downloaded. If `nil`,
# then the pod will only be cached.
#
# @param [Pathname,Nil] cache_path
# the path used to cache pod downloads. If `nil`, then no caching
# will be done.
#
def self.download(
request,
target,
cache_path: !Config.instance.skip_download_cache && Config.instance.cache_root + 'Pods'
)
cache_path, tmp_cache = Pathname(Dir.mktmpdir), true unless cache_path
cache = Cache.new(cache_path)
result = cache.download_pod(request)
if target
UI.message "Copying #{request.name} from `#{result.location}` to #{UI.path target}", '> ' do
FileUtils.rm_rf target
FileUtils.cp_r(result.location, target)
end
end
result
ensure
FileUtils.rm_r cache_path if tmp_cache
end
class DownloaderError; include CLAide::InformativeError; end
class Base
......
require 'fileutils'
require 'tmpdir'
module Pod
module Downloader
# The class responsible for managing Pod downloads, transparently caching
# them in a cache directory.
#
class Cache
# @return [Pathname] The root directory where this cache store its
# downloads.
#
attr_reader :root
# @param [Pathname,String] root
# see {#root}
#
def initialize(root)
@root = Pathname(root)
@root.mkpath
end
# Downloads the Pod from the given `request`
#
# @param [Request] request
# the request to be downloaded.
#
# @return [Response] the response from downloading `request`
#
def download_pod(request)
cached_pod(request) || uncached_pod(request)
rescue Informative
raise
rescue
UI.notice("Error installing #{request.name}")
raise
end
private
# @param [Request] request
# the request to be downloaded.
#
# @param [Hash<Symbol,String>] slug_opts
# the download options that should be used in constructing the
# cache slug for this request.
#
# @return [Pathname] The path for the Pod downloaded from the given
# `request`.
#
def path_for_pod(request, slug_opts = {})
root + request.slug(slug_opts)
end
# @param [Request] request
# the request to be downloaded.
#
# @param [Hash<Symbol,String>] slug_opts
# the download options that should be used in constructing the
# cache slug for this request.
#
# @return [Pathname] The path for the podspec downloaded from the given
# `request`.
#
def path_for_spec(request, slug_opts = {})
path = root + 'Specs' + request.slug(slug_opts)
path.sub_ext('.podspec.json')
end
# @param [Request] request
# the request to be downloaded.
#
# @return [Response] The download response for the given `request` that
# was found in the download cache.
#
def cached_pod(request)
path = path_for_pod(request)
spec = request.spec || cached_spec(request)
return unless spec && path.directory?
Response.new(path, spec, request.params)
end
# @param [Request] request
# the request to be downloaded.
#
# @return [Specification] The cached specification for the given
# `request`.
#
def cached_spec(request)
path = path_for_spec(request)
path.file? && Specification.from_file(path)
end
# @param [Request] request
# the request to be downloaded.
#
# @return [Response] The download response for the given `request` that
# was not found in the download cache.
#
def uncached_pod(request)
in_tmpdir do |target|
result = Response.new
result.checkout_options = download(request.name, target, request.params, request.head?)
if request.released_pod?
result.spec = request.spec
result.location = destination = path_for_pod(request, :params => result.checkout_options)
copy_and_clean(target, destination, request.spec)
write_spec(request.spec, path_for_spec(request, :params => result.checkout_options))
else
podspecs = Sandbox::PodspecFinder.new(target).podspecs
podspecs[request.name] = request.spec if request.spec
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
result.spec = spec
end
end
end
result
end
end
# Downloads a pod with the given `name` and `params` to `target`.
#
# @param [String] name
#
# @param [Pathname] target
#
# @param [Hash<Symbol,String>] params
#
# @param [Boolean] head
#
# @return [Hash] The checkout options required to re-download this exact
# same source.
#
def download(name, target, params, head)
downloader = Downloader.for_target(target, params)
if head
unless downloader.head_supported?
raise Informative, "The pod '#{name}' does not " \
"support the :head option, as it uses a #{downloader.name} " \
'source. Remove that option to use this pod.'
end
downloader.download_head
else
downloader.download
end
if downloader.options_specific? && !head
params
else
downloader.checkout_options
end
end
# Performs the given block inside a temporary directory,
# which is removed at the end of the block's scope.
#
# @return [Object] The return value of the given block
#
def in_tmpdir(&blk)
tmpdir = Pathname(Dir.mktmpdir)
blk.call(tmpdir)
ensure
FileUtils.remove_entry(tmpdir) if tmpdir.exist?
end
# Copies the `source` directory to `destination`, cleaning the directory
# of any files unused by `spec`.
#
# @param [Pathname] source
#
# @param [Pathname] destination
#
# @param [Specification] spec
#
# @return [Void]
#
def copy_and_clean(source, destination, spec)
specs_by_platform = {}
spec.available_platforms.each do |platform|
specs_by_platform[platform] = [spec, *spec.recursive_subspecs].select { |ss| ss.supported_on_platform?(platform) }
end
destination.parent.mkpath
FileUtils.cp_r(source, destination)
Sandbox::PodDirCleaner.new(destination, specs_by_platform).clean!
end
# Writes the given `spec` to the given `path`.
#
# @param [Specification] spec
# the specification to be written.
#
# @param [Pathname] path
# the path the specification is to be written to.
#
# @return [Void]
#
def write_spec(spec, path)
path.dirname.mkpath
path.open('w') { |f| f.write spec.to_pretty_json }
end
end
end
end
require 'digest'
module Pod
module Downloader
# This class represents a download request for a given Pod.
#
class Request
# @return [Specification,Nil] The specification for the pod whose download
# is being requested.
#
attr_reader :spec
# @return [Boolean] Whether this download request is for a released pod.
#
attr_reader :released_pod
alias_method :released_pod?, :released_pod
# @return [String] The name of the pod whose dowload is being requested.
#
attr_reader :name
# @return [Hash<Symbol, String>] The download parameters for this request.
#
attr_reader :params
# @return [Boolean] Whether the download request is for a head download.
#
attr_reader :head
alias_method :head?, :head
# @param [Specification,Nil] spec
# see {#spec}
#
# @param [Boolean] released
# see {#released_pod}
#
# @param [String,Nil] name
# see {#name}
#
# @param [Hash<Symbol,String>,Nil] params
# see {#params}
#
# @param [Boolean] head
# see {#head}
#
def initialize(spec: nil, released: false, name: nil, params: false, head: false)
@released_pod = released
@spec = spec
@params = spec ? (spec.source && spec.source.dup) : params
@name = spec ? spec.name : name
@head = head
validate!
end
# @param [String] name
# the name of the pod being downloaded.
#
# @param [Hash<#to_s, #to_s>] params
# the download parameters of the pod being downloaded.
#
# @return [String] The slug used to store the files resulting from this
# download request.
#
def slug(name: self.name, params: self.params, spec: self.spec)
checksum = spec && spec.checksum && '-' << spec.checksum[0, 5]
if released_pod?
"Release/#{name}/#{spec.version}#{checksum}"
else
opts = params.to_a.sort_by(&:first).map { |k, v| "#{k}=#{v}" }.join('-')
digest = Digest::MD5.hexdigest(opts)
"External/#{name}/#{digest}#{checksum}"
end
end
private
# Validates that the given request is well-formed.
#
# @return [Void]
#
def validate!
raise ArgumentError, 'Requires a name' unless name
raise ArgumentError, 'Must give a spec for a released download request' if released_pod? && !spec
raise ArgumentError, 'Requires a version if released' if released_pod? && !spec.version
raise ArgumentError, 'Requires params' unless params
end
end
end
end
module Pod
module Downloader
# A response to a download request.
#
# @attr [Pathname] location
# the location where this downloaded pod is stored on disk.
#
# @attr [Specification] spec
# the specification that describes this downloaded pod.
#
# @attr [Hash<Symbol, String>] checkout_options
# the downloader parameters necessary to recreate this exact download.
#
Response = Struct.new(:location, :spec, :checkout_options)
end
end
......@@ -98,26 +98,22 @@ module Pod
title = "Pre-downloading: `#{name}` #{description}"
UI.titled_section(title, :verbose_prefix => '-> ') do
target = sandbox.pod_dir(name)
target.rmtree if target.exist?
downloader = Downloader.for_target(target, params)
downloader.download
podspec_path = target + "#{name}.podspec"
json = false
unless Pathname(podspec_path).exist?
podspec_path = target + "#{name}.podspec.json"
json = true
end
download_result = Downloader.download(download_request, target)
spec = download_result.spec
raise Informative, "Unable to find a specification for '#{name}'." unless spec
store_podspec(sandbox, target + podspec_path, json)
store_podspec(sandbox, spec)
sandbox.store_pre_downloaded_pod(name)
if downloader.options_specific?
source = params
else
source = downloader.checkout_options
sandbox.store_checkout_source(name, download_result.checkout_options)
end
sandbox.store_checkout_source(name, source)
end
def download_request
Downloader::Request.new(
:name => name,
:params => params,
)
end
# Stores the podspec in the sandbox and marks it as from an external
......@@ -126,7 +122,7 @@ module Pod
# @param [Sandbox] sandbox
# The sandbox where the specification should be stored.
#
# @param [Pathname, String] spec
# @param [Pathname, String, Specification] spec
# The path of the specification or its contents.
#
# @note All the concrete implementations of #{fetch} should invoke this
......@@ -144,6 +140,9 @@ module Pod
elsif spec.is_a?(String) && !json
spec = Specification.from_string(spec, 'spec.podspec').to_pretty_json
json = true
elsif spec.is_a?(Specification)
spec = spec.to_pretty_json
json = true
end
sandbox.store_podspec(name, spec, true, json)
end
......
......@@ -3,8 +3,6 @@ module Pod
# Provides support for fetching a specification file from a path local to
# the machine running the installation.
#
# Works with the {LocalPod::LocalSourcedPod} class.
#
class PathSource < AbstractExternalSource
# @see AbstractExternalSource#fetch
#
......
......@@ -44,12 +44,6 @@ module Pod
def install!
download_source unless predownloaded? || local?
run_prepare_command
rescue Informative
raise
rescue Object
UI.notice("Error installing #{root_spec.name}")
clean!
raise
end
# Cleans the installations if appropriate.
......@@ -80,32 +74,21 @@ module Pod
# @return [void]
#
def download_source
root.rmtree if root.exist?
if head_pod?
begin
downloader.download_head
@specific_source = downloader.checkout_options
rescue RuntimeError => e
if e.message == 'Abstract method'
raise Informative, "The pod '" + root_spec.name + "' does not " \
'support the :head option, as it uses a ' + downloader.name +
' source. Remove that option to use this pod.'
else
raise
end
end
else
downloader.download
unless downloader.options_specific?
@specific_source = downloader.checkout_options
end
end
download_result = Downloader.download(download_request, root)
if specific_source
if (@specific_source = download_result.checkout_options) && specific_source != root_spec.source
sandbox.store_checkout_source(root_spec.name, specific_source)
end
end
def download_request
Downloader::Request.new(
:spec => root_spec,
:released => released?,
:head => head_pod?,
)
end
extend Executable
executable :bash
......@@ -135,20 +118,8 @@ module Pod
# @return [void]
#
def clean_installation
clean_paths.each { |path| FileUtils.rm_rf(path) } if root.exist?
end
#-----------------------------------------------------------------------#
public
# @!group Dependencies
# @return [Downloader] The downloader to use for the retrieving the
# source.
#
def downloader
@downloader ||= Downloader.for_target(root, root_spec.source.dup)
cleaner = Sandbox::PodDirCleaner.new(root, specs_by_platform)
cleaner.clean!
end
#-----------------------------------------------------------------------#
......@@ -194,76 +165,8 @@ module Pod
sandbox.head_pod?(root_spec.name)
end
#-----------------------------------------------------------------------#
private
# @!group Private helpers
# @return [Array<Sandbox::FileAccessor>] the file accessors for all the
# specifications on their respective platform.
#
def file_accessors
return @file_accessors if @file_accessors
@file_accessors = []
specs_by_platform.each do |platform, specs|
specs.each do |spec|
@file_accessors << Sandbox::FileAccessor.new(path_list, spec.consumer(platform))
end
end
@file_accessors
end
# @return [Sandbox::PathList] The path list for this Pod.
#
def path_list
@path_list ||= Sandbox::PathList.new(root)
end
# Finds the absolute paths, including hidden ones, of the files
# that are not used by the pod and thus can be safely deleted.
#
# @note Implementation detail: Don't use `Dir#glob` as there is an
# unexplained issue (#568, #572 and #602).
#
# @todo The paths are down-cased for the comparison as issues similar
# to #602 lead the files not being matched and so cleaning all
# the files. This solution might create side effects.
#
# @return [Array<Strings>] The paths that can be deleted.
#
def clean_paths
cached_used = used_files
glob_options = File::FNM_DOTMATCH | File::FNM_CASEFOLD
files = Pathname.glob(root + '**/*', glob_options).map(&:to_s)
files.reject! do |candidate|
candidate = candidate.downcase
candidate.end_with?('.', '..') || cached_used.any? do |path|
path = path.downcase
path.include?(candidate) || candidate.include?(path)
end
end
files
end
# @return [Array<String>] The absolute path of all the files used by the
# specifications (according to their platform) of this Pod.
#
def used_files
files = [
file_accessors.map(&:vendored_frameworks),
file_accessors.map(&:vendored_libraries),
file_accessors.map(&:resource_bundle_files),
file_accessors.map(&:license),
file_accessors.map(&:prefix_header),
file_accessors.map(&:preserve_paths),
file_accessors.map(&:readme),
file_accessors.map(&:resources),
file_accessors.map(&:source_files),
]
files.flatten.compact.map(&:to_s).uniq
def released?
!local? && !head_pod? && !predownloaded? && sandbox.specification(root_spec.name) != root_spec
end
#-----------------------------------------------------------------------#
......
......@@ -39,6 +39,8 @@ module Pod
autoload :FileAccessor, 'cocoapods/sandbox/file_accessor'
autoload :HeadersStore, 'cocoapods/sandbox/headers_store'
autoload :PathList, 'cocoapods/sandbox/path_list'
autoload :PodDirCleaner, 'cocoapods/sandbox/pod_dir_cleaner'
autoload :PodspecFinder, 'cocoapods/sandbox/podspec_finder'
# @return [Pathname] the root of the sandbox.
#
......
module Pod
class Sandbox
class PodDirCleaner
attr_reader :root
attr_reader :specs_by_platform
def initialize(root, specs_by_platform)
@root = root
@specs_by_platform = specs_by_platform
end
# Removes all the files not needed for the installation according to the
# specs by platform.
#
# @return [void]
#
def clean!
clean_paths.each { |path| FileUtils.rm_rf(path) } if root.exist?
end
private
# @return [Array<Sandbox::FileAccessor>] the file accessors for all the
# specifications on their respective platform.
#
def file_accessors
@file_accessors ||= specs_by_platform.flat_map do |platform, specs|
specs.flat_map { |spec| Sandbox::FileAccessor.new(path_list, spec.consumer(platform)) }
end
end
# @return [Sandbox::PathList] The path list for this Pod.
#
def path_list
@path_list ||= Sandbox::PathList.new(root)
end
# Finds the absolute paths, including hidden ones, of the files
# that are not used by the pod and thus can be safely deleted.
#
# @note Implementation detail: Don't use `Dir#glob` as there is an
# unexplained issue (#568, #572 and #602).
#
# @todo The paths are down-cased for the comparison as issues similar
# to #602 lead the files not being matched and so cleaning all
# the files. This solution might create side effects.
#
# @return [Array<Strings>] The paths that can be deleted.
#
def clean_paths
cached_used = used_files
glob_options = File::FNM_DOTMATCH | File::FNM_CASEFOLD
files = Pathname.glob(root + '**/*', glob_options).map(&:to_s)
files.reject do |candidate|
candidate = candidate.downcase
candidate.end_with?('.', '..') || cached_used.any? do |path|
path = path.downcase
path.include?(candidate) || candidate.include?(path)
end
end
end
# @return [Array<String>] The absolute path of all the files used by the
# specifications (according to their platform) of this Pod.
#
def used_files
files = [
file_accessors.map(&:vendored_frameworks),
file_accessors.map(&:vendored_libraries),
file_accessors.map(&:resource_bundle_files),
file_accessors.map(&:license),
file_accessors.map(&:prefix_header),
file_accessors.map(&:preserve_paths),
file_accessors.map(&:readme),
file_accessors.map(&:resources),
file_accessors.map(&:source_files),
]
files.flatten.compact.map(&:to_s).uniq
end
end
end
end
module Pod
class Sandbox
class PodspecFinder
attr_reader :root
def initialize(root)
@root = root
end
def podspecs
return @specs_by_name if @specs_by_name
@specs_by_name = {}
spec_files = Pathname.glob(root + '{,*,*/*}.podspec{,.json}')
spec_files.sort_by { |p| -p.to_path.split(File::SEPARATOR).size }.each do |file|
begin
spec = Specification.from_file(file)
@specs_by_name[spec.name] = spec
rescue => e
UI.warn "Unable to load a podspec from `#{file.basename}`, skipping:\n\n#{e}"
end
end
@specs_by_name
end
end
end
end
Subproject commit 3e8ec34e84b261eb9bd3fd5352ac2107660cff3e
Subproject commit bd72f0ebff59e9251e7b6d651012ff9159ddd0a7
......@@ -48,6 +48,7 @@ require 'pretty_bacon'
require 'colored'
require 'clintegracon'
require 'integration/xcodeproj_project_yaml'
require 'tmpdir'
CLIntegracon.configure do |c|
c.spec_path = ROOT + 'spec/cocoapods-integration-specs'
......@@ -98,7 +99,7 @@ describe_cli 'pod' do
s.executable = "ruby #{ROOT + 'bin/pod'}"
s.environment_vars = {
'CP_REPOS_DIR' => ROOT + 'spec/fixtures/spec-repos',
'CP_AGGRESSIVE_CACHE' => 'TRUE',
'COCOAPODS_SKIP_CACHE' => 'TRUE',
'XCODEPROJ_DISABLE_XCPROJ' => 'TRUE',
'CLAIDE_DISABLE_AUTO_WRAP' => 'TRUE',
}
......@@ -110,6 +111,7 @@ describe_cli 'pod' do
s.replace_path `which git`.chomp, 'GIT_BIN'
s.replace_path `which hg`.chomp, 'HG_BIN' if has_mercurial
s.replace_user_path 'Library/Caches/CocoaPods', 'CACHES_DIR'
s.replace_pattern /#{Dir.tmpdir}\/[\w-]+/i, 'TMPDIR'
s.replace_pattern /\d{4}-\d\d-\d\d \d\d:\d\d:\d\d [-+]\d{4}/, '<#DATE#>'
s.replace_pattern /\(Took \d+.\d+ seconds\)/, '(Took <#DURATION#> seconds)'
end
......
......@@ -12,6 +12,7 @@ module Bacon
c.repos_dir = fixture('spec-repos')
c.installation_root = SpecHelper.temporary_directory
c.skip_repo_update = true
c.cache_root = SpecHelper.temporary_directory + 'Cache'
end
::Pod::UI.output = ''
......
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Downloader::Cache do
before do
@cache = Downloader::Cache.new(Dir.mktmpdir)
@spec = fixture_spec('banana-lib/BananaLib.podspec')
@spec.source = { :git => SpecHelper.fixture('banana-lib') }
@request = Downloader::Request.new(:spec => @spec, :released => true)
@unreleased_request = Downloader::Request.new(:name => 'BananaLib', :params => @spec.source)
@stub_download = lambda do |cache, &blk|
cache.define_singleton_method(:download) do |_name, target, _params, _head|
FileUtils.mkdir_p target
Dir.chdir(target) { blk.call }
end
end
end
after do
@cache.root.rmtree if @cache.root.directory?
end
it 'returns the root' do
@cache.root.should.be.directory?
end
describe 'when the download is not cached' do
describe 'when downloading a released pod' do
it 'downloads the source' do
Downloader::Git.any_instance.expects(:download)
Downloader::Git.any_instance.expects(:checkout_options).returns(@spec.source)
response = @cache.download_pod(@request)
response.should == Downloader::Response.new(@cache.root + @request.slug, @spec, @spec.source)
end
end
describe 'when downloading an un-released pod' do
before do
@stub_download.call @cache do
File.open('BananaLib.podspec.json', 'w') { |f| f << @spec.to_pretty_json }
File.open('OrangeLib.podspec.json', 'w') { |f| f << @spec.to_pretty_json.sub(/"name": "BananaLib"/, '"name": "OrangeLib"') }
@spec.source
end
end
it 'downloads the source' do
@cache.expects(:copy_and_clean).twice
response = @cache.download_pod(@unreleased_request)
response.should == Downloader::Response.new(@cache.root + @unreleased_request.slug, @spec, @spec.source)
end
end
end
describe 'when the download is cached' do
before do
[@request, @unreleased_request].each do |request|
path_for_spec = @cache.send(:path_for_spec, request)
path_for_spec.dirname.mkpath
path_for_spec.open('w') { |f| f << @spec.to_pretty_json }
path_for_pod = @cache.send(:path_for_pod, request)
path_for_pod.mkpath
Dir.chdir(path_for_pod) do
FileUtils.mkdir_p 'Classes'
File.open('Classes/a.m', 'w') {}
end
end
end
describe 'when downloading a released pod' do
it 'does not download the source' do
Downloader::Git.any_instance.expects(:download).never
@cache.expects(:uncached_pod).never
response = @cache.download_pod(@request)
response.should == Downloader::Response.new(@cache.root + @request.slug, @spec, @spec.source)
end
end
describe 'when downloading an unreleased pod' do
it 'does not download the source' do
Downloader::Git.any_instance.expects(:download).never
@cache.expects(:uncached_pod).never
response = @cache.download_pod(@unreleased_request)
response.should == Downloader::Response.new(@cache.root + @unreleased_request.slug, @spec, @spec.source)
end
end
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Downloader::Request do
before do
@spec = fixture_spec('banana-lib/BananaLib.podspec')
end
#--------------------------------------#
describe 'Validation' do
it 'validates request initialization' do
options = [
{ :spec => nil, :name => nil },
{ :spec => nil, :released => true },
{ :spec => @spec.dup.tap { |s| s.source = nil }, :released => true },
]
options.each do |params|
should.raise(ArgumentError) { Downloader::Request.new(params) }
end
end
end
#--------------------------------------#
describe 'when released_pod? == true' do
before do
@request = Downloader::Request.new(:spec => @spec, :released => true)
end
it 'returns the spec' do
@request.spec.should == @spec
end
it 'returns whether the pod is released' do
@request.released_pod?.should == true
end
it 'returns the name of the spec' do
@request.name.should == @spec.name
end
it 'returns the source of the spec' do
@request.params.should == @spec.source
end
it 'returns the slug' do
@request.slug.should == 'Release/BananaLib/1.0-e2fb5'
end
end
#--------------------------------------#
describe 'when released_pod? == false' do
before do
@request = Downloader::Request.new(:name => 'BananaLib', :params => @spec.source)
end
it 'returns the spec' do
@request.spec.should.be.nil?
end
it 'returns whether the pod is released' do
@request.released_pod?.should == false
end
it 'returns the name of the spec' do
@request.name.should == 'BananaLib'
end
it 'returns the source of the spec' do
@request.params.should == @spec.source
end
it 'returns the slug' do
@request.slug.should == 'External/BananaLib/a0856313adccfbcc7c5b0ea859ee14f5'
end
end
end
end
......@@ -43,17 +43,6 @@ module Pod
},
}
end
it 'checks for JSON podspecs' do
path = config.sandbox.pod_dir('Reachability')
podspec_path = path + 'Reachability.podspec.json'
Dir.mkdir(path)
File.open(podspec_path, 'w') { |f| f.write '{}' }
Pathname.any_instance.stubs(:rmtree)
Downloader::Git.any_instance.stubs(:download)
config.sandbox.expects(:store_podspec).with('Reachability', "{\n}\n", true, true)
@subject.send(:pre_download, config.sandbox)
end
end
end
end
......@@ -16,7 +16,7 @@ module Pod
it 'downloads the source' do
@spec.source = { :git => SpecHelper.fixture('banana-lib'), :tag => 'v1.0' }
@installer.install!
@installer.specific_source.should.be.nil
@installer.specific_source[:tag].should == 'v1.0'
pod_folder = config.sandbox.pod_dir('BananaLib')
pod_folder.should.exist
end
......@@ -49,27 +49,7 @@ module Pod
}
end
it 'cleans up directory when an error occurs during download' do
config.sandbox.store_head_pod('BananaLib')
pod_folder = config.sandbox.pod_dir('BananaLib')
partially_downloaded_file = pod_folder + 'partially_downloaded_file'
mock_downloader = Object.new
singleton_class = class << mock_downloader; self; end
singleton_class.send(:define_method, :download_head) do
FileUtils.mkdir_p(pod_folder)
FileUtils.touch(partially_downloaded_file)
raise('some network error')
end
@installer.stubs(:downloader).returns(mock_downloader)
lambda do
@installer.install!
end.should.raise(RuntimeError).message.should.equal('some network error')
partially_downloaded_file.should.not.exist
end
it 'fails when using :head for Http source' do
it 'fails when using :head for http source' do
config.sandbox.store_head_pod('BananaLib')
@spec.source = { :http => 'http://dl.google.com/googleadmobadssdk/googleadmobsearchadssdkios.zip' }
@spec.source_files = 'GoogleAdMobSearchAdsSDK/*.h'
......@@ -153,81 +133,5 @@ module Pod
end
#-------------------------------------------------------------------------#
describe 'Private Helpers' do
it 'returns the clean paths' do
@installer.send(:download_source)
paths = @installer.send(:clean_paths)
relative_paths = paths.map { |p| p.gsub("#{temporary_directory}/", '') }
# Because there are thousands of files inside .git/, we're excluding
# them from the comparison.
paths_without_git = relative_paths.reject { |p| p.include? 'Pods/BananaLib/.git/' }
paths_without_git.sort.should == [
'Pods/BananaLib/.git',
'Pods/BananaLib/.gitmodules',
'Pods/BananaLib/BananaLib.podspec',
'Pods/BananaLib/libPusher',
'Pods/BananaLib/sub-dir',
'Pods/BananaLib/sub-dir/sub-dir-2',
'Pods/BananaLib/sub-dir/sub-dir-2/somefile.txt',
]
end
it 'returns the used files' do
@installer.send(:download_source)
paths = @installer.send(:used_files)
relative_paths = paths.map { |p| p.gsub("#{temporary_directory}/", '') }
relative_paths.sort.should == [
'Pods/BananaLib/Classes/Banana.h',
'Pods/BananaLib/Classes/Banana.m',
'Pods/BananaLib/Classes/BananaLib.pch',
'Pods/BananaLib/Classes/BananaPrivate.h',
'Pods/BananaLib/LICENSE',
'Pods/BananaLib/README',
'Pods/BananaLib/Resources/logo-sidebar.png',
]
end
it 'handles Pods with multiple file accessors' do
spec = fixture_spec('banana-lib/BananaLib.podspec')
spec.source = { :git => SpecHelper.fixture('banana-lib') }
spec.source_files = []
spec.ios.source_files = 'Classes/*.h'
spec.osx.source_files = 'Classes/*.m'
ios_spec = spec.dup
osx_spec = spec.dup
specs_by_platform = { :ios => [ios_spec], :osx => [osx_spec] }
@installer = Installer::PodSourceInstaller.new(config.sandbox, specs_by_platform)
@installer.send(:download_source)
paths = @installer.send(:used_files)
relative_paths = paths.map { |p| p.gsub("#{temporary_directory}/", '') }
relative_paths.sort.should == [
'Pods/BananaLib/Classes/Banana.h',
'Pods/BananaLib/Classes/Banana.m',
'Pods/BananaLib/Classes/BananaLib.pch',
'Pods/BananaLib/Classes/BananaPrivate.h',
'Pods/BananaLib/LICENSE',
'Pods/BananaLib/README',
'Pods/BananaLib/Resources/logo-sidebar.png',
]
end
it 'compacts the used files as nil would be converted to the empty string' do
Sandbox::FileAccessor.any_instance.stubs(:source_files)
Sandbox::FileAccessor.any_instance.stubs(:vendored_libraries)
Sandbox::FileAccessor.any_instance.stubs(:resources).returns(nil)
Sandbox::FileAccessor.any_instance.stubs(:preserve_paths)
Sandbox::FileAccessor.any_instance.stubs(:prefix_header)
Sandbox::FileAccessor.any_instance.stubs(:readme)
Sandbox::FileAccessor.any_instance.stubs(:license)
Sandbox::FileAccessor.any_instance.stubs(:vendored_frameworks)
paths = @installer.send(:used_files)
paths.should == []
end
end
#-------------------------------------------------------------------------#
end
end
......@@ -312,7 +312,7 @@ module Pod
@installer.stubs(:podfile).returns(podfile)
@installer.stubs(:lockfile).returns(nil)
Downloader::Git.any_instance.expects(:download_head).once
Downloader::Git.any_instance.expects(:checkout_options).returns({})
Downloader::Git.any_instance.stubs(:checkout_options).returns({})
@installer.prepare
@installer.resolve_dependencies
@installer.send(:root_specs).sort_by(&:name).map(&:version).map(&:head?).should == [true, nil]
......
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Sandbox::PodDirCleaner do
before do
@spec = fixture_spec('banana-lib/BananaLib.podspec')
specs_by_platform = { :ios => [@spec] }
@root = temporary_directory + 'BananaLib'
Downloader.for_target(@root, :git => SpecHelper.fixture('banana-lib')).download
@cleaner = Sandbox::PodDirCleaner.new(@root, specs_by_platform)
end
it 'returns the clean paths' do
paths = @cleaner.send(:clean_paths)
relative_paths = paths.map { |p| p.gsub("#{@root}/", '') }
# Because there are thousands of files inside .git/, we're excluding
# them from the comparison.
paths_without_git = relative_paths.reject { |p| p.include? '.git/' }
paths_without_git.sort.should == [
'.git',
'.gitmodules',
'BananaLib.podspec',
'libPusher',
'sub-dir',
'sub-dir/sub-dir-2',
'sub-dir/sub-dir-2/somefile.txt',
]
end
it 'returns the used files' do
paths = @cleaner.send(:used_files)
relative_paths = paths.map { |p| p.gsub("#{@root}/", '') }
relative_paths.sort.should == [
'Classes/Banana.h',
'Classes/Banana.m',
'Classes/BananaLib.pch',
'Classes/BananaPrivate.h',
'LICENSE',
'README',
'Resources/logo-sidebar.png',
]
end
it 'handles Pods with multiple file accessors' do
spec = fixture_spec('banana-lib/BananaLib.podspec')
spec.source_files = []
spec.ios.source_files = 'Classes/*.h'
spec.osx.source_files = 'Classes/*.m'
ios_spec = spec.dup
osx_spec = spec.dup
specs_by_platform = { :ios => [ios_spec], :osx => [osx_spec] }
@cleaner = Sandbox::PodDirCleaner.new(@root, specs_by_platform)
paths = @cleaner.send(:used_files)
relative_paths = paths.map { |p| p.gsub("#{@root}/", '') }
relative_paths.sort.should == [
'Classes/Banana.h',
'Classes/Banana.m',
'Classes/BananaLib.pch',
'Classes/BananaPrivate.h',
'LICENSE',
'README',
'Resources/logo-sidebar.png',
]
end
it 'compacts the used files as nil would be converted to the empty string' do
Sandbox::FileAccessor.any_instance.stubs(:source_files)
Sandbox::FileAccessor.any_instance.stubs(:vendored_libraries)
Sandbox::FileAccessor.any_instance.stubs(:resources).returns(nil)
Sandbox::FileAccessor.any_instance.stubs(:preserve_paths)
Sandbox::FileAccessor.any_instance.stubs(:prefix_header)
Sandbox::FileAccessor.any_instance.stubs(:readme)
Sandbox::FileAccessor.any_instance.stubs(:license)
Sandbox::FileAccessor.any_instance.stubs(:vendored_frameworks)
paths = @cleaner.send(:used_files)
paths.should == []
end
end
end
......@@ -103,6 +103,7 @@ module Pod
it 'returns the path of the search index' do
SourcesManager.unstub(:search_index_path)
config.cache_root = Config::DEFAULTS[:cache_root]
path = SourcesManager.search_index_path.to_s
path.should.match %r{Library/Caches/CocoaPods/search_index.yaml}
end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment