Commit c84575a3 authored by Fabio Pelosin's avatar Fabio Pelosin

Merge branch 'pod_update'

* pod_update: (37 commits)
  [Specification] Provide a meaningful error message if a subspec can't be found.
  Update lockfiles of examples.
  Handle pods in (case-insensitive) alphabetical order.
  [outdated] Don’t show pods that can't be checked.
  Fix outdated command.
  Improve Podfile.lock version when the `:head` option is used.
  Improve error message when a local source dir doesn't exist.
  Refactor and cleanup command source files.
  Don't break when a command isn't recognized and trying to show a help banner.
  Changelog.
  [Specs] Minor fix for Lockfile.
  [LocalSource] Specs.
  Changelog.
  Changelog.
  [Lockfile] Fix specs for grammar change.
  [Lockfile] Grammar.
  [LocalSource] Tuning.
  [Lockfile] Encode checksums only if supported.
  [ExternalSources] Better robustness :-)
  [Lockfile] Save checksums as string.
  ...

Conflicts:
	CHANGELOG.md
	Gemfile.lock
parents 9078b30c 811f98c7
......@@ -4,14 +4,46 @@
###### Enhancements
- Improved installation process:
- The installed version of a Pod (recorded in `Podfile.lock`) is preserved
across installations and machines.
- Pods are reinstalled if the version required changes and is not compatible
with current installed one.
[#191](https://github.com/CocoaPods/CocoaPods/issues/191)
- Pods are reinstalled when their external source changes.
- The folders of the Pods not required anymore are removed.
[#298](https://github.com/CocoaPods/CocoaPods/issues/298)
- Introducing `pod update` that installs the dependencies of the Podfile
regardless of the contents of `Podfile.lock`.
[#131](https://github.com/CocoaPods/CocoaPods/issues/131)
- Introducing `pod outdated` that show the pods with known updates.
- Added `:local` option for dependencies.
[#458,](https://github.com/CocoaPods/CocoaPods/issues/458)
[#415,](https://github.com/CocoaPods/CocoaPods/issues/415)
[#156](https://github.com/CocoaPods/CocoaPods/issues/156)
- Added a meaningful error messages:
- in case a podspec couldn’t be found in the root of an external source.
[#385,](https://github.com/CocoaPods/CocoaPods/issues/385)
[#338,](https://github.com/CocoaPods/CocoaPods/issues/338)
[#337](https://github.com/CocoaPods/CocoaPods/issues/337)
- in case a subspec name is misspelled.
[#327](https://github.com/CocoaPods/CocoaPods/issues/327)
- in case a unrecognized commands/argument is provided.
- The subversion downloader now does an export instead of a checkout, which
makes it play nicer with SCMs that store metadata in each directory.
[#245](https://github.com/CocoaPods/CocoaPods/issues/245)
###### Bug fixes
- The git cache now fetches the tags from the remote if it can’t find the
reference.
- Xcodeproj now builds on 10.6.8 and Travis CI without symlinking headers.
###### Known Limitations
- The SCM reference of head Pods is not preserved across machines.
- Pods whose inline specification changed are not detected as modified. As a
workaround, remove their folder stored in `Pods`.
## 0.13.0
......@@ -19,14 +51,18 @@
###### Enhancements
- Add Podfile `podspec` which allows to use the dependencies of a podspec file. [#162](https://github.com/CocoaPods/CocoaPods/issues/162)
- Check if any of the build settings defined in the xcconfig files is overridden. [#92](https://github.com/CocoaPods/CocoaPods/issues/92)
- Add Podfile `podspec` which allows to use the dependencies of a podspec file.
[#162](https://github.com/CocoaPods/CocoaPods/issues/162)
- Check if any of the build settings defined in the xcconfig files is
overridden. [#92](https://github.com/CocoaPods/CocoaPods/issues/92)
- The Linter now checks that there are no compiler flags that disable warnings.
###### Bug fixes
- The final project isn’t affected anymore by the `inhibit_all_warnings!` option.
- Support for redirects while using podspec from an url. [#462](https://github.com/CocoaPods/CocoaPods/issues/462)
- The final project isn’t affected anymore by the `inhibit_all_warnings!`
option.
- Support for redirects while using podspec from an url.
[#462](https://github.com/CocoaPods/CocoaPods/issues/462)
## 0.12.0
......@@ -35,14 +71,20 @@
###### Enhancements
- The documentation is generated using the public headers if they are specified.
- In case of a download failure the installation is aborted and the error message is shown.
- The documentation is generated using the public headers if they are
specified.
- In case of a download failure the installation is aborted and the error
message is shown.
- Git submodules are initialized only if requested.
- Don’t impose a certain structure of the user’s project by raising if no ‘Frameworks’ group exists. [#431](https://github.com/CocoaPods/CocoaPods/pull/431)
- Don’t impose a certain structure of the user’s project by raising if no
‘Frameworks’ group exists.
[#431](https://github.com/CocoaPods/CocoaPods/pull/431)
- Support for GitHub Gists in the linter.
- Allow specifying ARC settings in subspecs.
- Add Podfile `inhibit_all_warnings!` which will inhibit all warnings from the Pods library. [#209](https://github.com/CocoaPods/CocoaPods/issues/209)
- Make the Pods Xcode project prettier by namespacing subspecs in nested groups. [#466](https://github.com/CocoaPods/CocoaPods/pull/466)
- Add Podfile `inhibit_all_warnings!` which will inhibit all warnings from the
Pods library. [#209](https://github.com/CocoaPods/CocoaPods/issues/209)
- Make the Pods Xcode project prettier by namespacing subspecs in nested
groups. [#466](https://github.com/CocoaPods/CocoaPods/pull/466)
## 0.11.1
......@@ -52,7 +94,8 @@
###### Bug fixes
- Fixed a crash related to subspecs without header files. [#449]
- Git submodules are loaded after the appropriate referenced is checked out and will be not loaded anymore in the cache. [#451]
- Git submodules are loaded after the appropriate referenced is checked out and
will be not loaded anymore in the cache. [#451]
- Fixed SVN support for the head version. [#432]
......@@ -65,7 +108,9 @@
- Added support for public headers. [#440]
- Added `pod repo lint`. [#423]
- Improved support for `:head` option and SVN repositories.
- When integrating Pods with a project without "Frameworks" group in root of the project, raise an informative message. [#431](https://github.com/CocoaPods/CocoaPods/pull/431)
- When integrating Pods with a project without "Frameworks" group in root of
the project, raise an informative message.
[#431](https://github.com/CocoaPods/CocoaPods/pull/431)
- Dropped support for legacy `config.ios?` and `config.osx?`
###### Bug fixes
......@@ -80,16 +125,21 @@
###### Enhancements
- Added a `--local-only` option to `pod push` so that developers can push locally and test before pushing to a remote. [#405](http://git.io/0ILJEw)
- Added line number information for errors generated in the Podfile. [#408](http://git.io/fWQvMg)
- Pods stored in git repositories now initialize submodules. [#406](http://git.io/L9ssSw)
- Added a `--local-only` option to `pod push` so that developers can push
locally and test before pushing to a remote. [#405](http://git.io/0ILJEw)
- Added line number information for errors generated in the Podfile.
[#408](http://git.io/fWQvMg)
- Pods stored in git repositories now initialize submodules.
[#406](http://git.io/L9ssSw)
###### Bug fixes
- Removed note about the post install hook form the linter.
- Improved xcodebuild error detection in the linter.
- Ensure the git cache exists, before updating it, when trying to install the ‘bleeding edge’ of a pod. [#426](http://git.io/d4eqRA)
- Clean downloaded external pods **after** resolving and activating (sub)specs. [#414](http://git.io/i77q_w)
- Ensure the git cache exists, before updating it, when trying to install the
‘bleeding edge’ of a pod. [#426](http://git.io/d4eqRA)
- Clean downloaded external pods **after** resolving and activating (sub)specs.
[#414](http://git.io/i77q_w)
- Support `tar.gz` as filename in a HTTP source. [#428](http://git.io/qhwKkA)
......
......@@ -7,7 +7,7 @@ group :development do
gem "mocha", "~> 0.11.4"
gem "bacon"
gem "kicker", "~> 3.0.0pre1"
gem "kicker", :git => "https://github.com/alloy/kicker.git", :branch => "3.0.0"
gem "mocha-on-bacon"
gem "rake"
gem "rb-fsevent"
......
......@@ -5,6 +5,15 @@ GIT
xcodeproj (0.3.2)
activesupport (~> 3.2.6)
GIT
remote: https://github.com/alloy/kicker.git
revision: 6430787ebf8b9305acc2d2f89ae5cf01d2cd5488
branch: 3.0.0
specs:
kicker (3.0.0pre1)
listen
terminal-notifier
PATH
remote: .
specs:
......@@ -41,8 +50,6 @@ GEM
hashie (1.2.0)
i18n (0.6.0)
json (1.7.5)
kicker (3.0.0pre1)
listen
listen (0.4.7)
rb-fchange (~> 0.0.5)
rb-fsevent (~> 0.9.1)
......@@ -74,6 +81,7 @@ GEM
ffi (>= 0.5.0)
redcarpet (2.1.1)
slop (3.3.2)
terminal-notifier (1.3.0)
vcr (2.2.4)
webmock (1.8.9)
addressable (>= 2.2.7)
......@@ -88,7 +96,7 @@ DEPENDENCIES
bacon
cocoapods!
github-markup
kicker (~> 3.0.0pre1)
kicker!
mocha (~> 0.11.4)
mocha-on-bacon
pry
......
PODS:
- AFNetworking (0.7.0):
- AFNetworking (0.7.0):
- JSONKit
- FormatterKit (1.0.0):
- FormatterKit (1.0.0):
- FormatterKit/ArrayFormatter (= 1.0.0)
- FormatterKit/LocationFormatter (= 1.0.0)
- FormatterKit/OrdinalNumberFormatter (= 1.0.0)
- FormatterKit/TimeIntervalFormatter (= 1.0.0)
- FormatterKit/URLRequestFormatter (= 1.0.0)
- FormatterKit/UnitOfInformationFormatter (= 1.0.0)
- FormatterKit/ArrayFormatter (1.0.0)
- FormatterKit/LocationFormatter (1.0.0)
- FormatterKit/OrdinalNumberFormatter (1.0.0)
- FormatterKit/TimeIntervalFormatter (1.0.0)
- FormatterKit/URLRequestFormatter (1.0.0)
- FormatterKit/UnitOfInformationFormatter (1.0.0)
- JSONKit (1.5pre)
- FormatterKit/ArrayFormatter (1.0.0)
- FormatterKit/LocationFormatter (1.0.0)
- FormatterKit/OrdinalNumberFormatter (1.0.0)
- FormatterKit/TimeIntervalFormatter (1.0.0)
- FormatterKit/URLRequestFormatter (1.0.0)
- FormatterKit/UnitOfInformationFormatter (1.0.0)
- JSONKit (1.5pre)
DEPENDENCIES:
- AFNetworking (~> 0.7.0)
- FormatterKit
- AFNetworking (~> 0.7.0)
- FormatterKit
SPEC CHECKSUMS:
AFNetworking: 1dd63e737768662b7180659e91846408df4ae7df
FormatterKit: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/ArrayFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/LocationFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/OrdinalNumberFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/TimeIntervalFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/URLRequestFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/UnitOfInformationFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
JSONKit: 3d4708953ea7ae399a49777372d8b060a43ddd27
COCOAPODS: 0.13.0
PODS:
- AFNetworking (0.7.0):
- AFNetworking (0.7.0):
- JSONKit
- FormatterKit (1.0.0):
- FormatterKit (1.0.0):
- FormatterKit/ArrayFormatter (= 1.0.0)
- FormatterKit/LocationFormatter (= 1.0.0)
- FormatterKit/OrdinalNumberFormatter (= 1.0.0)
- FormatterKit/TimeIntervalFormatter (= 1.0.0)
- FormatterKit/URLRequestFormatter (= 1.0.0)
- FormatterKit/UnitOfInformationFormatter (= 1.0.0)
- FormatterKit/ArrayFormatter (1.0.0)
- FormatterKit/LocationFormatter (1.0.0)
- FormatterKit/OrdinalNumberFormatter (1.0.0)
- FormatterKit/TimeIntervalFormatter (1.0.0)
- FormatterKit/URLRequestFormatter (1.0.0)
- FormatterKit/UnitOfInformationFormatter (1.0.0)
- JSONKit (1.5pre)
- FormatterKit/ArrayFormatter (1.0.0)
- FormatterKit/LocationFormatter (1.0.0)
- FormatterKit/OrdinalNumberFormatter (1.0.0)
- FormatterKit/TimeIntervalFormatter (1.0.0)
- FormatterKit/URLRequestFormatter (1.0.0)
- FormatterKit/UnitOfInformationFormatter (1.0.0)
- JSONKit (1.5pre)
DEPENDENCIES:
- AFNetworking (~> 0.7.0)
- FormatterKit
- AFNetworking (~> 0.7.0)
- FormatterKit
SPEC CHECKSUMS:
AFNetworking: 1dd63e737768662b7180659e91846408df4ae7df
FormatterKit: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/ArrayFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/LocationFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/OrdinalNumberFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/TimeIntervalFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/URLRequestFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
FormatterKit/UnitOfInformationFormatter: 12dea999a2df19e389f7b821962fc4088de8b821
JSONKit: 3d4708953ea7ae399a49777372d8b060a43ddd27
COCOAPODS: 0.13.0
PODS:
- MGSplitViewController (1.0.0)
- MGSplitViewController (1.0.0)
DEPENDENCIES:
- MGSplitViewController (= 1.0.0)
- MGSplitViewController (= 1.0.0)
SPEC CHECKSUMS:
MGSplitViewController: e0b0bc01aa81e1559765c39e7a764c890dac373a
COCOAPODS: 0.13.0
PODS:
- FileMD5Hash (0.0.1)
- ISO8601DateFormatter (0.6)
- JSONKit (1.5pre)
- LibComponentLogging-Core (1.2.2)
- LibComponentLogging-NSLog (1.0.4):
- FileMD5Hash (0.0.1)
- ISO8601DateFormatter (0.6)
- JSONKit (1.5pre)
- LibComponentLogging-Core (1.2.2)
- LibComponentLogging-NSLog (1.0.4):
- LibComponentLogging-Core (>= 1.1.6)
- NSData+Base64 (1.0.0)
- RestKit (0.10.2):
- NSData+Base64 (1.0.0)
- RestKit (0.10.2):
- RestKit/JSON (= 0.10.2)
- RestKit/JSON (0.10.2):
- RestKit/JSON (0.10.2):
- RestKit/Network
- RestKit/ObjectMapping/CoreData
- RestKit/ObjectMapping/JSON
- RestKit/UI
- RestKit/Network (0.10.2):
- RestKit/Network (0.10.2):
- FileMD5Hash
- LibComponentLogging-NSLog (>= 1.0.4)
- SOCKit
- cocoa-oauth
- RestKit/ObjectMapping/CoreData (0.10.2):
- RestKit/ObjectMapping/CoreData (0.10.2):
- ISO8601DateFormatter (>= 0.6)
- RestKit/Network
- RestKit/ObjectMapping/JSON (0.10.2):
- RestKit/ObjectMapping/JSON (0.10.2):
- ISO8601DateFormatter (>= 0.6)
- JSONKit (>= 1.5pre)
- RestKit/Network
- RestKit/UI (0.10.2)
- SOCKit (1.0)
- cocoa-oauth (0.0.1):
- RestKit/UI (0.10.2)
- SOCKit (1.0)
- cocoa-oauth (0.0.1):
- NSData+Base64 (~> 1.0)
DEPENDENCIES:
- RestKit
- RestKit
SPEC CHECKSUMS:
FileMD5Hash: c3b015e6fb293eea0a0c276747f61f20408c2644
ISO8601DateFormatter: dc05a2481edf49668d85d3ff8c466c32eb544780
JSONKit: 3d4708953ea7ae399a49777372d8b060a43ddd27
LibComponentLogging-Core: c80e88f2b7fb204554de7bec13374daf394097e8
LibComponentLogging-NSLog: d8f54e7eb65bb893f91cab5864ecf8e85ebd9359
NSData+Base64: be7e201540fa7805ecedc1f38d456e008be259b2
RestKit: a9f24f857c792183bcc656f891abe93d38e342ee
RestKit/JSON: a9f24f857c792183bcc656f891abe93d38e342ee
RestKit/Network: a9f24f857c792183bcc656f891abe93d38e342ee
RestKit/ObjectMapping/CoreData: a9f24f857c792183bcc656f891abe93d38e342ee
RestKit/ObjectMapping/JSON: a9f24f857c792183bcc656f891abe93d38e342ee
RestKit/UI: a9f24f857c792183bcc656f891abe93d38e342ee
SOCKit: 9d71b00c699cf5aa2989d6aa12f3392b048123db
cocoa-oauth: 8f4c8b77c77ac660de37f8125557c4ec09b0118a
COCOAPODS: 0.13.0
PODS:
- AFNetworking (1.0RC1)
- AFNetworking (1.0RC1)
DEPENDENCIES:
- AFNetworking
- AFNetworking
SPEC CHECKSUMS:
AFNetworking: b21c1252d437fd322e7db1caa93b163d76a362cb
COCOAPODS: 0.13.0
PODS:
- AFNetworking (1.0RC1)
- SSToolkit (1.0.2)
- AFNetworking (1.0RC1)
- SSToolkit (1.0.2)
DEPENDENCIES:
- AFNetworking
- SSToolkit
- AFNetworking
- SSToolkit
SPEC CHECKSUMS:
AFNetworking: b21c1252d437fd322e7db1caa93b163d76a362cb
SSToolkit: 852044205c6a15586431e90f46b2ef01f693285a
COCOAPODS: 0.13.0
PODS:
- JSONKit (1.5pre)
- JSONKit (1.5pre)
DEPENDENCIES:
- JSONKit
- JSONKit
SPEC CHECKSUMS:
JSONKit: 3d4708953ea7ae399a49777372d8b060a43ddd27
COCOAPODS: 0.13.0
......@@ -32,6 +32,7 @@ module Pod
autoload :Executable, 'cocoapods/executable'
autoload :Installer, 'cocoapods/installer'
autoload :LocalPod, 'cocoapods/local_pod'
autoload :Lockfile, 'cocoapods/lockfile'
autoload :Platform, 'cocoapods/platform'
autoload :Podfile, 'cocoapods/podfile'
autoload :Project, 'cocoapods/project'
......
......@@ -6,20 +6,22 @@ module Pod
autoload :Install, 'cocoapods/command/install'
autoload :List, 'cocoapods/command/list'
autoload :Linter, 'cocoapods/command/linter'
autoload :Outdated, 'cocoapods/command/outdated'
autoload :Presenter, 'cocoapods/command/presenter'
autoload :Push, 'cocoapods/command/push'
autoload :Repo, 'cocoapods/command/repo'
autoload :Search, 'cocoapods/command/search'
autoload :Setup, 'cocoapods/command/setup'
autoload :Spec, 'cocoapods/command/spec'
autoload :Update, 'cocoapods/command/update'
class Help < Informative
def initialize(command_class, argv)
@command_class, @argv = command_class, argv
def initialize(command_class, argv, unrecognized_command = nil)
@command_class, @argv, @unrecognized_command = command_class, argv, unrecognized_command
end
def message
[
message = [
'',
@command_class.banner.gsub(/\$ pod (.*)/, '$ pod \1'.green),
'',
......@@ -28,6 +30,9 @@ module Pod
options,
"\n",
].join("\n")
message << "[!] Unrecognized command: `#{@unrecognized_command}'\n".red if @unrecognized_command
message << "[!] Unrecognized argument#{@argv.count > 1 ? 's' : ''}: `#{@argv.join(' - ')}'\n".red unless @argv.empty?
message
end
private
......@@ -48,10 +53,9 @@ module Pod
end
def self.banner
commands = ['install', 'list', 'push', 'repo', 'search', 'setup', 'spec'].sort
banner = "\nTo see help for the available commands run:\n\n"
commands.each {|cmd| banner << " * $ pod #{cmd.green} --help\n"}
banner
commands = ['install', 'update', 'outdated', 'list', 'push', 'repo', 'search', 'setup', 'spec'].sort
banner = "To see help for the available commands run:\n\n"
banner + commands.map { |cmd| " * $ pod #{cmd.green} --help" }.join("\n")
end
def self.options
......@@ -76,9 +80,9 @@ module Pod
Config.instance.verbose? ? raise : exit(1)
rescue Exception => e
if e.is_a?(PlainInformative) # also catches Informative
if e.is_a?(PlainInformative) || ENV['COCOA_PODS_ENV'] == 'development' # also catches Informative
puts e.message
puts *e.backtrace if Config.instance.verbose?
puts *e.backtrace if Config.instance.verbose? || ENV['COCOA_PODS_ENV'] == 'development'
else
puts ErrorReport.report(e)
end
......@@ -98,18 +102,22 @@ module Pod
String.send(:define_method, :colorize) { |string , _| string } if argv.option( '--no-color' )
command_class = case argv.shift_argument
command_class = case command_argument = argv.shift_argument
when 'install' then Install
when 'list' then List
when 'outdated' then Outdated
when 'push' then Push
when 'repo' then Repo
when 'search' then Search
when 'list' then List
when 'setup' then Setup
when 'spec' then Spec
when 'push' then Push
when 'update' then Update
end
if show_help || command_class.nil?
raise Help.new(command_class || self, argv)
if command_class.nil?
raise Help.new(self, argv, command_argument)
elsif show_help
raise Help.new(command_class, argv)
else
command_class.new(argv)
end
......@@ -123,6 +131,25 @@ module Pod
private
def verify_podfile_exists!
unless config.podfile
raise Informative, "No `Podfile' found in the current working directory."
end
end
def verify_lockfile_exists!
unless config.lockfile
raise Informative, "No `Podfile.lock' found in the current working directory, run `pod install'."
end
end
def update_spec_repos_if_necessary!
if @update_repo
print_title 'Updating Spec Repositories', true
Repo.new(ARGV.new(["update"])).run
end
end
def print_title(title, only_verbose = true)
if config.verbose?
puts "\n" + title.yellow
......
......@@ -19,8 +19,7 @@ module Pod
This will configure the project to reference the Pods static library,
add a build configuration file, and add a post build script to copy
Pod resources.
}
Pod resources.}
end
def self.options
......@@ -40,17 +39,17 @@ module Pod
super unless argv.empty?
end
def run
unless podfile = config.podfile
raise Informative, "No `Podfile' found in the current working directory."
end
if @update_repo
print_title 'Updating Spec Repositories', true
Repo.new(ARGV.new(["update"])).run
def run_install_with_update(update)
sandbox = Sandbox.new(config.project_pods_root)
resolver = Resolver.new(config.podfile, config.lockfile, sandbox)
resolver.update_mode = update
Installer.new(resolver).install!
end
Installer.new(podfile).install!
def run
verify_podfile_exists!
update_spec_repos_if_necessary!
run_install_with_update(false)
end
end
end
......
......@@ -103,7 +103,9 @@ module Pod
def install_pod
podfile = podfile_from_spec
config.verbose
installer = Installer.new(podfile)
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
......
module Pod
class Command
class Outdated < Command
def self.banner
%{Show outdated pods:
$ pod outdated
Shows the outdated pods in the current Podfile.lock, but only those from
spec repos, not those from local/external sources or `:head' versions.}
end
def self.options
[
["--no-update", "Skip running `pod repo update` before install"],
].concat(super)
end
def initialize(argv)
@update_repo = !argv.option('--no-update')
super unless argv.empty?
end
def run
verify_podfile_exists!
verify_lockfile_exists!
update_spec_repos_if_necessary!
sandbox = Sandbox.new(config.project_pods_root)
resolver = Resolver.new(config.podfile, config.lockfile, sandbox)
resolver.update_mode = true
resolver.update_external_specs = false
resolver.resolve
names = resolver.pods_to_install - resolver.pods_from_external_sources
specs = resolver.specs.select do |spec|
names.include?(spec.name) && !spec.version.head?
end
if specs.empty?
puts "No updates are available.".yellow
else
puts "The following updates are available:".green
puts " - " << specs.join("\n - ") << "\n"
end
end
end
end
end
......@@ -4,7 +4,7 @@ module Pod
class Command
class Push < Command
def self.banner
%{Pushing new specifications to a spec-repo:
%{Pushing new specifications to a spec-repo:
$ pod push REPO [NAME.podspec]
......
......@@ -2,7 +2,7 @@ module Pod
class Command
class Setup < Command
def self.banner
%{Setup CocoaPods environment:
%{Setup CocoaPods environment:
$ pod setup
......
......@@ -4,7 +4,7 @@ module Pod
class Command
class Spec < Command
def self.banner
%{Managing PodSpec files:
%{Managing PodSpec files:
$ pod spec create [ NAME | https://github.com/USER/REPO ]
......
module Pod
class Command
class Update < Install
def self.banner
%{Updating dependencies of a project:
$ pod update
Updates all dependencies.}
end
def run
verify_podfile_exists!
verify_lockfile_exists!
update_spec_repos_if_necessary!
run_install_with_update(true)
end
end
end
end
......@@ -41,11 +41,16 @@ module Pod
@project_podfile ||= project_root + 'Podfile'
end
def project_lockfile
@project_lockfile ||= project_root + 'Podfile.lock'
end
def headers_symlink_root
@headers_symlink_root ||= "#{project_pods_root}/Headers"
end
# Returns the spec at the pat returned from `project_podfile`.
# @return [Podfile] The Podfile to use for the current execution.
#
def podfile
@podfile ||= begin
Podfile.from_file(project_podfile) if project_podfile.exist?
......@@ -53,6 +58,14 @@ module Pod
end
attr_writer :podfile
# @return [Lockfile] The Lockfile to use for the current execution.
#
def lockfile
@lockfile ||= begin
Lockfile.from_file(project_lockfile) if project_lockfile.exist?
end
end
module Mixin
def config
Config.instance
......
......@@ -3,9 +3,9 @@ require 'cocoapods/open_uri'
module Pod
class Dependency < Gem::Dependency
attr_reader :external_source, :head
attr_reader :head
alias :head? :head
attr_accessor :specification
attr_accessor :specification, :external_source
def initialize(*name_and_version_requirements, &block)
if name_and_version_requirements.empty? && block
......@@ -40,7 +40,7 @@ module Pod
end
def ==(other)
super && (@specification ? @specification == other.specification : @external_source == other.external_source)
super && (head? == other.head?) && (@specification ? @specification == other.specification : @external_source == other.external_source)
end
def subspec_dependency?
......@@ -76,13 +76,14 @@ module Pod
if external?
version << @external_source.description
elsif inline?
version << "defined in Podfile"
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 += " [HEAD]" if head?
result << " (#{version})" unless version.empty?
result
end
......@@ -90,6 +91,10 @@ module Pod
@external_source.specification_from_sandbox(sandbox, platform)
end
def match_version?(version)
match?(name, version) && (version.head? == head?)
end
# Taken from RubyGems 1.3.7
unless public_method_defined?(:match?)
def match?(spec_name, spec_version)
......@@ -128,10 +133,13 @@ module Pod
module ExternalSources
def self.from_params(name, params)
return unless name && params
if params.key?(:git)
GitSource.new(name, params)
elsif params.key?(:podspec)
PodspecSource.new(name, params)
elsif params.key?(:local)
LocalSource.new(name, params)
else
raise Informative, "Unknown external source parameters for #{name}: #{params}"
end
......@@ -158,18 +166,22 @@ module Pod
def specification_from_external(sandbox, platform)
copy_external_source_into_sandbox(sandbox, platform)
specification_from_local(sandbox, platform)
spec = specification_from_local(sandbox, platform)
raise Informative, "No podspec found for `#{name}' in #{description}" unless spec
spec
end
def ==(other_source)
return if other_source.nil?
name == other_source.name && params == other_source.params
def ==(other)
return if other.nil?
name == other.name && params == other.params
end
end
class GitSource < AbstractExternalSource
def copy_external_source_into_sandbox(sandbox, platform)
puts " * Pre-downloading: '#{name}'" unless config.silent?
puts "-> Pre-downloading: '#{name}'" unless config.silent?
target = sandbox.root + name
target.rmtree if target.exist?
downloader = Downloader.for_target(sandbox.root + name, @params)
downloader.download
if local_pod = sandbox.installed_pod_named(name, platform)
......@@ -191,8 +203,10 @@ module Pod
def copy_external_source_into_sandbox(sandbox, _)
output_path = sandbox.root + "Local Podspecs/#{name}.podspec"
output_path.dirname.mkpath
puts " * Fetching podspec for `#{name}' from: #{@params[:podspec]}" unless config.silent?
open(@params[:podspec]) do |io|
puts "-> Fetching podspec for `#{name}' from: #{@params[:podspec]}" unless config.silent?
path = @params[:podspec]
path = Pathname.new(path).expand_path if path.start_with?("~")
open(path) do |io|
output_path.open('w') { |f| f << io.read }
end
end
......@@ -201,6 +215,35 @@ module Pod
"from `#{@params[:podspec]}'"
end
end
class LocalSource < AbstractExternalSource
def pod_spec_path
path = Pathname.new(@params[:local]).expand_path + "#{name}.podspec"
raise Informative, "No podspec found for `#{name}' in `#{@params[:local]}'" unless path.exist?
path
end
def copy_external_source_into_sandbox(sandbox, _)
output_path = sandbox.root + "Local Podspecs/#{name}.podspec"
output_path.dirname.mkpath
FileUtils.copy(pod_spec_path, output_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
......@@ -79,6 +79,7 @@ module Pod
git! "reset --hard HEAD"
git! "clean -d -x -f"
git! "pull origin master"
git! "fetch --tags"
end
end
......
......@@ -7,19 +7,12 @@ module Pod
include Config::Mixin
attr_reader :sandbox
attr_reader :resolver, :sandbox, :lockfile
def initialize(podfile)
@podfile = podfile
# FIXME: pass this into the installer as a parameter
@sandbox = Sandbox.new(config.project_pods_root)
@resolver = Resolver.new(@podfile, @sandbox)
# TODO: remove in 0.7 (legacy support for config.ios? and config.osx?)
config.podfile = podfile
end
def lock_file
config.project_root + 'Podfile.lock'
def initialize(resolver)
@resolver = resolver
@podfile = resolver.podfile
@sandbox = resolver.sandbox
end
def project
......@@ -29,7 +22,8 @@ module Pod
pods.each do |pod|
# Add all source files to the project grouped by pod
pod.relative_source_files_by_spec.each do |spec, paths|
group = @project.add_spec_group(spec.name)
parent_group = pod.local? ? @project.local_pods : @project.pods
group = @project.add_spec_to_group(pod.name, parent_group)
paths.each do |path|
group.files.new('path' => path.to_s)
end
......@@ -46,8 +40,17 @@ module Pod
end.compact
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.
#
# @return [void]
#
def install_dependencies!
pods.each do |pod|
pods.sort_by { |pod| pod.top_specification.name.downcase }.each do |pod|
name = pod.top_specification.name
should_install = @resolver.should_install?(name) || !pod.exists?
unless config.silent?
marker = config.verbose ? "\n-> ".green : ''
if subspec_name = pod.top_specification.preferred_dependency
......@@ -55,15 +58,14 @@ module Pod
else
name = pod.to_s
end
name << " [HEAD]" if pod.top_specification.version.head?
puts marker << ( pod.exists? ? "Using #{name}" : "Installing #{name}".green )
puts marker << ( should_install ? "Installing #{name}".green : "Using #{name}" )
end
download_pod(pod) unless pod.exists?
# This will not happen if the pod existed before we started the install
# process.
if pod.downloaded?
if should_install
unless pod.downloaded?
pod.implode
download_pod(pod)
end
# The docs need to be generated before cleaning because the
# documentation is created for all the subspecs.
generate_docs(pod)
......@@ -100,12 +102,26 @@ module Pod
end
end
# @TODO: use the local pod implode
#
def remove_deleted_dependencies!
resolver.removed_pods.each do |pod_name|
marker = config.verbose ? "\n-> ".red : ''
path = sandbox.root + pod_name
puts marker << "Removing #{pod_name}".red
path.rmtree if path.exist?
end
end
def install!
@sandbox.prepare_for_install
print_title "Resolving dependencies of: #{@podfile.defined_in_file}"
specs_by_target
print_title "Removing deleted dependencies" unless resolver.removed_pods.empty?
remove_deleted_dependencies!
print_title "Installing dependencies"
install_dependencies!
......@@ -119,15 +135,17 @@ module Pod
generate_dummy_source(target_installer)
end
generate_lock_file!(specifications)
puts "- Running post install hooks" if config.verbose?
# Post install hooks run _before_ saving of project, so that they can alter it before saving.
run_post_install_hooks
puts "- Writing Xcode project file to `#{@sandbox.project_path}'\n\n" if config.verbose?
puts "- Writing Xcode project file to `#{@sandbox.project_path}'" if config.verbose?
project.save_as(@sandbox.project_path)
puts "- Writing lockfile in `#{config.project_lockfile}'\n\n" if config.verbose?
@lockfile = Lockfile.generate(@podfile, specs_by_target.values.flatten)
@lockfile.write_to_disk(config.project_lockfile)
UserProjectIntegrator.new(@podfile).integrate! if config.integrate_targets?
end
......@@ -143,44 +161,6 @@ module Pod
@podfile.post_install!(self)
end
def generate_lock_file!(specs)
lock_file.open('w') do |file|
file.puts "PODS:"
# 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 osx 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 name and print
pod_and_deps.sort_by(&:first).each do |name, deps|
if deps.empty?
file.puts " - #{name}"
else
file.puts " - #{name}:"
deps.each { |dep| file.puts " - #{dep}" }
end
end
file.puts
file.puts "DEPENDENCIES:"
@podfile.dependencies.map(&:to_s).sort.each do |dep|
file.puts " - #{dep}"
end
end
end
def generate_dummy_source(target_installer)
class_name_identifier = target_installer.target_definition.label
dummy_source = Generator::DummySource.new(class_name_identifier)
......@@ -215,7 +195,11 @@ module Pod
specs_by_target.each do |target_definition, specs|
@pods_by_spec[target_definition.platform] = {}
result[target_definition] = specs.map do |spec|
if spec.local?
LocalPod::LocalSourcedPod.new(spec, sandbox, target_definition.platform)
else
@sandbox.local_pod_for_spec(spec, target_definition.platform)
end
end.uniq.compact
end
result
......
......@@ -45,12 +45,14 @@ module Pod
attr_accessor :downloaded
alias_method :downloaded?, :downloaded
# @param [Specification] specification
# The first activated specification of the pod.
# @param [Sandbox] sandbox
# The sandbox where the files of the pod will be located.
# @param [Platform] platform
# The platform that will be used to build the pod.
# @param [Specification] specification The first activated specification
# of the pod.
#
# @param [Sandbox] sandbox The sandbox where the files of the
# pod will be located.
#
# @param [Platform] platform The platform that will be used to
# build the pod.
#
# @todo The local pod should be initialized with all the activated
# specifications passed as an array, in order to be able to cache the
......@@ -96,9 +98,7 @@ module Pod
# the pods comes from a local source.
#
def to_s
result = top_specification.to_s
result << " [LOCAL]" if top_specification.local?
result
top_specification.to_s
end
# @return [String] The name of the Pod.
......@@ -140,6 +140,10 @@ module Pod
root.rmtree if exists?
end
def local?
false
end
# @!group Cleaning
# Deletes any path that is not used by the pod.
......@@ -508,5 +512,39 @@ module Pod
Pathname.glob(pattern, File::FNM_CASEFOLD)
end.flatten
end
# A {LocalSourcedPod} is a {LocalPod} that interacts with the files of
# a folder controlled by the users. As such this class does not alter
# in any way the contents of the folder.
#
class LocalSourcedPod < LocalPod
def downloaded?
true
end
def create
# No ops
end
def root
@root ||= Pathname.new(@top_specification.source[:local]).expand_path
end
def implode
# No ops
end
def clean!
# No ops
end
def to_s
super + " [LOCAL]"
end
def local?
true
end
end
end
end
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?
hash = YAML.load(File.open(path))
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)
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
......@@ -538,5 +538,9 @@ module Pod
def validate!
end
def to_s
"Podfile"
end
end
end
......@@ -34,9 +34,14 @@ module Pod
groups.find { |g| g.name == 'Pods' } || groups.new({ 'name' => 'Pods' })
end
# Shortcut access to the `Local Pods' PBXGroup.
def local_pods
groups.find { |g| g.name == 'Local Pods' } || groups.new({ 'name' => 'Local Pods' })
end
# Adds a group as child to the `Pods' group namespacing subspecs.
def add_spec_group(name)
groups = pods.groups
def add_spec_to_group(name, parent_group)
groups = parent_group.groups
group = nil
name.split('/').each do |name|
group = groups.find { |g| g.name == name } || groups.new({ 'name' => name })
......
......@@ -4,44 +4,169 @@ module Pod
class Resolver
include Config::Mixin
attr_reader :podfile, :sandbox
attr_accessor :cached_sets, :cached_sources
# @return [Bool] Whether the resolver should find the pods to install or
# the pods to update.
#
attr_accessor :update_mode
def initialize(podfile, sandbox)
# @return [Bool] Whether the resolver should update the external specs
# in the resolution process.
#
attr_accessor :update_external_specs
# @return [Podfile] The Podfile used by the resolver.
#
attr_reader :podfile
# @return [Lockfile] The Lockfile used by the resolver.
#
attr_reader :lockfile
# @return [Sandbox] The Sandbox used by the resolver to find external
# dependencies.
#
attr_reader :sandbox
# @return [Array<Strings>] The name of the pods that have an
# external source.
#
attr_reader :pods_from_external_sources
# @return [Array<Set>] A cache of the sets used to resolve the dependencies.
#
attr_reader :cached_sets
# @return [Source::Aggregate] A cache of the sources needed to find the
# podspecs.
#
attr_reader :cached_sources
# @return [Hash{Podfile::TargetDefinition => Array<Specification>}]
# Returns the resolved specifications grouped by target.
#
attr_reader :specs_by_target
def initialize(podfile, lockfile, sandbox)
@podfile = podfile
@lockfile = lockfile
@sandbox = sandbox
@update_external_specs = true
@cached_sets = {}
@cached_sources = Source::Aggregate.new
@log_indent = 0;
end
# Identifies the specifications that should be installed according whether
# the resolver is in update mode or not.
#
# @return [Hash{Podfile::TargetDefinition => Array<Specification>}] specs_by_target
#
def resolve
@specs = {}
targets_and_specs = {}
@cached_specs = {}
@specs_by_target = {}
@pods_from_external_sources = []
@pods_to_lock = []
@log_indent = 0
if @lockfile
puts "\nFinding added, modified or removed dependencies:".green if config.verbose?
@pods_by_state = @lockfile.detect_changes_with_podfile(podfile)
if config.verbose?
@pods_by_state.each do |symbol, pod_names|
case symbol
when :added
mark = "A".green
when :changed
mark = "M".yellow
when :removed
mark = "R".red
when :unchanged
mark = "-"
end
pod_names.each do |pod_name|
puts " #{mark} " << pod_name
end
end
end
@pods_to_lock = (lockfile.pods_names - @pods_by_state[:added] - @pods_by_state[:changed] - @pods_by_state[:removed]).uniq
end
@podfile.target_definitions.values.each do |target_definition|
puts "\nResolving dependencies for target `#{target_definition.name}' (#{target_definition.platform})".green if config.verbose?
puts "\nResolving dependencies for target `#{target_definition.name}' (#{target_definition.platform}):".green if config.verbose?
@loaded_specs = []
find_dependency_specs(@podfile, target_definition.dependencies, target_definition)
targets_and_specs[target_definition] = @specs.values_at(*@loaded_specs).sort_by(&:name)
@specs_by_target[target_definition] = @cached_specs.values_at(*@loaded_specs).sort_by(&:name)
end
@cached_specs.values.sort_by(&:name)
@specs_by_target
end
@specs.values.sort_by(&:name)
targets_and_specs
# @return [Array<Specification>] The specifications loaded by the resolver.
#
def specs
@cached_specs.values.uniq
end
# @return [Bool] Whether a pod should be installed/reinstalled.
#
def should_install?(name)
pods_to_install.include? name
end
# @return [Array<Strings>] The name of the pods that should be
# installed/reinstalled.
#
def pods_to_install
unless @pods_to_install
if lockfile
@pods_to_install = specs.select do |spec|
spec.version != lockfile.pods_versions[spec.pod_name]
end.map(&:name)
if update_mode
@pods_to_install += specs.select do |spec|
spec.version.head? || pods_from_external_sources.include?(spec.pod_name)
end.map(&:name)
end
@pods_to_install += @pods_by_state[:added] + @pods_by_state[:changed]
else
@pods_to_install = specs.map(&:name)
end
end
@pods_to_install
end
# @return [Array<Strings>] The name of the pods that were installed
# but don't have any dependency anymore. The name of the Pods are
# stripped from subspecs.
#
def removed_pods
return [] unless lockfile
unless @removed_pods
previusly_installed = lockfile.pods_names.map { |pod_name| pod_name.split('/').first }
installed = specs.map { |spec| spec.name.split('/').first }
@removed_pods = previusly_installed - installed
end
@removed_pods
end
private
# @return [Set] The cached set for a given dependency.
#
def find_cached_set(dependency, platform)
set_name = dependency.name.split('/').first
@cached_sets[set_name] ||= begin
if dependency.specification
Specification::Set::External.new(dependency.specification)
elsif external_source = dependency.external_source
# The platform isn't actually being used by the LocalPod instance
# that's being used behind the scenes, but passing it anyways for
# completeness sake.
if update_mode && update_external_specs
# Always update external sources in update mode.
specification = external_source.specification_from_external(@sandbox, platform)
else
# Don't update external sources in install mode if not needed.
specification = external_source.specification_from_sandbox(@sandbox, platform)
end
set = Specification::Set::External.new(specification)
if dependency.subspec_dependency?
@cached_sets[dependency.top_level_spec_name] ||= set
......@@ -53,29 +178,48 @@ module Pod
end
end
# Resolves the dependencies of a specification and stores them in @cached_specs
#
# @param [Specification] dependent_specification
# @param [Array<Dependency>] dependencies
# @param [TargetDefinition] target_definition
#
# @return [void]
#
def find_dependency_specs(dependent_specification, dependencies, target_definition)
@log_indent += 1
dependencies.each do |dependency|
# Replace the dependency with a more specific one if the pod is already installed.
if !update_mode && @pods_to_lock.include?(dependency.name)
dependency = lockfile.dependency_for_installed_pod_named(dependency.name)
end
puts ' ' * @log_indent + "- #{dependency}" if config.verbose?
set = find_cached_set(dependency, target_definition.platform)
set.required_by(dependent_specification)
set.required_by(dependency, dependent_specification.to_s)
# Ensure we don't resolve the same spec twice for one target
unless @loaded_specs.include?(dependency.name)
spec = set.specification_by_name(dependency.name)
@pods_from_external_sources << spec.pod_name if dependency.external?
@loaded_specs << spec.name
@specs[spec.name] = spec
@cached_specs[spec.name] = spec
# Configure the specification
spec.activate_platform(target_definition.platform)
spec.version.head = dependency.head?
# And recursively load the dependencies of the spec.
find_dependency_specs(spec, spec.dependencies, target_definition) if spec.dependencies
end
validate_platform!(spec || @specs[dependency.name], target_definition)
validate_platform(spec || @cached_specs[dependency.name], target_definition)
end
@log_indent -= 1
end
def validate_platform!(spec, target)
# Ensures that a spec is compatible with platform of a target.
#
# @raises If the spec is not supported by the target.
#
def validate_platform(spec, target)
unless spec.available_platforms.any? { |platform| target.platform.supports?(platform) }
raise Informative, "[!] The platform of the target `#{target.name}' (#{target.platform}) is not compatible with `#{spec}' which has a minimun requirement of #{spec.available_platforms.join(' - ')}.".red
end
......
......@@ -174,6 +174,12 @@ module Pod
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
......@@ -387,14 +393,14 @@ module Pod
# 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].split('/')
subspec_name = remainder.shift
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
raise Informative, "Unable to find a subspec named `#{name}'." unless subspec
remainder.empty? ? subspec : subspec.subspec_by_name(name)
end
......@@ -402,17 +408,9 @@ module Pod
!source.nil? && !source[:local].nil?
end
def local_path
Pathname.new(File.expand_path(source[:local]))
end
def pod_destroot
if local?
local_path
else
config.project_pods_root + top_level_parent.name
end
end
def self.pattern_list(patterns)
if patterns.is_a?(Array) && (!defined?(Rake) || !patterns.is_a?(Rake::FileList))
......
......@@ -8,22 +8,18 @@ module Pod
def initialize(pod_dir)
@pod_dir = pod_dir
@required_by = []
@dependencies = []
end
def required_by(specification)
# Skip subspecs because the can't require a different version of the top level parent
return if !specification.podfile? && specification.top_level_parent.name == name
dependency = specification.dependency_by_top_level_spec_name(name)
# TODO we don’t actually do anything in our Version subclass. Maybe we should just remove that.
def required_by(dependency, dependent_name)
unless @required_by.empty? || dependency.requirement.satisfied_by?(Gem::Version.new(required_version.to_s))
# TODO add graph that shows which dependencies led to this.
raise Informative, "#{specification} tries to activate `#{dependency}', " \
raise Informative, "#{dependent_name} tries to activate `#{dependency}', " \
"but already activated version `#{required_version}' " \
"by #{@required_by.to_sentence}."
end
@specification = nil
@required_by << specification
@required_by << dependent_name
@dependencies << dependency
end
def specification_by_name(name)
......@@ -31,8 +27,8 @@ module Pod
end
def dependency
@required_by.inject(Dependency.new(name)) do |previous, spec|
previous.merge(spec.dependency_by_top_level_spec_name(name).to_top_level_spec_dependency)
@dependencies.inject(Dependency.new(name)) do |previous, dependency|
previous.merge(dependency.to_top_level_spec_dependency)
end
end
......@@ -76,6 +72,7 @@ module Pod
def initialize(specification)
@specification = specification
@required_by = []
@dependencies = []
end
def name
......@@ -86,9 +83,9 @@ module Pod
self.class === other && name == other.name
end
def required_by(specification)
def required_by(dependency, dependent_name)
before = @specification
super(specification)
super(dependency, dependent_name)
ensure
@specification = before
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
require File.expand_path('../../../spec_helper', __FILE__)
describe Pod::Command::Outdated do
extend SpecHelper::Command
extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
it "tells the user that no Podfile was found in the current working dir" do
exception = lambda { run_command('outdated','--no-update') }.should.raise Pod::Informative
exception.message.should.include "No `Podfile' found in the current working directory."
end
it "tells the user that no Lockfile was found in the current working dir" do
file = temporary_directory + 'Podfile'
File.open(file, 'w') {|f| f.write('platform :ios') }
Dir.chdir(temporary_directory) do
exception = lambda { run_command('outdated','--no-update') }.should.raise Pod::Informative
exception.message.should.include "No `Podfile.lock' found in the current working directory"
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
describe Pod::Command::Update do
extend SpecHelper::Command
extend SpecHelper::TemporaryDirectory
extend SpecHelper::TemporaryRepos
it "tells the user that no Podfile was found in the current working dir" do
exception = lambda { run_command('update','--no-update') }.should.raise Pod::Informative
exception.message.should.include "No `Podfile' found in the current working directory."
end
it "tells the user that no Lockfile was found in the current working dir" do
file = temporary_directory + 'Podfile'
File.open(file, 'w') {|f| f.write('platform :ios') }
Dir.chdir(temporary_directory) do
exception = lambda { run_command('update','--no-update') }.should.raise Pod::Informative
exception.message.should.include "No `Podfile.lock' found in the current working directory"
end
end
end
This diff is collapsed.
......@@ -73,7 +73,7 @@ module Pod
it "identifies itself as a `bleeding edge' dependency" do
dependency = Dependency.new("cocoapods", :head)
dependency.should.be.head
dependency.to_s.should == "cocoapods [HEAD]"
dependency.to_s.should == "cocoapods (HEAD)"
end
it "only supports the `:head' option on the last version of a pod" do
......@@ -98,7 +98,7 @@ module Pod
Downloader.stubs(:for_target).returns(stub_everything)
pod = mock('LocaPod', :downloaded= => true)
sandbox = stub('Sandbox', :root => '', :installed_pod_named => pod)
sandbox = stub('Sandbox', :root => temporary_sandbox.root, :installed_pod_named => pod)
@dependency.external_source.copy_external_source_into_sandbox(sandbox, Platform.ios)
end
end
......
......@@ -14,7 +14,10 @@ module Pod
xcodeproj 'MyProject'
pod 'JSONKit'
end
@xcconfig = Installer.new(podfile).target_installers.first.xcconfig.to_hash
sandbox = Sandbox.new(fixture('integration'))
resolver = Resolver.new(podfile, nil, sandbox)
@xcconfig = Installer.new(resolver).target_installers.first.xcconfig.to_hash
end
it "sets the header search paths where installed Pod headers can be found" do
......@@ -35,7 +38,10 @@ module Pod
platform :osx
pod 'ASIHTTPRequest'
end
installer = Installer.new(podfile)
sandbox = Sandbox.new(fixture('integration'))
resolver = Resolver.new(podfile, nil, sandbox)
installer = Installer.new(resolver)
pods = installer.specifications.map do |spec|
LocalPod.new(spec, installer.sandbox, podfile.target_definitions[:default].platform)
end
......@@ -51,7 +57,8 @@ module Pod
pod 'JSONKit'
end
end
installer = Installer.new(podfile)
resolver = Resolver.new(podfile, nil, Sandbox.new(fixture('integration')))
installer = Installer.new(resolver)
installer.target_installers.map(&:target_definition).map(&:name).should == [:not_empty]
end
......@@ -61,7 +68,8 @@ module Pod
platform :ios
xcodeproj path, 'App Store' => :release
end
installer = Installer.new(podfile)
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
......@@ -70,7 +78,8 @@ module Pod
platform :ios
pod 'JSONKit', :head
end
installer = Installer.new(podfile)
resolver = Resolver.new(podfile, nil, Sandbox.new(fixture('integration')))
installer = Installer.new(resolver)
pod = installer.pods.first
downloader = stub('Downloader')
......
......@@ -323,4 +323,57 @@ describe Pod::LocalPod do
public_headers.should == %w{ UIKit.h }
end
end
describe "concerning a Pod with a local source" do
extend SpecHelper::TemporaryDirectory
before do
@local_path = temporary_directory + 'localBanana'
@sandbox = temporary_sandbox
@spec = fixture_spec('banana-lib/BananaLib.podspec')
@spec.source = {:local => @local_path}
@pod = Pod::LocalPod::LocalSourcedPod.new(@spec, @sandbox, Pod::Platform.new(:ios))
end
it "is marked as local" do
@pod.to_s.should.include? '[LOCAL]'
end
it "is marked as downloaded" do
@pod.downloaded?.should.be.true
end
it "correctly repports the root of the pod" do
@pod.root.should == @local_path
end
it "doesn't create the root" do
@pod.create
@local_path.exist?.should.be.false
end
before do
FileUtils.cp_r(fixture('banana-lib'), @local_path)
end
it "doesn't cleans the user files" do
useless_file = @local_path + 'useless.txt'
FileUtils.touch (useless_file)
@pod.root.should == @local_path
@pod.clean!
useless_file.exist?.should.be.true
end
it "doesn't implode" do
@pod.implode
@local_path.exist?.should.be.true
end
it "detects the files of the pod" do
@pod.source_files.map {|path| path.to_s.gsub(/.*tmp\//,'') }.sort.should == [
"localBanana/Classes/Banana.m",
"localBanana/Classes/Banana.h"
].sort
end
end
end
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
......@@ -12,7 +12,7 @@ describe 'Pod::Project' do
end
it "adds a group to the `Pods' group" do
group = @project.add_spec_group('JSONKit')
group = @project.add_spec_to_group('JSONKit', @project.pods)
@project.pods.child_references.should.include group.uuid
find_object({
'isa' => 'PBXGroup',
......@@ -23,7 +23,7 @@ describe 'Pod::Project' do
end
it "namespaces subspecs in groups" do
group = @project.add_spec_group('JSONKit/Subspec')
group = @project.add_spec_to_group('JSONKit/Subspec', @project.pods)
@project.pods.groups.find { |g| g.name == 'JSONKit' }.child_references.should.include group.uuid
find_object({
'isa' => 'PBXGroup',
......
......@@ -10,7 +10,7 @@ module Pod
pod 'BlocksKit'
# pod 'ASIWebPageRequest'
end
@resolver = Resolver.new(@podfile, stub('sandbox'))
@resolver = Resolver.new(@podfile, nil, stub('sandbox'))
end
it "holds the context state, such as cached specification sets" do
......@@ -73,7 +73,7 @@ module Pod
pod 'RestKit/Network'
pod 'RestKit/ObjectMapping/XML'
end
resolver = Resolver.new(@podfile, stub('sandbox'))
resolver = Resolver.new(@podfile, nil, stub('sandbox'))
resolver.resolve.values.flatten.map(&:name).sort.should == %w{
FileMD5Hash
ISO8601DateFormatter
......@@ -93,7 +93,7 @@ module Pod
platform :ios
pod 'RestKit'
end
resolver = Resolver.new(@podfile, stub('sandbox'))
resolver = Resolver.new(@podfile, nil, stub('sandbox'))
resolver.resolve.values.flatten.map(&:name).sort.should == %w{
FileMD5Hash
ISO8601DateFormatter
......@@ -139,7 +139,7 @@ module Pod
end
end
end
resolver = Resolver.new(@podfile, stub('sandbox'))
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{
......@@ -175,7 +175,7 @@ module Pod
end
end
@podfile.dependencies.first.external_source.stubs(:specification_from_sandbox).returns(spec)
resolver = Resolver.new(@podfile, stub('sandbox'))
resolver = Resolver.new(@podfile, nil, stub('sandbox'))
resolver.resolve.values.flatten.map(&:name).sort.should == %w{ MainSpec/FirstSubSpec MainSpec/FirstSubSpec/SecondSubSpec }
end
......@@ -185,10 +185,189 @@ module Pod
pod 'FileMD5Hash'
pod 'JSONKit', :head
end
resolver = Resolver.new(podfile, stub('sandbox'))
resolver = Resolver.new(podfile, nil, stub('sandbox'))
filemd5hash, jsonkit = resolver.resolve.values.first.sort_by(&:name)
filemd5hash.version.should.not.be.head
jsonkit.version.should.be.head
end
it "accepts a nil lockfile" do
lambda { Resolver.new(@podfile, nil, stub('sandbox'))}.should.not.raise
end
it "raises if it finds two conflicting dependencies" do
podfile = Podfile.new do
platform :ios
pod 'JSONKit', "1.4"
pod 'JSONKit', "1.5pre"
end
resolver = Resolver.new(podfile, nil, stub('sandbox'))
lambda {resolver.resolve}.should.raise Pod::Informative
end
describe "Concerning Installation mode" do
before do
config.repos_dir = fixture('spec-repos')
@podfile = 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 = Lockfile.generate(@podfile, @specs)
@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'
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
it "installs pods added to the Podfile" do
podfile = Podfile.new do
platform :ios
pod 'JSONKit'
pod 'BlocksKit'
pod 'libPusher'
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
podfile.dependencies.first.external_source.stubs(:specification_from_sandbox).returns(spec)
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.resolve
@resolver.should_install?("JSONKit").should.be.false
end
end
describe "Concerning Update mode" do
before do
config.repos_dir = fixture('spec-repos')
@podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit'
pod 'libPusher'
end
@specs = [
Pod::Specification.new do |s|
s.name = "libPusher"
s.version = "1.3"
end,
Pod::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 (1.5pre)"
@resolver.should_install?("JSONKit").should.be.true
end
it "respects the constraints of the podfile" do
podfile = Podfile.new do
platform :ios
pod 'BlocksKit'
pod 'JSONKit', '1.4'
end
@resolver = Resolver.new(podfile, @lockfile, stub('sandbox'))
@resolver.update_mode = true
installed = @resolver.resolve.values.flatten.map(&:to_s)
installed.should.include? "JSONKit (1.4)"
@resolver.should_install?("JSONKit").should.be.false
end
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
# 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
......@@ -14,23 +14,23 @@ describe "Pod::Specification::Set" do
end
it "checks if the dependency of the specification is compatible with existing requirements" do
@set.required_by(Pod::Spec.new { |s| s.dependency 'CocoaLumberjack', '1.2' })
@set.required_by(Pod::Spec.new { |s| s.dependency 'CocoaLumberjack', '< 1.2.1' })
@set.required_by(Pod::Spec.new { |s| s.dependency 'CocoaLumberjack', '> 1.1' })
@set.required_by(Pod::Spec.new { |s| s.dependency 'CocoaLumberjack', '~> 1.2.0' })
@set.required_by(Pod::Spec.new { |s| s.dependency 'CocoaLumberjack' })
@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::Spec.new { |s| s.dependency 'CocoaLumberjack', '< 1.0' })
@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::Spec.new { |s| s.dependency 'CocoaLumberjack', '< 1.0' })
@set.required_by(Pod::Dependency.new('CocoaLumberjack', '< 1.0'), 'Spec')
lambda { @set.required_version }.should.raise Pod::Informative
end
before do
@set.required_by(Pod::Spec.new { |s| s.dependency 'CocoaLumberjack', '< 1.2.1' })
@set.required_by(Pod::Dependency.new('CocoaLumberjack', '< 1.2.1'), 'Spec')
end
it "returns the version required for the dependency" do
......@@ -49,4 +49,9 @@ describe "Pod::Specification::Set" 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
......@@ -230,6 +230,12 @@ describe "A Pod::Specification, in general," 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
......@@ -479,7 +485,7 @@ describe "A Pod::Specification with :local source" do
end
it "it returns the expanded local path" do
@spec.local_path.should == fixture("integration/JSONKit")
@spec.source.should == {:local => fixture("integration/JSONKit")}
end
end
......
......@@ -8,5 +8,21 @@ module Pod
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