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 ...@@ -22,3 +22,4 @@ spec/fixtures/vcr
.yardoc .yardoc
/doc /doc
.rbx/ .rbx/
rakelib/doc
\ No newline at end of file
...@@ -6,9 +6,6 @@ process do |files| ...@@ -6,9 +6,6 @@ process do |files|
specs = files.take_and_map do |file| specs = files.take_and_map do |file|
if file =~ %r{lib/cocoapods/(.+?)\.rb$} if file =~ %r{lib/cocoapods/(.+?)\.rb$}
s = Dir.glob("spec/**/#{File.basename(file, '.rb')}_spec.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? s.uniq unless s.empty?
end end
end end
......
...@@ -2,8 +2,21 @@ language: ruby ...@@ -2,8 +2,21 @@ language: ruby
rvm: rvm:
- 1.8.7 - 1.8.7
- 1.9.3 - 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. # Rubinius in 1.8 mode on Travis does not work. It complains about st_data_t etc in Xcodeproj.
#- rbx-18mode #- rbx-18mode
- rbx-19mode - rbx-19mode
matrix:
allow_failures:
- rvm: rbx-19mode
#- rvm: 2.0.0
install: NOEXEC=skip rake travis:setup install: NOEXEC=skip rake travis:setup
script: bundle exec rake spec 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 ## 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 ###### 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 - Extracted command-line command & option handling into
[CLAide](https://github.com/CocoaPods/CLAide). [CLAide](https://github.com/CocoaPods/CLAide).
- Added PathList class. - Added PathList class.
- Added Podfile to the Pods project.
[#476](https://github.com/CocoaPods/CocoaPods/issues/476) [#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 - Added support for copying frameworks to the app bundle.
[CocoaPods](https://github.com/CocoaPods/CocoaPods/compare/0.16.0.rc2...master)[Xcodeproj](https://github.com/CocoaPods/Xcodeproj/compare/0.4.0.rc1...master) [#597](https://github.com/CocoaPods/CocoaPods/pull/597)
###### Bug fixes ###### Bug fixes
- Ignore PBXReferenceProxy while integrating into user project.
[#626](https://github.com/CocoaPods/CocoaPods/issues/626)
- Added support for PBXAggregateTarget and PBXLegacyTarget. - Added support for PBXAggregateTarget and PBXLegacyTarget.
[#615](https://github.com/CocoaPods/CocoaPods/issues/615) [#615](https://github.com/CocoaPods/CocoaPods/issues/615)
- Added support for PBXReferenceProxy. - Added support for PBXReferenceProxy.
......
...@@ -3,13 +3,15 @@ source "http://rubygems.org" ...@@ -3,13 +3,15 @@ source "http://rubygems.org"
gemspec gemspec
group :development do 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 "xcodeproj", :git => "git://github.com/CocoaPods/Xcodeproj.git"
gem "mocha", "~> 0.11.4" gem "mocha", "~> 0.11.4"
gem "bacon" gem "bacon"
gem "mocha-on-bacon" gem "mocha-on-bacon"
gem "rake" gem "rake"
gem "vcr" gem "vcr"
gem "webmock" gem "webmock", '>= 1.8.0', '< 1.9'
end end
group :debugging do group :debugging do
...@@ -23,5 +25,5 @@ group :documentation do ...@@ -23,5 +25,5 @@ group :documentation do
gem 'yard' gem 'yard'
gem 'redcarpet' gem 'redcarpet'
gem 'github-markup' gem 'github-markup'
gem 'pygments.rb'
end end
GIT GIT
remote: git://github.com/CocoaPods/Xcodeproj.git remote: git://github.com/CocoaPods/Xcodeproj.git
revision: 97a5a444ecfadf52e42afdbbf054f078f7c2cc5d revision: fbb5c11844b99bd954b2945098502dde0641c311
specs: specs:
xcodeproj (0.4.0.rc5) xcodeproj (0.4.0)
activesupport (~> 3.2.6) activesupport (~> 3.2.6)
colored (~> 1.2) colored (~> 1.2)
...@@ -18,22 +18,31 @@ GIT ...@@ -18,22 +18,31 @@ GIT
PATH PATH
remote: . remote: .
specs: specs:
cocoapods (0.16.0.rc2) cocoapods (0.17.0.alpha)
activesupport (~> 3.2.6) activesupport (~> 3.2.6)
claide (~> 0.1) claide (~> 0.1)
cocoapods-core
colored (~> 1.2) colored (~> 1.2)
escape (~> 0.0.4) escape (~> 0.0.4)
faraday (~> 0.8.1) faraday (~> 0.8.1)
json (~> 1.7.3) json (~> 1.7.3)
octokit (~> 1.7) octokit (~> 1.7)
open4 (~> 1.3.0) open4 (~> 1.3.0)
rake (~> 0.9.0) rake (~> 0.9.4)
xcodeproj (~> 0.4.0.rc1) 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 GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: specs:
activesupport (3.2.8) activesupport (3.2.9)
i18n (~> 0.6) i18n (~> 0.6)
multi_json (~> 1.0) multi_json (~> 1.0)
addressable (2.3.2) addressable (2.3.2)
...@@ -52,27 +61,31 @@ GEM ...@@ -52,27 +61,31 @@ GEM
hashie (1.2.0) hashie (1.2.0)
i18n (0.6.1) i18n (0.6.1)
json (1.7.5) json (1.7.5)
listen (0.5.3) listen (0.6.0)
metaclass (0.0.1) metaclass (0.0.1)
method_source (0.8.1) method_source (0.8.1)
mocha (0.11.4) mocha (0.11.4)
metaclass (~> 0.0.1) metaclass (~> 0.0.1)
mocha-on-bacon (0.2.1) mocha-on-bacon (0.2.1)
mocha (>= 0.9.8) mocha (>= 0.9.8)
multi_json (1.3.6) multi_json (1.4.0)
multipart-post (1.1.5) multipart-post (1.1.5)
octokit (1.18.0) octokit (1.19.0)
addressable (~> 2.2) addressable (~> 2.2)
faraday (~> 0.8) faraday (~> 0.8)
faraday_middleware (~> 0.8) faraday_middleware (~> 0.9)
hashie (~> 1.2) hashie (~> 1.2)
multi_json (~> 1.3) multi_json (~> 1.3)
open4 (1.3.0) open4 (1.3.0)
posix-spawn (0.3.6)
pry (0.9.10) pry (0.9.10)
coderay (~> 1.0.5) coderay (~> 1.0.5)
method_source (~> 0.8) method_source (~> 0.8)
slop (~> 3.3.1) 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) rb-fsevent (0.9.2)
redcarpet (2.2.2) redcarpet (2.2.2)
slop (3.3.3) slop (3.3.3)
...@@ -81,6 +94,7 @@ GEM ...@@ -81,6 +94,7 @@ GEM
webmock (1.8.11) webmock (1.8.11)
addressable (>= 2.2.7) addressable (>= 2.2.7)
crack (>= 0.1.7) crack (>= 0.1.7)
yajl-ruby (1.1.0)
yard (0.8.3) yard (0.8.3)
PLATFORMS PLATFORMS
...@@ -90,15 +104,17 @@ DEPENDENCIES ...@@ -90,15 +104,17 @@ DEPENDENCIES
awesome_print awesome_print
bacon bacon
cocoapods! cocoapods!
cocoapods-core!
github-markup github-markup
kicker! kicker!
mocha (~> 0.11.4) mocha (~> 0.11.4)
mocha-on-bacon mocha-on-bacon
pry pry
pygments.rb
rake rake
rb-fsevent rb-fsevent
redcarpet redcarpet
vcr vcr
webmock webmock (>= 1.8.0, < 1.9)
xcodeproj! xcodeproj!
yard yard
...@@ -96,7 +96,7 @@ namespace :gem do ...@@ -96,7 +96,7 @@ namespace :gem do
diff_lines.delete('CHANGELOG.md') diff_lines.delete('CHANGELOG.md')
if diff_lines != ['lib/cocoapods.rb'] if diff_lines != ['lib/cocoapods.rb']
$stderr.puts "[!] Only change the version number in a release commit!" $stderr.puts "[!] Only change the version number in a release commit!"
# exit 1 exit 1
end end
end end
...@@ -108,7 +108,7 @@ namespace :gem do ...@@ -108,7 +108,7 @@ namespace :gem do
required_xcodeproj_version = xcodeproj.requirement.requirements.first.last.to_s required_xcodeproj_version = xcodeproj.requirement.requirements.first.last.to_s
puts "* Checking if xcodeproj #{required_xcodeproj_version} exists on the gem host" 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(', ') remote_xcodeproj_versions = search_result.match(/xcodeproj \((.*)\)/m)[1].split(', ')
unless remote_xcodeproj_versions.include?(required_xcodeproj_version) unless remote_xcodeproj_versions.include?(required_xcodeproj_version)
$stderr.puts "[!] The Xcodeproj version `#{required_xcodeproj_version}' required by " \ $stderr.puts "[!] The Xcodeproj version `#{required_xcodeproj_version}' required by " \
......
...@@ -26,8 +26,9 @@ Gem::Specification.new do |s| ...@@ -26,8 +26,9 @@ Gem::Specification.new do |s|
s.executables = %w{ pod } s.executables = %w{ pod }
s.require_paths = %w{ lib } s.require_paths = %w{ lib }
s.add_runtime_dependency 'cocoapods-core'
s.add_runtime_dependency 'claide', '~> 0.1' 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 'faraday', '~> 0.8.1'
s.add_runtime_dependency 'octokit', '~> 1.7' s.add_runtime_dependency 'octokit', '~> 1.7'
...@@ -35,7 +36,7 @@ Gem::Specification.new do |s| ...@@ -35,7 +36,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'escape', '~> 0.0.4' s.add_runtime_dependency 'escape', '~> 0.0.4'
s.add_runtime_dependency 'json', '~> 1.7.3' s.add_runtime_dependency 'json', '~> 1.7.3'
s.add_runtime_dependency 'open4', '~> 1.3.0' 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_runtime_dependency 'activesupport', '~> 3.2.6'
s.add_development_dependency 'bacon', '~> 1.1' s.add_development_dependency 'bacon', '~> 1.1'
......
...@@ -25,26 +25,17 @@ module Pod ...@@ -25,26 +25,17 @@ module Pod
end end
autoload :Command, 'cocoapods/command' autoload :Command, 'cocoapods/command'
autoload :Config, 'cocoapods/config'
autoload :Dependency, 'cocoapods/dependency'
autoload :Downloader, 'cocoapods/downloader' autoload :Downloader, 'cocoapods/downloader'
autoload :Executable, 'cocoapods/executable' autoload :Executable, 'cocoapods/executable'
autoload :ExternalSources, 'cocoapods/external_sources'
autoload :Installer, 'cocoapods/installer' autoload :Installer, 'cocoapods/installer'
autoload :LocalPod, 'cocoapods/local_pod' autoload :LocalPod, 'cocoapods/local_pod'
autoload :Lockfile, 'cocoapods/lockfile'
autoload :Platform, 'cocoapods/platform'
autoload :Podfile, 'cocoapods/podfile'
autoload :Project, 'cocoapods/project' autoload :Project, 'cocoapods/project'
autoload :Resolver, 'cocoapods/resolver' autoload :Resolver, 'cocoapods/resolver'
autoload :Sandbox, 'cocoapods/sandbox' autoload :Sandbox, 'cocoapods/sandbox'
autoload :Source, 'cocoapods/source'
autoload :Spec, 'cocoapods/specification'
autoload :Specification, 'cocoapods/specification'
autoload :UI, 'cocoapods/user_interface' autoload :UI, 'cocoapods/user_interface'
autoload :Version, 'cocoapods/version_class'
autoload :Pathname, 'pathname' autoload :Pathname, 'pathname'
autoload :FileList, 'cocoapods/file_list'
module Generator module Generator
autoload :BridgeSupport, 'cocoapods/generator/bridge_support' autoload :BridgeSupport, 'cocoapods/generator/bridge_support'
...@@ -54,9 +45,17 @@ module Pod ...@@ -54,9 +45,17 @@ module Pod
autoload :Plist, 'cocoapods/generator/acknowledgements/plist' autoload :Plist, 'cocoapods/generator/acknowledgements/plist'
autoload :Markdown, 'cocoapods/generator/acknowledgements/markdown' autoload :Markdown, 'cocoapods/generator/acknowledgements/markdown'
autoload :DummySource, 'cocoapods/generator/dummy_source' autoload :DummySource, 'cocoapods/generator/dummy_source'
autoload :PrefixHeader, 'cocoapods/generator/prefix_header'
autoload :XCConfig, 'cocoapods/generator/xcconfig'
end end
require 'cocoapods/file_list'
require 'cocoapods-core'
require 'cocoapods/config'
require 'cocoapods/source'
end end
if ENV['COCOA_PODS_ENV'] == 'development' if ENV['COCOA_PODS_ENV'] == 'development'
require 'awesome_print' require 'awesome_print'
require 'pry'
end end
...@@ -7,8 +7,8 @@ module Pod ...@@ -7,8 +7,8 @@ module Pod
end end
class Command < CLAide::Command class Command < CLAide::Command
autoload :ErrorReport, 'cocoapods/command/error_report' autoload :ErrorReport, 'cocoapods/command/error_report'
autoload :Linter, 'cocoapods/command/linter' autoload :AdvancedLinter, 'cocoapods/command/advanced_linter'
self.abstract_command = true self.abstract_command = true
self.command = 'pod' 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 ...@@ -40,13 +40,14 @@ module Pod
self.summary = 'Lists pods introduced in the master spec-repo since the last check' self.summary = 'Lists pods introduced in the master spec-repo since the last check'
def run def run
puts "running"
update_if_necessary! update_if_necessary!
days = [1,2,3,5,8] days = [1,2,3,5,8]
dates, groups = {}, {} dates, groups = {}, {}
days.each {|d| dates[d] = Time.now - 60 * 60 * 24 * d} days.each {|d| dates[d] = Time.now - 60 * 60 * 24 * d}
sets = Source.all_sets 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| sets.each do |set|
set_date = creation_dates[set.name] set_date = creation_dates[set.name]
......
...@@ -19,10 +19,10 @@ module Pod ...@@ -19,10 +19,10 @@ module Pod
end end
def run_install_with_update(update) def run_install_with_update(update)
sandbox = Sandbox.new(config.project_pods_root) sandbox = Sandbox.new(config.project_pods_root)
resolver = Resolver.new(config.podfile, config.lockfile, sandbox) installer = Installer.new(sandbox, config.podfile, config.lockfile)
resolver.update_mode = update installer.update_mode = update
Installer.new(resolver).install! installer.install!
end end
end end
......
...@@ -87,7 +87,7 @@ module Pod ...@@ -87,7 +87,7 @@ module Pod
lint_argv = ["lint"] lint_argv = ["lint"]
lint_argv << "--only-errors" if @allow_warnings lint_argv << "--only-errors" if @allow_warnings
lint_argv << "--silent" if config.silent lint_argv << "--silent" if config.silent
all_valid = true # all_valid = true
podspec_files.each do |podspec| podspec_files.each do |podspec|
Spec.parse(lint_argv + [podspec.to_s]).run Spec.parse(lint_argv + [podspec.to_s]).run
end end
......
...@@ -107,7 +107,7 @@ module Pod ...@@ -107,7 +107,7 @@ module Pod
invalid_count = 0 invalid_count = 0
podspecs.each do |podspec| podspecs.each do |podspec|
linter = Linter.new(podspec) linter = AdvancedLinter.new(podspec)
linter.quick = true linter.quick = true
linter.repo_path = dir linter.repo_path = dir
...@@ -178,6 +178,8 @@ module Pod ...@@ -178,6 +178,8 @@ module Pod
is_compatilbe(versions) is_compatilbe(versions)
end end
#--------------------------------------#
private private
def versions(dir) def versions(dir)
......
...@@ -32,7 +32,13 @@ module Pod ...@@ -32,7 +32,13 @@ module Pod
def run def run
sets = Source.search_by_name(@query.strip, @full_text_search) 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 end
end end
......
...@@ -84,16 +84,11 @@ module Pod ...@@ -84,16 +84,11 @@ module Pod
UI.puts UI.puts
invalid_count = 0 invalid_count = 0
podspecs_to_lint.each do |podspec| podspecs_to_lint.each do |podspec|
linter = Linter.new(podspec) linter = AdvancedLinter.new(podspec)
linter.quick = @quick linter.quick = @quick
linter.local = @local linter.local = @local
linter.no_clean = @no_clean 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 case linter.result_type
when :error when :error
invalid_count += 1 invalid_count += 1
...@@ -104,14 +99,6 @@ module Pod ...@@ -104,14 +99,6 @@ module Pod
else else
color = :green color = :green
end 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 end
count = podspecs_to_lint.count count = podspecs_to_lint.count
...@@ -179,11 +166,6 @@ module Pod ...@@ -179,11 +166,6 @@ module Pod
# TODO some of the following methods can probably move to one of the subclasses. # TODO some of the following methods can probably move to one of the subclasses.
private private
def print_messages(type, messages)
return if config.silent?
messages.each {|msg| UI.puts " - #{type.ljust(5)} | #{msg}"}
end
def podspecs_to_lint def podspecs_to_lint
@podspecs_to_lint ||= begin @podspecs_to_lint ||= begin
files = [] files = []
...@@ -284,7 +266,7 @@ Pod::Spec.new do |s| ...@@ -284,7 +266,7 @@ Pod::Spec.new do |s|
# s.description = <<-DESC # s.description = <<-DESC
# An optional longer description of #{data[:name]} # An optional longer description of #{data[:name]}
# #
# * Markdonw format. # * Markdown format.
# * Don't worry about the indent, we strip it! # * Don't worry about the indent, we strip it!
# DESC # DESC
s.homepage = "#{data[:homepage]}" s.homepage = "#{data[:homepage]}"
...@@ -318,7 +300,7 @@ Pod::Spec.new do |s| ...@@ -318,7 +300,7 @@ Pod::Spec.new do |s|
# #
# s.author = '#{data[:author_name]}', 'other author' # 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 = { :git => "#{data[:source_url]}", #{data[:ref_type]} => "#{data[:ref]}" }
# s.source = { :svn => 'http://EXAMPLE/#{data[:name]}/tags/1.0.0' } # s.source = { :svn => 'http://EXAMPLE/#{data[:name]}/tags/1.0.0' }
...@@ -352,9 +334,9 @@ Pod::Spec.new do |s| ...@@ -352,9 +334,9 @@ Pod::Spec.new do |s|
# made available to the application. If the pattern is a directory then the # made available to the application. If the pattern is a directory then the
# path will automatically have '*.h' appended. # 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. # all headers of source_files will be made public.
# #
# s.public_header_files = 'Classes/**/*.h' # s.public_header_files = 'Classes/**/*.h'
...@@ -362,7 +344,7 @@ Pod::Spec.new do |s| ...@@ -362,7 +344,7 @@ Pod::Spec.new do |s|
# A list of resources included with the Pod. These are copied into the # A list of resources included with the Pod. These are copied into the
# target bundle with a build phase script. # 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.resource = "icon.png"
# s.resources = "Resources/*.png" # s.resources = "Resources/*.png"
...@@ -370,7 +352,7 @@ Pod::Spec.new do |s| ...@@ -370,7 +352,7 @@ Pod::Spec.new do |s|
# A list of paths to preserve after installing the Pod. # A list of paths to preserve after installing the Pod.
# CocoaPods cleans by default any file that is not used. # CocoaPods cleans by default any file that is not used.
# Please don't include documentation, example, and test files. # 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" # s.preserve_paths = "FilesToSave", "MoreFilesToSave"
......
...@@ -74,6 +74,12 @@ module Pod ...@@ -74,6 +74,12 @@ module Pod
end end
end end
# @return [Sandbox]
#
def sandbox
@sandbox ||= Sandbox.new(project_pods_root)
end
module Mixin module Mixin
def config def config
Config.instance 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() ...@@ -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}" 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}" 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}" 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}" cp -R "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
...@@ -26,7 +30,7 @@ EOS ...@@ -26,7 +30,7 @@ EOS
attr_reader :resources attr_reader :resources
# A list of files relative to the project pods root. # A list of files relative to the project pods root.
def initialize(resources) def initialize(resources = [])
@resources = resources @resources = resources
end 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 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 class Installer
autoload :TargetInstaller, 'cocoapods/installer/target_installer' autoload :TargetInstaller, 'cocoapods/installer/target_installer'
autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator' autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
include Config::Mixin 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) # @param [Sandbox] sandbox @see sandbox
@resolver = resolver # @param [Podfile] podfile @see podfile
@podfile = resolver.podfile # @param [Lockfile] lockfile @see lockfile
@sandbox = resolver.sandbox # @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 end
def project # Performs only the computation parts of an installation.
return @project if @project #
@project = Pod::Project.new # It is used by the `outdated` subcommand.
@project.user_build_configurations = @podfile.user_build_configurations #
pods.each { |p| p.add_file_references_to_project(@project) } # @return [void]
pods.each { |p| p.link_headers } #
@project 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 end
def target_installers #---------------------------------------------------------------------------#
@target_installers ||= @podfile.target_definitions.values.map do |definition|
TargetInstaller.new(@podfile, project, definition) unless definition.empty? # @!group Analysis products
end.compact
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 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] # @return [void]
# #
def install_dependencies! def prepare_for_legacy_compatibility
pods.sort_by { |pod| pod.top_specification.name.downcase }.each do |pod| # move_target_support_files_if_needed
should_install = @resolver.should_install?(pod.top_specification.name) || !pod.exists? # copy_lock_file_to_Pods_lock_if_needed
if should_install # move_Local_Podspecs_to_Podspecs_if_needed
UI.section("Installing #{pod}".green, "-> ".green) do # move_pods_to_sources_folder_if_needed
unless pod.downloaded? end
pod.implode
download_pod(pod) # @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 end
# The docs need to be generated before cleaning because the else
# documentation is created for all the subspecs. UI.section("Using #{pod}", "-> ".green)
generate_docs(pod)
# Here we clean pod's that just have been downloaded or have been
# pre-downloaded in AbstractExternalSource#specification_from_sandbox.
pod.clean! if config.clean?
end end
else
UI.section("Using #{pod}", "-> ".green)
end end
end 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) def download_pod(pod)
downloader = Downloader.for_pod(pod) downloader = Downloader.for_pod(pod)
# Force the `bleeding edge' version if necessary.
if pod.top_specification.version.head? if pod.top_specification.version.head?
if downloader.respond_to?(:download_head) if downloader.respond_to?(:download_head)
downloader.download_head downloader.download_head
else 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 end
else else
downloader.download downloader.download
...@@ -73,8 +499,12 @@ module Pod ...@@ -73,8 +499,12 @@ module Pod
pod.downloaded = true pod.downloaded = true
end end
#TODO: move to generator ? # Generates the documentation of a Pod unless it exists for a given
def generate_docs(pod) # version.
#
# @return [void]
#
def generate_docs_if_needed(pod)
doc_generator = Generator::Documentation.new(pod) doc_generator = Generator::Documentation.new(pod)
if ( config.generate_docs? && !doc_generator.already_installed? ) if ( config.generate_docs? && !doc_generator.already_installed? )
UI.section " > Installing documentation" UI.section " > Installing documentation"
...@@ -84,133 +514,184 @@ module Pod ...@@ -84,133 +514,184 @@ module Pod
end end
end end
# @TODO: use the local pod implode # Creates and populates the targets of the pods project.
# #
def remove_deleted_dependencies! # @note Post install hooks run _before_ saving of project, so that they
resolver.removed_pods.each do |pod_name| # can alter it before it is writtent to the disk.
UI.section("Removing #{pod_name}", "-> ".red) do #
path = sandbox.root + pod_name # @return [void]
path.rmtree if path.exist? #
end 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
end end
def install! # Creates the Pods project from scratch if it doesn't exists.
@sandbox.prepare_for_install #
UI.section "Resolving dependencies of #{UI.path @podfile.defined_in_file}" do # @todo Restore the build configuration support.
specs_by_target # @todo Clean and modify the project if it exists.
end #
# @return [void]
UI.section "Removing deleted dependencies" do #
remove_deleted_dependencies! def prepare_pods_project
end unless resolver.removed_pods.empty? UI.message "- Creating Pods project" do
@pods_project = Pod::Project.new(config.sandbox)
UI.section "Downloading dependencies" do @pods_project.add_podfile(config.project_podfile)
install_dependencies! # pods_project.user_build_configurations = podfile.user_build_configurations
end end
end
UI.section "Generating support files" do # Creates a target installer for each definition not empty.
UI.message "- Running pre install hooks" do #
run_pre_install_hooks # @return [void]
end #
def generate_target_installers
UI.message"- Generating project" do @target_installers = podfile.target_definitions.values.map do |definition|
project pods_for_target = local_pods_by_target[definition]
end TargetInstaller.new(pods_project, definition, pods_for_target) unless definition.empty?
end.compact
UI.message"- Installing targets" do end
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
UI.message "- Writing lockfile in #{UI.path config.project_lockfile}" do # Adds the source files of the Pods to the Pods project.
@lockfile = Lockfile.generate(@podfile, specs_by_target.values.flatten) #
@lockfile.write_to_disk(config.project_lockfile) # The source files are grouped by Pod and in turn by subspec
end # (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 end
UserProjectIntegrator.new(@podfile).integrate! if config.integrate_targets?
end 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 def run_pre_install_hooks
pods_by_target.each do |target_definition, pods| UI.message "- Running pre install hooks" do
pods.each do |pod| local_pods_by_target.each do |target_definition, pods|
pod.top_specification.pre_install(pod, target_definition) pods.each do |pod|
pod.top_specification.pre_install!(pod, target_definition)
end
end end
@podfile.pre_install!(self)
end end
@podfile.pre_install!(self)
end 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 def run_post_install_hooks
# we loop over target installers instead of pods, because we yield the target installer UI.message "- Running post install hooks" do
# to the spec post install hook. target_installers.each do |target_installer|
target_installers.each do |target_installer| specs_by_target[target_installer.target_definition].each do |spec|
specs_by_target[target_installer.target_definition].each do |spec| spec.post_install!(target_installer)
spec.post_install(target_installer) end
end end
@podfile.post_install!(self)
end end
@podfile.post_install!(self)
end 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 def generate_target_support_files
target_installers.each do |target_installer| UI.message"- Installing targets" do
pods_for_target = pods_by_target[target_installer.target_definition] target_installers.each do |target_installer|
target_installer.install!(pods_for_target, @sandbox) pods_for_target = local_pods_by_target[target_installer.target_definition]
acknowledgements_path = target_installer.target_definition.acknowledgements_path target_installer.install!
Generator::Acknowledgements.new(target_installer.target_definition, acknowledgements_path = target_installer.library.acknowledgements_path
pods_for_target).save_as(acknowledgements_path) Generator::Acknowledgements.new(target_installer.target_definition,
generate_dummy_source(target_installer) pods_for_target).save_as(acknowledgements_path)
generate_dummy_source(target_installer)
end
end 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) def generate_dummy_source(target_installer)
class_name_identifier = target_installer.target_definition.label class_name_identifier = target_installer.target_definition.label
dummy_source = Generator::DummySource.new(class_name_identifier) dummy_source = Generator::DummySource.new(class_name_identifier)
filename = "#{dummy_source.class_name}.m" filename = "#{dummy_source.class_name}.m"
pathname = Pathname.new(sandbox.root + filename) pathname = Pathname.new(sandbox.root + filename)
dummy_source.save_as(pathname) dummy_source.save_as(pathname)
file = pods_project.new_file(filename, "Targets Support Files")
file = project.new_file(filename, "Targets Support Files")
target_installer.target.source_build_phase.add_file_reference(file) target_installer.target.source_build_phase.add_file_reference(file)
end end
def specs_by_target # Writes the Pods project to the disk.
@specs_by_target ||= @resolver.resolve #
# @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 end
# @return [Array<Specification>] All dependencies that have been resolved. # Writes the Podfile and the {Sandbox} lock files.
def specifications #
specs_by_target.values.flatten # @return [void]
end #
# @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 # UI.message "- Writing Manifest in #{UI.path sandbox.manifest_path}" do
# dependency that is not a download-only one. # @lockfile.write_to_disk(sandbox.manifest_path)
def pods # end
pods_by_target.values.flatten.uniq
end end
def pods_by_target # Integrates the user project.
@pods_by_spec = {} #
result = {} # The following actions are performed:
specs_by_target.each do |target_definition, specs| # - libraries are added.
@pods_by_spec[target_definition.platform] = {} # - the build script are added.
result[target_definition] = specs.map do |spec| # - the xcconfig files are set.
if spec.local? #
@sandbox.locally_sourced_pod_for_spec(spec, target_definition.platform) # @return [void]
else #
@sandbox.local_pod_for_spec(spec, target_definition.platform) # @todo [#397] The libraries should be cleaned and the re-added on every
end # installation. Maybe a clean_user_project phase should be added.
end.uniq.compact #
end # @todo [#588] The resources should be added through a build phase instead
result # of using a script.
#
def integrate_user_project
UserProjectIntegrator.new(podfile, pods_project, config.project_root).integrate! if config.integrate_targets?
end end
end end
end end
module Pod module Pod
class Installer 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 # target in Pods project. Every target is generated from a target
# definition of the Podfile. # definition of the Podfile.
# #
class TargetInstaller class TargetInstaller
include Config::Mixin
# @return [Podfile]
#
# TODO: is really needed to pass the podfile?
#
attr_reader :podfile
# @return [Project] The Pods project. # @return [Project] The Pods project.
# #
attr_reader :project attr_reader :project
# @return [TargetDefinition] The target definition whoose target needs to # @return [TargetDefinition] The target definition whose target needs to
# be generated. # be generated.
# #
attr_reader :target_definition attr_reader :target_definition
def initialize(podfile, project, target_definition) # @param [Array<LocalPod>] pods the pods are required by the target
@podfile = podfile # definition of this installer.
@project = project #
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 @target_definition = target_definition
@pods = pods
end end
# @return [void] Creates the target in the Pods project and its support # Creates the target in the Pods project and its support files.
# files.
# #
# @param [Array<LocalPod>] pods The pods are required by the target # @return [void]
# definition of this installer.
# #
# @param [Sandbox] sandbox The sanbox where the support files def install!
# should be generated. 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) def prefix_header_filename
self.requires_arc = pods.any? { |pod| pod.requires_arc? } library.prefix_header_name
end
@target = @project.add_pod_target(@target_definition.label, @target_definition.platform) #-----------------------------------------------------------------------#
source_file_descriptions = [] # @!group Installation steps
pods.each { |p| p.add_build_files_to_target(@target) }
support_files_group = @project.support_files_group.new_group(@target_definition.label) private
target_support_files.each { |path| support_files_group.new_file(path) }
xcconfig_file = support_files_group.files.find { |f| f.path == @target_definition.xcconfig_name } # Adds the library for the {#target_definition} to the Pods
configure_build_configurations(xcconfig_file, sandbox) # project.
create_files(pods, sandbox) #
# @return [void]
#
def add_target
@library = @project.add_pod_library(target_definition)
@target = @library.target
end end
# @return [PBXNativeTarget] The target generated by the installation # Adds the build files of the pods to the target.
# process.
# #
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. @target.build_configurations.each do |c|
# c.base_configuration_reference = xcconfig_file_ref
def generate_xcconfig(pods, sandbox) c.build_settings.merge!(set)
xcconfig = Xcodeproj::Config.new({ end
'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
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) # @return [void]
@copy_resources_script ||= Generator::CopyResourcesScript.new(pods.map { |p| p.relative_resource_files }.flatten) #
end def create_xcconfig_file
UI.message "- Generating xcconfig file at #{UI.path(library.xcconfig_path)}" do
def bridge_support_generator_for(pods, sandbox) gen = Generator::XCConfig.new(sandbox, pods, library.relative_pods_root)
Generator::BridgeSupport.new(pods.map do |pod| gen.set_arc_compatibility_flag = target_definition.podfile.set_arc_compatibility_flag?
pod.relative_header_files.map { |header| sandbox.root + header } gen.save_as(library.xcconfig_path)
end.flatten) library.xcconfig = gen.xcconfig
end
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. # Creates a prefix header file which imports `UIKit` or `Cocoa`. This
def prefix_header_filename # file also include any prefix header content reported by the
@target_definition.prefix_header_name # 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 end
# TODO move out to Generator::PrefixHeader # Generates the bridge support metadata if requested by the {Podfile}.
def save_prefix_header_as(pathname, pods) #
pathname.open('w') do |header| # @note the bridge support metadata is added to the resources of the
header.puts "#ifdef __OBJC__" # library because it is needed for environments interpreted at
header.puts "#import #{@target_definition.platform == :ios ? '<UIKit/UIKit.h>' : '<Cocoa/Cocoa.h>'}" # runtime.
header.puts "#endif" #
pods.each do |pod| # @return [void]
if prefix_header_contents = pod.top_specification.prefix_header_contents #
header.puts def create_bridge_support_file
header.puts prefix_header_contents if target_definition.podfile.generate_bridge_support?
elsif prefix_header = pod.prefix_header_file UI.message "- Generating BridgeSupport metadata at #{UI.path library.bridge_support_path}" do
header.puts generator = Generator::BridgeSupport.new(pods.map do |pod|
header.puts prefix_header.read pod.relative_header_files.map { |header| sandbox.root + header }
end end.flatten)
generator.save_as(library.bridge_support_path)
copy_resources_script.resources << library.bridge_support_name
end end
end end
end end
def target_support_files # Creates a script that copies the resources to the bundle of the client
[:copy_resources_script_name, :prefix_header_name, :xcconfig_name].map { |file| @target_definition.send(file) } # target.
end #
# @todo This should be replaced by an Xcode copy resources build phase.
def configure_build_configurations(xcconfig_file, sandbox) #
@target.build_configurations.each do |config| # @return [void]
config.base_configuration_reference = xcconfig_file #
config.build_settings['OTHER_LDFLAGS'] = '' def create_copy_resources_script
config.build_settings['GCC_PREFIX_HEADER'] = @target_definition.prefix_header_name UI.message "- Generating copy resources script at #{UI.path(library.copy_resources_script_path)}" do
config.build_settings['PODS_ROOT'] = '${SRCROOT}' copy_resources_script.resources << pods.map { |p| p.relative_resource_files }.flatten
config.build_settings['PODS_HEADERS_SEARCH_PATHS'] = '${PODS_BUILD_HEADERS_SEARCH_PATHS}' copy_resources_script.save_as(library.copy_resources_script_path)
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'] = @target_definition.inhibit_all_warnings? ? 'YES' : 'NO'
end end
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) attr_reader :library
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?
UI.message "- Generating xcconfig file at #{UI.path(sandbox.root + @target_definition.xcconfig_name)}" do # @return [PBXNativeTarget] the target generated by the installation
generate_xcconfig(pods, sandbox) # process.
xcconfig.save_as(sandbox.root + @target_definition.xcconfig_name) #
@target_definition.xcconfig = xcconfig # @note Generated by the {#add_target} step.
end #
attr_reader :target
UI.message "- Generating prefix header at #{UI.path(sandbox.root + @target_definition.prefix_header_name)}" do # @return [PBXFileReference] the file reference to the xcconfig file of
save_prefix_header_as(sandbox.root + @target_definition.prefix_header_name, pods) # the target.
end #
# @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 # @!group Private helpers.
end
private private
def quoted(strings) # @return [Sandbox] sandbox the sandbox where the support files should
strings.map { |s| "\"#{s}\"" } # be generated.
#
def sandbox
project.sandbox
end end
def default_ld_flags # Creates and caches the copy resource script.
flags = %w{-ObjC} #
flags << '-fobjc-arc' if @podfile.set_arc_compatibility_flag? && self.requires_arc # @return [CopyResourcesScript]
flags.join(" ") #
def copy_resources_script
@copy_resources_script ||= Generator::CopyResourcesScript.new
end end
end end
end end
......
...@@ -8,20 +8,35 @@ module Pod ...@@ -8,20 +8,35 @@ module Pod
class Installer class Installer
# The {UserProjectIntegrator} integrates the libraries generated by # 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 class UserProjectIntegrator
include Pod::Config::Mixin
# @return [Podfile] the podfile that should be integrated with the user # @return [Podfile] the podfile that should be integrated with the user
# projects. # projects.
# #
attr_reader :podfile attr_reader :podfile
# @param [Podfile] podfile @see #podfile. # @return [Project] the pods project which contains the libraries to
# integrate.
# #
def initialize(podfile) attr_reader :pods_project
@podfile = podfile
# @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 end
# Integrates the user projects associated with the {TargetDefinitions} # Integrates the user projects associated with the {TargetDefinitions}
...@@ -30,75 +45,117 @@ module Pod ...@@ -30,75 +45,117 @@ module Pod
# @return [void] # @return [void]
# #
def integrate! def integrate!
create_workspace! create_workspace
target_integrators.map(&:integrate!) integrate_user_targets
warn_about_empty_podfile
end 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] # @return [void]
# #
def create_workspace! def create_workspace
workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path) workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
[pods_project_path, *user_project_paths].each do |project_path| [pods_project.path, *user_project_paths].each do |project_path|
project_path = project_path.relative_path_from(config.project_root).to_s path = project_path.relative_path_from(workspace_path.dirname).to_s
workspace << project_path unless workspace.include?(project_path) workspace << path unless workspace.include?(path)
end
unless workspace_path.exist?
UI.notice "From now on use `#{workspace_path.basename}'."
end end
workspace.save_as(workspace_path) workspace.save_as(workspace_path)
UI.notice "From now on use `#{workspace_path.basename}'." unless workspace_path.exist?
end end
# @return [Pathname] the path where the workspace containing the Pods # Integrates the targets of the user projects with the libraries
# project and the user projects should be saved. # generated from the {Podfile}.
# #
def workspace_path # @note {TargetDefinition} without dependencies are skipped prevent
podfile.workspace || raise(Informative, "Could not automatically select an Xcode workspace. " \ # creating empty libraries for targets definitions which are only
"Specify one in your Podfile.") # 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 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 def warn_about_empty_podfile
config.project_root + "Pods/Pods.xcodeproj" unless podfile.target_definitions.values.any?{ |td| !td.empty? }
UI.warn "[!] The Podfile does not contain any dependency."
end
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 def workspace_path
@target_integrators ||= @podfile.target_definitions.values.map do |definition| if podfile.workspace_path
TargetIntegrator.new(definition) unless definition.empty? podfile.workspace_path
end.compact 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 end
# @return [Array<Pathname>] the paths of all the user projects referenced # @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 def user_project_paths
@podfile.target_definitions.values.map do |td| pods_project.libraries.map do |lib|
next if td.empty? # TODO: remove
td.user_project.path next if lib.target_definition.empty?
lib.user_project_path
end.compact.uniq end.compact.uniq
end end
#-------------------------------------------------------------------------# #-----------------------------------------------------------------------#
# This class is responsible for integrating the library generated by a # This class is responsible for integrating the library generated by a
# {TargetDefinition} with its destination project. # {TargetDefinition} with its destination project.
# #
class TargetIntegrator class TargetIntegrator
include Pod::Config::Mixin
# @return [TargetDefinition] # @return [Library] the library that should be integrated.
# the target definition whose library 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) def initialize(library)
@target_definition = target_definition @library = library
end end
# Integrates the user project targets. Only the targets that do **not** # Integrates the user project targets. Only the targets that do **not**
...@@ -109,108 +166,108 @@ module Pod ...@@ -109,108 +166,108 @@ module Pod
# #
def integrate! def integrate!
return if targets.empty? return if targets.empty?
message = "Integrating `#{target_definition.lib_name}' into " \ UI.section(integration_message) do
"#{'target'.pluralize(targets.size)} `#{targets.map(&:name).to_sentence}' " \
"of project #{UI.path user_project_path}."
UI.section(message) do
add_xcconfig_base_configuration add_xcconfig_base_configuration
add_pods_library add_pods_library
add_copy_resources_script_phase add_copy_resources_script_phase
user_project.save_as(target_definition.user_project.path) save_user_project
end end
end end
# @return [Xcodeproj::Project] # @return [Array<PBXNativeTarget>] the list of targets that the Pods
# the project that will be integrated. # 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 def user_project
@user_project ||= Xcodeproj::Project.new(user_project_path) library.user_project
end end
# Returns the path of the user project that the {TargetDefinition} # @return [String] a string representation suitable for debugging.
# 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 inspect
# "#<#{self.class} for target `#{target_definition.label}'>"
def user_project_path end
path = target_definition.user_project.path
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? # @!group Integration steps
raise Informative, "The Xcode project `#{path}' does not exist."
end
path private
end
# Returns a list of the targets from the project of {TargetDefinition} # Adds the `xcconfig` configurations files generated for the current
# that needs to be integrated. # {TargetDefinition} to the build configurations of the targets that
# should be integrated.
# #
# The method first looks if there is a target specified with the # @note It also checks if any build setting of the build
# `link_with` option of the {TargetDefinition}. Otherwise it looks for # configurations overrides the `xcconfig` file and warns the
# the target that has the same name of the target definition. # user.
# Finally if no target was found the first encountered target is
# returned (it is assumed to be the one to integrate in simple
# projects).
# #
# In addition this will only return targets that do **not** already # @todo If the xcconfig is already set don't override it and inform
# have the Pods library in their frameworks build phase. # the user.
# #
# @return [Array<PBXNativeTarget>] # @return [void]
# the list of targets that the Pods lib should be linked with.
# #
def targets def add_xcconfig_base_configuration
unless @targets xcconfig = user_project.new_file(library.xcconfig_relative_path)
if link_with = target_definition.link_with targets.each do |target|
targets = user_project.targets.select { |t| link_with.include? t.name } check_overridden_build_settings(library.xcconfig, target)
raise Informative, "Unable to find a target named `#{link_with.to_sentence}` to link with target definition `#{target_definition.name}`" if targets.empty? target.build_configurations.each do |config|
elsif target_definition.name != :default config.base_configuration_reference = xcconfig
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 }
end end
end end
@targets
end end
#@!group Integration # Adds a file reference to the library of the {TargetDefinition} and
# adds it to the frameworks build phase of the targets.
# Adds the `xcconfig` configurations files generated for the current
# {TargetDefinition} to the build configurations of the targets that
# should be integrated.
# #
# It also checks if any build setting of the build configurations # @return [void]
# overrides the `xcconfig` file and warns the user.
# #
# TODO: If the xcconfig is already set don't override it and inform the def add_pods_library
# user. 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] # @return [void]
# #
def add_xcconfig_base_configuration def add_copy_resources_script_phase
xcconfig_ref = user_project.new_file(target_definition.xcconfig_relative_path)
targets.each do |target| targets.each do |target|
check_overridden_build_settings(target_definition.xcconfig, target) phase = target.new_shell_script_build_phase('Copy Pods Resources')
target.build_configurations.each do |config| path = library.copy_resources_script_relative_path
config.base_configuration_reference = xcconfig_ref phase.shell_script = %{"#{path}"\n}
end
end end
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 # Informs the user about any build setting of the target which might
# override the given xcconfig file. # override the given xcconfig file.
# #
...@@ -231,40 +288,25 @@ module Pod ...@@ -231,40 +288,25 @@ module Pod
configs_by_overridden_key.each do |key, config_names| configs_by_overridden_key.each do |key, config_names|
name = "#{target.name} [#{config_names.join(' - ')}]" name = "#{target.name} [#{config_names.join(' - ')}]"
actions = [ "Use the `$(inherited)' flag, or", "Remove the build settings from the target." ] actions = [
UI.warn("The target `#{name}' overrides the `#{key}' build setting defined in `#{target_definition.xcconfig_relative_path}'.", 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 end
end end
# Adds a file reference to the library of the {TargetDefinition} and # @return [String] the message that should be displayed for the target
# adds it to the frameworks build phase of the targets. # integration.
#
# @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 [void] def integration_message
# "Integrating `#{library.name}' into " \
def add_copy_resources_script_phase "#{'target'.pluralize(targets.size)} " \
targets.each do |target| "`#{targets.map(&:name).to_sentence}` " \
phase = target.new_shell_script_build_phase('Copy Pods Resources') "of project #{UI.path library.user_project_path}."
phase.shell_script = %{"#{target_definition.copy_resources_script_relative_path}"\n}
end
end
def inspect
"#<#{self.class} for target `#{target_definition.label}'>"
end end
end end
end end
......
...@@ -60,7 +60,7 @@ module Pod ...@@ -60,7 +60,7 @@ module Pod
# computed values. In other words, it should be immutable. # computed values. In other words, it should be immutable.
# #
def initialize(specification, sandbox, platform) 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) @top_specification.activate_platform(platform)
@specifications = [] << specification @specifications = [] << specification
end end
...@@ -81,7 +81,7 @@ module Pod ...@@ -81,7 +81,7 @@ module Pod
# @raise {Informative} If the specification is not part of the same pod. # @raise {Informative} If the specification is not part of the same pod.
# #
def add_specification(spec) def add_specification(spec)
unless spec.top_level_parent == top_specification unless spec.root == top_specification
raise Informative, raise Informative,
"[Local Pod] Attempt to add a specification from another pod" "[Local Pod] Attempt to add a specification from another pod"
end end
...@@ -105,7 +105,7 @@ module Pod ...@@ -105,7 +105,7 @@ module Pod
# #
def to_s def to_s
s = top_specification.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 s
end end
...@@ -300,7 +300,20 @@ module Pod ...@@ -300,7 +300,20 @@ module Pod
# @return [Array<Pathname>] The paths of the resources. # @return [Array<Pathname>] The paths of the resources.
# #
def resource_files 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 end
# @return [Array<Pathname>] The *relative* paths of the resources. # @return [Array<Pathname>] The *relative* paths of the resources.
...@@ -361,7 +374,14 @@ module Pod ...@@ -361,7 +374,14 @@ module Pod
end end
def xcconfig 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 end
# Computes the paths of all the public headers of the pod including every # Computes the paths of all the public headers of the pod including every
...@@ -440,7 +460,7 @@ module Pod ...@@ -440,7 +460,7 @@ module Pod
"project before adding the build files to the target." "project before adding the build files to the target."
end end
file_references_by_spec.each do |spec, file_reference| 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
end end
...@@ -498,7 +518,7 @@ module Pod ...@@ -498,7 +518,7 @@ module Pod
dir = spec.header_dir ? (headers_sandbox + spec.header_dir) : headers_sandbox dir = spec.header_dir ? (headers_sandbox + spec.header_dir) : headers_sandbox
paths.each do |from| paths.each do |from|
from_relative = from.relative_path_from(root) 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 (mappings[to.dirname] ||= []) << from
end end
end end
...@@ -516,8 +536,10 @@ module Pod ...@@ -516,8 +536,10 @@ module Pod
# included in the linker search paths. # included in the linker search paths.
# #
def headers_excluded_from_search_paths def headers_excluded_from_search_paths
paths = paths_by_spec(:exclude_header_search_paths, '*.{h,hpp,hh}') #TODO
paths.values.compact.uniq # paths = paths_by_spec(:exclude_header_search_paths, '*.{h,hpp,hh}')
# paths.values.compact.uniq
[]
end end
# @!group Paths Patterns # @!group Paths Patterns
...@@ -536,7 +558,7 @@ module Pod ...@@ -536,7 +558,7 @@ module Pod
specs = specs.sort_by { |s| s.name.length } specs = specs.sort_by { |s| s.name.length }
specs.each do |spec| 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? unless paths.empty?
paths_by_spec[spec] = paths - processed_paths paths_by_spec[spec] = paths - processed_paths
processed_paths += paths processed_paths += paths
......
module Pod module Pod
class LocalPod class LocalPod
# The {PathList} class is designed to perform multiple glob matches against # The PathList class is designed to perform multiple glob matches against
# a given directory. Basically, it generates a list of all the children # a given directory. Basically, it generates a list of all the children
# paths and matches the globs patterns against them, resulting in just # paths and matches the globs patterns against them, resulting in just one
# one access to the file system. # access to the file system.
# #
# @note A {PathList} once it has generated the list of the paths this is # @note A PathList once it has generated the list of the paths this is
# updated only if explicitly requested by calling # updated only if explicitly requested by calling
# {PathList#read_file_system} # {#read_file_system}
# #
class PathList class PathList
# @return [Pathname] The root of the list whose files and directories # @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 attr_accessor :root
# @param [Pathname] root The root of the PathList. # @param [Pathname] root The root of the PathList.
# #
def initialize(root) def initialize(root)
@root = root @root = root
end end
# @return [Array<String>] The list of absolute the path of all the files # @return [Array<String>] The list of absolute the path of all the files
# contained in {root}. # contained in {root}.
# #
def files def files
read_file_system unless @files read_file_system unless @files
...@@ -32,7 +32,7 @@ module Pod ...@@ -32,7 +32,7 @@ module Pod
end end
# @return [Array<String>] The list of absolute the path of all the # @return [Array<String>] The list of absolute the path of all the
# directories contained in {root}. # directories contained in {root}.
# #
def dirs def dirs
read_file_system unless @dirs read_file_system unless @dirs
...@@ -40,7 +40,7 @@ module Pod ...@@ -40,7 +40,7 @@ module Pod
end end
# @return [void] Reads the file system and populates the files and paths # @return [void] Reads the file system and populates the files and paths
# lists. # lists.
# #
def read_file_system def read_file_system
root_length = root.to_s.length+1 root_length = root.to_s.length+1
...@@ -53,22 +53,22 @@ module Pod ...@@ -53,22 +53,22 @@ module Pod
end end
# @return [Array<Pathname>] Similar to {glob} but returns the absolute # @return [Array<Pathname>] Similar to {glob} but returns the absolute
# paths. # paths.
# #
def glob(patterns, dir_pattern = nil, exclude_patterns = nil) def glob(patterns, dir_pattern = nil, exclude_patterns = nil)
relative_glob(patterns, dir_pattern, exclude_patterns).map {|p| root + p } relative_glob(patterns, dir_pattern, exclude_patterns).map {|p| root + p }
end end
# @return [Array<Pathname>] The list of relative paths that are case # @return [Array<Pathname>] The list of relative paths that are case
# insensitively matched by a given pattern. This method emulates # insensitively matched by a given pattern. This method emulates
# {Dir#glob} with the {File::FNM_CASEFOLD} option. # {Dir#glob} with the {File::FNM_CASEFOLD} option.
# #
# @param [String,Array<String>] patterns A signle {Dir#glob} like # @param [String,Array<String>] patterns
# pattern, or a list of patterns. # A signle {Dir#glob} like pattern, or a list of patterns.
# #
# @param [String] dir_pattern An optional pattern to append to a # @param [String] dir_pattern
# pattern, if it is the path to a # An optional pattern to append to a pattern, if it is the path
# directory. # to a directory.
# #
def relative_glob(patterns, dir_pattern = nil, exclude_patterns = nil) def relative_glob(patterns, dir_pattern = nil, exclude_patterns = nil)
return [] if patterns.empty? return [] if patterns.empty?
...@@ -94,9 +94,10 @@ module Pod ...@@ -94,9 +94,10 @@ module Pod
end end
# @return [Bool] Wether a path is a directory. The result of this method # @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) def directory?(sub_path)
sub_path = sub_path.to_s.downcase.sub(/\/$/, '') sub_path = sub_path.to_s.downcase.sub(/\/$/, '')
...@@ -104,8 +105,9 @@ module Pod ...@@ -104,8 +105,9 @@ module Pod
end end
# @return [Array<String>] An array of patterns converted from a # @return [Array<String>] An array of patterns converted from a
# {Dir.glob} pattern to patterns that {File.fnmatch} can handle. This # {Dir.glob} pattern to patterns that {File.fnmatch} can handle.
# is used by the {#relative_glob} method to emulate {Dir.glob}. # This is used by the {#relative_glob} method to emulate
# {Dir.glob}.
# #
# The expansion provides support for: # 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' 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 module Pod
# Provides support for generating the Pods project
#
class Project < Xcodeproj::Project class Project < Xcodeproj::Project
include Config::Mixin include Config::Mixin
attr_reader :support_files_group # @return [Sandbox] the sandbox that contains the project.
#
attr_reader :sandbox
def initialize(*) # @param [Sandbox] sandbox @see #sandbox
super #
podfile_path = config.project_podfile.relative_path_from(config.project_pods_root).to_s def initialize(sandbox)
podfile_ref = new_file(podfile_path) super(nil)
podfile_ref.xc_language_specification_identifier = 'xcode.lang.ruby' @sandbox = sandbox
new_group('Pods')
@support_files_group = new_group('Targets Support Files') @support_files_group = new_group('Targets Support Files')
@user_build_configurations = [] @user_build_configurations = []
@libraries = []
end end
def user_build_configurations=(user_build_configurations) # @return [Pathname] the path of the Pods project.
@user_build_configurations = user_build_configurations #
# The configurations at the top level only need to exist, they don't hold def path
# any build settings themselves, that's left to `add_pod_target`. sandbox.project_path
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 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 def pods
@pods ||= self['Pods'] || new_group('Pods') @pods ||= new_group('Pods')
end 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 def local_pods
@local_pods ||= self['Local Pods'] || new_group('Local Pods') @local_pods ||= new_group('Local Pods')
end 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) def add_spec_group(name, parent_group)
current_group = parent_group current_group = parent_group
group = nil group = nil
...@@ -59,19 +74,69 @@ module Pod ...@@ -59,19 +74,69 @@ module Pod
group group
end end
def add_pod_target(name, platform) #--------------------------------------#
target = new_target(:static_library, name, platform.name)
settings = {} #@!group Manipulating the project
if platform.requires_legacy_ios_archs?
settings['ARCHS'] = "armv6 armv7" # @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
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 unless platform
# TODO: add for osx as well raise Informative, "Missing platform for #{target_definition}"
settings['IPHONEOS_DEPLOYMENT_TARGET'] = platform.deployment_target.to_s
end end
settings = settings_for_platform(platform)
target = new_target(:static_library, name, platform.name)
target.build_settings('Debug').merge!(settings) target.build_settings('Debug').merge!(settings)
target.build_settings('Release').merge!(settings) target.build_settings('Release').merge!(settings)
...@@ -85,7 +150,271 @@ module Pod ...@@ -85,7 +150,271 @@ module Pod
end end
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 end
end end
require 'colored'
module Pod 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 class Resolver
include Config::Mixin
# @return [Bool] Whether the resolver should find the pods to install or include Config::Mixin
# the pods to update.
#
attr_accessor :update_mode
# @return [Bool] Whether the resolver should update the external specs # @return [Sandbox] the Sandbox used by the resolver to find external
# in the resolution process. # 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 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 # @return [Bool] whether the resolver should update the external specs
# dependencies. # in the resolution process. This option is used for detecting
# changes in with the Podfile without affecting the existing Pods
# installation
# #
attr_reader :sandbox # @note This option is used by `pod outdated`.
# @return [Array<Strings>] The name of the pods that have an
# external source.
# #
attr_reader :pods_from_external_sources # @todo: This implementation is not clean, because if the spec doesn't
# exists the sandbox will actually download and modify the
# @return [Array<Set>] A cache of the sets used to resolve the dependencies. # installation.
# #
attr_reader :cached_sets attr_accessor :update_external_specs
# @return [Source::Aggregate] A cache of the sources needed to find the # @param [Sandbox] sandbox @see sandbox
# podspecs. # @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) # @!group Resolution
@podfile = podfile
@lockfile = lockfile
@sandbox = sandbox
@update_external_specs = true
@cached_sets = {} public
@cached_sources = Source::Aggregate.new
end
# Identifies the specifications that should be installed according whether # Identifies the specifications that should be installed.
# the resolver is in update mode or not.
# #
# @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 def resolve
@cached_specs = {} @cached_sources = Source::Aggregate.new(config.repos_dir)
@cached_sets = {}
@cached_specs = {}
@specs_by_target = {} @specs_by_target = {}
@pods_from_external_sources = [] # @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
unless config.skip_repo_update? podfile.target_definitions.values.each do |target|
UI.section 'Updating spec repositories' do UI.section "Resolving dependencies for target `#{target.name}' (#{target.platform})" 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
@loaded_specs = [] @loaded_specs = []
find_dependency_specs(@podfile, target_definition.dependencies, target_definition) find_dependency_specs(podfile, target.dependencies, target)
@specs_by_target[target_definition] = @cached_specs.values_at(*@loaded_specs).sort_by(&:name) specs = cached_specs.values_at(*@loaded_specs).sort_by(&:name)
specs_by_target[target] = specs
end end
end end
@cached_specs.values.sort_by(&:name) cached_specs.values.sort_by(&:name)
@specs_by_target specs_by_target
end 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 def specs
@cached_specs.values.uniq specs_by_target.values.flatten.uniq
end 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) # TODO: Not sure if needed.
pods_to_install.include? name #
end # 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 # !@ Resolution context
# 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
private 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) # @todo Cache the sources globally?
set_name = dependency.name.split('/').first #
@cached_sets[set_name] ||= begin attr_accessor :cached_sources
if dependency.specification
Specification::Set::External.new(dependency.specification) # @return [Hash<String => Set>] A cache that keeps tracks of the sets
elsif external_source = dependency.external_source # loaded by the resolution process.
if update_mode && update_external_specs #
# Always update external sources in update mode. # @note Sets keep track of the TODO:
specification = external_source.specification_from_external(@sandbox, platform) #
else attr_accessor :cached_sets
# 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) attr_accessor :cached_specs
if dependency.subspec_dependency?
@cached_sets[dependency.top_level_spec_name] ||= set #
end #
set attr_writer :specs_by_target
else
@cached_sources.search(dependency)
end
end
end
# 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 # TODO: The set class should be aware whether it is in head mode.
# @param [TargetDefinition] target_definition
# #
# @return [void] # @return [void]
# #
def find_dependency_specs(dependent_specification, dependencies, target_definition) def find_dependency_specs(dependent_spec, dependencies, target_definition)
dependencies.each do |dependency| dependencies.each do |dependency|
# Replace the dependency with a more specific one if the pod is already installed. locked_dep = locked_dependencies.find { |ld| ld.name == dependency.name }
if !update_mode && @pods_to_lock.include?(dependency.name) dependency = locked_dep if locked_dep
dependency = lockfile.dependency_for_installed_pod_named(dependency.name)
end
UI.message("- #{dependency}", '', 2) do UI.message("- #{dependency}", '', 2) do
set = find_cached_set(dependency, target_definition.platform) set = find_cached_set(dependency)
set.required_by(dependency, dependent_specification.to_s) 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) unless @loaded_specs.include?(dependency.name)
spec = set.specification_by_name(dependency.name) spec = set.specification.subspec_by_name(dependency.name)
@pods_from_external_sources << spec.pod_name if dependency.external?
@loaded_specs << spec.name @loaded_specs << spec.name
@cached_specs[spec.name] = spec cached_specs[spec.name] = spec
# Configure the specification # @pods_from_external_sources << spec.root_name if dependency.external?
validate_platform(spec, target_definition)
spec.activate_platform(target_definition.platform) spec.activate_platform(target_definition.platform)
spec.version.head = dependency.head? 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 end
validate_platform(spec || @cached_specs[dependency.name], target_definition)
end end
end 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. # Ensures that a spec is compatible with the platform of a target.
# #
# @raises If the spec is not supported by the target. # @raises If the spec is not supported by the target.
# #
# @return [void]
#
def validate_platform(spec, target) def validate_platform(spec, target)
unless spec.available_platforms.any? { |platform| target.platform.supports?(platform) } 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 minimun requirement of #{spec.available_platforms.join(' - ')}.".red 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 end
end end
......
require 'fileutils' require 'fileutils'
module Pod 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 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 attr_reader :root
# @return [HeadersDirectory] the header directory for the Pods libraries.
#
attr_reader :build_headers attr_reader :build_headers
attr_reader :public_headers
BUILD_HEADERS_DIR = "BuildHeaders" # @return [HeadersDirectory] the header directory for the user targets.
PUBLIC_HEADERS_DIR = "Headers" #
attr_reader :public_headers
def initialize(path) # @param [String, Pathname] root @see root
@root = Pathname.new(path) #
@build_headers = HeadersDirectory.new(self, BUILD_HEADERS_DIR) # @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) @public_headers = HeadersDirectory.new(self, PUBLIC_HEADERS_DIR)
@cached_local_pods = {} @cached_local_pods = {}
@cached_locally_sourced_pods = {} @cached_locally_sourced_pods = {}
@predownloaded_pods = []
FileUtils.mkdir_p(@root) FileUtils.mkdir_p(@root)
end end
def implode # @return [Pathname] the path of the Pod project.
root.rmtree #
end
def project_path def project_path
root + "Pods.xcodeproj" root + "Pods.xcodeproj"
end 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 def prepare_for_install
build_headers.prepare_for_install build_headers.prepare_for_install
public_headers.prepare_for_install public_headers.prepare_for_install
end end
def local_pod_for_spec(spec, platform) # Removes the sandbox.
key = [spec.top_level_parent.name, platform.to_sym] #
(@cached_local_pods[key] ||= LocalPod.new(spec.top_level_parent, self, platform)).tap do |pod| # @return [void]
pod.add_specification(spec) #
end def implode
root.rmtree
end 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) def locally_sourced_pod_for_spec(spec, platform)
key = [spec.top_level_parent.name, platform.to_sym] key = [spec.root.name, platform.to_sym]
(@cached_locally_sourced_pods[key] ||= LocalPod::LocalSourcedPod.new(spec.top_level_parent, self, platform)).tap do |pod| 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) pod.add_specification(spec)
end end
end end
# @return [LocalPod]
#
def installed_pod_named(name, platform) def installed_pod_named(name, platform)
if spec_path = podspec_for_name(name) if spec_path = podspec_for_name(name)
key = [name, platform.to_sym] key = [name, platform.to_sym]
...@@ -54,23 +113,97 @@ module Pod ...@@ -54,23 +113,97 @@ module Pod
end end
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) def podspec_for_name(name)
path = root + "Local Podspecs/#{name}.podspec" path = root + "Local Podspecs/#{name}.podspec"
path.exist? ? path : nil path.exist? ? path : nil
end 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 end
#---------------------------------------------------------------------------#
# Provides support for managing a header directory. It also keeps track of
# the header search paths.
#
class HeadersDirectory 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 def root
@sandbox.root + @base_dir @sandbox.root + @relative_path
end 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) def add_file(namespace_path, relative_header_path)
namespaced_header_path = root + namespace_path namespaced_header_path = root + namespace_path
namespaced_header_path.mkpath unless File.exist?(namespaced_header_path) namespaced_header_path.mkpath unless File.exist?(namespaced_header_path)
...@@ -80,26 +213,27 @@ module Pod ...@@ -80,26 +213,27 @@ module Pod
namespaced_header_path + relative_header_path.basename namespaced_header_path + relative_header_path.basename
end end
#
#
def add_files(namespace_path, relative_header_paths) def add_files(namespace_path, relative_header_paths)
relative_header_paths.map { |path| add_file(namespace_path, path) } relative_header_paths.map { |path| add_file(namespace_path, path) }
end end
#
#
def search_paths def search_paths
@search_paths.uniq.map { |path| "${PODS_ROOT}/#{path}" } @search_paths.uniq.map { |path| "${PODS_ROOT}/#{path}" }
end end
# Adds an header search path to the sandbox. # Adds an header search path to the sandbox.
# #
# @param path [Pathname] The path tho add. # @param [Pathname] path
# the path tho add.
# #
# @return [void] # @return [void]
# #
def add_search_path(path) def add_search_path(path)
@search_paths << Pathname.new(@base_dir) + path @search_paths << Pathname.new(@relative_path) + path
end
def prepare_for_install
root.rmtree if root.exist?
end end
end end
end end
module Pod 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 Source
class << self
# @return [Pathname] The location of the repo. include Config::Mixin
#
attr_reader :repo
# @param [Pathname] repo @see repo.
#
def initialize(repo)
@repo = repo
end
# @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 [Array<Source>] the list of all the sources known to this
# installation of CocoaPods.
# @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.
# #
def all def all
@sources ||= dirs.map { |repo| Source.new(repo) }.sort_by(&:name) Aggregate.new(config.repos_dir).all
end
# @return [Array<String>] The names of all the pods available.
#
def all_pods
all.map(&:pods).flatten.uniq
end end
# @return [Array<Set>] The sets for all the pods available. # @return [Array<Specification::Set>] the list of all the specification
# # sets know to this installation of CocoaPods.
# @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.
# #
def all_sets def all_sets
pods_by_source = {} Aggregate.new(config.repos_dir).all_sets
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
end end
# @return [Set] A set for a given dependency including all the Sources # Search all the sources to match the set for the given dependency.
# that countain the Pod. #
# @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) def search(dependency)
sources = all.select { |s| !s.search(dependency).nil? } set = Aggregate.new(config.repos_dir).search(dependency)
raise(Informative, "[!] Unable to find a pod named `#{dependency.name}'".red) if sources.empty? raise Informative, "Unable to find a pod named `#{dependency.name}`" unless set
Specification::Set.new(dependency.top_level_spec_name, sources) set
end 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) # @raises If no source including the set can be found.
pods_by_source = {} #
result = [] # @note Full text search requires to load the specification for each
all.each { |s| pods_by_source[s] = s.search_by_name(query, full_text_search).map(&:name) } # pod, hence is considerably slower.
pod_names = pods_by_source.values.flatten.uniq #
pod_names.each do |pod| # @todo Move exceptions to clients?
sources = [] #
pods_by_source.each{ |source, pods| sources << source if pods.include?(pod) } # @return [Array<Set>] The sets that contain the search term.
result << Specification::Set.new(pod, sources) #
end 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? if result.empty?
extra = ", author, summary, or description" if full_text_search extra = ", author, summary, or description" if full_text_search
raise(Informative, "Unable to find a pod with name" \ raise Informative "Unable to find a pod with name#{extra} matching `#{query}'"
"#{extra} matching `#{query}'")
end end
result result
end 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 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 module Pod
require 'colored'
# Provides support for UI output. It provides support for nexted sections of
# information and for a verbose mode.
#
module UserInterface module UserInterface
autoload :UIPod, 'cocoapods/user_interface/ui_pod' require 'colored'
@title_colors = %w|yellow green| @title_colors = %w|yellow green|
@title_level = 0 @title_level = 0
...@@ -10,6 +13,7 @@ module Pod ...@@ -10,6 +13,7 @@ module Pod
@treat_titles_as_messages = false @treat_titles_as_messages = false
class << self class << self
include Config::Mixin include Config::Mixin
attr_accessor :indentation_level, :title_level attr_accessor :indentation_level, :title_level
...@@ -22,8 +26,8 @@ module Pod ...@@ -22,8 +26,8 @@ module Pod
# to their level. In normal mode titles are printed only if # to their level. In normal mode titles are printed only if
# they have nesting level smaller than 2. # they have nesting level smaller than 2.
# #
# TODO: refactor to title (for always visible titles like search) # TODO: Refactor to title (for always visible titles like search)
# and sections (titles that reppresent collapsible sections). # and sections (titles that represent collapsible sections).
# #
def section(title, verbose_prefix = '', relative_indentation = 0) def section(title, verbose_prefix = '', relative_indentation = 0)
if config.verbose? if config.verbose?
...@@ -110,11 +114,11 @@ module Pod ...@@ -110,11 +114,11 @@ module Pod
# that the user should take. # that the user should take.
# #
# @param [String] message The message to print. # @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] # return [void]
# #
def warn(message, actions) def warn(message, actions = [])
puts("\n[!] #{message}".yellow) puts("\n[!] #{message}".yellow)
actions.each do |action| actions.each do |action|
indented = wrap_string(action, " - ") indented = wrap_string(action, " - ")
...@@ -140,7 +144,7 @@ module Pod ...@@ -140,7 +144,7 @@ module Pod
if mode == :name if mode == :name
puts_indented set.name puts_indented set.name
else else
pod = UIPod.new(set) pod = Specification::Set::Presenter.new(set)
title("\n-> #{pod.name} (#{pod.version})".green, '', 1) do title("\n-> #{pod.name} (#{pod.version})".green, '', 1) do
puts_indented pod.summary puts_indented pod.summary
labeled('Homepage', pod.homepage) 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 module Pod
VERSION = '0.16.0.rc2' VERSION = '0.17.0.alpha'
end 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 @@ ...@@ -13,6 +13,7 @@
51075D541521D0C100E39B41 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 51075D521521D0C100E39B41 /* InfoPlist.strings */; }; 51075D541521D0C100E39B41 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 51075D521521D0C100E39B41 /* InfoPlist.strings */; };
51075D561521D0C100E39B41 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 51075D551521D0C100E39B41 /* main.m */; }; 51075D561521D0C100E39B41 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 51075D551521D0C100E39B41 /* main.m */; };
51075D5A1521D0C100E39B41 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 51075D591521D0C100E39B41 /* AppDelegate.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 */; }; A346497214F9BE9A0080D870 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A346497114F9BE9A0080D870 /* UIKit.framework */; };
A346497414F9BE9A0080D870 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A346497314F9BE9A0080D870 /* Foundation.framework */; }; A346497414F9BE9A0080D870 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A346497314F9BE9A0080D870 /* Foundation.framework */; };
A346497614F9BE9A0080D870 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A346497514F9BE9A0080D870 /* CoreGraphics.framework */; }; A346497614F9BE9A0080D870 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A346497514F9BE9A0080D870 /* CoreGraphics.framework */; };
...@@ -21,6 +22,16 @@ ...@@ -21,6 +22,16 @@
A346498214F9BE9A0080D870 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A346498114F9BE9A0080D870 /* AppDelegate.m */; }; A346498214F9BE9A0080D870 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A346498114F9BE9A0080D870 /* AppDelegate.m */; };
/* End PBXBuildFile section */ /* 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 */ /* Begin PBXFileReference section */
51075D4A1521D0C100E39B41 /* TestRunner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestRunner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 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>"; }; 51075D511521D0C100E39B41 /* TestRunner-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TestRunner-Info.plist"; sourceTree = "<group>"; };
...@@ -29,6 +40,7 @@ ...@@ -29,6 +40,7 @@
51075D571521D0C100E39B41 /* TestRunner-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TestRunner-Prefix.pch"; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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; }; 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; }; 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; }; A346497314F9BE9A0080D870 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
...@@ -56,6 +68,7 @@ ...@@ -56,6 +68,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
51E94E2A1644722C0035348C /* libSample Lib.a in Frameworks */,
A346497214F9BE9A0080D870 /* UIKit.framework in Frameworks */, A346497214F9BE9A0080D870 /* UIKit.framework in Frameworks */,
A346497414F9BE9A0080D870 /* Foundation.framework in Frameworks */, A346497414F9BE9A0080D870 /* Foundation.framework in Frameworks */,
A346497614F9BE9A0080D870 /* CoreGraphics.framework in Frameworks */, A346497614F9BE9A0080D870 /* CoreGraphics.framework in Frameworks */,
...@@ -86,9 +99,18 @@ ...@@ -86,9 +99,18 @@
name = "Supporting Files"; name = "Supporting Files";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
51E94E211644721F0035348C /* Products */ = {
isa = PBXGroup;
children = (
51E94E28164472200035348C /* libSample Lib.a */,
);
name = Products;
sourceTree = "<group>";
};
A346496214F9BE990080D870 = { A346496214F9BE990080D870 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
51E94E201644721F0035348C /* Sample Lib.xcodeproj */,
A346497714F9BE9A0080D870 /* SampleProject */, A346497714F9BE9A0080D870 /* SampleProject */,
51075D4F1521D0C100E39B41 /* TestRunner */, 51075D4F1521D0C100E39B41 /* TestRunner */,
A346497014F9BE9A0080D870 /* Frameworks */, A346497014F9BE9A0080D870 /* Frameworks */,
...@@ -192,6 +214,12 @@ ...@@ -192,6 +214,12 @@
mainGroup = A346496214F9BE990080D870; mainGroup = A346496214F9BE990080D870;
productRefGroup = A346496E14F9BE9A0080D870 /* Products */; productRefGroup = A346496E14F9BE9A0080D870 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectReferences = (
{
ProductGroup = 51E94E211644721F0035348C /* Products */;
ProjectRef = 51E94E201644721F0035348C /* Sample Lib.xcodeproj */;
},
);
projectRoot = ""; projectRoot = "";
targets = ( targets = (
A346496C14F9BE9A0080D870 /* SampleProject */, A346496C14F9BE9A0080D870 /* SampleProject */,
...@@ -200,6 +228,16 @@ ...@@ -200,6 +228,16 @@
}; };
/* End PBXProject section */ /* 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 */ /* Begin PBXResourcesBuildPhase section */
51075D481521D0C100E39B41 /* Resources */ = { 51075D481521D0C100E39B41 /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
......
Subproject commit ddda7566d193fe0e5fa1c419f5b5239ef964f263 Subproject commit 84ea24c2f3a5d463da1e7945c60fd3f33f73dee2
...@@ -8,7 +8,6 @@ Pod::Spec.new do |s| ...@@ -8,7 +8,6 @@ Pod::Spec.new do |s|
s.source = { :git => 'http://banana-corp.local/banana-lib.git', :tag => 'v1.0' } s.source = { :git => 'http://banana-corp.local/banana-lib.git', :tag => 'v1.0' }
s.source_files = 'Classes/*.{h,m}', 'Vendor' s.source_files = 'Classes/*.{h,m}', 'Vendor'
s.xcconfig = { 'OTHER_LDFLAGS' => '-framework SystemConfiguration' } s.xcconfig = { 'OTHER_LDFLAGS' => '-framework SystemConfiguration' }
s.clean_paths = "sub-dir"
s.prefix_header_file = 'Classes/BananaLib.pch' s.prefix_header_file = 'Classes/BananaLib.pch'
s.resources = "Resources/*.png" s.resources = "Resources/*.png"
s.dependency 'monkey', '~> 1.0.1', '< 1.0.9' s.dependency 'monkey', '~> 1.0.1', '< 1.0.9'
......
require File.expand_path('../../../spec_helper', __FILE__) require File.expand_path('../../../spec_helper', __FILE__)
module Pod module Pod
describe Command::List do describe "Command::List" do
extend SpecHelper::TemporaryRepos 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') out = run_command('list')
[ /ZBarSDK/, [ /BananaLib/,
/TouchJSON/, /JSONKit/,
/SDURLCache/,
/MagicalRecord/,
/A2DynamicDelegate/,
/\d+ pods were found/ /\d+ pods were found/
].each { |regex| out.should =~ regex } ].each { |regex| out.should =~ regex }
end end
it "lists the new pods" do it "returns the new pods" do
Time.stubs(:now).returns(Time.mktime(2012,2,3)) 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') out = run_command('list', 'new')
[ 'iCarousel', out.should.include('BananaLib')
'libPusher', out.should.not.include('JSONKit')
'SSCheckBoxView',
'KKPasscodeLock',
'SOCKit',
'FileMD5Hash',
'cocoa-oauth',
'iRate'
].each {|s| out.should.include s }
end end
end end
end end
......
...@@ -10,71 +10,76 @@ module Pod ...@@ -10,71 +10,76 @@ module Pod
config.repos_dir = SpecHelper.tmp_repos_path config.repos_dir = SpecHelper.tmp_repos_path
end end
def master_repo
fixture('spec-repos/master')
end
it "requires a spec-repo name" do it "requires a spec-repo name" do
lambda { command('push').validate! }.should.raise CLAide::Help lambda { command('push').validate! }.should.raise CLAide::Help
end end
it "complains if it can't find the repo" do it "complains if it can't find the repo" do
repo1 = add_repo('repo1', master_repo)
Dir.chdir(fixture('banana-lib')) do 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
end end
it "complains if it can't find a spec" do it "complains if it can't find a spec" do
repo1 = add_repo('repo1', master_repo) repo_make('test_repo')
lambda { run_command('push', 'repo1') }.should.raise Informative e = lambda { run_command('push', 'test_repo') }.should.raise Pod::Informative
e.message.should.match(/Couldn't find any .podspec/)
end end
it "it raises if the pod is not validated" do # TODO: the validation should not use the pod spec command
repo1 = add_repo('repo1', master_repo) xit "it raises if the specification doesn't validates" do
repo2 = add_repo('repo2', repo1.dir) repo_make('test_repo')
git_config('repo2', 'remote.origin.url').should == (tmp_repos_path + 'repo1').to_s Dir.chdir(temporary_directory) do
Dir.chdir(fixture('banana-lib')) do spec = "Spec.new do |s|; s.name = 'Broken'; end"
lambda { run_command('push', 'repo2', '--silent') }.should.raise Informative 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 end
# (repo1.dir + 'BananaLib/1.0/BananaLib.podspec').read.should.include 'Added!'
end end
#--------------------------------------#
before do before do
# prepare the repos repo_make('upstream')
@upstream = add_repo('upstream', master_repo) repo_clone('upstream', 'local_repo')
@local_repo = add_repo('local_repo', @upstream.dir)
git_config('local_repo', 'remote.origin.url').should == (tmp_repos_path + 'upstream').to_s
# prepare the spec # prepare the spec
spec = (fixture('spec-repos') + 'master/JSONKit/1.4/JSONKit.podspec').read 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_fix = spec.gsub(/https:\/\/github\.com\/johnezang\/JSONKit\.git/, fixture('integration/JSONKit').to_s)
spec_add = spec.gsub(/'JSONKit'/, "'PushTest'") 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) } File.open(temporary_directory + 'PushTest.podspec', 'w') {|f| f.write(spec_add) }
end end
it "refuses to push if the repo is not clean" do it "refuses to push if the repo is not clean" do
File.open(@local_repo.dir + 'README', 'w') {|f| f.write('Added!') } repo_make_readme_change('local_repo', 'dirty')
(@local_repo.dir + 'README').read.should.include 'Added!' Dir.chdir(temporary_directory) do
cmd = command('push', 'local_repo') cmd = command('push', 'local_repo')
cmd.expects(:validate_podspec_files).returns(true) cmd.expects(:validate_podspec_files).returns(true)
Dir.chdir(temporary_directory) { lambda { cmd.run }.should.raise Informative } e = lambda { cmd.run }.should.raise Pod::Informative
e.message.should.match(/repo not clean/)
(@upstream.dir + 'PushTest/1.4/PushTest.podspec').should.not.exist? end
(repo_path('upstream') + 'PushTest/1.4/PushTest.podspec').should.not.exist?
end end
it "sucessfully pushes a spec" do 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') cmd = command('push', 'local_repo')
Dir.chdir(repo_path 'upstream') { `git checkout -b tmp_for_push -q` }
cmd.expects(:validate_podspec_files).returns(true) cmd.expects(:validate_podspec_files).returns(true)
Dir.chdir(temporary_directory) { cmd.run } Dir.chdir(temporary_directory) { cmd.run }
UI.output.should.include('[Add] PushTest (1.4)') Pod::UI.output.should.include('[Add] PushTest (1.4)')
UI.output.should.include('[Fix] JSONKit (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. Dir.chdir(repo_path 'upstream') { `git checkout master -q` }
(@upstream.dir + 'PushTest/1.4/PushTest.podspec').read.should.include('PushTest') (repo_path('upstream') + 'PushTest/1.4/PushTest.podspec').read.should.include('PushTest')
end end
end end
end end
require File.expand_path('../../../spec_helper', __FILE__) require File.expand_path('../../../spec_helper', __FILE__)
describe "Pod::Command::Repo" do module Pod
describe Command::Repo do
before do describe "In general" do
config.repos_dir = SpecHelper.tmp_repos_path extend SpecHelper::Command
end extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
describe "In general" do
extend SpecHelper::TemporaryDirectory before do
extend SpecHelper::TemporaryRepos set_up_test_repo
config.repos_dir = SpecHelper.tmp_repos_path
it "adds a spec-repo" do end
run_command('repo', 'add', 'private', fixture('spec-repos/master'))
git_config('private', 'remote.origin.url').should == fixture('spec-repos/master').to_s
end
it "adds a spec-repo with on a specified branch" do it "updates a repository" do
repo1 = add_repo('repo1', fixture('spec-repos/master')) upstream = SpecHelper.temporary_directory + 'upstream'
Dir.chdir(repo1.dir) do FileUtils.cp_r(test_repo_path, upstream)
`git checkout -b my-branch >/dev/null 2>&1` Dir.chdir(test_repo_path) do
`git checkout master >/dev/null 2>&1` `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 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 it "lints a repository" do
repo1 = add_repo('repo1', fixture('spec-repos/master')) lambda { run_command('repo', 'lint', temporary_directory.to_s) }.should.not.raise
repo2 = add_repo('repo2', repo1.dir) end
make_change(repo1, 'repo1')
run_command('repo', 'update', 'repo2')
(repo2.dir + 'README').read.should.include 'Added!'
end
it "updates all the spec-repos" do it "adds a spec-repo" do
repo1 = add_repo('repo1', fixture('spec-repos/master')) run_command('repo', 'add', 'private', test_repo_path)
repo2 = add_repo('repo2', repo1.dir) Dir.chdir(config.repos_dir + 'private') do
repo3 = add_repo('repo3', repo1.dir) `git config --get remote.origin.url`.chomp.should == test_repo_path.to_s
make_change(repo1, 'repo1') end
run_command('repo', 'update') end
(repo2.dir + 'README').read.should.include 'Added!'
(repo3.dir + 'README').read.should.include 'Added!'
end
before do it "adds a spec-repo with a specified branch" do
config.repos_dir = fixture('spec-repos') repo1 = repo_make('repo1')
end 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 it "updates a spec-repo" do
cmd = command('repo', 'lint', 'master') repo1 = repo_make('repo1')
lambda { cmd.run }.should.raise Pod::Informative repo2 = repo_clone('repo1', 'repo2')
Pod::UI.output.should.include "Missing license type" 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
end
describe "Concerning a repo support" do describe "CocoaPods version" do
extend SpecHelper::TemporaryDirectory extend SpecHelper::Command
extend SpecHelper::TemporaryRepos extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
before do require 'yaml'
add_repo('repo1', fixture('spec-repos/master'))
FileUtils.rm_rf(versions_file)
versions_file.should.not.exist?
end
require 'yaml'
def versions_file before do
tmp_repos_path + "repo1/CocoaPods-version.yml" config.repos_dir = SpecHelper.tmp_repos_path
end @repo = repo_make('repo1')
end
def write_version_file(hash) def write_version_file(hash)
yaml = YAML.dump(hash) yaml = YAML.dump(hash)
File.open(versions_file, 'w') {|f| f.write(yaml) } @versions_file = tmp_repos_path + "repo1/CocoaPods-version.yml"
end File.open(@versions_file, 'w') {|f| f.write(yaml) }
end
it "it doesn't requires CocoaPods-version.yml" do it "it doesn't requires CocoaPods-version.yml" do
cmd = command('repo', 'update') cmd = command('repo', 'update')
lambda { cmd.check_versions(versions_file.dirname) }.should.not.raise lambda { cmd.check_versions(@repo) }.should.not.raise
end end
it "runs with a compatible repo" do it "runs with a compatible repo" do
write_version_file({'min' => "0.0.1"}) write_version_file({'min' => "0.0.1"})
cmd = command('repo', 'update') cmd = command('repo', 'update')
lambda { cmd.check_versions(versions_file.dirname) }.should.not.raise lambda { cmd.check_versions(@repo) }.should.not.raise
end end
it "raises if a repo is not compatible" do it "raises if a repo is not compatible" do
write_version_file({'min' => "999.0.0"}) write_version_file({'min' => "999.0.0"})
cmd = command('repo', 'update') cmd = command('repo', 'update')
lambda { cmd.check_versions(versions_file.dirname) }.should.raise Pod::Informative lambda { cmd.check_versions(@repo) }.should.raise Informative
end end
it "informs about a higher known CocoaPods version" do it "informs about a higher known CocoaPods version" do
write_version_file({'last' => "999.0.0"}) write_version_file({'last' => "999.0.0"})
cmd = command('repo', 'update') cmd = command('repo', 'update')
cmd.check_versions(versions_file.dirname) cmd.check_versions(@repo)
Pod::UI.output.should.include "Cocoapods 999.0.0 is available" UI.output.should.include "Cocoapods 999.0.0 is available"
end end
it "has a class method that returns if a repo is supported" do it "has a class method that returns if a repo is supported" do
write_version_file({'min' => "999.0.0"}) write_version_file({'min' => "999.0.0"})
Pod::Command::Repo.compatible?('repo1').should == false Command::Repo.compatible?('repo1').should == false
write_version_file({'min' => "0.0.1"}) write_version_file({'min' => "0.0.1"})
Pod::Command::Repo.compatible?('repo1').should == true Command::Repo.compatible?('repo1').should == true
end
end end
end end
end end
require File.expand_path('../../../spec_helper', __FILE__) require File.expand_path('../../../spec_helper', __FILE__)
module Pod describe Pod::Command::Setup do
describe Command::Setup do extend SpecHelper::Command
extend SpecHelper::TemporaryDirectory extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos extend SpecHelper::TemporaryRepos
before do before do
config.repos_dir = SpecHelper.tmp_repos_path config.repos_dir = SpecHelper.tmp_repos_path
end end
it "runs with correct parameters" do it "returns the read only URL of the `master` spec-repo" do
lambda { run_command('setup') }.should.not.raise cmd = Pod::Command::Setup.new(argv)
end cmd.url.should == 'https://github.com/CocoaPods/Specs.git'
end
it "complains for wrong parameters" do
lambda { run_command('setup', 'wrong') }.should.raise CLAide::Help it "returns the push URL of the `master' spec-repo" do
lambda { run_command('setup', '--wrong') }.should.raise CLAide::Help config.silent = true
end cmd = Pod::Command::Setup.new(argv('--push'))
cmd.url.should == 'git@github.com:CocoaPods/Specs.git'
it "returns the read only URL of the `master' spec-repo" do end
cmd = Command::Setup.new(argv)
cmd.url.should == 'https://github.com/CocoaPods/Specs.git' before do
end set_up_test_repo
Pod::Command::Setup.any_instance.stubs(:read_only_url).returns(test_repo_path.to_s)
it "returns the push URL of the `master' spec-repo" do config.repos_dir = SpecHelper.temporary_directory
config.silent = true end
cmd = Command::Setup.new(argv('--push'))
cmd.url.should == 'git@github.com:CocoaPods/Specs.git' it "runs with correct parameters" do
end lambda { run_command('setup') }.should.not.raise
end
class Command::Setup
def read_only_url; SpecHelper.fixture('spec-repos/master'); end it "creates the local spec-repos directory and creates a clone of the `master' repo" do
end output = run_command('setup')
output.should.include "Setup completed"
it "creates the local spec-repos directory and creates a clone of the `master' repo" do output.should.not.include "push"
output = run_command('setup') url = Dir.chdir(config.repos_dir + 'master') { `git config --get remote.origin.url`.chomp }
output.should.include "Setup completed" url.should == test_repo_path.to_s
output.should.not.include "push" end
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')
it "preserves push access for the `master' repo" do output.should.not.include "push"
output = run_command('setup') Dir.chdir(config.repos_dir + 'master') { `git remote set-url origin git@github.com:CocoaPods/Specs.git` }
output.should.not.include "push" command('setup').url.should == 'git@github.com:CocoaPods/Specs.git'
git('master', 'remote set-url origin git@github.com:CocoaPods/Specs.git') end
command('setup').url.should == 'git@github.com:CocoaPods/Specs.git'
end it "can run if needed" do
output = run_command('setup')
it "can run if needed" do output.should.include "Setup completed"
output = run_command('setup') Pod::UI.output = ''
output.should.include "Setup completed" command('setup').run_if_needed
UI.output = '' Pod::UI.output.should == ''
command('setup').run_if_needed
UI.output.should == ''
end
end end
end end
...@@ -22,7 +22,8 @@ module Pod ...@@ -22,7 +22,8 @@ module Pod
it "creates a new podspec stub file" do it "creates a new podspec stub file" do
run_command('spec', 'create', 'Bananas') run_command('spec', 'create', 'Bananas')
path = temporary_directory + 'Bananas.podspec' 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.name.should == 'Bananas'
spec.license.should == { :type => "MIT (example)" } spec.license.should == { :type => "MIT (example)" }
...@@ -31,7 +32,6 @@ module Pod ...@@ -31,7 +32,6 @@ module Pod
spec.homepage.should == 'http://EXAMPLE/Bananas' spec.homepage.should == 'http://EXAMPLE/Bananas'
spec.authors.should == { `git config --get user.name`.strip => `git config --get user.email`.strip} 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.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.source_files.should == ['Classes', 'Classes/**/*.{h,m}']
spec.public_header_files.should == [] spec.public_header_files.should == []
end end
......
...@@ -6,7 +6,7 @@ describe Pod::UI do ...@@ -6,7 +6,7 @@ describe Pod::UI do
before do before do
@set = Pod::Source.search(Pod::Dependency.new('CocoaLumberjack')) @set = Pod::Source.search(Pod::Dependency.new('CocoaLumberjack'))
Pod::Specification::Statistics.instance.cache_file = nil Pod::Specification::Set::Statistics.instance.cache_file = nil
end end
it "presents the name, version, description, homepage and source of a specification set" do it "presents the name, version, description, homepage and source of a specification set" do
...@@ -15,17 +15,12 @@ describe Pod::UI do ...@@ -15,17 +15,12 @@ describe Pod::UI do
output.should.include? 'CocoaLumberjack' output.should.include? 'CocoaLumberjack'
output.should.include? '1.0' output.should.include? '1.0'
output.should.include? '1.1' 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? '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'
output.should.include? 'https://github.com/robbiehanson/CocoaLumberjack.git' output.should.include? 'https://github.com/robbiehanson/CocoaLumberjack.git'
end 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 it "presents the stats of a specification set" do
repo = { "forks"=>42, "watchers"=>318, "pushed_at"=>"2011-01-26T19:06:43Z" } repo = { "forks"=>42, "watchers"=>318, "pushed_at"=>"2011-01-26T19:06:43Z" }
Octokit.expects(:repo).with("robbiehanson/CocoaLumberjack").returns(repo) 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 ...@@ -68,8 +68,7 @@ else
end end
# Note that we are *not* using the stubbed SpecHelper::Installer subclass. # 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(Pod::Sandbox.new(config.project_pods_root), podfile)
installer = Pod::Installer.new(resolver)
installer.install! installer.install!
result = installer.lockfile.to_hash result = installer.lockfile.to_hash
result['PODS'].should == ['SSToolkit (0.1.3)'] result['PODS'].should == ['SSToolkit (0.1.3)']
...@@ -86,8 +85,7 @@ else ...@@ -86,8 +85,7 @@ else
pod 'Reachability', :podspec => url pod 'Reachability', :podspec => url
end end
resolver = Pod::Resolver.new(podfile, nil, Pod::Sandbox.new(config.project_pods_root)) installer = SpecHelper::Installer.new(Pod::Sandbox.new(config.project_pods_root), podfile)
installer = SpecHelper::Installer.new(resolver)
installer.install! installer.install!
result = installer.lockfile.to_hash result = installer.lockfile.to_hash
result['PODS'].should == ['Reachability (1.2.3)'] result['PODS'].should == ['Reachability (1.2.3)']
...@@ -194,15 +192,19 @@ else ...@@ -194,15 +192,19 @@ else
result = installer.lockfile.to_hash result = installer.lockfile.to_hash
result['PODS'].should == [ result['PODS'].should == [
{ "ASIHTTPRequest (1.8.1)" => ["ASIHTTPRequest/ASIWebPageRequest (= 1.8.1)", { "ASIHTTPRequest (1.8.1)" =>
"ASIHTTPRequest/CloudFiles (= 1.8.1)", [ "ASIHTTPRequest/ASIWebPageRequest (= 1.8.1)",
"ASIHTTPRequest/S3 (= 1.8.1)", "ASIHTTPRequest/CloudFiles (= 1.8.1)",
"Reachability"]}, "ASIHTTPRequest/S3 (= 1.8.1)",
{ "ASIHTTPRequest/ASIWebPageRequest (1.8.1)" => ["Reachability"] }, "Reachability"
{ "ASIHTTPRequest/CloudFiles (1.8.1)" => ["Reachability"] }, ]
{ "ASIHTTPRequest/S3 (1.8.1)" => ["Reachability"] }, },
"JSONKit (1.4)", { "ASIHTTPRequest/ASIWebPageRequest (1.8.1)" => ["Reachability"] },
"Reachability (3.0.0)"] { "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)"] 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). # TODO might be nicer looking to not show the dependencies of the top level spec for each subspec (Reachability).
...@@ -302,7 +304,7 @@ else ...@@ -302,7 +304,7 @@ else
lockfile_contents = { lockfile_contents = {
'PODS' => [ 'PODS' => [
'JSONKit (999.999.999)', 'JSONKit (999.999.999)',
'Reachability (3.0.0)', 'Reachability (3.1.0)',
'SSZipArchive (0.1.1)', 'SSZipArchive (0.1.1)',
], ],
'DEPENDENCIES' => [ 'DEPENDENCIES' => [
......
...@@ -44,7 +44,7 @@ config = Pod::Config.instance ...@@ -44,7 +44,7 @@ config = Pod::Config.instance
config.silent = true config.silent = true
config.repos_dir = SpecHelper.tmp_repos_path config.repos_dir = SpecHelper.tmp_repos_path
config.project_root = SpecHelper.temporary_directory config.project_root = SpecHelper.temporary_directory
Pod::Specification::Statistics.instance.cache_file = nil Pod::Specification::Set::Statistics.instance.cache_file = nil
require 'tmpdir' require 'tmpdir'
...@@ -77,3 +77,4 @@ VCR.configure do |c| ...@@ -77,3 +77,4 @@ VCR.configure do |c|
c.allow_http_connections_when_no_cassette = true c.allow_http_connections_when_no_cassette = true
end end
require "active_support/core_ext/string/strip"
...@@ -3,10 +3,29 @@ module Bacon ...@@ -3,10 +3,29 @@ module Bacon
@needs_first_put = true @needs_first_put = true
module ColorOutput def self.color(color, string)
# Graciously yanked from https://github.com/zen-cms/Zen-Core and subsequently modified case color
# MIT License when :red
# Thanks, YorickPeterse! #:nodoc: "\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) def handle_specification(name)
if @needs_first_put if @needs_first_put
@needs_first_put = false @needs_first_put = false
...@@ -27,18 +46,20 @@ module Bacon ...@@ -27,18 +46,20 @@ module Bacon
error = yield error = yield
if !error.empty? if !error.empty?
puts "#{spaces}\e[31m- #{description} [FAILED]\e[0m" puts Bacon.color(:red, "#{spaces}- #{description} [FAILED]")
elsif disabled elsif disabled
puts "#{spaces}\e[33m- #{description} [DISABLED]\e[0m" puts Bacon.color(:yellow, "#{spaces}- #{description} [DISABLED]")
else else
puts "#{spaces}\e[32m- #{description}\e[0m" puts Bacon.color(:green, "#{spaces}- #{description}")
end end
end end
#:nodoc: #:nodoc:
def handle_summary def handle_summary
print ErrorLog if Backtraces 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" % puts "%d specifications (%d requirements), %d failures, %d errors" %
Counter.values_at(:specifications, :requirements, :failed, :errors) Counter.values_at(:specifications, :requirements, :failed, :errors)
end end
...@@ -48,7 +69,57 @@ module Bacon ...@@ -48,7 +69,57 @@ module Bacon
return ' ' * @specs_depth return ' ' * @specs_depth
end end
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 module FilterBacktraces
def handle_summary def handle_summary
...@@ -58,6 +129,9 @@ module Bacon ...@@ -58,6 +129,9 @@ module Bacon
super super
end end
end end
#---------------------------------------------------------------------------#
extend FilterBacktraces extend FilterBacktraces
class Context class Context
...@@ -67,3 +141,6 @@ module Bacon ...@@ -67,3 +141,6 @@ module Bacon
end end
end end
end end
require 'spec_helper/temporary_directory' require 'spec_helper/temporary_directory'
# Important
# Include with temporary directory
module SpecHelper module SpecHelper
def self.tmp_repos_path def self.tmp_repos_path
TemporaryRepos.tmp_repos_path TemporaryRepos.tmp_repos_path
...@@ -9,41 +12,64 @@ module SpecHelper ...@@ -9,41 +12,64 @@ module SpecHelper
extend Pod::Executable extend Pod::Executable
executable :git executable :git
def tmp_repos_path # @return [Pathname] The path for the repo with the given name.
SpecHelper.temporary_directory + 'cocoapods' #
def repo_path(name)
tmp_repos_path + name
end end
module_function :tmp_repos_path
alias_method :git_super, :git # Makes a repo with the given name.
def git(repo, command) #
Dir.chdir(tmp_repos_path + repo) do def repo_make(name)
if output = git_super(command) path = repo_path(name)
output.strip path.mkpath
end Dir.chdir(path) do
`git init`
repo_make_readme_change(name, 'Added')
`git add .`
`git commit -m "Initialized."`
end end
path
end end
def git_config(repo, attr) # Clones a repo to the given name.
git repo, "config --get #{attr}" #
def repo_clone(from_name, to_name)
Dir.chdir(tmp_repos_path) { `git clone #{from_name} #{to_name}` }
repo_path(to_name)
end end
def add_repo(name, from) def repo_make_readme_change(name, string)
command = Pod::Command.parse(['repo', 'add', name, from]) file = repo_path(name) + 'README'
command.run file.open('w') { |f| f << "#{string}" }
# The test branch is used by the push specs end
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 def test_repo_path
command repo_path('master')
end end
def make_change(repo, name) # Sets up a lighweight master repo in `tmp/cocoapods/master` with the
(repo.dir + 'README').open('w') { |f| f << 'Added!' } # contents of `spec/fixtures/spec-repos/test_repo`.
git(name, 'add README') #
git(name, 'commit -m "changed"') 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 end
#--------------------------------------#
def tmp_repos_path
SpecHelper.temporary_directory + 'cocoapods'
end
module_function :tmp_repos_path
def self.extended(base) def self.extended(base)
base.before do base.before do
tmp_repos_path.mkpath 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__) 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 it "returns the project" do
extend SpecHelper::TemporaryDirectory @installer.project.should == @project
end
before do it "returns the target_definition" do
@podfile = Pod::Podfile.new do @installer.target_definition.should == @target_definition
platform :ios
xcodeproj 'dummy'
end end
@target_definition = @podfile.target_definitions[:default]
@project = Pod::Project.new it "returns the pods of the target definition" do
@project.new_group('Targets Support Files') @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) def do_install!
FileUtils.cp_r(fixture('banana-lib'), TMP_POD_ROOT + 'BananaLib') # Prevent raise for missing dummy project.
@specification = fixture_spec('banana-lib/BananaLib.podspec') Pathname.any_instance.stubs(:exist?).returns(true)
@pods = [Pod::LocalPod.new(@specification, @sandbox, Pod::Platform.ios)] @pod.add_file_references_to_project(@project)
end @installer.install!
end
def do_install! it 'adds a new static library target to the project' do
@pods.each { |pod| pod.add_file_references_to_project(@project) } do_install!
@installer.install!(@pods, @sandbox) @project.targets.count.should == 1
end @project.targets.first.name.should == @target_definition.label
end
it 'adds a new static library target to the project' do it 'adds the source files of each pod to the target of the Pod library' do
do_install! do_install!
@project.targets.count.should == 1 names = @installer.target.source_build_phase.files.map { |bf| bf.file_ref.name }
@project.targets.first.name.should == @target_definition.label names.should == [ "Banana.m" ]
end end
it "adds the user's build configurations to the target" do it "adds file references for the support files of the target" do
@project.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'AppStore' => :release, 'Test' => :debug } do_install!
do_install! group = @project.support_files_group['Pods']
@project.targets.first.build_configurations.map(&:name).sort.should == %w{ AppStore Debug Release Test } group.children.map(&:display_name).sort.should == [
end "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 "adds the settings of the xcconfig that need to be overridden to the target" do
# it 'tells each pod to link its headers' do do_install!
# @pods[0].expects(:link_headers) build_configuration = @project.targets.first.build_configurations
# do_install! build_settings = build_configuration.first.build_settings
# end 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 it "adds the user's build configurations to the target" do
do_install! @project.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'AppStore' => :release, 'Test' => :debug }
@installer.xcconfig.to_hash['PODS_BUILD_HEADERS_SEARCH_PATHS'].should.include("\"#{@sandbox.build_headers.search_paths.join('" "')}\"") do_install!
end @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 it "does not enable the GCC_WARN_INHIBIT_ALL_WARNINGS flag by default" do
do_install! do_install!
@installer.xcconfig.to_hash['OTHER_LDFLAGS'].split(" ").should.not.include("-fobjc-arc") @installer.target.build_configurations.each do |config|
end 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 it "enables the GCC_WARN_INHIBIT_ALL_WARNINGS flag" do
@podfile.stubs(:set_arc_compatibility_flag? => true) @podfile.inhibit_all_warnings!
@specification.stubs(:requires_arc).returns(true) do_install!
do_install! @installer.target.build_configurations.each do |config|
@installer.xcconfig.to_hash['OTHER_LDFLAGS'].split(" ").should.include("-fobjc-arc") config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should == 'YES'
end end
end
it "does not enable the GCC_WARN_INHIBIT_ALL_WARNINGS flag by default" do it "creates and xcconfig file" do
do_install! do_install!
@installer.target.build_configurations.each do |config| file = config.sandbox.root + 'Pods.xcconfig'
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should == 'NO' xcconfig = Xcodeproj::Config.new(file)
xcconfig.to_hash['PODS_ROOT'].should == '${SRCROOT}/Pods'
end end
end
it "enables the GCC_WARN_INHIBIT_ALL_WARNINGS flag" do it "creates a prefix header, including the contents of the specification's prefix header" do
@podfile.inhibit_all_warnings! @pod.top_specification.prefix_header_contents = '#import "BlocksKit.h"'
do_install! do_install!
@installer.target.build_configurations.each do |config| prefix_header = config.sandbox.root + 'Pods-prefix.pch'
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should == 'YES' prefix_header.read.should == <<-EOS.strip_heredoc
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "BlocksKit.h"
EOS
end end
end
it "creates a prefix header, including the contents of the specification's prefix header file" do it "creates a bridge support file" do
do_install! Pod::Podfile.any_instance.stubs(:generate_bridge_support? => true)
prefix_header = @sandbox.root + 'Pods.pch' Pod::Generator::BridgeSupport.any_instance.expects(:save_as).once
@installer.save_prefix_header_as(prefix_header, @pods) do_install!
prefix_header.read.should == <<-EOS end
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import <BananaTree/BananaTree.h>
EOS
end
it "creates a prefix header, including the contents of the specification's prefix header" do it "creates a create copy resources script" do
do_install! do_install!
prefix_header = @sandbox.root + 'Pods.pch' script = config.sandbox.root + 'Pods-resources.sh'
@specification.prefix_header_contents = '#import "BlocksKit.h"' script.read.should.include?('logo-sidebar.png')
@installer.save_prefix_header_as(prefix_header, @pods) end
prefix_header.read.should == <<-EOS
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "BlocksKit.h"
EOS
end end
end end
require File.expand_path('../../../spec_helper', __FILE__) require File.expand_path('../../../spec_helper', __FILE__)
describe Pod::Installer::UserProjectIntegrator do describe UserProjectIntegrator = Pod::Installer::UserProjectIntegrator do
extend SpecHelper::TemporaryDirectory
describe "In general" do
before do extend SpecHelper::TemporaryDirectory
@sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject') before do
config.project_root = @sample_project_path.dirname @sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
sample_project_path = @sample_project_path sample_project_path = @sample_project_path
@podfile = Pod::Podfile.new do @podfile = Pod::Podfile.new do
platform :ios platform :ios
xcodeproj sample_project_path xcodeproj sample_project_path
pod 'JSONKit' pod 'JSONKit'
target :test_runner, :exclusive => true do target :test_runner, :exclusive => true do
link_with 'TestRunner' link_with 'TestRunner'
pod 'Kiwi' pod 'Kiwi'
end
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 end
@integrator = Pod::Installer::UserProjectIntegrator.new(@podfile) it "returns the podfile" do
end @integrator.podfile.should == @podfile
end
it "returns the path to the workspace from the Podfile" do it "returns the pods project" do
@integrator.workspace_path.should == config.project_root + 'SampleProject.xcworkspace' @integrator.pods_project.should == @pods_project
end end
it "raises if no workspace could be selected" do it "returns the project root" do
@podfile.stubs(:workspace) @integrator.project_root.should == @project_root
lambda { @integrator.workspace_path }.should.raise Pod::Informative end
end
it "returns the path to the Pods.xcodeproj document" do it "uses the path of the workspace defined in the podfile" do
@integrator.pods_project_path.should == config.project_root + 'Pods/Pods.xcodeproj' path = "a_path"
end @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 it "names the workspace after the user project if needed" do
@integrator.target_integrators.map(&:target_definition).should == @podfile.target_definitions.values @integrator.workspace_path.should == @sample_project_path.dirname + 'SampleProject.xcworkspace'
end end
before do it "raises if no workspace could be selected" do
@target_integrator = @integrator.target_integrators.first @integrator.expects(:user_project_paths).returns(%w[ project1 project2 ])
end lambda { @integrator.workspace_path }.should.raise Pod::Informative
end
it "returns the the user's project, that contains the target, from the Podfile" do it "returns the paths of the user projects" do
@target_integrator.user_project_path.should == @sample_project_path @integrator.user_project_paths.should == [ @sample_project_path ]
@target_integrator.user_project.should == Xcodeproj::Project.new(@sample_project_path) end
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 describe "Integration" do
@target_integrator.target_definition.user_project.path.stubs(:exist?).returns(false) extend SpecHelper::TemporaryDirectory
lambda { @target_integrator.user_project_path }.should.raise Pod::Informative
end
it "uses the target with the same name if the name is different from `:default'" do before do
target_integrator = @integrator.target_integrators[1] @sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
target_integrator.target_definition.stubs(:name).returns('TestRunner') sample_project_path = @sample_project_path
target_integrator.target_definition.stubs(:link_with).returns(nil) @podfile = Pod::Podfile.new do
target_integrator.targets.first.name.should == 'TestRunner' platform :ios
end 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 it "adds the Pods project to the workspace" do
target_integrator = @integrator.target_integrators.find { |ti| ti.target_definition.name == :test_runner } workspace = Xcodeproj::Workspace.new_from_xcworkspace(@integrator.workspace_path)
target_integrator.target_definition.stubs(:link_with).returns(nil) workspace.projpaths.find { |path| path =~ /Pods.xcodeproj/ }.should.not.be.nil
lambda { target_integrator.targets }.should.raise Pod::Informative 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
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 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 end
require File.expand_path('../../spec_helper', __FILE__) 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 module Pod
describe Installer do describe Installer do
before do before do
@sandbox = temporary_sandbox
config.repos_dir = fixture('spec-repos') config.repos_dir = fixture('spec-repos')
config.project_pods_root = @sandbox.root
FileUtils.cp_r(fixture('integration/JSONKit'), @sandbox.root + 'JSONKit')
end end
describe "by default" do describe "Concerning pre-installation computations" do
before do def generate_podfile(pods = ['JSONKit'])
podfile = Podfile.new do podfile = Podfile.new do
platform :ios platform :ios
xcodeproj 'MyProject' xcodeproj 'MyProject'
pod 'JSONKit' pods.each { |name| pod name }
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'
end end
FileUtils.cp_r(fixture('integration/ASIHTTPRequest'), @sandbox.root + 'ASIHTTPRequest') # @sandbox = temporary_sandbox
resolver = Resolver.new(podfile, nil, @sandbox) # config.project_pods_root = temporary_sandbox.root
installer = Installer.new(resolver) # FileUtils.cp_r(fixture('integration/JSONKit'), @sandbox.root + 'JSONKit')
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 # resolver = Resolver.new(podfile, nil, @sandbox)
podfile = Podfile.new do # @installer = Installer.new(resolver)
platform :ios # target_installer = @installer.target_installers.first
target :not_empty do # target_installer.install
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']
end 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) def generate_lockfile
installer.download_pod(pod) hash = {}
hash['PODS'] = []
hash['DEPENDENCIES'] = []
hash['SPEC CHECKSUMS'] = []
hash['COCOAPODS'] = Pod::VERSION
Pod::Lockfile.new(hash)
end end
end
describe "concerning multiple pods originating form the same spec" do
extend SpecHelper::Fixture
before do before do
sandbox = temporary_sandbox podfile = generate_podfile
Pod::Config.instance.project_pods_root = sandbox.root lockfile = generate_lockfile
Pod::Config.instance.integrate_targets = false @installer = Installer.new(@sandbox, podfile, lockfile)
podspec_path = fixture('integration/Reachability/Reachability.podspec') Project::Library.any_instance.stubs(:user_project_path).returns(config.project_root + 'test.xcodeproj')
podfile = Podfile.new do # TODO
platform :osx config.integrate_targets = false
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! @installer.install!
end end
it "cleans a pod only once" do # describe "#analyze" do
LocalPod.any_instance.expects(:clean!).once # it "doesn't affects creates changes in the file system" do
@installer.install! # end
end # end
it "adds the files of the pod to the Pods project only once" do # <<<<<<< HEAD
@installer.install! it "marks all pods as added if there is no lockfile" do
group = @installer.project.pods.groups.find { |g| g.name == 'Reachability' } @installer.pods_added_from_the_lockfile.should == ['JSONKit']
group.files.map(&:name).sort.should == ["Reachability.h", "Reachability.m"] # =======
# 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 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 end
describe "concerning namespacing" do # describe "by default" do
extend SpecHelper::Fixture # before do
# podfile = Podfile.new do
before do # platform :ios
sandbox = temporary_sandbox # xcodeproj 'MyProject'
Pod::Config.instance.project_pods_root = sandbox.root # pod 'JSONKit'
Pod::Config.instance.integrate_targets = false # end
podspec_path = fixture('chameleon')
podfile = Podfile.new do # @sandbox = temporary_sandbox
platform :osx # config.project_pods_root = temporary_sandbox.root
pod 'Chameleon', :local => podspec_path # FileUtils.cp_r(fixture('integration/JSONKit'), @sandbox.root + 'JSONKit')
end # @installer = Installer.new(@sandbox, podfile)
resolver = Resolver.new(podfile, nil, sandbox) # target_installer = @installer.target_installers.first
@installer = Installer.new(resolver) # target_installer.generate_xcconfig([], @sandbox)
end # @xcconfig = target_installer.xcconfig.to_hash
# end
it "namespaces local pods" do # it "generates a BridgeSupport metadata file from all the pod headers" do
@installer.install! # podfile = Podfile.new do
group = @installer.project['Local Pods'] # platform :osx
group.groups.map(&:name).sort.should == %w| Chameleon | # pod 'ASIHTTPRequest'
end # end
it "namespaces subspecs" do # FileUtils.cp_r(fixture('integration/ASIHTTPRequest'), @sandbox.root + 'ASIHTTPRequest')
@installer.install! # installer = Installer.new(@sandbox, podfile)
group = @installer.project['Local Pods/Chameleon'] # pods = installer.specifications.map do |spec|
group.groups.map(&:name).sort.should == %w| AVFoundation AssetsLibrary MediaPlayer MessageUI StoreKit UIKit | # LocalPod.new(spec, installer.sandbox, podfile.target_definitions[:default].platform)
end # end
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
end end
...@@ -81,14 +81,14 @@ describe Pod::LocalPod do ...@@ -81,14 +81,14 @@ describe Pod::LocalPod do
end end
it "can add it's source files to an Xcode project target" do 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) @pod.add_file_references_to_project(project)
project['Pods/BananaLib/Banana.h'].path.should == "BananaLib/Classes/Banana.h" project['Pods/BananaLib/Banana.h'].path.should == "BananaLib/Classes/Banana.h"
project['Pods/BananaLib/Banana.m'].path.should == "BananaLib/Classes/Banana.m" project['Pods/BananaLib/Banana.m'].path.should == "BananaLib/Classes/Banana.m"
end end
it "can add it's source files to a target with any specially configured compiler flags" do 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) target = project.new_target(:static, 'Pods', :ios)
@pod.top_specification.compiler_flags = '-d some_flag' @pod.top_specification.compiler_flags = '-d some_flag'
@pod.add_file_references_to_project(project) @pod.add_file_references_to_project(project)
...@@ -113,6 +113,8 @@ describe Pod::LocalPod do ...@@ -113,6 +113,8 @@ describe Pod::LocalPod do
end end
end end
#---------------------------------------------------------------------------#
describe "with installed source and multiple subspecs" do describe "with installed source and multiple subspecs" do
def assert_array_equals(expected, computed) 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__) require File.expand_path('../../spec_helper', __FILE__)
describe 'Pod::Project' do describe Pod::Project do
before do describe "In general" do
@project = Pod::Project.new before do
end @project = Pod::Project.new(config.sandbox)
end
it "adds the Podfile configured as a Ruby file" do it "returns the sandbox used for the project" do
f = @project['Podfile'] @project.sandbox.should == config.sandbox
f.name.should == 'Podfile' end
f.source_tree.should == 'SOURCE_ROOT'
f.xc_language_specification_identifier.should == 'xcode.lang.ruby'
f.path.should == '../Podfile'
end
it "adds a group to the `Pods' group" do it "creates the support file group on initialization" do
group = @project.add_spec_group('JSONKit', @project.pods) @project.support_files_group.name.should == 'Targets Support Files'
@project.pods.children.should.include?(group) end
g = @project['Pods/JSONKit']
g.name.should == 'JSONKit'
g.children.should.be.empty?
end
it "namespaces subspecs in groups" do it "returns its path" do
group = @project.add_spec_group('JSONKit/Subspec', @project.pods) @project.path.should == config.sandbox.project_path
@project.pods.groups.find { |g| g.name == 'JSONKit' }.children.should.include?(group) end
g = @project['Pods/JSONKit/Subspec']
g.name.should == 'Subspec'
g.children.should.be.empty?
end
# it "creates a copy build header phase which will copy headers to a specified path" do it "returns the `Pods` group" do
# @project.targets.new @project.pods.name.should == 'Pods'
# phase = @project.targets.first.copy_files_build_phases.new_pod_dir("SomePod", "Path/To/Source") end
# 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 "adds build configurations named after every configuration across all of the user's projects to a target" do it "returns the `Local Pods` group" do
@project.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'Test' => :debug, 'AppStore' => :release } @project.local_pods.name.should == 'Local Pods'
target = @project.add_pod_target('SomeTarget', Pod::Platform.ios) end
target.build_settings('Test')["VALIDATE_PRODUCT"].should == nil
target.build_settings('AppStore')["VALIDATE_PRODUCT"].should == "YES"
end
describe "concerning its :ios targets" do it "adds a group for a specification" do
it "sets VALIDATE_PRODUCT to YES for the Release configuration" do group = @project.add_spec_group('JSONKit', @project.pods)
target = Pod::Project.new.add_pod_target('Pods', Pod::Platform.ios) @project.pods.children.should.include?(group)
target.build_settings('Release')["VALIDATE_PRODUCT"].should == "YES" 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
end end
describe "concerning its :ios targets with a deployment target" do describe "Libraries" do
before 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 end
it "sets ARCHS to 'armv6 armv7' for both configurations if the deployment target is less than 4.3" do it "adds build configurations named after every configuration across all of the user's projects to a target" do
target = @project.add_pod_target('Pods', Pod::Platform.new(:ios, :deployment_target => "4.0")) @project.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'Test' => :debug, 'AppStore' => :release }
target.build_settings('Debug')["ARCHS"].should == "armv6 armv7" library = @project.add_pod_library(@target_definition)
target.build_settings('Release')["ARCHS"].should == "armv6 armv7" 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('Debug')["ARCHS"].should == "armv6 armv7"
target.build_settings('Release')["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")) before do
target.build_settings('Debug')["ARCHS"].should == "armv6 armv7" @lib = @project.add_pod_library(@target_definition)
target.build_settings('Release')["ARCHS"].should == "armv6 armv7" @target = @lib.target
end end
it "uses standard ARCHs if deployment target is 4.3 or above" do 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('Debug')["ARCHS"].should == "$(ARCHS_STANDARD_32_BIT)" @target.build_settings('Release')["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")) it "sets IPHONEOS_DEPLOYMENT_TARGET for iOS targets" do
target.build_settings('Debug')["ARCHS"].should == "$(ARCHS_STANDARD_32_BIT)" @target.build_settings('Debug')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.3"
target.build_settings('Release')["ARCHS"].should == "$(ARCHS_STANDARD_32_BIT)" @target.build_settings('Release')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.3"
end end
it "sets IPHONEOS_DEPLOYMENT_TARGET for both configurations" do it "returns the added libraries" do
target = @project.add_pod_target('Pods', Pod::Platform.new(:ios)) @project.libraries.should == [ @lib ]
target.build_settings('Debug')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.3" end
target.build_settings('Release')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.3" 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")) it "returns it target in the Pods project" do
target.build_settings('Debug')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.0" @lib.target.name.should == 'Pods'
target.build_settings('Release')["IPHONEOS_DEPLOYMENT_TARGET"].should == "4.0"
end 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
end end
...@@ -2,433 +2,439 @@ require File.expand_path('../../spec_helper', __FILE__) ...@@ -2,433 +2,439 @@ require File.expand_path('../../spec_helper', __FILE__)
module Pod module Pod
describe Resolver do describe Resolver do
before do describe "In general" do
config.repos_dir = fixture('spec-repos') before do
@podfile = Podfile.new do config.repos_dir = fixture('spec-repos')
platform :ios @podfile = Podfile.new do
pod 'BlocksKit' 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 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') ]) it "returns the sandbox" do
lambda { @resolver.resolve }.should.not.raise @resolver.sandbox.should == config.sandbox
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'
end 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 it "returns the podfile" do
@podfile = Podfile.new do @resolver.podfile.should == @podfile
platform :ios
pod 'RestKit'
end 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 it "returns the locked dependencies" do
@podfile = Podfile.new do @resolver.locked_dependencies.should == [Dependency.new('BlocksKit', '1.5.2')]
platform :ios end
pod do |s|
s.name = 'RestKit'
s.version = '0.10.0'
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| it "resolves the specification of the podfile" do
ns.dependency 'LibComponentLogging-NSLog', '>= 1.0.4' target_definition = @podfile.target_definitions[:default]
end specs = @resolver.resolve[target_definition]
s.subspec 'UI' specs.map(&:to_s).should == [
s.subspec 'ObjectMapping' do |os| "A2DynamicDelegate (2.0.2)",
os.subspec 'JSON' "BlocksKit (1.5.2)",
os.subspec 'XML' "libffi (3.0.11)"
os.subspec 'CoreData' ]
end
end
end 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 it "returns the resolved specifications grouped by target definition" do
@podfile = Podfile.new do @resolver.resolve
platform :ios target_definition = @podfile.target_definitions[:default]
pod 'MainSpec/FirstSubSpec', :git => 'GIT-URL' specs = @resolver.specs_by_target[target_definition]
end specs.map(&:to_s).should == [
spec = Spec.new do |s| "A2DynamicDelegate (2.0.2)",
s.name = 'MainSpec' "BlocksKit (1.5.2)",
s.version = '1.2.3' "libffi (3.0.11)"
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 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 it "returns all the resolved specifications" do
podfile = Podfile.new do @resolver.resolve
platform :ios @resolver.specs.map(&:class).uniq.should == [Specification]
pod 'FileMD5Hash' @resolver.specs.map(&:to_s).should == [
pod 'JSONKit', :head "A2DynamicDelegate (2.0.2)",
"BlocksKit (1.5.2)",
"libffi (3.0.11)"
]
end 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 it "it resolves specifications from external sources" do
podfile = Podfile.new do podspec = fixture('integration/Reachability/Reachability.podspec')
platform :ios podfile = Podfile.new do
pod 'JSONKit', "1.4" platform :ios
pod 'JSONKit', "1.5pre" pod "Reachability", :podspec => podspec
end
resolver = Resolver.new(config.sandbox, podfile)
resolver.resolve
resolver.specs.map(&:to_s).should == ['Reachability (3.0.0)']
end end
resolver = Resolver.new(podfile, nil, stub('sandbox'))
lambda {resolver.resolve}.should.raise Informative
end end
describe "Concerning Installation mode" do #-------------------------------------------------------------------------#
describe "Resolution" do
before do before do
config.repos_dir = fixture('spec-repos') config.repos_dir = fixture('spec-repos')
@podfile = Podfile.new do @podfile = Podfile.new do
platform :ios platform :ios, '6.0'
pod 'BlocksKit' pod 'BlocksKit', '1.5.2'
pod 'JSONKit'
end end
@specs = [ @resolver = Resolver.new(config.sandbox, @podfile)
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'))
end 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 it "holds the context state, such as cached specification sets" do
podfile = Podfile.new { platform :ios; pod 'JSONKit' } @resolver.resolve
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox')) cached_sets = @resolver.send(:cached_sets)
@resolver.resolve.values.flatten.map(&:name).should == %w{ JSONKit } 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 end
it "reinstalls pods updated in the Podfile" do it "raises once any of the dependencies does not match the platform of its podfile target" do
podfile = Podfile.new do Specification.any_instance.stubs(:available_platforms).returns([Platform.new(:ios, '999')])
platform :ios e = lambda { @resolver.resolve }.should.raise Informative
pod 'JSONKit', '1.5pre' e.message.should.match(/platform .* not compatible/)
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)"
end end
it "installs pods added to the Podfile" do it "does not raise if all dependencies are supported by the platform of the target definition" do
podfile = Podfile.new do lambda { @resolver.resolve }.should.not.raise
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)"
end end
it "handles head pods" do it "includes all the subspecs of a specification node" do
podfile = Podfile.new do @podfile = Podfile.new do
platform :ios platform :ios
pod 'JSONKit', :head # Existing pod switched to head mode pod 'RestKit'
pod 'libPusher', :head # New pod
end end
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox')) resolver = Resolver.new(config.sandbox, @podfile)
@resolver.resolve resolver.resolve.values.flatten.map(&:name).sort.should == %w{
@resolver.should_install?("JSONKit").should.be.true FileMD5Hash
@resolver.should_install?("libPusher").should.be.true 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 end
it "handles pods from external dependencies" do it "resolves subspecs with external constraints" do
podfile = Podfile.new do @podfile = Podfile.new do
platform :ios platform :ios
pod 'libPusher', :git => 'GIT-URL' pod 'MainSpec/FirstSubSpec', :git => 'GIT-URL'
end end
spec = Spec.new do |s| spec = Spec.new do |s|
s.name = 'libPusher' s.name = 'MainSpec'
s.version = '1.3' 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 end
podfile.dependencies.first.external_source.stubs(:specification_from_sandbox).returns(spec) ExternalSources::GitSource.any_instance.stubs(:specification).returns(spec)
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox')) resolver = Resolver.new(config.sandbox, @podfile)
@resolver.resolve resolver.resolve.values.flatten.map(&:name).sort.should == %w{ MainSpec/FirstSubSpec MainSpec/FirstSubSpec/SecondSubSpec }
@resolver.should_install?("JSONKit").should.be.false
end 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 podfile = Podfile.new do
platform :ios platform :ios
pod 'BlocksKit' pod 'FileMD5Hash'
pod 'JSONKit' pod 'JSONKit', :head
end end
config.skip_repo_update = false resolver = Resolver.new(config.sandbox, podfile)
Command::Repo.any_instance.expects(:run).never filemd5hash, jsonkit = resolver.resolve.values.first.sort_by(&:name)
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox')) filemd5hash.version.should.not.be.head
@resolver.resolve jsonkit.version.should.be.head
end 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 podfile = Podfile.new do
platform :ios platform :ios
pod 'BlocksKit' pod 'JSONKit', "1.4"
pod 'JSONKit' pod 'JSONKit', "1.5pre"
pod 'libPusher' # New pod
end end
config.skip_repo_update = false resolver = Resolver.new(config.sandbox, podfile)
Command::Repo::Update.any_instance.expects(:run).once e = lambda {resolver.resolve}.should.raise Pod::StandardError
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox')) e.message.should.match(/already activated version/)
@resolver.resolve
end end
it "doesn't update the repos if config indicate to skip it in any case" do xit "is robust against infinite loops" 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
end
describe "Concerning Update mode" do xit "takes into account locked dependencies" 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
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 end
it "respects the constraints of the podfile" do xit "transfers the head state of a dependency to a specification" 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
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 end
it "it always suggests to update pods in head mode" do xit "" 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 end
it "always updates the repos even if there is change in the pods" do xit "" 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 end
# TODO: stub the specification resolution for the sandbox xit "" do
xit "it always suggests to update pods from external sources" do
podfile = Podfile.new do
platform :ios
pod 'libPusher', :git => "example.com"
end
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.update_mode = true
@resolver.resolve
@resolver.should_install?("libPusher").should.be.true
end end
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
end end
require File.expand_path('../../spec_helper', __FILE__) require File.expand_path('../../spec_helper', __FILE__)
describe "Pod::Source" do module Pod
before do describe Pod::Source 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.
it "returns all the sources" do it "returns all the sources" do
Pod::Source.all.map(&:name).should == %w| master test_repo | 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 |
end end
it "raises if a specification set can't be found" do it "returns all the sets" do
lambda { Source.all_sets.map(&:name).should.include?('Chameleon')
dep = Pod::Dependency.new('DoesNotExist')
set = Pod::Source.search(dep)
}.should.raise Pod::Informative
end end
it "raises if a subspec can't be found" do it "searches for the set of a dependency" do
lambda { set = Source.search(Dependency.new('Chameleon'))
dep = Pod::Dependency.new('RestKit/DoesNotExist') set.class.should == Pod::Specification::Set
set = Pod::Source.search(dep) set.name.should == 'Chameleon'
}.should.raise Pod::Informative
end end
it "searches the sets by name" do it "searches sets by name" do
sets = Pod::Source.search_by_name('JSONKit') sets = Source.search_by_name('Chameleon')
sets.count.should == 1 sets.all?{ |s| s.class == Pod::Specification::Set}.should.be.true
set = sets.first sets.any?{ |s| s.name == 'Chameleon'}.should.be.true
set.name.should == 'JSONKit'
set.sources.map(&:name).should == %w| master test_repo |
end end
it "properly configures the sources of a set in search by name" do it "can perform a full text search of the sets" do
sets = Pod::Source.search_by_name('BananaLib') sets = Source.search_by_name('Drop in sharing', true)
sets.count.should == 1 sets.all?{ |s| s.class == Pod::Specification::Set}.should.be.true
set = sets.first sets.any?{ |s| s.name == 'ShareKit'}.should.be.true
set.name.should == 'BananaLib'
set.sources.map(&:name).should == %w| test_repo |
end end
end 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