Commit 9cbb2dfd authored by Fabio Pelosin's avatar Fabio Pelosin

[Install/Update] second iteration.

parent edea0a11
......@@ -36,16 +36,16 @@ module Pod
sandbox = Sandbox.new(config.project_pods_root)
resolver = Resolver.new(podfile, lockfile, sandbox)
resolver.update_mode = true
resolver.updated_external_specs = false
resolver.update_external_specs = false
resolver.resolve
specs_to_install = resolver.specs_to_install
external_pods = resolver.external_pods
pods_to_install = resolver.pods_to_install
external_pods = resolver.pods_from_external_sources
known_update_specs = []
head_mode_specs = []
resolver.specs.each do |s|
next if external_pods.include?(s.name)
next unless specs_to_install.include?(s.name)
next unless pods_to_install.include?(s.name)
if s.version.head?
head_mode_specs << s.name
......@@ -54,7 +54,7 @@ module Pod
end
end
if specs_to_install.empty?
if pods_to_install.empty?
puts "\nNo updates are available.\n".yellow
else
......
......@@ -101,6 +101,10 @@ module Pod
@external_source.specification_from_sandbox(sandbox, platform)
end
def match_version?(version)
match?(name, version) && (version.head? == head?)
end
# Taken from RubyGems 1.3.7
unless public_method_defined?(:match?)
def match?(spec_name, spec_version)
......@@ -139,6 +143,7 @@ module Pod
module ExternalSources
def self.from_params(name, params)
return unless name && params
if params.key?(:git)
GitSource.new(name, params)
elsif params.key?(:podspec)
......@@ -172,9 +177,9 @@ module Pod
specification_from_local(sandbox, platform)
end
def ==(other_source)
return if other_source.nil?
name == other_source.name && params == other_source.params
def ==(other)
return if other.nil?
name == other.name && params == other.params
end
end
......
......@@ -79,6 +79,7 @@ module Pod
git! "reset --hard HEAD"
git! "clean -d -x -f"
git! "pull origin master"
git! "fetch --tags"
end
end
......
......@@ -58,18 +58,16 @@ module Pod
end
if should_install
pod.implode
download_pod(pod)
# This will not happen if the pod existed before we started the install
# process.
if pod.downloaded?
# The docs need to be generated before cleaning because the
# documentation is created for all the subspecs.
generate_docs(pod)
# Here we clean pod's that just have been downloaded or have been
# pre-downloaded in AbstractExternalSource#specification_from_sandbox.
pod.clean! if config.clean?
unless pod.downloaded?
pod.implode
download_pod(pod)
end
# The docs need to be generated before cleaning because the
# documentation is created for all the subspecs.
generate_docs(pod)
# Here we clean pod's that just have been downloaded or have been
# pre-downloaded in AbstractExternalSource#specification_from_sandbox.
pod.clean! if config.clean?
end
end
end
......@@ -100,12 +98,26 @@ module Pod
end
end
# @TODO: use the local pod implode
#
def remove_deleted_dependencies!
resolver.removed_pods.each do |pod_name|
marker = config.verbose ? "\n-> ".red : ''
path = sandbox.root + pod_name
puts marker << "Removing #{pod_name}".red
path.rmtree if path.exist?
end
end
def install!
@sandbox.prepare_for_install
print_title "Resolving dependencies of: #{@podfile.defined_in_file}"
specs_by_target
print_title "Removing deleted dependencies" unless resolver.removed_pods.empty?
remove_deleted_dependencies!
print_title "Installing dependencies"
install_dependencies!
......@@ -123,12 +135,12 @@ module Pod
# Post install hooks run _before_ saving of project, so that they can alter it before saving.
run_post_install_hooks
puts "- Writing Xcode project file to `#{@sandbox.project_path}'\n\n" if config.verbose?
puts "- Writing Xcode project file to `#{@sandbox.project_path}'" if config.verbose?
project.save_as(@sandbox.project_path)
puts "- Writing lockfile in `#{config.project_lockfile}'\n\n" if config.verbose?
@lockfile = Lockfile.create(config.project_lockfile, @podfile, specs_by_target.values.flatten)
@lockfile.write_to_disk
@lockfile = Lockfile.generate(@podfile, specs_by_target.values.flatten)
@lockfile.write_to_disk(config.project_lockfile)
UserProjectIntegrator.new(@podfile).integrate! if config.integrate_targets?
end
......
module Pod
class Lockfile
# @return [Lockfile] Returns the Lockfile saved in path. If the
# file could not be loaded or is not compatible with current
# version of CocoaPods {nil}
# @return [Lockfile] Returns the Lockfile saved in path.
# Returns {nil} If the file can't be loaded.
#
def self.from_file(path)
lockfile = Lockfile.new(path)
lockfile.hash_reppresentation ? lockfile : nil
return nil unless path.exist?
hash = YAML.load(File.open(path))
lockfile = Lockfile.new(hash)
lockfile.defined_in_file = path
lockfile
end
# @return [Lockfile] Creates a new Lockfile ready to be saved in path.
# @return [Lockfile] Generates a lockfile from a {Podfile} and the
# list of {Specifications} that were installed.
#
def self.create(path, podfile, specs)
Lockfile.new(path, podfile, specs)
def self.generate(podfile, specs)
Lockfile.new(generate_hash_from_podfile(podfile, specs))
end
attr_reader :defined_in_file, :podfile, :specs, :hash_reppresentation
# @return [String] The file where this Lockfile is defined.
#
attr_accessor :defined_in_file
# @return [String] The hash used to initialize the Lockfile.
#
attr_reader :to_hash
# @param [Pathname] the path of the Lockfile.
# If no other value is provided the Lockfile is read from this path.
# @param [Podfile] the Podfile to use for generating the Lockfile.
# @param [specs] the specs installed.
#
def initialize(path, podfile = nil, specs = nil)
@defined_in_file = path
if podfile && specs
@podfile = podfile
@specs = specs
else
yaml = YAML.load(File.open(path))
if yaml && Version.new(yaml["COCOAPODS"]) >= Version.new("0.10")
@hash_reppresentation = yaml
end
def initialize(hash)
if Version.new(hash["COCOAPODS"]) <= Version.new("0.11")
# Convert old format to be compatible
# - Pods:
# - libPusher (1.0) [HEAD] -> libPusher (HEAD from 1.0)
# - Dependencies:
# - libPusher [HEAD] -> libPusher (HEAD)
end
@to_hash = hash
end
# @return [Array<String, Hash{String => Array[String]}>] The pods installed
# and their dependencies.
#
def pods
return [] unless to_hash
to_hash['PODS'] || []
@pods ||= to_hash['PODS'] || []
end
# @return [Array<Dependency>] The Podfile dependencies used during the last
# install.
#
def dependencies
return [] unless to_hash
to_hash['DEPENDENCIES'] || []
@dependencies ||= to_hash['DEPENDENCIES'].map { |dep| dependency_from_string(dep) } || []
end
# @return [Hash{String => Hash}] A hash where the name of the pods are
# the keys and the values are the parameters of an {AbstractExternalSource}
# of the dependency that required the pod.
#
def external_sources
return [] unless to_hash
to_hash["EXTERNAL SOURCES"] || []
@external_sources ||= to_hash["EXTERNAL SOURCES"] || {}
end
# @return [Array<Dependency>] The Podfile dependencies used during the last
# install.
# @return [Array<String>] The names of the installed Pods.
#
def podfile_dependencies
dependencies.map { |dep| dependency_from_string(dep) }
def pods_names
@pods_names ||= pods.map do |pod|
pod = pod.keys.first unless pod.is_a?(String)
name_and_version_for_pod(pod)[0]
end
end
# @return [Array<Dependency>] The dependencies that require the installed
# pods with their exact version.
# @return [Hash{String => Version}] A Hash containing the name
# of the installed Pods as the keys and their corresponding {Version}
# as the values.
#
def pods_versions
unless @pods_versions
@pods_versions = {}
pods.each do |pod|
pod = pod.keys.first unless pod.is_a?(String)
name, version = name_and_version_for_pod(pod)
@pods_versions[name] = version
end
end
@pods_versions
end
# @param [String] The string that describes a {Specification} generated
# from {Specification#to_s}.
#
# @example Strings examples
# "libPusher"
# "libPusher (1.0)"
# "libPusher (HEAD from 1.0)"
# "RestKit/JSON"
#
def dependencies_for_pods
pods.map { |pod| dependency_from_string(pod.is_a?(String) ? pod : pod.keys[0]) }
# @return [String, Version] The name and the version of a
# pod.
#
def name_and_version_for_pod(string)
match_data = string.match(/(\S*) \((.*)\)/)
name = match_data[1]
vers = Version.from_s(match_data[2])
return [name, vers]
end
# @param [String] The string that describes a {Dependency} generated
# from {Dependency#to_s}.
#
# @example Strings examples
# "libPusher"
# "libPusher (= 1.0)"
# "libPusher (~> 1.0.1)"
# "libPusher (> 1.0, < 2.0)"
# "libPusher (HEAD)"
# "libPusher (from `www.example.com')"
# "libPusher (defined in Podfile)"
# "RestKit/JSON"
#
# @return [Dependency] The dependency described by the string.
#
def dependency_from_string(string)
......@@ -79,8 +136,8 @@ module Pod
# @TODO: store the whole spec?, the version?
Dependency.new(name)
when /from `(.*)'/
external_source_info = external_sources.find {|hash| hash.keys[0] == name} || {}
Dependency.new(name, external_source_info[name])
external_source_info = external_sources[name]
Dependency.new(name, external_source_info)
when /HEAD/
# @TODO: find a way to serialize from the Downloader the information
# necessary to restore a head version.
......@@ -90,10 +147,60 @@ module Pod
end
end
# Analyzes the {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 head or external options.
# - removed: Pods that were removed form the Podfile.
# - unchanged: Pods that are still compatible with Podfile.
#
# @TODO: detect changes for inline dependencies?
#
# @return [Hash{Symbol=>Array[Strings]}] A hash where pods are grouped
# by the state in which they are.
#
def detect_changes_with_podfile(podfile)
previous_podfile_deps = dependencies.map(&:name)
user_installed_pods = pods_names.reject { |name| !previous_podfile_deps.include?(name) }
deps_to_install = podfile.dependencies.dup
result = {}
result[:added] = []
result[:changed] = []
result[:removed] = []
result[:unchanged] = []
user_installed_pods.each do |pod_name|
dependency = deps_to_install.find { |d| d.name == pod_name }
deps_to_install.delete(dependency)
version = pods_versions[pod_name]
external_source = Dependency::ExternalSources.from_params(pod_name, external_sources[pod_name])
if dependency.nil?
result[:removed] << pod_name
elsif !dependency.match_version?(version) || dependency.external_source != external_source
result[:changed] << pod_name
else
result[:unchanged] << pod_name
end
end
deps_to_install.each do |dependency|
result[:added] << dependency.name
end
result
end
# @return [void] Writes the Lockfile to {#path}.
#
def write_to_disk
File.open(defined_in_file, 'w') {|f| f.write(to_yaml) }
def write_to_disk(path)
File.open(path, 'w') {|f| f.write(to_yaml) }
defined_in_file = path
end
# @return [String] A string useful to represent the Lockfile in a message
......@@ -112,9 +219,7 @@ module Pod
# @return [Dictionary] The Dictionary representation of the Lockfile.
#
def to_hash
return @hash_reppresentation if @hash_reppresentation
return nil unless @podfile && @specs
def self.generate_hash_from_podfile(podfile, specs)
hash = {}
# Get list of [name, dependencies] pairs.
......@@ -122,7 +227,7 @@ module Pod
[spec.to_s, spec.dependencies.map(&:to_s).sort]
end.uniq
# Merge dependencies of ios and osx version of the same pod.
# Merge dependencies of iOS and OS X version of the same pod.
tmp = {}
pod_and_deps.each do |name, deps|
if tmp[name]
......@@ -136,12 +241,18 @@ module Pod
end
hash["PODS"] = pod_and_deps
hash["DEPENDENCIES"] = podfile.dependencies.map{ |d| "#{d}" }.sort
hash["DEPENDENCIES"] = podfile.dependencies.map{ |d| d.to_s }.sort
external_sources = podfile.dependencies.select(&:external?).sort{ |d, other| d.name <=> other.name}.map{ |d| { d.name => d.external_source.params } }
external_sources = {}
deps = podfile.dependencies.select(&:external?).sort{ |d, other| d.name <=> other.name}
deps.each{ |d| external_sources[d.name] = d.external_source.params }
hash["EXTERNAL SOURCES"] = external_sources unless external_sources.empty?
# hash["SPECS_CHECKSUM"]
checksums = {}
specs.select {|spec| !spec.defined_in_file.nil? }.each do |spec|
checksums[spec.name] = Digest::SHA1.hexdigest(File.read(spec.defined_in_file)).to_s
end
hash["SPECS CHECKSUM"] = checksums unless checksums.empty?
hash["COCOAPODS"] = VERSION
hash
end
......
This diff is collapsed.
......@@ -73,14 +73,10 @@ else
resolver = Pod::Resolver.new(podfile, nil, Pod::Sandbox.new(config.project_pods_root))
installer = Pod::Installer.new(resolver)
installer.install!
installer.lockfile.to_hash.should == {
'PODS' => ['SSToolkit (0.1.3)'],
'DEPENDENCIES' => ["SSToolkit (from `#{url}', commit `#{commit}')"],
'EXTERNAL SOURCES' => [ {"SSToolkit" => {
:git=>"/Users/fabio/Documents/GitHub/CP/CocoaPods/spec/fixtures/integration/sstoolkit", :commit=>"2adcd0f81740d6b0cd4589af98790eee3bd1ae7b"
}}],
'COCOAPODS' => Pod::VERSION
}
result = installer.lockfile.to_hash
result['PODS'].should == ['SSToolkit (0.1.3)']
result['DEPENDENCIES'].should == ["SSToolkit (from `#{url}', commit `#{commit}')"]
result['EXTERNAL SOURCES'].should == {"SSToolkit" => { :git=>url, :commit=>commit}}
end
it "installs a library with a podspec outside of the repo" do
......@@ -92,18 +88,13 @@ else
pod 'Reachability', :podspec => url
end
resolver = Pod::Resolver.new(podfile, nil, Pod::Sandbox.new(config.project_pods_root))
installer = SpecHelper::Installer.new(resolver)
installer.install!
installer.lockfile.to_hash.should == {
'PODS' => [ 'Reachability (1.2.3)' ],
'DEPENDENCIES' => ["Reachability (from `#{url}')"],
"EXTERNAL SOURCES"=>[{"Reachability"=>{
:podspec=>"https://raw.github.com/gist/1349824/3ec6aa60c19113573fc48eac19d0fafd6a69e033/Reachability.podspec"}}],
'COCOAPODS' => Pod::VERSION
}
result = installer.lockfile.to_hash
result['PODS'].should == ['Reachability (1.2.3)']
result['DEPENDENCIES'].should == ["Reachability (from `#{url}')"]
result['EXTERNAL SOURCES'].should == {"Reachability"=>{ :podspec=>"https://raw.github.com/gist/1349824/3ec6aa60c19113573fc48eac19d0fafd6a69e033/Reachability.podspec"}}
end
it "installs a dummy source file" do
......@@ -203,19 +194,19 @@ else
installer = SpecHelper::Installer.new(resolver)
installer.install!
result = installer.lockfile.to_hash
result['PODS'].should == [
{ "ASIHTTPRequest (1.8.1)" => ["ASIHTTPRequest/ASIWebPageRequest (= 1.8.1)",
"ASIHTTPRequest/CloudFiles (= 1.8.1)",
"ASIHTTPRequest/S3 (= 1.8.1)",
"Reachability"]},
{ "ASIHTTPRequest/ASIWebPageRequest (1.8.1)" => ["Reachability"] },
{ "ASIHTTPRequest/CloudFiles (1.8.1)" => ["Reachability"] },
{ "ASIHTTPRequest/S3 (1.8.1)" => ["Reachability"] },
"JSONKit (1.4)",
"Reachability (3.0.0)"]
result['DEPENDENCIES'].should == ["ASIHTTPRequest", "JSONKit (= 1.4)"]
# TODO might be nicer looking to not show the dependencies of the top level spec for each subspec (Reachability).
installer.lockfile.to_hash.should == {
"PODS" => [{ "ASIHTTPRequest (1.8.1)" => ["ASIHTTPRequest/ASIWebPageRequest (= 1.8.1)",
"ASIHTTPRequest/CloudFiles (= 1.8.1)",
"ASIHTTPRequest/S3 (= 1.8.1)",
"Reachability"]},
{ "ASIHTTPRequest/ASIWebPageRequest (1.8.1)" => ["Reachability"] },
{ "ASIHTTPRequest/CloudFiles (1.8.1)" => ["Reachability"] },
{ "ASIHTTPRequest/S3 (1.8.1)" => ["Reachability"] },
"JSONKit (1.4)", "Reachability (3.0.0)"],
"DEPENDENCIES" => ["ASIHTTPRequest", "JSONKit (= 1.4)"],
"COCOAPODS" => Pod::VERSION
}
should_xcodebuild(podfile.target_definitions[:ios_target])
should_xcodebuild(podfile.target_definitions[:osx_target])
......@@ -313,7 +304,9 @@ else
lockfile_contents['PODS'].delete_at(1)
# lockfile_contents['PODS'][0] = 'ASIHTTPRequest (1.8.1)'
end
installer.lockfile.to_hash.should == lockfile_contents
result = installer.lockfile.to_hash
result.delete("SPECS CHECKSUM")
result.should == lockfile_contents
root = config.project_pods_root
(root + 'Pods.xcconfig').read.should == installer.target_installers.first.xcconfig.to_s
......@@ -335,12 +328,9 @@ else
installer = SpecHelper::Installer.new(resolver)
installer.install!
installer.lockfile.to_hash.tap {|d| d.delete("COCOAPODS") }.should == {
# 'PODS' => [{ 'Reachability (2.0.4)' => ["ASIHTTPRequest (>= 1.8)"] }],
'PODS' => [ 'Reachability (2.0.4)' ],
# 'DOWNLOAD_ONLY' => ["ASIHTTPRequest (1.8.1)"],
'DEPENDENCIES' => ["Reachability (= 2.0.4)"]
}
result = installer.lockfile.to_hash
result['PODS'].should == [ 'Reachability (2.0.4)' ]
result['DEPENDENCIES'].should == ["Reachability (= 2.0.4)"]
end
end
......
This diff is collapsed.
......@@ -223,7 +223,7 @@ module Pod
s.version = "1.4"
end ]
@specs.each { |s| s.activate_platform(:ios) }
@lockfile = Lockfile.create(nil, @podfile, @specs)
@lockfile = Lockfile.generate(@podfile, @specs)
@resolver = Resolver.new(@podfile, @lockfile, stub('sandbox'))
end
......@@ -233,7 +233,7 @@ module Pod
@resolver.should_install?("JSONKit").should.be.false
end
it "doesn't updates pods still compatible with the Podfile" do
it "doesn't update the version of pods still compatible with the Podfile" do
installed = @resolver.resolve.values.flatten.map(&:to_s)
installed.should.include? "JSONKit (1.4)"
end
......@@ -315,7 +315,7 @@ module Pod
s.version = "1.4"
end ]
@specs.each { |s| s.activate_platform(:ios) }
@lockfile = Lockfile.create(nil, @podfile, @specs)
@lockfile = Lockfile.generate(@podfile, @specs)
@resolver = Resolver.new(@podfile, @lockfile, stub('sandbox'))
@resolver.update_mode = true
end
......@@ -326,7 +326,7 @@ module Pod
@resolver.should_install?("JSONKit").should.be.true
end
it "respecs the constraints of the pofile" do
it "respects the constraints of the pofile" do
podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
......@@ -339,10 +339,10 @@ module Pod
@resolver.should_install?("JSONKit").should.be.false
end
it "doesn't install new pods in `update_mode'" do
it "installs new pods" do
installed = @resolver.resolve.values.flatten.map(&:to_s)
installed.join(' ').should.not.include?('BlocksKit')
@resolver.should_install?("BlocksKit").should.be.false
installed.join(' ').should.include?('BlocksKit')
@resolver.should_install?("BlocksKit").should.be.true
end
it "it always suggests to update pods in head mode" do
......@@ -356,8 +356,15 @@ module Pod
@resolver.should_install?("libPusher").should.be.true
end
xit "it suggests to update pods from external sources" do
xit "it always suggests to update pods from external sources" do
podfile = Podfile.new do
platform :ios
pod 'libPusher', :git => "example.com"
end
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.update_mode = true
@resolver.resolve
@resolver.should_install?("libPusher").should.be.true
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