Commit a3c2efd9 authored by Fabio Pelosin's avatar Fabio Pelosin

Add Installer::PodSourceInstaller

parent 99bef96d
module Pod
class Installer
# Controller class responsible of installing the activated specifications
# of a single Pod.
#
# @note This class needs to consider all the activated specs of a Pod.
#
class PodSourceInstaller
# TODO: local option specs.
# TODO: add tests for multi platform / subspecs issues.
# @return [Sandbox]
#
attr_reader :sandbox
# @return [Hash{Symbol=>Array}] The specifications that need to be
# installed grouped by platform.
#
attr_reader :specs_by_platform
# @param [Sandbox] sandbox @see sandbox
# @param [Hash{Symbol=>Array}] specs_by_platform @see specs_by_platform
#
def initialize(sandbox, specs_by_platform)
@sandbox = sandbox
@specs_by_platform = specs_by_platform
@clean = true
@generate_docs = false
@install_docs = false
@agressive_cache = false
end
#-----------------------------------------------------------------------#
extend DependencyInjection
dependency :downloader_class, Downloader
dependency :docs_generator_class, Generator::Documentation
#-----------------------------------------------------------------------#
public
# @!group Configuration
# @return [Pathname] the path of the source of the Pod if using the
# `:local` option.
#
attr_accessor :local_path
# @return [Bool] whether the file not used by CocoaPods should be
# removed.
#
attr_accessor :clean
alias_method :clean?, :clean
# @return [Bool] whether the downloader should always check against the
# remote if issues might be generated (mostly useful to speed up
# testing).
#
# @note This might be removed in future.
#
attr_accessor :agressive_cache
alias_method :agressive_cache?, :agressive_cache
# @return [Bool] whether the documentation should be generated for the
# Pod.
#
attr_accessor :generate_docs
alias_method :generate_docs?, :generate_docs
# @return [Bool] whether the generated documentation should be installed
# in Xcode.
#
attr_accessor :install_docs
alias_method :install_docs?, :install_docs
#-----------------------------------------------------------------------#
public
# @!group Installation
# Creates the target in the Pods project and the relative support files.
#
# @return [void]
#
def install!
download_source unless predownloaded? || local?
generate_docs if generate_docs?
clean_installation if clean? && !local?
link_headers
end
# @return [Hash]
#
attr_reader :specific_source
#-----------------------------------------------------------------------#
private
# @!group Installation Steps
# Downloads the source of the Pod. It also stores the specific options
# needed to recreate the same exact installation if needed in
# `#specific_source`.
#
# @return [void]
#
def download_source
root.rmtree if root.exist?
if root_spec.version.head?
downloader.download_head
@specific_source = downloader.checkout_options
else
downloader.download
unless downloader.options_specific?
@specific_source = downloader.checkout_options
end
end
end
# Generates the documentation for the Pod.
#
# @return [void]
#
def generate_docs
if @cleaned
raise Informative, "Attempt to generate the documentation from a cleaned Pod."
end
if documentation_generator.already_installed?
UI.section " > Using existing documentation"
else
UI.section " > Installing documentation" do
documentation_generator.generate(install_docs?)
end
end
end
# Removes all the files not needed for the installation according to the
# specs by platform.
#
# @return [void]
#
def clean_installation
clean_paths.each { |path| FileUtils.rm_rf(path) }
@cleaned = true
end
# Creates the link to the headers of the Pod in the sandbox.
#
# @return [void]
#
def link_headers
headers_sandbox = Pathname.new(root_spec.name)
sandbox.build_headers.add_search_path(headers_sandbox)
sandbox.public_headers.add_search_path(headers_sandbox)
file_accessors.each do |file_accessor|
consumer = file_accessor.spec_consumer
header_mappings(headers_sandbox, consumer, file_accessor.headers).each do |namespaced_path, files|
sandbox.build_headers.add_files(namespaced_path, files)
end
header_mappings(headers_sandbox, consumer, file_accessor.public_headers).each do |namespaced_path, files|
sandbox.public_headers.add_files(namespaced_path, files)
end
end
end
#-----------------------------------------------------------------------#
public
# @!group Dependencies
# @return [String] The directory where CocoaPods caches the downloads.
#
CACHE_ROOT = "~/Library/Caches/CocoaPods"
# @return [Fixnum] The maximum size for the cache expressed in Mb.
#
MAX_CACHE_SIZE = 500
# @return [Downloader] The downloader to use for the retrieving the
# source.
#
def downloader
return @downloader if @downloader
@downloader = self.class.downloader_class.for_target(root, root_spec.source.dup)
@downloader.cache_root = CACHE_ROOT
@downloader.max_cache_size = MAX_CACHE_SIZE
@downloader.agressive_cache = agressive_cache?
@downloader
end
# @return [Generator::Documentation] The documentation generator to use
# for generating the documentation.
#
def documentation_generator
@documentation_generator ||= self.class.docs_generator_class.new(sandbox, root_spec, path_list)
end
#-----------------------------------------------------------------------#
private
# @!group Convenience methods.
# @return [Array<Specifications>] the specification of the Pod used in
# this installation.
#
def specs
specs_by_platform.values.flatten
end
# @return [Specification] the root specification of the Pod.
#
def root_spec
specs.first.root
end
# @return [Pathname] the folder where the source of the Pod is located.
#
def root
local? ? local_path : sandbox.pod_dir(root_spec.name)
end
# @return [Boolean] whether the source has been pre downloaded in the
# resolution process to retrieve its podspec.
#
def predownloaded?
sandbox.predownloaded_pods.include?(root_spec.name)
end
# @return [Boolean] whether the pod uses the local option and thus
# CocoaPods should not interfere with the files of the user.
#
def local?
!local_path.nil?
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).
#
# @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.end_with?('.', '..') || cached_used.any? do |path|
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(&:source_files),
file_accessors.map(&:resources),
file_accessors.map(&:preserve_paths),
file_accessors.map(&:prefix_header),
file_accessors.map(&:readme),
file_accessors.map(&:license),
]
files.compact!
files.flatten!
files.map!{ |path| path.to_s }
files
end
# Computes the destination sub-directory in the sandbox
#
# @param []
#
# @return [Hash{Pathname => Array<Pathname>}] A hash containing the
# headers folders as the keys and the absolute paths of the
# header files as the values.
#
# TODO This is being overridden in the RestKit 0.9.4 spec and that
# override should be fixed.
#
def header_mappings(headers_sandbox, consumer, headers)
dir = headers_sandbox
dir = base_dir + consumer.header_dir if consumer.header_dir
mappings = {}
headers.each do |header|
relative_path = header.relative_path_from(root)
sub_dir = dir
if consumer.header_mappings_dir
sub_dir = sub_dir + header.relative_path_from(consumer.header_mappings_dir).dirname
end
mappings[sub_dir] ||= []
mappings[sub_dir] << header
end
mappings
end
#-----------------------------------------------------------------------#
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
# module BaconFocusedMode; end
module Pod
describe Installer::PodSourceInstaller do
describe "In General" do
# TODO: describe defaults
end
#-------------------------------------------------------------------------#
describe "Installation" do
before do
@spec = fixture_spec('banana-lib/BananaLib.podspec')
@spec.source = { :git => SpecHelper.fixture('banana-lib') }
specs_by_platform = { :ios => [@spec] }
@installer = Installer::PodSourceInstaller.new(config.sandbox, specs_by_platform)
end
describe "Download" do
it "downloads the source" do
@spec.source = { :git => SpecHelper.fixture('banana-lib'), :tag => 'v1.0' }
@installer.install!
@installer.specific_source.should.be.nil
pod_folder = config.sandbox.root + 'BananaLib'
pod_folder.should.exist
end
it "downloads the head source if specified source" do
@spec.version.head = true
@spec.source = { :git => SpecHelper.fixture('banana-lib'), :tag => 'v1.0' }
@installer.install!
@installer.specific_source[:commit].should == "0b8b4084a43c38cfe308efa076fdeb3a64d9d2bc"
pod_folder = config.sandbox.root + 'BananaLib'
pod_folder.should.exist
end
it "returns the checkout options of the downloader if any" do
@spec.source = { :git => SpecHelper.fixture('banana-lib'), :branch => 'topicbranch' }
@installer.install!
@installer.specific_source[:commit].should == "446b22414597f1bb4062a62c4eed7af9627a3f1b"
pod_folder = config.sandbox.root + 'BananaLib'
pod_folder.should.exist
end
end
#--------------------------------------#
describe "Documentation" do
it "generates the documentation if needed" do
@installer.generate_docs = true
@installer.documentation_generator.expects(:generate)
@installer.install!
end
it "doesn't generates the documentation if it is already installed" do
@installer.generate_docs = true
@installer.documentation_generator.stubs(:already_installed?).returns(true)
@installer.documentation_generator.expects(:generate).never
@installer.install!
end
it "doesn't generates the documentation if disabled in the config" do
@installer.generate_docs = false
@installer.documentation_generator.expects(:generate).never
@installer.install!
end
end
#--------------------------------------#
describe "Cleaning" do
it "cleans the paths non used by the installation" do
@installer.clean = true
@installer.install!
unused_file = config.sandbox.root + 'BananaLib/sub-dir/sub-dir-2/somefile.txt'
unused_file.should.not.exist
end
it "preserves important files like the LICENSE and the README" do
@installer.clean = true
@installer.install!
readme_file = config.sandbox.root + 'BananaLib/README'
readme_file.should.exist
end
it "doesn't performs any cleaning if instructed to do so" do
@installer.clean = false
@installer.install!
unused_file = config.sandbox.root + 'BananaLib/sub-dir/sub-dir-2/somefile.txt'
unused_file.should.exist
end
end
#--------------------------------------#
describe "Headers" do
it "links the headers used to build the Pod library" do
@installer.install!
headers_root = config.sandbox.build_headers.root
public_header = headers_root+ 'BananaLib/Banana.h'
private_header = headers_root + 'BananaLib/BananaPrivate.h'
public_header.should.exist
private_header.should.exist
end
it "links the public headers" do
@installer.install!
headers_root = config.sandbox.public_headers.root
public_header = headers_root+ 'BananaLib/Banana.h'
private_header = headers_root + 'BananaLib/BananaPrivate.h'
public_header.should.exist
private_header.should.not.exist
end
end
end
#-------------------------------------------------------------------------#
describe "Private Helpers" do
#--------------------------------------#
describe "#clean_paths" do
end
#--------------------------------------#
describe "#used_files" do
end
#--------------------------------------#
describe "#header_mappings" do
xit "can handle the mappings headers of subspecs" do
end
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