Commit 3cfb3053 authored by Fabio Pelosin's avatar Fabio Pelosin

Add new class Analyzer::SandboxAnalyzer

parent d7c82883
...@@ -171,9 +171,6 @@ module Pod ...@@ -171,9 +171,6 @@ module Pod
# #
# @todo [#534] Detect if the folder of a Pod is empty (even if it exits). # @todo [#534] Detect if the folder of a Pod is empty (even if it exits).
# #
# @todo There could be issues with the current implementation regarding
# external specs.
#
def detect_pods_to_install def detect_pods_to_install
names = [] names = []
...@@ -214,9 +211,6 @@ module Pod ...@@ -214,9 +211,6 @@ module Pod
# #
# @todo [#247] Clean the headers of only the pods to install. # @todo [#247] Clean the headers of only the pods to install.
# #
# @todo Clean the podspecs of all the pods that aren't unchanged so the
# resolution process doesn't get confused by them.
#
def clean_sandbox def clean_sandbox
sandbox.build_headers.implode! sandbox.build_headers.implode!
sandbox.public_headers.implode! sandbox.public_headers.implode!
......
...@@ -8,6 +8,8 @@ module Pod ...@@ -8,6 +8,8 @@ module Pod
include Config::Mixin include Config::Mixin
autoload :SandboxAnalyzer, 'cocoapods/installer/analyzer/sandbox_analyzer'
# @return [Sandbox] The sandbox where the Pods should be installed. # @return [Sandbox] The sandbox where the Pods should be installed.
# #
attr_reader :sandbox attr_reader :sandbox
...@@ -200,9 +202,12 @@ module Pod ...@@ -200,9 +202,12 @@ module Pod
# that prevent the resolver to update a Pod. # that prevent the resolver to update a Pod.
# #
def generate_version_locking_dependencies def generate_version_locking_dependencies
return [] if update_mode? if update_mode?
result.podfile_state.unchanged.map do |pod| []
lockfile.dependency_to_lock_pod_named(pod) else
result.podfile_state.unchanged.map do |pod|
lockfile.dependency_to_lock_pod_named(pod)
end
end end
end end
...@@ -291,39 +296,10 @@ module Pod ...@@ -291,39 +296,10 @@ module Pod
# specifications. # specifications.
# #
def generate_sandbox_state def generate_sandbox_state
sandbox_lockfile = sandbox.manifest || lockfile sandbox_state = nil
sandbox_state = SpecsState.new
UI.section "Comparing resolved specification to the sandbox manifest" do UI.section "Comparing resolved specification to the sandbox manifest" do
resolved_subspecs_names = result.specifications.group_by { |s| s.root.name } sandbox_analyzer = SandboxAnalyzer.new(sandbox, result.specifications, update_mode, lockfile)
resolved_names = resolved_subspecs_names.keys sandbox_state = sandbox_analyzer.analyze
if sandbox_lockfile
sandbox_subspecs_names = sandbox_lockfile.pod_names.group_by { |name| Specification.root_name(name) }
sandbox_names = sandbox_subspecs_names.keys
all_names = (resolved_names + sandbox_names).uniq.sort
root_specs = result.specifications.map(&:root).uniq
is_changed = lambda do |name|
spec = root_specs.find { |r_spec| r_spec.name == name }
spec.version != sandbox_lockfile.version(name) \
|| spec.checksum != sandbox_lockfile.checksum(name) \
|| resolved_subspecs_names[name] =! sandbox_subspecs_names[name] \
end
all_names.each do |name|
state = case
when resolved_names.include?(name) && !sandbox_names.include?(name) then :added
when !resolved_names.include?(name) && sandbox_names.include?(name) then :deleted
when is_changed.call(name) then :changed
else :unchanged
end
sandbox_state.add_name(name, state)
end
else
sandbox_state.added.concat(resolved_names)
end
sandbox_state.print sandbox_state.print
end end
sandbox_state sandbox_state
...@@ -493,6 +469,9 @@ module Pod ...@@ -493,6 +469,9 @@ module Pod
# @note The names of the pods stored by this class are always the **root** # @note The names of the pods stored by this class are always the **root**
# name of the specification. # name of the specification.
# #
# @note The motivation for this class is to ensure that the names of the
# subspecs are added instead of the name of the Pods.
#
class SpecsState class SpecsState
# @param [Hash{Symbol=>String}] pods_by_state # @param [Hash{Symbol=>String}] pods_by_state
......
module Pod
class Installer
class Analyzer
# Analyze the sandbox to detect which Pods should be removed, and which
# ones should be reinstalled.
#
class SandboxAnalyzer
# @return [Sandbox] The sandbox to analyze.
#
attr_reader :sandbox
# @return [Array<Specifications>] The specifications returned by the
# resolver.
#
attr_reader :specs
# @return [Bool] Whether the installation is performed in update mode.
#
attr_reader :update_mode
# @return [Lockfile] The lockfile of the installation as a fall-back if
# there is no sandbox manifest. This is indented as a temporary
# solution to prevent the full re-installation from users which
# are upgrading from CP < 0.17.
#
# @todo Remove for CP 0.18.
#
attr_reader :lockfile
# @param [Sandbox] sandbox @see sandbox
# @param [Array<Specifications>] specs @see specs
# @param [Bool] update_mode @see update_mode
# @param [Lockfile] lockfile @see lockfile
#
def initialize(sandbox, specs, update_mode, lockfile = nil)
@sandbox = sandbox
@specs = specs
@update_mode = update_mode
@lockfile = lockfile
end
# Performs the analysis to the detect the state of the sandbox respect
# to the resolved specifications.
#
# @return [SpecsState] the state of the sandbox.
#
def analyze
state = SpecsState.new
if sandbox_manifest
all_names = (resolved_pods + sandbox_pods).uniq.sort
all_names.sort.each do |name|
state.add_name(name, pod_state(name))
end
else
state.added.concat(resolved_pods)
end
state
end
#---------------------------------------------------------------------#
private
# @!group Pod state
# Returns the state of the Pod with the given name.
#
# @param [String] pod
# the name of the Pod.
#
# @return [Symbol] The state
#
def pod_state(pod)
return :added if pod_added?(pod)
return :deleted if pod_deleted?(pod)
return :changed if pod_changed?(pod)
return :unchanged
end
# Returns whether the Pod with the given name should be installed.
#
# @note A Pod whose folder doesn't exists is considered added.
#
# @param [String] pod
# the name of the Pod.
#
# @return [Bool] Whether the Pod is added.
#
def pod_added?(pod)
return true if resolved_pods.include?(pod) && !sandbox_pods.include?(pod)
return true if !folder_exist?(pod)
return false
end
# Returns whether the Pod with the given name should be removed from
# the installation.
#
# @param [String] pod
# the name of the Pod.
#
# @return [Bool] Whether the Pod is deleted.
#
def pod_deleted?(pod)
return true if !resolved_pods.include?(pod) && sandbox_pods.include?(pod)
return false
end
# Returns whether the Pod with the given name should be considered
# changed and thus should be reinstalled.
#
# @note In update mode, as there is no way to know if a remote source
# hash changed the Pods in head mode and the ones from external
# sources are always marked as changed.
#
# @note A Pod whose folder is empty is considered changed.
#
# @param [String] pod
# the name of the Pod.
#
# @return [Bool] Whether the Pod is changed.
#
def pod_changed?(pod)
spec = root_spec(pod)
return true if spec.version != sandbox_version(pod)
return true if spec.checksum != sandbox_checksum(pod)
return true if resolved_spec_names(pod) != sandbox_spec_names(pod)
return true if folder_empty?(pod)
if update_mode
return true if spec.version.head?
# return true if sandbox.external_source?(pod) TODO
end
return false
end
#---------------------------------------------------------------------#
private
# @!group Private helpers
# @return [Lockfile] The manifest to use for the sandbox.
#
def sandbox_manifest
sandbox.manifest || lockfile
end
#--------------------------------------#
# @return [Array<String>] The name of the resolved Pods.
#
def resolved_pods
specs.map { |spec| spec.root.name }.uniq
end
# @return [Array<String>] The name of the Pods stored in the sandbox
# manifest.
#
def sandbox_pods
sandbox_manifest.pod_names.map { |name| Specification.root_name(name) }.uniq
end
# @return [Array<String>] The name of the resolved specifications
# (includes subspecs).
#
# @param [String] pod
# the name of the Pod.
#
def resolved_spec_names(pod)
specs.select { |s| s.root.name == pod }.map(&:name).uniq
end
# @return [Array<String>] The name of the specifications stored in the
# sandbox manifest (includes subspecs).
#
# @param [String] pod
# the name of the Pod.
#
def sandbox_spec_names(pod)
sandbox_manifest.pod_names.select { |name| Specification.root_name(name) == pod }.uniq
end
# @return [Specification] The root specification for the Pod with the
# given name.
#
# @param [String] pod
# the name of the Pod.
#
def root_spec(pod)
specs.find { |s| s.root.name == pod }.root
end
#--------------------------------------#
# @return [Version] The version of Pod with the given name stored in
# the sandbox.
#
# @param [String] pod
# the name of the Pod.
#
def sandbox_version(pod)
sandbox_manifest.version(pod)
end
# @return [String] The checksum of the specification of the Pod with
# the given name stored in the sandbox.
#
# @param [String] pod
# the name of the Pod.
#
def sandbox_checksum(pod)
sandbox_manifest.checksum(pod)
end
#--------------------------------------#
def folder_exist?(pod)
sandbox.pod_dir(pod).exist?
end
def folder_empty?(pod)
Dir.glob(sandbox.pod_dir(pod) + '*').empty?
end
#---------------------------------------------------------------------#
end
end
end
end
...@@ -23,7 +23,7 @@ module Pod ...@@ -23,7 +23,7 @@ module Pod
out = run_command('ipc', 'podfile', fixture('Podfile')) out = run_command('ipc', 'podfile', fixture('Podfile'))
out.should.include('---') out.should.include('---')
out.should.match /target_definitions:/ out.should.match /target_definitions:/
out.should.match /platform: :ios/ out.should.match /platform: ios/
out.should.match /- SSZipArchive:/ out.should.match /- SSZipArchive:/
end end
......
require File.expand_path('../../../../spec_helper', __FILE__)
#-----------------------------------------------------------------------------#
module Pod
describe Installer::Analyzer::SandboxAnalyzer do
before do
@spec = fixture_spec('banana-lib/BananaLib.podspec')
@sandbox = config.sandbox
lockfile_hash = { 'PODS' => ['BananaLib (1.0)'] }
@manifest = Pod::Lockfile.new(lockfile_hash)
@sandbox.stubs(:manifest).returns(@manifest)
@analyzer = Installer::Analyzer::SandboxAnalyzer.new(@sandbox, [@spec], false)
end
#-------------------------------------------------------------------------#
describe "Analysis" do
it "returns the sandbox state" do
@analyzer.stubs(:folder_exist?).returns(true)
@analyzer.stubs(:folder_empty?).returns(false)
@analyzer.stubs(:sandbox_checksum).returns(@spec.checksum)
state = @analyzer.analyze
state.class.should == Installer::Analyzer::SpecsState
state.unchanged.should == ["BananaLib"]
end
it "marks all the pods as added if no sandbox manifest is available" do
@sandbox.stubs(:manifest)
@analyzer.analyze.added.should == ['BananaLib']
end
end
#-------------------------------------------------------------------------#
describe "Analysis" do
before do
@analyzer.stubs(:folder_exist?).returns(true)
@analyzer.stubs(:folder_empty?).returns(false)
@analyzer.stubs(:sandbox_checksum).returns(@spec.checksum)
end
it "returns whether a Pod is unchanged" do
@analyzer.send(:pod_state, 'BananaLib').should == :unchanged
end
it "considers a Pod as added if it is not recorded in the sandbox manifest" do
@analyzer.stubs(:sandbox_pods).returns([])
@analyzer.send(:pod_added?, 'BananaLib').should == true
end
it "considers a Pod as added if it folder doesn't exits" do
@analyzer.stubs(:folder_exist?).returns(false)
@analyzer.send(:pod_added?, 'BananaLib').should == true
end
it "considers deleted a Pod without any resolved specification" do
@analyzer.stubs(:resolved_pods).returns([])
@analyzer.send(:pod_deleted?, 'BananaLib').should == true
end
it "considers changed a Pod whose versions do not match" do
@analyzer.stubs(:sandbox_version).returns(Version.new(999))
@analyzer.send(:pod_changed?, 'BananaLib').should == true
end
it "considers changed a Pod whose checksums do not match" do
@analyzer.stubs(:sandbox_checksum).returns('SHA')
@analyzer.send(:pod_changed?, 'BananaLib').should == true
end
it "considers changed a Pod whose activated specifications do not match" do
@analyzer.stubs(:sandbox_spec_names).returns(['BananaLib', 'BananaLib/Subspec'])
@analyzer.send(:pod_changed?, 'BananaLib').should == true
end
it "considers changed a Pod whose folder is empty" do
@analyzer.stubs(:folder_empty?).returns(true)
@analyzer.send(:pod_changed?, 'BananaLib').should == true
end
it "considers changed a Pod whose specification is in head mode if in update mode" do
@spec.version.head = true
@analyzer.stubs(:update_mode).returns(true)
@analyzer.send(:pod_changed?, 'BananaLib').should == true
end
xit "considers changed a Pod fetched from an external source if in update mode" do
end
end
#-------------------------------------------------------------------------#
describe "Private helpers" do
it "returns the sandbox manifest" do
@analyzer.send(:sandbox_manifest).should == @manifest
end
it "returns the lockfile as the sandbox if one is not available" do
lockfile = Lockfile.new({})
@sandbox.stubs(:manifest)
@analyzer.stubs(:lockfile).returns(lockfile)
@analyzer.send(:sandbox_manifest).should == lockfile
end
#--------------------------------------#
it "returns the root name of the resolved Pods" do
subspec = Spec.new(@spec, 'Subspec')
@analyzer.stubs(:specs).returns([@spec, subspec])
@analyzer.send(:resolved_pods).should == ['BananaLib']
end
it "returns the root name of pods stored in the sandbox manifest" do
@manifest.stubs(:pod_names).returns(['BananaLib', 'BananaLib/Subspec'])
@analyzer.send(:sandbox_pods).should == ['BananaLib']
end
it "returns the name of the resolved specifications" do
subspec = Spec.new(@spec, 'Subspec')
@analyzer.stubs(:specs).returns([@spec, subspec])
@analyzer.send(:resolved_spec_names, 'BananaLib').should == ['BananaLib', 'BananaLib/Subspec']
end
it "returns the name of the specifications stored in the sandbox manifest" do
@manifest.stubs(:pod_names).returns(['BananaLib', 'BananaLib/Subspec'])
@analyzer.send(:sandbox_spec_names, 'BananaLib').should == ['BananaLib', 'BananaLib/Subspec']
end
it "returns the root specification for the Pod with the given name" do
subspec = Spec.new(@spec, 'Subspec')
@analyzer.stubs(:specs).returns([@spec, subspec])
@analyzer.send(:root_spec, 'BananaLib').should == @spec
end
#--------------------------------------#
it "returns the version for the Pod with the given name stored in the manifest" do
@analyzer.send(:sandbox_version, 'BananaLib').should == Version.new('1.0')
end
it "returns the checksum for the spec of the Pods with the given name stored in the manifest" do
@manifest.stubs(:checksum).returns(@spec.checksum)
@analyzer.send(:sandbox_checksum, 'BananaLib').should == @spec.checksum
end
#--------------------------------------#
it "returns whether the folder containing the Pod with the given name is empty" do
@analyzer.send(:folder_exist?, 'BananaLib').should.be.false
path = temporary_directory + 'Pods/BananaLib'
path.mkpath
@analyzer.send(:folder_exist?, 'BananaLib').should.be.true
end
it "returns whether the folder containing the Pod with the given name is empty" do
@analyzer.send(:folder_empty?, 'BananaLib').should.be.true
path = temporary_directory + 'Pods/BananaLib'
path.mkpath
File.open(path + "file", "w") {}
@analyzer.send(:folder_empty?, 'BananaLib').should.be.false
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