Commit a1a6919b authored by Samuel E. Giddins's avatar Samuel E. Giddins

Merge pull request #3563 from CocoaPods/ali-cache-command

Adding "pod cache" command
parents bfced99f 771c0b83
......@@ -21,6 +21,7 @@ spec/fixtures/banana-lib
spec/fixtures/chameleon
spec/fixtures/integration/Headers/
spec/fixtures/vcr
spec/fixtures/cache
# Examples
examples/**/Podfile.lock
......
......@@ -8,6 +8,11 @@ To install release candidates run `[sudo] gem install cocoapods --pre`
##### Enhancements
* New commands `pod cache list` and `pod cache clean` allows you to see the
contents of the cache and clean it.
[Olivier Halligon](https://github.com/AliSoftware)
[#3508](https://github.com/CocoaPods/CocoaPods/issues/3508)
* The download cache will automatically be reset when changing CocoaPods
versions.
[Samuel Giddins](https://github.com/segiddins)
......
......@@ -24,6 +24,7 @@ module Pod
require 'cocoapods/command/setup'
require 'cocoapods/command/spec'
require 'cocoapods/command/init'
require 'cocoapods/command/cache'
self.abstract_command = true
self.command = 'pod'
......
require 'cocoapods/downloader'
require 'cocoapods/command/cache/list'
require 'cocoapods/command/cache/clean'
module Pod
class Command
class Cache < Command
self.abstract_command = true
self.summary = 'Manipulate the CocoaPods cache'
self.description = <<-DESC
Manipulate the download cache for pods, like printing the cache content
or cleaning the pods cache.
DESC
def initialize(argv)
@cache = Downloader::Cache.new(Config.instance.cache_root + 'Pods')
super
end
private
def pod_type(pod_cache_descriptor)
pod_cache_descriptor[:release] ? 'Release' : 'External'
end
end
end
end
module Pod
class Command
class Cache < Command
class Clean < Cache
self.summary = 'Remove the cache for pods'
self.description = <<-DESC
Remove the cache for a given pod, or clear the cache completely.
If there is multiple cache for various versions of the requested pod,
you will be asked which one to clean. Use `--all` to clean them all.
If you don't give a pod `NAME`, you need to specify the `--all`
flag (this is to avoid cleaning all the cache by mistake).
DESC
self.arguments = [
CLAide::Argument.new('NAME', false),
]
def self.options
[[
'--all', 'Remove all the cached pods without asking'
]].concat(super)
end
def initialize(argv)
@pod_name = argv.shift_argument
@wipe_all = argv.flag?('all')
super
end
def run
if @pod_name.nil?
# Note: at that point, @wipe_all is always true (thanks to `validate!`)
# Remove all
clear_cache
else
# Remove only cache for this pod
cache_descriptors = @cache.cache_descriptors_per_pod[@pod_name]
if cache_descriptors.nil?
UI.notice("No cache for pod named #{@pod_name} found")
elsif cache_descriptors.count > 1 && !@wipe_all
# Ask which to remove
choices = cache_descriptors.map { |c| "#{@pod_name} v#{c[:version]} (#{pod_type(c)})" }
index = UI.choose_from_array(choices, 'Which pod cache do you want to remove?')
remove_caches([cache_descriptors[index]])
else
# Remove all found cache of this pod
remove_caches(cache_descriptors)
end
end
end
def validate!
super
if @pod_name.nil? && !@wipe_all
# Security measure, to avoid removing the pod cache too agressively by mistake
help! 'You should either specify a pod name or use the --all flag'
end
end
private
# Removes the specified cache
#
# @param [Array<Hash>] cache_descriptors
# An array of caches to remove, each specified with the same
# hash as cache_descriptors_per_pod especially :spec_file and :slug
#
def remove_caches(cache_descriptors)
cache_descriptors.each do |desc|
UI.message("Removing spec #{desc[:spec_file]} (v#{desc[:version]})") do
FileUtils.rm(desc[:spec_file])
end
UI.message("Removing cache #{desc[:slug]}") do
FileUtils.rm_rf(desc[:slug])
end
end
end
def clear_cache
UI.message("Removing the whole cache dir #{@cache.root}") do
FileUtils.rm_rf(@cache.root)
end
end
end
end
end
end
module Pod
class Command
class Cache < Command
class List < Cache
self.summary = 'List the paths of pod caches for each known pod'
self.description = <<-DESC
Shows the content of the pods cache as a YAML tree output, organized by pod.
If `NAME` is given, only the caches for that pod will be included in the output.
DESC
self.arguments = [
CLAide::Argument.new('NAME', false),
]
def self.options
[[
'--short', 'Only print the path relative to the cache root'
]].concat(super)
end
def initialize(argv)
@pod_name = argv.shift_argument
@short_output = argv.flag?('short')
super
end
def run
UI.puts("$CACHE_ROOT: #{@cache.root}") if @short_output
if @pod_name.nil? # Print all
@cache.cache_descriptors_per_pod.each do |pod_name, cache_descriptors|
print_pod_cache_infos(pod_name, cache_descriptors)
end
else # Print only for the requested pod
cache_descriptors = @cache.cache_descriptors_per_pod[@pod_name]
if cache_descriptors.nil?
UI.notice("No cache for pod named #{@pod_name} found")
else
print_pod_cache_infos(@pod_name, cache_descriptors)
end
end
end
private
# Prints the list of specs & pod cache dirs for a single pod name.
#
# This output is valid YAML so it can be parsed with 3rd party tools
#
# @param [Array<Hash>] cache_descriptors
# The various infos about a pod cache. Keys are
# :spec_file, :version, :release and :slug
#
def print_pod_cache_infos(pod_name, cache_descriptors)
UI.puts "#{pod_name}:"
cache_descriptors.each do |desc|
if @short_output
[:spec_file, :slug].each { |k| desc[k] = desc[k].relative_path_from(@cache.root) }
end
UI.puts(" - Version: #{desc[:version]}")
UI.puts(" Type: #{pod_type(desc)}")
UI.puts(" Spec: #{desc[:spec_file]}")
UI.puts(" Pod: #{desc[:slug]}")
end
end
end
end
end
end
......@@ -30,23 +30,6 @@ module Pod
help! 'A valid regular expression is required.'
end
# @return [Fixnum] the index of the chosen array item
#
def choose_from_array(array, message)
array.each_with_index do |item, index|
UI.puts "#{ index + 1 }: #{ item }"
end
UI.puts message
index = UI.gets.chomp.to_i - 1
if index < 0 || index > array.count - 1
raise Informative, "#{ index + 1 } is invalid [1-#{ array.count }]"
else
index
end
end
# @param [String] spec
# The name of the specification.
#
......
......@@ -37,7 +37,7 @@ module Pod
query = @use_regex ? @query : Regexp.escape(@query)
filepath = if @show_all
specs = get_path_of_spec(query, @show_all).split(/\n/)
index = choose_from_array(specs, "Which spec would you like to print [1-#{ specs.count }]? ")
index = UI.choose_from_array(specs, "Which spec would you like to print [1-#{ specs.count }]? ")
specs[index]
else
get_path_of_spec(query)
......
......@@ -38,7 +38,7 @@ module Pod
if @show_all
specs = get_path_of_spec(query, @show_all).split(/\n/)
message = "Which spec would you like to edit [1-#{specs.count}]? "
index = choose_from_array(specs, message)
index = UI.choose_from_array(specs, message)
filepath = specs[index]
else
filepath = get_path_of_spec(query)
......
......@@ -38,6 +38,37 @@ module Pod
raise
end
# @return [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
#
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
private
# Ensures the cache on disk was created with the same CocoaPods version as
......
......@@ -285,6 +285,32 @@ module Pod
end
end
# Presents a choice among the elements of an array to the user.
#
# @param [Array<#to_s>] array
# The list of the elements among which the user should make his
# choice.
#
# @param [String] message
# The message to display to the user.
#
# @return [Fixnum] The index of the chosen array item.
#
def choose_from_array(array, message)
array.each_with_index do |item, index|
UI.puts "#{ index + 1 }: #{ item }"
end
UI.puts message
index = UI.gets.chomp.to_i - 1
if index < 0 || index > array.count - 1
raise Informative, "#{ index + 1 } is invalid [1-#{ array.count }]"
else
index
end
end
public
# @!group Basic methods
......
require File.expand_path('../../../../spec_helper', __FILE__)
module Pod
describe Command::Cache::Clean do
extend SpecHelper::Command
extend SpecHelper::TemporaryCache
before do
SpecHelper::TemporaryCache.set_up_test_cache
config.cache_root = SpecHelper::TemporaryCache.tmp_cache_path
end
it 'requires --all if no name given' do
e = lambda { run_command('cache', 'clean') }.should.raise CLAide::Help
e.message.should.match(/specify a pod name or use the --all flag/)
end
it 'asks the pod to clean when multiple matches' do
e = lambda { run_command('cache', 'clean', 'AFNetworking') }.should.raise Pod::Informative
e.message.should == '[!] 0 is invalid [1-2]'
end
it 'clean all matching pods when given a name and --all' do
run_command('cache', 'clean', '--all', 'AFNetworking')
remaining_occurences = Dir.glob(tmp_cache_path + '**/AFNetworking')
# We only clean files (so there may still be some empty dirs), so check for files only
remaining_occurences.select { |f| File.file?(f) }.should == []
end
it 'clean all pods when given --all' do
run_command('cache', 'clean', '--all')
Dir.glob(tmp_cache_path + '**/*').should == []
end
it 'warns when no matching pod found in the cache' do
output = run_command('cache', 'clean', 'non-existing-pod')
output.should.match(/No cache for pod/)
end
end
end
require File.expand_path('../../../../spec_helper', __FILE__)
require 'yaml'
module Pod
describe Command::Cache::List do
extend SpecHelper::Command
extend SpecHelper::TemporaryCache
before do
SpecHelper::TemporaryCache.set_up_test_cache
config.cache_root = SpecHelper::TemporaryCache.tmp_cache_path
end
describe 'lists the whole content of the cache as YAML' do
it 'shows the long form without --short' do
output = run_command('cache', 'list')
yaml = YAML.load(output)
yaml.should == SpecHelper::TemporaryCache.test_cache_yaml(false)
end
it 'shows the short form with --short' do
output = run_command('cache', 'list', '--short')
yaml = YAML.load(output)
yaml.should == SpecHelper::TemporaryCache.test_cache_yaml(true)
end
end
describe 'lists only the cache content for the requested pod as YAML' do
it 'shows the long form without --short' do
output = run_command('cache', 'list', 'AFNetworking')
yaml = YAML.load(output)
yaml.should == SpecHelper::TemporaryCache.test_cache_yaml(false).select do |key, _|
key == 'AFNetworking'
end
end
it 'shows the short form with --short' do
run_command('cache', 'list', '--short', 'bananalib')
output = run_command('cache', 'list', 'AFNetworking')
yaml = YAML.load(output)
yaml.should == SpecHelper::TemporaryCache.test_cache_yaml(false).select do |key, _|
['AFNetworking', '$CACHE_ROOT'].include?(key)
end
end
end
end
end
......@@ -364,22 +364,6 @@ module Pod
path.should == fixture('spec-repos') + 'master/Specs/AFNetworking/2.4.1/AFNetworking.podspec.json'
end
end
describe '#choose_from_array' do
it 'should return a valid index for the given array' do
UI.next_input = "1\n"
index = @command.send(:choose_from_array, %w(item1 item2 item3), 'A message')
UI.output.should.include "1: item1\n2: item2\n3: item3\nA message\n"
index.should == 0
end
it 'should raise when the index is out of bounds' do
UI.next_input = "4\n"
lambda { @command.send(:choose_from_array, %w(item1 item2 item3), 'A message') }.should.raise Pod::Informative
UI.next_input = "0\n"
lambda { @command.send(:choose_from_array, %w(item1 item2 item3), 'A message') }.should.raise Pod::Informative
end
end
end
#-------------------------------------------------------------------------#
......
......@@ -41,6 +41,7 @@ require 'claide'
require 'spec_helper/command' # Allows to run Pod commands and returns their output.
require 'spec_helper/fixture' # Provides access to the fixtures and unpacks them if needed.
require 'spec_helper/temporary_repos' # Allows to create and modify temporary spec repositories.
require 'spec_helper/temporary_cache' # Allows to create temporary cache directory.
require 'spec_helper/user_interface' # Redirects UI to UI.output & UI.warnings.
require 'spec_helper/pre_flight' # Cleans the temporary directory, the config & the UI.output before every test.
......
require File.expand_path('../fixture', __FILE__)
module SpecHelper
module TemporaryCache
# Sets up a lighweight cache in `tmp/cocoapods/cache` with the
# contents of `spec/fixtures/cache/CocoaPods`.
#
def set_up_test_cache
require 'fileutils'
fixture_path = SpecHelper::Fixture.fixture('cache')
destination = SpecHelper.temporary_directory + 'cocoapods'
FileUtils.rm_rf(destination)
destination.mkpath
FileUtils.cp_r(fixture_path, destination)
# Add version file so that the cache isn't imploded on version mismatch
# (We don't include it in the tar.gz as we don't want to regenerate it each time)
version_file = tmp_cache_path + 'Pods/VERSION'
version_file.open('w') { |f| f << Pod::VERSION }
end
def tmp_cache_path
SpecHelper.temporary_directory + 'cocoapods/cache/CocoaPods'
end
def test_cache_yaml(short = false)
cache_root = "#{tmp_cache_path}/Pods"
root_path = short ? '' : "#{cache_root}/"
yaml = {
'AFNetworking' => [
{ 'Version' => '2.5.4',
'Type' => 'External',
'Spec' => "#{root_path}Specs/External/AFNetworking/d9ac25e7b83cea885663771c90998c47.podspec.json",
'Pod' => "#{root_path}External/AFNetworking/e84d20f40f2049470632ce56ff0ce26f-05edc",
},
{ 'Version' => '2.5.4',
'Type' => 'Release',
'Spec' => "#{root_path}Specs/Release/AFNetworking/2.5.podspec.json",
'Pod' => "#{root_path}Release/AFNetworking/2.5.4-05edc",
},
],
'CocoaLumberjack' => [
{ 'Version' => '2.0.0',
'Type' => 'Release',
'Spec' => "#{root_path}Specs/Release/CocoaLumberjack/2.0.podspec.json",
'Pod' => "#{root_path}Release/CocoaLumberjack/2.0.0-a6f77",
},
],
}
yaml['$CACHE_ROOT'] = cache_root if short
yaml
end
module_function :set_up_test_cache, :tmp_cache_path, :test_cache_yaml
end
end
......@@ -71,5 +71,21 @@ module Pod
UI.output.should == "#{' ' * 10}- label:\n" + values.map { |v| "#{' ' * 12}- #{v}\n" }.join
end
end
describe '#choose_from_array' do
it 'should return a valid index for the given array' do
UI.next_input = "1\n"
index = UI.choose_from_array(%w(item1 item2 item3), 'A message')
UI.output.should.include "1: item1\n2: item2\n3: item3\nA message\n"
index.should == 0
end
it 'should raise when the index is out of bounds' do
UI.next_input = "4\n"
lambda { UI.choose_from_array(%w(item1 item2 item3), 'A message') }.should.raise Pod::Informative
UI.next_input = "0\n"
lambda { UI.choose_from_array(%w(item1 item2 item3), 'A message') }.should.raise Pod::Informative
end
end
end
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