diff options
42 files changed, 2433 insertions, 378 deletions
diff --git a/.travis.yml b/.travis.yml index ca0a5c9ef6..9aa1486030 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,6 @@ before_script: - sudo apt-get install groff -y - rake spec:deps -script: rake spec:travis - -rvm: - - 1.8.7 - - 1.9.2 - - 1.9.3 - env: - RGV=v1.3.6 - RGV=v1.3.7 @@ -18,6 +11,8 @@ env: - RGV=v1.7.2 - RGV=v1.8.10 +language: ruby + matrix: exclude: - rvm: 1.9.2 @@ -37,3 +32,11 @@ notifications: email: - travis-ci@andrearko.com - hone02@gmail.com + - sferik@gmail.com + +rvm: + - 1.8.7 + - 1.9.2 + - 1.9.3 + +script: rake spec:travis diff --git a/CHANGELOG.md b/CHANGELOG.md index a3f0515ec1..f3d254ade6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,37 @@ +## 1.2.0.pre (May 4, 2012) + +Features: + + - bundle package now accepts --all to package git and path dependencies + - bundle config now accepts --local, --global and --delete options + - It is possible to override a git repository via configuration. + For instance, if you have a git dependency on rack, you can force + it to use a local repo with `bundle config local.rack ~/path/to/rack` + - Cache gemspec loads for performance (@dekellum, #1635) + - add --full-index flag to `bundle update` (@fluxx, #1829) + - add --quiet flag to `bundle update` (@nashby, #1654) + - Add Bundler::GemHelper.gemspec (@knu, #1637) + - Graceful handling of Gemfile syntax errors (@koraktor, #1661) + - `bundle platform` command + - add ruby to DSL, to specify version of ruby + - error out if the ruby version doesn't match + +Performance: + + - bundle exec shouldn't run Bundler.setup just setting the right rubyopts options is enough (@spastorino, #1598) + +Bugfixes: + + - Avoid passing RUBYOPT changes in with_clean_env block (@eric1234, #1604) + - Use the same ruby to run subprocesses as is running rake (@brixen) + +Documentation: + + - Add :github documentation in DSL (@zofrex, #1848, #1851, #1852) + - Add docs for the --no-cache option (@fluxx, #1796) + - Add basic documentation for bin_path and bundle_path (@radar) + - Add documentation for the run method in Bundler::Installer + ## 1.1.4 (May 27, 2012) Bugfixes: @@ -4,7 +4,7 @@ So! You're having problems with Bundler. This file is here to help. If you're ru ## Documentation -Instructions for common Bundler uses can be found on the [Bundler documentation site](http://gembundler.com/v1.0/). +Instructions for common Bundler uses can be found on the [Bundler documentation site](http://gembundler.com/v1.1/). Detailed information about each Bundler command, including help with common problems, can be found in the [Bundler man pages](http://gembundler.com/man/bundle.1.html). @@ -1,3 +1,5 @@ +[![Build Status](https://secure.travis-ci.org/carlhuda/bundler.png?branch=master)](http://travis-ci.org/carlhuda/bundler) + # Bundler: a gem to bundle gems Bundler is a tool that manages gem dependencies for your ruby application. It @@ -1,5 +1,6 @@ # -*- encoding: utf-8 -*- $:.unshift File.expand_path("../lib", __FILE__) +require 'rubygems' require 'bundler/gem_tasks' task :release => ["man:clean", "man:build"] @@ -18,8 +19,8 @@ end namespace :spec do desc "Ensure spec dependencies are installed" task :deps do - sh "gem list ronn | (grep 'ronn' 1> /dev/null) || gem install ronn --no-ri --no-rdoc" - sh "gem list rspec | (grep 'rspec (2.' 1> /dev/null) || gem install rspec --no-ri --no-rdoc" + sh "#{Gem.ruby} -S gem list ronn | (grep 'ronn' 1> /dev/null) || #{Gem.ruby} -S gem install ronn --no-ri --no-rdoc" + sh "#{Gem.ruby} -S gem list rspec | (grep 'rspec (2.' 1> /dev/null) || #{Gem.ruby} -S gem install rspec --no-ri --no-rdoc" end end @@ -40,7 +41,7 @@ begin rm_rf 'tmp' end - desc "Run the real-world spec suite (reequires internet)" + desc "Run the real-world spec suite (requires internet)" task :realworld => ["set_realworld", "spec"] task :set_realworld do @@ -142,7 +143,7 @@ begin roff = "lib/bundler/man/#{basename}" file roff => ["lib/bundler/man", ronn] do - sh "ronn --roff --pipe #{ronn} > #{roff}" + sh "#{Gem.ruby} -S ronn --roff --pipe #{ronn} > #{roff}" end file "#{roff}.txt" => roff do @@ -178,7 +179,7 @@ begin desc "Install CI dependencies" task :deps do - sh "gem list ci_reporter | (grep 'ci_reporter' 1> /dev/null) || gem install ci_reporter --no-ri --no-rdoc" + sh "#{Gem.ruby} -S gem list ci_reporter | (grep 'ci_reporter' 1> /dev/null) || #{Gem.ruby} -S gem install ci_reporter --no-ri --no-rdoc" end task :deps => "spec:deps" end diff --git a/bin/bundle b/bin/bundle index 1957812ab0..53997cf2f4 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,29 +1,14 @@ #!/usr/bin/env ruby require 'bundler' -begin - # Check if an older version of bundler is installed - $:.each do |path| - if path =~ %r'/bundler-0.(\d+)' && $1.to_i < 9 - err = "Please remove Bundler 0.8 versions." - err << "This can be done by running `gem cleanup bundler`." - abort(err) - end +# Check if an older version of bundler is installed +$:.each do |path| + if path =~ %r'/bundler-0.(\d+)' && $1.to_i < 9 + err = "Please remove Bundler 0.8 versions." + err << "This can be done by running `gem cleanup bundler`." + abort(err) end - require 'bundler/cli' - Bundler::CLI.start -rescue Bundler::BundlerError => e - Bundler.ui.error e.message - Bundler.ui.debug e.backtrace.join("\n") - exit e.status_code -rescue Interrupt => e - Bundler.ui.error "\nQuitting..." - Bundler.ui.debug e.backtrace.join("\n") - exit 1 -rescue SystemExit => e - exit e.status -rescue Exception => e - Bundler.ui.error( - "Unfortunately, a fatal error has occurred. Please see the Bundler \n" \ - "troubleshooting documentation at http://bit.ly/bundler-issues. Thanks! \n") - raise e end + +require 'bundler/cli' +require 'bundler/friendly_errors' +Bundler.with_friendly_errors { Bundler::CLI.start } diff --git a/lib/bundler.rb b/lib/bundler.rb index ee4d5f6971..8c325dac93 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -27,12 +27,14 @@ module Bundler autoload :MatchPlatform, 'bundler/match_platform' autoload :RemoteSpecification, 'bundler/remote_specification' autoload :Resolver, 'bundler/resolver' + autoload :RubyVersion, 'bundler/ruby_version' autoload :Runtime, 'bundler/runtime' autoload :Settings, 'bundler/settings' autoload :SharedHelpers, 'bundler/shared_helpers' autoload :SpecSet, 'bundler/spec_set' autoload :Source, 'bundler/source' autoload :Specification, 'bundler/shared_helpers' + autoload :SystemRubyVersion, 'bundler/ruby_version' autoload :UI, 'bundler/ui' class BundlerError < StandardError @@ -41,19 +43,20 @@ module Bundler end end - class GemfileNotFound < BundlerError; status_code(10) ; end - class GemNotFound < BundlerError; status_code(7) ; end - class GemfileError < BundlerError; status_code(4) ; end - class InstallError < BundlerError; status_code(5) ; end - class InstallHookError < BundlerError; status_code(6) ; end - class PathError < BundlerError; status_code(13) ; end - class GitError < BundlerError; status_code(11) ; end - class DeprecatedError < BundlerError; status_code(12) ; end - class GemspecError < BundlerError; status_code(14) ; end - class DslError < BundlerError; status_code(15) ; end - class ProductionError < BundlerError; status_code(16) ; end - class InvalidOption < DslError ; end - class HTTPError < BundlerError; status_code(17) ; end + class GemfileNotFound < BundlerError; status_code(10) ; end + class GemNotFound < BundlerError; status_code(7) ; end + class GemfileError < BundlerError; status_code(4) ; end + class InstallError < BundlerError; status_code(5) ; end + class InstallHookError < BundlerError; status_code(6) ; end + class PathError < BundlerError; status_code(13) ; end + class GitError < BundlerError; status_code(11) ; end + class DeprecatedError < BundlerError; status_code(12) ; end + class GemspecError < BundlerError; status_code(14) ; end + class DslError < BundlerError; status_code(15) ; end + class ProductionError < BundlerError; status_code(16) ; end + class InvalidOption < DslError ; end + class HTTPError < BundlerError; status_code(17) ; end + class RubyVersionMismatch < BundlerError; status_code(18) ; end WINDOWS = RbConfig::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw)! @@ -85,10 +88,12 @@ module Bundler @ui ||= UI.new end + # Returns absolute path of where gems are installed on the filesystem. def bundle_path @bundle_path ||= Pathname.new(settings.path).expand_path(root) end + # Returns absolute location of where binstubs are installed to. def bin_path @bin_path ||= begin path = settings[:bin] || "bin" @@ -102,6 +107,8 @@ module Bundler # Just return if all groups are already loaded return @setup if defined?(@setup) + definition.validate_ruby! + if groups.empty? # Load all groups, but only once @setup = load.setup @@ -268,6 +275,15 @@ module Bundler end def load_gemspec(file) + @gemspec_cache ||= {} + key = File.expand_path(file) + spec = ( @gemspec_cache[key] ||= load_gemspec_uncached(file) ) + # Protect against caching side-effected gemspecs by returning a + # new instance each time. + spec.dup if spec + end + + def load_gemspec_uncached(file) path = Pathname.new(file) # Eval the gemspec from its parent directory Dir.chdir(path.dirname.to_s) do @@ -294,6 +310,10 @@ module Bundler end end + def clear_gemspec_cache + @gemspec_cache = {} + end + private def configure_gem_home_and_path diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 665f2365c7..c5f4fc7ad6 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -99,7 +99,9 @@ module Bundler Bundler.settings[:path] = File.expand_path(options[:path]) if options[:path] begin - not_installed = Bundler.definition.missing_specs + definition = Bundler.definition + definition.validate_ruby! + not_installed = definition.missing_specs rescue GemNotFound, VersionConflict Bundler.ui.error "Your Gemfile's dependencies could not be satisfied" Bundler.ui.warn "Install missing gems with `bundle install`" @@ -111,6 +113,9 @@ module Bundler not_installed.each { |s| Bundler.ui.error " * #{s.name} (#{s.version})" } Bundler.ui.warn "Install missing gems with `bundle install`" exit 1 + elsif !Bundler.default_lockfile.exist? && Bundler.settings[:frozen] + Bundler.ui.error "This bundle has been frozen, but there is no Gemfile.lock present" + exit 1 else Bundler.load.lock Bundler.ui.info "The Gemfile's dependencies are satisfied" @@ -217,7 +222,9 @@ module Bundler # rubygems plugins sometimes hook into the gem install process Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins) - Installer.install(Bundler.root, Bundler.definition, opts) + definition = Bundler.definition + definition.validate_ruby! + Installer.install(Bundler.root, definition, opts) Bundler.load.cache if Bundler.root.join("vendor/cache").exist? && !options["no-cache"] if Bundler.settings[:path] @@ -256,8 +263,13 @@ module Bundler method_option "source", :type => :array, :banner => "Update a specific source (and all gems associated with it)" method_option "local", :type => :boolean, :banner => "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "quiet", :type => :boolean, :banner => + "Only output warnings and errors." + method_option "full-index", :type => :boolean, :banner => + "Use the rubygems modern index instead of the API endpoint" def update(*gems) sources = Array(options[:source]) + Bundler.ui.be_quiet! if options[:quiet] if gems.empty? && sources.empty? # We're doing a full update @@ -266,10 +278,13 @@ module Bundler Bundler.definition(:gems => gems, :sources => sources) end + Bundler::Fetcher.disable_endpoint = options["full-index"] + opts = {"update" => true, "local" => options[:local]} # rubygems plugins sometimes hook into the gem install process Gem.load_env_plugins if Gem.respond_to?(:load_env_plugins) + Bundler.definition.validate_ruby! Installer.install Bundler.root, Bundler.definition, opts Bundler.load.cache if Bundler.root.join("vendor/cache").exist? clean if Bundler.settings[:clean] && Bundler.settings[:path] @@ -285,6 +300,7 @@ module Bundler method_option "paths", :type => :boolean, :banner => "List the paths of all gems that are required by your Gemfile." def show(gem_name = nil) + Bundler.definition.validate_ruby! Bundler.load.lock if gem_name @@ -314,6 +330,7 @@ module Bundler "Do not attempt to fetch gems remotely and use the gem cache instead" def outdated(*gems) sources = Array(options[:source]) + Bundler.definition.validate_ruby! current_specs = Bundler.load.specs if gems.empty? && sources.empty? @@ -362,8 +379,11 @@ module Bundler desc "cache", "Cache all the gems to vendor/cache", :hide => true method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache." + method_option "all", :type => :boolean, :banner => "Include all sources (including path and git)." def cache + Bundler.definition.validate_ruby! Bundler.definition.resolve_with_cache! + setup_cache_all Bundler.load.cache Bundler.settings[:no_prune] = true if options["no-prune"] Bundler.load.lock @@ -375,6 +395,7 @@ module Bundler desc "package", "Locks and then caches all of the gems into vendor/cache" method_option "no-prune", :type => :boolean, :banner => "Don't remove stale gems from the cache." + method_option "all", :type => :boolean, :banner => "Include all sources (including path and git)." long_desc <<-D The package command will copy the .gem files for every gem in the bundle into the directory ./vendor/cache. If you then check that directory into your source @@ -382,6 +403,7 @@ module Bundler bundle without having to download any additional gems. D def package + setup_cache_all install # TODO: move cache contents here now that all bundles are locked Bundler.load.cache @@ -397,6 +419,7 @@ module Bundler def exec(*) ARGV.shift # remove "exec" + Bundler.definition.validate_ruby! Bundler.load.setup_environment begin @@ -427,10 +450,17 @@ module Bundler will show the current value, as well as any superceded values and where they were specified. D - def config(name = nil, *args) + def config(*) values = ARGV.dup values.shift # remove config - values.shift # remove the name + + peek = values.shift + + if peek && peek =~ /^\-\-/ + name, scope = values.shift, $' + else + name, scope = peek, "global" + end unless name Bundler.ui.confirm "Settings are listed in order of priority. The top value will be used.\n" @@ -447,29 +477,45 @@ module Bundler return end - if values.empty? - Bundler.ui.confirm "Settings for `#{name}` in order of priority. The top value will be used" - with_padding do - Bundler.settings.pretty_values_for(name).each { |line| Bundler.ui.info line } + case scope + when "delete" + Bundler.settings.set_local(name, nil) + Bundler.settings.set_global(name, nil) + when "local", "global" + if values.empty? + Bundler.ui.confirm "Settings for `#{name}` in order of priority. The top value will be used" + with_padding do + Bundler.settings.pretty_values_for(name).each { |line| Bundler.ui.info line } + end + return end - else + locations = Bundler.settings.locations(name) - if local = locations[:local] - Bundler.ui.info "Your application has set #{name} to #{local.inspect}. This will override the " \ - "system value you are currently setting" - end + if scope == "global" + if local = locations[:local] + Bundler.ui.info "Your application has set #{name} to #{local.inspect}. This will override the " \ + "global value you are currently setting" + end + + if env = locations[:env] + Bundler.ui.info "You have a bundler environment variable for #{name} set to #{env.inspect}. " \ + "This will take precedence over the global value you are setting" + end - if global = locations[:global] - Bundler.ui.info "You are replacing the current system value of #{name}, which is currently #{global}" + if global = locations[:global] + Bundler.ui.info "You are replacing the current global value of #{name}, which is currently #{global.inspect}" + end end - if env = locations[:env] - Bundler.ui.info "You have set a bundler environment variable for #{env}. This will take precedence " \ - "over the system value you are setting" + if scope == "local" && local = locations[:local] + Bundler.ui.info "You are replacing the current local value of #{name}, which is currently #{local.inspect}" end - Bundler.settings.set_global(name, values.join(" ")) + Bundler.settings.send("set_#{scope}", name, values.join(" ")) + else + Bundler.ui.error "Invalid scope --#{scope} given. Please use --local or --global." + exit 1 end end @@ -583,8 +629,53 @@ module Bundler end end + desc "platform", "Displays platform compatibility information" + method_option "ruby", :type => :boolean, :default => false, :banner => + "only display ruby related platform information" + def platform + platforms = Bundler.definition.platforms.map {|p| "* #{p}" } + ruby_version = Bundler.definition.ruby_version + output = [] + + if options[:ruby] + if ruby_version + output << ruby_version + else + output << "No ruby version specified" + end + else + output << "Your platform is: #{RUBY_PLATFORM}" + output << "Your app has gems that work on these platforms:\n#{platforms.join("\n")}" + + if ruby_version + output << "Your Gemfile specifies a Ruby version requirement:\n* #{ruby_version}" + + begin + Bundler.definition.validate_ruby! + output << "Your current platform satisfies the Ruby version requirement." + rescue RubyVersionMismatch => e + output << e.message + end + else + output << "Your Gemfile does not specify a Ruby version requirement." + end + end + + Bundler.ui.info output.join("\n\n") + end + private + def setup_cache_all + if options.key?("all") + Bundler.settings[:cache_all] = options[:all] || nil + elsif Bundler.definition.sources.any? { |s| !s.is_a?(Source::Rubygems) } + Bundler.ui.warn "Your Gemfile contains path and git dependencies. If you want " \ + "to package them as well, please pass the --all flag. This will be the default " \ + "on Bundler 2.0." + end + end + def have_groff? !(`which groff` rescue '').empty? end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 874d2e1011..e215b771b1 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -5,7 +5,7 @@ module Bundler class Definition include GemHelpers - attr_reader :dependencies, :platforms, :sources + attr_reader :dependencies, :platforms, :sources, :ruby_version def self.build(gemfile, lockfile, unlock) unlock ||= {} @@ -30,13 +30,14 @@ module Bundler specs, then we can try to resolve locally. =end - def initialize(lockfile, dependencies, sources, unlock) + def initialize(lockfile, dependencies, sources, unlock, ruby_version = "") @unlocking = unlock == true || !unlock.empty? @dependencies, @sources, @unlock = dependencies, sources, unlock @remote = false @specs = nil @lockfile_contents = "" + @ruby_version = ruby_version if lockfile && File.exists?(lockfile) @lockfile_contents = Bundler.read_file(lockfile) @@ -68,28 +69,13 @@ module Bundler @new_platform = !@platforms.include?(current_platform) @platforms |= [current_platform] - @path_changes = @sources.any? do |source| - next unless source.instance_of?(Source::Path) - - locked = @locked_sources.find do |ls| - ls.class == source.class && ls.path == source.path - end - - if locked - unlocking = locked.specs.any? do |spec| - @locked_specs.any? do |locked_spec| - locked_spec.source != locked - end - end - end - - !locked || unlocking || source.specs != locked.specs - end + @path_changes = converge_paths eager_unlock = expand_dependencies(@unlock[:gems]) @unlock[:gems] = @locked_specs.for(eager_unlock).map { |s| s.name } @source_changes = converge_sources @dependency_changes = converge_dependencies + @local_changes = converge_locals fixup_dependency_types! end @@ -174,7 +160,7 @@ module Bundler def resolve @resolve ||= begin - if Bundler.settings[:frozen] || (!@unlocking && !@source_changes && !@dependency_changes && !@new_platform && !@path_changes) + if Bundler.settings[:frozen] || (!@unlocking && nothing_changed?) @locked_specs else last_resolve = converge_locked_specs @@ -350,8 +336,32 @@ module Bundler raise ProductionError, msg if added.any? || deleted.any? || changed.any? end + def validate_ruby! + return unless ruby_version + + system_ruby_version = Bundler::SystemRubyVersion.new + if diff = ruby_version.diff(system_ruby_version) + problem, expected, actual = diff + + msg = case problem + when :engine + "Your Ruby engine is #{actual}, but your Gemfile specified #{expected}" + when :version + "Your Ruby version is #{actual}, but your Gemfile specified #{expected}" + when :engine_version + "Your #{system_ruby_version.engine} version is #{actual}, but your Gemfile specified #{ruby_version.engine} #{expected}" + end + + raise RubyVersionMismatch, msg + end + end + private + def nothing_changed? + !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes + end + def pretty_dep(dep, source = false) msg = "#{dep.name}" msg << " (#{dep.requirement})" unless dep.requirement == Gem::Requirement.default @@ -359,6 +369,52 @@ module Bundler msg end + # Check if the specs of the given source changed + # according to the locked source. A block should be + # in order to specify how the locked version of + # the source should be found. + def specs_changed?(source, &block) + locked = @locked_sources.find(&block) + + if locked + unlocking = locked.specs.any? do |spec| + @locked_specs.any? do |locked_spec| + locked_spec.source != locked + end + end + end + + !locked || unlocking || source.specs != locked.specs + end + + # Get all locals and override their matching sources. + # Return true if any of the locals changed (for example, + # they point to a new revision) or depend on new specs. + def converge_locals + locals = [] + + Bundler.settings.local_overrides.map do |k,v| + spec = @dependencies.find { |s| s.name == k } + source = spec && spec.source + if source && source.respond_to?(:local_override!) + locals << [ source, source.local_override!(v) ] + end + end + + locals.any? do |source, changed| + changed || specs_changed?(source) { |o| source.class === o.class && source.uri == o.uri } + end + end + + def converge_paths + @sources.any? do |source| + next unless source.instance_of?(Source::Path) + specs_changed?(source) do |ls| + ls.class == source.class && ls.path == source.path + end + end + end + def converge_sources changes = false diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index f62cf03ee9..f54b0d9ef8 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -4,7 +4,7 @@ module Bundler class Dsl def self.evaluate(gemfile, lockfile, unlock) builder = new - builder.instance_eval(Bundler.read_file(gemfile.to_s), gemfile.to_s, 1) + builder.eval_gemfile(gemfile) builder.to_definition(lockfile, unlock) rescue ScriptError, RegexpError, NameError, ArgumentError => e e.backtrace[0] = "#{e.backtrace[0]}: #{e.message} (#{e.class})" @@ -15,6 +15,8 @@ module Bundler VALID_PLATFORMS = Bundler::Dependency::PLATFORM_MAP.keys.freeze + attr_accessor :dependencies + def initialize @rubygems_source = Source::Rubygems.new @source = nil @@ -23,9 +25,15 @@ module Bundler @groups = [] @platforms = [] @env = nil + @ruby_version = nil end - attr_accessor :dependencies + def eval_gemfile(gemfile) + instance_eval(Bundler.read_file(gemfile.to_s), gemfile.to_s, 1) + rescue SyntaxError => e + bt = e.message.split("\n")[1..-1] + raise GemfileError, ["Gemfile syntax error:", *bt].join("\n") + end def gemspec(opts = nil) path = opts && opts[:path] || '.' @@ -59,7 +67,6 @@ module Bundler options = Hash === args.last ? args.pop : {} version = args || [">= 0"] - _deprecated_options(options) _normalize_options(name, version, options) dep = Dependency.new(name, version, options) @@ -138,7 +145,7 @@ module Bundler def to_definition(lockfile, unlock) @sources << @rubygems_source unless @sources.include?(@rubygems_source) - Definition.new(lockfile, @dependencies, @sources, unlock) + Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version) end def group(*args, &blk) @@ -163,19 +170,18 @@ module Bundler @env = old end - # Deprecated methods + def ruby(ruby_version, options = {}) + raise GemfileError, "Please define :engine_version" if options[:engine] && options[:engine_version].nil? + raise GemfileError, "Please define :engine" if options[:engine_version] && options[:engine].nil? - def self.deprecate(name, replacement = nil) - define_method(name) do |*| - message = "'#{name}' has been removed from the Gemfile DSL, " - if replacement - message << "and has been replaced with '#{replacement}'." - else - message << "and is no longer supported." - end - message << "\nSee the README for more information on upgrading from Bundler 0.8." - raise DeprecatedError, message - end + raise GemfileError, "ruby_version must match the :engine_version for MRI" if options[:engine] == "ruby" && options[:engine_version] && ruby_version != options[:engine_version] + @ruby_version = RubyVersion.new(ruby_version, options[:engine], options[:engine_version]) + end + + def method_missing(name, *args) + location = caller[0].split(':')[0..1].join(':') + raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile\n" \ + " from #{location}" end private @@ -194,7 +200,8 @@ module Bundler def _normalize_options(name, version, opts) _normalize_hash(opts) - invalid_keys = opts.keys - %w(group groups git github path name branch ref tag require submodules platform platforms type) + valid_keys = %w(group groups git github path name branch ref tag require submodules platform platforms type) + invalid_keys = opts.keys - valid_keys if invalid_keys.any? plural = invalid_keys.size > 1 message = "You passed #{invalid_keys.map{|k| ':'+k }.join(", ")} " @@ -203,6 +210,8 @@ module Bundler else message << "as an option for gem '#{name}', but it is invalid." end + + message << " Valid options are: #{valid_keys.join(", ")}" raise InvalidOption, message end @@ -243,7 +252,5 @@ module Bundler opts["group"] = groups end - def _deprecated_options(options) - end end end diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index b7cf4028bc..9121328419 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -180,12 +180,13 @@ module Bundler name, requirement = d dep = Gem::Dependency.new(name, requirement.split(", ")) rescue ArgumentError => e - if e.message.include?('Illformed requirement ["#<YAML::Syck::DefaultKey') + if e.message.include?('Ill-formed requirement ["#<YAML::Syck::DefaultKey') puts # we shouldn't print the error message on the "fetching info" status line raise GemspecError, %{Unfortunately, the gem #{s[:name]} (#{s[:number]}) } + %{has an invalid gemspec. As a result, Bundler cannot install this Gemfile. } + %{Please ask the gem author to yank the bad version to fix this issue. For } + - %{more information, see http://bit.ly/syck-defaultkey.} + %{more information, see http://bit.ly/syck-defaultkey. For a temporary } + + %{workaround try using the --full-index option.} else raise e end diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb new file mode 100644 index 0000000000..5cd4e19d26 --- /dev/null +++ b/lib/bundler/friendly_errors.rb @@ -0,0 +1,22 @@ +module Bundler + def self.with_friendly_errors + begin + yield + rescue Bundler::BundlerError => e + Bundler.ui.error e.message + Bundler.ui.debug e.backtrace.join("\n") + exit e.status_code + rescue Interrupt => e + Bundler.ui.error "\nQuitting..." + Bundler.ui.debug e.backtrace.join("\n") + exit 1 + rescue SystemExit => e + exit e.status + rescue Exception => e + Bundler.ui.error( + "Unfortunately, a fatal error has occurred. Please see the Bundler \n" \ + "troubleshooting documentation at http://bit.ly/bundler-issues. Thanks! \n") + raise e + end + end +end diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb index 2de7aeb0c2..74cd4f8538 100644 --- a/lib/bundler/gem_helper.rb +++ b/lib/bundler/gem_helper.rb @@ -6,16 +6,26 @@ module Bundler class GemHelper include Rake::DSL if defined? Rake::DSL - def self.install_tasks(opts = {}) - dir = opts[:dir] || Dir.pwd - self.new(dir, opts[:name]).install + class << self + # set when install'd. + attr_accessor :instance + + def install_tasks(opts = {}) + new(opts[:dir], opts[:name]).install + end + + def gemspec(&block) + gemspec = instance.gemspec + block.call(gemspec) if block + gemspec + end end attr_reader :spec_path, :base, :gemspec - def initialize(base, name = nil) + def initialize(base = nil, name = nil) Bundler.ui = UI::Shell.new(Thor::Base.shell.new) - @base = base + @base = (base ||= Dir.pwd) gemspecs = name ? [File.join(base, "#{name}.gemspec")] : Dir[File.join(base, "{,*}.gemspec")] raise "Unable to determine name from existing gemspec. Use :name => 'gemname' in #install_tasks to manually set it." unless gemspecs.size == 1 @spec_path = gemspecs.first @@ -37,6 +47,8 @@ module Bundler task 'release' do release_gem end + + GemHelper.instance = self end def build_gem diff --git a/lib/bundler/graph.rb b/lib/bundler/graph.rb index 6ab23d90ef..897c9a806f 100644 --- a/lib/bundler/graph.rb +++ b/lib/bundler/graph.rb @@ -40,9 +40,9 @@ module Bundler @relations[dependency.name] += child_dependencies.map(&:name).to_set tmp += child_dependencies - @node_options[dependency.name] = {:label => _make_label(dependency, :node)} + @node_options[dependency.name] = _make_label(dependency, :node) child_dependencies.each do |c_dependency| - @edge_options["#{dependency.name}_#{c_dependency.name}"] = {:label => _make_label(c_dependency, :edge)} + @edge_options["#{dependency.name}_#{c_dependency.name}"] = _make_label(c_dependency, :edge) end end parent_dependencies = tmp @@ -58,8 +58,8 @@ module Bundler relations[group.to_s].add(dependency) @relations[group.to_s].add(dependency.name) - @node_options[group.to_s] ||= {:label => _make_label(group, :node)} - @edge_options["#{group}_#{dependency.name}"] = {:label => _make_label(dependency, :edge)} + @node_options[group.to_s] ||= _make_label(group, :node) + @edge_options["#{group}_#{dependency.name}"] = _make_label(dependency, :edge) end end @groups = relations.keys @@ -84,7 +84,7 @@ module Bundler else raise ArgumentError, "2nd argument is invalid" end - label + label.nil? ? {} : { :label => label } end class GraphVizClient @@ -109,7 +109,7 @@ module Bundler def run @groups.each do |group| - g.add_node( + g.add_nodes( group, {:style => 'filled', :fillcolor => '#B9B9D5', @@ -121,11 +121,11 @@ module Bundler @relations.each do |parent, children| children.each do |child| if @groups.include?(parent) - g.add_node(child, {:style => 'filled', :fillcolor => '#B9B9D5'}.merge(@node_options[child])) - g.add_edge(parent, child, {:constraint => false}.merge(@edge_options["#{parent}_#{child}"])) + g.add_nodes(child, {:style => 'filled', :fillcolor => '#B9B9D5'}.merge(@node_options[child])) + g.add_edges(parent, child, {:constraint => false}.merge(@edge_options["#{parent}_#{child}"])) else - g.add_node(child, @node_options[child]) - g.add_edge(parent, child, @edge_options["#{parent}_#{child}"]) + g.add_nodes(child, @node_options[child]) + g.add_edges(parent, child, @edge_options["#{parent}_#{child}"]) end end end diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index 790721b624..a99f0650bf 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -7,12 +7,45 @@ module Bundler attr_accessor :post_install_messages end + # Begins the installation process for Bundler. + # For more information see the #run method on this class. def self.install(root, definition, options = {}) installer = new(root, definition) installer.run(options) installer end + # Runs the install procedures for a specific Gemfile. + # + # Firstly, this method will check to see if Bundler.bundle_path exists + # and if not then will create it. This is usually the location of gems + # on the system, be it RVM or at a system path. + # + # Secondly, it checks if Bundler has been configured to be "frozen" + # Frozen ensures that the Gemfile and the Gemfile.lock file are matching. + # This stops a situation where a developer may update the Gemfile but may not run + # `bundle install`, which leads to the Gemfile.lock file not being correctly updated. + # If this file is not correctly updated then any other developer running + # `bundle install` will potentially not install the correct gems. + # + # Thirdly, Bundler checks if there are any dependencies specified in the Gemfile using + # Bundler::Environment#dependencies. If there are no dependencies specified then + # Bundler returns a warning message stating so and this method returns. + # + # Fourthly, Bundler checks if the default lockfile (Gemfile.lock) exists, and if so + # then proceeds to set up a defintion based on the default gemfile (Gemfile) and the + # default lock file (Gemfile.lock). However, this is not the case if the platform is different + # to that which is specified in Gemfile.lock, or if there are any missing specs for the gems. + # + # Fifthly, Bundler resolves the dependencies either through a cache of gems or by remote. + # This then leads into the gems being installed, along with stubs for their executables, + # but only if the --binstubs option has been passed or Bundler.options[:bin] has been set + # earlier. + # + # Sixthly, a new Gemfile.lock is created from the installed gems to ensure that the next time + # that a user runs `bundle install` they will receive any updates from this process. + # + # Finally: TODO add documentation for how the standalone process works. def run(options) # Create the BUNDLE_PATH directory begin @@ -88,7 +121,7 @@ module Bundler # other failure, likely a native extension build failure Bundler.ui.info "" Bundler.ui.warn "#{e.class}: #{e.message}" - msg = "An error occured while installing #{spec.name} (#{spec.version})," + msg = "An error occurred while installing #{spec.name} (#{spec.version})," msg << " and Bundler cannot continue.\nMake sure that `gem install" msg << " #{spec.name} -v '#{spec.version}'` succeeds before bundling." Bundler.ui.debug e.backtrace.join("\n") diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb new file mode 100644 index 0000000000..57b6caaa51 --- /dev/null +++ b/lib/bundler/ruby_version.rb @@ -0,0 +1,94 @@ +module Bundler + class RubyVersion + attr_reader :version, :engine, :engine_version + + def initialize(version, engine, engine_version) + # The parameters to this method must satisfy the + # following constraints, which are verified in + # the DSL: + # + # * If an engine is specified, an engine version + # must also be specified + # * If an engine version is specified, an engine + # must also be specified + # * If the engine is "ruby", the engine version + # must not be specified, or the engine version + # specified must match the version. + + @version = version + @engine = engine || "ruby" + @engine_version = engine_version || version + end + + def to_s + output = "ruby #{version}" + output << " (#{engine} #{engine_version})" unless engine == "ruby" + + output + end + + def ==(other) + version == other.version && + engine == other.engine && + engine_version == other.engine_version + end + + # Returns a tuple of thsee things: + # [diff, this, other] + # The priority of attributes are + # 1. engine + # 2. ruby_version + # 3. engine_version + def diff(other) + if engine != other.engine + [ :engine, engine, other.engine ] + elsif version != other.version + [ :version, version, other.version ] + elsif engine_version != other.engine_version + [ :engine_version, engine_version, other.engine_version ] + else + nil + end + end + end + + # A subclass of RubyVersion that implements version, + # engine and engine_version based upon the current + # information in the system. It can be used anywhere + # a RubyVersion object is expected, and can be + # compared with a RubyVersion object. + class SystemRubyVersion < RubyVersion + def initialize(*) + # override the default initialize, because + # we will implement version, engine and + # engine_version dynamically + end + + def version + RUBY_VERSION + end + + def engine + if defined?(RUBY_ENGINE) + RUBY_ENGINE + else + # not defined in ruby 1.8.7 + "ruby" + end + end + + def engine_version + case engine + when "ruby" + RUBY_VERSION + when "rbx" + Rubinius::VERSION + when "jruby" + JRUBY_VERSION + else + raise BundlerError, "That RUBY_ENGINE is not recognized" + nil + end + end + end +end diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 86153b2a93..52e3dc6e51 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -97,7 +97,7 @@ module Bundler def cache FileUtils.mkdir_p(cache_path) unless File.exists?(cache_path) - Bundler.ui.info "Updating .gem files in vendor/cache" + Bundler.ui.info "Updating files in vendor/cache" specs.each do |spec| next if spec.name == 'bundler' spec.source.cache(spec) if spec.source.respond_to?(:cache) diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index f7dc1b477c..adf018505c 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -15,6 +15,8 @@ module Bundler set_key(key, value, @local_config, local_config_file) end + alias :set_local :[]= + def delete(key) @local_config.delete(key_for(key)) end @@ -32,9 +34,19 @@ module Bundler end end + def local_overrides + repos = {} + all.each do |k| + if k =~ /^local\./ + repos[$'] = self[k] + end + end + repos + end + def locations(key) + key = key_for(key) locations = {} - locations[:local] = @local_config[key] if @local_config.key?(key) locations[:env] = ENV[key] if ENV[key] locations[:global] = @global_config[key] if @global_config.key?(key) @@ -71,8 +83,9 @@ module Bundler # @local_config["BUNDLE_PATH"] should be prioritized over ENV["BUNDLE_PATH"] def path - path = ENV[key_for(:path)] || @global_config[key_for(:path)] - return path if path && !@local_config.key?(key_for(:path)) + key = key_for(:path) + path = ENV[key] || @global_config[key] + return path if path && !@local_config.key?(key) if path = self[:path] "#{path}/#{Bundler.ruby_scope}" diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index 0de9983d2c..f5d55a0d79 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -4,7 +4,7 @@ require "rubygems/installer" require "rubygems/spec_fetcher" require "rubygems/format" require "digest/sha1" -require "open3" +require "fileutils" module Bundler module Source @@ -264,12 +264,37 @@ module Bundler Bundler.rubygems.sources = old end end - end + class Path - attr_reader :path, :options - # Kind of a hack, but needed for the lock file parser + class Installer < Bundler::GemInstaller + def initialize(spec, options = {}) + @spec = spec + @bin_dir = Bundler.requires_sudo? ? "#{Bundler.tmp}/bin" : "#{Bundler.rubygems.gem_dir}/bin" + @gem_dir = Bundler.rubygems.path(spec.full_gem_path) + @wrappers = options[:wrappers] || true + @env_shebang = options[:env_shebang] || true + @format_executable = options[:format_executable] || false + end + + def generate_bin + return if spec.executables.nil? || spec.executables.empty? + + if Bundler.requires_sudo? + FileUtils.mkdir_p("#{Bundler.tmp}/bin") unless File.exist?("#{Bundler.tmp}/bin") + end + super + if Bundler.requires_sudo? + Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/bin" + spec.executables.each do |exe| + Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Bundler.rubygems.gem_dir}/bin/" + end + end + end + end + + attr_reader :path, :options attr_writer :name attr_accessor :version @@ -287,8 +312,12 @@ module Bundler @path = @path.expand_path(Bundler.root) unless @path.relative? end - @name = options["name"] + @name = options["name"] @version = options["version"] + + # Stores the original path. If at any point we move to the + # cached directory, we still have the original path to copy from. + @original_path = @path end def remote! @@ -330,9 +359,46 @@ module Bundler File.basename(path.expand_path(Bundler.root).to_s) end + def install(spec) + Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} " + # Let's be honest, when we're working from a path, we can't + # really expect native extensions to work because the whole point + # is to just be able to modify what's in that path and go. So, let's + # not put ourselves through the pain of actually trying to generate + # the full gem. + Installer.new(spec).generate_bin + end + + def cache(spec) + return unless Bundler.settings[:cache_all] + return if @original_path.expand_path(Bundler.root).to_s.index(Bundler.root.to_s) == 0 + FileUtils.rm_rf(app_cache_path) + FileUtils.cp_r("#{@original_path}/.", app_cache_path) + end + + def local_specs(*) + @local_specs ||= load_spec_files + end + + def specs + if has_app_cache? + @path = app_cache_path + end + local_specs + end + + private + + def app_cache_path + @app_cache_path ||= Bundler.app_cache.join(name) + end + + def has_app_cache? + SharedHelpers.in_bundle? && app_cache_path.exist? + end + def load_spec_files index = Index.new - expanded_path = path.expand_path(Bundler.root) if File.directory?(expanded_path) @@ -368,61 +434,10 @@ module Bundler index end - def local_specs(*) - @local_specs ||= load_spec_files - end - - class Installer < Bundler::GemInstaller - def initialize(spec, options = {}) - @spec = spec - @bin_dir = Bundler.requires_sudo? ? "#{Bundler.tmp}/bin" : "#{Bundler.rubygems.gem_dir}/bin" - @gem_dir = Bundler.rubygems.path(spec.full_gem_path) - @wrappers = options[:wrappers] || true - @env_shebang = options[:env_shebang] || true - @format_executable = options[:format_executable] || false - end - - def generate_bin - return if spec.executables.nil? || spec.executables.empty? - - if Bundler.requires_sudo? - FileUtils.mkdir_p("#{Bundler.tmp}/bin") unless File.exist?("#{Bundler.tmp}/bin") - end - super - if Bundler.requires_sudo? - Bundler.mkdir_p "#{Bundler.rubygems.gem_dir}/bin" - spec.executables.each do |exe| - Bundler.sudo "cp -R #{Bundler.tmp}/bin/#{exe} #{Bundler.rubygems.gem_dir}/bin/" - end - end - end - end - - def install(spec) - Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} " - # Let's be honest, when we're working from a path, we can't - # really expect native extensions to work because the whole point - # is to just be able to modify what's in that path and go. So, let's - # not put ourselves through the pain of actually trying to generate - # the full gem. - Installer.new(spec).generate_bin - end - - alias specs local_specs - - def cache(spec) - unless path.expand_path(Bundler.root).to_s.index(Bundler.root.to_s) == 0 - Bundler.ui.warn " * #{spec.name} at `#{path}` will not be cached." - end - end - - private - def relative_path if path.to_s.match(%r{^#{Regexp.escape Bundler.root.to_s}}) return path.relative_path_from(Bundler.root) end - path end @@ -442,7 +457,7 @@ module Bundler gem_file = Dir.chdir(gem_dir){ Gem::Builder.new(spec).build } - installer = Installer.new(spec, :env_shebang => false) + installer = Path::Installer.new(spec, :env_shebang => false) run_hooks(:pre_install, installer) installer.build_extensions run_hooks(:post_build, installer) @@ -479,20 +494,161 @@ module Bundler end class Git < Path + # The GitProxy is responsible to iteract with git repositories. + # All actions required by the Git source is encapsualted in this + # object. + class GitProxy + attr_accessor :path, :uri, :ref, :revision + + def initialize(path, uri, ref, revision=nil, &allow) + @path = path + @uri = uri + @ref = ref + @revision = revision + @allow = allow || Proc.new { true } + end + + def revision + @revision ||= allowed_in_path { git("rev-parse #{ref}").strip } + end + + def branch + @branch ||= allowed_in_path do + git("branch") =~ /^\* (.*)$/ && $1.strip + end + end + + def contains?(commit) + allowed_in_path do + result = git_null("branch --contains #{commit}") + $? == 0 && result =~ /^\* (.*)$/ + end + end + + def checkout + if path.exist? + return if has_revision_cached? + Bundler.ui.info "Updating #{uri}" + in_path do + git %|fetch --force --quiet --tags #{uri_escaped} "refs/heads/*:refs/heads/*"| + end + else + Bundler.ui.info "Fetching #{uri}" + FileUtils.mkdir_p(path.dirname) + git %|clone #{uri_escaped} "#{path}" --bare --no-hardlinks| + end + end + + def copy_to(destination, submodules=false) + unless File.exist?(destination.join(".git")) + FileUtils.mkdir_p(destination.dirname) + FileUtils.rm_rf(destination) + git %|clone --no-checkout "#{path}" "#{destination}"| + File.chmod((0777 & ~File.umask), destination) + end + + Dir.chdir(destination) do + git %|fetch --force --quiet --tags "#{path}"| + git "reset --hard #{@revision}" + + if submodules + git "submodule update --init --recursive" + end + end + end + + private + + # TODO: Do not rely on /dev/null. + # Given that open3 is not cross platform until Ruby 1.9.3, + # the best solution is to pipe to /dev/null if it exists. + # If it doesn't, everything will work fine, but the user + # will get the $stderr messages as well. + def git_null(command) + if !Bundler::WINDOWS && File.exist?("/dev/null") + git("#{command} 2>/dev/null", false) + else + git(command, false) + end + end + + def git(command, check_errors=true) + if allow? + out = %x{git #{command}} + + if check_errors && $?.exitstatus != 0 + msg = "Git error: command `git #{command}` in directory #{Dir.pwd} has failed." + msg << "\nIf this error persists you could try removing the cache directory '#{path}'" if path.exist? + raise GitError, msg + end + out + else + raise GitError, "Bundler is trying to run a `git #{command}` at runtime. You probably need to run `bundle install`. However, " \ + "this error message could probably be more useful. Please submit a ticket at http://github.com/carlhuda/bundler/issues " \ + "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}" + end + end + + def has_revision_cached? + return unless @revision + in_path { git("cat-file -e #{@revision}") } + true + rescue GitError + false + end + + # Escape the URI for git commands + def uri_escaped + if Bundler::WINDOWS + # Windows quoting requires double quotes only, with double quotes + # inside the string escaped by being doubled. + '"' + uri.gsub('"') {|s| '""'} + '"' + else + # Bash requires single quoted strings, with the single quotes escaped + # by ending the string, escaping the quote, and restarting the string. + "'" + uri.gsub("'") {|s| "'\\''"} + "'" + end + end + + def allow? + @allow.call + end + + def in_path(&blk) + checkout unless path.exist? + Dir.chdir(path, &blk) + end + + def allowed_in_path + if allow? + in_path { yield } + else + raise GitError, "The git source #{uri} is not yet checked out. Please run `bundle install` before trying to start your application" + end + end + end + attr_reader :uri, :ref, :options, :submodules def initialize(options) - super + @options = options + @glob = options["glob"] || DEFAULT_GLOB + + @allow_cached = false + @allow_remote = false - # stringify options that could be set as symbols + # Stringify options that could be set as symbols %w(ref branch tag revision).each{|k| options[k] = options[k].to_s if options[k] } @uri = options["uri"] @ref = options["ref"] || options["branch"] || options["tag"] || 'master' - @revision = options["revision"] @submodules = options["submodules"] + @name = options["name"] + @version = options["version"] + @update = false @installed = nil + @local = false end def self.from_lock(options) @@ -522,15 +678,21 @@ module Bundler alias == eql? def to_s - sref = options["ref"] ? shortref_for_display(options["ref"]) : ref - "#{uri} (at #{sref})" + at = if local? + path + elsif options["ref"] + shortref_for_display(options["ref"]) + else + ref + end + "#{uri} (at #{at})" end def name File.basename(@uri, '.git') end - def path + def install_path @install_path ||= begin git_scope = "#{base_name}-#{shortref_for_path(revision)}" @@ -542,32 +704,86 @@ module Bundler end end + alias :path :install_path + def unlock! - @revision = nil + git_proxy.revision = nil + end + + def local_override!(path) + return false if local? + + path = Pathname.new(path) + path = path.expand_path(Bundler.root) unless path.relative? + + unless options["branch"] + raise GitError, "Cannot use local override for #{name} at #{path} because " \ + ":branch is not specified in Gemfile. Specify a branch or check " \ + "`bundle config --delete` to remove the local override" + end + + unless path.exist? + raise GitError, "Cannot use local override for #{name} because #{path} " \ + "does not exist. Check `bundle config --delete` to remove the local override" + end + + set_local!(path) + + # Create a new git proxy without the cached revision + # so the Gemfile.lock always picks up the new revision. + @git_proxy = GitProxy.new(path, uri, ref) + + if git_proxy.branch != options["branch"] + raise GitError, "Local override for #{name} at #{path} is using branch " \ + "#{git_proxy.branch} but Gemfile specifies #{options["branch"]}" + end + + changed = cached_revision && cached_revision != git_proxy.revision + + if changed && !git_proxy.contains?(cached_revision) + raise GitError, "The Gemfile lock is pointing to revision #{shortref_for_display(cached_revision)} " \ + "but the current branch in your local override for #{name} does not contain such commit. " \ + "Please make sure your branch is up to date." + end + + changed end # TODO: actually cache git specs def specs(*) - if allow_git_ops? && !@update - # Start by making sure the git cache is up to date - cache - checkout + if has_app_cache? && !local? + set_local!(app_cache_path) + end + + if requires_checkout? && !@update + git_proxy.checkout + git_proxy.copy_to(install_path, submodules) @update = true end + local_specs end def install(spec) Bundler.ui.info "Using #{spec.name} (#{spec.version}) from #{to_s} " - - unless @installed + if requires_checkout? && !@installed Bundler.ui.debug " * Checking out revision: #{ref}" - checkout if allow_git_ops? + git_proxy.copy_to(install_path, submodules) @installed = true end generate_bin(spec) end + def cache(spec) + return unless Bundler.settings[:cache_all] + return if path.expand_path(Bundler.root).to_s.index(Bundler.root.to_s) == 0 + cached! + FileUtils.rm_rf(app_cache_path) + git_proxy.checkout if requires_checkout? + git_proxy.copy_to(app_cache_path, @submodules) + FileUtils.rm_rf(app_cache_path.join(".git")) + end + def load_spec_files super rescue PathError, GitError @@ -585,23 +801,29 @@ module Bundler end end end + private - def git(command) - if allow_git_ops? - out = %x{git #{command}} + def set_local!(path) + @local = true + @local_specs = @git_proxy = nil + @cache_path = @install_path = path + end + + def has_app_cache? + cached_revision && super + end - if $?.exitstatus != 0 - msg = "Git error: command `git #{command}` in directory #{Dir.pwd} has failed." - msg << "\nIf this error persists you could try removing the cache directory '#{cache_path}'" if cached? - raise GitError, msg - end - out - else - raise GitError, "Bundler is trying to run a `git #{command}` at runtime. You probably need to run `bundle install`. However, " \ - "this error message could probably be more useful. Please submit a ticket at http://github.com/carlhuda/bundler/issues " \ - "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}" - end + def app_cache_path + @app_cache_path ||= Bundler.app_cache.join("#{base_name}-#{shortref_for_path(cached_revision || revision)}") + end + + def local? + @local + end + + def requires_checkout? + allow_git_ops? && !local? end def base_name @@ -628,82 +850,25 @@ module Bundler Digest::SHA1.hexdigest(input) end - # Escape the URI for git commands - def uri_escaped - if Bundler::WINDOWS - # Windows quoting requires double quotes only, with double quotes - # inside the string escaped by being doubled. - '"' + uri.gsub('"') {|s| '""'} + '"' - else - # Bash requires single quoted strings, with the single quotes escaped - # by ending the string, escaping the quote, and restarting the string. - "'" + uri.gsub("'") {|s| "'\\''"} + "'" - end - end - - def cache - if cached? - return if has_revision_cached? - Bundler.ui.info "Updating #{uri}" - in_cache do - git %|fetch --force --quiet --tags #{uri_escaped} "refs/heads/*:refs/heads/*"| - end - else - Bundler.ui.info "Fetching #{uri}" - FileUtils.mkdir_p(cache_path.dirname) - git %|clone #{uri_escaped} "#{cache_path}" --bare --no-hardlinks| - end - end - - def checkout - unless File.exist?(path.join(".git")) - FileUtils.mkdir_p(path.dirname) - FileUtils.rm_rf(path) - git %|clone --no-checkout "#{cache_path}" "#{path}"| - File.chmod((0777 & ~File.umask), path) - end - Dir.chdir(path) do - git %|fetch --force --quiet --tags "#{cache_path}"| - git "reset --hard #{revision}" - - if @submodules - git "submodule init" - git "submodule update" - end - end - end - - def has_revision_cached? - return unless @revision - in_cache { git %|cat-file -e #{@revision}| } - true - rescue GitError - false - end - def allow_git_ops? @allow_remote || @allow_cached end + def cached_revision + options["revision"] + end + def revision - @revision ||= begin - if allow_git_ops? - in_cache { git("rev-parse #{ref}").strip } - else - raise GitError, "The git source #{uri} is not yet checked out. Please run `bundle install` before trying to start your application" - end - end + git_proxy.revision end def cached? cache_path.exist? end - def in_cache(&blk) - cache unless cached? - Dir.chdir(cache_path, &blk) + def git_proxy + @git_proxy ||= GitProxy.new(cache_path, uri, ref, cached_revision){ allow_git_ops? } end end - end end diff --git a/lib/bundler/templates/newgem/README.md.tt b/lib/bundler/templates/newgem/README.md.tt index 23a4b10beb..98692c5506 100644 --- a/lib/bundler/templates/newgem/README.md.tt +++ b/lib/bundler/templates/newgem/README.md.tt @@ -24,6 +24,6 @@ TODO: Write usage instructions here 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Added some feature'`) +3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request diff --git a/lib/bundler/vendor/thor/parser/options.rb b/lib/bundler/vendor/thor/parser/options.rb index 9b1d042d10..4a241a4777 100644 --- a/lib/bundler/vendor/thor/parser/options.rb +++ b/lib/bundler/vendor/thor/parser/options.rb @@ -1,7 +1,4 @@ class Thor - # This is a modified version of Daniel Berger's Getopt::Long class, licensed - # under Ruby's license. - # class Options < Arguments #:nodoc: LONG_RE = /^(--\w+(?:-\w+)*)$/ SHORT_RE = /^(-[a-z])$/i diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 2fd259aa09..4c7eb90185 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -2,5 +2,5 @@ module Bundler # We're doing this because we might write tests that deal # with other versions of bundler and we are unsure how to # handle this better. - VERSION = "1.1.4" unless defined?(::Bundler::VERSION) + VERSION = "1.2.0.pre.1" unless defined?(::Bundler::VERSION) end diff --git a/man/bundle-config.ronn b/man/bundle-config.ronn index cff3591e0d..46c9ac6ea7 100644 --- a/man/bundle-config.ronn +++ b/man/bundle-config.ronn @@ -23,6 +23,14 @@ Executing `bundle config <name> <value>` will set that configuration to the value specified for all bundles executed as the current user. The configuration will be stored in `~/.bundle/config`. +Executing `bundle config --global <name> <value>` works the same as above. + +Executing `bundle config --local <name> <value>` will set that configuration to +the local application. The configuration will be stored in `app/.bundle/config`. + +Executing `bundle config --delete <name>` will delete the configuration in both +local and global sources. + ## BUILD OPTIONS You can use `bundle config` to give bundler the flags to pass to the gem @@ -88,3 +96,35 @@ You can set them globally either via environment variables or `bundle config`, whichever is preferable for your setup. If you use both, environment variables will take preference over global settings. +## LOCAL GIT REPOS + +Bundler also allows you to work against a git repository locally +instead of using the remote version. This can be achieved by setting +up a local override: + + bundle config local.GEM_NAME /path/to/local/git/repository + +For example, in order to use a local Rack repository, a developer could call: + + bundle config local.rack ~/Work/git/rack + +Now instead of checking out the remote git repository, the local +override will be used. Similar to a path source, every time the local +git repository change, changes will be automatically picked up by +Bundler. This means a commit in the local git repo will update the +revision in the `Gemfile.lock` to the local git repo revision. This +requires the same attention as git submodules. Before pushing to +the remote, you need to ensure the local override was pushed, otherwise +you may point to a commit that only exists in your local machine. + +Bundler does many checks to ensure a developer won't work with +invalid references. Particularly, we force a developer to specify +a branch in the `Gemfile` in order to use this feature. If the branch +specified in the `Gemfile` and the current branch in the local git +repository do not match, Bundler will abort. This ensures that +a developer is always working against the correct branches, avoiding +a reference to be accidentally updated. + +Finally, Bundler also ensures that the current revision in the +`Gemfile.lock` exists in the local git repository. By doing this, Bundler +forces you to fetch the latest changes in the remotes.
\ No newline at end of file diff --git a/man/bundle-install.ronn b/man/bundle-install.ronn index cbddd09d4a..9d169af5dd 100644 --- a/man/bundle-install.ronn +++ b/man/bundle-install.ronn @@ -10,6 +10,7 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile [--binstubs[=DIRECTORY]] [--standalone[=GROUP1[ GROUP2...]]] [--quiet] + [--no-cache] ## DESCRIPTION @@ -81,6 +82,11 @@ update process below under [CONSERVATIVE UPDATING][]. `bundle` directory and installs the bundle there. It also generates a `bundle/bundler/setup.rb` file to replace Bundler's own setup. +* `--no-cache`: + Do not update the cache in `vendor/cache` with the newly bundled gems. This + does not remove any existing cached gems, only stops the newly bundled gems + from being cached during the install. + ## DEPLOYMENT MODE Bundler's defaults are optimized for development. To switch to diff --git a/man/bundle-package.ronn b/man/bundle-package.ronn index d8a709bfc9..834959bfd0 100644 --- a/man/bundle-package.ronn +++ b/man/bundle-package.ronn @@ -13,9 +13,9 @@ use the gems in the cache in preference to the ones on `rubygems.org`. ## GIT AND PATH GEMS -In Bundler 1.0, the `bundle package` command only packages `.gem` files, -not gems specified using the `:git` or `:path` options. This will likely -change in the future. +Since Bundler 1.2, the `bundle package` command can also package `:git` and +`:path` dependencies besides .gem files. This needs to be explicitly enabled +via the `--all` option. Once used, the `--all` option will be remembered. ## REMOTE FETCHING diff --git a/man/gemfile.5.ronn b/man/gemfile.5.ronn index 1899f173a7..53e6140247 100644 --- a/man/gemfile.5.ronn +++ b/man/gemfile.5.ronn @@ -25,6 +25,33 @@ might contain the gems listed in the `Gemfile`. Each of these _source_s `MUST` be a valid Rubygems repository. +## RUBY (#ruby) + +If your application requires a specific Ruby version or engine, specify your +requirements using the `ruby` method, with the following arguments. +All parameters are `OPTIONAL` unless otherwise specified. + +### VERSION (required) + +The version of Ruby that your application requires. If your application +requires an alternate Ruby engine, such as JRuby or Rubinius, this should be +the Ruby version that the engine is compatible with. + + ruby "1.9.3" + +### ENGINE (:engine) + +Each application _may_ specify a Ruby engine. If an engine is specified, an +engine version _must_ also be specified. + +### ENGINE VERSION (:engine_version) + +Each application _may_ specify a Ruby engine version. If an engine version is +specified, an engine _must_ also be specified. If the engine is "ruby" the +engine version specified _must_ match the Ruby version. + + ruby "1.8.7", :engine => "jruby", :engine_version => "1.6.7" + ## GEMS (#gem) Specify gem requirements using the `gem` method, with the following arguments. @@ -49,7 +76,6 @@ Each _gem_ `MAY` specify files that should be used when autorequiring via `Bundler.require`. You may pass an array with multiple files, or `false` to prevent any file from being autorequired. - gem "sqlite3-ruby", :require => "sqlite3" gem "redis", :require => ["redis/connection/hiredis", "redis"] gem "webmock", :require => false @@ -203,7 +229,7 @@ the `.gemspec`. | |-actionpack.gemspec [actionpack gem located here] |~activesupport | |-activesupport.gemspec [activesupport gem located here] - ... + |... To install a gem located in a git repository, bundler changes to the directory containing the gemspec, runs `gem build name.gemspec` @@ -211,6 +237,20 @@ and then installs the resulting gem. The `gem build` command, which comes standard with Rubygems, evaluates the `.gemspec` in the context of the directory in which it is located. +### GITHUB (:github) + +If the git repository you want to use is hosted on GitHub and is public, you can use the +:github shorthand to specify just the github username and repository name (without the +trailing ".git"), separated by a slash. If both the username and repository name are the +same, you can omit one. + + gem "rails", :github => "rails/rails" + gem "rails", :github => "rails" + +Are both equivalent to + + gem "rails", :git => "git://github.com/rails/rails.git" + ### PATH (:path) You can specify that a gem is located in a particular location @@ -239,7 +279,7 @@ applied to a group of gems by using block form. platforms :ruby do gem "ruby-debug" - gem "sqlite3-ruby" + gem "sqlite3" end group :development do diff --git a/spec/bundler/dsl_spec.rb b/spec/bundler/dsl_spec.rb index 4f50c3c6bb..2bc0e40696 100644 --- a/spec/bundler/dsl_spec.rb +++ b/spec/bundler/dsl_spec.rb @@ -1,12 +1,12 @@ require 'spec_helper' describe Bundler::Dsl do - describe '#_normalize_options' do - before do - @rubygems = mock("rubygems") - Bundler::Source::Rubygems.stub(:new){ @rubygems } - end + before do + @rubygems = mock("rubygems") + Bundler::Source::Rubygems.stub(:new){ @rubygems } + end + describe '#_normalize_options' do it "should convert :github to :git" do subject.gem("sparks", :github => "indirect/sparks") github_uri = "git://github.com/indirect/sparks.git" @@ -20,6 +20,24 @@ describe Bundler::Dsl do end end + describe '#method_missing' do + it 'should raise an error for unknown DSL methods' do + Bundler.should_receive(:read_file).with("Gemfile").and_return("unknown") + error_msg = "Undefined local variable or method `unknown'" \ + " for Gemfile\\s+from Gemfile:1" + lambda{ subject.eval_gemfile("Gemfile") }. + should raise_error(Bundler::GemfileError, Regexp.new(error_msg)) + end + end + + describe "#eval_gemfile" do + it "handles syntax errors with a useful message" do + Bundler.should_receive(:read_file).with("Gemfile").and_return("}") + lambda{ subject.eval_gemfile("Gemfile") }. + should raise_error(Bundler::GemfileError, /Gemfile syntax error/) + end + end + describe "syntax errors" do it "should raise a Bundler::GemfileError" do gemfile "gem 'foo', :path => /unquoted/string/syntax/error" diff --git a/spec/bundler/gem_helper_spec.rb b/spec/bundler/gem_helper_spec.rb index ad2bb32792..426760401e 100644 --- a/spec/bundler/gem_helper_spec.rb +++ b/spec/bundler/gem_helper_spec.rb @@ -1,4 +1,5 @@ require "spec_helper" +require 'rake' require 'bundler/gem_helper' describe "Bundler::GemHelper tasks" do @@ -61,6 +62,36 @@ describe "Bundler::GemHelper tasks" do Bundler.ui.should be_a(Bundler::UI::Shell) end + describe 'install_tasks' do + before(:each) do + @saved, Rake.application = Rake.application, Rake::Application.new + end + + after(:each) do + Rake.application = @saved + end + + it "defines Rake tasks" do + names = %w[build install release] + + names.each { |name| + proc { Rake.application[name] }.should raise_error(/Don't know how to build task/) + } + + @helper.install + + names.each { |name| + proc { Rake.application[name] }.should_not raise_error + Rake.application[name].should be_instance_of Rake::Task + } + end + + it "provides a way to access the gemspec object" do + @helper.install + Bundler::GemHelper.gemspec.name.should == 'test' + end + end + describe 'build' do it "builds" do mock_build_message diff --git a/spec/cache/gems_spec.rb b/spec/cache/gems_spec.rb index aef513b5f2..5270cd2411 100644 --- a/spec/cache/gems_spec.rb +++ b/spec/cache/gems_spec.rb @@ -123,7 +123,7 @@ describe "bundle cache" do it "re-caches during install" do cached_gem("rack-1.0.0").rmtree bundle :install - out.should include("Updating .gem files in vendor/cache") + out.should include("Updating files in vendor/cache") cached_gem("rack-1.0.0").should exist end @@ -215,6 +215,15 @@ describe "bundle cache" do out.should_not =~ /removing/i end + it "does not warn about all if it doesn't have any git/path dependency" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + bundle "cache" + out.should_not =~ /\-\-all/ + end + it "should install gems with the name bundler in them (that aren't bundler)" do build_gem "foo-bundler", "1.0", :path => bundled_app('vendor/cache') diff --git a/spec/cache/git_spec.rb b/spec/cache/git_spec.rb index 42c3ad57b9..207af1ce26 100644 --- a/spec/cache/git_spec.rb +++ b/spec/cache/git_spec.rb @@ -1,5 +1,6 @@ require "spec_helper" -describe "bundle cache with git" do + +describe "git base name" do it "base_name should strip private repo uris" do source = Bundler::Source::Git.new("uri" => "git@github.com:bundler.git") source.send(:base_name).should == "bundler" @@ -9,4 +10,115 @@ describe "bundle cache with git" do source = Bundler::Source::Git.new("uri" => "//MachineName/ShareFolder") source.send(:base_name).should == "ShareFolder" end - end +end + +%w(cache package).each do |cmd| + describe "bundle #{cmd} with git" do + it "copies repository to vendor cache and uses it" do + git = build_git "foo" + ref = git.ref_for("master", 11) + + install_gemfile <<-G + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + + bundle "#{cmd} --all" + bundled_app("vendor/cache/foo-1.0-#{ref}").should exist + bundled_app("vendor/cache/foo-1.0-#{ref}/.git").should_not exist + + FileUtils.rm_rf lib_path("foo-1.0") + should_be_installed "foo 1.0" + end + + it "runs twice without exploding" do + build_git "foo" + + install_gemfile <<-G + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + + bundle "#{cmd} --all" + bundle "#{cmd} --all" + + err.should == "" + FileUtils.rm_rf lib_path("foo-1.0") + should_be_installed "foo 1.0" + end + + it "tracks updates" do + git = build_git "foo" + old_ref = git.ref_for("master", 11) + + install_gemfile <<-G + gem "foo", :git => '#{lib_path("foo-1.0")}' + G + + bundle "#{cmd} --all" + + update_git "foo" do |s| + s.write "lib/foo.rb", "puts :CACHE" + end + + ref = git.ref_for("master", 11) + ref.should_not == old_ref + + bundle "update" + bundle "#{cmd} --all" + + bundled_app("vendor/cache/foo-1.0-#{ref}").should exist + + FileUtils.rm_rf lib_path("foo-1.0") + run "require 'foo'" + out.should == "CACHE" + end + + it "uses the local repository to generate the cache" do + git = build_git "foo" + ref = git.ref_for("master", 11) + + gemfile <<-G + gem "foo", :git => '#{lib_path("foo-invalid")}', :branch => :master + G + + bundle %|config local.foo #{lib_path('foo-1.0')}| + bundle "install" + bundle "#{cmd} --all" + + bundled_app("vendor/cache/foo-invalid-#{ref}").should exist + + # Updating the local still uses the local. + update_git "foo" do |s| + s.write "lib/foo.rb", "puts :LOCAL" + end + + run "require 'foo'" + out.should == "LOCAL" + end + + it "copies repository to vendor cache, including submodules" do + build_git "submodule", "1.0" + + git = build_git "has_submodule", "1.0" do |s| + s.add_dependency "submodule" + end + + Dir.chdir(lib_path('has_submodule-1.0')) do + `git submodule add #{lib_path('submodule-1.0')} submodule-1.0` + `git commit -m "submodulator"` + end + + install_gemfile <<-G + git "#{lib_path('has_submodule-1.0')}", :submodules => true do + gem "has_submodule" + end + G + + ref = git.ref_for("master", 11) + bundle "#{cmd} --all" + + bundled_app("vendor/cache/has_submodule-1.0-#{ref}").should exist + bundled_app("vendor/cache/has_submodule-1.0-#{ref}/submodule-1.0").should exist + should_be_installed "has_submodule 1.0" + end + end +end
\ No newline at end of file diff --git a/spec/cache/path_spec.rb b/spec/cache/path_spec.rb index 6a78ac3b5e..a50e5d9331 100644 --- a/spec/cache/path_spec.rb +++ b/spec/cache/path_spec.rb @@ -1,27 +1,103 @@ require "spec_helper" -describe "bundle cache" do - describe "with path sources" do - it "is silent when the path is within the bundle" do +%w(cache package).each do |cmd| + describe "bundle #{cmd} with path" do + it "is no-op when the path is within the bundle" do build_lib "foo", :path => bundled_app("lib/foo") install_gemfile <<-G gem "foo", :path => '#{bundled_app("lib/foo")}' G - bundle "cache" - out.should == "Updating .gem files in vendor/cache" + bundle "#{cmd} --all" + bundled_app("vendor/cache/foo-1.0").should_not exist + should_be_installed "foo 1.0" end - it "warns when the path is outside of the bundle" do + it "copies when the path is outside the bundle " do build_lib "foo" install_gemfile <<-G gem "foo", :path => '#{lib_path("foo-1.0")}' G - bundle "cache" - out.should include("foo at `#{lib_path("foo-1.0")}` will not be cached") + bundle "#{cmd} --all" + bundled_app("vendor/cache/foo-1.0").should exist + + FileUtils.rm_rf lib_path("foo-1.0") + should_be_installed "foo 1.0" + end + + it "updates the path on each cache" do + build_lib "foo" + + install_gemfile <<-G + gem "foo", :path => '#{lib_path("foo-1.0")}' + G + + bundle "#{cmd} --all" + + build_lib "foo" do |s| + s.write "lib/foo.rb", "puts :CACHE" + end + + bundle "#{cmd} --all" + + bundled_app("vendor/cache/foo-1.0").should exist + FileUtils.rm_rf lib_path("foo-1.0") + + run "require 'foo'" + out.should == "CACHE" + end + + it "raises a warning without --all" do + build_lib "foo" + + install_gemfile <<-G + gem "foo", :path => '#{lib_path("foo-1.0")}' + G + + bundle cmd + out.should =~ /please pass the \-\-all flag/ + bundled_app("vendor/cache/foo-1.0").should_not exist + end + + it "stores the given flag" do + build_lib "foo" + + install_gemfile <<-G + gem "foo", :path => '#{lib_path("foo-1.0")}' + G + + bundle "#{cmd} --all" + build_lib "bar" + + install_gemfile <<-G + gem "foo", :path => '#{lib_path("foo-1.0")}' + gem "bar", :path => '#{lib_path("bar-1.0")}' + G + + bundle cmd + bundled_app("vendor/cache/bar-1.0").should exist + end + + it "can rewind chosen configuration" do + build_lib "foo" + + install_gemfile <<-G + gem "foo", :path => '#{lib_path("foo-1.0")}' + G + + bundle "#{cmd} --all" + build_lib "baz" + + gemfile <<-G + gem "foo", :path => '#{lib_path("foo-1.0")}' + gem "baz", :path => '#{lib_path("baz-1.0")}' + G + + bundle "#{cmd} --no-all" + bundled_app("vendor/cache/baz-1.0").should_not exist end end -end +end
\ No newline at end of file diff --git a/spec/install/deprecated_spec.rb b/spec/install/deprecated_spec.rb deleted file mode 100644 index fd586ec354..0000000000 --- a/spec/install/deprecated_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require "spec_helper" - -describe "bundle install with deprecated features" do - before :each do - in_app_root - end - - %w().each do |deprecated| - - it "reports that #{deprecated} is deprecated" do - gemfile <<-G - #{deprecated} - G - - bundle :install - out.should =~ /'#{deprecated}' has been removed/ - out.should =~ /See the README for more information/ - end - - end - - - %w().each do |deprecated| - - it "reports that :#{deprecated} is deprecated" do - gemfile <<-G - gem "rack", :#{deprecated} => true - G - - bundle :install - out.should =~ /Please replace :#{deprecated}|The :#{deprecated} option is no longer supported/ - end - - end - -end diff --git a/spec/install/gems/dependency_api_spec.rb b/spec/install/gems/dependency_api_spec.rb index 8e6fe9cb67..1a94917f45 100644 --- a/spec/install/gems/dependency_api_spec.rb +++ b/spec/install/gems/dependency_api_spec.rb @@ -158,15 +158,28 @@ describe "gemcutter's dependency API" do out.should match(/Too many redirects/) end - it "uses the modern index when --full-index is passed" do - gemfile <<-G - source "#{source_uri}" - gem "rack" - G + context "when --full-index is specified" do + it "should use the modern index for install" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G - bundle "install --full-index", :artifice => "endpoint" - out.should include("Fetching source index from #{source_uri}") - should_be_installed "rack 1.0.0" + bundle "install --full-index", :artifice => "endpoint" + out.should include("Fetching source index from #{source_uri}") + should_be_installed "rack 1.0.0" + end + + it "should use the modern index for update" do + gemfile <<-G + source "#{source_uri}" + gem "rack" + G + + bundle "update --full-index", :artifice => "endpoint" + out.should include("Fetching source index from #{source_uri}") + should_be_installed "rack 1.0.0" + end end it "fetches again when more dependencies are found in subsequent sources" do diff --git a/spec/install/git_spec.rb b/spec/install/git_spec.rb index 41a23b42f2..048880bab5 100644 --- a/spec/install/git_spec.rb +++ b/spec/install/git_spec.rb @@ -156,6 +156,155 @@ describe "bundle install with git sources" do end end + describe "when specifying local" do + it "uses the local repository instead of checking a new one out" do + # We don't generate it because we actually don't need it + # build_git "rack", "0.8" + + build_git "rack", "0.8", :path => lib_path('local-rack') do |s| + s.write "lib/rack.rb", "puts :LOCAL" + end + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path('rack-0.8')}", :branch => "master" + G + + bundle %|config local.rack #{lib_path('local-rack')}| + bundle :install + out.should =~ /at #{lib_path('local-rack')}/ + + run "require 'rack'" + out.should == "LOCAL" + end + + it "chooses the local repository on runtime" do + build_git "rack", "0.8" + + FileUtils.cp_r("#{lib_path('rack-0.8')}/.", lib_path('local-rack')) + + update_git "rack", "0.8", :path => lib_path('local-rack') do |s| + s.write "lib/rack.rb", "puts :LOCAL" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path('rack-0.8')}", :branch => "master" + G + + bundle %|config local.rack #{lib_path('local-rack')}| + run "require 'rack'" + out.should == "LOCAL" + end + + it "updates specs on runtime" do + system_gems "nokogiri-1.4.2" + + build_git "rack", "0.8" + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path('rack-0.8')}", :branch => "master" + G + + lockfile0 = File.read(bundled_app("Gemfile.lock")) + + FileUtils.cp_r("#{lib_path('rack-0.8')}/.", lib_path('local-rack')) + update_git "rack", "0.8", :path => lib_path('local-rack') do |s| + s.add_dependency "nokogiri", "1.4.2" + end + + bundle %|config local.rack #{lib_path('local-rack')}| + run "require 'rack'" + + lockfile1 = File.read(bundled_app("Gemfile.lock")) + lockfile1.should_not == lockfile0 + end + + it "updates ref on install" do + build_git "rack", "0.8" + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path('rack-0.8')}", :branch => "master" + G + + lockfile0 = File.read(bundled_app("Gemfile.lock")) + + FileUtils.cp_r("#{lib_path('rack-0.8')}/.", lib_path('local-rack')) + update_git "rack", "0.8", :path => lib_path('local-rack') + + bundle %|config local.rack #{lib_path('local-rack')}| + bundle :install + + lockfile1 = File.read(bundled_app("Gemfile.lock")) + lockfile1.should_not == lockfile0 + end + + it "explodes if given path does not exist" do + build_git "rack", "0.8" + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path('rack-0.8')}", :branch => "master" + G + + bundle %|config local.rack #{lib_path('local-rack')}| + bundle :install + out.should =~ /Cannot use local override for rack-0.8 because #{Regexp.escape(lib_path('local-rack').to_s)} does not exist/ + end + + it "explodes if branch is not given" do + build_git "rack", "0.8" + FileUtils.cp_r("#{lib_path('rack-0.8')}/.", lib_path('local-rack')) + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path('rack-0.8')}" + G + + bundle %|config local.rack #{lib_path('local-rack')}| + bundle :install + out.should =~ /because :branch is not specified in Gemfile/ + end + + it "explodes on different branches" do + build_git "rack", "0.8" + + FileUtils.cp_r("#{lib_path('rack-0.8')}/.", lib_path('local-rack')) + + update_git "rack", "0.8", :path => lib_path('local-rack'), :branch => "another" do |s| + s.write "lib/rack.rb", "puts :LOCAL" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path('rack-0.8')}", :branch => "master" + G + + bundle %|config local.rack #{lib_path('local-rack')}| + bundle :install + out.should =~ /is using branch another but Gemfile specifies master/ + end + + it "explodes on invalid revision" do + build_git "rack", "0.8" + + build_git "rack", "0.8", :path => lib_path('local-rack') do |s| + s.write "lib/rack.rb", "puts :LOCAL" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :git => "#{lib_path('rack-0.8')}", :branch => "master" + G + + bundle %|config local.rack #{lib_path('local-rack')}| + bundle :install + out.should =~ /The Gemfile lock is pointing to revision \w+/ + end + end + describe "specified inline" do # TODO: Figure out how to write this test so that it is not flaky depending # on the current network situation. @@ -573,7 +722,6 @@ describe "bundle install with git sources" do install_gemfile <<-G source "file://#{gem_repo1}" - gem "valim", "= 1.0", :git => "#{lib_path('valim')}" G diff --git a/spec/other/check_spec.rb b/spec/other/check_spec.rb index 244bcce441..d6ff6d337b 100644 --- a/spec/other/check_spec.rb +++ b/spec/other/check_spec.rb @@ -201,6 +201,20 @@ describe "bundle check" do end end + it "fails when there's no lock file and frozen is set" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "foo" + G + + bundle "install" + bundle "install --deployment" + FileUtils.rm(bundled_app("Gemfile.lock")) + + bundle :check, :exitstatus => true + exitstatus.should_not eq(0) + end + context "--path" do before do gemfile <<-G diff --git a/spec/other/config_spec.rb b/spec/other/config_spec.rb index 9d65288f8a..e1b92bd49b 100644 --- a/spec/other/config_spec.rb +++ b/spec/other/config_spec.rb @@ -8,33 +8,131 @@ describe ".bundle/config" do G end - it "can be moved with an environment variable" do - ENV['BUNDLE_APP_CONFIG'] = tmp('foo/bar').to_s - bundle "install --path vendor/bundle" + describe "BUNDLE_APP_CONFIG" do + it "can be moved with an environment variable" do + ENV['BUNDLE_APP_CONFIG'] = tmp('foo/bar').to_s + bundle "install --path vendor/bundle" - bundled_app('.bundle').should_not exist - tmp('foo/bar/config').should exist - should_be_installed "rack 1.0.0" + bundled_app('.bundle').should_not exist + tmp('foo/bar/config').should exist + should_be_installed "rack 1.0.0" + end + + it "can provide a relative path with the environment variable" do + FileUtils.mkdir_p bundled_app('omg') + Dir.chdir bundled_app('omg') + + ENV['BUNDLE_APP_CONFIG'] = "../foo" + bundle "install --path vendor/bundle" + + bundled_app(".bundle").should_not exist + bundled_app("../foo/config").should exist + should_be_installed "rack 1.0.0" + end + + it "removes environment.rb from BUNDLE_APP_CONFIG's path" do + FileUtils.mkdir_p(tmp('foo/bar')) + ENV['BUNDLE_APP_CONFIG'] = tmp('foo/bar').to_s + bundle "install" + FileUtils.touch tmp('foo/bar/environment.rb') + should_be_installed "rack 1.0.0" + tmp('foo/bar/environment.rb').should_not exist + end end - it "can provide a relative path with the environment variable" do - FileUtils.mkdir_p bundled_app('omg') - Dir.chdir bundled_app('omg') + describe "global" do + before(:each) { bundle :install } + + it "is the default" do + bundle "config foo global" + run "puts Bundler.settings[:foo]" + out.should == "global" + end + + it "can also be set explicitly" do + bundle "config --global foo global" + run "puts Bundler.settings[:foo]" + out.should == "global" + end + + it "has lower precedence than local" do + bundle "config --local foo local" + + bundle "config --global foo global" + out.should =~ /Your application has set foo to "local"/ + + run "puts Bundler.settings[:foo]" + out.should == "local" + end + + it "has lower precedence than env" do + begin + ENV["BUNDLE_FOO"] = "env" + + bundle "config --global foo global" + out.should =~ /You have a bundler environment variable for foo set to "env"/ - ENV['BUNDLE_APP_CONFIG'] = "../foo" - bundle "install --path vendor/bundle" + run "puts Bundler.settings[:foo]" + out.should == "env" + ensure + ENV.delete("BUNDLE_FOO") + end + end - bundled_app(".bundle").should_not exist - bundled_app("../foo/config").should exist - should_be_installed "rack 1.0.0" + it "can be deleted" do + bundle "config --global foo global" + bundle "config --delete foo" + + run "puts Bundler.settings[:foo] == nil" + out.should == "true" + end + + it "warns when overriding" do + bundle "config --global foo previous" + bundle "config --global foo global" + out.should =~ /You are replacing the current global value of foo/ + + run "puts Bundler.settings[:foo]" + out.should == "global" + end end - it "removes environment.rb from BUNDLE_APP_CONFIG's path" do - FileUtils.mkdir_p(tmp('foo/bar')) - ENV['BUNDLE_APP_CONFIG'] = tmp('foo/bar').to_s - bundle "install" - FileUtils.touch tmp('foo/bar/environment.rb') - should_be_installed "rack 1.0.0" - tmp('foo/bar/environment.rb').should_not exist + describe "local" do + before(:each) { bundle :install } + + it "can also be set explicitly" do + bundle "config --local foo local" + run "puts Bundler.settings[:foo]" + out.should == "local" + end + + it "has higher precedence than env" do + begin + ENV["BUNDLE_FOO"] = "env" + bundle "config --local foo local" + + run "puts Bundler.settings[:foo]" + out.should == "local" + ensure + ENV.delete("BUNDLE_FOO") + end + end + + it "can be deleted" do + bundle "config --local foo local" + bundle "config --delete foo" + + run "puts Bundler.settings[:foo] == nil" + out.should == "true" + end + + it "warns when overriding" do + bundle "config --local foo previous" + bundle "config --local foo local" + out.should =~ /You are replacing the current local value of foo/ + + run "puts Bundler.settings[:foo]" + out.should == "local" + end end -end +end
\ No newline at end of file diff --git a/spec/other/newgem_spec.rb b/spec/other/newgem_spec.rb index df21035b0c..7aca310b3b 100644 --- a/spec/other/newgem_spec.rb +++ b/spec/other/newgem_spec.rb @@ -7,6 +7,8 @@ describe "bundle gem" do @git_email = `git config --global user.email`.chomp `git config --global user.email user@example.com` bundle 'gem test-gem' + # reset gemspec cache for each test because of commit 3d4163a + Bundler.clear_gemspec_cache end after :each do diff --git a/spec/other/platform_spec.rb b/spec/other/platform_spec.rb new file mode 100644 index 0000000000..06e97cd078 --- /dev/null +++ b/spec/other/platform_spec.rb @@ -0,0 +1,881 @@ +require "spec_helper" + +describe "bundle platform" do + context "without flags" do + it "returns all the output" do + gemfile <<-G + source "file://#{gem_repo1}" + + #{ruby_version_correct} + + gem "foo" + G + + bundle "platform" + out.should == <<-G.chomp +Your platform is: #{RUBY_PLATFORM} + +Your app has gems that work on these platforms: +* ruby + +Your Gemfile specifies a Ruby version requirement: +* ruby #{RUBY_VERSION} + +Your current platform satisfies the Ruby version requirement. +G + end + + it "doesn't print ruby version requirement if it isn't specified" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "foo" + G + + bundle "platform" + out.should == <<-G.chomp +Your platform is: #{RUBY_PLATFORM} + +Your app has gems that work on these platforms: +* ruby + +Your Gemfile does not specify a Ruby version requirement. +G + end + + it "doesn't match the ruby version requirement" do + gemfile <<-G + source "file://#{gem_repo1}" + + #{ruby_version_incorrect} + + gem "foo" + G + + bundle "platform" + out.should == <<-G.chomp +Your platform is: #{RUBY_PLATFORM} + +Your app has gems that work on these platforms: +* ruby + +Your Gemfile specifies a Ruby version requirement: +* ruby #{not_local_ruby_version} + +Your Ruby version is #{RUBY_VERSION}, but your Gemfile specified #{not_local_ruby_version} +G + end + end + + context "--ruby" do + it "returns ruby version when explicit" do + gemfile <<-G + source "file://#{gem_repo1}" + ruby "1.9.3", :engine => 'ruby', :engine_version => '1.9.3' + + gem "foo" + G + + bundle "platform --ruby" + + out.should eq("ruby 1.9.3") + end + + it "engine defaults to MRI" do + gemfile <<-G + source "file://#{gem_repo1}" + ruby "1.9.3" + + gem "foo" + G + + bundle "platform --ruby" + + out.should eq("ruby 1.9.3") + end + + it "handles jruby" do + gemfile <<-G + source "file://#{gem_repo1}" + ruby "1.8.7", :engine => 'jruby', :engine_version => '1.6.5' + + gem "foo" + G + + bundle "platform --ruby" + + out.should eq("ruby 1.8.7 (jruby 1.6.5)") + end + + it "handles rbx" do + gemfile <<-G + source "file://#{gem_repo1}" + ruby "1.8.7", :engine => 'rbx', :engine_version => '1.2.4' + + gem "foo" + G + + bundle "platform --ruby" + + out.should eq("ruby 1.8.7 (rbx 1.2.4)") + end + + it "raises an error if engine is used but engine version is not" do + gemfile <<-G + source "file://#{gem_repo1}" + ruby "1.8.7", :engine => 'rbx' + + gem "foo" + G + + bundle "platform", :exitstatus => true + + exitstatus.should_not == 0 + end + + it "raises an error if engine_version is used but engine is not" do + gemfile <<-G + source "file://#{gem_repo1}" + ruby "1.8.7", :engine_version => '1.2.4' + + gem "foo" + G + + bundle "platform", :exitstatus => true + + exitstatus.should_not == 0 + end + + it "raises an error if engine version doesn't match ruby version for mri" do + gemfile <<-G + source "file://#{gem_repo1}" + ruby "1.8.7", :engine => 'ruby', :engine_version => '1.2.4' + + gem "foo" + G + + bundle "platform", :exitstatus => true + + exitstatus.should_not == 0 + end + end + + let(:ruby_version_correct) { "ruby \"#{RUBY_VERSION}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{local_engine_version}\"" } + let(:ruby_version_incorrect) { "ruby \"#{not_local_ruby_version}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{not_local_ruby_version}\"" } + let(:engine_incorrect) { "ruby \"#{RUBY_VERSION}\", :engine => \"#{not_local_tag}\", :engine_version => \"#{RUBY_VERSION}\"" } + let(:engine_version_incorrect) { "ruby \"#{RUBY_VERSION}\", :engine => \"#{local_ruby_engine}\", :engine_version => \"#{not_local_engine_version}\"" } + + def should_be_ruby_version_incorrect(opts = {:exitstatus => true}) + exitstatus.should eq(18) if opts[:exitstatus] + out.should be_include("Your Ruby version is #{RUBY_VERSION}, but your Gemfile specified #{not_local_ruby_version}") + end + + def should_be_engine_incorrect(opts = {:exitstatus => true}) + exitstatus.should eq(18) if opts[:exitstatus] + out.should be_include("Your Ruby engine is #{local_ruby_engine}, but your Gemfile specified #{not_local_tag}") + end + + def should_be_engine_version_incorrect(opts = {:exitstatus => true}) + exitstatus.should eq(18) if opts[:exitstatus] + out.should be_include("Your #{local_ruby_engine} version is #{local_engine_version}, but your Gemfile specified #{local_ruby_engine} #{not_local_engine_version}") + end + + context "bundle install" do + it "installs fine when the ruby version matches" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + + #{ruby_version_correct} + G + + bundled_app('Gemfile.lock').should exist + end + + it "doesn't install when the ruby version doesn't match" do + install_gemfile <<-G, :exitstatus => true + source "file://#{gem_repo1}" + gem "rack" + + #{ruby_version_incorrect} + G + + bundled_app('Gemfile.lock').should_not exist + should_be_ruby_version_incorrect + end + + it "doesn't install when engine doesn't match" do + install_gemfile <<-G, :exitstatus => true + source "file://#{gem_repo1}" + gem "rack" + + #{engine_incorrect} + G + + bundled_app('Gemfile.lock').should_not exist + should_be_engine_incorrect + end + + it "doesn't install when engine version doesn't match" do + simulate_ruby_engine "jruby" do + install_gemfile <<-G, :exitstatus => true + source "file://#{gem_repo1}" + gem "rack" + + #{engine_version_incorrect} + G + + bundled_app('Gemfile.lock').should_not exist + should_be_engine_version_incorrect + end + end + end + + context "bundle check" do + it "checks fine when the ruby version matches" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + + #{ruby_version_correct} + G + + bundle :check, :exitstatus => true + exitstatus.should eq(0) + out.should == "The Gemfile's dependencies are satisfied" + end + + it "fails when ruby version doesn't match" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + + #{ruby_version_incorrect} + G + + bundle :check, :exitstatus => true + should_be_ruby_version_incorrect + end + + it "fails when engine doesn't match" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + + #{engine_incorrect} + G + + bundle :check, :exitstatus => true + should_be_engine_incorrect + end + + it "fails when engine version doesn't match" do + simulate_ruby_engine "ruby" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + + #{engine_version_incorrect} + G + + bundle :check, :exitstatus => true + should_be_engine_version_incorrect + end + end + end + + context "bundle update" do + before do + build_repo2 + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + gem "rack-obama" + G + end + + it "updates successfully when the ruby version matches" do + gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + gem "rack-obama" + + #{ruby_version_correct} + G + update_repo2 do + build_gem "activesupport", "3.0" + end + + bundle "update" + should_be_installed "rack 1.2", "rack-obama 1.0", "activesupport 3.0" + end + + it "fails when ruby version doesn't match" do + gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + gem "rack-obama" + + #{ruby_version_incorrect} + G + update_repo2 do + build_gem "activesupport", "3.0" + end + + bundle :update, :exitstatus => true + should_be_ruby_version_incorrect + end + + it "fails when ruby engine doesn't match" do + gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + gem "rack-obama" + + #{engine_incorrect} + G + update_repo2 do + build_gem "activesupport", "3.0" + end + + bundle :update, :exitstatus => true + should_be_engine_incorrect + end + + it "fails when ruby engine version doesn't match" do + simulate_ruby_engine "jruby" do + gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + gem "rack-obama" + + #{engine_version_incorrect} + G + update_repo2 do + build_gem "activesupport", "3.0" + end + + bundle :update, :exitstatus => true + should_be_engine_version_incorrect + end + end + end + + context "bundle show" do + before do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + end + + it "prints path if ruby version is correct" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + + #{ruby_version_correct} + G + + bundle "show rails" + out.should == default_bundle_path('gems', 'rails-2.3.2').to_s + end + + it "fails if ruby version doesn't match" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + + #{ruby_version_incorrect} + G + + bundle "show rails", :exitstatus => true + should_be_ruby_version_incorrect + end + + it "fails if engine doesn't match" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + + #{engine_incorrect} + G + + bundle "show rails", :exitstatus => true + should_be_engine_incorrect + end + + it "fails if engine version doesn't match" do + simulate_ruby_engine "jruby" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + + #{engine_version_incorrect} + G + + bundle "show rails", :exitstatus => true + should_be_engine_version_incorrect + end + end + end + + context "bundle cache" do + before do + gemfile <<-G + gem 'rack' + G + + system_gems "rack-1.0.0" + end + + it "copies the .gem file to vendor/cache when ruby version matches" do + gemfile <<-G + gem 'rack' + + #{ruby_version_correct} + G + + bundle :cache + bundled_app("vendor/cache/rack-1.0.0.gem").should exist + end + + it "fails if the ruby version doesn't match" do + gemfile <<-G + gem 'rack' + + #{ruby_version_incorrect} + G + + bundle :cache, :exitstatus => true + should_be_ruby_version_incorrect + end + + it "fails if the engine doesn't match" do + gemfile <<-G + gem 'rack' + + #{engine_incorrect} + G + + bundle :cache, :exitstatus => true + should_be_engine_incorrect + end + + it "fails if the engine version doesn't match" do + simulate_ruby_engine "jruby" do + gemfile <<-G + gem 'rack' + + #{engine_version_incorrect} + G + + bundle :cache, :exitstatus => true + should_be_engine_version_incorrect + end + end + end + + context "bundle pack" do + before do + gemfile <<-G + gem 'rack' + G + + system_gems "rack-1.0.0" + end + + it "copies the .gem file to vendor/cache when ruby version matches" do + gemfile <<-G + gem 'rack' + + #{ruby_version_correct} + G + + bundle :pack + bundled_app("vendor/cache/rack-1.0.0.gem").should exist + end + + it "fails if the ruby version doesn't match" do + gemfile <<-G + gem 'rack' + + #{ruby_version_incorrect} + G + + bundle :pack, :exitstatus => true + should_be_ruby_version_incorrect + end + + it "fails if the engine doesn't match" do + gemfile <<-G + gem 'rack' + + #{engine_incorrect} + G + + bundle :pack, :exitstatus => true + should_be_engine_incorrect + end + + it "fails if the engine version doesn't match" do + simulate_ruby_engine "jruby" do + gemfile <<-G + gem 'rack' + + #{engine_version_incorrect} + G + + bundle :pack, :exitstatus => true + should_be_engine_version_incorrect + end + end + end + + context "bundle exec" do + before do + system_gems "rack-1.0.0", "rack-0.9.1" + end + + it "activates the correct gem when ruby version matches" do + gemfile <<-G + gem "rack", "0.9.1" + + #{ruby_version_correct} + G + + bundle "exec rackup" + out.should == "0.9.1" + end + + it "fails when the ruby version doesn't match" do + gemfile <<-G + gem "rack", "0.9.1" + + #{ruby_version_incorrect} + G + + bundle "exec rackup", :exitstatus => true + should_be_ruby_version_incorrect + end + + it "fails when the engine doesn't match" do + gemfile <<-G + gem "rack", "0.9.1" + + #{engine_incorrect} + G + + bundle "exec rackup", :exitstatus => true + should_be_engine_incorrect + end + + it "fails when the engine version doesn't match" do + simulate_ruby_engine "jruby" do + gemfile <<-G + gem "rack", "0.9.1" + + #{engine_version_incorrect} + G + + bundle "exec rackup", :exitstatus => true + should_be_engine_version_incorrect + end + end + end + + context "bundle console" do + before do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "activesupport", :group => :test + gem "rack_middleware", :group => :development + G + end + + it "starts IRB with the default group loaded when ruby version matches" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "activesupport", :group => :test + gem "rack_middleware", :group => :development + + #{ruby_version_correct} + G + + bundle "console" do |input| + input.puts("puts RACK") + input.puts("exit") + end + out.should include("0.9.1") + end + + it "fails when ruby version doesn't match" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "activesupport", :group => :test + gem "rack_middleware", :group => :development + + #{ruby_version_incorrect} + G + + bundle "console", :exitstatus => true + should_be_ruby_version_incorrect + end + + it "fails when engine doesn't match" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "activesupport", :group => :test + gem "rack_middleware", :group => :development + + #{engine_incorrect} + G + + bundle "console", :exitstatus => true + should_be_engine_incorrect + end + + it "fails when engine version doesn't match" do + simulate_ruby_engine "jruby" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "activesupport", :group => :test + gem "rack_middleware", :group => :development + + #{engine_version_incorrect} + G + + bundle "console", :exitstatus => true + should_be_engine_version_incorrect + end + end + end + + context "Bundler.setup" do + before do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "yard" + gem "rack", :group => :test + G + end + + it "makes a Gemfile.lock if setup succeeds" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "yard" + gem "rack" + + #{ruby_version_correct} + G + + File.read(bundled_app("Gemfile.lock")) + + FileUtils.rm(bundled_app("Gemfile.lock")) + + run "1" + bundled_app("Gemfile.lock").should exist + end + + it "fails when ruby version doesn't match" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "yard" + gem "rack" + + #{ruby_version_incorrect} + G + + File.read(bundled_app("Gemfile.lock")) + + FileUtils.rm(bundled_app("Gemfile.lock")) + + ruby <<-R + require 'rubygems' + require 'bundler' + + begin + Bundler.setup + rescue Bundler::RubyVersionMismatch => e + puts e.message + end + R + + bundled_app("Gemfile.lock").should_not exist + should_be_ruby_version_incorrect(:exitstatus => false) + end + + it "fails when engine doesn't match" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "yard" + gem "rack" + + #{engine_incorrect} + G + + File.read(bundled_app("Gemfile.lock")) + + FileUtils.rm(bundled_app("Gemfile.lock")) + + ruby <<-R + require 'rubygems' + require 'bundler' + + begin + Bundler.setup + rescue Bundler::RubyVersionMismatch => e + puts e.message + end + R + + bundled_app("Gemfile.lock").should_not exist + should_be_engine_incorrect(:exitstatus => false) + end + + it "fails when engine version doesn't match" do + simulate_ruby_engine "jruby" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "yard" + gem "rack" + + #{engine_version_incorrect} + G + + File.read(bundled_app("Gemfile.lock")) + + FileUtils.rm(bundled_app("Gemfile.lock")) + + ruby <<-R + require 'rubygems' + require 'bundler' + + begin + Bundler.setup + rescue Bundler::RubyVersionMismatch => e + puts e.message + end + R + + bundled_app("Gemfile.lock").should_not exist + should_be_engine_version_incorrect(:exitstatus => false) + end + end + end + + context "bundle outdated" do + before do + build_repo2 do + build_git "foo", :path => lib_path("foo") + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "2.3.5" + gem "foo", :git => "#{lib_path('foo')}" + G + end + + it "returns list of outdated gems when the ruby version matches" do + update_repo2 do + build_gem "activesupport", "3.0" + update_git "foo", :path => lib_path("foo") + end + + gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "2.3.5" + gem "foo", :git => "#{lib_path('foo')}" + + #{ruby_version_correct} + G + + bundle "outdated" + out.should include("activesupport (3.0 > 2.3.5)") + out.should include("foo (1.0") + end + + it "fails when the ruby version doesn't match" do + update_repo2 do + build_gem "activesupport", "3.0" + update_git "foo", :path => lib_path("foo") + end + + gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "2.3.5" + gem "foo", :git => "#{lib_path('foo')}" + + #{ruby_version_incorrect} + G + + bundle "outdated", :exitstatus => true + should_be_ruby_version_incorrect + end + + it "fails when the engine doesn't match" do + update_repo2 do + build_gem "activesupport", "3.0" + update_git "foo", :path => lib_path("foo") + end + + gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "2.3.5" + gem "foo", :git => "#{lib_path('foo')}" + + #{engine_incorrect} + G + + bundle "outdated", :exitstatus => true + should_be_engine_incorrect + end + + it "fails when the engine version doesn't match" do + simulate_ruby_engine "jruby" do + update_repo2 do + build_gem "activesupport", "3.0" + update_git "foo", :path => lib_path("foo") + end + + gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "2.3.5" + gem "foo", :git => "#{lib_path('foo')}" + + #{engine_version_incorrect} + G + + bundle "outdated", :exitstatus => true + should_be_engine_version_incorrect + end + end + end +end diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb index 664ac416e5..c458b6f6b9 100644 --- a/spec/support/helpers.rb +++ b/spec/support/helpers.rb @@ -60,7 +60,7 @@ module Spec def bundle(cmd, options = {}) expect_err = options.delete(:expect_err) exitstatus = options.delete(:exitstatus) - options["no-color"] = true unless options.key?("no-color") || cmd.to_s[0..3] == "exec" + options["no-color"] = true unless options.key?("no-color") || %w(exec conf).include?(cmd.to_s[0..3]) bundle_bin = File.expand_path('../../../bin/bundle', __FILE__) @@ -292,6 +292,17 @@ module Spec ENV['BUNDLER_SPEC_PLATFORM'] = old if block_given? end + def simulate_ruby_engine(engine, version = "1.6.0") + return if engine == local_ruby_engine + + old, ENV['BUNDLER_SPEC_RUBY_ENGINE'] = ENV['BUNDLER_SPEC_RUBY_ENGINE'], engine + old_version, ENV['BUNDLER_SPEC_RUBY_ENGINE_VERSION'] = ENV['BUNDLER_SPEC_RUBY_ENGINE_VERSION'], version + yield if block_given? + ensure + ENV['BUNDLER_SPEC_RUBY_ENGINE'] = old if block_given? + ENV['BUNDLER_SPEC_RUBY_ENGINE_VERSION'] = old_version if block_given? + end + def simulate_bundler_version(version) old, ENV['BUNDLER_SPEC_VERSION'] = ENV['BUNDLER_SPEC_VERSION'], version.to_s yield if block_given? diff --git a/spec/support/platforms.rb b/spec/support/platforms.rb index 658339badc..a3114585a5 100644 --- a/spec/support/platforms.rb +++ b/spec/support/platforms.rb @@ -49,5 +49,38 @@ module Spec def not_local_tag [:ruby, :jruby].find { |tag| tag != local_tag } end + + def local_ruby_engine + ENV["BUNDLER_SPEC_RUBY_ENGINE"] || (defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby") + end + + def local_engine_version + return ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"] if ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"] + + case local_ruby_engine + when "ruby" + RUBY_VERSION + when "rbx" + Rubinius::VERSION + when "jruby" + JRUBY_VERSION + else + raise BundlerError, "That RUBY_ENGINE is not recognized" + nil + end + end + + def not_local_engine_version + case not_local_tag + when :ruby + not_local_ruby_version + when :jruby + "1.6.1" + end + end + + def not_local_ruby_version + "1.12" + end end end diff --git a/spec/support/rubygems_hax/platform.rb b/spec/support/rubygems_hax/platform.rb index 183d0801a7..a473768531 100644 --- a/spec/support/rubygems_hax/platform.rb +++ b/spec/support/rubygems_hax/platform.rb @@ -8,4 +8,15 @@ if ENV['BUNDLER_SPEC_VERSION'] module Bundler VERSION = ENV['BUNDLER_SPEC_VERSION'].dup end -end
\ No newline at end of file +end + +class Object + if ENV['BUNDLER_SPEC_RUBY_ENGINE'] + remove_const :RUBY_ENGINE if defined?(RUBY_ENGINE) + RUBY_ENGINE = ENV['BUNDLER_SPEC_RUBY_ENGINE'] + + if RUBY_ENGINE == "jruby" + JRUBY_VERSION = ENV["BUNDLER_SPEC_RUBY_ENGINE_VERSION"] + end + end +end diff --git a/spec/update/gems_spec.rb b/spec/update/gems_spec.rb index 282e13d8c6..8832f276d4 100644 --- a/spec/update/gems_spec.rb +++ b/spec/update/gems_spec.rb @@ -33,6 +33,18 @@ describe "bundle update" do end end + describe "--quiet argument" do + it 'shows UI messages without --quiet argument' do + bundle "update" + out.should include("Fetching source") + end + + it 'does not show UI messages with --quiet argument' do + bundle "update --quiet" + out.should_not include("Fetching source") + end + end + describe "with a top level dependency" do it "unlocks all child dependencies that are unrelated to other locked dependencies" do update_repo2 do |