Commit cfebf438 authored by Fabio Pelosin's avatar Fabio Pelosin

Merge branch 'core-extraction' into 0.17

* core-extraction: (75 commits)
  [Documenation] Clean-up.
  Fixes for core changes.
  [Gitignore] Added docs.
  [Rake] Added docs support.
  Make most of the specs green.
  [Sandbox] Added #specification.
  [Resolver] No need to pass the platform to external sources.
  [ExternalSources] Clean-up and docs.
  [Sandbox] Comments.
  [Resolver] Clean-up and specs update.
  [SpecHelper] Fine tune bacon helper.
  Updated master repo fixture.
  [Specs] Fix for master repo update & small performance improvements.
  [Gemfile] Use WebMock >= 1.8.0, < 1.9 as requested by VCR.
  Minor fixes.
  [Changelog]
  [SpecHelper] More refinement for bacon support.
  [TargetInstaller] Extracted XCConfig generation to Generator::XCConfing.
  Release 0.16.0
  [RakeFile] More fixes for release task.
  ...

Conflicts:
	CHANGELOG.md
parents a66b7cb3 042b51f9
......@@ -22,3 +22,4 @@ spec/fixtures/vcr
.yardoc
/doc
.rbx/
rakelib/doc
\ No newline at end of file
......@@ -6,9 +6,6 @@ process do |files|
specs = files.take_and_map do |file|
if file =~ %r{lib/cocoapods/(.+?)\.rb$}
s = Dir.glob("spec/**/#{File.basename(file, '.rb')}_spec.rb")
if file =~ %r{lib/cocoapods/installer.*\.rb$}
s.concat(['spec/unit/installer_spec.rb', 'spec/unit/installer/target_installer_spec.rb'])
end
s.uniq unless s.empty?
end
end
......
......@@ -2,8 +2,21 @@ language: ruby
rvm:
- 1.8.7
- 1.9.3
# Not in Travis non-pro yet
#- 2.0.0
# Rubinius in 1.8 mode on Travis does not work. It complains about st_data_t etc in Xcodeproj.
#- rbx-18mode
- rbx-19mode
matrix:
allow_failures:
- rvm: rbx-19mode
#- rvm: 2.0.0
install: NOEXEC=skip rake travis:setup
script: bundle exec rake spec
notifications:
# email: false
campfire:
on_success: change
on_failure: always
rooms:
- secure: "qOE5zmgaHe/qQu3W9rmj7wygA5Ivl+cx50fqWGag2bdRl8ly5yj1NVoOKk/O\nZmQc4Lze+301uvTXi+r5v8A/tF6W1kUZw7yBiKuXoYFUGmDiVR9o2I/FPwkL\ngSzPJttrXTQfkQ4PbnrkX+JO+5bLWrKaO0hKXT4B2yUu4UXLVk0="
## Branch 0.17
[CocoaPods](https://github.com/CocoaPods/CocoaPods/compare/master...0.17)
###### TODO
- TODO: Dropped script for resources.
- Add `s.exclude_source_files` and related attributes to the specification class.
###### DSL Changes
- Deprecated header_mappings hook for specifications.
- `preferred_dependency` has been renamed to `default_subspec`.
- Added `s.exclude_source_files` and related attributes to the specification class.
- Added support for prefix_header_file in subspecs
- Added support for prefix_header_contents in subspecs
- Removed exclude_header_search_paths
- Added screenshot attribute
- Renamed preferred_dependency
###### Enhancements
- Released [documentation](docs.cocoapods.org).
- Adds new subcommand `pod spec cat NAME` to print a spec file to standard output.
- Added Podfile to the Pods project.
- Major clean up and refactor to the code base.
- Extrace models to [CocoaPods-Core](https://github.com/CocoaPods/Core) gem.
- Extracted command-line command & option handling into
[CLAide](https://github.com/CocoaPods/CLAide).
- Added PathList class.
- Added Podfile to the Pods project.
[#476](https://github.com/CocoaPods/CocoaPods/issues/476)
- Extracted XCConfig generator.
- Adds new subcommand `pod spec cat NAME` to print a spec file to standard output.
## 0.16.0
[CocoaPods](https://github.com/CocoaPods/CocoaPods/compare/0.16.0.rc5...master)
###### Enhancements
- Use Rake 0.9.4
[#657](https://github.com/CocoaPods/CocoaPods/issues/657)
## 0.16.0.rc5
[CocoaPods](https://github.com/CocoaPods/CocoaPods/compare/0.16.0.rc4...0.16.0.rc5)
###### Deprecated
- The usage of specifications defined in a Podfile is deprecated. Use the
`:podspec` option with a file path instead. Complete removal will most
probably happen in 0.17.0.
[#549](https://github.com/CocoaPods/CocoaPods/issues/549)
[#616](https://github.com/CocoaPods/CocoaPods/issues/616)
[#525](https://github.com/CocoaPods/CocoaPods/issues/525)
###### Bug fixes
- Always consider inline podspecs as needing installation.
- Fix detection when the lib has already been integrated with the user’s target.
[#643](https://github.com/CocoaPods/CocoaPods/issues/643)
[#614](https://github.com/CocoaPods/CocoaPods/issues/614)
[#613](https://github.com/CocoaPods/CocoaPods/issues/613)
## 0.16.0.rc4
[CocoaPods](https://github.com/CocoaPods/CocoaPods/compare/0.16.0.rc3...0.16.0.rc4)
###### Bug fixes
- Fix for Rake 0.9.3
[#657](https://github.com/CocoaPods/CocoaPods/issues/657)
## 0.16.0.rc3
[CocoaPods](https://github.com/CocoaPods/CocoaPods/compare/0.16.0.rc2...0.16.0.rc3)[Xcodeproj](https://github.com/CocoaPods/Xcodeproj/compare/0.4.0.rc1...0.4.0.rc6)
###### Enhancements
## Master
[CocoaPods](https://github.com/CocoaPods/CocoaPods/compare/0.16.0.rc2...master)[Xcodeproj](https://github.com/CocoaPods/Xcodeproj/compare/0.4.0.rc1...master)
- Added support for copying frameworks to the app bundle.
[#597](https://github.com/CocoaPods/CocoaPods/pull/597)
###### Bug fixes
- Ignore PBXReferenceProxy while integrating into user project.
[#626](https://github.com/CocoaPods/CocoaPods/issues/626)
- Added support for PBXAggregateTarget and PBXLegacyTarget.
[#615](https://github.com/CocoaPods/CocoaPods/issues/615)
- Added support for PBXReferenceProxy.
......
......@@ -3,13 +3,15 @@ source "http://rubygems.org"
gemspec
group :development do
# gem "cocoapods-core", :git => "git://github.com/CocoaPods/Core.git"
gem "cocoapods-core", :path => "../Core"
gem "xcodeproj", :git => "git://github.com/CocoaPods/Xcodeproj.git"
gem "mocha", "~> 0.11.4"
gem "bacon"
gem "mocha-on-bacon"
gem "rake"
gem "vcr"
gem "webmock"
gem "webmock", '>= 1.8.0', '< 1.9'
end
group :debugging do
......@@ -23,5 +25,5 @@ group :documentation do
gem 'yard'
gem 'redcarpet'
gem 'github-markup'
gem 'pygments.rb'
end
GIT
remote: git://github.com/CocoaPods/Xcodeproj.git
revision: 97a5a444ecfadf52e42afdbbf054f078f7c2cc5d
revision: fbb5c11844b99bd954b2945098502dde0641c311
specs:
xcodeproj (0.4.0.rc5)
xcodeproj (0.4.0)
activesupport (~> 3.2.6)
colored (~> 1.2)
......@@ -18,22 +18,31 @@ GIT
PATH
remote: .
specs:
cocoapods (0.16.0.rc2)
cocoapods (0.17.0.alpha)
activesupport (~> 3.2.6)
claide (~> 0.1)
cocoapods-core
colored (~> 1.2)
escape (~> 0.0.4)
faraday (~> 0.8.1)
json (~> 1.7.3)
octokit (~> 1.7)
open4 (~> 1.3.0)
rake (~> 0.9.0)
xcodeproj (~> 0.4.0.rc1)
rake (~> 0.9.4)
xcodeproj (~> 0.4.0)
PATH
remote: ../Core
specs:
cocoapods-core (0.16.0.rc2)
activesupport (~> 3.2.6)
faraday (~> 0.8.1)
octokit (~> 1.7)
GEM
remote: http://rubygems.org/
specs:
activesupport (3.2.8)
activesupport (3.2.9)
i18n (~> 0.6)
multi_json (~> 1.0)
addressable (2.3.2)
......@@ -52,27 +61,31 @@ GEM
hashie (1.2.0)
i18n (0.6.1)
json (1.7.5)
listen (0.5.3)
listen (0.6.0)
metaclass (0.0.1)
method_source (0.8.1)
mocha (0.11.4)
metaclass (~> 0.0.1)
mocha-on-bacon (0.2.1)
mocha (>= 0.9.8)
multi_json (1.3.6)
multi_json (1.4.0)
multipart-post (1.1.5)
octokit (1.18.0)
octokit (1.19.0)
addressable (~> 2.2)
faraday (~> 0.8)
faraday_middleware (~> 0.8)
faraday_middleware (~> 0.9)
hashie (~> 1.2)
multi_json (~> 1.3)
open4 (1.3.0)
posix-spawn (0.3.6)
pry (0.9.10)
coderay (~> 1.0.5)
method_source (~> 0.8)
slop (~> 3.3.1)
rake (0.9.2.2)
pygments.rb (0.3.2)
posix-spawn (~> 0.3.6)
yajl-ruby (~> 1.1.0)
rake (0.9.5)
rb-fsevent (0.9.2)
redcarpet (2.2.2)
slop (3.3.3)
......@@ -81,6 +94,7 @@ GEM
webmock (1.8.11)
addressable (>= 2.2.7)
crack (>= 0.1.7)
yajl-ruby (1.1.0)
yard (0.8.3)
PLATFORMS
......@@ -90,15 +104,17 @@ DEPENDENCIES
awesome_print
bacon
cocoapods!
cocoapods-core!
github-markup
kicker!
mocha (~> 0.11.4)
mocha-on-bacon
pry
pygments.rb
rake
rb-fsevent
redcarpet
vcr
webmock
webmock (>= 1.8.0, < 1.9)
xcodeproj!
yard
......@@ -96,7 +96,7 @@ namespace :gem do
diff_lines.delete('CHANGELOG.md')
if diff_lines != ['lib/cocoapods.rb']
$stderr.puts "[!] Only change the version number in a release commit!"
# exit 1
exit 1
end
end
......@@ -108,7 +108,7 @@ namespace :gem do
required_xcodeproj_version = xcodeproj.requirement.requirements.first.last.to_s
puts "* Checking if xcodeproj #{required_xcodeproj_version} exists on the gem host"
search_result = silent_sh("gem search --pre --remote xcodeproj")
search_result = silent_sh("gem search --all --remote xcodeproj")
remote_xcodeproj_versions = search_result.match(/xcodeproj \((.*)\)/m)[1].split(', ')
unless remote_xcodeproj_versions.include?(required_xcodeproj_version)
$stderr.puts "[!] The Xcodeproj version `#{required_xcodeproj_version}' required by " \
......
......@@ -26,8 +26,9 @@ Gem::Specification.new do |s|
s.executables = %w{ pod }
s.require_paths = %w{ lib }
s.add_runtime_dependency 'cocoapods-core'
s.add_runtime_dependency 'claide', '~> 0.1'
s.add_runtime_dependency 'xcodeproj', '~> 0.4.0.rc1'
s.add_runtime_dependency 'xcodeproj', '~> 0.4.0'
s.add_runtime_dependency 'faraday', '~> 0.8.1'
s.add_runtime_dependency 'octokit', '~> 1.7'
......@@ -35,7 +36,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'escape', '~> 0.0.4'
s.add_runtime_dependency 'json', '~> 1.7.3'
s.add_runtime_dependency 'open4', '~> 1.3.0'
s.add_runtime_dependency 'rake', '~> 0.9.0'
s.add_runtime_dependency 'rake', '~> 0.9.4'
s.add_runtime_dependency 'activesupport', '~> 3.2.6'
s.add_development_dependency 'bacon', '~> 1.1'
......
......@@ -25,26 +25,17 @@ module Pod
end
autoload :Command, 'cocoapods/command'
autoload :Config, 'cocoapods/config'
autoload :Dependency, 'cocoapods/dependency'
autoload :Downloader, 'cocoapods/downloader'
autoload :Executable, 'cocoapods/executable'
autoload :ExternalSources, 'cocoapods/external_sources'
autoload :Installer, 'cocoapods/installer'
autoload :LocalPod, 'cocoapods/local_pod'
autoload :Lockfile, 'cocoapods/lockfile'
autoload :Platform, 'cocoapods/platform'
autoload :Podfile, 'cocoapods/podfile'
autoload :Project, 'cocoapods/project'
autoload :Resolver, 'cocoapods/resolver'
autoload :Sandbox, 'cocoapods/sandbox'
autoload :Source, 'cocoapods/source'
autoload :Spec, 'cocoapods/specification'
autoload :Specification, 'cocoapods/specification'
autoload :UI, 'cocoapods/user_interface'
autoload :Version, 'cocoapods/version_class'
autoload :Pathname, 'pathname'
autoload :FileList, 'cocoapods/file_list'
module Generator
autoload :BridgeSupport, 'cocoapods/generator/bridge_support'
......@@ -54,9 +45,17 @@ module Pod
autoload :Plist, 'cocoapods/generator/acknowledgements/plist'
autoload :Markdown, 'cocoapods/generator/acknowledgements/markdown'
autoload :DummySource, 'cocoapods/generator/dummy_source'
autoload :PrefixHeader, 'cocoapods/generator/prefix_header'
autoload :XCConfig, 'cocoapods/generator/xcconfig'
end
require 'cocoapods/file_list'
require 'cocoapods-core'
require 'cocoapods/config'
require 'cocoapods/source'
end
if ENV['COCOA_PODS_ENV'] == 'development'
require 'awesome_print'
require 'pry'
end
......@@ -7,8 +7,8 @@ module Pod
end
class Command < CLAide::Command
autoload :ErrorReport, 'cocoapods/command/error_report'
autoload :Linter, 'cocoapods/command/linter'
autoload :ErrorReport, 'cocoapods/command/error_report'
autoload :AdvancedLinter, 'cocoapods/command/advanced_linter'
self.abstract_command = true
self.command = 'pod'
......
module Pod
class Command
# Extends the Linter from the Core to add additional which require the
# LocalPod and the Installer.
#
# In detail it checks that the file patterns defined by the user match
# actually do match at least a file and that the Pod builds, by installing
# it without integration and building the project with xcodebuild.
#
class AdvancedLinter
include Config::Mixin
# @param [Specification, Pathname, String] spec_or_path
# the Specification or the path of the `podspec` file to lint.
#
def initialize(spec_or_path)
@simple_linter = Specification::Linter.new(spec_or_path)
end
# @return [Specification] the specification to lint.
#
def spec
@simple_linter.spec
end
# @return [Pathname] the path of the `podspec` file where {#spec} is
# defined.
#
def file
@simple_linter.file
end
# @return [Specification::Linter] the linter instance from CocoaPods
# Core.
#
attr_reader :simple_linter
# Lints the specification adding a {Result} for any failed check to the
# {#results} list.
#
# @return [Bool] whether the specification passed validation.
#
def lint
# Show immediately which pod is being processed.
print " -> #{spec.name}\r" unless config.silent?
$stdout.flush
simple_linter.lint
unless quick
check_repo_path
spec.available_platforms.each do |platform|
UI.section "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed do
# current_platform = platform
# set_up_lint_environment
# install_pod
# build_pod
# check_file_patterns
# tear_down_lint_environment
end
end
end
# This overwrites the previously printed text
UI.puts " -> ".send(color) << spec.name unless config.silent?
print_messages('ERROR', simple_linter.errors)
print_messages('WARN', simple_linter.warnings)
print_messages('NOTE', simple_linter.notes)
UI.puts unless config.silent?
errors.empty? && warnings.empty? && deprecations.empty?
end
#-----------------------------------------------------------------------#
# @!group Configuration
# @return [Bool] whether the lint should skip the checks that requires
# the download or the build of the library.
#
attr_accessor :quick
# @return [Bool] whether the linter should not clean up temporary files
# for inspection.
#
attr_accessor :no_clean
# @return [Pathname] whether the lint should be performed
#
attr_accessor :repo_path
# @return [Bool] whether the lint should be performed against the root of
# the podspec instead to its original source. Uses the `:local` option
# of the Podfile.
#
attr_writer :local
def local?; @local; end
#-----------------------------------------------------------------------#
# !@group Lint results
public
# @return [Array<Result>] all the notes generated by the Linter.
#
def notes
@errors ||= results.select { |r| r.type == :note }
end
# TODO
def result_type
:note
end
#-----------------------------------------------------------------------#
private
# !@group Lint steps
def check_repo_path
return unless repo_path
expected_path = "#{spec.name}/#{spec.version}/#{spec.name}.podspec"
path = file.relative_path_from(repo_path).to_s
unless path.end_with?(expected_path)
error "Incorrect path, the path is `#{file}` and should be `#{expected_path}`"
end
end
def set_up_lint_environment
tmp_dir.rmtree if tmp_dir.exist?
tmp_dir.mkpath
@original_config = Config.instance.clone
config.project_root = tmp_dir
config.project_pods_root = tmp_dir + 'Pods'
config.silent = !config.verbose
config.integrate_targets = false
config.generate_docs = false
end
def tear_down_lint_environment
tmp_dir.rmtree unless no_clean
Config.instance = @original_config
end
# It creates a podfile in memory and builds a library containing
# the pod for all available platforms with xcodebuild.
#
def install_pod
spec.activate_platform(current_platform)
podfile = podfile_from_spec
config.verbose
config.skip_repo_update = true
sandbox = Sandbox.new(config.project_pods_root)
resolver = Resolver.new(podfile, nil, sandbox)
installer = Installer.new(resolver)
installer.install!
@pod = installer.pods.find { |pod| pod.top_specification == spec }
config.silent
end
# Performs platform specific analysis.
# It requires to download the source at each iteration
#
# @note Treat xcodebuild warnings as notes because the spec maintainer
# might not be the author of the library
#
def build_pod
if `which xcodebuild`.strip.empty?
UI.warn "Skipping compilation with `xcodebuild' because it can't be found.\n".yellow
else
UI.message "\nBuilding with xcodebuild.\n".yellow if config.verbose? do
messages = []
output = Dir.chdir(config.project_pods_root) { `xcodebuild clean build 2>&1` }
clean_output = parse_xcodebuild_output(output)
messages += clean_output
puts(output) if config.verbose?
messages.each { |msg| ( msg.include?('error: ') ? @platform_errors[@platform] : @platform_notes[@platform] ) << msg }
end
end
end
# It checks that every file pattern specified in a spec yields
# at least one file. It requires the pods to be already present
# in the current working directory under Pods/spec.name.
#
# @return [Array<String>]
#
def check_file_patterns
error "The sources did not match any file" if !spec.source_files.empty? && @pod.source_files.empty?
error "The resources did not match any file" if !spec.resources.empty? && @pod.resource_files.empty?
error "The preserve_paths did not match any file" if !spec.preserve_paths.empty? && @pod.preserve_files.empty?
error "The exclude_header_search_paths did not match any file" if !spec.exclude_header_search_paths.empty? && @pod.headers_excluded_from_search_paths.empty?
unless @pod.license_file || spec.license && ( spec.license[:type] == 'Public Domain' || spec.license[:text] )
warning "Unable to find a license file"
end
end
# @errors += (@platform_errors[platform] - @errors).map {|m| "[#{platform}] #{m}"}
#-----------------------------------------------------------------------#
private
# !@group Helpers
# @return [Podfile] a podfile that requires the specification on the
# current platform.
#
# @note The generated podfile takes into account whether the linter is
# in local mode.
#
def podfile_from_spec
name = spec.name
podspec = file.realpath
platform = current_platform
local = local?
podfile = Pod::Podfile.new do
platform(platform.to_sym, platform.deployment_target)
if (local)
pod name, :local => podspec.dirname.to_s
else
pod name, :podspec => podspec.to_s
end
end
podfile
end
# @return [Pathname] the temporary directory used by the linter.
#
def tmp_dir
Pathname.new('/tmp/CocoaPods/Lint')
end
# @return [Pathname] the root of the installed pod.
#
def pod_dir
tmp_dir + 'Pods' + spec.name
end
# Parse the xcode build output to identify the lines which are relevant
# to the linter. It also removes the indentation and the temporary path.
#
# @param [String] output the output generated by the xcodebuild tool.
#
# @return [Array<String>] the lines that are relevant to the linter.
#
def parse_xcodebuild_output(output)
lines = output.split("\n")
selected_lines = lines.select do |l|
l.include?('error: ') &&
(l !~ /errors? generated\./) && (l !~ /error: \(null\)/) ||
l.include?('warning: ') && (l !~ /warnings? generated\./) ||
l.include?('note: ') && (l !~ /expanded from macro/)
end
selected_lines.map do |l|
new = l.gsub(/\/tmp\/CocoaPods\/Lint\/Pods\//,'') # Remove the unnecessary tmp path
new.gsub!(/^ */,' ') # Remove indentation
"XCODEBUILD > " << new # Mark
end
end
#
#
def print_messages(type, messages)
return if config.silent?
messages.each {|msg| UI.puts " - #{type.ljust(5)} | #{msg}"}
end
end
end
end
module Pod
class Command
class Linter
include Config::Mixin
# TODO: Add check to ensure that attributes inherited by subspecs are not duplicated ?
attr_accessor :quick, :no_clean, :repo_path
# @return [Bool] Wether the lint should be performed against the root of
# the podspec instead to its original source. Uses the `:local` option
# of the Podfile.
#
attr_accessor :local
alias :local? :local
attr_reader :spec, :file
attr_reader :errors, :warnings, :notes
def initialize(podspec)
@file = podspec
end
def spec_name
name = file.basename('.*').to_s
if @spec
name << " (#{spec.version})"
elsif @repo_path
name << " (#{file.dirname.basename})"
end
name
end
# Takes an array of podspec files and lints them all
#
# It returns true if the spec passed validation
#
def lint
@errors, @warnings, @notes = [], [], []
@platform_errors, @platform_warnings, @platform_notes = {}, {}, {}
if !deprecation_errors.empty?
@errors = deprecation_errors
@errors << "#{spec_name} [!] Fatal errors found skipping the rest of the validation"
else
@spec = Specification.from_file(file)
platforms = spec.available_platforms
if @repo_path
expected_path = "#{@spec.name}/#{@spec.version}/#{@spec.name}.podspec"
path = file.relative_path_from(@repo_path).to_s
@errors << "Incorrect path, the path is `#{file}` and should be `#{expected_path}`" unless path.end_with?(expected_path)
end
platforms.each do |platform|
@platform_errors[platform], @platform_warnings[platform], @platform_notes[platform] = [], [], []
spec.activate_platform(platform)
@platform = platform
puts "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed if config.verbose? && !@quick
# Skip validation if there are errors in the podspec as it would result in a crash
if !podspec_errors.empty?
@platform_errors[platform] += podspec_errors
@platform_notes[platform] << "#{platform.name} [!] Fatal errors found skipping the rest of the validation"
else
@platform_warnings[platform] += podspec_warnings
peform_extensive_analysis unless quick
end
end
# Get common messages
@errors += @platform_errors.values.reduce(:&)
@warnings += @platform_warnings.values.reduce(:&)
@notes += @platform_notes.values.reduce(:&)
platforms.each do |platform|
# Mark platform specific messages
@errors += (@platform_errors[platform] - @errors).map {|m| "[#{platform}] #{m}"}
@warnings += (@platform_warnings[platform] - @warnings).map {|m| "[#{platform}] #{m}"}
@notes += (@platform_notes[platform] - @notes).map {|m| "[#{platform}] #{m}"}
end
end
end
def result_type
return :error unless errors.empty?
return :warning unless warnings.empty?
return :note unless notes.empty?
:success
end
# Performs platform specific analysis.
# It requires to download the source at each iteration
#
def peform_extensive_analysis
set_up_lint_environment
install_pod
if `which xcodebuild`.strip.empty?
puts "Skipping compilation with `xcodebuild' because it can't be found.\n".yellow if config.verbose?
else
puts "\nBuilding with xcodebuild.\n".yellow if config.verbose?
# treat xcodebuild warnings as notes because the spec maintainer might not be the author of the library
xcodebuild_output.each { |msg| ( msg.include?('error: ') ? @platform_errors[@platform] : @platform_notes[@platform] ) << msg }
end
@platform_errors[@platform] += file_patterns_errors
@platform_warnings[@platform] += file_patterns_warnings
tear_down_lint_environment
end
def install_pod
podfile = podfile_from_spec
config.verbose
config.skip_repo_update = true
sandbox = Sandbox.new(config.project_pods_root)
resolver = Resolver.new(podfile, nil, sandbox)
installer = Installer.new(resolver)
installer.install!
@pod = installer.pods.find { |pod| pod.top_specification == spec }
config.silent
end
def podfile_from_spec
name = spec.name
podspec = file.realpath
platform = @platform
local = local?
podfile = Pod::Podfile.new do
platform(platform.to_sym, platform.deployment_target)
if (local)
pod name, :local => podspec.dirname.to_s
else
pod name, :podspec => podspec.to_s
end
end
podfile
end
def set_up_lint_environment
tmp_dir.rmtree if tmp_dir.exist?
tmp_dir.mkpath
@original_config = Config.instance.clone
config.project_root = tmp_dir
config.project_pods_root = tmp_dir + 'Pods'
config.silent = !config.verbose
config.integrate_targets = false
config.generate_docs = false
end
def tear_down_lint_environment
tmp_dir.rmtree unless no_clean
Config.instance = @original_config
end
def tmp_dir
Pathname.new('/tmp/CocoaPods/Lint')
end
def pod_dir
tmp_dir + 'Pods' + spec.name
end
# It reads a podspec file and checks for strings corresponding
# to features that are or will be deprecated
#
# @return [Array<String>]
#
def deprecation_errors
text = @file.read
deprecations = []
deprecations << "`config.ios?' and `config.osx?' are deprecated" if text. =~ /config\..?os.?/
deprecations << "clean_paths are deprecated and ignored (use preserve_paths)" if text. =~ /clean_paths/
deprecations
end
# @return [Array<String>] List of the fatal defects detected in a podspec
def podspec_errors
messages = []
messages << "The name of the spec should match the name of the file" unless names_match?
messages << "Unrecognized platfrom" unless platform_valid?
messages << "Missing name" unless spec.name
messages << "Missing version" unless spec.version
messages << "Missing summary" if !spec.summary || spec.summary.empty?
messages << "Missing homepage" unless spec.homepage
messages << "Missing author(s)" unless spec.authors
messages << "Missing or invalid source: #{spec.source}" unless source_valid?
messages << "The summary should be short use `description` (max 140 characters)." if spec.summary && spec.summary.length > 140
# attributes with multiplatform values
return messages unless platform_valid?
messages << "The spec appears to be empty (no source files, resources, or preserve paths)" if spec.source_files.empty? && spec.subspecs.empty? && spec.resources.empty? && spec.preserve_paths.empty?
messages += paths_starting_with_a_slash_errors
messages += deprecation_errors
messages
end
def names_match?
return true unless spec.name
root_name = spec.name.match(/[^\/]*/)[0]
file.basename.to_s == root_name + '.podspec'
end
def platform_valid?
!spec.platform || [:ios, :osx].include?(spec.platform.name)
end
def source_valid?
spec.source && !(spec.source =~ /http:\/\/EXAMPLE/)
end
def paths_starting_with_a_slash_errors
messages = []
%w[source_files public_header_files resources clean_paths].each do |accessor|
patterns = spec.send(accessor.to_sym)
# Some values are multiplaform
patterns = patterns.is_a?(Hash) ? patterns.values.flatten(1) : patterns
patterns = patterns.compact # some patterns may be nil (public_header_files, for instance)
patterns.each do |pattern|
# Skip FileList that would otherwise be resolved from the working directory resulting
# in a potentially very expensi operation
next if pattern.is_a?(FileList)
invalid = pattern.is_a?(Array) ? pattern.any? { |path| path.start_with?('/') } : pattern.start_with?('/')
if invalid
messages << "Paths cannot start with a slash (#{accessor})"
break
end
end
end
messages
end
# @return [Array<String>] List of the **non** fatal defects detected in a podspec
def podspec_warnings
license = spec.license || {}
source = spec.source || {}
text = @file.read
messages = []
messages << "Missing license type" unless license[:type]
messages << "Sample license type" if license[:type] && license[:type] =~ /\(example\)/
messages << "Invalid license type" if license[:type] && license[:type] =~ /\n/
messages << "The summary is not meaningful" if spec.summary =~ /A short description of/
messages << "The description is not meaningful" if spec.description && spec.description =~ /An optional longer description of/
messages << "The summary should end with a dot" if spec.summary !~ /.*\./
messages << "The description should end with a dot" if spec.description !~ /.*\./ && spec.description != spec.summary
messages << "The summary should end with a dot" if spec.summary !~ /.*\./
messages << "Comments must be deleted" if text.scan(/^\s*#/).length > 24
messages << "Warnings must not be disabled (`-Wno' compiler flags)" if spec.compiler_flags.split(' ').any? {|flag| flag.start_with?('-Wno') }
if (git_source = source[:git])
messages << "Git sources should specify either a tag or a commit" unless source[:commit] || source[:tag]
if spec.version.to_s != '0.0.1'
messages << "The version of the spec should be part of the git tag (not always applicable)" if source[:tag] && !source[:tag].include?(spec.version.to_s)
messages << "Git sources without tag should be marked as 0.0.1 (not always applicable)" if !source[:tag]
end
if git_source.include?('github.com')
messages << "Github repositories should end in `.git'" unless git_source.end_with?('.git')
messages << "Github repositories should use `https' link" unless git_source.start_with?('https://github.com') || git_source.start_with?('git://gist.github.com')
end
end
messages
end
# It creates a podfile in memory and builds a library containing
# the pod for all available platfroms with xcodebuild.
#
# @return [Array<String>]
#
def xcodebuild_output
return [] if `which xcodebuild`.strip.empty?
messages = []
output = Dir.chdir(config.project_pods_root) { `xcodebuild clean build 2>&1` }
clean_output = process_xcode_build_output(output)
messages += clean_output
puts(output) if config.verbose?
messages
end
def process_xcode_build_output(output)
output_by_line = output.split("\n")
selected_lines = output_by_line.select do |l|
l.include?('error: ') && (l !~ /errors? generated\./) && (l !~ /error: \(null\)/)\
|| l.include?('warning: ') && (l !~ /warnings? generated\./)\
|| l.include?('note: ') && (l !~ /expanded from macro/)
end
selected_lines.map do |l|
new = l.gsub(/\/tmp\/CocoaPods\/Lint\/Pods\//,'') # Remove the unnecessary tmp path
new.gsub!(/^ */,' ') # Remove indentation
"XCODEBUILD > " << new # Mark
end
end
# It checks that every file pattern specified in a spec yields
# at least one file. It requires the pods to be alredy present
# in the current working directory under Pods/spec.name.
#
# @return [Array<String>]
#
def file_patterns_errors
messages = []
messages << "The sources did not match any file" if !spec.source_files.empty? && @pod.source_files.empty?
messages << "The resources did not match any file" if !spec.resources.empty? && @pod.resource_files.empty?
messages << "The preserve_paths did not match any file" if !spec.preserve_paths.empty? && @pod.preserve_files.empty?
messages << "The exclude_header_search_paths did not match any file" if !spec.exclude_header_search_paths.empty? && @pod.headers_excluded_from_search_paths.empty?
messages
end
def file_patterns_warnings
messages = []
unless @pod.license_file || spec.license && ( spec.license[:type] == 'Public Domain' || spec.license[:text] )
messages << "Unable to find a license file"
end
messages
end
end
end
end
......@@ -40,13 +40,14 @@ module Pod
self.summary = 'Lists pods introduced in the master spec-repo since the last check'
def run
puts "running"
update_if_necessary!
days = [1,2,3,5,8]
dates, groups = {}, {}
days.each {|d| dates[d] = Time.now - 60 * 60 * 24 * d}
sets = Source.all_sets
creation_dates = Pod::Specification::Statistics.instance.creation_dates(sets)
creation_dates = Specification::Set::Statistics.instance.creation_dates(sets)
sets.each do |set|
set_date = creation_dates[set.name]
......
......@@ -19,10 +19,10 @@ module Pod
end
def run_install_with_update(update)
sandbox = Sandbox.new(config.project_pods_root)
resolver = Resolver.new(config.podfile, config.lockfile, sandbox)
resolver.update_mode = update
Installer.new(resolver).install!
sandbox = Sandbox.new(config.project_pods_root)
installer = Installer.new(sandbox, config.podfile, config.lockfile)
installer.update_mode = update
installer.install!
end
end
......
......@@ -87,7 +87,7 @@ module Pod
lint_argv = ["lint"]
lint_argv << "--only-errors" if @allow_warnings
lint_argv << "--silent" if config.silent
all_valid = true
# all_valid = true
podspec_files.each do |podspec|
Spec.parse(lint_argv + [podspec.to_s]).run
end
......
......@@ -107,7 +107,7 @@ module Pod
invalid_count = 0
podspecs.each do |podspec|
linter = Linter.new(podspec)
linter = AdvancedLinter.new(podspec)
linter.quick = true
linter.repo_path = dir
......@@ -178,6 +178,8 @@ module Pod
is_compatilbe(versions)
end
#--------------------------------------#
private
def versions(dir)
......
......@@ -32,7 +32,13 @@ module Pod
def run
sets = Source.search_by_name(@query.strip, @full_text_search)
sets.each { |set| UI.pod(set, (@stats ? :stats : :normal)) }
sets.each do |set|
begin
UI.pod(set, (@stats ? :stats : :normal))
rescue DSLError => e
UI.warn "Skipping `#{set.name}` because the podspec contains errors."
end
end
end
end
end
......
......@@ -84,16 +84,11 @@ module Pod
UI.puts
invalid_count = 0
podspecs_to_lint.each do |podspec|
linter = Linter.new(podspec)
linter.quick = @quick
linter.local = @local
linter = AdvancedLinter.new(podspec)
linter.quick = @quick
linter.local = @local
linter.no_clean = @no_clean
# Show immediatly which pod is being processed.
print " -> #{linter.spec_name}\r" unless config.silent?
$stdout.flush
linter.lint
case linter.result_type
when :error
invalid_count += 1
......@@ -104,14 +99,6 @@ module Pod
else
color = :green
end
# This overwrites the previously printed text
UI.puts " -> ".send(color) << linter.spec_name unless config.silent?
print_messages('ERROR', linter.errors)
print_messages('WARN', linter.warnings)
print_messages('NOTE', linter.notes)
UI.puts unless config.silent?
end
count = podspecs_to_lint.count
......@@ -179,11 +166,6 @@ module Pod
# TODO some of the following methods can probably move to one of the subclasses.
private
def print_messages(type, messages)
return if config.silent?
messages.each {|msg| UI.puts " - #{type.ljust(5)} | #{msg}"}
end
def podspecs_to_lint
@podspecs_to_lint ||= begin
files = []
......@@ -284,7 +266,7 @@ Pod::Spec.new do |s|
# s.description = <<-DESC
# An optional longer description of #{data[:name]}
#
# * Markdonw format.
# * Markdown format.
# * Don't worry about the indent, we strip it!
# DESC
s.homepage = "#{data[:homepage]}"
......@@ -318,7 +300,7 @@ Pod::Spec.new do |s|
#
# s.author = '#{data[:author_name]}', 'other author'
# Specify the location from where the source should be retreived.
# Specify the location from where the source should be retrieved.
#
s.source = { :git => "#{data[:source_url]}", #{data[:ref_type]} => "#{data[:ref]}" }
# s.source = { :svn => 'http://EXAMPLE/#{data[:name]}/tags/1.0.0' }
......@@ -352,9 +334,9 @@ Pod::Spec.new do |s|
# made available to the application. If the pattern is a directory then the
# path will automatically have '*.h' appended.
#
# Also allows the use of the FileList class like `source_files does.
# Also allows the use of the FileList class like `source_files' does.
#
# If you do not explicitely set the list of public header files,
# If you do not explicitly set the list of public header files,
# all headers of source_files will be made public.
#
# s.public_header_files = 'Classes/**/*.h'
......@@ -362,7 +344,7 @@ Pod::Spec.new do |s|
# A list of resources included with the Pod. These are copied into the
# target bundle with a build phase script.
#
# Also allows the use of the FileList class like `source_files does.
# Also allows the use of the FileList class like `source_files' does.
#
# s.resource = "icon.png"
# s.resources = "Resources/*.png"
......@@ -370,7 +352,7 @@ Pod::Spec.new do |s|
# A list of paths to preserve after installing the Pod.
# CocoaPods cleans by default any file that is not used.
# Please don't include documentation, example, and test files.
# Also allows the use of the FileList class like `source_files does.
# Also allows the use of the FileList class like `source_files' does.
#
# s.preserve_paths = "FilesToSave", "MoreFilesToSave"
......
......@@ -74,6 +74,12 @@ module Pod
end
end
# @return [Sandbox]
#
def sandbox
@sandbox ||= Sandbox.new(project_pods_root)
end
module Mixin
def config
Config.instance
......
require 'cocoapods/open_uri'
module Pod
class Dependency < Gem::Dependency
attr_reader :head
alias :head? :head
attr_accessor :specification, :external_source
def initialize(*name_and_version_requirements, &block)
if name_and_version_requirements.empty? && block
@inline_podspec = true
@specification = Specification.new(&block)
super(@specification.name, @specification.version)
elsif !name_and_version_requirements.empty? && block.nil?
version = name_and_version_requirements.last
if name_and_version_requirements.last.is_a?(Hash)
@external_source = ExternalSources.from_params(name_and_version_requirements[0].split('/').first, name_and_version_requirements.pop)
elsif version.is_a?(Symbol) && version == :head || version.is_a?(Version) && version.head?
name_and_version_requirements.pop
@head = true
end
super(*name_and_version_requirements)
if head? && !latest_version?
raise Informative, "A `:head' dependency may not specify version requirements."
end
else
raise Informative, "A dependency needs either a name and version requirements, " \
"a source hash, or a block which defines a podspec."
end
end
def latest_version?
versions = @version_requirements.requirements.map(&:last)
versions == [Gem::Version.new('0')]
end
def ==(other)
super && (head? == other.head?) && (@specification ? @specification == other.specification : @external_source == other.external_source)
end
def subspec_dependency?
@name.include?('/')
end
def inline?
@inline_podspec
end
def external?
!@external_source.nil?
end
# In case this is a dependency for a subspec, e.g. 'RestKit/Networking',
# this returns 'RestKit', which is what the Pod::Source needs to know to
# retrieve the correct Set from disk.
def top_level_spec_name
subspec_dependency? ? @name.split('/').first : @name
end
# Returns a copy of the dependency, but with the name of the top level
# spec. This is used by Pod::Specification::Set to merge dependencies on
# the complete set, irrespective of what spec in the set wil be used.
def to_top_level_spec_dependency
dep = dup
dep.name = top_level_spec_name
dep
end
def to_s
version = ''
if external?
version << @external_source.description
elsif inline?
version << 'defined in Podfile'
elsif head?
version << 'HEAD'
elsif @version_requirements != Gem::Requirement.default
version << @version_requirements.to_s
end
result = @name.dup
result << " (#{version})" unless version.empty?
result
end
def specification_from_sandbox(sandbox, platform)
@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)
pattern = name
if Regexp === pattern
return false unless pattern =~ spec_name
else
return false unless pattern == spec_name
end
return true if requirement.to_s == ">= 0"
requirement.satisfied_by? Gem::Version.new(spec_version)
end
end
# Taken from a newer version of RubyGems
unless public_method_defined?(:merge)
def merge other
unless name == other.name then
raise ArgumentError,
"#{self} and #{other} have different names"
end
default = Gem::Requirement.default
self_req = self.requirement
other_req = other.requirement
return self.class.new name, self_req if other_req == default
return self.class.new name, other_req if self_req == default
self.class.new name, self_req.as_list.concat(other_req.as_list)
end
end
module ExternalSources
def self.from_params(name, params)
return unless name && params
if params.key?(:git)
GitSource.new(name, params)
elsif params.key?(:podspec)
PodspecSource.new(name, params)
elsif params.key?(:local)
LocalSource.new(name, params)
else
raise Informative, "Unknown external source parameters for #{name}: #{params}"
end
end
class AbstractExternalSource
include Config::Mixin
attr_reader :name, :params
def initialize(name, params)
@name, @params = name, params
end
def specification_from_sandbox(sandbox, platform)
specification_from_local(sandbox, platform) || specification_from_external(sandbox, platform)
end
def specification_from_local(sandbox, platform)
if local_pod = sandbox.installed_pod_named(name, platform)
local_pod.top_specification
end
end
def specification_from_external(sandbox, platform)
podspec = copy_external_source_into_sandbox(sandbox, platform)
spec = specification_from_local(sandbox, platform)
raise Informative, "No podspec found for `#{name}' in #{description}" unless spec
spec
end
# Can store from a pathname or a string
#
def store_podspec(sandbox, podspec)
output_path = sandbox.root + "Local Podspecs/#{name}.podspec"
output_path.dirname.mkpath
if podspec.is_a?(String)
raise Informative, "No podspec found for `#{name}' in #{description}" unless podspec.include?('Spec.new')
output_path.open('w') { |f| f.puts(podspec) }
else
raise Informative, "No podspec found for `#{name}' in #{description}" unless podspec.exist?
FileUtils.copy(podspec, output_path)
end
end
def ==(other)
return if other.nil?
name == other.name && params == other.params
end
end
class GitSource < AbstractExternalSource
def copy_external_source_into_sandbox(sandbox, platform)
UI.info("->".green + " Pre-downloading: '#{name}'") do
target = sandbox.root + name
target.rmtree if target.exist?
downloader = Downloader.for_target(sandbox.root + name, @params)
downloader.download
store_podspec(sandbox, target + "#{name}.podspec")
if local_pod = sandbox.installed_pod_named(name, platform)
local_pod.downloaded = true
end
end
end
def description
"from `#{@params[:git]}'".tap do |description|
description << ", commit `#{@params[:commit]}'" if @params[:commit]
description << ", branch `#{@params[:branch]}'" if @params[:branch]
description << ", tag `#{@params[:tag]}'" if @params[:tag]
end
end
end
# can be http, file, etc
class PodspecSource < AbstractExternalSource
def copy_external_source_into_sandbox(sandbox, _)
UI.info("->".green + " Fetching podspec for `#{name}' from: #{@params[:podspec]}") do
path = @params[:podspec]
path = Pathname.new(path).expand_path if path.start_with?("~")
open(path) { |io| store_podspec(sandbox, io.read) }
end
end
def description
"from `#{@params[:podspec]}'"
end
end
class LocalSource < AbstractExternalSource
def pod_spec_path
path = Pathname.new(@params[:local]).expand_path
path += "#{name}.podspec"# unless path.to_s.include?("#{name}.podspec")
raise Informative, "No podspec found for `#{name}' in `#{@params[:local]}'" unless path.exist?
path
end
def copy_external_source_into_sandbox(sandbox, _)
store_podspec(sandbox, pod_spec_path)
end
def specification_from_local(sandbox, platform)
specification_from_external(sandbox, platform)
end
def specification_from_external(sandbox, platform)
copy_external_source_into_sandbox(sandbox, platform)
spec = Specification.from_file(pod_spec_path)
spec.source = @params
spec
end
def description
"from `#{@params[:local]}'"
end
end
end
end
end
module Pod
# Provides support for initializing the correct concrete class of an external
# source.
#
module ExternalSources
# @return [AbstractExternalSource] an initialized instance of the concrete
# external source class associated with the option specified in the
# hash.
#
def self.from_dependency(dependency)
name = dependency.root_name
params = dependency.external_source
klass = if params.key?(:git) then GitSource
elsif params.key?(:podspec) then PodspecSource
elsif params.key?(:local) then LocalSource
end
if klass
klass.new(name, params)
else
msg = "Unknown external source parameters for `#{name}`: `#{params}`"
raise Informative, msg
end
end
#-------------------------------------------------------------------------#
# Abstract class that defines the common behaviour of external sources.
#
class AbstractExternalSource
# @return [String] the name of the Pod described by this external source.
#
attr_reader :name
# @return [Hash{Symbol => String}] the hash representation of the
# external source.
#
attr_reader :params
# @param [String] name @see name
# @param [Hash] params @see params
#
def initialize(name, params)
@name, @params = name, params
end
# @return [Bool] whether an external source source is equal to another
# according to the {#name} and to the {#params}.
#
def ==(other)
return false if other.nil?
name == other.name && params == other.params
end
#--------------------------------------#
# @!group Specifications
public
# @return [Specification] returns the specification, either from the
# sandbox or by fetching the remote source, associated with the
# external source.
#
def specification(sandbox)
specification_from_local(sandbox) || specification_from_external(sandbox)
end
# @return [Specification] returns the specification associated with the
# external source if available in the sandbox.
#
def specification_from_local(sandbox)
sandbox.specification(name)
end
# @return [Specification] returns the specification associated with the
# external source after fetching it from the remote source, even
# if is already present in the sandbox.
#
# @raise If not specification could be found.
#
def specification_from_external(sandbox)
copy_external_source_into_sandbox(sandbox)
spec = specification_from_local(sandbox)
unless spec
raise Informative, "No podspec found for `#{name}' in #{description}"
end
spec
end
#--------------------------------------#
# @!group Subclasses hooks
# Fetches the external source from the remote according to the params.
#
# @param [Sandbox] sandbox
# the sandbox where the specification should be stored.
#
# @return [void]
#
def copy_external_source_into_sandbox(sandbox)
raise "Abstract method"
end
# @return [String] a string representation of the source suitable for UI.
#
def description
raise "Abstract method"
end
private
# Stores a specification in the `Local Podspecs` folder.
#
# @param [Sandbox] sandbox
# the sandbox where the podspec should be stored.
#
# @param [String, Pathname] podspec
# The contents of the specification (String) or the path to a
# podspec file (Pathname).
#
# @todo This could be done by the sandbox.
# @todo Store all the specifications (including those not originating
# from external sources) so users can check them.
# @todo The check for the podspec string is a bit primitive.
#
def store_podspec(sandbox, podspec)
output_path = sandbox.root + "Local Podspecs/#{name}.podspec"
output_path.dirname.mkpath
if podspec.is_a?(String)
unless podspec.include?('Spec.new')
raise Informative, "The `#{name}.podspec` from `#{description}` appears to be invalid."
end
output_path.open('w') { |f| f.puts(podspec) }
else
unless podspec.exist?
raise Informative, "No podspec found for `#{name}` in #{description}"
end
FileUtils.copy(podspec, output_path)
end
end
end
#-------------------------------------------------------------------------#
# Provides support for fetching a specification file from a Git remote.
# Supports all the options of the downloader (is similar to the git key of
# `source` attribute of a specification).
#
# @note The podspec must be in the root of the repository and should have a
# name matching the one of the dependency.
#
class GitSource < AbstractExternalSource
# @see AbstractExternalSource#copy_external_source_into_sandbox
#
# @note To prevent a double download of the repository the pod is marked
# as pre-downloaded indicating to the installer that only clean
# operations are needed.
#
def copy_external_source_into_sandbox(sandbox)
UI.info("->".green + " Pre-downloading: '#{name}'") do
target = sandbox.root + name
target.rmtree if target.exist?
downloader = Downloader.for_target(sandbox.root + name, @params)
downloader.download
store_podspec(sandbox, target + "#{name}.podspec")
sandbox.predownloaded_pods << name
end
end
# @see AbstractExternalSource#description
#
def description
"from `#{@params[:git]}'".tap do |description|
description << ", commit `#{@params[:commit]}`" if @params[:commit]
description << ", branch `#{@params[:branch]}`" if @params[:branch]
description << ", tag `#{@params[:tag]}`" if @params[:tag]
end
end
end
#-------------------------------------------------------------------------#
# Provides support for fetching a specification file from an URL. Can be
# http, file, etc.
#
class PodspecSource < AbstractExternalSource
# @see AbstractExternalSource#copy_external_source_into_sandbox
#
def copy_external_source_into_sandbox(sandbox)
UI.info("->".green + " Fetching podspec for `#{name}' from: #{@params[:podspec]}") do
path = @params[:podspec]
path = Pathname.new(path).expand_path if path.to_s.start_with?("~")
open(path) { |io| store_podspec(sandbox, io.read) }
end
end
# @see AbstractExternalSource#description
#
def description
"from `#{@params[:podspec]}`"
end
end
#-------------------------------------------------------------------------#
# Provides support for fetching a specification file from a path local to
# the machine running the installation.
#
# Works with the {LocalPod::LocalSourcedPod} class.
#
class LocalSource < AbstractExternalSource
# @see AbstractExternalSource#copy_external_source_into_sandbox
#
def copy_external_source_into_sandbox(sandbox)
store_podspec(sandbox, pod_spec_path)
end
# @see AbstractExternalSource#description
#
def description
"from `#{@params[:local]}`"
end
# @see AbstractExternalSource#specification_from_local
#
# @note The LocalSource class always fetches podspecs from the external
# source to provide always the freshest specification. Otherwise,
# once installed, the podspec would be updated only by `pod
# update`.
#
def specification_from_local(sandbox)
specification_from_external(sandbox)
end
# @see AbstractExternalSource#specification_from_local
#
# @note The LocalSource overrides the source of the specification to
# point to the local path.
#
def specification_from_external(sandbox)
copy_external_source_into_sandbox(sandbox)
spec = Specification.from_file(pod_spec_path)
spec.source = @params
spec
end
#--------------------------------------#
# @!group Helpers
private
# @return [Pathname] the path of the podspec.
#
def pod_spec_path
path = Pathname.new(@params[:local]).expand_path
path += "#{name}.podspec"# unless path.to_s.include?("#{name}.podspec")
unless path.exist?
raise Informative, "No podspec found for `#{name}` in `#{@params[:local]}`"
end
path
end
end
end
end
......@@ -15,6 +15,10 @@ install_resource()
echo "ibtool --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename $1 .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}"
ibtool --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename $1 .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}"
;;
*.framework)
echo "rsync -rp ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
rsync -rp "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
;;
*)
echo "cp -R ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
cp -R "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
......@@ -26,7 +30,7 @@ EOS
attr_reader :resources
# A list of files relative to the project pods root.
def initialize(resources)
def initialize(resources = [])
@resources = resources
end
......
module Pod
module Generator
# Generates a prefix header file for a Pods library. The prefix header is
# generated according to the platform of the target and the pods.
#
# According to the platform the prefix header imports `UIKit/UIKit.h` or
# `Cocoa/Cocoa.h`.
#
class PrefixHeader
# @return [Platform] the platform for which the prefix header will be
# generated.
#
attr_reader :platform
# @return [Array<LocalPod>] the LocalPod for the target for which the
# prefix header needs to be generated.
#
attr_reader :pods
# @param [Platform] platform @see platform
#
# @param [Array<LocalPod>] @see pods
#
def initialize(platform, pods)
@platform = platform
@pods = pods
end
#--------------------------------------#
# Generates the contents of the prefix header according to the platform
# and the pods.
#
# @note If the platform is iOS an import call to `UIKit/UIKit.h` is
# added to the top of the prefix header. For OS X `Cocoa/Cocoa.h`
# is imported.
#
# @return [String]
#
# @todo Subspecs can specify prefix header information too.
#
def generate
result = "#ifdef __OBJC__\n"
result << "#import #{platform == :ios ? '<UIKit/UIKit.h>' : '<Cocoa/Cocoa.h>'}\n"
result << "#endif\n"
pods.each do |pod|
result << "\n"
if prefix_header_contents = pod.top_specification.prefix_header_contents
result << prefix_header_contents
result << "\n"
elsif prefix_header = pod.prefix_header_file
result << prefix_header.read
end
end
result
end
# Generates and saves the prefix header to the given path.
#
# @param [Pathname] path
# the path where the prefix header should be stored.
#
# @return [void]
#
def save_as(path)
path.open('w') { |header| header.write(generate) }
end
end
end
end
module Pod
module Generator
# Generates an xcconfig file for each target of the Pods project. The
# configuration file should be used by the user target as well.
#
class XCConfig
# @return [Sandbox] the sandbox where the Pods project is installed.
#
attr_reader :sandbox
# @return [Array<LocalPod>] the list of LocalPods for the library.
#
attr_reader :pods
# @return [String] the relative path of the Pods root respect the user
# project that should be integrated by this library.
#
attr_reader :relative_pods_root
# @param [Platform] platform @see platform
#
# @param [Array<LocalPod>] @see pods
#
def initialize(sandbox, pods, relative_pods_root)
@sandbox = sandbox
@pods = pods
@relative_pods_root = relative_pods_root
end
# @return [Bool] whether the Podfile specifies to add the `-fobjc-arc`
# flag for compatibility.
#
attr_accessor :set_arc_compatibility_flag
#-----------------------------------------------------------------------#
# Generates the xcconfig for the library.
#
# @return [Xcodeproj::Config]
#
# @note The value `PODS_HEADERS_SEARCH_PATHS` is used to store the headers
# so xcconfig can reference the variable.
#
# @todo Add Xcodeproj::Config#[]
#
def generate
ld_flags = '-ObjC'
if set_arc_compatibility_flag && pods.any? { |pod| pod.requires_arc? }
ld_flags << ' -fobjc-arc'
end
@xcconfig = Xcodeproj::Config.new({
'ALWAYS_SEARCH_USER_PATHS' => 'YES',
'OTHER_LDFLAGS' => ld_flags,
'HEADER_SEARCH_PATHS' => '${PODS_HEADERS_SEARCH_PATHS}',
'PODS_ROOT' => relative_pods_root,
'PODS_HEADERS_SEARCH_PATHS' => '${PODS_PUBLIC_HEADERS_SEARCH_PATHS}',
'PODS_BUILD_HEADERS_SEARCH_PATHS' => quote(sandbox.build_headers.search_paths),
'PODS_PUBLIC_HEADERS_SEARCH_PATHS' => quote(sandbox.public_headers.search_paths),
})
pods.each { |pod| @xcconfig.merge!(pod.xcconfig) }
@xcconfig
end
# @return [Xcodeproj::Config] The generated xcconfig.
#
attr_reader :xcconfig
# @return [Hash] The settings of the xcconfig that the Pods project
# needs to override.
#
def self.pods_project_settings
{ 'PODS_ROOT' => '${SRCROOT}',
'PODS_HEADERS_SEARCH_PATHS' => '${PODS_BUILD_HEADERS_SEARCH_PATHS}' }
end
# Generates and saves the xcconfig to the given path.
#
# @param [Pathname] path
# the path where the prefix header should be stored.
#
# @return [void]
#
def save_as(path)
path.open('w') { |file| file.write(generate) }
end
#-----------------------------------------------------------------------#
# @!group Private helpers.
private
# @return [String] the default linker flags. `-ObjC` is always included
# while `-fobjc-arc` is included only if requested in the
# Podfile.
#
def default_ld_flags
flags = %w[ -ObjC ]
requires_arc = pods.any? { |pod| pod.requires_arc? }
if requires_arc && set_arc_compatibility_flag
flags << '-fobjc-arc'
end
flags.join(" ")
end
# Converts an array of strings to a single string where the each string
# is surrounded by double quotes and separated by a space. Used to
# represent strings in a xcconfig file.
#
# @param [Array<String>] strings
# a list of strings.
#
# @return [String] the resulting string.
#
def quote(strings)
strings.map { |s| %W|"#{s}"| }.join(" ")
end
end
end
end
require 'colored'
module Pod
# The {Installer} is the core of CocoaPods. This class is responsible of
# taking a Podfile and transform it in the Pods libraries. This class also
# integrates the user project so the Pods libraries can be used out of the
# box.
#
# The Installer is capable of doing incremental updates to an existing Pod
# installation.
#
# The Installer gets the information that it needs mainly from 3 files:
#
# - Podfile: The specification written by the user that contains
# information about targets and Pods.
# - Podfile.lock: Contains information about the pods that were previously
# installed and in concert with the Podfile provides information about
# which specific version of a Pod should be installed. This file is
# ignored in update mode.
# - Manifest.lock: A file contained in the Pods folder that keeps track of
# the pods installed in the local machine. This files is used once the
# exact versions of the Pods has been computed to detect if that version
# is already installed. This file is not intended to be kept under source
# control and is a copy of the Podfile.lock.
#
# Once completed the installer should produce the following file structure:
#
# Pods
# |
# +-- Headers
# | +-- Build
# | | +-- [Pod Name]
# | +-- Public
# | +-- [Pod Name]
# |
# +-- Sources
# | +-- [Pod Name]
# |
# +-- Specifications
# |
# +-- Target Support Files
# | +-- [Target Name]
# | +-- Acknowledgements.markdown
# | +-- Acknowledgements.plist
# | +-- Pods.xcconfig
# | +-- Pods-prefix.pch
# | +-- PodsDummy_Pods.m
# |
# +-- Manifest.lock
# |
# +-- Pods.xcodeproj
#
class Installer
autoload :TargetInstaller, 'cocoapods/installer/target_installer'
autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
include Config::Mixin
attr_reader :resolver, :sandbox, :lockfile
# @return [Sandbox] The sandbox where the Pods should be installed.
#
attr_reader :sandbox
# @return [Podfile] The Podfile specification that contains the information
# of the Pods that should be installed.
#
attr_reader :podfile
# @return [Lockfile] The Lockfile that stores the information about the
# Pods previously installed on any machine.
#
attr_reader :lockfile
def initialize(resolver)
@resolver = resolver
@podfile = resolver.podfile
@sandbox = resolver.sandbox
# @param [Sandbox] sandbox @see sandbox
# @param [Podfile] podfile @see podfile
# @param [Lockfile] lockfile @see lockfile
# @param [Bool] update_mode @see update_mode
#
def initialize(sandbox, podfile, lockfile = nil)
@sandbox = sandbox
@podfile = podfile
@lockfile = lockfile
end
# @return [Bool] Whether the installer is in update mode. In update mode
# the contents of the Lockfile are not taken into account for
# deciding what Pods to install.
#
attr_accessor :update_mode
# Installs the Pods.
#
# The installation process of is mostly linear with few minor complications
# to keep in mind:
#
# - The stored podspecs need to be cleaned before the resolution step
# otherwise the sandbox might return an old podspec and not download
# the new one from an external source.
# - The resolver might trigger the download of Pods from external sources
# necessary to retrieve their podspec (unless it is instructed not to
# do it).
#
# @note The order of the steps is very important and should be changed
# carefully.
#
# @return [void]
#
def install!
analyze
prepare_for_legacy_compatibility
clean_global_support_files
clean_removed_pods
clean_pods_to_install
install_dependencies
install_targets
write_lockfiles
integrate_user_project
end
def project
return @project if @project
@project = Pod::Project.new
@project.user_build_configurations = @podfile.user_build_configurations
pods.each { |p| p.add_file_references_to_project(@project) }
pods.each { |p| p.link_headers }
@project
# Performs only the computation parts of an installation.
#
# It is used by the `outdated` subcommand.
#
# @return [void]
#
def analyze
generate_pods_by_podfile_state
update_repositories_if_needed
generate_locked_dependencies
resolve_dependencies
generate_local_pods
generate_pods_that_should_be_installed
end
def target_installers
@target_installers ||= @podfile.target_definitions.values.map do |definition|
TargetInstaller.new(@podfile, project, definition) unless definition.empty?
end.compact
#---------------------------------------------------------------------------#
# @!group Analysis products
public
# @return [Array<String>]
# the names of the pods that were added to Podfile since the last
# installation on any machine.
#
attr_reader :pods_added_from_the_lockfile
# @return [Array<String>]
# the names of the pods whose version requirements in the Podfile are
# incompatible with the version stored in the lockfile.
#
attr_reader :pods_changed_from_the_lockfile
# @return [Array<String>]
# the names of the pods that were deleted from Podfile since the last
# installation on any machine.
#
attr_reader :pods_deleted_from_the_lockfile
# @return [Array<String>]
# the names of the pods that didn't change since the last installation on
# any machine.
#
attr_reader :pods_unchanged_from_the_lockfile
# @return [Array<Dependency>]
# the dependencies generate by the lockfile that prevent the resolver to
# update a Pod.
#
attr_reader :locked_dependencies
# @return [Hash{TargetDefinition => Array<Spec>}]
# the specifications grouped by target as identified in the
# resolve_dependencies step.
#
attr_reader :specs_by_target
# @return [Array<Specification>]
# the specifications of the resolved version of Pods that should be
# installed.
#
attr_reader :specifications
# @return [Hash{TargetDefinition => Array<LocalPod>}]
# the local pod instances grouped by target.
#
attr_reader :local_pods_by_target
# @return [Array<LocalPod>]
# the list of LocalPod instances for each dependency sorted by name.
#
attr_reader :local_pods
# @return [Array<String>]
# the Pods that should be installed.
#
attr_reader :pods_to_install
#---------------------------------------------------------------------------#
# @!group Installation products
public
# @return [Pod::Project]
# the `Pods/Pods.xcodeproj` project.
#
attr_reader :pods_project
# @return [Array<TargetInstaller>]
#
attr_reader :target_installers
#---------------------------------------------------------------------------#
# @!group Pre-installation computations
private
# Compares the {Podfile} with the {Lockfile} in order to detect which
# dependencies should be locked.
#
# @return [void]
#
# @todo If there is not Lockfile all the Pods should be marked as added.
#
# @todo Once the manifest.lock is implemented only the unchanged pods
# should be tracked.
#
def generate_pods_by_podfile_state
if lockfile
UI.section "Finding added, modified or removed dependencies:" do
pods_by_state = lockfile.detect_changes_with_podfile(podfile)
@pods_added_from_the_lockfile = pods_by_state[:added] || []
@pods_deleted_from_the_lockfile = pods_by_state[:removed] || []
@pods_changed_from_the_lockfile = pods_by_state[:changed] || []
@pods_unchanged_from_the_lockfile = pods_by_state[:unchanged] || []
display_pods_by_lockfile_state
end
else
@pods_added_from_the_lockfile = []
@pods_deleted_from_the_lockfile = []
@pods_changed_from_the_lockfile = []
@pods_unchanged_from_the_lockfile = []
end
end
# Displays the state of each dependency.
#
# @return [void]
#
def display_pods_by_lockfile_state
return unless config.verbose?
pods_added_from_the_lockfile .each { |pod| UI.message("A".green + "#{pod}", '', 2) }
pods_deleted_from_the_lockfile .each { |pod| UI.message("R".red + "#{pod}", '', 2) }
pods_changed_from_the_lockfile .each { |pod| UI.message("M".yellow + "#{pod}", '', 2) }
pods_unchanged_from_the_lockfile .each { |pod| UI.message("-" + "#{pod}", '', 2) }
end
# Lazily updates the source repositories. The update is triggered if:
#
# - There are pods that changed in the Podfile.
# - The lockfile is missing.
# - The installer is in update_mode.
#
# @todo Remove the lockfile condition once compare_podfile_and_lockfile
# is updated.
#
# @todo Lazy resolution can't be done if we want to fully support detection
# of changes in specifications checksum.
#
# @return [void]
#
def update_repositories_if_needed
return if config.skip_repo_update?
changed_pods = (pods_changed_from_the_lockfile + pods_deleted_from_the_lockfile)
should_update = !lockfile || !changed_pods.empty? || update_mode
if should_update
UI.section 'Updating spec repositories' do
Command::Repo.new(Command::ARGV.new(["update"])).run
end
end
end
# Generates dependencies that require the specific version of the Pods that
# haven't changed in the {Lockfile}.
#
# These dependencies are passed to the {Resolver}, unless the installer is
# in update mode, to prevent it from upgrading the Pods that weren't
# changed in the {Podfile}.
#
# @return [void]
#
def generate_locked_dependencies
@locked_dependencies = pods_unchanged_from_the_lockfile.map do |pod|
lockfile.dependency_to_lock_pod_named(pod)
end
end
# Converts the Podfile in a list of specifications grouped by target.
#
# @note As some dependencies might have external sources the resolver is
# aware of the {Sandbox} and interacts with it to download the
# podspecs of the external sources. This is necessary because the
# resolver needs the specifications to analyze their dependencies
# (which might be from external sources).
#
# @note In update mode the resolver is set to always update the specs
# from external sources.
#
# @return [void]
#
def resolve_dependencies
UI.section "Resolving dependencies of #{UI.path podfile.defined_in_file}" do
locked_deps = update_mode ? [] : locked_dependencies
resolver = Resolver.new(sandbox, podfile, locked_deps)
resolver.update_external_specs = update_mode
@specs_by_target = resolver.resolve
@specifications = specs_by_target.values.flatten
end
end
# Computes the list of the Pods that should be installed or reinstalled in
# the {Sandbox}.
#
# The pods to install are identified as the Pods that don't exist in the
# sandbox or the Pods whose version differs from the one of the lockfile.
#
# In update mode specs originating from external dependencies and or from
# head sources are always reinstalled.
#
# @return [void]
#
# @todo Use {Sandbox} manifest.
#
# @todo [#534] Detect if the folder of a Pod is empty.
#
def generate_pods_that_should_be_installed
changed_pods_names = []
if lockfile
changed_pods = local_pods.select do |pod|
pod.top_specification.version != lockfile.pod_versions[pod.name]
end
if update_mode
changed_pods_names += pods.select do |pods|
pod.top_specification.version.head? ||
resolver.pods_from_external_sources.include?(pod.name)
end
end
changed_pods_names += pods_added_from_the_lockfile + pods_changed_from_the_lockfile
else
changed_pods = local_pods
end
not_existing_pods = local_pods.reject { |pod| pod.exists? }
@pods_to_install = (changed_pods + not_existing_pods).uniq
end
# Converts the specifications produced by the Resolver in local pods.
#
# The LocalPod class is responsible to handle the concrete representation
# of a specification in the {Sandbox}.
#
# @return [void]
#
# @todo [#535] Pods should be accumulated per Target, also in the Local
# Pod class. The Local Pod class should have a method to add itself
# to a given project so it can use the sources of all the activated
# podspecs across all targets. Also cleaning should take into account
# that.
#
def generate_local_pods
@local_pods_by_target = {}
specs_by_target.each do |target_definition, specs|
@local_pods_by_target[target_definition] = specs.map do |spec|
if spec.local?
sandbox.locally_sourced_pod_for_spec(spec, target_definition.platform)
else
sandbox.local_pod_for_spec(spec, target_definition.platform)
end
end.uniq.compact
end
@local_pods = local_pods_by_target.values.flatten.uniq.sort_by { |pod| pod.name.downcase }
end
# Install the Pods. If the resolver indicated that a Pod should be installed
# and it exits, it is removed an then reinstalled. In any case if the Pod
# doesn't exits it is installed.
#---------------------------------------------------------------------------#
# @!group Installation
private
# Prepares the Pods folder in order to be compatible with the most recent
# version of CocoaPods.
#
# @return [void]
#
def install_dependencies!
pods.sort_by { |pod| pod.top_specification.name.downcase }.each do |pod|
should_install = @resolver.should_install?(pod.top_specification.name) || !pod.exists?
if should_install
UI.section("Installing #{pod}".green, "-> ".green) do
unless pod.downloaded?
pod.implode
download_pod(pod)
def prepare_for_legacy_compatibility
# move_target_support_files_if_needed
# copy_lock_file_to_Pods_lock_if_needed
# move_Local_Podspecs_to_Podspecs_if_needed
# move_pods_to_sources_folder_if_needed
end
# @return [void] In this step we clean all the folders that will be
# regenerated from scratch and any file which might not be overwritten.
#
# @todo Clean the podspecs of all the pods that aren't unchanged so the
# resolution process doesn't get confused by them.
#
def clean_global_support_files
sandbox.prepare_for_install
end
# @return [void] In this step we clean all the files related to the removed
# Pods.
#
# @todo Use the local pod implode.
# @todo [#534] Clean all the Pods folder that are not unchanged?
#
def clean_removed_pods
UI.section "Removing deleted dependencies" do
pods_deleted_from_the_lockfile.each do |pod_name|
UI.section("Removing #{pod_name}", "-> ".red) do
path = sandbox.root + pod_name
path.rmtree if path.exist?
end
end
end unless pods_deleted_from_the_lockfile.empty?
end
# @return [void] In this step we clean the files of the Pods that will be
# installed. We clean the files that might affect the resolution process
# and the files that might not be overwritten.
#
# @todo [#247] Clean the headers of only the pods to install.
#
def clean_pods_to_install
end
# @return [void] Install the Pods. If the resolver indicated that a Pod
# should be installed and it exits, it is removed an then reinstalled. In
# any case if the Pod doesn't exits it is installed.
#
def install_dependencies
UI.section "Downloading dependencies" do
local_pods.each do |pod|
if pods_to_install.include?(pod)
UI.section("Installing #{pod}".green, "-> ".green) do
install_local_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?
else
UI.section("Using #{pod}", "-> ".green)
end
else
UI.section("Using #{pod}", "-> ".green)
end
end
end
# @return [void] Downloads, clean and generates the documentation of a pod.
#
# @note The docs need to be generated before cleaning because the
# documentation is created for all the subspecs.
#
# @note In this step we clean also the Pods that have been pre-downloaded
# in AbstractExternalSource#specification_from_sandbox.
#
# @todo [#529] Podspecs should not be preserved anymore to prevent user
# confusion. Currently we are copying the ones form external sources
# in `Local Podspecs` and this feature is not needed anymore.
# I think that copying all the used podspecs would be helpful for
# debugging.
#
def install_local_pod(pod)
unless pod.downloaded?
pod.implode
download_pod(pod)
end
generate_docs_if_needed(pod)
pod.clean! if config.clean?
end
# Downloads a Pod forcing the `bleeding edge' version if requested.
#
# @return [void]
#
def download_pod(pod)
downloader = Downloader.for_pod(pod)
# Force the `bleeding edge' version if necessary.
if pod.top_specification.version.head?
if downloader.respond_to?(:download_head)
downloader.download_head
else
raise Informative, "The downloader of class `#{downloader.class.name}' does not support the `:head' option."
raise Informative,
"The downloader of class `#{downloader.class.name}' does not" \
"support the `:head' option."
end
else
downloader.download
......@@ -73,8 +499,12 @@ module Pod
pod.downloaded = true
end
#TODO: move to generator ?
def generate_docs(pod)
# Generates the documentation of a Pod unless it exists for a given
# version.
#
# @return [void]
#
def generate_docs_if_needed(pod)
doc_generator = Generator::Documentation.new(pod)
if ( config.generate_docs? && !doc_generator.already_installed? )
UI.section " > Installing documentation"
......@@ -84,133 +514,184 @@ module Pod
end
end
# @TODO: use the local pod implode
# Creates and populates the targets of the pods project.
#
def remove_deleted_dependencies!
resolver.removed_pods.each do |pod_name|
UI.section("Removing #{pod_name}", "-> ".red) do
path = sandbox.root + pod_name
path.rmtree if path.exist?
end
# @note Post install hooks run _before_ saving of project, so that they
# can alter it before it is writtent to the disk.
#
# @return [void]
#
def install_targets
UI.section "Generating support files" do
prepare_pods_project
generate_target_installers
add_source_files_to_pods_project
run_pre_install_hooks
generate_target_support_files
run_post_install_hooks
write_pod_project
end
end
def install!
@sandbox.prepare_for_install
UI.section "Resolving dependencies of #{UI.path @podfile.defined_in_file}" do
specs_by_target
end
UI.section "Removing deleted dependencies" do
remove_deleted_dependencies!
end unless resolver.removed_pods.empty?
UI.section "Downloading dependencies" do
install_dependencies!
# Creates the Pods project from scratch if it doesn't exists.
#
# @todo Restore the build configuration support.
# @todo Clean and modify the project if it exists.
#
# @return [void]
#
def prepare_pods_project
UI.message "- Creating Pods project" do
@pods_project = Pod::Project.new(config.sandbox)
@pods_project.add_podfile(config.project_podfile)
# pods_project.user_build_configurations = podfile.user_build_configurations
end
end
UI.section "Generating support files" do
UI.message "- Running pre install hooks" do
run_pre_install_hooks
end
UI.message"- Generating project" do
project
end
UI.message"- Installing targets" do
generate_target_support_files
end
UI.message "- Running post install hooks" do
# Post install hooks run _before_ saving of project, so that they can alter it before saving.
run_post_install_hooks
end
UI.message "- Writing Xcode project file to #{UI.path @sandbox.project_path}" do
project.save_as(@sandbox.project_path)
end
# Creates a target installer for each definition not empty.
#
# @return [void]
#
def generate_target_installers
@target_installers = podfile.target_definitions.values.map do |definition|
pods_for_target = local_pods_by_target[definition]
TargetInstaller.new(pods_project, definition, pods_for_target) unless definition.empty?
end.compact
end
UI.message "- Writing lockfile in #{UI.path config.project_lockfile}" do
@lockfile = Lockfile.generate(@podfile, specs_by_target.values.flatten)
@lockfile.write_to_disk(config.project_lockfile)
end
# Adds the source files of the Pods to the Pods project.
#
# The source files are grouped by Pod and in turn by subspec
# (recursively). Pods are generally added to the `Pods` group, however, if
# they have a local source they are added to the `Local Pods` group.
#
# @return [void]
#
# @todo Clean the groups of the deleted Pods and add only the Pods that
# should be installed.
# @todo [#588] Add file references for the resources of the Pods as well
# so they are visible for the user.
#
def add_source_files_to_pods_project
UI.message "- Adding source files to Pods project" do
local_pods.each { |p| p.add_file_references_to_project(pods_project) }
local_pods.each { |p| p.link_headers }
end
UserProjectIntegrator.new(@podfile).integrate! if config.integrate_targets?
end
# Runs the pre install hooks of the installed specs and of the Podfile.
#
# @todo Run the hooks only for the installed pods.
# @todo Print a messsage with the names of the specs.
#
# @return [void]
#
def run_pre_install_hooks
pods_by_target.each do |target_definition, pods|
pods.each do |pod|
pod.top_specification.pre_install(pod, target_definition)
UI.message "- Running pre install hooks" do
local_pods_by_target.each do |target_definition, pods|
pods.each do |pod|
pod.top_specification.pre_install!(pod, target_definition)
end
end
@podfile.pre_install!(self)
end
@podfile.pre_install!(self)
end
# Runs the post install hooks of the installed specs and of the Podfile.
#
# @todo Run the hooks only for the installed pods.
# @todo Print a messsage with the names of the specs.
#
# @return [void]
#
def run_post_install_hooks
# we loop over target installers instead of pods, because we yield the target installer
# to the spec post install hook.
target_installers.each do |target_installer|
specs_by_target[target_installer.target_definition].each do |spec|
spec.post_install(target_installer)
UI.message "- Running post install hooks" do
target_installers.each do |target_installer|
specs_by_target[target_installer.target_definition].each do |spec|
spec.post_install!(target_installer)
end
end
@podfile.post_install!(self)
end
@podfile.post_install!(self)
end
# Installs the targets of the Pods projects and generates their support
# files.
#
# @todo Move the acknowledgements to the target installer?
#
def generate_target_support_files
target_installers.each do |target_installer|
pods_for_target = pods_by_target[target_installer.target_definition]
target_installer.install!(pods_for_target, @sandbox)
acknowledgements_path = target_installer.target_definition.acknowledgements_path
Generator::Acknowledgements.new(target_installer.target_definition,
pods_for_target).save_as(acknowledgements_path)
generate_dummy_source(target_installer)
UI.message"- Installing targets" do
target_installers.each do |target_installer|
pods_for_target = local_pods_by_target[target_installer.target_definition]
target_installer.install!
acknowledgements_path = target_installer.library.acknowledgements_path
Generator::Acknowledgements.new(target_installer.target_definition,
pods_for_target).save_as(acknowledgements_path)
generate_dummy_source(target_installer)
end
end
end
# Generates a dummy source file for each target so libraries that contain
# only cathegories build.
#
# @todo Move to the target installer?
#
def generate_dummy_source(target_installer)
class_name_identifier = target_installer.target_definition.label
dummy_source = Generator::DummySource.new(class_name_identifier)
filename = "#{dummy_source.class_name}.m"
pathname = Pathname.new(sandbox.root + filename)
dummy_source.save_as(pathname)
file = project.new_file(filename, "Targets Support Files")
file = pods_project.new_file(filename, "Targets Support Files")
target_installer.target.source_build_phase.add_file_reference(file)
end
def specs_by_target
@specs_by_target ||= @resolver.resolve
# Writes the Pods project to the disk.
#
# @return [void]
#
def write_pod_project
UI.message "- Writing Xcode project file to #{UI.path @sandbox.project_path}" do
pods_project.save_as(@sandbox.project_path)
end
end
# @return [Array<Specification>] All dependencies that have been resolved.
def specifications
specs_by_target.values.flatten
end
# Writes the Podfile and the {Sandbox} lock files.
#
# @return [void]
#
# @todo [#552] Implement manifest.
#
def write_lockfiles
@lockfile = Lockfile.generate(podfile, specs_by_target.values.flatten)
UI.message "- Writing Lockfile in #{UI.path config.project_lockfile}" do
@lockfile.write_to_disk(config.project_lockfile)
end
# @return [Array<LocalPod>] A list of LocalPod instances for each
# dependency that is not a download-only one.
def pods
pods_by_target.values.flatten.uniq
# UI.message "- Writing Manifest in #{UI.path sandbox.manifest_path}" do
# @lockfile.write_to_disk(sandbox.manifest_path)
# end
end
def pods_by_target
@pods_by_spec = {}
result = {}
specs_by_target.each do |target_definition, specs|
@pods_by_spec[target_definition.platform] = {}
result[target_definition] = specs.map do |spec|
if spec.local?
@sandbox.locally_sourced_pod_for_spec(spec, target_definition.platform)
else
@sandbox.local_pod_for_spec(spec, target_definition.platform)
end
end.uniq.compact
end
result
# Integrates the user project.
#
# The following actions are performed:
# - libraries are added.
# - the build script are added.
# - the xcconfig files are set.
#
# @return [void]
#
# @todo [#397] The libraries should be cleaned and the re-added on every
# installation. Maybe a clean_user_project phase should be added.
#
# @todo [#588] The resources should be added through a build phase instead
# of using a script.
#
def integrate_user_project
UserProjectIntegrator.new(podfile, pods_project, config.project_root).integrate! if config.integrate_targets?
end
end
end
module Pod
class Installer
# This class is reponsible of creating and configuring the static library
# This class is responsible of creating and configuring the static library
# target in Pods project. Every target is generated from a target
# definition of the Podfile.
#
class TargetInstaller
include Config::Mixin
# @return [Podfile]
#
# TODO: is really needed to pass the podfile?
#
attr_reader :podfile
# @return [Project] The Pods project.
#
attr_reader :project
# @return [TargetDefinition] The target definition whoose target needs to
# @return [TargetDefinition] The target definition whose target needs to
# be generated.
#
attr_reader :target_definition
def initialize(podfile, project, target_definition)
@podfile = podfile
@project = project
# @param [Array<LocalPod>] pods the pods are required by the target
# definition of this installer.
#
attr_reader :pods
# @param [Project] project @see project
# @param [TargetDefinition] target_definition @see target_definition
# @param [Array<LocalPod>] pods @see pods
#
def initialize(project, target_definition, pods)
@project = project
@target_definition = target_definition
@pods = pods
end
# @return [void] Creates the target in the Pods project and its support
# files.
# Creates the target in the Pods project and its support files.
#
# @param [Array<LocalPod>] pods The pods are required by the target
# definition of this installer.
# @return [void]
#
# @param [Sandbox] sandbox The sanbox where the support files
# should be generated.
def install!
add_target
add_build_files_to_target
add_file_reference_for_support_files
configure_target
create_xcconfig_file
create_prefix_header
create_bridge_support_file
create_copy_resources_script
end
# @todo This has to be removed, but this means the specs have to be
# updated if they need a reference to the prefix header.
#
def install!(pods, sandbox)
self.requires_arc = pods.any? { |pod| pod.requires_arc? }
def prefix_header_filename
library.prefix_header_name
end
@target = @project.add_pod_target(@target_definition.label, @target_definition.platform)
#-----------------------------------------------------------------------#
source_file_descriptions = []
pods.each { |p| p.add_build_files_to_target(@target) }
# @!group Installation steps
support_files_group = @project.support_files_group.new_group(@target_definition.label)
target_support_files.each { |path| support_files_group.new_file(path) }
private
xcconfig_file = support_files_group.files.find { |f| f.path == @target_definition.xcconfig_name }
configure_build_configurations(xcconfig_file, sandbox)
create_files(pods, sandbox)
# Adds the library for the {#target_definition} to the Pods
# project.
#
# @return [void]
#
def add_target
@library = @project.add_pod_library(target_definition)
@target = @library.target
end
# @return [PBXNativeTarget] The target generated by the installation
# process.
# Adds the build files of the pods to the target.
#
attr_reader :target
# @return [void]
#
def add_build_files_to_target
pods.each { |p| p.add_build_files_to_target(target) }
end
# Adds the file references for the support files that are generated by
# the installation process to the Pods project.
#
# @return [void]
#
def add_file_reference_for_support_files
g = project.support_files_group.new_group(target_definition.label)
g.new_file(library.copy_resources_script_name)
g.new_file(library.prefix_header_name)
g.new_file(library.xcconfig_name).tap { |f| @xcconfig_file_ref = f }
end
# @return [Boold] Wether the any of the pods requires arc.
# Configures the build settings of the target.
#
# @note The `PODS_HEADERS_SEARCH_PATHS` overrides the xcconfig.
#
# TODO: This should not be an attribute reader.
# @return [void]
#
attr_accessor :requires_arc
def configure_target
set = {
'GCC_PREFIX_HEADER' => library.prefix_header_name
}
if @target_definition.inhibit_all_warnings?
set['GCC_WARN_INHIBIT_ALL_WARNINGS'] = 'YES'
end
attr_reader :xcconfig
Generator::XCConfig.pods_project_settings.each do |key, value|
set[key] = value
end
# In a workspace this is where the static library headers should be found.
#
def generate_xcconfig(pods, sandbox)
xcconfig = Xcodeproj::Config.new({
'ALWAYS_SEARCH_USER_PATHS' => 'YES', # needed to make EmbedReader build
'OTHER_LDFLAGS' => default_ld_flags,
'HEADER_SEARCH_PATHS' => '${PODS_HEADERS_SEARCH_PATHS}',
# CocoaPods global keys
'PODS_ROOT' => @target_definition.relative_pods_root,
'PODS_BUILD_HEADERS_SEARCH_PATHS' => quoted(sandbox.build_headers.search_paths).join(" "),
'PODS_PUBLIC_HEADERS_SEARCH_PATHS' => quoted(sandbox.public_headers.search_paths).join(" "),
# Pods project specific keys
'PODS_HEADERS_SEARCH_PATHS' => '${PODS_PUBLIC_HEADERS_SEARCH_PATHS}'
})
pods.each { |pod| xcconfig.merge!(pod.xcconfig) }
@xcconfig = xcconfig
@target.build_configurations.each do |c|
c.base_configuration_reference = xcconfig_file_ref
c.build_settings.merge!(set)
end
end
# Generates the contents of the xcconfig file and saves it to disk.
#
# @note The `ALWAYS_SEARCH_USER_PATHS` flag is enabled to support
# libraries like `EmbedReader`.
#
def copy_resources_script_for(pods)
@copy_resources_script ||= Generator::CopyResourcesScript.new(pods.map { |p| p.relative_resource_files }.flatten)
end
def bridge_support_generator_for(pods, sandbox)
Generator::BridgeSupport.new(pods.map do |pod|
pod.relative_header_files.map { |header| sandbox.root + header }
end.flatten)
# @return [void]
#
def create_xcconfig_file
UI.message "- Generating xcconfig file at #{UI.path(library.xcconfig_path)}" do
gen = Generator::XCConfig.new(sandbox, pods, library.relative_pods_root)
gen.set_arc_compatibility_flag = target_definition.podfile.set_arc_compatibility_flag?
gen.save_as(library.xcconfig_path)
library.xcconfig = gen.xcconfig
end
end
# TODO This has to be removed, but this means the specs have to be updated if they need a reference to the prefix header.
def prefix_header_filename
@target_definition.prefix_header_name
# Creates a prefix header file which imports `UIKit` or `Cocoa`. This
# file also include any prefix header content reported by the
# specification of the pods.
#
# @return [void]
#
def create_prefix_header
UI.message "- Generating prefix header at #{UI.path(library.prefix_header_path)}" do
gen = Generator::PrefixHeader.new(target_definition.platform, pods)
gen.save_as(library.prefix_header_path)
end
end
# TODO move out to Generator::PrefixHeader
def save_prefix_header_as(pathname, pods)
pathname.open('w') do |header|
header.puts "#ifdef __OBJC__"
header.puts "#import #{@target_definition.platform == :ios ? '<UIKit/UIKit.h>' : '<Cocoa/Cocoa.h>'}"
header.puts "#endif"
pods.each do |pod|
if prefix_header_contents = pod.top_specification.prefix_header_contents
header.puts
header.puts prefix_header_contents
elsif prefix_header = pod.prefix_header_file
header.puts
header.puts prefix_header.read
end
# Generates the bridge support metadata if requested by the {Podfile}.
#
# @note the bridge support metadata is added to the resources of the
# library because it is needed for environments interpreted at
# runtime.
#
# @return [void]
#
def create_bridge_support_file
if target_definition.podfile.generate_bridge_support?
UI.message "- Generating BridgeSupport metadata at #{UI.path library.bridge_support_path}" do
generator = Generator::BridgeSupport.new(pods.map do |pod|
pod.relative_header_files.map { |header| sandbox.root + header }
end.flatten)
generator.save_as(library.bridge_support_path)
copy_resources_script.resources << library.bridge_support_name
end
end
end
def target_support_files
[:copy_resources_script_name, :prefix_header_name, :xcconfig_name].map { |file| @target_definition.send(file) }
end
def configure_build_configurations(xcconfig_file, sandbox)
@target.build_configurations.each do |config|
config.base_configuration_reference = xcconfig_file
config.build_settings['OTHER_LDFLAGS'] = ''
config.build_settings['GCC_PREFIX_HEADER'] = @target_definition.prefix_header_name
config.build_settings['PODS_ROOT'] = '${SRCROOT}'
config.build_settings['PODS_HEADERS_SEARCH_PATHS'] = '${PODS_BUILD_HEADERS_SEARCH_PATHS}'
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'] = @target_definition.inhibit_all_warnings? ? 'YES' : 'NO'
# Creates a script that copies the resources to the bundle of the client
# target.
#
# @todo This should be replaced by an Xcode copy resources build phase.
#
# @return [void]
#
def create_copy_resources_script
UI.message "- Generating copy resources script at #{UI.path(library.copy_resources_script_path)}" do
copy_resources_script.resources << pods.map { |p| p.relative_resource_files }.flatten
copy_resources_script.save_as(library.copy_resources_script_path)
end
end
#-----------------------------------------------------------------------#
# @!group Installation products.
public
# @return [Library] the library for the target definition.
#
# @note Generated by the {#add_target} step.
#
def create_files(pods, sandbox)
bridge_support_metadata_path = sandbox.root + @target_definition.bridge_support_name
UI.message "- Generating BridgeSupport metadata file at #{UI.path bridge_support_metadata_path}" do
bridge_support_generator_for(pods, sandbox).save_as(bridge_support_metadata_path)
copy_resources_script_for(pods).resources << @target_definition.bridge_support_name
end if @podfile.generate_bridge_support?
attr_reader :library
UI.message "- Generating xcconfig file at #{UI.path(sandbox.root + @target_definition.xcconfig_name)}" do
generate_xcconfig(pods, sandbox)
xcconfig.save_as(sandbox.root + @target_definition.xcconfig_name)
@target_definition.xcconfig = xcconfig
end
# @return [PBXNativeTarget] the target generated by the installation
# process.
#
# @note Generated by the {#add_target} step.
#
attr_reader :target
UI.message "- Generating prefix header at #{UI.path(sandbox.root + @target_definition.prefix_header_name)}" do
save_prefix_header_as(sandbox.root + @target_definition.prefix_header_name, pods)
end
# @return [PBXFileReference] the file reference to the xcconfig file of
# the target.
#
# @note Generated by the {#add_file_reference_for_support_files} step.
#
attr_reader :xcconfig_file_ref
UI.message "- Generating copy resources script at #{UI.path(sandbox.root + @target_definition.copy_resources_script_name)}" do
copy_resources_script_for(pods).save_as(sandbox.root + @target_definition.copy_resources_script_name)
end
end
#-----------------------------------------------------------------------#
# @!group Private helpers.
private
def quoted(strings)
strings.map { |s| "\"#{s}\"" }
# @return [Sandbox] sandbox the sandbox where the support files should
# be generated.
#
def sandbox
project.sandbox
end
def default_ld_flags
flags = %w{-ObjC}
flags << '-fobjc-arc' if @podfile.set_arc_compatibility_flag? && self.requires_arc
flags.join(" ")
# Creates and caches the copy resource script.
#
# @return [CopyResourcesScript]
#
def copy_resources_script
@copy_resources_script ||= Generator::CopyResourcesScript.new
end
end
end
......
......@@ -8,20 +8,35 @@ module Pod
class Installer
# The {UserProjectIntegrator} integrates the libraries generated by
# TargetDefinitions of the {Podfile} with their correspondent user project.
# TargetDefinitions of the {Podfile} with their correspondent user
# projects.
#
class UserProjectIntegrator
include Pod::Config::Mixin
# @return [Podfile] the podfile that should be integrated with the user
# projects.
# projects.
#
attr_reader :podfile
# @param [Podfile] podfile @see #podfile.
# @return [Project] the pods project which contains the libraries to
# integrate.
#
def initialize(podfile)
@podfile = podfile
attr_reader :pods_project
# @return [Pathname] the path of the installation.
#
# @todo This is only used to compute the workspace path in case that it
# should be inferred by the project. If the workspace should be in
# the same dir of the project, this could be removed.
#
attr_reader :project_root
# @param [Podfile] podfile @see #podfile.
#
def initialize(podfile, pods_project, project_root)
@podfile = podfile
@pods_project = pods_project
@project_root = project_root
end
# Integrates the user projects associated with the {TargetDefinitions}
......@@ -30,75 +45,117 @@ module Pod
# @return [void]
#
def integrate!
create_workspace!
target_integrators.map(&:integrate!)
create_workspace
integrate_user_targets
warn_about_empty_podfile
end
# creates the workspace containing the Pods project and the user projects
# should be saved.
#--------------------------------------#
# @!group Integration steps
private
# Creates and saved the workspace containing the Pods project and the
# user projects.
#
# @return [void]
#
def create_workspace!
def create_workspace
workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
[pods_project_path, *user_project_paths].each do |project_path|
project_path = project_path.relative_path_from(config.project_root).to_s
workspace << project_path unless workspace.include?(project_path)
[pods_project.path, *user_project_paths].each do |project_path|
path = project_path.relative_path_from(workspace_path.dirname).to_s
workspace << path unless workspace.include?(path)
end
unless workspace_path.exist?
UI.notice "From now on use `#{workspace_path.basename}'."
end
workspace.save_as(workspace_path)
UI.notice "From now on use `#{workspace_path.basename}'." unless workspace_path.exist?
end
# @return [Pathname] the path where the workspace containing the Pods
# project and the user projects should be saved.
# Integrates the targets of the user projects with the libraries
# generated from the {Podfile}.
#
def workspace_path
podfile.workspace || raise(Informative, "Could not automatically select an Xcode workspace. " \
"Specify one in your Podfile.")
# @note {TargetDefinition} without dependencies are skipped prevent
# creating empty libraries for targets definitions which are only
# wrappers for others.
#
# @return [void]
#
def integrate_user_targets
pods_project.libraries.each do |lib|
next if lib.target_definition.empty?
TargetIntegrator.new(lib).integrate!
end
end
# @return [Pathname] the path of the Pods project.
# Warns the user if the podfile is empty.
#
# @note The workspace is created in any case and all the user projects
# are added to it, however the projects are not integrated as
# there is no way to discern between target definitions which are
# empty and target definitions which just serve the purpose to
# wrap other ones. This is not an issue because empty target
# definitions generate empty libraries.
#
# @return [void]
#
def pods_project_path
config.project_root + "Pods/Pods.xcodeproj"
def warn_about_empty_podfile
unless podfile.target_definitions.values.any?{ |td| !td.empty? }
UI.warn "[!] The Podfile does not contain any dependency."
end
end
# @return [Array<TargetIntegrator>] the target integrators for the non
# empty target definitions.
#--------------------------------------#
# @!group Helpers.
public
# @return [Pathname] the path where the workspace containing the Pods
# project and the user projects should be saved.
#
def target_integrators
@target_integrators ||= @podfile.target_definitions.values.map do |definition|
TargetIntegrator.new(definition) unless definition.empty?
end.compact
def workspace_path
if podfile.workspace_path
podfile.workspace_path
elsif user_project_paths.count == 1
project = user_project_paths.first.basename('.xcodeproj')
project_root + "#{project}.xcworkspace"
else
raise Informative, "Could not automatically select an Xcode " \
"workspace. Specify one in your Podfile like so:\n\n" \
" workspace 'path/to/Workspace.xcworkspace'\n"
end
end
# @return [Array<Pathname>] the paths of all the user projects referenced
# by the target definitons.
# by the target definitions.
#
# @note Empty target definitions are ignored.
#
def user_project_paths
@podfile.target_definitions.values.map do |td|
next if td.empty?
td.user_project.path
pods_project.libraries.map do |lib|
# TODO: remove
next if lib.target_definition.empty?
lib.user_project_path
end.compact.uniq
end
#-------------------------------------------------------------------------#
#-----------------------------------------------------------------------#
# This class is responsible for integrating the library generated by a
# {TargetDefinition} with its destination project.
#
class TargetIntegrator
include Pod::Config::Mixin
# @return [TargetDefinition]
# the target definition whose library should be integrated.
# @return [Library] the library that should be integrated.
#
attr_reader :target_definition
attr_reader :library
# @param [TargetDefinition] target_definition @see #target_definition
# @param [Library] library @see #target_definition
#
def initialize(target_definition)
@target_definition = target_definition
def initialize(library)
@library = library
end
# Integrates the user project targets. Only the targets that do **not**
......@@ -109,108 +166,108 @@ module Pod
#
def integrate!
return if targets.empty?
message = "Integrating `#{target_definition.lib_name}' into " \
"#{'target'.pluralize(targets.size)} `#{targets.map(&:name).to_sentence}' " \
"of project #{UI.path user_project_path}."
UI.section(message) do
UI.section(integration_message) do
add_xcconfig_base_configuration
add_pods_library
add_copy_resources_script_phase
user_project.save_as(target_definition.user_project.path)
save_user_project
end
end
# @return [Xcodeproj::Project]
# the project that will be integrated.
# @return [Array<PBXNativeTarget>] the list of targets that the Pods
# lib that need to be integrated.
#
# @note A target is considered integrated if it already references
#
def targets
@targets ||= library.user_targets.reject do |t|
t.frameworks_build_phase.files.any? do |build_file|
file_ref = build_file.file_ref
!file_ref.proxy? && file_ref.display_name == library.name
end
end
end
def user_project
@user_project ||= Xcodeproj::Project.new(user_project_path)
library.user_project
end
# Returns the path of the user project that the {TargetDefinition}
# should integrate.
#
# @raises If the project is implicit and there are multiple projects.
#
# @raises If the path doesn't exits.
# @return [String] a string representation suitable for debugging.
#
# @return [Pathname] the path of the user project.
#
def user_project_path
path = target_definition.user_project.path
def inspect
"#<#{self.class} for target `#{target_definition.label}'>"
end
unless path
raise Informative, "Could not automatically select an Xcode project.\n" \
"Specify one in your Podfile like so:\n\n xcodeproj 'path/to/NAME.xcodeproj'"
end
#--------------------------------------#
unless path.exist?
raise Informative, "The Xcode project `#{path}' does not exist."
end
# @!group Integration steps
path
end
private
# Returns a list of the targets from the project of {TargetDefinition}
# that needs to be integrated.
# Adds the `xcconfig` configurations files generated for the current
# {TargetDefinition} to the build configurations of the targets that
# should be integrated.
#
# The method first looks if there is a target specified with the
# `link_with` option of the {TargetDefinition}. Otherwise it looks for
# the target that has the same name of the target definition.
# Finally if no target was found the first encountered target is
# returned (it is assumed to be the one to integrate in simple
# projects).
# @note It also checks if any build setting of the build
# configurations overrides the `xcconfig` file and warns the
# user.
#
# In addition this will only return targets that do **not** already
# have the Pods library in their frameworks build phase.
# @todo If the xcconfig is already set don't override it and inform
# the user.
#
# @return [Array<PBXNativeTarget>]
# the list of targets that the Pods lib should be linked with.
# @return [void]
#
def targets
unless @targets
if link_with = target_definition.link_with
targets = user_project.targets.select { |t| link_with.include? t.name }
raise Informative, "Unable to find a target named `#{link_with.to_sentence}` to link with target definition `#{target_definition.name}`" if targets.empty?
elsif target_definition.name != :default
target = user_project.targets.find { |t| t.name == target_definition.name.to_s }
targets = [target]
raise Informative, "Unable to find a target named `#{target_definition.name.to_s}`" unless target
else
targets = [user_project.targets.first]
raise Informative, "Unable to find a target" if targets.empty?
end
@targets = targets.reject do |target|
target.frameworks_build_phase.files.any? { |bf| bf.file_ref.name == target_definition.lib_name }
def add_xcconfig_base_configuration
xcconfig = user_project.new_file(library.xcconfig_relative_path)
targets.each do |target|
check_overridden_build_settings(library.xcconfig, target)
target.build_configurations.each do |config|
config.base_configuration_reference = xcconfig
end
end
@targets
end
#@!group Integration
# Adds the `xcconfig` configurations files generated for the current
# {TargetDefinition} to the build configurations of the targets that
# should be integrated.
# Adds a file reference to the library of the {TargetDefinition} and
# adds it to the frameworks build phase of the targets.
#
# It also checks if any build setting of the build configurations
# overrides the `xcconfig` file and warns the user.
# @return [void]
#
# TODO: If the xcconfig is already set don't override it and inform the
# user.
def add_pods_library
frameworks = user_project.frameworks_group
pods_library = frameworks.new_static_library(library.label)
targets.each do |target|
target.frameworks_build_phase.add_file_reference(pods_library)
end
end
# Adds a shell script build phase responsible to copy the resources
# generated by the TargetDefinition to the bundle of the product of the
# targets.
#
# @return [void]
#
def add_xcconfig_base_configuration
xcconfig_ref = user_project.new_file(target_definition.xcconfig_relative_path)
def add_copy_resources_script_phase
targets.each do |target|
check_overridden_build_settings(target_definition.xcconfig, target)
target.build_configurations.each do |config|
config.base_configuration_reference = xcconfig_ref
end
phase = target.new_shell_script_build_phase('Copy Pods Resources')
path = library.copy_resources_script_relative_path
phase.shell_script = %{"#{path}"\n}
end
end
# Saves the changes to the user project to the disk.
#
# @return [void]
#
def save_user_project
user_project.save_as(library.user_project_path)
end
#--------------------------------------#
# @!group Private helpers.
private
# Informs the user about any build setting of the target which might
# override the given xcconfig file.
#
......@@ -231,40 +288,25 @@ module Pod
configs_by_overridden_key.each do |key, config_names|
name = "#{target.name} [#{config_names.join(' - ')}]"
actions = [ "Use the `$(inherited)' flag, or", "Remove the build settings from the target." ]
UI.warn("The target `#{name}' overrides the `#{key}' build setting defined in `#{target_definition.xcconfig_relative_path}'.", actions)
actions = [
"Use the `$(inherited)' flag, or",
"Remove the build settings from the target."
]
UI.warn("The target `#{name}' overrides the `#{key}' build " \
"setting defined in `#{xcconfig_relative_path}'.",
actions)
end
end
end
# Adds a file reference to the library of the {TargetDefinition} and
# adds it to the frameworks build phase of the targets.
#
# @return [void]
#
def add_pods_library
frameworks = user_project.frameworks_group
pods_library = frameworks.new_static_library(target_definition.label)
targets.each do |target|
target.frameworks_build_phase.add_file_reference(pods_library)
end
end
# Adds a shell script build phase responsible to copy the resources
# generated by the TargetDefinition to the bundle of the product of the
# targets.
# @return [String] the message that should be displayed for the target
# integration.
#
# @return [void]
#
def add_copy_resources_script_phase
targets.each do |target|
phase = target.new_shell_script_build_phase('Copy Pods Resources')
phase.shell_script = %{"#{target_definition.copy_resources_script_relative_path}"\n}
end
end
def inspect
"#<#{self.class} for target `#{target_definition.label}'>"
def integration_message
"Integrating `#{library.name}' into " \
"#{'target'.pluralize(targets.size)} " \
"`#{targets.map(&:name).to_sentence}` " \
"of project #{UI.path library.user_project_path}."
end
end
end
......
......@@ -60,7 +60,7 @@ module Pod
# computed values. In other words, it should be immutable.
#
def initialize(specification, sandbox, platform)
@top_specification, @sandbox, @platform = specification.top_level_parent, sandbox, platform
@top_specification, @sandbox, @platform = specification.root, sandbox, platform
@top_specification.activate_platform(platform)
@specifications = [] << specification
end
......@@ -81,7 +81,7 @@ module Pod
# @raise {Informative} If the specification is not part of the same pod.
#
def add_specification(spec)
unless spec.top_level_parent == top_specification
unless spec.root == top_specification
raise Informative,
"[Local Pod] Attempt to add a specification from another pod"
end
......@@ -105,7 +105,7 @@ module Pod
#
def to_s
s = top_specification.to_s
s << " defaulting to #{top_specification.preferred_dependency} subspec" if top_specification.preferred_dependency
s << " defaulting to #{top_specification.default_subspec} subspec" if top_specification.default_subspec
s
end
......@@ -300,7 +300,20 @@ module Pod
# @return [Array<Pathname>] The paths of the resources.
#
def resource_files
paths_by_spec(:resources).values.flatten
specs ||= specifications
paths_by_spec = {}
processed_paths = []
specs = specs.sort_by { |s| s.name.length }
specs.each do |spec|
spec_paths = spec.resources[:resources]
paths = expanded_paths(spec_paths, '**/*', spec.exclude_files)
unless paths.empty?
paths_by_spec[spec] = paths - processed_paths
processed_paths += paths
end
end
paths_by_spec.values.flatten
end
# @return [Array<Pathname>] The *relative* paths of the resources.
......@@ -361,7 +374,14 @@ module Pod
end
def xcconfig
specifications.map { |s| s.xcconfig }.reduce(:merge)
config = Xcodeproj::Config.new
specifications.each do |s|
config.merge(s.xcconfig)
config.libraries.merge(s.libraries)
config.frameworks.merge(s.frameworks)
config.weak_frameworks.merge(s.weak_frameworks)
end
config
end
# Computes the paths of all the public headers of the pod including every
......@@ -440,7 +460,7 @@ module Pod
"project before adding the build files to the target."
end
file_references_by_spec.each do |spec, file_reference|
target.add_file_references(file_reference, spec.compiler_flags.strip)
target.add_file_references(file_reference, (spec.compiler_flags * " ").strip)
end
end
......@@ -498,7 +518,7 @@ module Pod
dir = spec.header_dir ? (headers_sandbox + spec.header_dir) : headers_sandbox
paths.each do |from|
from_relative = from.relative_path_from(root)
to = dir + spec.copy_header_mapping(from_relative)
to = dir + (spec.header_mappings_dir ? from.relative_path_from(spec.header_mappings_dir) : from.basename)
(mappings[to.dirname] ||= []) << from
end
end
......@@ -516,8 +536,10 @@ module Pod
# included in the linker search paths.
#
def headers_excluded_from_search_paths
paths = paths_by_spec(:exclude_header_search_paths, '*.{h,hpp,hh}')
paths.values.compact.uniq
#TODO
# paths = paths_by_spec(:exclude_header_search_paths, '*.{h,hpp,hh}')
# paths.values.compact.uniq
[]
end
# @!group Paths Patterns
......@@ -536,7 +558,7 @@ module Pod
specs = specs.sort_by { |s| s.name.length }
specs.each do |spec|
paths = expanded_paths(spec.send(accessor), dir_pattern, spec.exclude_patterns)
paths = expanded_paths(spec.send(accessor), dir_pattern, spec.exclude_files)
unless paths.empty?
paths_by_spec[spec] = paths - processed_paths
processed_paths += paths
......
module Pod
class LocalPod
# The {PathList} class is designed to perform multiple glob matches against
# a given directory. Basically, it generates a list of all the children
# paths and matches the globs patterns against them, resulting in just
# one access to the file system.
# The PathList class is designed to perform multiple glob matches against
# a given directory. Basically, it generates a list of all the children
# paths and matches the globs patterns against them, resulting in just one
# access to the file system.
#
# @note A {PathList} once it has generated the list of the paths this is
# updated only if explicitly requested by calling
# {PathList#read_file_system}
# @note A PathList once it has generated the list of the paths this is
# updated only if explicitly requested by calling
# {#read_file_system}
#
class PathList
# @return [Pathname] The root of the list whose files and directories
# are used to perform the matching operations.
# are used to perform the matching operations.
#
attr_accessor :root
# @param [Pathname] root The root of the PathList.
# @param [Pathname] root The root of the PathList.
#
def initialize(root)
@root = root
end
# @return [Array<String>] The list of absolute the path of all the files
# contained in {root}.
# contained in {root}.
#
def files
read_file_system unless @files
......@@ -32,7 +32,7 @@ module Pod
end
# @return [Array<String>] The list of absolute the path of all the
# directories contained in {root}.
# directories contained in {root}.
#
def dirs
read_file_system unless @dirs
......@@ -40,7 +40,7 @@ module Pod
end
# @return [void] Reads the file system and populates the files and paths
# lists.
# lists.
#
def read_file_system
root_length = root.to_s.length+1
......@@ -53,22 +53,22 @@ module Pod
end
# @return [Array<Pathname>] Similar to {glob} but returns the absolute
# paths.
# paths.
#
def glob(patterns, dir_pattern = nil, exclude_patterns = nil)
relative_glob(patterns, dir_pattern, exclude_patterns).map {|p| root + p }
end
# @return [Array<Pathname>] The list of relative paths that are case
# insensitively matched by a given pattern. This method emulates
# {Dir#glob} with the {File::FNM_CASEFOLD} option.
# insensitively matched by a given pattern. This method emulates
# {Dir#glob} with the {File::FNM_CASEFOLD} option.
#
# @param [String,Array<String>] patterns A signle {Dir#glob} like
# pattern, or a list of patterns.
# @param [String,Array<String>] patterns
# A signle {Dir#glob} like pattern, or a list of patterns.
#
# @param [String] dir_pattern An optional pattern to append to a
# pattern, if it is the path to a
# directory.
# @param [String] dir_pattern
# An optional pattern to append to a pattern, if it is the path
# to a directory.
#
def relative_glob(patterns, dir_pattern = nil, exclude_patterns = nil)
return [] if patterns.empty?
......@@ -94,9 +94,10 @@ module Pod
end
# @return [Bool] Wether a path is a directory. The result of this method
# computed without accessing the file system and is case insensitive.
# computed without accessing the file system and is case
# insensitive.
#
# @param [String, Pathname] sub_path The path that could be a directory.
# @param [String, Pathname] sub_path The path that could be a directory.
#
def directory?(sub_path)
sub_path = sub_path.to_s.downcase.sub(/\/$/, '')
......@@ -104,8 +105,9 @@ module Pod
end
# @return [Array<String>] An array of patterns converted from a
# {Dir.glob} pattern to patterns that {File.fnmatch} can handle. This
# is used by the {#relative_glob} method to emulate {Dir.glob}.
# {Dir.glob} pattern to patterns that {File.fnmatch} can handle.
# This is used by the {#relative_glob} method to emulate
# {Dir.glob}.
#
# The expansion provides support for:
#
......
require 'digest'
module Pod
class Lockfile
# @return [Lockfile] Returns the Lockfile saved in path.
# Returns {nil} If the file can't be loaded.
#
def self.from_file(path)
return nil unless path.exist?
begin
hash = YAML.load(File.open(path))
rescue Exception => e
raise Informative, "Podfile.lock syntax error: #{e.inspect}"
end
lockfile = Lockfile.new(hash)
lockfile.defined_in_file = path
lockfile
end
# @return [Lockfile] Generates a lockfile from a {Podfile} and the
# list of {Specifications} that were installed.
#
def self.generate(podfile, specs)
Lockfile.new(generate_hash_from_podfile(podfile, specs))
end
# @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 [Hash] hash A Hash representation of a Lockfile.
#
def initialize(hash)
@to_hash = hash
end
# @return [Array<String, Hash{String => Array[String]}>] The pods installed
# and their dependencies.
#
def pods
@pods ||= to_hash['PODS'] || []
end
# @return [Array<Dependency>] The Podfile dependencies used during the last
# install.
#
def 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
@external_sources ||= to_hash["EXTERNAL SOURCES"] || {}
end
# @return [Array<String>] The names of the installed Pods.
#
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 [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
# @return [Dependency] A dependency that describes the exact installed version
# of a Pod.
#
def dependency_for_installed_pod_named(name)
version = pods_versions[name]
raise Informative, "Attempt to lock a Pod without an known version." unless version
dependency = Dependency.new(name, version)
if external_source = external_sources[name]
dependency.external_source = Dependency::ExternalSources.from_params(dependency.name, external_source)
end
dependency
end
# @param [String] The string that describes a {Specification} generated
# from {Specification#to_s}.
#
# @example Strings examples
# "libPusher"
# "libPusher (1.0)"
# "libPusher (HEAD based on 1.0)"
# "RestKit/JSON"
#
# @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_string(match_data[2])
[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)
match_data = string.match(/(\S*)( (.*))?/)
name = match_data[1]
version = match_data[2]
version = version.gsub(/[()]/,'') if version
case version
when nil
Dependency.new(name)
when /defined in Podfile/
# @TODO: store the whole spec?, the version?
Dependency.new(name)
when /from `(.*)'/
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.
Dependency.new(name, :head)
else
Dependency.new(name, version)
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(path)
path.dirname.mkpath unless path.dirname.exist?
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
# presented to the user.
#
def to_s
"Podfile.lock"
end
# @return [String] The YAML representation of the Lockfile, used for
# serialization.
#
def to_yaml
to_hash.to_yaml.gsub(/^--- ?\n/,"").gsub(/^([A-Z])/,"\n\\1")
end
# @return [Hash] The Hash representation of the Lockfile generated from
# a given Podfile and the list of resolved Specifications.
#
def self.generate_hash_from_podfile(podfile, specs)
hash = {}
# Get list of [name, dependencies] pairs.
pod_and_deps = specs.map do |spec|
[spec.to_s, spec.dependencies.map(&:to_s).sort]
end.uniq
# Merge dependencies of iOS and OS X version of the same pod.
tmp = {}
pod_and_deps.each do |name, deps|
if tmp[name]
tmp[name].concat(deps).uniq!
else
tmp[name] = deps
end
end
pod_and_deps = tmp.sort_by(&:first).map do |name, deps|
deps.empty? ? name : { name => deps }
end
hash["PODS"] = pod_and_deps
hash["DEPENDENCIES"] = podfile.dependencies.map{ |d| d.to_s }.sort
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?
checksums = {}
specs.select { |spec| !spec.defined_in_file.nil? }.each do |spec|
checksum = Digest::SHA1.hexdigest(File.read(spec.defined_in_file))
checksum = checksum.encode('UTF-8') if checksum.respond_to?(:encode)
checksums[spec.name] = checksum
end
hash["SPEC CHECKSUMS"] = checksums unless checksums.empty?
hash["COCOAPODS"] = VERSION
hash
end
end
end
module Pod
# A platform describes an SDK name and deployment target.
#
class Platform
# @return [Platform] Convenience method to initialize an iOS platform.
#
def self.ios
new :ios
end
# @return [Platform] Convenience method to initialize an OS X platform.
#
def self.osx
new :osx
end
# Constructs a platform from either another platform or by
# specifying the symbolic name and optionally the deployment target.
#
# @overload initialize(name, deployment_target)
# @param [Symbol] name The name of platform.
# @param [String, Version] deployment_target The optional deployment.
# If not provided a default value according to the platform name will
# be assigned.
#
# @note that if the name is not specified a default deployment
# target will not be assigned.
#
# @example
#
# Platform.new(:ios)
# Platform.new(:ios, '4.3')
#
# @overload initialize(platform)
# @param [Platform] platform Another {Platform}.
#
# @example
#
# platform = Platform.new(:ios)
# Platform.new(platform)
#
def initialize(input, target = nil)
if input.is_a? Platform
@symbolic_name = input.name
@deployment_target = input.deployment_target
else
@symbolic_name = input
target = target[:deployment_target] if target.is_a?(Hash)
@deployment_target = Version.create(target)
end
end
# @return [Symbol] The name of the SDK represented by the platform.
#
def name
@symbolic_name
end
# @return [Version] The deployment target of the platform.
#
attr_reader :deployment_target
# @param [Platform, Symbol] other The other platform to check.
#
# @note If a symbol is passed the comparison does not take into account
# the deployment target.
#
# @return [Boolean] Whether two platforms are the equivalent.
#
def ==(other)
if other.is_a?(Symbol)
@symbolic_name == other
else
(name == other.name) && (deployment_target == other.deployment_target)
end
end
# In the context of operating system SDKs, a platform supports another
# one if they have the same name and the other platform has a minor or
# equal deployment target.
#
# @return Whether the platform supports another platform.
#
def supports?(other)
other = Platform.new(other)
if other.deployment_target && deployment_target
(other.name == name) && (other.deployment_target <= deployment_target)
else
other.name == name
end
end
# @return [String] A string representation including the deployment target.
#
def to_s
case @symbolic_name
when :ios
s = 'iOS'
when :osx
s = 'OS X'
end
s << " #{deployment_target}" if deployment_target
s
end
# @return [Symbol] A Symbol representation of the name.
#
def to_sym
name
end
# @return Whether the platform requires legacy architectures for iOS.
#
def requires_legacy_ios_archs?
(name == :ios) && deployment_target && (deployment_target < Version.new("4.3"))
end
end
end
module Pod
class Podfile
class Informative < ::Pod::Informative
def podfile_line
@podfile_line ||= self.backtrace.find {|t| t =~ /Podfile/}
end
def message
if podfile_line
super + " (#{podfile_line})\n".red
else
super
end
end
end
class UserProject
include Config::Mixin
DEFAULT_BUILD_CONFIGURATIONS = { 'Debug' => :debug, 'Release' => :release }.freeze
def initialize(path = nil, build_configurations = {})
self.path = path if path
@build_configurations = build_configurations.merge(DEFAULT_BUILD_CONFIGURATIONS)
end
def path=(path)
path = path.to_s
@path = Pathname.new(File.extname(path) == '.xcodeproj' ? path : "#{path}.xcodeproj")
@path = config.project_root + @path unless @path.absolute?
@path
end
def path
if @path
@path
else
xcodeprojs = Pathname.glob(config.project_root + '*.xcodeproj')
if xcodeprojs.size == 1
@path = xcodeprojs.first
end
end
end
def project
Xcodeproj::Project.new(path) if path && path.exist?
end
def build_configurations
if project
project.build_configurations.map(&:name).inject({}) do |hash, name|
hash[name] = :release; hash
end.merge(@build_configurations)
else
@build_configurations
end
end
end
class TargetDefinition
include Config::Mixin
attr_reader :name, :target_dependencies
attr_accessor :user_project, :link_with, :platform, :parent, :exclusive, :inhibit_all_warnings
def initialize(name, options = {})
@name, @target_dependencies = name, []
@parent, @exclusive = options.values_at(:parent, :exclusive)
end
# A target is automatically `exclusive` if the `platform` does not match
# the parent's `platform`.
def exclusive
if @exclusive.nil?
if @platform.nil?
false
else
@parent.platform != @platform
end
else
@exclusive
end
end
alias_method :exclusive?, :exclusive
def user_project
@user_project || @parent.user_project
end
def link_with=(targets)
@link_with = targets.is_a?(Array) ? targets : [targets]
end
def platform
@platform || (@parent.platform if @parent)
end
def inhibit_all_warnings
@inhibit_all_warnings.nil? ? (@parent.inhibit_all_warnings? if @parent) : @inhibit_all_warnings
end
alias_method :inhibit_all_warnings?, :inhibit_all_warnings
def label
if name == :default
"Pods"
elsif exclusive?
"Pods-#{name}"
else
"#{@parent.label}-#{name}"
end
end
def acknowledgements_path
config.project_pods_root + "#{label}-Acknowledgements"
end
# Returns a path, which is relative to the project_root, relative to the
# `$(SRCROOT)` of the user's project.
def relative_to_srcroot(path)
if user_project.path.nil?
# TODO this is not in the right place
raise Informative, "[!] Unable to find an Xcode project to integrate".red if config.integrate_targets
path
else
(config.project_root + path).relative_path_from(user_project.path.dirname)
end
end
def relative_pods_root
"${SRCROOT}/#{relative_to_srcroot "Pods"}"
end
def lib_name
"lib#{label}.a"
end
def xcconfig_name
"#{label}.xcconfig"
end
def xcconfig_relative_path
relative_to_srcroot("Pods/#{xcconfig_name}").to_s
end
attr_accessor :xcconfig
def copy_resources_script_name
"#{label}-resources.sh"
end
def copy_resources_script_relative_path
"${SRCROOT}/#{relative_to_srcroot("Pods/#{copy_resources_script_name}")}"
end
def prefix_header_name
"#{label}-prefix.pch"
end
def bridge_support_name
"#{label}.bridgesupport"
end
# Returns *all* dependencies of this target, not only the target specific
# ones in `target_dependencies`.
def dependencies
@target_dependencies + (exclusive? ? [] : @parent.dependencies)
end
def empty?
target_dependencies.empty?
end
end
def self.from_file(path)
podfile = Podfile.new do
string = File.open(path, 'r:utf-8') { |f| f.read }
# TODO: work around for Rubinius incomplete encoding in 1.9 mode
string.encode!('UTF-8') if string.respond_to?(:encoding) && string.encoding.name != "UTF-8"
begin
eval(string, nil, path.to_s)
rescue Exception => e
raise Informative, "Podfile syntax error: #{e.inspect}"
end
end
podfile.defined_in_file = path
podfile.validate!
podfile
end
include Config::Mixin
def initialize(&block)
@target_definition = TargetDefinition.new(:default, :exclusive => true)
@target_definition.user_project = UserProject.new
@target_definitions = { :default => @target_definition }
instance_eval(&block)
end
# Specifies the platform for which a static library should be build.
# This can be either `:osx` for Mac OS X applications, or `:ios` for iOS
# applications.
#
# @param [Symbol] name The name of platform.
# @param [String, Version] target The optional deployment.
# If not provided a default value according to the platform name will
# be assigned.
#
# @example
#
# platform :ios, "4.0"
# platform :ios
#
# @note If the deployment target requires it (< 4.3), armv6 will be added
# to ARCHS.
#
def platform(name, target = nil)
# Support for deprecated options parameter
target = target[:deployment_target] if target.is_a?(Hash)
unless target
case name
when :ios
target = '4.3'
when :osx
target = '10.6'
else
raise ::Pod::Podfile::Informative, "Unsupported platform: platform must be one of [:ios, :osx]"
end
end
@target_definition.platform = Platform.new(name, target)
end
# Specifies the Xcode workspace that should contain all the projects.
#
# If no explicit Xcode workspace is specified and only **one** project exists
# in the same directory as the Podfile, then the name of that project is used
# as the workspace’s name.
#
# @example
#
# workspace 'MyWorkspace'
#
def workspace(path = nil)
if path
@workspace = config.project_root + (File.extname(path) == '.xcworkspace' ? path : "#{path}.xcworkspace")
elsif @workspace
@workspace
else
projects = @target_definitions.map { |_, td| td.user_project.path }.uniq
if projects.size == 1 && (xcodeproj = @target_definitions[:default].user_project.path)
config.project_root + "#{xcodeproj.basename('.xcodeproj')}.xcworkspace"
end
end
end
# Specifies the Xcode project that contains the target that the Pods library
# should be linked with.
#
# If no explicit project is specified, it will use the Xcode project of the
# parent target. If none of the target definitions specify an explicit project
# and there is only **one** project in the same directory as the Podfile then
# that project will be used.
#
# @example
#
# # Look for target to link with in an Xcode project called ‘MyProject.xcodeproj’.
# xcodeproj 'MyProject'
#
# target :test do
# # This Pods library links with a target in another project.
# xcodeproj 'TestProject'
# end
#
def xcodeproj(path, build_configurations = {})
@target_definition.user_project = UserProject.new(path, build_configurations)
end
# Specifies the target(s) in the user’s project that this Pods library
# should be linked in.
#
# If no explicit target is specified, then the Pods target will be linked
# with the first target in your project. So if you only have one target you
# do not need to specify the target to link with.
#
# @example
#
# # Link with a target called ‘MyApp’ (in the user's project).
# link_with 'MyApp'
#
# # Link with the targets in the user’s project called ‘MyApp’ and ‘MyOtherApp’.
# link_with ['MyApp', 'MyOtherApp']
#
def link_with(targets)
@target_definition.link_with = targets
end
# Inhibits **all** warnings from the Pods library.
#
# When used, this is applied to all targets inheriting from the current one.
def inhibit_all_warnings!
@target_definition.inhibit_all_warnings = true
end
# Specifies a dependency of the project.
#
# A dependency requirement is defined by the name of the Pod and _optionally_
# a list of version requirements.
#
#
# When starting out with a project it is likely that you will want to use the
# latest version of a Pod. If this is the case, simply omit the version
# requirements.
#
# pod 'SSZipArchive'
#
#
# Later on in the project you may want to freeze to a specific version of a
# Pod, in which case you can specify that version number.
#
# pod 'Objection', '0.9'
#
#
# Besides no version, or a specific one, it is also possible to use operators:
#
# * `> 0.1` Any version higher than 0.1
# * `>= 0.1` Version 0.1 and any higher version
# * `< 0.1` Any version lower than 0.1
# * `<= 0.1` Version 0.1 and any lower version
# * `~> 0.1.2` Version 0.1.2 and the versions upto 0.2, not including 0.2
#
# A list of version requirements can be specified for even more fine
# grained control.
#
#
# For more information, regarding versioning policy, see:
#
# * http://semver.org
# * http://docs.rubygems.org/read/chapter/7
#
#
# Finally, instead of a version, you can specify the `:head` flag. This will
# use the pod’s latest version spec version, but force the download of the
# ‘bleeding edge’ version. Use this with caution, as the spec might not be
# compatible anymore.
#
#
# ## Dependency on a library, outside those available in a spec repo.
#
# ### From a podspec in the root of a library repo.
#
# Sometimes you may want to use the bleeding edge version of a Pod. Or a
# specific revision. If this is the case, you can specify that with your
# pod declaration.
#
#
# To use the `master` branch of the repo:
#
# pod 'TTTFormatterKit', :git => 'https://github.com/gowalla/AFNetworking.git'
#
#
# Or specify a commit:
#
# pod 'TTTFormatterKit', :git => 'https://github.com/gowalla/AFNetworking.git', :commit => '082f8319af'
#
#
# It is important to note, though, that this means that the version will
# have to satisfy any other dependencies on the Pod by other Pods.
#
#
# The `podspec` file is expected to be in the root of the repo, if this
# library does not have a `podspec` file in its repo yet, you will have to
# use one of the approaches outlined in the sections below.
#
#
# ### From a podspec outside a spec repo, for a library without podspec.
#
# If a podspec is available from another source outside of the library’s
# repo. Consider, for instance, a podpsec available via HTTP:
#
# pod 'JSONKit', :podspec => 'https://raw.github.com/gist/1346394/1d26570f68ca27377a27430c65841a0880395d72/JSONKit.podspec'
#
#
# ### For a library without any available podspec
#
# Finally, if no living soul has created a podspec, for the library you want
# to use, yet, you will have to specify the library yourself.
#
#
# When you omit arguments and pass a block to `pod`, an instance of
# Pod::Specification is yielded to the block. This is the same class which
# is normally used to specify a Pod.
#
# ```
# pod do |spec|
# spec.name = 'JSONKit'
# spec.version = '1.4'
# spec.source = { :git => 'https://github.com/johnezang/JSONKit.git', :tag => 'v1.4' }
# spec.source_files = 'JSONKit.*'
# end
# ```
#
#
# For more info on the definition of a Pod::Specification see:
# https://github.com/CocoaPods/CocoaPods/wiki/A-pod-specification
def pod(*name_and_version_requirements, &block)
@target_definition.target_dependencies << Dependency.new(*name_and_version_requirements, &block)
end
# Use the dependencies of a podspec file.
#
def podspec(options = nil)
if options && path = options[:path]
path = File.extname(path) == '.podspec' ? path : "#{path}.podspec"
file = Pathname.new(File.expand_path(path))
elsif options && name = options[:name]
name = File.extname(name) == '.podspec' ? name : "#{name}.podspec"
file = config.project_root + name
else
file = config.project_root.glob('*.podspec').first
end
spec = Specification.from_file(file)
spec.activate_platform(@target_definition.platform)
deps = spec.recursive_subspecs.push(spec).map {|specification| specification.external_dependencies }
deps = deps.flatten.uniq
@target_definition.target_dependencies.concat(deps)
end
def dependency(*name_and_version_requirements, &block)
warn "[DEPRECATED] `dependency' is deprecated (use `pod')"
pod(*name_and_version_requirements, &block)
end
# Specifies that a BridgeSupport metadata document should be generated from
# the headers of all installed Pods.
#
# This is for scripting languages such as MacRuby, Nu, and JSCocoa, which use
# it to bridge types, functions, etc better.
def generate_bridge_support!
@generate_bridge_support = true
end
# Defines a new static library target and scopes dependencies defined from
# the given block. The target will by default include the dependencies
# defined outside of the block, unless the `:exclusive => true` option is
# given.
#
# Consider the following Podfile:
#
# pod 'ASIHTTPRequest'
#
# target :debug do
# pod 'SSZipArchive'
# end
#
# target :test, :exclusive => true do
# pod 'JSONKit'
# end
#
# This Podfile defines three targets. The first one is the `:default` target,
# which produces the `libPods.a` file. The second and third are the `:debug`
# and `:test` ones, which produce the `libPods-debug.a` and `libPods-test.a`
# files.
#
# The `:default` target has only one dependency (ASIHTTPRequest), whereas the
# `:debug` target has two (ASIHTTPRequest, SSZipArchive). The `:test` target,
# however, is an exclusive target which means it will only have one
# dependency (JSONKit).
def target(name, options = {})
parent = @target_definition
options[:parent] = parent
@target_definitions[name] = @target_definition = TargetDefinition.new(name, options)
yield
ensure
@target_definition = parent
end
# This hook allows you to make any changes to the downloaded Pods and to
# their targets before they are installed.
#
# pre_install do |installer|
# # Do something fancy!
# end
#
def pre_install(&block)
@pre_install_callback = block
end
# This hook allows you to make any last changes to the generated Xcode project
# before it is written to disk, or any other tasks you might want to perform.
#
# For instance, say you'd want to customize the `OTHER_LDFLAGS` of all targets:
#
# post_install do |installer|
# installer.project.targets.each do |target|
# target.build_configurations.each do |config|
# config.build_settings['GCC_ENABLE_OBJC_GC'] = 'supported'
# end
# end
# end
def post_install(&block)
@post_install_callback = block
end
# Specifies that the -fobjc-arc flag should be added to the OTHER_LD_FLAGS.
#
# This is used as a workaround for a compiler bug with non-ARC projects.
# (see https://github.com/CocoaPods/CocoaPods/issues/142)
#
# This was originally done automatically but libtool as of Xcode 4.3.2 no
# longer seems to support the -fobjc-arc flag. Therefore it now has to be
# enabled explicitly using this method.
#
# This may be removed in a future release.
def set_arc_compatibility_flag!
@set_arc_compatibility_flag = true
end
# Not attributes
def podfile?
true
end
attr_accessor :defined_in_file
attr_reader :target_definitions
def dependencies
@target_definitions.values.map(&:target_dependencies).flatten.uniq
end
def dependency_by_top_level_spec_name(name)
dependencies.find { |d| d.top_level_spec_name == name }
end
def generate_bridge_support?
@generate_bridge_support
end
def set_arc_compatibility_flag?
@set_arc_compatibility_flag
end
def user_build_configurations
configs_array = @target_definitions.values.map { |td| td.user_project.build_configurations }
configs_array.inject({}) { |hash, config| hash.merge(config) }
end
def pre_install!(installer)
@pre_install_callback.call(installer) if @pre_install_callback
end
def post_install!(installer)
@post_install_callback.call(installer) if @post_install_callback
end
def validate!
end
def to_s
"Podfile"
end
end
end
require 'xcodeproj'
# Xcodeproj::Project::Object::PBXCopyFilesBuildPhase.instance_eval do
# def self.new_pod_dir(project, pod_name, path)
# new(project, nil, {
# "dstPath" => "Pods/#{path}",
# "name" => "Copy #{pod_name} Public Headers",
# })
# end
# end
module Pod
# Provides support for generating the Pods project
#
class Project < Xcodeproj::Project
include Config::Mixin
attr_reader :support_files_group
# @return [Sandbox] the sandbox that contains the project.
#
attr_reader :sandbox
def initialize(*)
super
podfile_path = config.project_podfile.relative_path_from(config.project_pods_root).to_s
podfile_ref = new_file(podfile_path)
podfile_ref.xc_language_specification_identifier = 'xcode.lang.ruby'
new_group('Pods')
# @param [Sandbox] sandbox @see #sandbox
#
def initialize(sandbox)
super(nil)
@sandbox = sandbox
@support_files_group = new_group('Targets Support Files')
@user_build_configurations = []
@libraries = []
end
def user_build_configurations=(user_build_configurations)
@user_build_configurations = user_build_configurations
# The configurations at the top level only need to exist, they don't hold
# any build settings themselves, that's left to `add_pod_target`.
user_build_configurations.each do |name, _|
unless build_configurations.map(&:name).include?(name)
bc = new(XCBuildConfiguration)
bc.name = name
build_configurations << bc
end
end
# @return [Pathname] the path of the Pods project.
#
def path
sandbox.project_path
end
# Shortcut access to the `Pods' PBXGroup.
# @return [String] a string representation suited for debugging.
#
def inspect
"#<#{self.class}>"
end
#--------------------------------------#
#@!group Working with groups
# @return [PBXGroup] the group where the support files for the Pod
# libraries should be added.
#
attr_reader :support_files_group
# Returns the `Pods` group, creating it if needed.
#
# @return [PBXGroup] the group.
#
def pods
@pods ||= self['Pods'] || new_group('Pods')
@pods ||= new_group('Pods')
end
# Shortcut access to the `Local Pods' PBXGroup.
# Returns the `Local Pods` group, creating it if needed. This group is used
# to contain locally sourced pods.
#
# @return [PBXGroup] the group.
#
def local_pods
@local_pods ||= self['Local Pods'] || new_group('Local Pods')
@local_pods ||= new_group('Local Pods')
end
# Adds a group as child to the `Pods' group namespacing subspecs.
# Adds a group as child to the `Pods` group namespacing subspecs.
#
# TODO: Pass the specification directly and don't expose the pods groups.
#
def add_spec_group(name, parent_group)
current_group = parent_group
group = nil
......@@ -59,19 +74,69 @@ module Pod
group
end
def add_pod_target(name, platform)
target = new_target(:static_library, name, platform.name)
#--------------------------------------#
settings = {}
if platform.requires_legacy_ios_archs?
settings['ARCHS'] = "armv6 armv7"
#@!group Manipulating the project
# @return [Array<Library>] the libraries generated from the target
# definitions of the Podfile.
#
attr_reader :libraries
# Adds a file reference to the podfile.
#
# @param [#to_s] path
# the path of the podfile
#
# @return [PBXFileReference]
#
def add_podfile(podfile_path)
podfile_path = Pathname.new(podfile_path)
podfile_ref = new_file(podfile_path.relative_path_from(path.dirname))
podfile_ref.xc_language_specification_identifier = 'xcode.lang.ruby'
podfile_ref
end
# Creates the user build configurations for the Pods project.
#
# @note The configurations at the top level only need to exist, they
# don't hold any build settings themselves, that's left to
# `add_pod_library`.
#
# @return [void]
#
# TODO: why is this needed?
#
def user_build_configurations=(user_build_configurations)
@user_build_configurations = user_build_configurations
user_build_configurations.each do |name, _|
unless build_configurations.map(&:name).include?(name)
bc = new(XCBuildConfiguration)
bc.name = name
build_configurations << bc
end
end
end
# Creates a static library target for the given target_definition.
#
# @param [TargetDefinition] target_definition
# the target definition of the library.
#
# @raise If the target definition doesn't specifies a platform.
#
# @return [Library] the library for the created target.
#
def add_pod_library(target_definition)
name = target_definition.label
platform = target_definition.platform
if platform == :ios && platform.deployment_target
# TODO: add for osx as well
settings['IPHONEOS_DEPLOYMENT_TARGET'] = platform.deployment_target.to_s
unless platform
raise Informative, "Missing platform for #{target_definition}"
end
settings = settings_for_platform(platform)
target = new_target(:static_library, name, platform.name)
target.build_settings('Debug').merge!(settings)
target.build_settings('Release').merge!(settings)
......@@ -85,7 +150,271 @@ module Pod
end
end
target
lib = Library.new(target_definition, target)
libraries << lib
lib
end
#--------------------------------------#
#@!group Helpers
private
# Returns the Xcode build settings for a target with the given platform.
#
# @param [Platform] platform
# the platform for which the build settings are needed.
#
# @return [Hash] the build settings.
#
def settings_for_platform(platform)
settings = {}
settings['ARCHS'] = "armv6 armv7" if platform.requires_legacy_ios_archs?
if dt = platform.deployment_target
if platform == :ios
settings['IPHONEOS_DEPLOYMENT_TARGET'] = dt.to_s
else
# TODO: add MACOSX_DEPLOYMENT_TARGET
end
end
settings
end
#-------------------------------------------------------------------------#
# Describes a library generated for the Pods project.
#
class Library
include Config::Mixin
# @return [PBXNativeTarget] the target definition of the Podfile that
# generated this library.
#
attr_reader :target_definition
# @return [PBXNativeTarget] the target generated in the Pods project for
# this library.
#
attr_reader :target
# @param [TargetDefinition] target_definition @see target_definition
# @param [PBXNativeTarget] target @see target
# @param [Project] project @see project
#
def initialize(target_definition, target)
@target_definition = target_definition
@target = target
end
def label
target_definition.label
end
#-----------------------------------------------------------------------#
# @!group User project
# @return [Xcodeproj::Project]
# the project that will be integrated.
#
def user_project
@user_project ||= Xcodeproj::Project.new(user_project_path)
end
# Returns the path of the user project that the {TargetDefinition}
# should integrate.
#
# @raises If the project is implicit and there are multiple projects.
#
# @raises If the path doesn't exits.
#
# @return [Pathname] the path of the user project.
#
def user_project_path
unless @user_project_path
if target_definition.user_project_path
@user_project_path = Pathname.new(config.project_root + target_definition.user_project_path)
unless @user_project_path.exist?
raise Informative, "Unable to find the Xcode project `#{@user_project_path}` for the target `#{label}`."
end
else
xcodeprojs = Pathname.glob(config.project_root + '*.xcodeproj')
if xcodeprojs.size == 1
@user_project_path = xcodeprojs.first
else
raise Informative, "Could not automatically select an Xcode project. " \
"Specify one in your Podfile like so:\n\n" \
" xcodeproj 'path/to/Project.xcodeproj'\n"
end
end
end
@user_project_path
end
# Returns a list of the targets from the project of {TargetDefinition}
# that needs to be integrated.
#
# @note The method first looks if there is a target specified with
# the `link_with` option of the {TargetDefinition}. Otherwise
# it looks for the target that has the same name of the target
# definition. Finally if no target was found the first
# encountered target is returned (it is assumed to be the one
# to integrate in simple projects).
#
# @note This will only return targets that do **not** already have
# the Pods library in their frameworks build phase.
#
# @return [Array<PBXNativeTarget>] the list of targets that the Pods
# lib should be linked with.
#
def user_targets
unless @targets
if link_with = target_definition.link_with
@targets = user_project.targets.select { |t| link_with.include? t.name }
raise Informative, "Unable to find a target named `#{link_with.to_sentence}` to link with target definition `#{target_definition.name}`" if @targets.empty?
elsif target_definition.name != :default
target = user_project.targets.find { |t| t.name == target_definition.name.to_s }
@targets = [ target ].compact
raise Informative, "Unable to find a target named `#{target_definition.name.to_s}`" if @targets.empty?
else
@targets = [ user_project.targets.first ].compact
raise Informative, "Unable to find a target" if @targets.empty?
end
end
@targets
end
#-----------------------------------------------------------------------#
# @!group TargetInstaller & UserProjectIntegrator helpers
# @return [String] the name of the library.
#
def name
"lib#{target_definition.label}.a"
end
# @return [Project] the Pods project.
#
def project
target.project
end
# Computes the relative path of a sandboxed file from the `$(SRCROOT)` of
# the user's project.
#
# @param [Pathname] path
#
# @return [String] the computed path.
#
def relative_to_srcroot(path = nil)
base_path = path ? config.project_pods_root + path : config.project_pods_root
(base_path).relative_path_from(user_project_path.dirname).to_s
end
def relative_pods_root
"${SRCROOT}/#{relative_to_srcroot}"
end
# @return [Pathname] the folder where to store the support files of this
# library.
#
# @todo each library should have a group for its support files
#
def support_files_root
project.sandbox.root
end
#---------------------------------------#
# @return [Xcodeproj::Config] the configuration file of the library
#
# @note The configuration is generated by the {TargetInstaller} and
# used by {UserProjectIntegrator} to check for any overridden
# values.
#
attr_accessor :xcconfig
# @return [String] the name of the xcconfig file relative to this target.
#
def xcconfig_name
"#{label}.xcconfig"
end
# @return [Pathname] the absolute path of the xcconfig file.
#
def xcconfig_path
support_files_root + xcconfig_name
end
# @return [String] the path of the xcconfig file relative to the root of
# the user project.
#
def xcconfig_relative_path
relative_to_srcroot("#{xcconfig_name}").to_s
end
#---------------------------------------#
# @return [String] the name of the copy resources script relative to this
# target.
#
def copy_resources_script_name
"#{label}-resources.sh"
end
# @return [Pathname] the absolute path of the copy resources script.
#
def copy_resources_script_path
support_files_root + copy_resources_script_name
end
# @return [String] the path of the copy resources script relative to the
# root of the user project.
#
def copy_resources_script_relative_path
"${SRCROOT}/#{relative_to_srcroot("#{copy_resources_script_name}")}"
end
#---------------------------------------#
# @return [String] the name of the prefix header file relative to this
# target.
#
def prefix_header_name
"#{label}-prefix.pch"
end
# @return [Pathname] the absolute path of the prefix header file.
#
def prefix_header_path
support_files_root + prefix_header_name
end
#---------------------------------------#
# @return [String] the name of the bridge support file relative to this
# target.
#
def bridge_support_name
"#{label}.bridgesupport"
end
# @return [Pathname] the absolute path of the bridge support file.
#
def bridge_support_path
support_files_root + bridge_support_name
end
# @todo
#
def acknowledgements_path
support_files_root + "#{label}-Acknowledgements"
end
end
end
end
require 'colored'
module Pod
# The resolver is responsible of generating a list of specifications grouped
# by target for a given Podfile.
#
# Its current implementation is naive, in the sense that it can't do full
# automatic resolves like Bundler:
# [how-does-bundler-bundle](http://patshaughnessy.net/2011/9/24/how-does-bundler-bundle)
#
# Another important aspect to keep in mind of the current implementation
# is that the order of the dependencies matters.
#
class Resolver
include Config::Mixin
# @return [Bool] Whether the resolver should find the pods to install or
# the pods to update.
#
attr_accessor :update_mode
include Config::Mixin
# @return [Bool] Whether the resolver should update the external specs
# in the resolution process.
# @return [Sandbox] the Sandbox used by the resolver to find external
# dependencies.
#
attr_accessor :update_external_specs
attr_reader :sandbox
# @return [Podfile] The Podfile used by the resolver.
# @return [Podfile] the Podfile used by the resolver.
#
attr_reader :podfile
# @return [Lockfile] The Lockfile used by the resolver.
# @return [Array<Dependency>] the list of dependencies locked to a specific
# version.
#
attr_reader :lockfile
attr_reader :locked_dependencies
# @return [Sandbox] The Sandbox used by the resolver to find external
# dependencies.
# @return [Bool] whether the resolver should update the external specs
# in the resolution process. This option is used for detecting
# changes in with the Podfile without affecting the existing Pods
# installation
#
attr_reader :sandbox
# @return [Array<Strings>] The name of the pods that have an
# external source.
# @note This option is used by `pod outdated`.
#
attr_reader :pods_from_external_sources
# @return [Array<Set>] A cache of the sets used to resolve the dependencies.
# @todo: This implementation is not clean, because if the spec doesn't
# exists the sandbox will actually download and modify the
# installation.
#
attr_reader :cached_sets
attr_accessor :update_external_specs
# @return [Source::Aggregate] A cache of the sources needed to find the
# podspecs.
# @param [Sandbox] sandbox @see sandbox
# @param [Podfile] podfile @see podfile
# @param [Array<Dependency>] locked_dependencies @see locked_dependencies
#
attr_reader :cached_sources
def initialize(sandbox, podfile, locked_dependencies = [])
@sandbox = sandbox
@podfile = podfile
@locked_dependencies = locked_dependencies
end
# @return [Hash{Podfile::TargetDefinition => Array<Specification>}]
# Returns the resolved specifications grouped by target.
#
attr_reader :specs_by_target
#-------------------------------------------------------------------------#
def initialize(podfile, lockfile, sandbox)
@podfile = podfile
@lockfile = lockfile
@sandbox = sandbox
@update_external_specs = true
# @!group Resolution
@cached_sets = {}
@cached_sources = Source::Aggregate.new
end
public
# Identifies the specifications that should be installed according whether
# the resolver is in update mode or not.
# Identifies the specifications that should be installed.
#
# @return [Hash{Podfile::TargetDefinition => Array<Specification>}] specs_by_target
# @return [Hash{TargetDefinition => Array<Specification>}] specs_by_target
# the specifications that need to be installed grouped by target
# definition.
#
def resolve
@cached_specs = {}
@cached_sources = Source::Aggregate.new(config.repos_dir)
@cached_sets = {}
@cached_specs = {}
@specs_by_target = {}
@pods_from_external_sources = []
@pods_to_lock = []
if @lockfile
@pods_by_state = @lockfile.detect_changes_with_podfile(podfile)
UI.section "Finding added, modified or removed dependencies:" do
marks = {:added => "A".green, :changed => "M".yellow, :removed => "R".red, :unchanged => "-" }
@pods_by_state.each do |symbol, pod_names|
pod_names.each do |pod_name|
UI.message("#{marks[symbol]} #{pod_name}", '',2)
end
end
end if config.verbose?
@pods_to_lock = (lockfile.pods_names - @pods_by_state[:added] - @pods_by_state[:changed] - @pods_by_state[:removed]).uniq
end
# @pods_from_external_sources = []
unless config.skip_repo_update?
UI.section 'Updating spec repositories' do
Command::Repo::Update.parse([]).run
end if !@lockfile || !(@pods_by_state[:added] + @pods_by_state[:changed]).empty? || update_mode
end
@podfile.target_definitions.values.each do |target_definition|
UI.section "Resolving dependencies for target `#{target_definition.name}' (#{target_definition.platform})" do
podfile.target_definitions.values.each do |target|
UI.section "Resolving dependencies for target `#{target.name}' (#{target.platform})" do
@loaded_specs = []
find_dependency_specs(@podfile, target_definition.dependencies, target_definition)
@specs_by_target[target_definition] = @cached_specs.values_at(*@loaded_specs).sort_by(&:name)
find_dependency_specs(podfile, target.dependencies, target)
specs = cached_specs.values_at(*@loaded_specs).sort_by(&:name)
specs_by_target[target] = specs
end
end
@cached_specs.values.sort_by(&:name)
@specs_by_target
cached_specs.values.sort_by(&:name)
specs_by_target
end
# @return [Array<Specification>] The specifications loaded by the resolver.
# @return [Hash{Podfile::TargetDefinition => Array<Specification>}]
# returns the resolved specifications grouped by target.
#
# @note The returned specifications can be subspecs.
#
attr_reader :specs_by_target
# @return [Array<Specification>] All the specifications resolved.
#
def specs
@cached_specs.values.uniq
specs_by_target.values.flatten.uniq
end
# @return [Bool] Whether a pod should be installed/reinstalled.
# @return [Array<Strings>] The name of the pods that have an
# external source.
#
def should_install?(name)
pods_to_install.include? name
end
# TODO: Not sure if needed.
#
# attr_reader :pods_from_external_sources
# @return [Array<Strings>] The name of the pods that should be
# installed/reinstalled.
#
def pods_to_install
unless @pods_to_install
if lockfile
@pods_to_install = specs.select do |spec|
spec.version != lockfile.pods_versions[spec.pod_name]
end.map(&:name)
if update_mode
@pods_to_install += specs.select do |spec|
spec.version.head? || pods_from_external_sources.include?(spec.pod_name)
end.map(&:name)
end
@pods_to_install += @pods_by_state[:added] + @pods_by_state[:changed]
else
@pods_to_install = specs.map(&:name)
end
end
@pods_to_install
end
#-------------------------------------------------------------------------#
# @return [Array<Strings>] The name of the pods that were installed
# but don't have any dependency anymore. The name of the Pods are
# stripped from subspecs.
#
def removed_pods
return [] unless lockfile
unless @removed_pods
previusly_installed = lockfile.pods_names.map { |pod_name| pod_name.split('/').first }
installed = specs.map { |spec| spec.name.split('/').first }
@removed_pods = previusly_installed - installed
end
@removed_pods
end
# !@ Resolution context
private
# @return [Set] The cached set for a given dependency.
# @return [Source::Aggregate] A cache of the sources needed to find the
# podspecs.
#
def find_cached_set(dependency, platform)
set_name = dependency.name.split('/').first
@cached_sets[set_name] ||= begin
if dependency.specification
Specification::Set::External.new(dependency.specification)
elsif external_source = dependency.external_source
if update_mode && update_external_specs
# Always update external sources in update mode.
specification = external_source.specification_from_external(@sandbox, platform)
else
# Don't update external sources in install mode if not needed.
specification = external_source.specification_from_sandbox(@sandbox, platform)
end
set = Specification::Set::External.new(specification)
if dependency.subspec_dependency?
@cached_sets[dependency.top_level_spec_name] ||= set
end
set
else
@cached_sources.search(dependency)
end
end
end
# @todo Cache the sources globally?
#
attr_accessor :cached_sources
# @return [Hash<String => Set>] A cache that keeps tracks of the sets
# loaded by the resolution process.
#
# @note Sets keep track of the TODO:
#
attr_accessor :cached_sets
#
#
attr_accessor :cached_specs
#
#
attr_writer :specs_by_target
# Resolves the dependencies of a specification and stores them in @cached_specs
#-------------------------------------------------------------------------#
# !@ Resolution helpers
private
# Resolves recursively the dependencies of a specification and stores them
# in the @cached_specs ivar.
#
# @param [Podfile, Specification] dependent_spec
# the specification whose dependencies are being resolved.
#
# @param [Array<Dependency>] dependencies
# the dependencies of the specification.
#
# @param [TargetDefinition] target_definition
# the target definition that owns the specification.
#
# @note If there is a locked dependency with the same name of a
# given dependency the locked one is used in place of the
# dependency of the specification. In this way it is possible to
# not updated the installed pods without without introducing
# dependencies in other target definitions.
#
# @todo Just add the requirement to the set?
# @todo Use root name?
#
# @note The recursive process checks if a dependency has already been
# loaded to prevent an infinite loop. For this reason the
# @loaded_specs ivar must be cleaned when changing target
# definition.
#
# @param [Specification] dependent_specification
# @param [Array<Dependency>] dependencies
# @param [TargetDefinition] target_definition
#
# TODO: The set class should be aware whether it is in head mode.
#
# @return [void]
#
def find_dependency_specs(dependent_specification, dependencies, target_definition)
def find_dependency_specs(dependent_spec, dependencies, target_definition)
dependencies.each do |dependency|
# Replace the dependency with a more specific one if the pod is already installed.
if !update_mode && @pods_to_lock.include?(dependency.name)
dependency = lockfile.dependency_for_installed_pod_named(dependency.name)
end
locked_dep = locked_dependencies.find { |ld| ld.name == dependency.name }
dependency = locked_dep if locked_dep
UI.message("- #{dependency}", '', 2) do
set = find_cached_set(dependency, target_definition.platform)
set.required_by(dependency, dependent_specification.to_s)
set = find_cached_set(dependency)
set.required_by(dependency, dependent_spec.to_s)
# Ensure we don't resolve the same spec twice for one target
unless @loaded_specs.include?(dependency.name)
spec = set.specification_by_name(dependency.name)
@pods_from_external_sources << spec.pod_name if dependency.external?
spec = set.specification.subspec_by_name(dependency.name)
@loaded_specs << spec.name
@cached_specs[spec.name] = spec
# Configure the specification
cached_specs[spec.name] = spec
# @pods_from_external_sources << spec.root_name if dependency.external?
validate_platform(spec, target_definition)
spec.activate_platform(target_definition.platform)
spec.version.head = dependency.head?
# And recursively load the dependencies of the spec.
find_dependency_specs(spec, spec.dependencies, target_definition) if spec.dependencies
find_dependency_specs(spec, spec.dependencies, target_definition)
end
validate_platform(spec || @cached_specs[dependency.name], target_definition)
end
end
end
# Loads or returns a previously initialized {Set} for the given dependency.
#
# @param [Dependency] dependency
# the dependency for which the set is needed.
# TODO: check dependency.specification
#
# @param [Platform] platform
# the platform on which the dependency is needed this is used by
# the sandbox to locate external sources.
# TODO why?
#
# @note If the {#update_external_specs} flag is activated the
# dependencies with external sources are always resolved against
# the remote. Otherwise the specification is retrieved from the
# sandbox that fetches the external source only if needed.
#
# TODO If the set is loaded from a normal source and then from an
# external one that information is lost.
#
# @return [Set] the cached set for a given dependency.
#
def find_cached_set(dependency)
name = dependency.root_name
unless cached_sets[name]
if dependency.specification
set = Specification::Set::External.new(dependency.specification)
elsif dependency.external_source
set = set_from_external_source(dependency)
else
set = cached_sources.search(dependency)
end
cached_sets[name] = set
end
cached_sets[name]
end
# Returns a new set created from an external source
#
def set_from_external_source(dependency)
source = ExternalSources.from_dependency(dependency)
if update_external_specs
spec = source.specification_from_external(sandbox)
else
spec = source.specification(sandbox)
end
set = Specification::Set::External.new(spec)
set
end
# Ensures that a spec is compatible with the platform of a target.
#
# @raises If the spec is not supported by the target.
#
# @return [void]
#
def validate_platform(spec, target)
unless spec.available_platforms.any? { |platform| target.platform.supports?(platform) }
raise Informative, "[!] The platform of the target `#{target.name}' (#{target.platform}) is not compatible with `#{spec}' which has a minimun requirement of #{spec.available_platforms.join(' - ')}.".red
unless spec.available_platforms.any? { |p| target.platform.supports?(p) }
raise Informative, "The platform of the target `#{target.name}` " \
"(#{target.platform}) is not compatible with `#{spec}` which has " \
"a minimum requirement of #{spec.available_platforms.join(' - ')}."
end
end
end
......
require 'fileutils'
module Pod
# The sandbox provides support for the directory that CocoaPods uses for an
# installation. In this directory the Pods projects, the support files and
# the sources of the Pods are stored.
#
# CocoaPods assumes to have control of the sandbox.
#
class Sandbox
# The path of the build headers directory relative to the root.
#
BUILD_HEADERS_DIR = "BuildHeaders"
# The path of the public headers directory relative to the root.
#
PUBLIC_HEADERS_DIR = "Headers"
# @return [Pathname] the root of the sandbox.
#
attr_reader :root
# @return [HeadersDirectory] the header directory for the Pods libraries.
#
attr_reader :build_headers
attr_reader :public_headers
BUILD_HEADERS_DIR = "BuildHeaders"
PUBLIC_HEADERS_DIR = "Headers"
# @return [HeadersDirectory] the header directory for the user targets.
#
attr_reader :public_headers
def initialize(path)
@root = Pathname.new(path)
@build_headers = HeadersDirectory.new(self, BUILD_HEADERS_DIR)
# @param [String, Pathname] root @see root
#
# @todo the headers should be stored in a `Headers` folder.
#
def initialize(root)
@root = Pathname.new(root)
@build_headers = HeadersDirectory.new(self, BUILD_HEADERS_DIR)
@public_headers = HeadersDirectory.new(self, PUBLIC_HEADERS_DIR)
@cached_local_pods = {}
@cached_locally_sourced_pods = {}
@predownloaded_pods = []
FileUtils.mkdir_p(@root)
end
def implode
root.rmtree
end
# @return [Pathname] the path of the Pod project.
#
def project_path
root + "Pods.xcodeproj"
end
# @return [String] a string representation suitable for debugging.
#
def inspect
"#<#{self.class}> with root #{root}"
end
#--------------------------------------#
# @!group Life cycle
public
# Cleans the sandbox for a new installation.
#
# @return [void]
#
def prepare_for_install
build_headers.prepare_for_install
public_headers.prepare_for_install
end
def local_pod_for_spec(spec, platform)
key = [spec.top_level_parent.name, platform.to_sym]
(@cached_local_pods[key] ||= LocalPod.new(spec.top_level_parent, self, platform)).tap do |pod|
pod.add_specification(spec)
end
# Removes the sandbox.
#
# @return [void]
#
def implode
root.rmtree
end
# TODO: refactor the pods from a local source should not be chached by the sandbox
#--------------------------------------#
# @!group Local Pod support
public
# @todo refactor the pods from a local source should not be cached by the
# sandbox
#
# @return [LocalPod]
#
def locally_sourced_pod_for_spec(spec, platform)
key = [spec.top_level_parent.name, platform.to_sym]
(@cached_locally_sourced_pods[key] ||= LocalPod::LocalSourcedPod.new(spec.top_level_parent, self, platform)).tap do |pod|
key = [spec.root.name, platform.to_sym]
local_pod = @cached_locally_sourced_pods[key] ||= LocalPod::LocalSourcedPod.new(spec.root, self, platform)
local_pod.add_specification(spec)
local_pod
end
def local_pod_for_spec(spec, platform)
key = [spec.root.name, platform.to_sym]
(@cached_local_pods[key] ||= LocalPod.new(spec.root, self, platform)).tap do |pod|
pod.add_specification(spec)
end
end
# @return [LocalPod]
#
def installed_pod_named(name, platform)
if spec_path = podspec_for_name(name)
key = [name, platform.to_sym]
......@@ -54,23 +113,97 @@ module Pod
end
end
# Returns the path of the specification for the Pod with the
# given name.
#
# @param [String] name
# the name of the Pod for which the podspec file is requested.
#
# @return [Pathname] the path or nil.
#
def podspec_for_name(name)
path = root + "Local Podspecs/#{name}.podspec"
path.exist? ? path : nil
end
# Returns the specification for the Pod with the given name.
#
# @param [String] name
# the name of the Pod for which the specification is requested.
#
# @return [Specification] the specification if the file is found.
#
def specification(name)
if file = podspec_for_name(name)
Specification.from_file(file)
end
end
# @return [Array<String>] the names of the pods that have been
# pre-downloaded from an external source.
#
# @todo the installer needs to be aware of it.
#
attr_reader :predownloaded_pods
#--------------------------------------#
# @!group Private methods
private
attr_accessor :cached_local_pods
attr_accessor :cached_locally_sourced_pods
end
#---------------------------------------------------------------------------#
# Provides support for managing a header directory. It also keeps track of
# the header search paths.
#
class HeadersDirectory
def initialize(sandbox, base_dir)
@sandbox = sandbox
@base_dir = base_dir
@search_paths = [base_dir]
end
# @return [Pathname] the absolute path of this header directory.
#
def root
@sandbox.root + @base_dir
@sandbox.root + @relative_path
end
# @param [Sandbox] sandbox
# the sandbox that contains this header dir.
#
# @param [String] relative_path
# the relative path to the sandbox root and hence to the Pods
# project.
#
def initialize(sandbox, relative_path)
@sandbox = sandbox
@relative_path = relative_path
@search_paths = [relative_path]
end
#--------------------------------------#
# @!group Life cycle
public
# Removes the directory as it is regenerated from scratch during each
# installation.
#
def prepare_for_install
root.rmtree if root.exist?
end
#--------------------------------------#
# @!group Adding headers
public
#
#
def add_file(namespace_path, relative_header_path)
namespaced_header_path = root + namespace_path
namespaced_header_path.mkpath unless File.exist?(namespaced_header_path)
......@@ -80,26 +213,27 @@ module Pod
namespaced_header_path + relative_header_path.basename
end
#
#
def add_files(namespace_path, relative_header_paths)
relative_header_paths.map { |path| add_file(namespace_path, path) }
end
#
#
def search_paths
@search_paths.uniq.map { |path| "${PODS_ROOT}/#{path}" }
end
# Adds an header search path to the sandbox.
#
# @param path [Pathname] The path tho add.
# @param [Pathname] path
# the path tho add.
#
# @return [void]
#
def add_search_path(path)
@search_paths << Pathname.new(@base_dir) + path
end
def prepare_for_install
root.rmtree if root.exist?
@search_paths << Pathname.new(@relative_path) + path
end
end
end
module Pod
# The {Source} class is responsible to manage a collection of podspecs.
#
# @note The backing store of the podspecs collection is an implementation detail
# abstraced from the rest of CocoaPods.
#
# @note The default implementation uses a git repo as a backing store, where the
# podspecs are namespaces as:
#
# #{POD_NAME}/#{VERSION}/#{POD_NAME}.podspec
#
# @todo For better abstranction the sources should be responsible to update themselves.
#
class Source
class << self
# @return [Pathname] The location of the repo.
#
attr_reader :repo
# @param [Pathname] repo @see repo.
#
def initialize(repo)
@repo = repo
end
include Config::Mixin
# @return [String] the name of the repo.
#
def name
@repo.basename.to_s
end
# @!group Quering the source
# @return [Array<String>] The name of all the Pods.
#
def pods
@repo.children.map do |child|
child.basename.to_s if child.directory? && child.basename.to_s != '.git'
end.compact
end
# @return [Array<Sets>] The sets of all the Pods.
#
def pod_sets
pods.map { |pod| Specification::Set.new(pod, self) }
end
# @return [Array<Version>] All the available versions for the Pod, sorted
# from highest to lowest.
#
# @param [String] name The name of the Pod.
#
def versions(name)
pod_dir = repo + name
pod_dir.children.map do |v|
basename = v.basename.to_s
Version.new(basename) if v.directory? && basename[0,1] != '.'
end.compact.sort.reverse
end
# @return [Specification] The specification for a given version of Pod.
#
# @param [String] name The name of the Pod.
#
# @param [Version,String] version
# The version for the specification.
#
def specification(name, version)
specification_path = repo + name + version.to_s + "#{name}.podspec"
Specification.from_file(specification_path)
end
# @!group Searching the source
# @return [Set] A set for a given dependency. The set is identified by the
# name of the dependency and takes into account subspecs.
#
def search(dependency)
pod_sets.find do |set|
# First match the (top level) name, which does not yet load the spec from disk
set.name == dependency.top_level_spec_name &&
# Now either check if it's a dependency on the top level spec, or if it's not
# check if the requested subspec exists in the top level spec.
set.specification.subspec_by_name(dependency.name)
end
end
# @return [Array<Set>] The sets that contain the search term.
#
# @param [String] query The search term.
#
# @param [Bool] full_text_search Whether the search should be limited to
# the name of the Pod or should include
# also the author, the summary, and the
# description.
#
# @note Full text search requires to load the specification for each pod,
# hence is considerably slower.
#
def search_by_name(query, full_text_search = false)
pod_sets.map do |set|
text = if full_text_search
s = set.specification
"#{s.name} #{s.authors} #{s.summary} #{s.description}"
else
set.name
end
set if text.downcase.include?(query.downcase)
end.compact
end
# The {Source::Aggregate} manages all the sources available to CocoaPods.
#
class Aggregate
# @return [Array<Source>] All the sources.
# @return [Array<Source>] the list of all the sources known to this
# installation of CocoaPods.
#
def all
@sources ||= dirs.map { |repo| Source.new(repo) }.sort_by(&:name)
end
# @return [Array<String>] The names of all the pods available.
#
def all_pods
all.map(&:pods).flatten.uniq
Aggregate.new(config.repos_dir).all
end
# @return [Array<Set>] The sets for all the pods available.
#
# @note Implementation detail: The sources don't cache their values
# because they might change in response to an update. Therefore
# this method to prevent slowness caches the values before
# processing them.
# @return [Array<Specification::Set>] the list of all the specification
# sets know to this installation of CocoaPods.
#
def all_sets
pods_by_source = {}
all.each do |source|
pods_by_source[source] = source.pods
end
sources = pods_by_source.keys
pods = pods_by_source.values.flatten.uniq
pods.map do |pod|
pod_sources = sources.select{ |s| pods_by_source[s].include?(pod) }.compact
Specification::Set.new(pod, pod_sources)
end
Aggregate.new(config.repos_dir).all_sets
end
# @return [Set] A set for a given dependency including all the Sources
# that countain the Pod.
# Search all the sources to match the set for the given dependency.
#
# @return [Set, nil] a set for a given dependency including all the
# {Source} that contain the Pod. If no sources containing the
# Pod where found it returns nil.
#
# @raises If no source including the set can be foud.
# @todo Move exceptions to clients?
#
# @see Source#search
# @raise If no source including the set can be found.
#
def search(dependency)
sources = all.select { |s| !s.search(dependency).nil? }
raise(Informative, "[!] Unable to find a pod named `#{dependency.name}'".red) if sources.empty?
Specification::Set.new(dependency.top_level_spec_name, sources)
set = Aggregate.new(config.repos_dir).search(dependency)
raise Informative, "Unable to find a pod named `#{dependency.name}`" unless set
set
end
# @return [Array<Set>] The sets that contain the search term.
# Search all the sources with the given search term.
#
# @raises If no source including the set can be foud.
# @param [String] query
# The search term.
#
# @see Source#search_by_name
# @param [Bool] full_text_search
# Whether the search should be limited to the name of the Pod or
# should include also the author, the summary, and the
# description.
#
def search_by_name(query, full_text_search = false)
pods_by_source = {}
result = []
all.each { |s| pods_by_source[s] = s.search_by_name(query, full_text_search).map(&:name) }
pod_names = pods_by_source.values.flatten.uniq
pod_names.each do |pod|
sources = []
pods_by_source.each{ |source, pods| sources << source if pods.include?(pod) }
result << Specification::Set.new(pod, sources)
end
# @raises If no source including the set can be found.
#
# @note Full text search requires to load the specification for each
# pod, hence is considerably slower.
#
# @todo Move exceptions to clients?
#
# @return [Array<Set>] The sets that contain the search term.
#
def search_by_name(name, full_text_search = false)
result = Aggregate.new(config.repos_dir).search_by_name(name, full_text_search)
if result.empty?
extra = ", author, summary, or description" if full_text_search
raise(Informative, "Unable to find a pod with name" \
"#{extra} matching `#{query}'")
raise Informative "Unable to find a pod with name#{extra} matching `#{query}'"
end
result
end
# @return [Array<Pathname>] The directories where the sources are stored.
#
# @raises If the repos dir doesn't exits.
#
def dirs
if ENV['CP_MASTER_REPO_DIR']
[Pathname.new(ENV['CP_MASTER_REPO_DIR'])]
else
repos_dir = Config.instance.repos_dir
unless repos_dir.exist?
raise Informative, "No spec repos found in `#{repos_dir}'. " \
"To fetch the `master' repo run: $ pod setup"
end
repos_dir.children.select(&:directory?)
end
end
end
# @!group Shortcuts
def self.all
Aggregate.new.all
end
def self.all_sets
Aggregate.new.all_sets
end
def self.search(dependency)
Aggregate.new.search(dependency)
end
def self.search_by_name(name, full_text_search = false)
Aggregate.new.search_by_name(name, full_text_search)
end
end
end
require 'xcodeproj/config'
require 'active_support/core_ext/string/strip.rb'
module Pod
extend Config::Mixin
def self._eval_podspec(path)
string = File.open(path, 'r:utf-8') { |f| f.read }
# TODO: work around for Rubinius incomplete encoding in 1.9 mode
string.encode!('UTF-8') if string.respond_to?(:encoding) && string.encoding.name != "UTF-8"
eval(string, nil, path.to_s)
end
class Specification
autoload :Set, 'cocoapods/specification/set'
autoload :Statistics, 'cocoapods/specification/statistics'
### Initalization
# The file is expected to define and return a Pods::Specification.
# If name is equals to nil it returns the top level Specification,
# otherwise it returned the specification with the name that matches
def self.from_file(path, subspec_name = nil)
unless path.exist?
raise Informative, "No podspec exists at path `#{path}'."
end
spec = ::Pod._eval_podspec(path)
spec.defined_in_file = path
spec.subspec_by_name(subspec_name)
end
def initialize(parent = nil, name = nil)
@parent, @name = parent, name
@define_for_platforms = [:osx, :ios]
@clean_paths, @subspecs = [], []
@deployment_target = {}
unless parent
@source = {:git => ''}
end
# multi-platform attributes
%w[ source_files
exclude_patterns
public_header_files
resources
preserve_paths
exclude_header_search_paths
frameworks
weak_frameworks
libraries
dependencies
compiler_flags ].each do |attr|
instance_variable_set( "@#{attr}", { :ios => [], :osx => [] } )
end
@xcconfig = { :ios => Xcodeproj::Config.new, :osx => Xcodeproj::Config.new }
@header_dir = { :ios => nil, :osx => nil }
@requires_arc = { :ios => nil, :osx => nil }
@header_mappings_dir = { :ios => nil, :osx => nil }
yield self if block_given?
end
### Meta programming
# Creates a top level attribute reader. A lambda can
# be passed to process the ivar before returning it
def self.top_attr_reader(attr, read_lambda = nil)
define_method(attr) do
ivar = instance_variable_get("@#{attr}")
@parent ? top_level_parent.send(attr) : ( read_lambda ? read_lambda.call(self, ivar) : ivar )
end
end
# Creates a top level attribute writer. A lambda can
# be passed to initalize the value
def self.top_attr_writer(attr, init_lambda = nil)
define_method("#{attr}=") do |value|
raise Informative, "#{self.inspect} Can't set `#{attr}' for subspecs." if @parent
instance_variable_set("@#{attr}", init_lambda ? init_lambda.call(value) : value);
end
end
# Creates a top level attribute accessor. A lambda can
# be passed to initialize the value in the attribute writer.
def self.top_attr_accessor(attr, writer_labmda = nil)
top_attr_reader attr
top_attr_writer attr, writer_labmda
end
# Returns the value of the attribute for the active platform
# chained with the upstream specifications. The ivar must store
# the platform specific values as an array.
#
def self.pltf_chained_attr_reader(attr)
define_method(attr) do
active_plaform_check
ivar_value = instance_variable_get("@#{attr}")[active_platform]
@parent ? @parent.send(attr) + ivar_value : ( ivar_value )
end
end
# Returns the first value defined of the attribute traversing the chain
# upwards.
#
def self.pltf_first_defined_attr_reader(attr)
define_method(attr) do
active_plaform_check
ivar_value = instance_variable_get("@#{attr}")[active_platform]
ivar_value.nil? ? (@parent.send(attr) if @parent) : ivar_value
end
end
def active_plaform_check
raise Informative, "#{self.inspect} not activated for a platform before consumption." unless active_platform
end
# Attribute writer that works in conjuction with the PlatformProxy.
def self.platform_attr_writer(attr, block = nil)
define_method("#{attr}=") do |value|
current = instance_variable_get("@#{attr}")
@define_for_platforms.each do |platform|
block ? current[platform] = block.call(value, current[platform]) : current[platform] = value
end
end
end
def self.pltf_chained_attr_accessor(attr, block = nil)
pltf_chained_attr_reader(attr)
platform_attr_writer(attr, block)
end
# The PlatformProxy works in conjuction with Specification#_on_platform.
# It allows a syntax like `spec.ios.source_files = file`
class PlatformProxy
def initialize(specification, platform)
@specification, @platform = specification, platform
end
%w{ source_files=
exclude_patterns=
public_header_files=
resource=
resources=
preserve_paths=
preserve_path=
xcconfig=
framework=
frameworks=
weak_framework=
weak_frameworks=
library=
libraries=
compiler_flags=
deployment_target=
header_dir=
requires_arc
dependency }.each do |method|
define_method(method) do |args|
@specification._on_platform(@platform) do
@specification.send(method, args)
end
end
end
end
def ios
PlatformProxy.new(self, :ios)
end
def osx
PlatformProxy.new(self, :osx)
end
### Deprecated attributes - TODO: remove once master repo and fixtures have been updated
attr_writer :part_of_dependency
attr_writer :part_of
top_attr_accessor :clean_paths, lambda { |patterns| pattern_list(patterns) }
alias_method :clean_path=, :clean_paths=
### Regular attributes
attr_accessor :parent
attr_accessor :preferred_dependency
def name
@parent ? "#{@parent.name}/#{@name}" : @name
end
attr_writer :name
# @return [String] The name of the pod.
#
def pod_name
top_level_parent.name
end
### Attributes that return the first value defined in the chain
def platform
@platform || ( @parent ? @parent.platform : nil )
end
def platform=(platform)
@platform = Platform.new(*platform)
end
# If not platform is specified all the platforms are returned.
def available_platforms
platform.nil? ? @define_for_platforms.map { |platform| Platform.new(platform, deployment_target(platform)) } : [ platform ]
end
### Top level attributes. These attributes represent the unique features of pod and can't be specified by subspecs.
top_attr_accessor :defined_in_file
top_attr_accessor :source
top_attr_accessor :homepage
top_attr_accessor :summary
top_attr_accessor :documentation
top_attr_accessor :version, lambda { |v| Version.new(v) }
top_attr_reader :description, lambda { |instance, ivar| ivar || instance.summary }
top_attr_writer :description, lambda { |d| d.strip_heredoc }
# @!method license
#
# @abstract
# The license of the pod.
#
# @example
# s.license = 'MIT'
# s.license = { :type => 'MIT', :file => 'license.txt', :text => 'Permission is granted to...' }
#
top_attr_accessor :license, lambda { |license|
license = ( license.kind_of? String ) ? { :type => license } : license
license[:text] = license[:text].strip_heredoc if license[:text]
license
}
# @!method authors
#
# @abstract
# The list of the authors (with email) of the pod.
#
top_attr_accessor :authors, lambda { |*names_and_email_addresses|
list = names_and_email_addresses.flatten
unless list.first.is_a?(Hash)
authors = list.last.is_a?(Hash) ? list.pop : {}
list.each { |name| authors[name] = nil }
end
authors || list.first
}
alias_method :author=, :authors=
### Attributes **with** multiple platform support
# @todo allow for subspecs?
#
top_attr_accessor :prefix_header_file, lambda { |file| Pathname.new(file) }
top_attr_accessor :prefix_header_contents
pltf_chained_attr_accessor :source_files, lambda {|value, current| pattern_list(value) }
pltf_chained_attr_accessor :exclude_patterns, lambda {|value, current| pattern_list(value) }
pltf_chained_attr_accessor :public_header_files, lambda {|value, current| pattern_list(value) }
pltf_chained_attr_accessor :resources, lambda {|value, current| pattern_list(value) }
pltf_chained_attr_accessor :preserve_paths, lambda {|value, current| pattern_list(value) } # Paths that should not be cleaned
pltf_chained_attr_accessor :exclude_header_search_paths, lambda {|value, current| pattern_list(value) } # Headers to be excluded from being added to search paths (RestKit)
pltf_chained_attr_accessor :frameworks, lambda {|value, current| (current << value).flatten }
pltf_chained_attr_accessor :weak_frameworks, lambda {|value, current| (current << value).flatten }
pltf_chained_attr_accessor :libraries, lambda {|value, current| (current << value).flatten }
alias_method :resource=, :resources=
alias_method :preserve_path=, :preserve_paths=
alias_method :framework=, :frameworks=
alias_method :weak_framework=, :weak_frameworks=
alias_method :library=, :libraries=
# @!method requires_arc=
#
# @abstract Wether the `-fobjc-arc' flag should be added to the compiler
# flags.
#
# @param [Bool] Wether the source files require ARC.
#
platform_attr_writer :requires_arc
pltf_first_defined_attr_reader :requires_arc
# @!method header_dir=
#
# @abstract The directory where to name space the headers files of
# the specification.
#
# @param [String] The headers directory.
#
platform_attr_writer :header_dir, lambda { |dir, _| Pathname.new(dir) }
pltf_first_defined_attr_reader :header_dir
# If not provided the headers files are flattened
#
platform_attr_writer :header_mappings_dir, lambda { |file, _| Pathname.new(file) }
pltf_first_defined_attr_reader :header_mappings_dir
# @!method xcconfig=
#
platform_attr_writer :xcconfig, lambda {|value, current| current.tap { |c| c.merge!(value) } }
def xcconfig
result = raw_xconfig.dup
result.libraries.merge(libraries)
result.frameworks.merge(frameworks)
result.weak_frameworks.merge(weak_frameworks)
result
end
def raw_xconfig
@parent ? @parent.raw_xconfig.merge(@xcconfig[active_platform]) : @xcconfig[active_platform]
end
def recursive_compiler_flags
@parent ? @parent.recursive_compiler_flags | @compiler_flags[active_platform] : @compiler_flags[active_platform]
end
def compiler_flags
flags = recursive_compiler_flags.dup
flags << '-fobjc-arc' if requires_arc
flags.join(' ')
end
platform_attr_writer :compiler_flags, lambda {|value, current| current << value }
def dependency(*name_and_version_requirements)
name, *version_requirements = name_and_version_requirements.flatten
raise Informative, "A specification can't require self as a subspec" if name == self.name
raise Informative, "A subspec can't require one of its parents specifications" if @parent && @parent.name.include?(name)
dep = Dependency.new(name, *version_requirements)
@define_for_platforms.each do |platform|
@dependencies[platform] << dep
end
dep
end
# External dependencies are inherited by subspecs
def external_dependencies(all_platforms = false)
active_plaform_check unless all_platforms
result = all_platforms ? @dependencies.values.flatten : @dependencies[active_platform]
result += parent.external_dependencies if parent
result
end
# A specification inherits the preferred_dependency or
# all the compatible subspecs as dependencies
def subspec_dependencies
active_plaform_check
specs = preferred_dependency ? [subspec_by_name("#{name}/#{preferred_dependency}")] : subspecs
specs.compact \
.select { |s| s.supports_platform?(active_platform) } \
.map { |s| Dependency.new(s.name, version) }
end
def dependencies
external_dependencies + subspec_dependencies
end
include Config::Mixin
def top_level_parent
@parent ? @parent.top_level_parent : self
end
def subspec?
!@parent.nil?
end
def subspec(name, &block)
subspec = Specification.new(self, name, &block)
@subspecs << subspec
subspec
end
attr_reader :subspecs
def recursive_subspecs
@recursive_subspecs ||= begin
mapper = lambda do |spec|
spec.subspecs.map do |subspec|
[subspec, *mapper.call(subspec)]
end.flatten
end
mapper.call(self)
end
end
def subspec_by_name(name)
return self if name.nil? || name == self.name
# Remove this spec's name from the beginning of the name we’re looking for
# and take the first component from the remainder, which is the spec we need
# to find now.
remainder = name[self.name.size+1..-1]
raise Informative, "Unable to find a specification named `#{name}' in `#{pod_name}'." unless remainder
subspec_name = remainder.split('/').shift
subspec = subspecs.find { |s| s.name == "#{self.name}/#{subspec_name}" }
raise Informative, "Unable to find a specification named `#{name}' in `#{pod_name}'." unless subspec
# If this was the last component in the name, then return the subspec,
# otherwise we recursively keep calling subspec_by_name until we reach the
# last one and return that
remainder.empty? ? subspec : subspec.subspec_by_name(name)
end
def local?
!source.nil? && !source[:local].nil?
end
def pod_destroot
config.project_pods_root + top_level_parent.name
end
def self.pattern_list(patterns)
if patterns.is_a?(Array) && (!defined?(Rake) || !patterns.is_a?(Rake::FileList))
patterns
else
[patterns]
end
end
# This method takes a header path and returns the location it should have
# in the pod's header dir.
#
# By default all headers are copied to the pod's header dir without any
# namespacing. However if the top level attribute accessor header_mappings_dir
# is specified the namespacing will be preserved from that directory.
def copy_header_mapping(from)
header_mappings_dir ? from.relative_path_from(header_mappings_dir) : from.basename
end
# This is a convenience method which gets called after all pods have been
# downloaded but before they have been installed, and the Xcode project and
# related files have been generated. (It receives the Pod::LocalPod
# instance generated form the specification and the #
# Pod::Podfile::TargetDefinition instance for the current target.) Override
# this to, for instance, to run any build script:
#
# Pod::Spec.new do |s|
# def s.pre_install(pod, target_definition)
# Dir.chdir(pod.root){ `sh make.sh` }
# end
# end
def pre_install(pod, target_definition)
FALSE
end
# This is a convenience method which gets called after all pods have been
# downloaded, installed, and the Xcode project and related files have been
# generated. (It receives the Pod::Installer::TargetInstaller instance for
# the current target.) Override this to, for instance, add to the prefix
# header:
#
# Pod::Spec.new do |s|
# def s.post_install(target_installer)
# prefix_header = config.project_pods_root + target_installer.prefix_header_filename
# prefix_header.open('a') do |file|
# file.puts(%{#ifdef __OBJC__\n#import "SSToolkitDefines.h"\n#endif})
# end
# end
# end
def post_install(target_installer)
FALSE
end
def podfile?
false
end
# This is used by the specification set
def dependency_by_top_level_spec_name(name)
external_dependencies(true).each do |dep|
return dep if dep.top_level_spec_name == name
end
end
def to_s
"#{name} (#{version})"
end
def inspect
"#<#{self.class.name} for #{to_s}>"
end
def ==(other)
object_id == other.object_id ||
(self.class === other &&
name && name == other.name &&
version && version == other.version)
end
# Returns whether the specification is supported in a given platform
def supports_platform?(*platform)
platform = platform[0].is_a?(Platform) ? platform[0] : Platform.new(*platform)
available_platforms.any? { |p| platform.supports?(p) }
end
# Defines the active platform for comsumption of the specification and
# returns self for method chainability.
# The active platform must the the same accross the chain so attributes
# that are inherited can be correctly resolved.
def activate_platform(*platform)
platform = platform[0].is_a?(Platform) ? platform[0] : Platform.new(*platform)
raise Informative, "#{to_s} is not compatible with #{platform}." unless supports_platform?(platform)
top_level_parent.active_platform = platform.to_sym
self
end
top_attr_accessor :active_platform
### Not attributes
# @visibility private
#
# This is used by PlatformProxy to assign attributes for the scoped platform.
def _on_platform(platform)
before, @define_for_platforms = @define_for_platforms, [platform]
yield
ensure
@define_for_platforms = before
end
# @visibility private
#
# This is multi-platform and to support
# subspecs with different platforms is is resolved as the
# first non nil value accross the chain.
def deployment_target=(version)
raise Informative, "The deployment target must be defined per platform like `s.ios.deployment_target = '5.0'`." unless @define_for_platforms.count == 1
@deployment_target[@define_for_platforms.first] = version
end
def deployment_target(platform)
@deployment_target[platform] || ( @parent ? @parent.deployment_target(platform) : nil )
end
end
Spec = Specification
end
require 'active_support/core_ext/array/conversions'
module Pod
class Specification
# A Specification::Set is resposible of handling all the specifications of
# a Pod. This class stores the information of the dependencies that reuired
# a Pod in the resolution process.
#
# @note The alpahbetical order of the sets is used to select a specification
# if multiple are available for a given version.
#
# @note The set class is not and should be not aware of the backing store
# of a Source.
#
class Set
# @return [String] The name of the Pod.
#
attr_reader :name
# @return [Array<Source>] The sources that contain the specifications for
# the available versions of a Pod.
#
attr_reader :sources
# @param [String] name The name of the Pod.
#
# @param [Array<Source>,Source] sources
# The sources that contain a Pod.
#
def initialize(name, sources)
@name = name
sources = sources.is_a?(Array) ? sources : [sources]
@sources = sources.sort_by(&:name)
@required_by = []
@dependencies = []
end
# @return [void] Stores a dependency on the Pod.
#
# @param [Dependency] dependency A dependency that requires the Pod.
#
# @param [String] dependent_name The name of the owner of the
# dependency. It is used only to display
# the Pod::Informative.
#
# @raises If the versions requirement of the dependency are not
# compatible with the previously stored dependencies.
#
def required_by(dependency, dependent_name)
unless @required_by.empty? || dependency.requirement.satisfied_by?(Gem::Version.new(required_version.to_s))
raise Informative, "#{dependent_name} tries to activate `#{dependency}', but already activated version `#{required_version}' by #{@required_by.to_sentence}."
end
@specification = nil
@required_by << dependent_name
@dependencies << dependency
end
# @return [Dependency] A dependency including all the versions
# requirements of the stored dependencies.
#
def dependency
@dependencies.inject(Dependency.new(name)) do |previous, dependency|
previous.merge(dependency.to_top_level_spec_dependency)
end
end
# @return [Specification] The specification for the given subspec name,
# from {specification}.
#
# @param [String] name The name of the specification. It can be the
# name of the top level parent or the name of a
# subspec.
#
# @see specification
#
def specification_by_name(name)
specification.top_level_parent.subspec_by_name(name)
end
# @return [Specification] The top level specification of the Pod for the
# {required_version}.
#
# @note If multiple sources have a specification for the
# {required_version} The alpahbetical order of their names is used
# to disambiguate.
#
def specification
unless @specification
sources = []
versions_by_source.each{ |source, versions| sources << source if versions.include?(required_version) }
source = sources.sort_by(&:name).first
@specification = source.specification(name, required_version)
end
@specification
end
# @return [Version] The highest version that satisfies {dependency}.
#
def required_version
versions.find { |v| dependency.match?(name, v) } ||
raise(Informative, "Required version (#{dependency}) not found for `#{name}'.\nAvailable versions: #{versions.join(', ')}")
end
# @return [Array<Version>] All the available versions for the Pod, sorted
# from highest to lowest.
#
def versions
versions_by_source.values.flatten.uniq.sort.reverse
end
# @return [Hash{Source => Version}] All the available versions for the
# Pod groupped by source.
#
def versions_by_source
result = {}
sources.each do |source|
result[source] = source.versions(name)
end
result
end
def ==(other)
self.class === other && @name == other.name && @sources.map(&:name) == other.sources.map(&:name)
end
def to_s
"#<#{self.class.name} for `#{name}' with required version `#{required_version}' available at `#{sources.map(&:name) * ', '}'>"
end
alias_method :inspect, :to_s
# The Set::External class handles Pods from external sources. Pods from
# external sources don't use the {Pod::Sources} and are intialized by a
# given specification.
#
# @note External sources *don't* support subspecs.
#
class External < Set
def initialize(specification)
@specification = specification
@required_by = []
@dependencies = []
end
def name
@specification.top_level_parent.name
end
def ==(other)
self.class === other && @specification == other.specification
end
def required_by(dependency, dependent_name)
before = @specification
super(dependency, dependent_name)
ensure
@specification = before
end
def specification_path
raise "specification_path"
end
def specification
@specification
end
def versions
[@specification.version]
end
end
end
end
end
require 'yaml'
# This is to make sure Faraday doesn't warn the user about the `system_timer` gem missing.
old_warn, $-w = $-w, nil
begin
require 'faraday'
ensure
$-w = old_warn
end
require 'octokit'
module Pod
class Specification
class Statistics
def self.instance
@instance ||= new
end
def self.instance=(instance)
@instance = instance
end
attr_accessor :cache_file, :cache_expiration
def initialize
@cache_file = Config.instance.repos_dir + 'statistics.yml'
@cache_expiration = 60 * 60 * 24 * 3
end
def creation_date(set)
compute_creation_date(set)
end
def creation_dates(sets)
dates = {}
sets.each { |set| dates[set.name] = compute_creation_date(set, false) }
save_cache
dates
end
def github_watchers(set)
github_stats_if_needed(set)
get_value(set, :gh_watchers)
end
def github_forks(set)
github_stats_if_needed(set)
get_value(set, :gh_forks)
end
def github_pushed_at(set)
github_stats_if_needed(set)
get_value(set, :pushed_at)
end
private
def cache
@cache ||= cache_file && cache_file.exist? ? YAML::load(cache_file.read) : {}
end
def get_value(set, key)
if cache[set.name] && cache[set.name][key]
cache[set.name][key]
end
end
def set_value(set, key, value)
cache[set.name] ||= {}
cache[set.name][key] = value
end
def save_cache
File.open(cache_file, 'w') { |f| f.write(YAML::dump(cache)) } if cache_file
end
def compute_creation_date(set, save = true)
date = get_value(set, :creation_date)
unless date
Dir.chdir(set.sources.first.repo) do
date = Time.at(`git log --first-parent --format=%ct #{set.name}`.split("\n").last.to_i)
end
set_value(set, :creation_date, date)
end
save_cache if save
date
end
def github_stats_if_needed(set)
return if get_value(set, :gh_date) && get_value(set, :gh_date) > Time.now - cache_expiration
spec = set.specification
url = spec.source[:git] || ''
repo_id = url[/github.com\/([^\/\.]*\/[^\/\.]*)\.*/, 1]
return unless repo_id
begin
repo = Octokit.repo(repo_id)
rescue
return
end
cache[set.name] ||= {}
set_value(set, :gh_watchers, repo['watchers'])
set_value(set, :gh_forks, repo['forks'])
set_value(set, :pushed_at, repo['pushed_at'])
set_value(set, :gh_date, Time.now)
save_cache
end
end
end
end
module Pod
require 'colored'
# Provides support for UI output. It provides support for nexted sections of
# information and for a verbose mode.
#
module UserInterface
autoload :UIPod, 'cocoapods/user_interface/ui_pod'
require 'colored'
@title_colors = %w|yellow green|
@title_level = 0
......@@ -10,6 +13,7 @@ module Pod
@treat_titles_as_messages = false
class << self
include Config::Mixin
attr_accessor :indentation_level, :title_level
......@@ -22,8 +26,8 @@ module Pod
# to their level. In normal mode titles are printed only if
# they have nesting level smaller than 2.
#
# TODO: refactor to title (for always visible titles like search)
# and sections (titles that reppresent collapsible sections).
# TODO: Refactor to title (for always visible titles like search)
# and sections (titles that represent collapsible sections).
#
def section(title, verbose_prefix = '', relative_indentation = 0)
if config.verbose?
......@@ -110,11 +114,11 @@ module Pod
# that the user should take.
#
# @param [String] message The message to print.
# @param [Actions] actions The actions that the user should take.
# @param [Array] actions The actions that the user should take.
#
# return [void]
#
def warn(message, actions)
def warn(message, actions = [])
puts("\n[!] #{message}".yellow)
actions.each do |action|
indented = wrap_string(action, " - ")
......@@ -140,7 +144,7 @@ module Pod
if mode == :name
puts_indented set.name
else
pod = UIPod.new(set)
pod = Specification::Set::Presenter.new(set)
title("\n-> #{pod.name} (#{pod.version})".green, '', 1) do
puts_indented pod.summary
labeled('Homepage', pod.homepage)
......
require 'active_support/core_ext/array/conversions'
module Pod
module UserInterface
class UIPod
attr_accessor :set
def initialize(set)
@set = set
end
# set information
def name
@set.name
end
def version
@set.versions.first
end
def versions
@set.versions.sort.reverse
end
def verions_by_source
result = []
@set.versions_by_source.each do |source, versions|
result << "#{versions.map(&:to_s) * ', '} [#{source.name} repo]"
end
result * ' - '
end
# @return [Array<String>]
#
def sources
@set.sources.map(&:name).sort
end
# specification information
def spec
@set.specification
end
def authors
spec.authors ? spec.authors.keys.to_sentence : ''
end
def homepage
spec.homepage
end
def description
spec.description
end
def summary
spec.summary
end
def source_url
spec.source.reject {|k,_| k == :commit || k == :tag }.values.first
end
def platform
spec.available_platforms.sort { |a,b| a.to_s.downcase <=> b.to_s.downcase }.join(' - ')
end
def license
spec.license[:type] if spec.license
end
# will return array of all subspecs (recursevly) or nil
def subspecs
(spec.recursive_subspecs.any? && spec.recursive_subspecs) || nil
end
# Statistics information
def creation_date
Pod::Specification::Statistics.instance.creation_date(@set)
end
def github_watchers
Pod::Specification::Statistics.instance.github_watchers(@set)
end
def github_forks
Pod::Specification::Statistics.instance.github_forks(@set)
end
def github_last_activity
distance_from_now_in_words(Pod::Specification::Statistics.instance.github_pushed_at(@set))
end
def ==(other)
self.class === other && @set == other.set
end
def eql?(other)
self.class === other && name.eql?(other.name)
end
def hash
name.hash
end
private
def distance_from_now_in_words(from_time)
return nil unless from_time
from_time = Time.parse(from_time)
to_time = Time.now
distance_in_days = (((to_time - from_time).abs)/60/60/24).round
case distance_in_days
when 0..7
"less than a week ago"
when 8..29
"#{distance_in_days} days ago"
when 30..45
"1 month ago"
when 46..365
"#{(distance_in_days.to_f / 30).round} months ago"
else
"more than a year ago"
end
end
end
end
end
module Pod
VERSION = '0.16.0.rc2'
VERSION = '0.17.0.alpha'
end
module Pod
class Version < Gem::Version
# @returns A Version described by its #to_s method.
#
# @TODO The `from' part of the regexp should be remove before 1.0.0.
#
def self.from_string(string)
if string =~ /HEAD (based on|from) (.*)/
v = Version.new($2)
v.head = true
v
else
Version.new(string)
end
end
attr_accessor :head
alias_method :head?, :head
def to_s
head? ? "HEAD based on #{super}" : super
end
end
end
doc @ e3d65416
Subproject commit e3d65416e8678c4621c0fa22d90355c805f0b6a2
require 'pathname'
ROOT = Pathname.new(File.expand_path('../../', __FILE__))
require 'active_support'
namespace :doc do
task :load do
unless (ROOT + 'rakelib/doc').exist?
Dir.chdir(ROOT + 'rakelib') do
sh "git clone git@github.com:CocoaPods/cocoapods.github.com.git doc"
end
end
require ROOT + 'rakelib/doc/lib/doc'
end
desc 'Update vendor doc repo'
task :update do
Dir.chdir(ROOT + 'rakelib/doc') do
sh "git checkout **/*.html"
sh "git pull"
end
end
desc 'Generate docs and push to remote'
task :release => [:update, :generate] do
Dir.chdir(ROOT + 'rakelib/doc') do
sh "git add **/*.html"
sh "git commit -m 'Update documentation [CocoaPods]'"
sh "git push"
end
end
task :generate => :load do
generator = Pod::Doc::Gem.new(ROOT + 'cocoapods.gemspec', 'Pod')
generator.render
sh "open '#{generator.output_file}'"
end
end
desc "Genereates the documentation"
task :doc => 'doc:generate'
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
51E94E13164472080035348C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E94E12164472080035348C /* Foundation.framework */; };
51E94E18164472090035348C /* Sample_Lib.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 51E94E17164472090035348C /* Sample_Lib.h */; };
51E94E1A164472090035348C /* Sample_Lib.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E94E19164472090035348C /* Sample_Lib.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
51E94E0D164472080035348C /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/${PRODUCT_NAME}";
dstSubfolderSpec = 16;
files = (
51E94E18164472090035348C /* Sample_Lib.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
51E94E0F164472080035348C /* libSample Lib.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libSample Lib.a"; sourceTree = BUILT_PRODUCTS_DIR; };
51E94E12164472080035348C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
51E94E16164472090035348C /* Sample Lib-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Sample Lib-Prefix.pch"; sourceTree = "<group>"; };
51E94E17164472090035348C /* Sample_Lib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Sample_Lib.h; sourceTree = "<group>"; };
51E94E19164472090035348C /* Sample_Lib.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Sample_Lib.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
51E94E0C164472080035348C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
51E94E13164472080035348C /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
51E94E04164472080035348C = {
isa = PBXGroup;
children = (
51E94E14164472090035348C /* Sample Lib */,
51E94E11164472080035348C /* Frameworks */,
51E94E10164472080035348C /* Products */,
);
sourceTree = "<group>";
};
51E94E10164472080035348C /* Products */ = {
isa = PBXGroup;
children = (
51E94E0F164472080035348C /* libSample Lib.a */,
);
name = Products;
sourceTree = "<group>";
};
51E94E11164472080035348C /* Frameworks */ = {
isa = PBXGroup;
children = (
51E94E12164472080035348C /* Foundation.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
51E94E14164472090035348C /* Sample Lib */ = {
isa = PBXGroup;
children = (
51E94E17164472090035348C /* Sample_Lib.h */,
51E94E19164472090035348C /* Sample_Lib.m */,
51E94E15164472090035348C /* Supporting Files */,
);
path = "Sample Lib";
sourceTree = "<group>";
};
51E94E15164472090035348C /* Supporting Files */ = {
isa = PBXGroup;
children = (
51E94E16164472090035348C /* Sample Lib-Prefix.pch */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
51E94E0E164472080035348C /* Sample Lib */ = {
isa = PBXNativeTarget;
buildConfigurationList = 51E94E1D164472090035348C /* Build configuration list for PBXNativeTarget "Sample Lib" */;
buildPhases = (
51E94E0B164472080035348C /* Sources */,
51E94E0C164472080035348C /* Frameworks */,
51E94E0D164472080035348C /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "Sample Lib";
productName = "Sample Lib";
productReference = 51E94E0F164472080035348C /* libSample Lib.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
51E94E06164472080035348C /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0450;
ORGANIZATIONNAME = CocoaPods;
};
buildConfigurationList = 51E94E09164472080035348C /* Build configuration list for PBXProject "Sample Lib" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 51E94E04164472080035348C;
productRefGroup = 51E94E10164472080035348C /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
51E94E0E164472080035348C /* Sample Lib */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
51E94E0B164472080035348C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
51E94E1A164472090035348C /* Sample_Lib.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
51E94E1B164472090035348C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
SDKROOT = iphoneos;
};
name = Debug;
};
51E94E1C164472090035348C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
51E94E1E164472090035348C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
DSTROOT = /tmp/Sample_Lib.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Sample Lib/Sample Lib-Prefix.pch";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
51E94E1F164472090035348C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
DSTROOT = /tmp/Sample_Lib.dst;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Sample Lib/Sample Lib-Prefix.pch";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
51E94E09164472080035348C /* Build configuration list for PBXProject "Sample Lib" */ = {
isa = XCConfigurationList;
buildConfigurations = (
51E94E1B164472090035348C /* Debug */,
51E94E1C164472090035348C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
51E94E1D164472090035348C /* Build configuration list for PBXNativeTarget "Sample Lib" */ = {
isa = XCConfigurationList;
buildConfigurations = (
51E94E1E164472090035348C /* Debug */,
51E94E1F164472090035348C /* Release */,
);
defaultConfigurationIsVisible = 0;
};
/* End XCConfigurationList section */
};
rootObject = 51E94E06164472080035348C /* Project object */;
}
//
// Prefix header for all source files of the 'Sample Lib' target in the 'Sample Lib' project
//
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif
//
// Sample_Lib.h
// Sample Lib
//
// Created by Eloy Durán on 11/2/12.
// Copyright (c) 2012 CocoaPods. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Sample_Lib : NSObject
@end
//
// Sample_Lib.m
// Sample Lib
//
// Created by Eloy Durán on 11/2/12.
// Copyright (c) 2012 CocoaPods. All rights reserved.
//
#import "Sample_Lib.h"
@implementation Sample_Lib
@end
......@@ -13,6 +13,7 @@
51075D541521D0C100E39B41 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 51075D521521D0C100E39B41 /* InfoPlist.strings */; };
51075D561521D0C100E39B41 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 51075D551521D0C100E39B41 /* main.m */; };
51075D5A1521D0C100E39B41 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 51075D591521D0C100E39B41 /* AppDelegate.m */; };
51E94E2A1644722C0035348C /* libSample Lib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 51E94E28164472200035348C /* libSample Lib.a */; };
A346497214F9BE9A0080D870 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A346497114F9BE9A0080D870 /* UIKit.framework */; };
A346497414F9BE9A0080D870 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A346497314F9BE9A0080D870 /* Foundation.framework */; };
A346497614F9BE9A0080D870 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A346497514F9BE9A0080D870 /* CoreGraphics.framework */; };
......@@ -21,6 +22,16 @@
A346498214F9BE9A0080D870 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A346498114F9BE9A0080D870 /* AppDelegate.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
51E94E27164472200035348C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 51E94E201644721F0035348C /* Sample Lib.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 51E94E0F164472080035348C;
remoteInfo = "Sample Lib";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
51075D4A1521D0C100E39B41 /* TestRunner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestRunner.app; sourceTree = BUILT_PRODUCTS_DIR; };
51075D511521D0C100E39B41 /* TestRunner-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TestRunner-Info.plist"; sourceTree = "<group>"; };
......@@ -29,6 +40,7 @@
51075D571521D0C100E39B41 /* TestRunner-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TestRunner-Prefix.pch"; sourceTree = "<group>"; };
51075D581521D0C100E39B41 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
51075D591521D0C100E39B41 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
51E94E201644721F0035348C /* Sample Lib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "Sample Lib.xcodeproj"; path = "Sample Lib/Sample Lib.xcodeproj"; sourceTree = "<group>"; };
A346496D14F9BE9A0080D870 /* SampleProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleProject.app; sourceTree = BUILT_PRODUCTS_DIR; };
A346497114F9BE9A0080D870 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
A346497314F9BE9A0080D870 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
......@@ -56,6 +68,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
51E94E2A1644722C0035348C /* libSample Lib.a in Frameworks */,
A346497214F9BE9A0080D870 /* UIKit.framework in Frameworks */,
A346497414F9BE9A0080D870 /* Foundation.framework in Frameworks */,
A346497614F9BE9A0080D870 /* CoreGraphics.framework in Frameworks */,
......@@ -86,9 +99,18 @@
name = "Supporting Files";
sourceTree = "<group>";
};
51E94E211644721F0035348C /* Products */ = {
isa = PBXGroup;
children = (
51E94E28164472200035348C /* libSample Lib.a */,
);
name = Products;
sourceTree = "<group>";
};
A346496214F9BE990080D870 = {
isa = PBXGroup;
children = (
51E94E201644721F0035348C /* Sample Lib.xcodeproj */,
A346497714F9BE9A0080D870 /* SampleProject */,
51075D4F1521D0C100E39B41 /* TestRunner */,
A346497014F9BE9A0080D870 /* Frameworks */,
......@@ -192,6 +214,12 @@
mainGroup = A346496214F9BE990080D870;
productRefGroup = A346496E14F9BE9A0080D870 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = 51E94E211644721F0035348C /* Products */;
ProjectRef = 51E94E201644721F0035348C /* Sample Lib.xcodeproj */;
},
);
projectRoot = "";
targets = (
A346496C14F9BE9A0080D870 /* SampleProject */,
......@@ -200,6 +228,16 @@
};
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
51E94E28164472200035348C /* libSample Lib.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libSample Lib.a";
remoteRef = 51E94E27164472200035348C /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
/* End PBXReferenceProxy section */
/* Begin PBXResourcesBuildPhase section */
51075D481521D0C100E39B41 /* Resources */ = {
isa = PBXResourcesBuildPhase;
......
Subproject commit ddda7566d193fe0e5fa1c419f5b5239ef964f263
Subproject commit 84ea24c2f3a5d463da1e7945c60fd3f33f73dee2
......@@ -8,7 +8,6 @@ Pod::Spec.new do |s|
s.source = { :git => 'http://banana-corp.local/banana-lib.git', :tag => 'v1.0' }
s.source_files = 'Classes/*.{h,m}', 'Vendor'
s.xcconfig = { 'OTHER_LDFLAGS' => '-framework SystemConfiguration' }
s.clean_paths = "sub-dir"
s.prefix_header_file = 'Classes/BananaLib.pch'
s.resources = "Resources/*.png"
s.dependency 'monkey', '~> 1.0.1', '< 1.0.9'
......
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Command::List do
describe "Command::List" do
extend SpecHelper::TemporaryRepos
extend SpecHelper::TemporaryDirectory
it "lists the known pods" do
before do
set_up_test_repo
config.repos_dir = SpecHelper.tmp_repos_path
end
it "presents the known pods" do
out = run_command('list')
[ /ZBarSDK/,
/TouchJSON/,
/SDURLCache/,
/MagicalRecord/,
/A2DynamicDelegate/,
[ /BananaLib/,
/JSONKit/,
/\d+ pods were found/
].each { |regex| out.should =~ regex }
end
it "lists the new pods" do
Time.stubs(:now).returns(Time.mktime(2012,2,3))
it "returns the new pods" do
sets = Source.all_sets
jsonkit_set = sets.find { |s| s.name == 'JSONKit' }
dates = {
'BananaLib' => Time.now,
'JSONKit' => Time.parse('01/01/1970') }
Specification::Set::Statistics.any_instance.stubs(:creation_dates).returns(dates)
out = run_command('list', 'new')
[ 'iCarousel',
'libPusher',
'SSCheckBoxView',
'KKPasscodeLock',
'SOCKit',
'FileMD5Hash',
'cocoa-oauth',
'iRate'
].each {|s| out.should.include s }
out.should.include('BananaLib')
out.should.not.include('JSONKit')
end
end
end
......
......@@ -10,71 +10,76 @@ module Pod
config.repos_dir = SpecHelper.tmp_repos_path
end
def master_repo
fixture('spec-repos/master')
end
it "requires a spec-repo name" do
lambda { command('push').validate! }.should.raise CLAide::Help
end
it "complains if it can't find the repo" do
repo1 = add_repo('repo1', master_repo)
Dir.chdir(fixture('banana-lib')) do
lambda { run_command('push', 'repo2') }.should.raise Informative
cmd = command('push', 'missing_repo')
cmd.expects(:validate_podspec_files).returns(true)
e = lambda { cmd.run }.should.raise Informative
e.message.should.match(/repo not found/)
end
end
it "complains if it can't find a spec" do
repo1 = add_repo('repo1', master_repo)
lambda { run_command('push', 'repo1') }.should.raise Informative
repo_make('test_repo')
e = lambda { run_command('push', 'test_repo') }.should.raise Pod::Informative
e.message.should.match(/Couldn't find any .podspec/)
end
it "it raises if the pod is not validated" do
repo1 = add_repo('repo1', master_repo)
repo2 = add_repo('repo2', repo1.dir)
git_config('repo2', 'remote.origin.url').should == (tmp_repos_path + 'repo1').to_s
Dir.chdir(fixture('banana-lib')) do
lambda { run_command('push', 'repo2', '--silent') }.should.raise Informative
# TODO: the validation should not use the pod spec command
xit "it raises if the specification doesn't validates" do
repo_make('test_repo')
Dir.chdir(temporary_directory) do
spec = "Spec.new do |s|; s.name = 'Broken'; end"
File.open('Broken.podspec', 'w') {|f| f.write(spec) }
cmd = command('push', 'test_repo')
cmd.expects(:validate_podspec_files).returns(true)
e = lambda { cmd.run }.should.raise Pod::Informative
e.message.should.match(/repo not clean/)
end
# (repo1.dir + 'BananaLib/1.0/BananaLib.podspec').read.should.include 'Added!'
end
#--------------------------------------#
before do
# prepare the repos
@upstream = add_repo('upstream', master_repo)
@local_repo = add_repo('local_repo', @upstream.dir)
git_config('local_repo', 'remote.origin.url').should == (tmp_repos_path + 'upstream').to_s
repo_make('upstream')
repo_clone('upstream', 'local_repo')
# prepare the spec
spec = (fixture('spec-repos') + 'master/JSONKit/1.4/JSONKit.podspec').read
spec_fix = spec.gsub(/https:\/\/github\.com\/johnezang\/JSONKit\.git/, fixture('integration/JSONKit').to_s)
spec_add = spec.gsub(/'JSONKit'/, "'PushTest'")
File.open(temporary_directory + 'JSONKit.podspec', 'w') {|f| f.write(spec_fix) }
File.open(temporary_directory + 'JSONKit.podspec', 'w') {|f| f.write(spec_fix) }
File.open(temporary_directory + 'PushTest.podspec', 'w') {|f| f.write(spec_add) }
end
it "refuses to push if the repo is not clean" do
File.open(@local_repo.dir + 'README', 'w') {|f| f.write('Added!') }
(@local_repo.dir + 'README').read.should.include 'Added!'
cmd = command('push', 'local_repo')
cmd.expects(:validate_podspec_files).returns(true)
Dir.chdir(temporary_directory) { lambda { cmd.run }.should.raise Informative }
(@upstream.dir + 'PushTest/1.4/PushTest.podspec').should.not.exist?
repo_make_readme_change('local_repo', 'dirty')
Dir.chdir(temporary_directory) do
cmd = command('push', 'local_repo')
cmd.expects(:validate_podspec_files).returns(true)
e = lambda { cmd.run }.should.raise Pod::Informative
e.message.should.match(/repo not clean/)
end
(repo_path('upstream') + 'PushTest/1.4/PushTest.podspec').should.not.exist?
end
it "sucessfully pushes a spec" do
git('upstream', 'checkout master') # checkout master, to allow push in a non-bare repository
cmd = command('push', 'local_repo')
Dir.chdir(repo_path 'upstream') { `git checkout -b tmp_for_push -q` }
cmd.expects(:validate_podspec_files).returns(true)
Dir.chdir(temporary_directory) { cmd.run }
UI.output.should.include('[Add] PushTest (1.4)')
UI.output.should.include('[Fix] JSONKit (1.4)')
Pod::UI.output.should.include('[Add] PushTest (1.4)')
Pod::UI.output.should.include('[Add] JSONKit (1.4)')
# TODO check the commit messages
# Pod::UI.output.should.include('[Fix] JSONKit (1.4)')
git('upstream', 'checkout test') # checkout because test because is it the branch used in the specs.
(@upstream.dir + 'PushTest/1.4/PushTest.podspec').read.should.include('PushTest')
Dir.chdir(repo_path 'upstream') { `git checkout master -q` }
(repo_path('upstream') + 'PushTest/1.4/PushTest.podspec').read.should.include('PushTest')
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
describe "Pod::Command::Repo" do
before do
config.repos_dir = SpecHelper.tmp_repos_path
end
describe "In general" do
extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
it "adds a spec-repo" do
run_command('repo', 'add', 'private', fixture('spec-repos/master'))
git_config('private', 'remote.origin.url').should == fixture('spec-repos/master').to_s
end
module Pod
describe Command::Repo do
describe "In general" do
extend SpecHelper::Command
extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
before do
set_up_test_repo
config.repos_dir = SpecHelper.tmp_repos_path
end
it "adds a spec-repo with on a specified branch" do
repo1 = add_repo('repo1', fixture('spec-repos/master'))
Dir.chdir(repo1.dir) do
`git checkout -b my-branch >/dev/null 2>&1`
`git checkout master >/dev/null 2>&1`
it "updates a repository" do
upstream = SpecHelper.temporary_directory + 'upstream'
FileUtils.cp_r(test_repo_path, upstream)
Dir.chdir(test_repo_path) do
`git remote add origin #{upstream}`
`git remote -v`
`git fetch -q`
`git branch --set-upstream master origin/master`
end
lambda { command('repo', 'update').run }.should.not.raise
end
repo2 = command( 'repo' ,'add', 'repo2', repo1.dir, 'my-branch')
repo2.run
Dir.chdir(repo2.dir) { `git symbolic-ref HEAD` }.should.include? 'my-branch'
end
it "updates a spec-repo" do
repo1 = add_repo('repo1', fixture('spec-repos/master'))
repo2 = add_repo('repo2', repo1.dir)
make_change(repo1, 'repo1')
run_command('repo', 'update', 'repo2')
(repo2.dir + 'README').read.should.include 'Added!'
end
it "lints a repository" do
lambda { run_command('repo', 'lint', temporary_directory.to_s) }.should.not.raise
end
it "updates all the spec-repos" do
repo1 = add_repo('repo1', fixture('spec-repos/master'))
repo2 = add_repo('repo2', repo1.dir)
repo3 = add_repo('repo3', repo1.dir)
make_change(repo1, 'repo1')
run_command('repo', 'update')
(repo2.dir + 'README').read.should.include 'Added!'
(repo3.dir + 'README').read.should.include 'Added!'
end
it "adds a spec-repo" do
run_command('repo', 'add', 'private', test_repo_path)
Dir.chdir(config.repos_dir + 'private') do
`git config --get remote.origin.url`.chomp.should == test_repo_path.to_s
end
end
before do
config.repos_dir = fixture('spec-repos')
end
it "adds a spec-repo with a specified branch" do
repo1 = repo_make('repo1')
Dir.chdir(repo1) do
`git checkout -b my-branch >/dev/null 2>&1`
`git checkout master >/dev/null 2>&1`
end
repo2 = command( 'repo' ,'add', 'repo2', repo1.to_s, 'my-branch')
repo2.run
Dir.chdir(repo2.dir) { `git symbolic-ref HEAD` }.should.include? 'my-branch'
end
it "lints a repo" do
cmd = command('repo', 'lint', 'master')
lambda { cmd.run }.should.raise Pod::Informative
Pod::UI.output.should.include "Missing license type"
it "updates a spec-repo" do
repo1 = repo_make('repo1')
repo2 = repo_clone('repo1', 'repo2')
repo_make_readme_change(repo1, 'Updated')
Dir.chdir(repo1) {`git commit -a -m "Update"`}
run_command('repo', 'update', 'repo2')
(repo2 + 'README').read.should.include 'Updated'
end
end
end
describe "Concerning a repo support" do
extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
describe "CocoaPods version" do
extend SpecHelper::Command
extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
before do
add_repo('repo1', fixture('spec-repos/master'))
FileUtils.rm_rf(versions_file)
versions_file.should.not.exist?
end
require 'yaml'
require 'yaml'
def versions_file
tmp_repos_path + "repo1/CocoaPods-version.yml"
end
before do
config.repos_dir = SpecHelper.tmp_repos_path
@repo = repo_make('repo1')
end
def write_version_file(hash)
yaml = YAML.dump(hash)
File.open(versions_file, 'w') {|f| f.write(yaml) }
end
def write_version_file(hash)
yaml = YAML.dump(hash)
@versions_file = tmp_repos_path + "repo1/CocoaPods-version.yml"
File.open(@versions_file, 'w') {|f| f.write(yaml) }
end
it "it doesn't requires CocoaPods-version.yml" do
cmd = command('repo', 'update')
lambda { cmd.check_versions(versions_file.dirname) }.should.not.raise
end
it "it doesn't requires CocoaPods-version.yml" do
cmd = command('repo', 'update')
lambda { cmd.check_versions(@repo) }.should.not.raise
end
it "runs with a compatible repo" do
write_version_file({'min' => "0.0.1"})
cmd = command('repo', 'update')
lambda { cmd.check_versions(versions_file.dirname) }.should.not.raise
end
it "runs with a compatible repo" do
write_version_file({'min' => "0.0.1"})
cmd = command('repo', 'update')
lambda { cmd.check_versions(@repo) }.should.not.raise
end
it "raises if a repo is not compatible" do
write_version_file({'min' => "999.0.0"})
cmd = command('repo', 'update')
lambda { cmd.check_versions(versions_file.dirname) }.should.raise Pod::Informative
end
it "raises if a repo is not compatible" do
write_version_file({'min' => "999.0.0"})
cmd = command('repo', 'update')
lambda { cmd.check_versions(@repo) }.should.raise Informative
end
it "informs about a higher known CocoaPods version" do
write_version_file({'last' => "999.0.0"})
cmd = command('repo', 'update')
cmd.check_versions(versions_file.dirname)
Pod::UI.output.should.include "Cocoapods 999.0.0 is available"
end
it "informs about a higher known CocoaPods version" do
write_version_file({'last' => "999.0.0"})
cmd = command('repo', 'update')
cmd.check_versions(@repo)
UI.output.should.include "Cocoapods 999.0.0 is available"
end
it "has a class method that returns if a repo is supported" do
write_version_file({'min' => "999.0.0"})
Pod::Command::Repo.compatible?('repo1').should == false
it "has a class method that returns if a repo is supported" do
write_version_file({'min' => "999.0.0"})
Command::Repo.compatible?('repo1').should == false
write_version_file({'min' => "0.0.1"})
Pod::Command::Repo.compatible?('repo1').should == true
write_version_file({'min' => "0.0.1"})
Command::Repo.compatible?('repo1').should == true
end
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Command::Setup do
extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
before do
config.repos_dir = SpecHelper.tmp_repos_path
end
it "runs with correct parameters" do
lambda { run_command('setup') }.should.not.raise
end
it "complains for wrong parameters" do
lambda { run_command('setup', 'wrong') }.should.raise CLAide::Help
lambda { run_command('setup', '--wrong') }.should.raise CLAide::Help
end
it "returns the read only URL of the `master' spec-repo" do
cmd = Command::Setup.new(argv)
cmd.url.should == 'https://github.com/CocoaPods/Specs.git'
end
it "returns the push URL of the `master' spec-repo" do
config.silent = true
cmd = Command::Setup.new(argv('--push'))
cmd.url.should == 'git@github.com:CocoaPods/Specs.git'
end
class Command::Setup
def read_only_url; SpecHelper.fixture('spec-repos/master'); end
end
it "creates the local spec-repos directory and creates a clone of the `master' repo" do
output = run_command('setup')
output.should.include "Setup completed"
output.should.not.include "push"
git_config('master', 'remote.origin.url').should == fixture('spec-repos/master').to_s
end
it "preserves push access for the `master' repo" do
output = run_command('setup')
output.should.not.include "push"
git('master', 'remote set-url origin git@github.com:CocoaPods/Specs.git')
command('setup').url.should == 'git@github.com:CocoaPods/Specs.git'
end
it "can run if needed" do
output = run_command('setup')
output.should.include "Setup completed"
UI.output = ''
command('setup').run_if_needed
UI.output.should == ''
end
describe Pod::Command::Setup do
extend SpecHelper::Command
extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
before do
config.repos_dir = SpecHelper.tmp_repos_path
end
it "returns the read only URL of the `master` spec-repo" do
cmd = Pod::Command::Setup.new(argv)
cmd.url.should == 'https://github.com/CocoaPods/Specs.git'
end
it "returns the push URL of the `master' spec-repo" do
config.silent = true
cmd = Pod::Command::Setup.new(argv('--push'))
cmd.url.should == 'git@github.com:CocoaPods/Specs.git'
end
before do
set_up_test_repo
Pod::Command::Setup.any_instance.stubs(:read_only_url).returns(test_repo_path.to_s)
config.repos_dir = SpecHelper.temporary_directory
end
it "runs with correct parameters" do
lambda { run_command('setup') }.should.not.raise
end
it "creates the local spec-repos directory and creates a clone of the `master' repo" do
output = run_command('setup')
output.should.include "Setup completed"
output.should.not.include "push"
url = Dir.chdir(config.repos_dir + 'master') { `git config --get remote.origin.url`.chomp }
url.should == test_repo_path.to_s
end
it "preserves push access for the `master' repo" do
output = run_command('setup')
output.should.not.include "push"
Dir.chdir(config.repos_dir + 'master') { `git remote set-url origin git@github.com:CocoaPods/Specs.git` }
command('setup').url.should == 'git@github.com:CocoaPods/Specs.git'
end
it "can run if needed" do
output = run_command('setup')
output.should.include "Setup completed"
Pod::UI.output = ''
command('setup').run_if_needed
Pod::UI.output.should == ''
end
end
......@@ -22,7 +22,8 @@ module Pod
it "creates a new podspec stub file" do
run_command('spec', 'create', 'Bananas')
path = temporary_directory + 'Bananas.podspec'
spec = Specification.from_file(path).activate_platform(:ios)
spec = Specification.from_file(path)
spec.activate_platform(:ios)
spec.name.should == 'Bananas'
spec.license.should == { :type => "MIT (example)" }
......@@ -31,7 +32,6 @@ module Pod
spec.homepage.should == 'http://EXAMPLE/Bananas'
spec.authors.should == { `git config --get user.name`.strip => `git config --get user.email`.strip}
spec.source.should == { :git => 'http://EXAMPLE/Bananas.git', :tag => '0.0.1' }
spec.description.should == 'A short description of Bananas.'
spec.source_files.should == ['Classes', 'Classes/**/*.{h,m}']
spec.public_header_files.should == []
end
......
......@@ -6,7 +6,7 @@ describe Pod::UI do
before do
@set = Pod::Source.search(Pod::Dependency.new('CocoaLumberjack'))
Pod::Specification::Statistics.instance.cache_file = nil
Pod::Specification::Set::Statistics.instance.cache_file = nil
end
it "presents the name, version, description, homepage and source of a specification set" do
......@@ -15,17 +15,12 @@ describe Pod::UI do
output.should.include? 'CocoaLumberjack'
output.should.include? '1.0'
output.should.include? '1.1'
output.should.include? '[master repo]'
output.should.include? 'A fast & simple, yet powerful & flexible logging framework for Mac and iOS.'
output.should.include? 'https://github.com/robbiehanson/CocoaLumberjack'
output.should.include? 'https://github.com/robbiehanson/CocoaLumberjack.git'
end
it "presents the name, version, description, homepage and source of a specification set" do
Pod::UI.pod(@set)
output = Pod::UI.output
output.should.include? 'Versions: 1.6, 1.3.3, 1.3.2, 1.3.1, 1.3, 1.2.3, 1.2.2, 1.2.1, 1.2, 1.1, 1.0 [master repo]'
end
it "presents the stats of a specification set" do
repo = { "forks"=>42, "watchers"=>318, "pushed_at"=>"2011-01-26T19:06:43Z" }
Octokit.expects(:repo).with("robbiehanson/CocoaLumberjack").returns(repo)
......
require File.expand_path('../../spec_helper', __FILE__)
describe Pod::Installer::UserProjectIntegrator do
extend SpecHelper::TemporaryDirectory
before do
sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
config.project_root = sample_project_path.dirname
@podfile = Pod::Podfile.new do
platform :ios
xcodeproj sample_project_path, 'Test' => :debug
link_with 'SampleProject' # this is an app target!
pod 'JSONKit'
target :test_runner, :exclusive => true do
link_with 'TestRunner'
pod 'Kiwi'
end
end
@sample_project_path = sample_project_path
@integrator = Pod::Installer::UserProjectIntegrator.new(@podfile)
@integrator.integrate!
@sample_project = Xcodeproj::Project.new(sample_project_path)
end
it 'adds references to the Pods static libraries to the Frameworks group' do
@sample_project["Frameworks/libPods.a"].should.not == nil
@sample_project["Frameworks/libPods-test_runner.a"].should.not == nil
end
it 'creates a workspace with a name matching the project' do
workspace_path = @sample_project_path.dirname + "SampleProject.xcworkspace"
workspace_path.should.exist
end
it 'adds the project being integrated to the workspace' do
workspace = Xcodeproj::Workspace.new_from_xcworkspace(@sample_project_path.dirname + "SampleProject.xcworkspace")
workspace.projpaths.sort.should == %w{ Pods/Pods.xcodeproj SampleProject.xcodeproj }
end
it 'adds the Pods project to the workspace' do
workspace = Xcodeproj::Workspace.new_from_xcworkspace(@sample_project_path.dirname + "SampleProject.xcworkspace")
workspace.projpaths.find { |path| path =~ /Pods.xcodeproj/ }.should.not.be.nil
end
it 'sets the Pods xcconfig as the base config for each build configuration' do
@podfile.target_definitions.each do |_, definition|
target = @sample_project.targets.find { |t| t.name == definition.link_with.first }
xcconfig_file = @sample_project.files.find { |f| f.path == "Pods/#{definition.xcconfig_name}" }
target.build_configurations.each do |config|
config.base_configuration_reference.should == xcconfig_file
end
end
end
it 'adds the libPods static library to the "Link binary with libraries" build phase of each target' do
@podfile.target_definitions.each do |_, definition|
target = @sample_project.targets.find { |t| t.name == definition.link_with.first }
target.frameworks_build_phase.files.find { |f| f.file_ref.name == definition.lib_name}.should.not == nil
end
end
it 'adds a Copy Pods Resources build phase to each target' do
@podfile.target_definitions.each do |_, definition|
target = @sample_project.targets.find { |t| t.name == definition.link_with.first }
phase = target.shell_script_build_phases.find { |bp| bp.name == "Copy Pods Resources" }
phase.shell_script.strip.should == "\"${SRCROOT}/Pods/#{definition.copy_resources_script_name}\""
end
end
before do
# Reset the cached TargetIntegrator#targets lists.
@integrator.instance_variable_set(:@target_integrators, nil)
end
it "only tries to integrate Pods libraries into user targets that haven't been integrated yet" do
app_integrator = @integrator.target_integrators.find { |t| t.target_definition.name == :default }
test_runner_integrator = @integrator.target_integrators.find { |t| t.target_definition.name == :test_runner }
# Remove libPods.a from the app target. But don't do it through TargetIntegrator#targets,
# as it will return only those that still need integration.
app_target = app_integrator.user_project.targets.find { |t| t.name == 'SampleProject' }
app_target.frameworks_build_phase.files.last.remove_from_project
app_integrator.expects(:add_pods_library)
test_runner_integrator.expects(:add_pods_library).never
@integrator.integrate!
end
it "does not even try to save the project if none of the target integrators had any work to do" do
@integrator.target_integrators.first.user_project.expects(:save_as).never
@integrator.integrate!
end
end
......@@ -68,8 +68,7 @@ else
end
# Note that we are *not* using the stubbed SpecHelper::Installer subclass.
resolver = Pod::Resolver.new(podfile, nil, Pod::Sandbox.new(config.project_pods_root))
installer = Pod::Installer.new(resolver)
installer = Pod::Installer.new(Pod::Sandbox.new(config.project_pods_root), podfile)
installer.install!
result = installer.lockfile.to_hash
result['PODS'].should == ['SSToolkit (0.1.3)']
......@@ -86,8 +85,7 @@ 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 = SpecHelper::Installer.new(Pod::Sandbox.new(config.project_pods_root), podfile)
installer.install!
result = installer.lockfile.to_hash
result['PODS'].should == ['Reachability (1.2.3)']
......@@ -194,15 +192,19 @@ else
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)"]
{ "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.1.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).
......@@ -302,7 +304,7 @@ else
lockfile_contents = {
'PODS' => [
'JSONKit (999.999.999)',
'Reachability (3.0.0)',
'Reachability (3.1.0)',
'SSZipArchive (0.1.1)',
],
'DEPENDENCIES' => [
......
......@@ -44,7 +44,7 @@ config = Pod::Config.instance
config.silent = true
config.repos_dir = SpecHelper.tmp_repos_path
config.project_root = SpecHelper.temporary_directory
Pod::Specification::Statistics.instance.cache_file = nil
Pod::Specification::Set::Statistics.instance.cache_file = nil
require 'tmpdir'
......@@ -77,3 +77,4 @@ VCR.configure do |c|
c.allow_http_connections_when_no_cassette = true
end
require "active_support/core_ext/string/strip"
......@@ -3,10 +3,29 @@ module Bacon
@needs_first_put = true
module ColorOutput
# Graciously yanked from https://github.com/zen-cms/Zen-Core and subsequently modified
# MIT License
# Thanks, YorickPeterse! #:nodoc:
def self.color(color, string)
case color
when :red
"\e[31m#{string}\e[0m"
when :green
"\e[32m#{string}\e[0m"
when :yellow
"\e[33m#{string}\e[0m"
else
# Support for Conque
"\e[0m#{string}\e[0m"
end
end
#---------------------------------------------------------------------------#
# Overrides the SpecDoxzRtput to provide colored output by default
#
# Based on https://github.com/zen-cms/Zen-Core and subsequently modified
# which is available under the MIT License. Thanks YorickPeterse!
#
module SpecDoxOutput
def handle_specification(name)
if @needs_first_put
@needs_first_put = false
......@@ -27,18 +46,20 @@ module Bacon
error = yield
if !error.empty?
puts "#{spaces}\e[31m- #{description} [FAILED]\e[0m"
puts Bacon.color(:red, "#{spaces}- #{description} [FAILED]")
elsif disabled
puts "#{spaces}\e[33m- #{description} [DISABLED]\e[0m"
puts Bacon.color(:yellow, "#{spaces}- #{description} [DISABLED]")
else
puts "#{spaces}\e[32m- #{description}\e[0m"
puts Bacon.color(:green, "#{spaces}- #{description}")
end
end
#:nodoc:
def handle_summary
print ErrorLog if Backtraces
puts "\e[33m#{Counter[:disabled]} disabled specifications\n\e[0m" unless Counter[:disabled].zero?
unless Counter[:disabled].zero?
puts Bacon.color(:yellow, "#{Counter[:disabled]} disabled specifications\n")
end
puts "%d specifications (%d requirements), %d failures, %d errors" %
Counter.values_at(:specifications, :requirements, :failed, :errors)
end
......@@ -48,7 +69,57 @@ module Bacon
return ' ' * @specs_depth
end
end
extend ColorOutput
#---------------------------------------------------------------------------#
# Overrides the TestUnitOutput to provide colored result output.
#
module TestUnitOutput
def handle_specification(name)
print Bacon.color(nil, ':')
yield
end
def handle_requirement(description, disabled = false)
if false && @first_error
print Bacon.color(nil, '_')
else
error = yield
if !error.empty?
m = error[0..0]
c = (m == "E" ? :red : :yellow)
print Bacon.color(c, m)
@first_error = true
elsif disabled
print "D"
else
print Bacon.color(nil, '.')
end
end
end
def handle_summary
first_error = ''
error_count = 0
ErrorLog.lines.each do |s|
error_count += 1 if s.include?('Error:') || s.include?('Informative')
first_error << s if error_count <= 1
end
puts "\n#{first_error}" if Backtraces
unless Counter[:disabled].zero?
puts Bacon.color(:yellow, "#{Counter[:disabled]} disabled specifications")
end
result = "%d specifications (%d requirements), %d failures, %d errors" %
Counter.values_at(:specifications, :requirements, :failed, :errors)
if Counter[:failed].zero?
puts Bacon.color(:green, result)
else
puts Bacon.color(:red, result)
end
end
end
#---------------------------------------------------------------------------#
module FilterBacktraces
def handle_summary
......@@ -58,6 +129,9 @@ module Bacon
super
end
end
#---------------------------------------------------------------------------#
extend FilterBacktraces
class Context
......@@ -67,3 +141,6 @@ module Bacon
end
end
end
require 'spec_helper/temporary_directory'
# Important
# Include with temporary directory
module SpecHelper
def self.tmp_repos_path
TemporaryRepos.tmp_repos_path
......@@ -9,41 +12,64 @@ module SpecHelper
extend Pod::Executable
executable :git
def tmp_repos_path
SpecHelper.temporary_directory + 'cocoapods'
# @return [Pathname] The path for the repo with the given name.
#
def repo_path(name)
tmp_repos_path + name
end
module_function :tmp_repos_path
alias_method :git_super, :git
def git(repo, command)
Dir.chdir(tmp_repos_path + repo) do
if output = git_super(command)
output.strip
end
# Makes a repo with the given name.
#
def repo_make(name)
path = repo_path(name)
path.mkpath
Dir.chdir(path) do
`git init`
repo_make_readme_change(name, 'Added')
`git add .`
`git commit -m "Initialized."`
end
path
end
def git_config(repo, attr)
git repo, "config --get #{attr}"
# Clones a repo to the given name.
#
def repo_clone(from_name, to_name)
Dir.chdir(tmp_repos_path) { `git clone #{from_name} #{to_name}` }
repo_path(to_name)
end
def add_repo(name, from)
command = Pod::Command.parse(['repo', 'add', name, from])
command.run
# The test branch is used by the push specs
Dir.chdir(command.dir) do
`git checkout -b test >/dev/null 2>&1`
`git branch --set-upstream test origin/master >/dev/null 2>&1`
end
command
def repo_make_readme_change(name, string)
file = repo_path(name) + 'README'
file.open('w') { |f| f << "#{string}" }
end
#--------------------------------------#
def test_repo_path
repo_path('master')
end
def make_change(repo, name)
(repo.dir + 'README').open('w') { |f| f << 'Added!' }
git(name, 'add README')
git(name, 'commit -m "changed"')
# Sets up a lighweight master repo in `tmp/cocoapods/master` with the
# contents of `spec/fixtures/spec-repos/test_repo`.
#
def set_up_test_repo
require 'fileutils'
test_repo_path.mkpath
origin = ROOT + 'spec/fixtures/spec-repos/test_repo/.'
destination = tmp_repos_path + 'master'
FileUtils.cp_r(origin, destination)
repo_make('master')
end
#--------------------------------------#
def tmp_repos_path
SpecHelper.temporary_directory + 'cocoapods'
end
module_function :tmp_repos_path
def self.extended(base)
base.before do
tmp_repos_path.mkpath
......
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Command::AdvancedLinter do
extend SpecHelper::TemporaryDirectory
def write_podspec(text, name = 'JSONKit.podspec')
file = temporary_directory + 'JSONKit.podspec'
File.open(file, 'w') {|f| f.write(text) }
file
end
def stub_podspec(pattern = nil, replacement = nil)
spec = (fixture('spec-repos') + 'master/JSONKit/1.4/JSONKit.podspec').read
spec.gsub!(/https:\/\/github\.com\/johnezang\/JSONKit\.git/, fixture('integration/JSONKit').to_s)
spec.gsub!(pattern, replacement) if pattern && replacement
spec
end
it "respects quick mode" do
file = write_podspec(stub_podspec)
linter = Command::AdvancedLinter.new(file)
linter.expects(:peform_multiplatform_analysis).never
linter.expects(:install_pod).never
linter.expects(:xcodebuild_output_for_platfrom).never
linter.expects(:file_patterns_errors_for_platfrom).never
linter.quick = true
linter.lint
end
unless skip_xcodebuild?
it "uses xcodebuild to generate notes and warnings" do
file = write_podspec(stub_podspec)
linter = Command::AdvancedLinter.new(file)
linter.lint
linter.result_type.should == :warning
linter.notes.join(' | ').should.include "JSONKit/JSONKit.m:1640:27: warning: equality comparison with extraneous parentheses"
end
end
it "checks for file patterns" do
file = write_podspec(stub_podspec(/s\.source_files = 'JSONKit\.\*'/, "s.source_files = 'JSONKit.*'\ns.resources = 'WRONG_FOLDER'"))
linter = Command::AdvancedLinter.new(file)
linter.stubs(:xcodebuild_output).returns([])
linter.quick = false
linter.lint
linter.result_type.should == :error
linter.errors.join(' | ').should.include "The resources did not match any file"
end
it "uses the deployment target of the specification" do
file = write_podspec(stub_podspec(/s.name *= 'JSONKit'/, "s.name = 'JSONKit'; s.platform = :ios, '5.0'"))
linter = Command::AdvancedLinter.new(file)
linter.quick = true
linter.lint
podfile = linter.podfile_from_spec
deployment_target = podfile.target_definitions[:default].platform.deployment_target
deployment_target.to_s.should == "5.0"
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
describe "Pod::Command::Linter" do
extend SpecHelper::TemporaryDirectory
def write_podspec(text, name = 'JSONKit.podspec')
file = temporary_directory + 'JSONKit.podspec'
File.open(file, 'w') {|f| f.write(text) }
file
end
def stub_podspec(pattern = nil, replacement = nil)
spec = (fixture('spec-repos') + 'master/JSONKit/1.4/JSONKit.podspec').read
spec.gsub!(/https:\/\/github\.com\/johnezang\/JSONKit\.git/, fixture('integration/JSONKit').to_s)
spec.gsub!(pattern, replacement) if pattern && replacement
spec
end
it "fails a specifications that does not contain the minimum required attributes" do
file = write_podspec('Pod::Spec.new do |s| end')
linter = Pod::Command::Spec::Linter.new(file)
linter.quick = true
linter.lint
linter.result_type.should == :error
linter.errors.join(' | ') =~ /name.*version.*summary.*homepage.*authors.*(source.*part_of).*source_files/
end
it "fails specifications if the name does not match the name of the file" do
file = write_podspec(stub_podspec(/s.name *= 'JSONKit'/, "s.name = 'JSONKitAAA'"))
linter = Pod::Command::Spec::Linter.new(file)
linter.quick = true
linter.lint
linter.result_type.should == :error
linter.errors.count.should == 1
linter.errors[0].should =~ /The name of the spec should match the name of the file/
end
it "fails a specification if a path starts with a slash" do
file = write_podspec(stub_podspec(/s.source_files = 'JSONKit\.\*'/, "s.source_files = '/JSONKit.*'"))
linter = Pod::Command::Spec::Linter.new(file)
linter.quick = true
linter.lint
linter.result_type.should == :error
linter.errors.count.should == 1
linter.errors[0].should =~ /Paths cannot start with a slash/
end
it "fails a specification if the platform is unrecognized" do
file = write_podspec(stub_podspec(/s.name *= 'JSONKit'/, "s.name = 'JSONKit'\ns.platform = :iososx\n"))
linter = Pod::Command::Spec::Linter.new(file)
linter.quick = true
linter.lint
linter.result_type.should == :error
linter.errors.count.should == 1
linter.errors[0].should =~ /Unrecognized platfrom/
end
it "fails validation if the specification contains warnings" do
file = write_podspec(stub_podspec(/.*license.*/, ""))
linter = Pod::Command::Spec::Linter.new(file)
linter.quick = true
linter.lint
linter.result_type.should == :warning
linter.errors.should.be.empty
linter.warnings.should.not.be.empty
end
it "correctly report specification that only contain warnings" do
file = write_podspec(stub_podspec(/.*license.*/, ""))
linter = Pod::Command::Spec::Linter.new(file)
linter.quick = true
linter.lint
linter.result_type.should == :warning
end
it "respects quick mode" do
file = write_podspec(stub_podspec)
linter = Pod::Command::Spec::Linter.new(file)
linter.expects(:peform_multiplatform_analysis).never
linter.expects(:install_pod).never
linter.expects(:xcodebuild_output_for_platfrom).never
linter.expects(:file_patterns_errors_for_platfrom).never
linter.quick = true
linter.lint
end
it "produces deprecation notices" do
file = write_podspec(stub_podspec(/s\.source_files = 'JSONKit\.\*'/, "s.source_files = 'JSONKit.*'\n if config.ios?\nend"))
linter = Pod::Command::Spec::Linter.new(file)
linter.quick = true
linter.lint
linter.result_type.should == :error
linter.warnings.should.be.empty
linter.errors.join(' | ').should =~ /`config.ios\?' and `config.osx\?' are deprecated/
end
unless skip_xcodebuild?
it "uses xcodebuild to generate notes and warnings" do
file = write_podspec(stub_podspec)
linter = Pod::Command::Spec::Linter.new(file)
linter.lint
linter.result_type.should == :warning
linter.notes.join(' | ').should.include "JSONKit/JSONKit.m:1640:27: warning: equality comparison with extraneous parentheses"
end
end
it "checks for file patterns" do
file = write_podspec(stub_podspec(/s\.source_files = 'JSONKit\.\*'/, "s.source_files = 'JSONKit.*'\ns.resources = 'WRONG_FOLDER'"))
linter = Pod::Command::Spec::Linter.new(file)
linter.stubs(:xcodebuild_output).returns([])
linter.quick = false
linter.lint
linter.result_type.should == :error
linter.errors.join(' | ').should.include "The resources did not match any file"
end
it "uses the deployment target of the specification" do
file = write_podspec(stub_podspec(/s.name *= 'JSONKit'/, "s.name = 'JSONKit'; s.platform = :ios, '5.0'"))
linter = Pod::Command::Spec::Linter.new(file)
linter.quick = true
linter.lint
podfile = linter.podfile_from_spec
deployment_target = podfile.target_definitions[:default].platform.deployment_target
deployment_target.to_s.should == "5.0"
end
end
require File.expand_path('../../spec_helper', __FILE__)
module Pod
describe Dependency do
it "merges dependencies (taken from newer RubyGems version)" do
dep1 = Dependency.new('bananas', '>= 1.8')
dep2 = Dependency.new('bananas', '1.9')
dep1.merge(dep2).should == Dependency.new('bananas', '>= 1.8', '1.9')
end
it "returns the name of the dependency, or the name of the pod of which this is a subspec" do
dep = Dependency.new('RestKit')
dep.top_level_spec_name.should == 'RestKit'
dep = Dependency.new('RestKit/Networking')
dep.top_level_spec_name.should == 'RestKit'
end
it "returns a copy of the dependency but for the top level spec, if it's a subspec" do
dep = Dependency.new('RestKit', '>= 1.2.3')
dep.to_top_level_spec_dependency.should == Dependency.new('RestKit', '>= 1.2.3')
dep = Dependency.new('RestKit/Networking', '>= 1.2.3')
dep.to_top_level_spec_dependency.should == Dependency.new('RestKit', '>= 1.2.3')
end
it "is equal to another dependency if `external_source' is the same" do
dep1 = Dependency.new('bananas', :git => 'GIT-URL')
dep2 = Dependency.new('bananas')
dep1.should.not == dep2
dep3 = Dependency.new('bananas', :git => 'GIT-URL')
dep1.should == dep3
end
it "is equal to another dependency if `specification' is equal" do
dep1 = Dependency.new { |s| s.name = 'bananas'; s.version = '1' }
dep2 = Dependency.new('bananas')
dep1.should.not == dep2
dep2 = Dependency.new { |s| s.name = 'bananas'; s.version = '1' }
dep1.should == dep2
end
it 'raises if created without either valid name/version/external requirements or a block' do
lambda { Dependency.new }.should.raise Informative
end
describe "defined with a block" do
before do
@dependency = Dependency.new do |spec|
spec.name = "my-custom-spec"
spec.version = "1.0.3"
end
end
it 'it identifies itself as an inline dependency' do
@dependency.should.be.inline
end
it 'attaches a custom spec to the dependency, configured by the block' do
@dependency.specification.name.should == "my-custom-spec"
end
end
describe "with a hash of external source settings" do
before do
@dependency = Dependency.new("cocoapods", :git => "git://github.com/cocoapods/cocoapods")
end
it 'identifies itself as an external dependency' do
@dependency.should.be.external
end
end
describe "with flags" do
it "identifies itself as a `bleeding edge' dependency" do
dependency = Dependency.new("cocoapods", :head)
dependency.should.be.head
dependency.to_s.should == "cocoapods (HEAD)"
end
it "only supports the `:head' option on the last version of a pod" do
should.raise Informative do
Dependency.new("cocoapods", "1.2.3", :head)
end
end
it "raises if an invalid flag is given" do
should.raise ArgumentError do
Dependency.new("cocoapods", :foot)
end
end
end
describe Dependency::ExternalSources do
before do
@sandbox = temporary_sandbox
end
it "marks a LocalPod as downloaded if it's from GitSource" do
dependency = Dependency.new("Reachability", :git => fixture('integration/Reachability'))
dependency.external_source.copy_external_source_into_sandbox(@sandbox, Platform.ios)
@sandbox.installed_pod_named('Reachability', Platform.ios).downloaded.should.be.true
end
it "creates a copy of the podspec (GitSource)" do
dependency = Dependency.new("Reachability", :git => fixture('integration/Reachability'))
dependency.external_source.copy_external_source_into_sandbox(@sandbox, Platform.ios)
path = @sandbox.root + 'Local Podspecs/Reachability.podspec'
path.should.exist?
end
it "creates a copy of the podspec (PodspecSource)" do
dependency = Dependency.new("Reachability", :podspec => fixture('integration/Reachability/Reachability.podspec').to_s)
dependency.external_source.copy_external_source_into_sandbox(@sandbox, Platform.ios)
path = @sandbox.root + 'Local Podspecs/Reachability.podspec'
path.should.exist?
end
it "creates a copy of the podspec (LocalSource)" do
dependency = Dependency.new("Reachability", :local => fixture('integration/Reachability'))
dependency.external_source.copy_external_source_into_sandbox(@sandbox, Platform.ios)
path = @sandbox.root + 'Local Podspecs/Reachability.podspec'
path.should.exist?
end
end
end
end
require File.expand_path('../../spec_helper', __FILE__)
module Pod
describe ExternalSources do
it "returns the instance of appropriate concrete class according to the parameters" do
git = Dependency.new("Reachability", :git => nil)
podspec = Dependency.new("Reachability", :podspec => nil)
local = Dependency.new("Reachability", :local => nil)
ExternalSources.from_dependency(git).class.should == ExternalSources::GitSource
ExternalSources.from_dependency(podspec).class.should == ExternalSources::PodspecSource
ExternalSources.from_dependency(local).class.should == ExternalSources::LocalSource
end
end
describe ExternalSources::AbstractExternalSource do
xit "returns the name" do end
xit "returns the params" do end
xit "returns the compares to another" do end
xit "returns the specification" do end
xit "returns the specification from the sandbox if available" do end
xit "returns the specification fetching it from the external source" do end
end
describe ExternalSources::GitSource do
it "creates a copy of the podspec" do
dependency = Dependency.new("Reachability", :git => fixture('integration/Reachability'))
external_source = ExternalSources.from_dependency(dependency)
external_source.copy_external_source_into_sandbox(config.sandbox)
path = config.sandbox.root + 'Local Podspecs/Reachability.podspec'
path.should.exist?
end
it "marks a LocalPod as downloaded" do
dependency = Dependency.new("Reachability", :git => fixture('integration/Reachability'))
external_source = ExternalSources.from_dependency(dependency)
external_source.copy_external_source_into_sandbox(config.sandbox)
config.sandbox.predownloaded_pods.should == ["Reachability"]
end
xit "returns the description" do end
end
describe ExternalSources::PodspecSource do
it "creates a copy of the podspec" do
dependency = Dependency.new("Reachability", :podspec => fixture('integration/Reachability/Reachability.podspec').to_s)
external_source = ExternalSources.from_dependency(dependency)
external_source.copy_external_source_into_sandbox(config.sandbox)
path = config.sandbox.root + 'Local Podspecs/Reachability.podspec'
path.should.exist?
end
xit "returns the description" do end
end
describe ExternalSources::LocalSource do
it "creates a copy of the podspec" do
dependency = Dependency.new("Reachability", :local => fixture('integration/Reachability'))
external_source = ExternalSources.from_dependency(dependency)
external_source.copy_external_source_into_sandbox(config.sandbox)
path = config.sandbox.root + 'Local Podspecs/Reachability.podspec'
path.should.exist?
end
xit "returns the description" do end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
describe PrefixHeader = Pod::Generator::PrefixHeader do
before do
platform = Pod::Platform.ios
specification = fixture_spec('banana-lib/BananaLib.podspec')
@pod = Pod::LocalPod.new(specification, nil, platform)
pods = [ @pod ]
@gen = PrefixHeader.new(platform, pods)
@pod.stubs(:root).returns(Pathname.new(fixture('banana-lib')))
end
it "includes the contents of the specification's prefix header" do
spec = @pod.top_specification
spec.prefix_header_contents = '#import "BlocksKit.h"'
@gen.generate.should == <<-EOS.strip_heredoc
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "BlocksKit.h"
EOS
end
it "includes the contents of the specification's prefix header file" do
@gen.generate.should == <<-EOS.strip_heredoc
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import <BananaTree/BananaTree.h>
EOS
end
it "prefers the inline specification of the prefix header contents" do
spec = @pod.top_specification
spec.prefix_header_contents = '#import "BlocksKit.h"'
@pod.prefix_header_file.should.be.not.nil
@gen.generate.should == <<-EOS.strip_heredoc
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "BlocksKit.h"
EOS
end
it "imports UIKit in iOS platforms" do
@gen.stubs(:platform).returns(Pod::Platform.ios)
@gen.generate.should.include?('#import <UIKit/UIKit.h>')
end
it "imports Cocoa for OS X platforms" do
@gen.stubs(:platform).returns(Pod::Platform.osx)
@gen.generate.should.include?('#import <Cocoa/Cocoa.h>')
end
extend SpecHelper::TemporaryDirectory
it "writes the prefix header file to the disk" do
path = temporary_directory + 'Test.pch'
@gen.save_as(path)
path.read.should == <<-EOS.strip_heredoc
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import <BananaTree/BananaTree.h>
EOS
end
end
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Generator::XCConfig do
before do
specification = fixture_spec('banana-lib/BananaLib.podspec')
@pod = Pod::LocalPod.new(specification, config.sandbox, :ios)
@generator = Generator::XCConfig.new(config.sandbox, [@pod], './Pods')
end
it "returns the sandbox" do
@generator.sandbox.class.should == Sandbox
end
it "returns the pods" do
@generator.pods.should == [@pod]
end
it "returns the path of the pods root relative to the user project" do
@generator.relative_pods_root.should == './Pods'
end
#-----------------------------------------------------------------------#
before do
@xcconfig = @generator.generate
end
it "generates the xcconfig" do
@xcconfig.class.should == Xcodeproj::Config
end
it "sets to always search the user paths" do
@xcconfig.to_hash['ALWAYS_SEARCH_USER_PATHS'].should == 'YES'
end
it "configures the project to load all members that implement Objective-c classes or categories from the static library" do
@xcconfig.to_hash['OTHER_LDFLAGS'].should == '-ObjC'
end
it 'does not add the -fobjc-arc to OTHER_LDFLAGS by default as Xcode 4.3.2 does not support it' do
@xcconfig.to_hash['OTHER_LDFLAGS'].should.not.include("-fobjc-arc")
end
it 'adds the -fobjc-arc to OTHER_LDFLAGS if any pods require arc (to support non-ARC projects on iOS 4.0)' do
@generator.set_arc_compatibility_flag = true
@pod.top_specification.stubs(:requires_arc).returns(true)
@xcconfig = @generator.generate
@xcconfig.to_hash['OTHER_LDFLAGS'].split(" ").should.include("-fobjc-arc")
end
it "sets the PODS_ROOT build variable" do
@xcconfig.to_hash['PODS_ROOT'].should.not == nil
end
it "redirects the HEADERS_SEARCH_PATHS to the pod variable" do
@xcconfig.to_hash['HEADER_SEARCH_PATHS'].should =='${PODS_HEADERS_SEARCH_PATHS}'
end
it "sets the PODS_HEADERS_SEARCH_PATHS to look for the public headers as it is overridden in the Pods project" do
@xcconfig.to_hash['PODS_HEADERS_SEARCH_PATHS'].should =='${PODS_PUBLIC_HEADERS_SEARCH_PATHS}'
end
it 'adds the sandbox build headers search paths to the xcconfig, with quotes' do
expected = "\"#{config.sandbox.build_headers.search_paths.join('" "')}\""
@xcconfig.to_hash['PODS_BUILD_HEADERS_SEARCH_PATHS'].should == expected
end
it 'adds the sandbox public headers search paths to the xcconfig, with quotes' do
expected = "\"#{config.sandbox.public_headers.search_paths.join('" "')}\""
@xcconfig.to_hash['PODS_PUBLIC_HEADERS_SEARCH_PATHS'].should == expected
end
#-----------------------------------------------------------------------#
it 'returns the settings that the pods project needs to override' do
Generator::XCConfig.pods_project_settings.should.not.be.nil
end
it 'overrides the relative path of the pods root in the Pods project' do
Generator::XCConfig.pods_project_settings['PODS_ROOT'].should == '${SRCROOT}'
end
it 'overrides the headers search path of the pods project to the build headers folder' do
expected = '${PODS_BUILD_HEADERS_SEARCH_PATHS}'
Generator::XCConfig.pods_project_settings['PODS_HEADERS_SEARCH_PATHS'].should == expected
end
#-----------------------------------------------------------------------#
extend SpecHelper::TemporaryDirectory
it "saves the xcconfig" do
path = temporary_directory + 'sample.xcconfig'
@generator.save_as(path)
generated = Xcodeproj::Config.new(path)
generated.class.should == Xcodeproj::Config
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
TMP_POD_ROOT = ROOT + "tmp" + "podroot" unless defined? TMP_POD_ROOT
describe TargetInstaller = Pod::Installer::TargetInstaller do
describe "In general" do
before do
@podfile = Pod::Podfile.new do
platform :ios
end
@target_definition = @podfile.target_definitions[:default]
@project = Pod::Project.new(config.sandbox)
@specification = fixture_spec('banana-lib/BananaLib.podspec')
@pods = [Pod::LocalPod.new(@specification, config.sandbox, Pod::Platform.ios)]
@installer = TargetInstaller.new(@project, @target_definition, @pods,)
end
describe Pod::Installer::TargetInstaller do
extend SpecHelper::TemporaryDirectory
it "returns the project" do
@installer.project.should == @project
end
before do
@podfile = Pod::Podfile.new do
platform :ios
xcodeproj 'dummy'
it "returns the target_definition" do
@installer.target_definition.should == @target_definition
end
@target_definition = @podfile.target_definitions[:default]
@project = Pod::Project.new
@project.new_group('Targets Support Files')
it "returns the pods of the target definition" do
@installer.pods.should == @pods
end
end
@installer = Pod::Installer::TargetInstaller.new(@podfile, @project, @target_definition)
describe "Installation" do
extend SpecHelper::TemporaryDirectory
before do
@podfile = Pod::Podfile.new do
platform :ios
xcodeproj 'dummy'
end
@target_definition = @podfile.target_definitions[:default]
@project = Pod::Project.new(config.sandbox)
specification = fixture_spec('banana-lib/BananaLib.podspec')
@pod = Pod::LocalPod.new(specification, config.sandbox, @target_definition.platform)
@installer = TargetInstaller.new(@project, @target_definition, [@pod])
specification.prefix_header_contents = '#import "BlocksKit.h"'
@pod.stubs(:root).returns(Pathname.new(fixture('banana-lib')))
end
@sandbox = Pod::Sandbox.new(TMP_POD_ROOT)
FileUtils.cp_r(fixture('banana-lib'), TMP_POD_ROOT + 'BananaLib')
@specification = fixture_spec('banana-lib/BananaLib.podspec')
@pods = [Pod::LocalPod.new(@specification, @sandbox, Pod::Platform.ios)]
end
def do_install!
# Prevent raise for missing dummy project.
Pathname.any_instance.stubs(:exist?).returns(true)
@pod.add_file_references_to_project(@project)
@installer.install!
end
def do_install!
@pods.each { |pod| pod.add_file_references_to_project(@project) }
@installer.install!(@pods, @sandbox)
end
it 'adds a new static library target to the project' do
do_install!
@project.targets.count.should == 1
@project.targets.first.name.should == @target_definition.label
end
it 'adds a new static library target to the project' do
do_install!
@project.targets.count.should == 1
@project.targets.first.name.should == @target_definition.label
end
it 'adds the source files of each pod to the target of the Pod library' do
do_install!
names = @installer.target.source_build_phase.files.map { |bf| bf.file_ref.name }
names.should == [ "Banana.m" ]
end
it "adds the user's build configurations to the target" do
@project.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'AppStore' => :release, 'Test' => :debug }
do_install!
@project.targets.first.build_configurations.map(&:name).sort.should == %w{ AppStore Debug Release Test }
end
it "adds file references for the support files of the target" do
do_install!
group = @project.support_files_group['Pods']
group.children.map(&:display_name).sort.should == [
"Pods-prefix.pch", "Pods-resources.sh", "Pods.xcconfig"
]
end
it 'adds each pod to the static library target' do
@pods[0].expects(:add_build_files_to_target)
do_install!
end
#--------------------------------------#
# TODO: move to project
# it 'tells each pod to link its headers' do
# @pods[0].expects(:link_headers)
# do_install!
# end
it "adds the settings of the xcconfig that need to be overridden to the target" do
do_install!
build_configuration = @project.targets.first.build_configurations
build_settings = build_configuration.first.build_settings
Pod::Generator::XCConfig.pods_project_settings.each do |key, value|
build_settings[key].should == value
end
end
it 'adds the sandbox header search paths to the xcconfig, with quotes' do
do_install!
@installer.xcconfig.to_hash['PODS_BUILD_HEADERS_SEARCH_PATHS'].should.include("\"#{@sandbox.build_headers.search_paths.join('" "')}\"")
end
it "adds the user's build configurations to the target" do
@project.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'AppStore' => :release, 'Test' => :debug }
do_install!
@project.targets.first.build_configurations.map(&:name).sort.should == %w{ AppStore Debug Release Test }
end
it 'does not add the -fobjc-arc to OTHER_LDFLAGS by default as Xcode 4.3.2 does not support it' do
do_install!
@installer.xcconfig.to_hash['OTHER_LDFLAGS'].split(" ").should.not.include("-fobjc-arc")
end
it "does not enable the GCC_WARN_INHIBIT_ALL_WARNINGS flag by default" do
do_install!
@installer.target.build_configurations.each do |config|
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should.be.nil
end
end
it 'adds the -fobjc-arc to OTHER_LDFLAGS if any pods require arc (to support non-ARC projects on iOS 4.0)' do
@podfile.stubs(:set_arc_compatibility_flag? => true)
@specification.stubs(:requires_arc).returns(true)
do_install!
@installer.xcconfig.to_hash['OTHER_LDFLAGS'].split(" ").should.include("-fobjc-arc")
end
it "enables the GCC_WARN_INHIBIT_ALL_WARNINGS flag" do
@podfile.inhibit_all_warnings!
do_install!
@installer.target.build_configurations.each do |config|
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should == 'YES'
end
end
it "does not enable the GCC_WARN_INHIBIT_ALL_WARNINGS flag by default" do
do_install!
@installer.target.build_configurations.each do |config|
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should == 'NO'
it "creates and xcconfig file" do
do_install!
file = config.sandbox.root + 'Pods.xcconfig'
xcconfig = Xcodeproj::Config.new(file)
xcconfig.to_hash['PODS_ROOT'].should == '${SRCROOT}/Pods'
end
end
it "enables the GCC_WARN_INHIBIT_ALL_WARNINGS flag" do
@podfile.inhibit_all_warnings!
do_install!
@installer.target.build_configurations.each do |config|
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should == 'YES'
it "creates a prefix header, including the contents of the specification's prefix header" do
@pod.top_specification.prefix_header_contents = '#import "BlocksKit.h"'
do_install!
prefix_header = config.sandbox.root + 'Pods-prefix.pch'
prefix_header.read.should == <<-EOS.strip_heredoc
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "BlocksKit.h"
EOS
end
end
it "creates a prefix header, including the contents of the specification's prefix header file" do
do_install!
prefix_header = @sandbox.root + 'Pods.pch'
@installer.save_prefix_header_as(prefix_header, @pods)
prefix_header.read.should == <<-EOS
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import <BananaTree/BananaTree.h>
EOS
end
it "creates a bridge support file" do
Pod::Podfile.any_instance.stubs(:generate_bridge_support? => true)
Pod::Generator::BridgeSupport.any_instance.expects(:save_as).once
do_install!
end
it "creates a prefix header, including the contents of the specification's prefix header" do
do_install!
prefix_header = @sandbox.root + 'Pods.pch'
@specification.prefix_header_contents = '#import "BlocksKit.h"'
@installer.save_prefix_header_as(prefix_header, @pods)
prefix_header.read.should == <<-EOS
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "BlocksKit.h"
EOS
it "creates a create copy resources script" do
do_install!
script = config.sandbox.root + 'Pods-resources.sh'
script.read.should.include?('logo-sidebar.png')
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
describe Pod::Installer::UserProjectIntegrator do
extend SpecHelper::TemporaryDirectory
before do
@sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
config.project_root = @sample_project_path.dirname
sample_project_path = @sample_project_path
@podfile = Pod::Podfile.new do
platform :ios
xcodeproj sample_project_path
pod 'JSONKit'
target :test_runner, :exclusive => true do
link_with 'TestRunner'
pod 'Kiwi'
describe UserProjectIntegrator = Pod::Installer::UserProjectIntegrator do
describe "In general" do
extend SpecHelper::TemporaryDirectory
before do
@sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
sample_project_path = @sample_project_path
@podfile = Pod::Podfile.new do
platform :ios
xcodeproj sample_project_path
pod 'JSONKit'
target :test_runner, :exclusive => true do
link_with 'TestRunner'
pod 'Kiwi'
end
end
@project_root = @sample_project_path.dirname
@pods_project = Pod::Project.new(config.sandbox)
@integrator = UserProjectIntegrator.new(@podfile, @pods_project, @project_root)
@podfile.target_definitions.values.each { |td| @pods_project.add_pod_library(td) }
end
@integrator = Pod::Installer::UserProjectIntegrator.new(@podfile)
end
it "returns the podfile" do
@integrator.podfile.should == @podfile
end
it "returns the path to the workspace from the Podfile" do
@integrator.workspace_path.should == config.project_root + 'SampleProject.xcworkspace'
end
it "returns the pods project" do
@integrator.pods_project.should == @pods_project
end
it "raises if no workspace could be selected" do
@podfile.stubs(:workspace)
lambda { @integrator.workspace_path }.should.raise Pod::Informative
end
it "returns the project root" do
@integrator.project_root.should == @project_root
end
it "returns the path to the Pods.xcodeproj document" do
@integrator.pods_project_path.should == config.project_root + 'Pods/Pods.xcodeproj'
end
it "uses the path of the workspace defined in the podfile" do
path = "a_path"
@podfile.workspace path
@integrator.workspace_path.should == path + ".xcworkspace"
end
it "returns a Pod::Installer::UserProjectIntegrator::Target for each target definition in the Podfile" do
@integrator.target_integrators.map(&:target_definition).should == @podfile.target_definitions.values
end
it "names the workspace after the user project if needed" do
@integrator.workspace_path.should == @sample_project_path.dirname + 'SampleProject.xcworkspace'
end
before do
@target_integrator = @integrator.target_integrators.first
end
it "raises if no workspace could be selected" do
@integrator.expects(:user_project_paths).returns(%w[ project1 project2 ])
lambda { @integrator.workspace_path }.should.raise Pod::Informative
end
it "returns the the user's project, that contains the target, from the Podfile" do
@target_integrator.user_project_path.should == @sample_project_path
@target_integrator.user_project.should == Xcodeproj::Project.new(@sample_project_path)
it "returns the paths of the user projects" do
@integrator.user_project_paths.should == [ @sample_project_path ]
end
end
it "raises if no project could be selected" do
@target_integrator.target_definition.user_project.stubs(:path).returns(nil)
lambda { @target_integrator.user_project_path }.should.raise Pod::Informative
end
#--------------------------------------#
it "raises if the project path doesn't exist" do
@target_integrator.target_definition.user_project.path.stubs(:exist?).returns(false)
lambda { @target_integrator.user_project_path }.should.raise Pod::Informative
end
describe "Integration" do
extend SpecHelper::TemporaryDirectory
it "uses the target with the same name if the name is different from `:default'" do
target_integrator = @integrator.target_integrators[1]
target_integrator.target_definition.stubs(:name).returns('TestRunner')
target_integrator.target_definition.stubs(:link_with).returns(nil)
target_integrator.targets.first.name.should == 'TestRunner'
end
before do
@sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
sample_project_path = @sample_project_path
@podfile = Pod::Podfile.new do
platform :ios
xcodeproj sample_project_path
pod 'JSONKit'
end
@project_root = @sample_project_path.dirname
@pods_project = Pod::Project.new(config.sandbox)
@integrator = UserProjectIntegrator.new(@podfile, @pods_project, @project_root)
@podfile.target_definitions.values.each { |td| @pods_project.add_pod_library(td) }
@integrator.integrate!
end
it "adds the project being integrated to the workspace" do
workspace = Xcodeproj::Workspace.new_from_xcworkspace(@integrator.workspace_path)
workspace.projpaths.sort.should == %w{ ../Pods/Pods.xcodeproj SampleProject.xcodeproj }
end
it "it raises if it can't find a target with the same name" do
target_integrator = @integrator.target_integrators.find { |ti| ti.target_definition.name == :test_runner }
target_integrator.target_definition.stubs(:link_with).returns(nil)
lambda { target_integrator.targets }.should.raise Pod::Informative
it "adds the Pods project to the workspace" do
workspace = Xcodeproj::Workspace.new_from_xcworkspace(@integrator.workspace_path)
workspace.projpaths.find { |path| path =~ /Pods.xcodeproj/ }.should.not.be.nil
end
xit "warns if the podfile does not contain any dependency" do
Pod::UI.output.should.include?('The Podfile does not contain any dependency')
end
end
end
#-----------------------------------------------------------------------------#
describe TargetIntegrator = Pod::Installer::UserProjectIntegrator::TargetIntegrator do
describe "In general" do
before do
@sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
sample_project_path = @sample_project_path
@podfile = Pod::Podfile.new do
platform :ios
xcodeproj sample_project_path
end
@pods_project = Pod::Project.new(config.sandbox)
@podfile.target_definitions.values.each { |td| @pods_project.add_pod_library(td) }
@library = @pods_project.libraries.first
@target_integrator = TargetIntegrator.new(@library)
end
it "returns the Pod library that should be integrated" do
@target_integrator.library.should == @library
end
it "returns the user's project, that contains the target, from the Podfile" do
@target_integrator.user_project.should == Xcodeproj::Project.new(@sample_project_path)
end
it "uses the first target in the user's project if no explicit target is specified" do
target_integrator = @integrator.target_integrators.find { |ti| ti.target_definition.name == :default }
target_integrator.target_definition.stubs(:link_with).returns(nil)
target_integrator.targets.should == [Xcodeproj::Project.new(@sample_project_path).targets.first]
end
# before do
# sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
# config.project_root = sample_project_path.dirname
# @podfile = Pod::Podfile.new do
# platform :ios
# xcodeproj sample_project_path, 'Test' => :debug
# link_with 'SampleProject' # this is an app target!
# pod 'JSONKit'
#
# target :test_runner, :exclusive => true do
# link_with 'TestRunner'
# pod 'Kiwi'
# end
# end
# @sample_project_path = sample_project_path
# pods_project = Pod::Project.new(config.sandbox)
# @integrator = Pod::Installer::UserProjectIntegrator.new(@podfile, pods_project)
# @integrator.integrate!
# @sample_project = Xcodeproj::Project.new(sample_project_path)
# end
#
# it 'adds references to the Pods static libraries to the Frameworks group' do
# @sample_project["Frameworks/libPods.a"].should.not == nil
# @sample_project["Frameworks/libPods-test_runner.a"].should.not == nil
# end
#
#
# it 'sets the Pods xcconfig as the base config for each build configuration' do
# @podfile.target_definitions.each do |_, definition|
# target = @sample_project.targets.find { |t| t.name == definition.link_with.first }
# xcconfig_file = @sample_project.files.find { |f| f.path == "Pods/#{definition.xcconfig_name}" }
# target.build_configurations.each do |config|
# config.base_configuration_reference.should == xcconfig_file
# end
# end
# end
#
# it 'adds the libPods static library to the "Link binary with libraries" build phase of each target' do
# @podfile.target_definitions.each do |_, definition|
# target = @sample_project.targets.find { |t| t.name == definition.link_with.first }
# target.frameworks_build_phase.files.find { |f| f.file_ref.name == definition.lib_name}.should.not == nil
# end
# end
#
# it 'adds a Copy Pods Resources build phase to each target' do
# @podfile.target_definitions.each do |_, definition|
# target = @sample_project.targets.find { |t| t.name == definition.link_with.first }
# phase = target.shell_script_build_phases.find { |bp| bp.name == "Copy Pods Resources" }
# phase.shell_script.strip.should == "\"${SRCROOT}/Pods/#{definition.copy_resources_script_name}\""
# end
# end
#
# before do
# # Reset the cached TargetIntegrator#targets lists.
# @integrator.instance_variable_set(:@target_integrators, nil)
# end
#
# it "only tries to integrate Pods libraries into user targets that haven't been integrated yet" do
# app_integrator = @integrator.target_integrators.find { |t| t.target_definition.name == :default }
# test_runner_integrator = @integrator.target_integrators.find { |t| t.target_definition.name == :test_runner }
#
# # Remove libPods.a from the app target. But don't do it through TargetIntegrator#targets,
# # as it will return only those that still need integration.
# app_target = app_integrator.user_project.targets.find { |t| t.name == 'SampleProject' }
# app_target.frameworks_build_phase.files.last.remove_from_project
#
# # Set the name of the libPods.a PBXFileReference to `nil` to ensure the file’s basename
# # is used instead. Not sure yet what makes it so that the name is nil in the first place.
# test_target = test_runner_integrator.user_project.targets.find { |t| t.name == 'TestRunner' }
# build_file = test_target.frameworks_build_phase.files.last
# build_file.file_ref.name = nil
#
# app_integrator.expects(:add_pods_library)
# test_runner_integrator.expects(:add_pods_library).never
#
# @integrator.integrate!
# end
#
# it "does not even try to save the project if none of the target integrators had any work to do" do
# @integrator.target_integrators.first.user_project.expects(:save_as).never
# @integrator.integrate!
# end
# end
#
end
require File.expand_path('../../spec_helper', __FILE__)
# it 'tells each pod to link its headers' do
# @pods[0].expects(:link_headers)
# do_install!
# end
module Pod
describe Installer do
before do
@sandbox = temporary_sandbox
config.repos_dir = fixture('spec-repos')
config.project_pods_root = @sandbox.root
FileUtils.cp_r(fixture('integration/JSONKit'), @sandbox.root + 'JSONKit')
end
describe "by default" do
before do
describe "Concerning pre-installation computations" do
def generate_podfile(pods = ['JSONKit'])
podfile = Podfile.new do
platform :ios
xcodeproj 'MyProject'
pod 'JSONKit'
end
@sandbox = temporary_sandbox
config.project_pods_root = temporary_sandbox.root
FileUtils.cp_r(fixture('integration/JSONKit'), @sandbox.root + 'JSONKit')
resolver = Resolver.new(podfile, nil, @sandbox)
@installer = Installer.new(resolver)
target_installer = @installer.target_installers.first
target_installer.generate_xcconfig([], @sandbox)
@xcconfig = target_installer.xcconfig.to_hash
end
it "sets the header search paths where installed Pod headers can be found" do
@xcconfig['ALWAYS_SEARCH_USER_PATHS'].should == 'YES'
end
it "configures the project to load all members that implement Objective-c classes or categories from the static library" do
@xcconfig['OTHER_LDFLAGS'].should == '-ObjC'
end
it "sets the PODS_ROOT build variable" do
@xcconfig['PODS_ROOT'].should.not == nil
end
it "generates a BridgeSupport metadata file from all the pod headers" do
podfile = Podfile.new do
platform :osx
pod 'ASIHTTPRequest'
pods.each { |name| pod name }
end
FileUtils.cp_r(fixture('integration/ASIHTTPRequest'), @sandbox.root + 'ASIHTTPRequest')
resolver = Resolver.new(podfile, nil, @sandbox)
installer = Installer.new(resolver)
pods = installer.specifications.map do |spec|
LocalPod.new(spec, installer.sandbox, podfile.target_definitions[:default].platform)
end
expected = pods.map { |pod| pod.header_files }.flatten.map { |header| config.project_pods_root + header }
expected.size.should > 0
installer.target_installers.first.bridge_support_generator_for(pods, installer.sandbox).headers.should == expected
end
# @sandbox = temporary_sandbox
# config.project_pods_root = temporary_sandbox.root
# FileUtils.cp_r(fixture('integration/JSONKit'), @sandbox.root + 'JSONKit')
it "omits empty target definitions" do
podfile = Podfile.new do
platform :ios
target :not_empty do
pod 'JSONKit'
end
end
resolver = Resolver.new(podfile, nil, @sandbox)
installer = Installer.new(resolver)
installer.target_installers.map(&:target_definition).map(&:name).should == [:not_empty]
end
it "adds the user's build configurations" do
path = fixture('SampleProject/SampleProject.xcodeproj')
podfile = Podfile.new do
platform :ios
xcodeproj path, 'App Store' => :release
end
resolver = Resolver.new(podfile, nil, Sandbox.new(fixture('integration')))
installer = Installer.new(resolver)
installer.project.build_configurations.map(&:name).sort.should == ['App Store', 'Debug', 'Release', 'Test']
# resolver = Resolver.new(podfile, nil, @sandbox)
# @installer = Installer.new(resolver)
# target_installer = @installer.target_installers.first
# target_installer.install
end
it "forces downloading of the `bleeding edge' version of a pod" do
podfile = Podfile.new do
platform :ios
pod 'JSONKit', :head
end
resolver = Resolver.new(podfile, nil, Sandbox.new(fixture('integration')))
installer = Installer.new(resolver)
pod = installer.pods.first
downloader = stub('Downloader')
Downloader.stubs(:for_pod).returns(downloader)
downloader.expects(:download_head)
installer.download_pod(pod)
def generate_lockfile
hash = {}
hash['PODS'] = []
hash['DEPENDENCIES'] = []
hash['SPEC CHECKSUMS'] = []
hash['COCOAPODS'] = Pod::VERSION
Pod::Lockfile.new(hash)
end
end
describe "concerning multiple pods originating form the same spec" do
extend SpecHelper::Fixture
before do
sandbox = temporary_sandbox
Pod::Config.instance.project_pods_root = sandbox.root
Pod::Config.instance.integrate_targets = false
podspec_path = fixture('integration/Reachability/Reachability.podspec')
podfile = Podfile.new do
platform :osx
pod 'Reachability', :podspec => podspec_path.to_s
target :debug do
pod 'Reachability'
end
end
resolver = Resolver.new(podfile, nil, sandbox)
@installer = Installer.new(resolver)
end
# The double installation leads to a bug when different subspecs are
# activated for the same pod. We need a way to install a pod only
# once while keeping all the files of the actived subspecs.
#
# LocalPodSet?
#
it "installs the pods only once" do
LocalPod.any_instance.stubs(:downloaded?).returns(false)
Downloader::GitHub.any_instance.expects(:download).once
podfile = generate_podfile
lockfile = generate_lockfile
@installer = Installer.new(@sandbox, podfile, lockfile)
Project::Library.any_instance.stubs(:user_project_path).returns(config.project_root + 'test.xcodeproj')
# TODO
config.integrate_targets = false
@installer.install!
end
it "cleans a pod only once" do
LocalPod.any_instance.expects(:clean!).once
@installer.install!
end
# describe "#analyze" do
# it "doesn't affects creates changes in the file system" do
# end
# end
it "adds the files of the pod to the Pods project only once" do
@installer.install!
group = @installer.project.pods.groups.find { |g| g.name == 'Reachability' }
group.files.map(&:name).sort.should == ["Reachability.h", "Reachability.m"]
# <<<<<<< HEAD
it "marks all pods as added if there is no lockfile" do
@installer.pods_added_from_the_lockfile.should == ['JSONKit']
# =======
# it "adds the files of the pod to the Pods project only once" do
# @installer.install!
# group = @installer.project.pods.groups.find { |g| g.name == 'Reachability' }
# group.files.map(&:name).sort.should == ["Reachability.h", "Reachability.m"]
# >>>>>>> core-extraction
end
it "lists a pod only once" do
reachability_pods = @installer.pods.map(&:to_s).select { |s| s.include?('Reachability') }
reachability_pods.count.should == 1
end
end
describe "concerning namespacing" do
extend SpecHelper::Fixture
before do
sandbox = temporary_sandbox
Pod::Config.instance.project_pods_root = sandbox.root
Pod::Config.instance.integrate_targets = false
podspec_path = fixture('chameleon')
podfile = Podfile.new do
platform :osx
pod 'Chameleon', :local => podspec_path
end
resolver = Resolver.new(podfile, nil, sandbox)
@installer = Installer.new(resolver)
end
it "namespaces local pods" do
@installer.install!
group = @installer.project['Local Pods']
group.groups.map(&:name).sort.should == %w| Chameleon |
end
it "namespaces subspecs" do
@installer.install!
group = @installer.project['Local Pods/Chameleon']
group.groups.map(&:name).sort.should == %w| AVFoundation AssetsLibrary MediaPlayer MessageUI StoreKit UIKit |
end
end
# describe "by default" do
# before do
# podfile = Podfile.new do
# platform :ios
# xcodeproj 'MyProject'
# pod 'JSONKit'
# end
# @sandbox = temporary_sandbox
# config.project_pods_root = temporary_sandbox.root
# FileUtils.cp_r(fixture('integration/JSONKit'), @sandbox.root + 'JSONKit')
# @installer = Installer.new(@sandbox, podfile)
# target_installer = @installer.target_installers.first
# target_installer.generate_xcconfig([], @sandbox)
# @xcconfig = target_installer.xcconfig.to_hash
# end
# it "generates a BridgeSupport metadata file from all the pod headers" do
# podfile = Podfile.new do
# platform :osx
# pod 'ASIHTTPRequest'
# end
# FileUtils.cp_r(fixture('integration/ASIHTTPRequest'), @sandbox.root + 'ASIHTTPRequest')
# installer = Installer.new(@sandbox, podfile)
# pods = installer.specifications.map do |spec|
# LocalPod.new(spec, installer.sandbox, podfile.target_definitions[:default].platform)
# end
# expected = pods.map { |pod| pod.header_files }.flatten.map { |header| config.project_pods_root + header }
# expected.size.should > 0
# installer.target_installers.first.bridge_support_generator_for(pods, installer.sandbox).headers.should == expected
# end
# it "omits empty target definitions" do
# podfile = Podfile.new do
# platform :ios
# target :not_empty do
# pod 'JSONKit'
# end
# end
# installer = Installer.new(@sandbox, podfile)
# installer.target_installers.map(&:target_definition).map(&:name).should == [:not_empty]
# end
# it "adds the user's build configurations" do
# path = fixture('SampleProject/SampleProject.xcodeproj')
# podfile = Podfile.new do
# platform :ios
# xcodeproj path, 'App Store' => :release
# end
# installer = Installer.new(@sandbox, podfile)
# installer.project.build_configurations.map(&:name).sort.should == ['App Store', 'Debug', 'Release', 'Test']
# end
# it "forces downloading of the `bleeding edge' version of a pod" do
# podfile = Podfile.new do
# platform :ios
# pod 'JSONKit', :head
# end
# installer = Installer.new(@sandbox, podfile)
# pod = installer.pods.first
# downloader = stub('Downloader')
# Downloader.stubs(:for_pod).returns(downloader)
# downloader.expects(:download_head)
# installer.download_pod(pod)
# end
# end
# describe "concerning multiple pods originating form the same spec" do
# extend SpecHelper::Fixture
# before do
# sandbox = temporary_sandbox
# Pod::Config.instance.project_pods_root = sandbox.root
# Pod::Config.instance.integrate_targets = false
# podspec_path = fixture('integration/Reachability/Reachability.podspec')
# podfile = Podfile.new do
# platform :osx
# pod 'Reachability', :podspec => podspec_path.to_s
# target :debug do
# pod 'Reachability'
# end
# end
# resolver = Resolver.new(podfile, nil, sandbox)
# @installer = Installer.new(resolver)
# end
# # The double installation leads to a bug when different subspecs are
# # activated for the same pod. We need a way to install a pod only
# # once while keeping all the files of the actived subspecs.
# #
# # LocalPodSet?
# #
# it "installs the pods only once" do
# LocalPod.any_instance.stubs(:downloaded?).returns(false)
# Downloader::GitHub.any_instance.expects(:download).once
# @installer.install!
# end
# it "cleans a pod only once" do
# LocalPod.any_instance.expects(:clean!).once
# @installer.install!
# end
# it "adds the files of the pod to the Pods project only once" do
# @installer.install!
# group = @installer.project.pods.groups.find { |g| g.name == 'Reachability' }
# group.files.map(&:name).should == ["Reachability.h", "Reachability.m"]
# end
# it "lists a pod only once" do
# reachability_pods = @installer.pods.map(&:to_s).select { |s| s.include?('Reachability') }
# reachability_pods.count.should == 1
# end
# end
# describe "concerning namespacing" do
# extend SpecHelper::Fixture
# before do
# sandbox = temporary_sandbox
# Pod::Config.instance.project_pods_root = sandbox.root
# Pod::Config.instance.integrate_targets = false
# podspec_path = fixture('chameleon')
# podfile = Podfile.new do
# platform :osx
# pod 'Chameleon', :local => podspec_path
# end
# resolver = Resolver.new(podfile, nil, sandbox)
# @installer = Installer.new(resolver)
# end
# it "namespaces local pods" do
# @installer.install!
# group = @installer.project['Local Pods']
# group.groups.map(&:name).sort.should == %w| Chameleon |
# end
# it "namespaces subspecs" do
# @installer.install!
# group = @installer.project['Local Pods/Chameleon']
# group.groups.map(&:name).sort.should == %w| AVFoundation AssetsLibrary MediaPlayer MessageUI StoreKit UIKit |
# end
# end
end
end
......@@ -81,14 +81,14 @@ describe Pod::LocalPod do
end
it "can add it's source files to an Xcode project target" do
project = Pod::Project.new
project = Pod::Project.new(@sandbox)
@pod.add_file_references_to_project(project)
project['Pods/BananaLib/Banana.h'].path.should == "BananaLib/Classes/Banana.h"
project['Pods/BananaLib/Banana.m'].path.should == "BananaLib/Classes/Banana.m"
end
it "can add it's source files to a target with any specially configured compiler flags" do
project = Pod::Project.new
project = Pod::Project.new(@sandbox)
target = project.new_target(:static, 'Pods', :ios)
@pod.top_specification.compiler_flags = '-d some_flag'
@pod.add_file_references_to_project(project)
......@@ -113,6 +113,8 @@ describe Pod::LocalPod do
end
end
#---------------------------------------------------------------------------#
describe "with installed source and multiple subspecs" do
def assert_array_equals(expected, computed)
......
require File.expand_path('../../spec_helper', __FILE__)
describe "Pod::Lockfile" do
describe "In general" do
extend SpecHelper::TemporaryDirectory
def sample
text = <<-LOCKFILE.strip_heredoc
PODS:
- BananaLib (1.0):
- monkey (< 1.0.9, ~> 1.0.1)
- monkey (1.0.8)
DEPENDENCIES:
- BananaLib (~> 1.0)
SPEC CHECKSUMS:
BananaLib: !binary |-
MjI2Y2RkMTJkMzBhMWU4ZWM4OGM1ZmRkZWU2MDcwZDg0YTI1MGZjMQ==
COCOAPODS: #{Pod::VERSION}
LOCKFILE
end
def podfile
Pod::Podfile.new do
platform :ios
pod 'BananaLib', '~>1.0'
end
end
def specs
specs = [
Pod::Specification.from_file(fixture('banana-lib/BananaLib.podspec')),
Pod::Specification.new do |s|
s.name = "monkey"
s.version = "1.0.8"
end
]
specs.each { |s| s.activate_platform(:ios) }
specs
end
def tmp_path
temporary_directory + 'Podfile.lock'
end
it "loads from a hash" do
lockfile = Pod::Lockfile.new(YAML.load(sample))
lockfile.to_hash.should == YAML.load(sample)
end
it "loads from a file" do
File.open(tmp_path, 'w') {|f| f.write(sample) }
lockfile = Pod::Lockfile.from_file(tmp_path)
lockfile.defined_in_file.should == tmp_path
lockfile.to_hash.should == YAML.load(sample)
end
it "can be generated from a Podfile and a list of Specifications" do
lockfile = Pod::Lockfile.generate(podfile, specs)
lockfile.to_hash.should == YAML.load(sample)
end
before do
@lockfile = Pod::Lockfile.generate(podfile, specs)
end
it "generates a valid YAML representation" do
YAML.load(@lockfile.to_yaml).should == YAML.load(sample)
end
it "generates a valid Dictionary representation" do
@lockfile.to_hash.should == YAML.load(sample)
end
it "returns the list of the installed pods" do
@lockfile.pods_names.should == %w| BananaLib monkey |
end
it "returns the versions of the installed pods" do
@lockfile.pods_versions.should == {
"BananaLib" => Pod::Version.new("1.0"),
"monkey" => Pod::Version.new("1.0.8")
}
end
it "serializes correctly `:head' dependencies" do
podfile = Pod::Podfile.new do
platform :ios
pod 'BananaLib', :head
end
specs = [
Pod::Specification.new do |s|
s.name = "BananaLib"
s.version = "1.0"
end,
Pod::Specification.new do |s|
s.name = "monkey"
s.version = "1.0.8"
end
]
specs.each { |s| s.activate_platform(:ios) }
lockfile = Pod::Lockfile.generate(podfile, specs)
lockfile.to_hash["DEPENDENCIES"][0].should == "BananaLib (HEAD)"
end
it "serializes correctly external dependencies" do
podfile = Pod::Podfile.new do
platform :ios
pod 'BananaLib', { :git => "www.example.com", :tag => '1.0' }
end
specs = [
Pod::Specification.new do |s|
s.name = "BananaLib"
s.version = "1.0"
end,
Pod::Specification.new do |s|
s.name = "monkey"
s.version = "1.0.8"
end
]
specs.each { |s| s.activate_platform(:ios) }
lockfile = Pod::Lockfile.generate(podfile, specs)
lockfile.to_hash["DEPENDENCIES"][0].should == "BananaLib (from `www.example.com', tag `1.0')"
lockfile.to_hash["EXTERNAL SOURCES"]["BananaLib"].should == { :git => "www.example.com", :tag => '1.0' }
end
it "creates a dependency from a string" do
d = @lockfile.dependency_from_string("BananaLib (1.0)")
d.name.should == "BananaLib"
d.requirement.should =~ Pod::Version.new("1.0")
d.head.should.be.nil
d.external_source.should.be.nil
end
it "creates a head dependency from a string" do
d = @lockfile.dependency_from_string("BananaLib (HEAD)")
d.name.should == "BananaLib"
d.requirement.should.be.none?
d.head.should.be.true
d.external_source.should.be.nil
end
it "creates an external dependency from a string" do
podfile = Pod::Podfile.new do
platform :ios
pod 'BananaLib', { :git => "www.example.com", :tag => '1.0' }
end
lockfile = Pod::Lockfile.generate(podfile, [])
d = lockfile.dependency_from_string("BananaLib (from `www.example.com', tag `1.0')")
d.name.should == "BananaLib"
d.requirement.should.be.none?
d.external?.should.be.true
d.external_source.description.should == "from `www.example.com', tag `1.0'"
end
end
describe "Concerning initialization from a file" do
extend SpecHelper::TemporaryDirectory
it "returns nil if it can't find the initialization file" do
lockfile = Pod::Lockfile.from_file(temporary_directory + 'Podfile.lock_not_existing')
lockfile.should == nil
end
end
describe "Concerning the identification of changes in the Podfile" do
before do
@podfile = Pod::Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit'
end
@specs = [
Pod::Specification.new do |s|
s.name = "BlocksKit"
s.version = "1.0.0"
end,
Pod::Specification.new do |s|
s.name = "JSONKit"
s.version = "1.4"
end ]
@specs.each { |s| s.activate_platform(:ios) }
@lockfile = Pod::Lockfile.generate(@podfile, @specs)
end
it "detects an added Pod" do
podfile = Pod::Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit'
pod 'TTTAttributedLabel'
end
@lockfile.detect_changes_with_podfile(podfile).should == {
:changed=>[],
:removed=>[],
:unchanged=>["BlocksKit", "JSONKit"],
:added=>["TTTAttributedLabel"]
}
end
it "detects an removed Pod" do
podfile = Pod::Podfile.new do
platform :ios
pod 'BlocksKit'
end
@lockfile.detect_changes_with_podfile(podfile).should == {
:changed=>[],
:removed=>["JSONKit"],
:unchanged=>["BlocksKit"],
:added=>[]
}
end
it "detects Pods whose version changed" do
podfile = Pod::Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit', "> 1.4"
end
@lockfile.detect_changes_with_podfile(podfile).should == {
:changed=>["JSONKit"],
:removed=>[],
:unchanged=>["BlocksKit"],
:added=>[]
}
end
it "it doesn't mark a changed Pods whose version changed but is still compatible with the Podfile" do
podfile = Pod::Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit', "> 1.0"
end
@lockfile.detect_changes_with_podfile(podfile).should == {
:changed=>[],
:removed=>[],
:unchanged=>["BlocksKit", "JSONKit"],
:added=>[]
}
end
it "detects Pods whose external source changed" do
podfile = Pod::Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit', :git => "example1.com"
end
@lockfile.detect_changes_with_podfile(podfile).should == {
:changed=>["JSONKit"],
:removed=>[],
:unchanged=>["BlocksKit"],
:added=>[]
}
@lockfile = Pod::Lockfile.generate(podfile, @specs)
podfile = Pod::Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit', :git => "example2.com"
end
@lockfile.detect_changes_with_podfile(podfile).should == {
:changed=>["JSONKit"],
:removed=>[],
:unchanged=>["BlocksKit"],
:added=>[]
}
end
it "detects Pods whose head state changed" do
podfile = Pod::Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit', :head
end
@lockfile.detect_changes_with_podfile(podfile).should == {
:changed=>["JSONKit"],
:removed=>[],
:unchanged=>["BlocksKit"],
:added=>[]
}
@specs = [
Pod::Specification.new do |s|
s.name = "BlocksKit"
s.version = "1.0.0"
end,
Pod::Specification.new do |s|
s.name = "JSONKit"
s.version = "1.4"
s.version.head = true
end ]
@specs.each { |s| s.activate_platform(:ios) }
@lockfile = Pod::Lockfile.generate(podfile, @specs)
podfile = Pod::Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit'
end
@lockfile.detect_changes_with_podfile(podfile).should == {
:changed=>["JSONKit"],
:removed=>[],
:unchanged=>["BlocksKit"],
:added=>[]
}
end
end
end
require File.expand_path('../../spec_helper', __FILE__)
describe Pod::Platform do
describe "by default" do
it "returns a new Platform instance" do
Pod::Platform.ios.should == Pod::Platform.new(:ios)
Pod::Platform.osx.should == Pod::Platform.new(:osx)
end
it "can be initialized from another platform" do
platform = Pod::Platform.new(:ios)
new = Pod::Platform.new(platform)
new.should == platform
end
before do
@platform = Pod::Platform.ios
end
it "exposes it's symbolic name" do
@platform.name.should == :ios
end
it "can be compared for equality with another platform with the same symbolic name" do
@platform.should == Pod::Platform.new(:ios)
end
it "can be compared for equality with another platform with the same symbolic name and the same deployment target" do
@platform.should.not == Pod::Platform.new(:ios, '4.0')
Pod::Platform.new(:ios, '4.0').should == Pod::Platform.new(:ios, '4.0')
end
it "can be compared for equality with a matching symbolic name (backwards compatibility reasons)" do
@platform.should == :ios
end
it "presents an accurate string representation" do
@platform.to_s.should == "iOS"
Pod::Platform.new(:osx).to_s.should == 'OS X'
Pod::Platform.new(:ios, '5.0.0').to_s.should == 'iOS 5.0.0'
Pod::Platform.new(:osx, '10.7').to_s.should == 'OS X 10.7'
end
it "uses it's name as it's symbold version" do
@platform.to_sym.should == :ios
end
it "allows to specify the deployment target on initialization" do
p = Pod::Platform.new(:ios, '4.0.0')
p.deployment_target.should == Pod::Version.new('4.0.0')
end
it "allows to specify the deployment target in a hash on initialization (backwards compatibility from 0.6)" do
p = Pod::Platform.new(:ios, { :deployment_target => '4.0.0' })
p.deployment_target.should == Pod::Version.new('4.0.0')
end
end
describe "regarding supporting platforms" do
it "supports platforms with the same operating system" do
p1 = Pod::Platform.new(:ios)
p2 = Pod::Platform.new(:ios)
p1.should.supports?(p2)
p1 = Pod::Platform.new(:osx)
p2 = Pod::Platform.new(:osx)
p1.should.supports?(p2)
end
it "supports a platform with a lower or equal deployment_target" do
p1 = Pod::Platform.new(:ios, '5.0')
p2 = Pod::Platform.new(:ios, '4.0')
p1.should.supports?(p1)
p1.should.supports?(p2)
p2.should.not.supports?(p1)
end
it "doesn't supports a platform with a different operating system" do
p1 = Pod::Platform.new(:ios)
p2 = Pod::Platform.new(:osx)
p1.should.not.supports?(p2)
end
end
end
require File.expand_path('../../spec_helper', __FILE__)
describe "Pod::Podfile" do
it "loads from a file" do
podfile = Pod::Podfile.from_file(fixture('Podfile'))
podfile.defined_in_file.should == fixture('Podfile')
end
it "assigns the platform attribute to the current target" do
podfile = Pod::Podfile.new { platform :ios }
podfile.target_definitions[:default].platform.should == :ios
end
it "provides a default deployment target if not specified" do
podfile = Pod::Podfile.new { platform :ios }
podfile.target_definitions[:default].platform.deployment_target.should == Pod::Version.new('4.3')
end
it "raise error if unsupported platform is supplied" do
lambda {
Pod::Podfile.new { platform :iOS }
}.should.raise Pod::Podfile::Informative
begin
Pod::Podfile.new { platform :iOS }
rescue Pod::Podfile::Informative => e
e.stubs(:podfile_line).returns("./podfile_spec.rb:1")
e.message.should.be =~ /podfile_spec\.rb:1/
end
end
it "adds dependencies" do
podfile = Pod::Podfile.new { pod 'ASIHTTPRequest'; pod 'SSZipArchive', '>= 0.1' }
podfile.dependencies.size.should == 2
podfile.dependency_by_top_level_spec_name('ASIHTTPRequest').should == Pod::Dependency.new('ASIHTTPRequest')
podfile.dependency_by_top_level_spec_name('SSZipArchive').should == Pod::Dependency.new('SSZipArchive', '>= 0.1')
end
it "adds a dependency on a Pod repo outside of a spec repo (the repo is expected to contain a podspec)" do
podfile = Pod::Podfile.new do
pod 'SomeExternalPod', :git => 'GIT-URL', :commit => '1234'
end
dep = podfile.dependency_by_top_level_spec_name('SomeExternalPod')
dep.external_source.params.should == { :git => 'GIT-URL', :commit => '1234' }
end
it "adds a subspec dependency on a Pod repo outside of a spec repo (the repo is expected to contain a podspec)" do
podfile = Pod::Podfile.new do
pod 'MainSpec/FirstSubSpec', :git => 'GIT-URL', :commit => '1234'
end
dep = podfile.dependency_by_top_level_spec_name('MainSpec')
dep.external_source.name.should == 'MainSpec'
end
it "adds a dependency on a library outside of a spec repo (the repo does not need to contain a podspec)" do
podfile = Pod::Podfile.new do
pod 'SomeExternalPod', :podspec => 'http://gist/SomeExternalPod.podspec'
end
dep = podfile.dependency_by_top_level_spec_name('SomeExternalPod')
dep.external_source.params.should == { :podspec => 'http://gist/SomeExternalPod.podspec' }
end
it "adds a dependency on a library by specifying the podspec inline" do
podfile = Pod::Podfile.new do
pod do |s|
s.name = 'SomeExternalPod'
end
end
dep = podfile.dependency_by_top_level_spec_name('SomeExternalPod')
dep.specification.name.should == 'SomeExternalPod'
end
it "specifies that BridgeSupport metadata should be generated" do
Pod::Podfile.new {}.should.not.generate_bridge_support
Pod::Podfile.new { generate_bridge_support! }.should.generate_bridge_support
end
it 'specifies that ARC compatibility flag should be generated' do
Pod::Podfile.new { set_arc_compatibility_flag! }.should.set_arc_compatibility_flag
end
it "stores a block that will be called with the Installer before the target integration" do
yielded = nil
Pod::Podfile.new do
pre_install do |installer|
yielded = installer
end
end.pre_install!(:an_installer)
yielded.should == :an_installer
end
it "stores a block that will be called with the Installer instance once installation is finished (but the project is not written to disk yet)" do
yielded = nil
Pod::Podfile.new do
post_install do |installer|
yielded = installer
end
end.post_install!(:an_installer)
yielded.should == :an_installer
end
it "assumes the xcode project is the only existing project in the root" do
podfile = Pod::Podfile.new do
target(:another_target) {}
end
path = config.project_root + 'MyProject.xcodeproj'
Pathname.expects(:glob).with(config.project_root + '*.xcodeproj').returns([path])
podfile.target_definitions[:default].user_project.path.should == path
podfile.target_definitions[:another_target].user_project.path.should == path
end
it "assumes the basename of the workspace is the same as the default target's project basename" do
path = config.project_root + 'MyProject.xcodeproj'
Pathname.expects(:glob).with(config.project_root + '*.xcodeproj').returns([path])
Pod::Podfile.new {}.workspace.should == config.project_root + 'MyProject.xcworkspace'
Pod::Podfile.new do
xcodeproj 'AnotherProject.xcodeproj'
end.workspace.should == config.project_root + 'AnotherProject.xcworkspace'
end
it "does not base the workspace name on the default target's project if there are multiple projects specified" do
Pod::Podfile.new do
xcodeproj 'MyProject'
target :another_target do
xcodeproj 'AnotherProject'
end
end.workspace.should == nil
end
it "specifies the Xcode workspace to use" do
Pod::Podfile.new do
xcodeproj 'AnotherProject'
workspace 'MyWorkspace'
end.workspace.should == config.project_root + 'MyWorkspace.xcworkspace'
Pod::Podfile.new do
xcodeproj 'AnotherProject'
workspace 'MyWorkspace.xcworkspace'
end.workspace.should == config.project_root + 'MyWorkspace.xcworkspace'
end
describe "concerning targets (dependency groups)" do
it "returns wether or not a target has any dependencies" do
Pod::Podfile.new do
end.target_definitions[:default].should.be.empty
Pod::Podfile.new do
pod 'JSONKit'
end.target_definitions[:default].should.not.be.empty
end
before do
@podfile = Pod::Podfile.new do
platform :ios
xcodeproj 'iOS Project', 'iOS App Store' => :release, 'Test' => :debug
target :debug do
pod 'SSZipArchive'
end
target :test, :exclusive => true do
link_with 'TestRunner'
inhibit_all_warnings!
pod 'JSONKit'
target :subtarget do
pod 'Reachability'
end
end
target :osx_target do
platform :osx
xcodeproj 'OSX Project.xcodeproj', 'Mac App Store' => :release, 'Test' => :debug
link_with 'OSXTarget'
pod 'ASIHTTPRequest'
target :nested_osx_target do
end
end
pod 'ASIHTTPRequest'
end
end
it "returns all dependencies of all targets combined, which is used during resolving to ensure compatible dependencies" do
@podfile.dependencies.map(&:name).sort.should == %w{ ASIHTTPRequest JSONKit Reachability SSZipArchive }
end
it "adds dependencies outside of any explicit target block to the default target" do
target = @podfile.target_definitions[:default]
target.label.should == 'Pods'
target.dependencies.should == [Pod::Dependency.new('ASIHTTPRequest')]
end
it "adds dependencies of the outer target to non-exclusive targets" do
target = @podfile.target_definitions[:debug]
target.label.should == 'Pods-debug'
target.dependencies.sort_by(&:name).should == [
Pod::Dependency.new('ASIHTTPRequest'),
Pod::Dependency.new('SSZipArchive')
]
end
it "does not add dependencies of the outer target to exclusive targets" do
target = @podfile.target_definitions[:test]
target.label.should == 'Pods-test'
target.dependencies.should == [Pod::Dependency.new('JSONKit')]
end
it "adds dependencies of the outer target to nested targets" do
target = @podfile.target_definitions[:subtarget]
target.label.should == 'Pods-test-subtarget'
target.dependencies.should == [Pod::Dependency.new('Reachability'), Pod::Dependency.new('JSONKit')]
end
it "returns the Xcode project that contains the target to link with" do
[:default, :debug, :test, :subtarget].each do |target_name|
target = @podfile.target_definitions[target_name]
target.user_project.path.should == config.project_root + 'iOS Project.xcodeproj'
end
[:osx_target, :nested_osx_target].each do |target_name|
target = @podfile.target_definitions[target_name]
target.user_project.path.should == config.project_root + 'OSX Project.xcodeproj'
end
end
it "returns a Xcode project found in the working dir when no explicit project is specified" do
xcodeproj1 = config.project_root + '1.xcodeproj'
Pathname.expects(:glob).with(config.project_root + '*.xcodeproj').returns([xcodeproj1])
Pod::Podfile::UserProject.new.path.should == xcodeproj1
end
it "returns `nil' if more than one Xcode project was found in the working when no explicit project is specified" do
xcodeproj1, xcodeproj2 = config.project_root + '1.xcodeproj', config.project_root + '2.xcodeproj'
Pathname.expects(:glob).with(config.project_root + '*.xcodeproj').returns([xcodeproj1, xcodeproj2])
Pod::Podfile::UserProject.new.path.should == nil
end
it "leaves the name of the target, to link with, to be automatically resolved" do
target = @podfile.target_definitions[:default]
target.link_with.should == nil
end
it "returns the names of the explicit targets to link with" do
target = @podfile.target_definitions[:test]
target.link_with.should == ['TestRunner']
end
it "returns the name of the Pods static library" do
@podfile.target_definitions[:default].lib_name.should == 'libPods.a'
@podfile.target_definitions[:test].lib_name.should == 'libPods-test.a'
end
it "returns the name of the xcconfig file for the target" do
@podfile.target_definitions[:default].xcconfig_name.should == 'Pods.xcconfig'
@podfile.target_definitions[:default].xcconfig_relative_path.should == 'Pods/Pods.xcconfig'
@podfile.target_definitions[:test].xcconfig_name.should == 'Pods-test.xcconfig'
@podfile.target_definitions[:test].xcconfig_relative_path.should == 'Pods/Pods-test.xcconfig'
end
it "returns the name of the 'copy resources script' file for the target" do
@podfile.target_definitions[:default].copy_resources_script_name.should == 'Pods-resources.sh'
@podfile.target_definitions[:default].copy_resources_script_relative_path.should == '${SRCROOT}/Pods/Pods-resources.sh'
@podfile.target_definitions[:test].copy_resources_script_name.should == 'Pods-test-resources.sh'
@podfile.target_definitions[:test].copy_resources_script_relative_path.should == '${SRCROOT}/Pods/Pods-test-resources.sh'
end
it "returns the name of the 'prefix header' file for the target" do
@podfile.target_definitions[:default].prefix_header_name.should == 'Pods-prefix.pch'
@podfile.target_definitions[:test].prefix_header_name.should == 'Pods-test-prefix.pch'
end
it "returns the name of the BridgeSupport file for the target" do
@podfile.target_definitions[:default].bridge_support_name.should == 'Pods.bridgesupport'
@podfile.target_definitions[:test].bridge_support_name.should == 'Pods-test.bridgesupport'
end
it "returns the platform of the target" do
@podfile.target_definitions[:default].platform.should == :ios
@podfile.target_definitions[:test].platform.should == :ios
@podfile.target_definitions[:osx_target].platform.should == :osx
end
it "assigs a deployment target to the platforms if not specified" do
@podfile.target_definitions[:default].platform.deployment_target.to_s.should == '4.3'
@podfile.target_definitions[:test].platform.deployment_target.to_s.should == '4.3'
@podfile.target_definitions[:osx_target].platform.deployment_target.to_s.should == '10.6'
end
it "autmatically marks a target as exclusive if the parent platform doesn't match" do
@podfile.target_definitions[:osx_target].should.be.exclusive
@podfile.target_definitions[:nested_osx_target].should.not.be.exclusive
end
it "returns the specified configurations and wether it should be based on a debug or a release build" do
Pod::Podfile::UserProject.any_instance.stubs(:project)
all = { 'Release' => :release, 'Debug' => :debug, 'Test' => :debug }
@podfile.target_definitions[:default].user_project.build_configurations.should == all.merge('iOS App Store' => :release)
@podfile.target_definitions[:test].user_project.build_configurations.should == all.merge('iOS App Store' => :release)
@podfile.target_definitions[:osx_target].user_project.build_configurations.should == all.merge('Mac App Store' => :release)
@podfile.target_definitions[:nested_osx_target].user_project.build_configurations.should == all.merge('Mac App Store' => :release)
@podfile.user_build_configurations.should == all.merge('iOS App Store' => :release, 'Mac App Store' => :release)
end
it "defaults, for unspecified configurations, to a release build" do
project = Pod::Podfile::UserProject.new(fixture('SampleProject/SampleProject.xcodeproj'), 'Test' => :debug)
project.build_configurations.should == { 'Release' => :release, 'Debug' => :debug, 'Test' => :debug, 'App Store' => :release }
end
it "specifies that the inhibit all warnings flag should be added to the target's build settings" do
@podfile.target_definitions[:default].should.not.inhibit_all_warnings
@podfile.target_definitions[:test].should.inhibit_all_warnings
@podfile.target_definitions[:subtarget].should.inhibit_all_warnings
end
describe "with an Xcode project that's not in the project_root" do
before do
@target_definition = @podfile.target_definitions[:default]
@target_definition.user_project.stubs(:path).returns(config.project_root + 'subdir/iOS Project.xcodeproj')
end
it "returns the $(PODS_ROOT) relative to the project's $(SRCROOT)" do
@target_definition.relative_pods_root.should == '${SRCROOT}/../Pods'
end
it "simply returns the $(PODS_ROOT) path if no xcodeproj file is available and doesn't needs to integrate" do
config.integrate_targets.should.equal true
config.integrate_targets = false
@target_definition.relative_pods_root.should == '${SRCROOT}/../Pods'
@target_definition.user_project.stubs(:path).returns(nil)
@target_definition.relative_pods_root.should == '${SRCROOT}/Pods'
config.integrate_targets = true
end
it "returns the xcconfig file path relative to the project's $(SRCROOT)" do
@target_definition.xcconfig_relative_path.should == '../Pods/Pods.xcconfig'
end
it "returns the 'copy resources script' path relative to the project's $(SRCROOT)" do
@target_definition.copy_resources_script_relative_path.should == '${SRCROOT}/../Pods/Pods-resources.sh'
end
end
end
describe "concerning the podspec method" do
xit "it can use use the dependencies of a podspec" do
end
xit "it allows to specify the name of a podspec" do
end
xit "it allows to specify the path of a podspec" do
end
end
describe "concerning validations" do
it "raises if it should integrate and can't find an xcodeproj" do
config.integrate_targets = true
target_definition = Pod::Podfile.new {}.target_definitions[:default]
target_definition.user_project.stubs(:path).returns(nil)
exception = lambda {
target_definition.relative_pods_root
}.should.raise Pod::Informative
exception.message.should.include "Xcode project"
end
end
end
require File.expand_path('../../spec_helper', __FILE__)
describe 'Pod::Project' do
before do
@project = Pod::Project.new
end
describe Pod::Project do
describe "In general" do
before do
@project = Pod::Project.new(config.sandbox)
end
it "adds the Podfile configured as a Ruby file" do
f = @project['Podfile']
f.name.should == 'Podfile'
f.source_tree.should == 'SOURCE_ROOT'
f.xc_language_specification_identifier.should == 'xcode.lang.ruby'
f.path.should == '../Podfile'
end
it "returns the sandbox used for the project" do
@project.sandbox.should == config.sandbox
end
it "adds a group to the `Pods' group" do
group = @project.add_spec_group('JSONKit', @project.pods)
@project.pods.children.should.include?(group)
g = @project['Pods/JSONKit']
g.name.should == 'JSONKit'
g.children.should.be.empty?
end
it "creates the support file group on initialization" do
@project.support_files_group.name.should == 'Targets Support Files'
end
it "namespaces subspecs in groups" do
group = @project.add_spec_group('JSONKit/Subspec', @project.pods)
@project.pods.groups.find { |g| g.name == 'JSONKit' }.children.should.include?(group)
g = @project['Pods/JSONKit/Subspec']
g.name.should == 'Subspec'
g.children.should.be.empty?
end
it "returns its path" do
@project.path.should == config.sandbox.project_path
end
# it "creates a copy build header phase which will copy headers to a specified path" do
# @project.targets.new
# phase = @project.targets.first.copy_files_build_phases.new_pod_dir("SomePod", "Path/To/Source")
# find_object({
# 'isa' => 'PBXCopyFilesBuildPhase',
# 'dstPath' => 'Pods/Path/To/Source',
# 'name' => 'Copy SomePod Public Headers'
# }).should.not == nil
# @project.targets.first.build_phases.should.include phase
# end
it "adds build configurations named after every configuration across all of the user's projects" do
@project.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'Test' => :debug, 'AppStore' => :release }
@project.build_configurations.map(&:name).sort.should == %w{ AppStore Debug Release Test }
end
it "returns the `Pods` group" do
@project.pods.name.should == 'Pods'
end
it "adds build configurations named after every configuration across all of the user's projects to a target" do
@project.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'Test' => :debug, 'AppStore' => :release }
target = @project.add_pod_target('SomeTarget', Pod::Platform.ios)
target.build_settings('Test')["VALIDATE_PRODUCT"].should == nil
target.build_settings('AppStore')["VALIDATE_PRODUCT"].should == "YES"
end
it "returns the `Local Pods` group" do
@project.local_pods.name.should == 'Local Pods'
end
describe "concerning its :ios targets" do
it "sets VALIDATE_PRODUCT to YES for the Release configuration" do
target = Pod::Project.new.add_pod_target('Pods', Pod::Platform.ios)
target.build_settings('Release')["VALIDATE_PRODUCT"].should == "YES"
it "adds a group for a specification" do
group = @project.add_spec_group('JSONKit', @project.pods)
@project.pods.children.should.include?(group)
g = @project['Pods/JSONKit']
g.name.should == 'JSONKit'
g.children.should.be.empty?
end
it "namespaces subspecs in groups" do
group = @project.add_spec_group('JSONKit/Subspec', @project.pods)
@project.pods.groups.find { |g| g.name == 'JSONKit' }.children.should.include?(group)
g = @project['Pods/JSONKit/Subspec']
g.name.should == 'Subspec'
g.children.should.be.empty?
end
it "adds the Podfile configured as a Ruby file" do
@project.add_podfile(config.project_podfile)
f = @project['Podfile']
f.name.should == 'Podfile'
f.source_tree.should == 'SOURCE_ROOT'
f.xc_language_specification_identifier.should == 'xcode.lang.ruby'
f.path.should == '../Podfile'
end
it "adds build configurations named after every configuration across all of the user's projects" do
@project.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'Test' => :debug, 'AppStore' => :release }
@project.build_configurations.map(&:name).sort.should == %w{ AppStore Debug Release Test }
end
end
describe "concerning its :ios targets with a deployment target" do
describe "Libraries" do
before do
@project = Pod::Project.new
@project = Pod::Project.new(config.sandbox)
podfile = Pod::Podfile.new do
platform :ios, '4.3'
pod 'JSONKit'
end
@target_definition = podfile.target_definitions.values.first
end
it "sets ARCHS to 'armv6 armv7' for both configurations if the deployment target is less than 4.3" do
target = @project.add_pod_target('Pods', Pod::Platform.new(:ios, :deployment_target => "4.0"))
target.build_settings('Debug')["ARCHS"].should == "armv6 armv7"
target.build_settings('Release')["ARCHS"].should == "armv6 armv7"
it "adds build configurations named after every configuration across all of the user's projects to a target" do
@project.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'Test' => :debug, 'AppStore' => :release }
library = @project.add_pod_library(@target_definition)
target = library.target
target.build_settings('Test')["VALIDATE_PRODUCT"].should == nil
target.build_settings('AppStore')["VALIDATE_PRODUCT"].should == "YES"
end
target = @project.add_pod_target('Pods', Pod::Platform.new(:ios, :deployment_target => "4.1"))
it "sets ARCHS to 'armv6 armv7' for both configurations if the deployment target is less than 4.3 for iOS targets" do
@target_definition.platform = Pod::Platform.new(:ios, '4.2')
library = @project.add_pod_library(@target_definition)
target = library.target
target.build_settings('Debug')["ARCHS"].should == "armv6 armv7"
target.build_settings('Release')["ARCHS"].should == "armv6 armv7"
end
target = @project.add_pod_target('Pods', Pod::Platform.new(:ios, :deployment_target => "4.2"))
target.build_settings('Debug')["ARCHS"].should == "armv6 armv7"
target.build_settings('Release')["ARCHS"].should == "armv6 armv7"
before do
@lib = @project.add_pod_library(@target_definition)
@target = @lib.target
end
it "uses standard ARCHs if deployment target is 4.3 or above" do
target = @project.add_pod_target('Pods', Pod::Platform.new(:ios, :deployment_target => "4.3"))
target.build_settings('Debug')["ARCHS"].should == "$(ARCHS_STANDARD_32_BIT)"
target.build_settings('Release')["ARCHS"].should == "$(ARCHS_STANDARD_32_BIT)"
@target.build_settings('Debug')["ARCHS"].should == "$(ARCHS_STANDARD_32_BIT)"
@target.build_settings('Release')["ARCHS"].should == "$(ARCHS_STANDARD_32_BIT)"
end
it "sets VALIDATE_PRODUCT to YES for the Release configuration for iOS targets" do
@lib.target.build_settings('Release')["VALIDATE_PRODUCT"].should == "YES"
end
target = @project.add_pod_target('Pods', Pod::Platform.new(:ios, :deployment_target => "4.4"))
target.build_settings('Debug')["ARCHS"].should == "$(ARCHS_STANDARD_32_BIT)"
target.build_settings('Release')["ARCHS"].should == "$(ARCHS_STANDARD_32_BIT)"
it "sets IPHONEOS_DEPLOYMENT_TARGET for iOS targets" do
@target.build_settings('Debug')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.3"
@target.build_settings('Release')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.3"
end
it "sets IPHONEOS_DEPLOYMENT_TARGET for both configurations" do
target = @project.add_pod_target('Pods', Pod::Platform.new(:ios))
target.build_settings('Debug')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.3"
target.build_settings('Release')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.3"
it "returns the added libraries" do
@project.libraries.should == [ @lib ]
end
end
end
#-----------------------------------------------------------------------------#
describe Pod::Project::Library do
describe "In general" do
before do
project = Pod::Project.new(config.sandbox)
podfile = Pod::Podfile.new do
platform :ios
pod 'JSONKit'
end
@target_definition = podfile.target_definitions.values.first
@lib = project.add_pod_library(@target_definition)
end
it "returns the target_definition that generated it" do
@lib.target_definition.should == @target_definition
end
target = @project.add_pod_target('Pods', Pod::Platform.new(:ios, :deployment_target => "4.0"))
target.build_settings('Debug')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.0"
target.build_settings('Release')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.0"
it "returns it target in the Pods project" do
@lib.target.name.should == 'Pods'
end
it "returns the label of the target definition" do
@lib.label.should == 'Pods'
end
end
#---------------------------------------#
describe "User project" do
before do
user_project_path = fixture('SampleProject/SampleProject.xcodeproj')
project = Pod::Project.new(config.sandbox)
podfile = Pod::Podfile.new do
platform :ios
xcodeproj user_project_path
pod 'JSONKit'
end
@target_definition = podfile.target_definitions.values.first
@lib = project.add_pod_library(@target_definition)
end
it "returns the user project path" do
path = fixture('SampleProject/SampleProject.xcodeproj')
@lib.user_project_path.should == path
end
it "raises if no project could be selected" do
@target_definition.stubs(:user_project_path).returns(nil)
Pathname.any_instance.stubs(:exist?).returns(true)
lambda { @lib.user_project_path }.should.raise Pod::Informative
end
it "raises if the project path doesn't exist" do
Pathname.any_instance.stubs(:exist?).returns(false)
lambda { @lib.user_project_path }.should.raise Pod::Informative
end
it "returns the user project" do
@lib.user_project.class.should == Xcodeproj::Project
end
it "returns the user targets associated with the target definition" do
@lib.user_targets.all? { |t| t.isa == 'PBXNativeTarget' }.should.be.true
@lib.user_targets.map(&:name).should == [ 'SampleProject' ]
end
it "uses the targets specified to link with by the target definition" do
@target_definition.stubs(:link_with).returns(['TestRunner'])
@target_definition.stubs(:name).returns('NON-EXISTING')
@lib.user_targets.first.name.should == 'TestRunner'
end
it "it raises if it can't find any target specified to link with by the target definition" do
@target_definition.stubs(:link_with).returns(['NON-EXISTING'])
lambda { @lib.user_targets }.should.raise Pod::Informative
end
it "uses the target with the same name if the target definition name is different from `:default'" do
@target_definition.stubs(:name).returns('TestRunner')
@lib.user_targets.first.name.should == 'TestRunner'
end
it "it raises if it can't find a target with the same name of the target definition" do
@target_definition.stubs(:name).returns('NON-EXISTING')
lambda { @lib.user_targets }.should.raise Pod::Informative
end
it "uses the first target in the user's project if no explicit target is specified for the default target definition" do
project = Xcodeproj::Project.new(@lib.user_project_path)
@lib.user_targets.should == [ project.targets.first ]
end
end
#---------------------------------------#
describe "TargetInstaller & UserProjectIntegrator" do
before do
user_project_path = fixture('SampleProject/SampleProject.xcodeproj')
project = Pod::Project.new(config.sandbox)
podfile = Pod::Podfile.new do
platform :ios
xcodeproj user_project_path
pod 'JSONKit'
end
@target_definition = podfile.target_definitions.values.first
@lib = project.add_pod_library(@target_definition)
end
#---------------------------------------#
it "returns the name of its product" do
@lib.name.should == 'libPods.a'
end
it "returns it Pods project" do
@lib.project.path.should == config.sandbox.project_path
end
#---------------------------------------#
it "stores the xcconfig" do
@lib.xcconfig = Xcodeproj::Config.new({'PODS_ROOT' => '${SRCROOT}'})
@lib.xcconfig.to_hash['PODS_ROOT'].should == '${SRCROOT}'
end
it "returns the xcconfig name" do
@lib.xcconfig_name.should == 'Pods.xcconfig'
end
it "returns the absolute path of the xcconfig file" do
@lib.xcconfig_path.to_s.should.include?('Pods/Pods.xcconfig')
end
it "returns the path of the xcconfig file relative to the user project" do
@lib.xcconfig_relative_path.should == '../../../tmp/Pods/Pods.xcconfig'
end
it "returns the resources script name" do
@lib.copy_resources_script_name.should == 'Pods-resources.sh'
end
it "returns the absolute path of the resources script" do
@lib.copy_resources_script_path.to_s.should.include?('Pods/Pods-resources.sh')
end
it "returns the path of the resources script relative to the user project" do
@lib.copy_resources_script_relative_path.should == '${SRCROOT}/../../../tmp/Pods/Pods-resources.sh'
end
it "returns the prefix header file name" do
@lib.prefix_header_name.should == 'Pods-prefix.pch'
end
it "returns the absolute path of the prefix header file" do
@lib.prefix_header_path.to_s.should.include?('Pods/Pods-prefix.pch')
end
it "returns the bridge support file name" do
@lib.bridge_support_name.should == 'Pods.bridgesupport'
end
it "returns the absolute path of the bridge support file" do
@lib.bridge_support_path.to_s.should.include?('Pods/Pods.bridgesupport')
end
end
end
......@@ -2,433 +2,439 @@ require File.expand_path('../../spec_helper', __FILE__)
module Pod
describe Resolver do
before do
config.repos_dir = fixture('spec-repos')
@podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
describe "In general" do
before do
config.repos_dir = fixture('spec-repos')
@podfile = Podfile.new do
platform :ios
pod 'BlocksKit', '1.5.2'
end
locked_deps = [Dependency.new('BlocksKit', '1.5.2')]
@resolver = Resolver.new(config.sandbox, @podfile, locked_deps)
end
@resolver = Resolver.new(@podfile, nil, stub('sandbox'))
end
it "holds the context state, such as cached specification sets" do
@resolver.resolve
@resolver.cached_sets.values.sort_by(&:name).should == [
Source.search_by_name('A2DynamicDelegate').first,
Source.search_by_name('BlocksKit').first,
Source.search_by_name('libffi').first
].sort_by(&:name)
end
it "returns all specs needed for the dependency" do
specs = @resolver.resolve.values.flatten
specs.map(&:class).uniq.should == [Specification]
specs.map(&:name).sort.should == %w{ A2DynamicDelegate BlocksKit libffi }
end
it "does not raise if all dependencies match the platform of the root spec (Podfile)" do
@podfile.platform :ios
lambda { @resolver.resolve }.should.not.raise
@podfile.platform :osx
lambda { @resolver.resolve }.should.not.raise
end
it "raises once any of the dependencies does not match the platform of its podfile target" do
set = Source.search_by_name('BlocksKit').first
@resolver.cached_sets['BlocksKit'] = set
def set.stub_platform=(platform); @stubbed_platform = platform; end
def set.specification; spec = super; spec.platform = @stubbed_platform; spec; end
@podfile.platform :ios
set.stub_platform = :ios
lambda { @resolver.resolve }.should.not.raise
set.stub_platform = :osx
lambda { @resolver.resolve }.should.raise Informative
@podfile.platform :osx
set.stub_platform = :osx
lambda { @resolver.resolve }.should.not.raise
set.stub_platform = :ios
lambda { @resolver.resolve }.should.raise Informative
end
it "raises once any of the dependencies does not have a deployment_target compatible with its podfile target" do
set = Source.search_by_name('BlocksKit').first
@resolver.cached_sets['BlocksKit'] = set
@podfile.platform :ios, "4.0"
Specification.any_instance.stubs(:available_platforms).returns([ Platform.new(:ios, '4.0'), Platform.new(:osx, '10.7') ])
lambda { @resolver.resolve }.should.not.raise
Specification.any_instance.stubs(:available_platforms).returns([ Platform.new(:ios, '5.0'), Platform.new(:osx, '10.7') ])
lambda { @resolver.resolve }.should.raise Informative
end
it "resolves subspecs" do
@podfile = Podfile.new do
platform :ios
pod 'RestKit/Network'
pod 'RestKit/ObjectMapping/XML'
it "returns the sandbox" do
@resolver.sandbox.should == config.sandbox
end
resolver = Resolver.new(@podfile, nil, stub('sandbox'))
resolver.resolve.values.flatten.map(&:name).sort.should == %w{
FileMD5Hash
ISO8601DateFormatter
LibComponentLogging-Core
LibComponentLogging-NSLog
NSData+Base64
RestKit/Network
RestKit/ObjectMapping/XML
SOCKit
XMLReader
cocoa-oauth
}
end
it "includes all the subspecs of a specification node" do
@podfile = Podfile.new do
platform :ios
pod 'RestKit'
it "returns the podfile" do
@resolver.podfile.should == @podfile
end
resolver = Resolver.new(@podfile, nil, stub('sandbox'))
resolver.resolve.values.flatten.map(&:name).sort.should == %w{
FileMD5Hash
ISO8601DateFormatter
JSONKit
LibComponentLogging-Core
LibComponentLogging-NSLog
NSData+Base64
RestKit
RestKit/JSON
RestKit/Network
RestKit/ObjectMapping/CoreData
RestKit/ObjectMapping/JSON
RestKit/UI
SOCKit
cocoa-oauth
}
end
it "it includes only the main subspec of a specification node" do
@podfile = Podfile.new do
platform :ios
pod do |s|
s.name = 'RestKit'
s.version = '0.10.0'
it "returns the locked dependencies" do
@resolver.locked_dependencies.should == [Dependency.new('BlocksKit', '1.5.2')]
end
s.preferred_dependency = 'JSON'
it "allows to specify whether the external sources should be updated against the remote" do
# TODO
@resolver.update_external_specs = true
@resolver.update_external_specs.should.be.true
end
s.subspec 'JSON' do |js|
js.dependency 'RestKit/Network'
js.dependency 'RestKit/UI'
js.dependency 'RestKit/ObjectMapping/JSON'
js.dependency 'RestKit/ObjectMapping/CoreData'
end
#--------------------------------------#
s.subspec 'Network' do |ns|
ns.dependency 'LibComponentLogging-NSLog', '>= 1.0.4'
end
s.subspec 'UI'
s.subspec 'ObjectMapping' do |os|
os.subspec 'JSON'
os.subspec 'XML'
os.subspec 'CoreData'
end
end
it "resolves the specification of the podfile" do
target_definition = @podfile.target_definitions[:default]
specs = @resolver.resolve[target_definition]
specs.map(&:to_s).should == [
"A2DynamicDelegate (2.0.2)",
"BlocksKit (1.5.2)",
"libffi (3.0.11)"
]
end
resolver = Resolver.new(@podfile, nil, stub('sandbox'))
specs = resolver.resolve.values.flatten.map(&:name).sort
specs.should.not.include 'RestKit/ObjectMapping/XML'
specs.should == %w{
LibComponentLogging-Core
LibComponentLogging-NSLog
RestKit
RestKit/JSON
RestKit/Network
RestKit/ObjectMapping/CoreData
RestKit/ObjectMapping/JSON
RestKit/UI
}
end
it "resolves subspecs with external constraints" do
@podfile = Podfile.new do
platform :ios
pod 'MainSpec/FirstSubSpec', :git => 'GIT-URL'
end
spec = Spec.new do |s|
s.name = 'MainSpec'
s.version = '1.2.3'
s.platform = :ios
s.license = 'MIT'
s.author = 'Joe the Plumber'
s.summary = 'A spec with subspecs'
s.source = { :git => '/some/url' }
s.requires_arc = true
s.subspec 'FirstSubSpec' do |fss|
fss.source_files = 'some/file'
fss.subspec 'SecondSubSpec'
end
it "returns the resolved specifications grouped by target definition" do
@resolver.resolve
target_definition = @podfile.target_definitions[:default]
specs = @resolver.specs_by_target[target_definition]
specs.map(&:to_s).should == [
"A2DynamicDelegate (2.0.2)",
"BlocksKit (1.5.2)",
"libffi (3.0.11)"
]
end
@podfile.dependencies.first.external_source.stubs(:specification_from_sandbox).returns(spec)
resolver = Resolver.new(@podfile, nil, stub('sandbox'))
resolver.resolve.values.flatten.map(&:name).sort.should == %w{ MainSpec/FirstSubSpec MainSpec/FirstSubSpec/SecondSubSpec }
end
it "marks a specification's version to be a `bleeding edge' version" do
podfile = Podfile.new do
platform :ios
pod 'FileMD5Hash'
pod 'JSONKit', :head
it "returns all the resolved specifications" do
@resolver.resolve
@resolver.specs.map(&:class).uniq.should == [Specification]
@resolver.specs.map(&:to_s).should == [
"A2DynamicDelegate (2.0.2)",
"BlocksKit (1.5.2)",
"libffi (3.0.11)"
]
end
resolver = Resolver.new(podfile, nil, stub('sandbox'))
filemd5hash, jsonkit = resolver.resolve.values.first.sort_by(&:name)
filemd5hash.version.should.not.be.head
jsonkit.version.should.be.head
end
it "accepts a nil lockfile" do
lambda { Resolver.new(@podfile, nil, stub('sandbox'))}.should.not.raise
end
it "raises if it finds two conflicting dependencies" do
podfile = Podfile.new do
platform :ios
pod 'JSONKit', "1.4"
pod 'JSONKit', "1.5pre"
it "it resolves specifications from external sources" do
podspec = fixture('integration/Reachability/Reachability.podspec')
podfile = Podfile.new do
platform :ios
pod "Reachability", :podspec => podspec
end
resolver = Resolver.new(config.sandbox, podfile)
resolver.resolve
resolver.specs.map(&:to_s).should == ['Reachability (3.0.0)']
end
resolver = Resolver.new(podfile, nil, stub('sandbox'))
lambda {resolver.resolve}.should.raise Informative
end
describe "Concerning Installation mode" do
#-------------------------------------------------------------------------#
describe "Resolution" do
before do
config.repos_dir = fixture('spec-repos')
@podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit'
platform :ios, '6.0'
pod 'BlocksKit', '1.5.2'
end
@specs = [
Specification.new do |s|
s.name = "BlocksKit"
s.version = "1.0.0"
end,
Specification.new do |s|
s.name = "JSONKit"
s.version = "1.4"
end ]
@specs.each { |s| s.activate_platform(:ios) }
@lockfile = Lockfile.generate(@podfile, @specs)
@resolver = Resolver.new(@podfile, @lockfile, stub('sandbox'))
@resolver = Resolver.new(config.sandbox, @podfile)
end
it "doesn't install pods still compatible with the Podfile" do
@resolver.resolve
@resolver.should_install?("BlocksKit").should.be.false
@resolver.should_install?("JSONKit").should.be.false
end
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
it "doesn't include pods removed from the Podfile" do
podfile = Podfile.new { platform :ios; pod 'JSONKit' }
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.resolve.values.flatten.map(&:name).should == %w{ JSONKit }
it "holds the context state, such as cached specification sets" do
@resolver.resolve
cached_sets = @resolver.send(:cached_sets)
cached_sets.values.sort_by(&:name).should == [
Source.search_by_name('A2DynamicDelegate').first,
Source.search_by_name('BlocksKit').first,
Source.search_by_name('libffi').first
].sort_by(&:name)
end
it "reinstalls pods updated in the Podfile" do
podfile = Podfile.new do
platform :ios
pod 'JSONKit', '1.5pre'
pod 'BlocksKit'
end
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
installed = @resolver.resolve.values.flatten.map(&:to_s)
installed.should.include? "BlocksKit (1.0.0)"
installed.should.include? "JSONKit (1.5pre)"
it "raises once any of the dependencies does not match the platform of its podfile target" do
Specification.any_instance.stubs(:available_platforms).returns([Platform.new(:ios, '999')])
e = lambda { @resolver.resolve }.should.raise Informative
e.message.should.match(/platform .* not compatible/)
end
it "installs pods added to the Podfile" do
podfile = Podfile.new do
platform :ios
pod 'JSONKit'
pod 'BlocksKit'
pod 'libPusher' # New pod
end
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
installed = @resolver.resolve.values.flatten.map(&:to_s)
installed.should.include? "libPusher (1.3)"
it "does not raise if all dependencies are supported by the platform of the target definition" do
lambda { @resolver.resolve }.should.not.raise
end
it "handles head pods" do
podfile = Podfile.new do
it "includes all the subspecs of a specification node" do
@podfile = Podfile.new do
platform :ios
pod 'JSONKit', :head # Existing pod switched to head mode
pod 'libPusher', :head # New pod
pod 'RestKit'
end
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.resolve
@resolver.should_install?("JSONKit").should.be.true
@resolver.should_install?("libPusher").should.be.true
resolver = Resolver.new(config.sandbox, @podfile)
resolver.resolve.values.flatten.map(&:name).sort.should == %w{
FileMD5Hash
ISO8601DateFormatter
JSONKit
LibComponentLogging-Core
LibComponentLogging-NSLog
NSData+Base64
RestKit
RestKit/JSON
RestKit/Network
RestKit/ObjectMapping/CoreData
RestKit/ObjectMapping/JSON
RestKit/UI
SOCKit
cocoa-oauth
}
end
it "handles pods from external dependencies" do
podfile = Podfile.new do
it "resolves subspecs with external constraints" do
@podfile = Podfile.new do
platform :ios
pod 'libPusher', :git => 'GIT-URL'
pod 'MainSpec/FirstSubSpec', :git => 'GIT-URL'
end
spec = Spec.new do |s|
s.name = 'libPusher'
s.version = '1.3'
s.name = 'MainSpec'
s.version = '1.2.3'
s.platform = :ios
s.license = 'MIT'
s.author = 'Joe the Plumber'
s.summary = 'A spec with subspecs'
s.source = { :git => '/some/url' }
s.requires_arc = true
s.subspec 'FirstSubSpec' do |fss|
fss.source_files = 'some/file'
fss.subspec 'SecondSubSpec'
end
end
podfile.dependencies.first.external_source.stubs(:specification_from_sandbox).returns(spec)
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.resolve
@resolver.should_install?("JSONKit").should.be.false
ExternalSources::GitSource.any_instance.stubs(:specification).returns(spec)
resolver = Resolver.new(config.sandbox, @podfile)
resolver.resolve.values.flatten.map(&:name).sort.should == %w{ MainSpec/FirstSubSpec MainSpec/FirstSubSpec/SecondSubSpec }
end
it "doesn't updates the repos if there no change in the pods" do
it "marks a specification's version to be a `bleeding edge' version" do
podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit'
pod 'FileMD5Hash'
pod 'JSONKit', :head
end
config.skip_repo_update = false
Command::Repo.any_instance.expects(:run).never
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.resolve
resolver = Resolver.new(config.sandbox, podfile)
filemd5hash, jsonkit = resolver.resolve.values.first.sort_by(&:name)
filemd5hash.version.should.not.be.head
jsonkit.version.should.be.head
end
it "updates the repos if there is a new pod" do
it "raises if it finds two conflicting dependencies" do
podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit'
pod 'libPusher' # New pod
pod 'JSONKit', "1.4"
pod 'JSONKit', "1.5pre"
end
config.skip_repo_update = false
Command::Repo::Update.any_instance.expects(:run).once
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.resolve
resolver = Resolver.new(config.sandbox, podfile)
e = lambda {resolver.resolve}.should.raise Pod::StandardError
e.message.should.match(/already activated version/)
end
it "doesn't update the repos if config indicate to skip it in any case" do
podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit', :head #changed to head
pod 'libPusher' # New pod
end
config.skip_repo_update = true
Command::Repo::Update.any_instance.expects(:run).never
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.resolve
end
xit "is robust against infinite loops" do
it "updates the repos if there is a new pod" do
podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit', :head #changed to head
end
config.skip_repo_update = false
Command::Repo::Update.any_instance.expects(:run).once
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.resolve
end
end
describe "Concerning Update mode" do
before do
config.repos_dir = fixture('spec-repos')
@podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit'
pod 'libPusher'
end
@specs = [
Specification.new do |s|
s.name = "libPusher"
s.version = "1.3"
end,
Specification.new do |s|
s.name = "JSONKit"
s.version = "1.4"
end ]
@specs.each { |s| s.activate_platform(:ios) }
@lockfile = Lockfile.generate(@podfile, @specs)
@resolver = Resolver.new(@podfile, @lockfile, stub('sandbox'))
@resolver.update_mode = true
end
xit "takes into account locked dependencies" do
it "identifies the pods that can be updated" do
installed = @resolver.resolve.values.flatten.map(&:to_s)
installed.should.include? "JSONKit (999.999.999)"
@resolver.should_install?("JSONKit").should.be.true
end
it "respects the constraints of the podfile" do
podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit', '1.4'
end
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.update_mode = true
installed = @resolver.resolve.values.flatten.map(&:to_s)
installed.should.include? "JSONKit (1.4)"
@resolver.should_install?("JSONKit").should.be.false
end
xit "transfers the head state of a dependency to a specification" do
it "installs new pods" do
installed = @resolver.resolve.values.flatten.map(&:to_s)
installed.join(' ').should.include?('BlocksKit')
@resolver.should_install?("BlocksKit").should.be.true
end
it "it always suggests to update pods in head mode" do
podfile = Podfile.new do
platform :ios
pod 'libPusher', :head
end
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.update_mode = true
@resolver.resolve
@resolver.should_install?("libPusher").should.be.true
xit "" do
end
it "always updates the repos even if there is change in the pods" do
podfile = Podfile.new do
platform :ios
pod 'JSONKit'
pod 'libPusher'
end
config.skip_repo_update = false
Command::Repo::Update.any_instance.expects(:run).once
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.update_mode = true
@resolver.resolve
xit "" do
end
# TODO: stub the specification resolution for the sandbox
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
xit "" do
end
end
# describe "Concerning Installation mode" do
# before do
# config.repos_dir = fixture('spec-repos')
# @podfile = Podfile.new do
# platform :ios
# pod 'BlocksKit', '1.5.2'
# pod 'JSONKit'
# end
# @specs = [
# Specification.new do |s|
# s.name = "BlocksKit"
# s.version = "1.5.2"
# end,
# Specification.new do |s|
# s.name = "JSONKit"
# s.version = "1.4"
# end ]
# @specs.each { |s| s.activate_platform(:ios) }
# @resolver = Resolver.new(@podfile, @lockfile, stub('sandbox'))
# end
# it "doesn't install pods still compatible with the Podfile" do
# @resolver.resolve
# @resolver.should_install?("BlocksKit").should.be.false
# @resolver.should_install?("JSONKit").should.be.false
# end
# 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
# it "doesn't include pods removed from the Podfile" do
# podfile = Podfile.new { platform :ios; pod 'JSONKit' }
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# @resolver.resolve.values.flatten.map(&:name).should == %w{ JSONKit }
# end
# it "reinstalls pods updated in the Podfile" do
# podfile = Podfile.new do
# platform :ios
# pod 'JSONKit', '1.5pre'
# pod 'BlocksKit', '1.5.2'
# end
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# installed = @resolver.resolve.values.flatten.map(&:to_s)
# installed.should.include? "BlocksKit (1.5.2)"
# installed.should.include? "JSONKit (1.5pre)"
# end
# it "installs pods added to the Podfile" do
# podfile = Podfile.new do
# platform :ios
# pod 'JSONKit'
# pod 'BlocksKit'
# pod 'libPusher', '1.3' # New pod
# end
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# installed = @resolver.resolve.values.flatten.map(&:to_s)
# installed.should.include? "libPusher (1.3)"
# end
# it "handles head pods" do
# podfile = Podfile.new do
# platform :ios
# pod 'JSONKit', :head # Existing pod switched to head mode
# pod 'libPusher', :head # New pod
# end
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# @resolver.resolve
# @resolver.should_install?("JSONKit").should.be.true
# @resolver.should_install?("libPusher").should.be.true
# end
# it "handles pods from external dependencies" do
# podfile = Podfile.new do
# platform :ios
# pod 'libPusher', :git => 'GIT-URL'
# end
# spec = Spec.new do |s|
# s.name = 'libPusher'
# s.version = '1.3'
# end
# ExternalSources::GitSource.any_instance.stubs(:specification_from_sandbox).returns(spec)
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# @resolver.resolve
# @resolver.should_install?("JSONKit").should.be.false
# end
# it "doesn't updates the repos if there no change in the pods" do
# podfile = Podfile.new do
# platform :ios
# pod 'BlocksKit'
# pod 'JSONKit'
# end
# config.skip_repo_update = false
# Command::Repo.any_instance.expects(:run).never
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# @resolver.resolve
# end
# it "updates the repos if there is a new pod" do
# podfile = Podfile.new do
# platform :ios
# pod 'BlocksKit'
# pod 'JSONKit'
# pod 'libPusher' # New pod
# end
# config.skip_repo_update = false
# Command::Repo::Update.any_instance.expects(:run).once
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# @resolver.resolve
# end
# it "doesn't update the repos if config indicate to skip it in any case" do
# podfile = Podfile.new do
# platform :ios
# pod 'BlocksKit'
# pod 'JSONKit', :head #changed to head
# pod 'libPusher' # New pod
# end
# config.skip_repo_update = true
# Command::Repo::Update.any_instance.expects(:run).never
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# @resolver.resolve
# end
# it "updates the repos if there is a new pod" do
# podfile = Podfile.new do
# platform :ios
# pod 'BlocksKit'
# pod 'JSONKit', :head #changed to head
# end
# config.skip_repo_update = false
# Command::Repo::Update.any_instance.expects(:run).once
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# @resolver.resolve
# end
# end
# describe "Concerning Update mode" do
# before do
# config.repos_dir = fixture('spec-repos')
# previous_podfile = Podfile.new do
# platform :ios
# pod 'JSONKit'
# pod 'libPusher'
# end
# @specs = [
# Specification.new do |s|
# s.name = "libPusher"
# s.version = "1.3"
# end,
# Specification.new do |s|
# s.name = "JSONKit"
# s.version = "1.4"
# end ]
# @specs.each { |s| s.activate_platform(:ios) }
# @lockfile = Lockfile.generate(previous_podfile, @specs)
# @podfile = Podfile.new do
# platform :ios
# pod 'BlocksKit', '1.5.2'
# pod 'JSONKit'
# pod 'libPusher'
# end
# @resolver = Resolver.new(@podfile, @lockfile, stub('sandbox'))
# @resolver.update_mode = true
# end
# it "identifies the pods that can be updated" do
# installed = @resolver.resolve.values.flatten.map(&:to_s)
# installed.should.include? "JSONKit (999.999.999)"
# @resolver.should_install?("JSONKit").should.be.true
# end
# it "respects the constraints of the podfile" do
# podfile = Podfile.new do
# platform :ios
# pod 'BlocksKit', '1.5.2'
# pod 'JSONKit', '1.4'
# end
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# @resolver.update_mode = true
# installed = @resolver.resolve.values.flatten.map(&:to_s)
# installed.should.include? "JSONKit (1.4)"
# @resolver.should_install?("JSONKit").should.be.false
# end
# it "installs new pods" do
# installed = @resolver.resolve.values.flatten.map(&:to_s)
# installed.join(' ').should.include?('BlocksKit')
# @resolver.should_install?("BlocksKit").should.be.true
# end
# it "it always suggests to update pods in head mode" do
# podfile = Podfile.new do
# platform :ios
# pod 'libPusher', :head
# end
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# @resolver.update_mode = true
# @resolver.resolve
# @resolver.should_install?("libPusher").should.be.true
# end
# it "always updates the repos even if there is change in the pods" do
# podfile = Podfile.new do
# platform :ios
# pod 'JSONKit'
# pod 'libPusher'
# end
# config.skip_repo_update = false
# Command::Repo::Update.any_instance.expects(:run).once
# @resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
# @resolver.update_mode = true
# @resolver.resolve
# end
# # TODO: stub the specification resolution for the sandbox
# 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
end
end
end
require File.expand_path('../../spec_helper', __FILE__)
describe "Pod::Source" do
before do
@source = Pod::Source.new(fixture('spec-repos/master'))
end
it "return its name" do
@source.name.should == 'master'
end
it "returns the sets of all the available Pods" do
set_names = @source.pod_sets.map(&:name)
set_names.should.include('JSONKit')
set_names.should.include('Reachability')
end
it "returns the available versions of a Pod" do
@source.versions('Reachability').map(&:to_s).should == %w| 3.0.0 2.0.5 2.0.4 |
end
it "returns the specification of a given version of a Pod" do
spec = @source.specification('Reachability', Pod::Version.new('3.0.0'))
spec.name.should == 'Reachability'
spec.version.should.to_s == '3.0.0'
end
it "properly configures the sources of a set in seach by name" do
source = Pod::Source.new(fixture('spec-repos/test_repo'))
sets = source.search_by_name('monkey', true)
sets.count.should == 1
set = sets.first
set.name.should == 'BananaLib'
set.sources.map(&:name).should == %w| test_repo |
end
describe "Pod::Source::Aggregate" do
# BananaLib is available only in test_repo.
# JSONKit is in test repo has version 1.4 (duplicated) and the 999.999.999.
module Pod
describe Pod::Source do
it "returns all the sources" do
Pod::Source.all.map(&:name).should == %w| master test_repo |
end
it "returns the name of all the available pods" do
pod_names = Pod::Source::Aggregate.new.all_pods
pod_names.should.include('JSONKit')
pod_names.should.include('BananaLib')
end
it "returns all the available sets with the sources configured" do
sets = Pod::Source.all_sets
banana_sets = sets.select{ |set| set.name == 'BananaLib' }
banana_sets.count.should == 1
banana_sets.first.sources.map(&:name).should == %w| test_repo |
json_set = sets.select{ |set| set.name == 'JSONKit' }
json_set.count.should == 1
json_set.first.sources.map(&:name).should == %w| master test_repo |
end
it "searches the sets by dependency" do
dep = Pod::Dependency.new('JSONKit')
set = Pod::Source.search(dep)
set.name.should == 'JSONKit'
set.sources.map(&:name).should == %w| master test_repo |
end
it "searches the sets specifing a dependency on a subspec" do
dep = Pod::Dependency.new('RestKit/JSON')
set = Pod::Source.search(dep)
set.name.should == 'RestKit'
set.sources.map(&:name).should == %w| master |
Source.all.map(&:name).should == %w[master test_repo]
end
it "raises if a specification set can't be found" do
lambda {
dep = Pod::Dependency.new('DoesNotExist')
set = Pod::Source.search(dep)
}.should.raise Pod::Informative
it "returns all the sets" do
Source.all_sets.map(&:name).should.include?('Chameleon')
end
it "raises if a subspec can't be found" do
lambda {
dep = Pod::Dependency.new('RestKit/DoesNotExist')
set = Pod::Source.search(dep)
}.should.raise Pod::Informative
it "searches for the set of a dependency" do
set = Source.search(Dependency.new('Chameleon'))
set.class.should == Pod::Specification::Set
set.name.should == 'Chameleon'
end
it "searches the sets by name" do
sets = Pod::Source.search_by_name('JSONKit')
sets.count.should == 1
set = sets.first
set.name.should == 'JSONKit'
set.sources.map(&:name).should == %w| master test_repo |
it "searches sets by name" do
sets = Source.search_by_name('Chameleon')
sets.all?{ |s| s.class == Pod::Specification::Set}.should.be.true
sets.any?{ |s| s.name == 'Chameleon'}.should.be.true
end
it "properly configures the sources of a set in search by name" do
sets = Pod::Source.search_by_name('BananaLib')
sets.count.should == 1
set = sets.first
set.name.should == 'BananaLib'
set.sources.map(&:name).should == %w| test_repo |
it "can perform a full text search of the sets" do
sets = Source.search_by_name('Drop in sharing', true)
sets.all?{ |s| s.class == Pod::Specification::Set}.should.be.true
sets.any?{ |s| s.name == 'ShareKit'}.should.be.true
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
describe "Pod::Specification::Set" do
describe "In general" do
before do
@source = Pod::Source.new(fixture('spec-repos/master'))
@set = Pod::Spec::Set.new('CocoaLumberjack', @source)
end
it "returns the name of the pod" do
@set.name.should == 'CocoaLumberjack'
end
it "returns the versions available for this pod ordered from highest to lowest" do
@set.versions.should == %w[1.6 1.3.3 1.3.2 1.3.1 1.3 1.2.3 1.2.2 1.2.1 1.2 1.1 1.0].map { |v| Pod::Version.new(v) }
end
it "checks if the dependency of the specification is compatible with existing requirements" do
@set.required_by(Pod::Dependency.new('CocoaLumberjack', '1.2'), 'Spec')
@set.required_by(Pod::Dependency.new('CocoaLumberjack', '< 1.2.1'), 'Spec')
@set.required_by(Pod::Dependency.new('CocoaLumberjack', '> 1.1'), 'Spec')
@set.required_by(Pod::Dependency.new('CocoaLumberjack', '~> 1.2.0'), 'Spec')
@set.required_by(Pod::Dependency.new('CocoaLumberjack'), 'Spec')
lambda {
@set.required_by(Pod::Dependency.new('CocoaLumberjack', '< 1.0' ), 'Spec')
}.should.raise Pod::Informative
end
it "raises if the required version doesn't exist" do
@set.required_by(Pod::Dependency.new('CocoaLumberjack', '< 1.0'), 'Spec')
lambda { @set.required_version }.should.raise Pod::Informative
end
it "can test if it is equal to another set" do
@set.should == Pod::Spec::Set.new('CocoaLumberjack', @source)
@set.should.not == Pod::Spec::Set.new('RestKit', @source)
end
before do
@set.required_by(Pod::Dependency.new('CocoaLumberjack', '< 1.2.1'), 'Spec')
end
it "returns the version required for the dependency" do
@set.required_version.should == Pod::Version.new('1.2')
end
it "returns the specification for the required version" do
@set.specification.should == Pod::Spec.new { |s| s.name = 'CocoaLumberjack'; s.version = '1.2' }
end
it "ignores dotfiles when getting the version directories" do
`touch #{fixture('spec-repos/master/CocoaLumberjack/.DS_Store')}`
lambda { @set.versions }.should.not.raise
end
it "raises if a version is incompatible with the activated version" do
spec = Pod::Dependency.new('CocoaLumberjack', '1.2.1')
lambda { @set.required_by(spec, 'Spec') }.should.raise Pod::Informative
end
end
describe "Concerning multiple sources" do
before do
# JSONKit is in test repo has version 1.4 (duplicated) and the 999.999.999.
@set = Pod::Source.search_by_name('JSONKit').first
end
it "returns all the available versions sorted from biggest to lowest" do
@set.versions.map(&:to_s).should == %w| 999.999.999 1.5pre 1.4 |
end
it "returns all the available versions by source sorted from bigest to lowest" do
hash = {}
@set.versions_by_source.each { |source, versions| hash[source.name] = versions.map(&:to_s) }
hash['master'].should == %w| 1.5pre 1.4 |
hash['test_repo'].should == %w| 999.999.999 1.4 |
hash.keys.sort.should == %w| master test_repo |
end
it "returns the specification from the `master` source for the required version" do
dep = Pod::Dependency.new('JSONKit', '1.5pre')
@set.required_by(dep, 'Spec')
spec = @set.specification
spec.name.should == 'JSONKit'
spec.version.to_s.should == '1.5pre'
spec.defined_in_file.should == fixture('spec-repos/master/JSONKit/1.5pre/JSONKit.podspec')
end
it "returns the specification from `test_repo` source for the required version" do
dep = Pod::Dependency.new('JSONKit', '999.999.999')
@set.required_by(dep, 'Spec')
spec = @set.specification
spec.name.should == 'JSONKit'
spec.version.to_s.should == '999.999.999'
spec.defined_in_file.should == fixture('spec-repos/test_repo/JSONKit/999.999.999/JSONKit.podspec')
end
it "prefers sources by alphabetical order" do
dep = Pod::Dependency.new('JSONKit', '1.4')
@set.required_by(dep, 'Spec')
spec = @set.specification
spec.name.should == 'JSONKit'
spec.version.to_s.should == '1.4'
spec.defined_in_file.should == fixture('spec-repos/master/JSONKit/1.4/JSONKit.podspec')
end
end
end
require File.expand_path('../../spec_helper', __FILE__)
describe "A Pod::Specification loaded from a podspec" do
before do
fixture('banana-lib') # ensure the archive is unpacked
@spec = Pod::Specification.from_file(fixture('banana-lib/BananaLib.podspec'))
end
it "has no parent if it is the top level spec" do
@spec.parent.nil?.should == true
end
it "returns that it's not loaded from a podfile" do
@spec.should.not.be.podfile
end
it "returns the path to the podspec" do
@spec.defined_in_file.should == fixture('banana-lib/BananaLib.podspec')
end
it "returns the directory where the pod should be checked out to" do
@spec.pod_destroot.should == config.project_pods_root + 'BananaLib'
end
it "returns the pod's name" do
@spec.name.should == 'BananaLib'
end
it "returns the pod's version" do
@spec.version.should == Pod::Version.new('1.0')
end
it "returns a list of authors and their email addresses" do
@spec.authors.should == {
'Banana Corp' => nil,
'Monkey Boy' => 'monkey@banana-corp.local'
}
end
it "returns the pod's homepage" do
@spec.homepage.should == 'http://banana-corp.local/banana-lib.html'
end
it "returns the pod's summary" do
@spec.summary.should == 'Chunky bananas!'
end
it "returns the pod's description" do
@spec.description.should == 'Full of chunky bananas.'
end
it "returns the pod's source" do
@spec.source.should == {
:git => 'http://banana-corp.local/banana-lib.git',
:tag => 'v1.0'
}
end
it "returns the pod's source files" do
@spec.activate_platform(:ios).source_files.should == ['Classes/*.{h,m}', 'Vendor']
@spec.activate_platform(:osx).source_files.should == ['Classes/*.{h,m}', 'Vendor']
end
it "returns the pod's dependencies" do
expected = Pod::Dependency.new('monkey', '~> 1.0.1', '< 1.0.9')
@spec.activate_platform(:ios).dependencies.should == [expected]
@spec.activate_platform(:osx).dependencies.should == [expected]
end
it "returns the pod's xcconfig settings" do
@spec.activate_platform(:ios).xcconfig.should == { 'OTHER_LDFLAGS' => '-framework SystemConfiguration' }
end
it "has a shortcut to add frameworks to the xcconfig" do
@spec.frameworks = 'CFNetwork', 'CoreText'
@spec.activate_platform(:ios).xcconfig.should == {
'OTHER_LDFLAGS' => '-framework CFNetwork ' \
'-framework CoreText ' \
'-framework SystemConfiguration' }
end
it "has a shortcut to add weak frameworks to the xcconfig" do
@spec.weak_frameworks = 'Twitter'
@spec.activate_platform(:ios).xcconfig.should == {
"OTHER_LDFLAGS"=>"-framework SystemConfiguration -weak_framework Twitter"
}
end
it "has a shortcut to add libraries to the xcconfig" do
@spec.libraries = 'z', 'xml2'
@spec.activate_platform(:ios).xcconfig.should == {
'OTHER_LDFLAGS' => '-lxml2 -lz -framework SystemConfiguration'
}
end
it "returns that it's equal to another specification if the name and version are equal" do
@spec.should == Pod::Spec.new { |s| s.name = 'BananaLib'; s.version = '1.0' }
@spec.should.not == Pod::Spec.new { |s| s.name = 'OrangeLib'; s.version = '1.0' }
@spec.should.not == Pod::Spec.new { |s| s.name = 'BananaLib'; s.version = '1.1' }
@spec.should.not == Pod::Spec.new
end
it "never equals when it's from a Podfile" do
Pod::Spec.new.should.not == Pod::Spec.new
end
it "adds compiler flags if ARC is required" do
@spec.parent.should == nil
@spec.requires_arc = true
@spec.activate_platform(:ios).compiler_flags.should == "-fobjc-arc"
@spec.activate_platform(:osx).compiler_flags.should == "-fobjc-arc"
@spec.compiler_flags = "-Wunused-value"
@spec.activate_platform(:ios).compiler_flags.should == "-Wunused-value -fobjc-arc"
@spec.activate_platform(:osx).compiler_flags.should == "-Wunused-value -fobjc-arc"
end
end
describe "A Pod::Specification, in general," do
before do
@spec = Pod::Spec.new
end
it "returns the platform that the static library should be build for" do
@spec.platform = :ios
@spec.platform.should == :ios
end
it "returns the platform and the deployment target" do
@spec.platform = :ios, '4.0'
@spec.platform.should == :ios
@spec.platform.deployment_target.should == Pod::Version.new('4.0')
end
it "returns the available platforms for which the pod is supported" do
@spec.platform = :ios, '4.0'
@spec.available_platforms.count.should == 1
@spec.available_platforms.first.should == :ios
@spec.available_platforms.first.deployment_target.should == Pod::Version.new('4.0')
end
it "returns the license of the Pod" do
@spec.license = {
:type => 'MIT',
:file => 'LICENSE',
:text => 'Permission is hereby granted ...'
}
@spec.license.should == {
:type => 'MIT',
:file => 'LICENSE',
:text => 'Permission is hereby granted ...'
}
end
it "returns the license of the Pod specified in the old format" do
@spec.license = 'MIT'
@spec.license.should == {
:type => 'MIT',
}
end
it "returns the documentation of the Pod" do
@spec.documentation = {
:html => 'http://EXAMPLE/#{@name}/documentation',
:appledoc => ['--project-name', '#{@name}',
'--project-company', '"Company Name"',
'--company-id', 'com.company',
'--ignore', 'Common',
'--ignore', '.m']
}
@spec.documentation[:html].should == 'http://EXAMPLE/#{@name}/documentation'
@spec.documentation[:appledoc].should == ['--project-name', '#{@name}',
'--project-company', '"Company Name"',
'--company-id', 'com.company',
'--ignore', 'Common',
'--ignore', '.m']
end
it "takes a list of paths to clean" do
@spec.clean_paths = 'Demo', 'Doc'
@spec.clean_paths.should == %w{ Demo Doc }
end
it "takes any object for clean_paths as long as it responds to #glob (we provide this for Rake::FileList)" do
@spec.clean_paths = Pod::FileList['*'].exclude('Rakefile')
list = ROOT + @spec.clean_paths.first
list.glob.should == Pod::FileList[(ROOT + '*').to_s].exclude('Rakefile').map { |path| Pathname.new(path) }
end
it "takes a list of paths to preserve" do
@spec.preserve_paths = 'script.sh'
@spec.activate_platform(:ios).preserve_paths.should == %w{ script.sh }
end
it "takes any object for source_files as long as it responds to #glob (we provide this for Rake::FileList)" do
@spec.source_files = Pod::FileList['*'].exclude('Rakefile')
@spec.activate_platform(:ios)
list = ROOT + @spec.source_files.first
list.glob.should == Pod::FileList[(ROOT + '*').to_s].exclude('Rakefile').map { |path| Pathname.new(path) }
end
it "takes a prefix header path which will be appended to the Pods pch file" do
@spec.prefix_header_file.should == nil
@spec.prefix_header_file = 'Classes/Demo.pch'
@spec.prefix_header_file.should == Pathname.new('Classes/Demo.pch')
end
it "takes code that's to be appended to the Pods pch file" do
@spec.prefix_header_contents.should == nil
@spec.prefix_header_contents = '#import "BlocksKit.h"'
@spec.prefix_header_contents.should == '#import "BlocksKit.h"'
end
it "can be activated for a supported platorm" do
@spec.platform = :ios
lambda {@spec.activate_platform(:ios)}.should.not.raise Pod::Informative
end
it "raised if attempted to be activated for an unsupported platform" do
@spec.platform = :osx, '10.7'
lambda {@spec.activate_platform(:ios)}.should.raise Pod::Informative
lambda {@spec.activate_platform(:ios, '10.6')}.should.raise Pod::Informative
end
it "raises if not activated for a platform before accessing a multiplatform value" do
@spec.platform = :ios
lambda {@spec.source_files}.should.raise Pod::Informative
end
it "returns self on activation for method chainablity" do
@spec.platform = :ios
@spec.activate_platform(:ios).should == @spec
end
it "it handles local sources" do
@spec.activate_platform(:ios)
@spec.source = {:local => '/tmp/local/path'}
@spec.local?.should.be.true
end
end
describe "A Pod::Specification, hierarchy" do
before do
@spec = Pod::Spec.new do |s|
s.name = 'MainSpec'
s.version = '0.999'
s.dependency 'awesome_lib'
s.subspec 'SubSpec.0' do |fss|
fss.platform = :ios
fss.subspec 'SubSpec.0.0' do |sss|
end
end
s.subspec 'SubSpec.1'
end
@subspec = @spec.subspecs.first
@spec.activate_platform(:ios)
end
it "automatically includes all the compatible subspecs as a dependencis if not preference is given" do
@spec.dependencies.map { |s| s.name }.should == %w[ awesome_lib MainSpec/SubSpec.0 MainSpec/SubSpec.1 ]
@spec.activate_platform(:osx).dependencies.map { |s| s.name }.should == %w[ awesome_lib MainSpec/SubSpec.1 ]
end
it "uses the spec version for the dependencies" do
@spec.dependencies.
select { |d| d.name =~ /MainSpec/ }.
all? { |d| d.requirement.to_s == '= 0.999' }.
should.be.true
end
it "respecs the preferred dependency for subspecs, if specified" do
@spec.preferred_dependency = 'SubSpec.0'
@spec.dependencies.map { |s| s.name }.should == %w[ awesome_lib MainSpec/SubSpec.0 ]
end
it "raises if it has dependecy on a self or on an upstream subspec" do
lambda { @subspec.dependency('MainSpec/SubSpec.0') }.should.raise Pod::Informative
lambda { @subspec.dependency('MainSpec') }.should.raise Pod::Informative
end
it "inherits external dependecies from the parent" do
@subspec.dependencies.map { |s| s.name }.should == %w[ awesome_lib MainSpec/SubSpec.0/SubSpec.0.0 ]
end
it "it accepts a dependency on a subspec that is in the same level of the hierarchy" do
@subspec.dependency('MainSpec/SubSpec.1')
@subspec.dependencies.map { |s| s.name }.should == %w[ MainSpec/SubSpec.1 awesome_lib MainSpec/SubSpec.0/SubSpec.0.0 ]
end
end
describe "A Pod::Specification subspec" do
before do
@spec = Pod::Spec.new do |s|
s.name = 'MainSpec'
s.version = '1.2.3'
s.license = 'MIT'
s.author = 'Joe the Plumber'
s.source = { :git => '/some/url' }
s.requires_arc = true
s.source_files = 'spec.m'
s.resource = 'resource'
s.platform = :ios
s.library = 'xml'
s.framework = 'CoreData'
s.subspec 'FirstSubSpec' do |fss|
fss.ios.source_files = 'subspec_ios.m'
fss.osx.source_files = 'subspec_osx.m'
fss.framework = 'CoreGraphics'
fss.weak_framework = 'Twitter'
fss.library = 'z'
fss.subspec 'SecondSubSpec' do |sss|
sss.source_files = 'subsubspec.m'
sss.requires_arc = false
end
end
end
@subspec = @spec.subspecs.first
@subsubspec = @subspec.subspecs.first
end
it "returns the top level parent spec" do
@spec.subspecs.first.top_level_parent.should == @spec
@spec.subspecs.first.subspecs.first.top_level_parent.should == @spec
end
it "is named after the parent spec" do
@spec.subspecs.first.name.should == 'MainSpec/FirstSubSpec'
@spec.subspecs.first.subspecs.first.name.should == 'MainSpec/FirstSubSpec/SecondSubSpec'
end
it "correctly resolves the inheritance chain" do
@spec.subspecs.first.subspecs.first.parent.should == @spec.subspecs.first
@spec.subspecs.first.parent.should == @spec
end
it "automatically forwards top level attributes to the subspecs" do
@spec.activate_platform(:ios)
[:version, :license, :authors].each do |attr|
@spec.subspecs.first.send(attr).should == @spec.send(attr)
@spec.subspecs.first.subspecs.first.send(attr).should == @spec.send(attr)
end
end
it "resolves correctly chained attributes" do
@spec.activate_platform(:ios)
@spec.source_files.map { |f| f.to_s }.should == %w[ spec.m ]
@subspec.source_files.map { |f| f.to_s }.should == %w[ spec.m subspec_ios.m ]
@subsubspec.source_files.map { |f| f.to_s }.should == %w[ spec.m subspec_ios.m subsubspec.m ]
@subsubspec.resources.should == %w[ resource ]
@subsubspec.compiler_flags = '-Wdeprecated-implementations'
@subsubspec.compiler_flags.should == '-Wdeprecated-implementations'
end
it "allows to specify arc settings for subspecs" do
@spec.activate_platform(:ios)
@spec.requires_arc.should == true
@subspec.requires_arc.should == true
@subsubspec.requires_arc.should == false
end
it "returns empty arrays for chained attributes with no value in the chain" do
@spec = Pod::Spec.new do |s|
s.name = 'MainSpec'
s.platform = :ios
s.subspec 'FirstSubSpec' do |fss|
fss.subspec 'SecondSubSpec' do |sss|
sss.source_files = 'subsubspec.m'
end
end
end
@spec.activate_platform(:ios).source_files.should == []
@spec.subspecs.first.source_files.should == []
@spec.subspecs.first.subspecs.first.source_files.should == %w[ subsubspec.m ]
end
it "does not cache platform attributes and can activate another platform" do
@spec.stubs(:platform).returns nil
@spec.activate_platform(:ios)
@subsubspec.source_files.map { |f| f.to_s }.should == %w[ spec.m subspec_ios.m subsubspec.m ]
@spec.activate_platform(:osx)
@subsubspec.source_files.map { |f| f.to_s }.should == %w[ spec.m subspec_osx.m subsubspec.m ]
end
it "resolves correctly the available platforms" do
@spec.stubs(:platform).returns nil
@subspec.platform = :ios, '4.0'
@spec.available_platforms.map{ |p| p.to_sym }.should == [ :osx, :ios ]
@subspec.available_platforms.first.to_sym.should == :ios
@subsubspec.available_platforms.first.to_sym.should == :ios
@subsubspec.platform = :ios, '5.0'
@subspec.available_platforms.first.deployment_target.to_s.should == '4.0'
@subsubspec.available_platforms.first.deployment_target.to_s.should == '5.0'
end
it "resolves reports correctly the supported platforms" do
@spec.stubs(:platform).returns nil
@subspec.platform = :ios, '4.0'
@subsubspec.platform = :ios, '5.0'
@spec.supports_platform?(:ios).should.be.true
@spec.supports_platform?(:osx).should.be.true
@subspec.supports_platform?(:ios).should.be.true
@subspec.supports_platform?(:osx).should.be.false
@subspec.supports_platform?(:ios, '4.0').should.be.true
@subspec.supports_platform?(:ios, '5.0').should.be.true
@subsubspec.supports_platform?(:ios).should.be.true
@subsubspec.supports_platform?(:osx).should.be.false
@subsubspec.supports_platform?(:ios, '4.0').should.be.false
@subsubspec.supports_platform?(:ios, '5.0').should.be.true
@subsubspec.supports_platform?(Pod::Platform.new(:ios, '4.0')).should.be.false
@subsubspec.supports_platform?(Pod::Platform.new(:ios, '5.0')).should.be.true
end
it "raises a top level attribute is assigned to a spec with a parent" do
lambda { @subspec.version = '0.0.1' }.should.raise Pod::Informative
end
it "returns subspecs by name" do
@spec.subspec_by_name(nil).should == @spec
@spec.subspec_by_name('MainSpec').should == @spec
@spec.subspec_by_name('MainSpec/FirstSubSpec').should == @subspec
@spec.subspec_by_name('MainSpec/FirstSubSpec/SecondSubSpec').should == @subsubspec
end
it "has the same active platform accross the chain attributes" do
@spec.activate_platform(:ios)
@subspec.active_platform.should == :ios
@subsubspec.active_platform.should == :ios
@spec.stubs(:platform).returns nil
@subsubspec.activate_platform(:osx)
@subspec.active_platform.should == :osx
@spec.active_platform.should == :osx
end
it "resolves the libraries correctly" do
@spec.activate_platform(:ios)
@spec.libraries.should == %w[ xml ]
@subspec.libraries.should == %w[ xml z ]
@subsubspec.libraries.should == %w[ xml z ]
end
it "resolves the frameworks correctly" do
@spec.activate_platform(:ios)
@spec.frameworks.should == %w[ CoreData ]
@subspec.frameworks.should == %w[ CoreData CoreGraphics ]
@subsubspec.frameworks.should == %w[ CoreData CoreGraphics ]
end
it "resolves the weak frameworks correctly" do
@spec.activate_platform(:ios)
@spec.weak_frameworks.should == %w[ ]
@subspec.weak_frameworks.should == %w[ Twitter ]
end
it "resolves the xcconfig" do
@spec.activate_platform(:ios)
@spec.xcconfig = { 'OTHER_LDFLAGS' => "-Wl,-no_compact_unwind" }
@spec.xcconfig.should == {"OTHER_LDFLAGS"=>"-Wl,-no_compact_unwind -lxml -framework CoreData"}
@subspec.xcconfig.should == {"OTHER_LDFLAGS"=>"-Wl,-no_compact_unwind -lxml -lz -framework CoreData -framework CoreGraphics -weak_framework Twitter"}
@subsubspec.xcconfig.should == {"OTHER_LDFLAGS"=>"-Wl,-no_compact_unwind -lxml -lz -framework CoreData -framework CoreGraphics -weak_framework Twitter"}
@subsubspec.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' }
@spec.xcconfig.should == {"OTHER_LDFLAGS"=>"-Wl,-no_compact_unwind -lxml -framework CoreData"}
@subsubspec.xcconfig.should == {"OTHER_LDFLAGS"=>"-Wl,-no_compact_unwind -lxml -lz -framework CoreData -framework CoreGraphics -weak_framework Twitter", "HEADER_SEARCH_PATHS"=>"$(SDKROOT)/usr/include/libxml2"}
end
end
describe "A Pod::Specification with :local source" do
before do
@spec = Pod::Spec.new do |s|
s.name = 'MainSpec'
s.source = { :local => fixture("integration/JSONKit") }
s.source_files = "."
end
end
it "is marked as local" do
@spec.should.be.local
end
it "it returns the expanded local path" do
@spec.source.should == {:local => fixture("integration/JSONKit")}
end
end
describe "A Pod::Specification, concerning its attributes that support different values per platform," do
describe "when **no** platform specific values are given" do
before do
@spec = Pod::Spec.new do |s|
s.source_files = 'file1', 'file2'
s.resources = 'file1', 'file2'
s.xcconfig = { 'OTHER_LDFLAGS' => '-lObjC' }
s.framework = 'QuartzCore'
s.library = 'z'
s.compiler_flags = '-Wdeprecated-implementations'
s.requires_arc = true
s.dependency 'JSONKit'
s.dependency 'SSZipArchive'
end
end
it "returns the same list of source files for each platform" do
@spec.activate_platform(:ios).source_files.should == %w{ file1 file2 }
@spec.activate_platform(:osx).source_files.should == %w{ file1 file2 }
end
it "returns the same list of resources for each platform" do
@spec.activate_platform(:ios).resources.should == %w{ file1 file2 }
@spec.activate_platform(:osx).resources.should == %w{ file1 file2 }
end
it "returns the same list of xcconfig build settings for each platform" do
build_settings = { 'OTHER_LDFLAGS' => '-lObjC -lz -framework QuartzCore' }
@spec.activate_platform(:ios).xcconfig.should == build_settings
@spec.activate_platform(:osx).xcconfig.should == build_settings
end
it "returns the same list of compiler flags for each platform" do
compiler_flags = '-Wdeprecated-implementations -fobjc-arc'
@spec.activate_platform(:ios).compiler_flags.should == compiler_flags
@spec.activate_platform(:osx).compiler_flags.should == compiler_flags
end
it "returns the same list of dependencies for each platform" do
dependencies = %w{ JSONKit SSZipArchive }.map { |name| Pod::Dependency.new(name) }
@spec.activate_platform(:ios).dependencies.should == dependencies
@spec.activate_platform(:osx).dependencies.should == dependencies
end
end
describe "when platform specific values are given" do
before do
@spec = Pod::Spec.new do |s|
s.ios.source_files = 'file1'
s.osx.source_files = 'file1', 'file2'
s.ios.resource = 'file1'
s.osx.resources = 'file1', 'file2'
s.ios.xcconfig = { 'OTHER_LDFLAGS' => '-lObjC' }
s.osx.xcconfig = { 'OTHER_LDFLAGS' => '-lObjC -all_load' }
s.ios.framework = 'QuartzCore'
s.osx.frameworks = 'QuartzCore', 'CoreData'
s.ios.library = 'z'
s.osx.libraries = 'z', 'xml'
s.ios.compiler_flags = '-Wdeprecated-implementations'
s.osx.compiler_flags = '-Wfloat-equal'
s.requires_arc = true # does not take platform options, just here to check it's added to compiler_flags
s.ios.dependency 'JSONKit'
s.osx.dependency 'SSZipArchive'
s.ios.deployment_target = '4.0'
end
end
it "returns a different list of source files for each platform" do
@spec.activate_platform(:ios).source_files.should == %w{ file1 }
@spec.activate_platform(:osx).source_files.should == %w{ file1 file2 }
end
it "returns a different list of resources for each platform" do
@spec.activate_platform(:ios).resources.should == %w{ file1 }
@spec.activate_platform(:osx).resources.should == %w{ file1 file2 }
end
it "returns a different list of xcconfig build settings for each platform" do
@spec.activate_platform(:ios).xcconfig.should == { 'OTHER_LDFLAGS' => '-lObjC -lz -framework QuartzCore' }
@spec.activate_platform(:osx).xcconfig.should == { 'OTHER_LDFLAGS' => '-all_load -lObjC -lxml -lz -framework CoreData -framework QuartzCore' }
end
it "returns the list of the supported platfroms and deployment targets" do
@spec.available_platforms.count.should == 2
@spec.available_platforms.should.include? Pod::Platform.new(:osx)
@spec.available_platforms.should.include? Pod::Platform.new(:ios, '4.0')
end
it "returns the same list of compiler flags for each platform" do
@spec.activate_platform(:ios).compiler_flags.should == '-Wdeprecated-implementations -fobjc-arc'
@spec.activate_platform(:osx).compiler_flags.should == '-Wfloat-equal -fobjc-arc'
end
it "returns the same list of dependencies for each platform" do
@spec.activate_platform(:ios).dependencies.should == [Pod::Dependency.new('JSONKit')]
@spec.activate_platform(:osx).dependencies.should == [Pod::Dependency.new('SSZipArchive')]
end
end
end
require File.expand_path('../../spec_helper', __FILE__)
module Pod
describe Version do
it "returns wether or not it's a `bleeding edge' version" do
version = Version.new('1.2.3')
version.should.not.be.head
version.head = true
version.should.be.head
end
it "serializes to and from a string" do
version = Version.from_string('1.2.3')
version.to_s.should == '1.2.3'
version.should.not.be.head
version = Version.from_string('HEAD based on 1.2.3')
version.should.be.head
version.to_s.should == 'HEAD based on 1.2.3'
end
it "supports the previous way that a HEAD version was described" do
version = Version.from_string('HEAD from 1.2.3')
version.should.be.head
version.to_s.should == 'HEAD based on 1.2.3'
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