diff options
-rw-r--r-- | Rakefile | 14 | ||||
-rw-r--r-- | lib/bundler/cli.rb | 24 | ||||
-rw-r--r-- | lib/bundler/dsl.rb | 2 | ||||
-rw-r--r-- | lib/bundler/fetcher.rb | 6 | ||||
-rw-r--r-- | lib/bundler/vendor/net/http/persistent.rb | 174 | ||||
-rw-r--r-- | lib/bundler/vendor/thor.rb | 2 | ||||
-rw-r--r-- | lib/bundler/vendor/thor/base.rb | 19 | ||||
-rw-r--r-- | lib/bundler/vendor/thor/error.rb | 4 | ||||
-rw-r--r-- | spec/bundler/dsl_spec.rb | 4 | ||||
-rw-r--r-- | spec/other/binstubs_spec.rb | 24 |
10 files changed, 199 insertions, 74 deletions
@@ -220,18 +220,4 @@ end task :build => ["man:clean", "man:build"] task :release => ["man:clean", "man:build"] -namespace :vendor do - desc "Build the vendor dir" - task :build => :clean do - sh "git clone git://github.com/wycats/thor.git lib/bundler/vendor/tmp" - sh "mv lib/bundler/vendor/tmp/lib/* lib/bundler/vendor/" - rm_rf "lib/bundler/vendor/tmp" - end - - desc "Clean the vendor dir" - task :clean do - rm_rf "lib/bundler/vendor" - end -end - task :default => :spec diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 137a3e13f7..5953f5a025 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -379,18 +379,26 @@ module Bundler "binstub destination directory (default bin)" method_option "force", :type => :boolean, :default => false, :banner => "overwrite existing binstubs if they exist" - def binstubs(gem_name) + def binstubs(*gems) Bundler.definition.validate_ruby! Bundler.settings[:bin] = options["path"] if options["path"] Bundler.settings[:bin] = nil if options["path"] && options["path"].empty? installer = Installer.new(Bundler.root, Bundler.definition) - spec = installer.specs.find{|s| s.name == gem_name } - raise GemNotFound, not_found_message(gem_name, Bundler.definition.specs) unless spec - if spec.name == "bundler" - Bundler.ui.warn "Sorry, Bundler can only be run via Rubygems." - else - installer.generate_bundler_executable_stubs(spec, :force => options[:force], :binstubs_cmd => true) + if gems.empty? + Bundler.ui.error "`bundle binstubs` needs at least one gem to run." + exit 1 + end + + gems.each do |gem_name| + spec = installer.specs.find{|s| s.name == gem_name } + raise GemNotFound, not_found_message(gem_name, Bundler.definition.specs) unless spec + + if spec.name == "bundler" + Bundler.ui.warn "Sorry, Bundler can only be run via Rubygems." + else + installer.generate_bundler_executable_stubs(spec, :force => options[:force], :binstubs_cmd => true) + end end end @@ -485,6 +493,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)." + method_option "quiet", :type => :boolean, :banner => "Only output warnings and errors." 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 @@ -492,6 +501,7 @@ module Bundler bundle without having to download any additional gems. D def package + Bundler.ui.level = "warn" if options[:quiet] setup_cache_all install # TODO: move cache contents here now that all bundles are locked diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index 947aca7ba8..7f88a1bc5d 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -230,7 +230,7 @@ module Bundler if github = opts.delete("github") github = "#{github}/#{github}" unless github.include?("/") - opts["git"] = "git://github.com/#{github}.git" + opts["git"] = "https://github.com/#{github}.git" end if gist = opts.delete("gist") diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 9c8e155ce1..9f6c99a137 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -209,11 +209,11 @@ module Bundler begin Bundler.ui.debug "Fetching from: #{uri}" + req = Net::HTTP::Get.new uri.request_uri + req.basic_auth(uri.user, uri.password) if uri.user && uri.password if defined?(Net::HTTP::Persistent) - response = @connection.request(uri) + response = @connection.request(uri, req) else - req = Net::HTTP::Get.new uri.request_uri - req.basic_auth(uri.user, uri.password) if uri.user && uri.password response = @connection.request(req) end rescue OpenSSL::SSL::SSLError diff --git a/lib/bundler/vendor/net/http/persistent.rb b/lib/bundler/vendor/net/http/persistent.rb index dcdc5b53cb..f99f0625ed 100644 --- a/lib/bundler/vendor/net/http/persistent.rb +++ b/lib/bundler/vendor/net/http/persistent.rb @@ -1,5 +1,9 @@ require 'net/http' -require 'net/https' +begin + require 'net/https' +rescue LoadError + # net/https or openssl +end if RUBY_VERSION < '1.9' # but only for 1.8 require 'net/http/faster' require 'uri' require 'cgi' # for escaping @@ -9,6 +13,8 @@ begin rescue LoadError end +autoload :OpenSSL, 'openssl' + ## # Persistent connections for Net::HTTP # @@ -37,6 +43,11 @@ end # # perform a GET # response = http.request uri # +# # or +# +# get = Net::HTTP::Get.new uri.request_uri +# response = http.request get +# # # create a POST # post_uri = uri + 'create' # post = Net::HTTP::Post.new post_uri.path @@ -45,6 +56,10 @@ end # # perform the POST, the URI is always required # response http.request post_uri, post # +# Note that for GET, HEAD and other requests that do not have a body you want +# to use URI#request_uri not URI#path. The request_uri contains the query +# params which are sent in the body for other requests. +# # == SSL # # SSL connections are automatically created depending upon the scheme of the @@ -105,6 +120,13 @@ end # The amount of time allowed between reading two chunks from the socket. Set # through #read_timeout # +# === Max Requests +# +# The number of requests that should be made before opening a new connection. +# Typically many keep-alive capable servers tune this to 100 or less, so the +# 101st request will fail with ECONNRESET. If unset (default), this value has no +# effect, if set, connections will be reset on the request after max_requests. +# # === Open Timeout # # The amount of time to wait for a connection to be opened. Set through @@ -174,9 +196,29 @@ class Net::HTTP::Persistent EPOCH = Time.at 0 # :nodoc: ## + # Is OpenSSL available? This test works with autoload + + HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc: + + ## # The version of Net::HTTP::Persistent you are using - VERSION = '2.8' + VERSION = '2.9' + + ## + # Exceptions rescued for automatic retry on ruby 2.0.0. This overlaps with + # the exception list for ruby 1.x. + + RETRIED_EXCEPTIONS = [ # :nodoc: + (Net::ReadTimeout if Net.const_defined? :ReadTimeout), + IOError, + EOFError, + Errno::ECONNRESET, + Errno::ECONNABORTED, + Errno::EPIPE, + (OpenSSL::SSL::SSLError if HAVE_OPENSSL), + Timeout::Error, + ].compact ## # Error class for errors raised by Net::HTTP::Persistent. Various @@ -226,6 +268,8 @@ class Net::HTTP::Persistent $stderr.puts "sleeping #{sleep_time}" if $DEBUG sleep sleep_time end + rescue + # ignore StandardErrors, we've probably found the idle timeout. ensure http.shutdown @@ -288,6 +332,12 @@ class Net::HTTP::Persistent attr_accessor :idle_timeout ## + # Maximum number of requests on a connection before it is considered expired + # and automatically closed. + + attr_accessor :max_requests + + ## # The value sent in the Keep-Alive header. Defaults to 30. Not needed for # HTTP/1.1 servers. # @@ -442,6 +492,7 @@ class Net::HTTP::Persistent @open_timeout = nil @read_timeout = nil @idle_timeout = 5 + @max_requests = nil @socket_options = [] @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if @@ -458,15 +509,22 @@ class Net::HTTP::Persistent @private_key = nil @ssl_version = nil @verify_callback = nil - @verify_mode = OpenSSL::SSL::VERIFY_PEER + @verify_mode = nil @cert_store = nil @generation = 0 # incremented when proxy URI changes @ssl_generation = 0 # incremented when SSL session variables change - @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session + + if HAVE_OPENSSL then + @verify_mode = OpenSSL::SSL::VERIFY_PEER + @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session + end @retry_change_requests = false + @ruby_1 = RUBY_VERSION < '2' + @retried_on_ruby_2 = !@ruby_1 + self.proxy = proxy if proxy end @@ -536,6 +594,9 @@ class Net::HTTP::Persistent use_ssl = uri.scheme.downcase == 'https' if use_ssl then + raise Net::HTTP::Persistent::Error, 'OpenSSL is not available' unless + HAVE_OPENSSL + ssl_generation = @ssl_generation ssl_cleanup ssl_generation @@ -606,10 +667,12 @@ class Net::HTTP::Persistent end ## - # Returns true if the connection should be reset due to an idle timeout, - # false otherwise. + # Returns true if the connection should be reset due to an idle timeout, or + # maximum request count, false otherwise. def expired? connection + requests = Thread.current[@request_key][connection.object_id] + return true if @max_requests && requests >= @max_requests return false unless @idle_timeout return true if @idle_timeout.zero? @@ -679,10 +742,15 @@ class Net::HTTP::Persistent end ## - # Is the request idempotent or is retry_change_requests allowed + # Is the request +req+ idempotent or is retry_change_requests allowed. + # + # If +retried_on_ruby_2+ is true, true will be returned if we are on ruby, + # retry_change_requests is allowed and the request is not idempotent. - def can_retry? req - retry_change_requests or idempotent?(req) + def can_retry? req, retried_on_ruby_2 = false + return @retry_change_requests && !idempotent?(req) if retried_on_ruby_2 + + @retry_change_requests || idempotent?(req) end if RUBY_VERSION > '1.9' then @@ -901,31 +969,14 @@ class Net::HTTP::Persistent # # +req+ must be a Net::HTTPRequest subclass (see Net::HTTP for a list). # - # If there is an error and the request is idempontent according to RFC 2616 + # If there is an error and the request is idempotent according to RFC 2616 # it will be retried automatically. def request uri, req = nil, &block retried = false bad_response = false - req = Net::HTTP::Get.new uri.request_uri unless req - - @headers.each do |pair| - req.add_field(*pair) - end - - if uri.user or uri.password - req.basic_auth uri.user, uri.password - end - - @override_headers.each do |name, value| - req[name] = value - end - - unless req['Connection'] then - req.add_field 'Connection', 'keep-alive' - req.add_field 'Keep-Alive', @keep_alive - end + req = request_setup req || uri connection = connection_for uri connection_id = connection.object_id @@ -950,23 +1001,25 @@ class Net::HTTP::Persistent bad_response = true retry - rescue IOError, EOFError, Timeout::Error, - Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, - Errno::EINVAL, OpenSSL::SSL::SSLError => e - - if retried or not can_retry? req - due_to = "(due to #{e.message} - #{e.class})" - message = error_message connection + rescue *RETRIED_EXCEPTIONS => e # retried on ruby 2 + request_failed e, req, connection if + retried or not can_retry? req, @retried_on_ruby_2 - finish connection + reset connection - raise Error, "too many connection resets #{due_to} #{message}" - end + retried = true + retry + rescue Errno::EINVAL, Errno::ETIMEDOUT => e # not retried on ruby 2 + request_failed e, req, connection if retried or not can_retry? req reset connection retried = true retry + rescue Exception => e + finish connection + + raise ensure Thread.current[@timeout_key][connection_id] = Time.now end @@ -977,6 +1030,51 @@ class Net::HTTP::Persistent end ## + # Raises an Error for +exception+ which resulted from attempting the request + # +req+ on the +connection+. + # + # Finishes the +connection+. + + def request_failed exception, req, connection # :nodoc: + due_to = "(due to #{exception.message} - #{exception.class})" + message = "too many connection resets #{due_to} #{error_message connection}" + + finish connection + + + raise Error, message, exception.backtrace + end + + ## + # Creates a GET request if +req_or_uri+ is a URI and adds headers to the + # request. + # + # Returns the request. + + def request_setup req_or_uri # :nodoc: + req = if URI === req_or_uri then + Net::HTTP::Get.new req_or_uri.request_uri + else + req_or_uri + end + + @headers.each do |pair| + req.add_field(*pair) + end + + @override_headers.each do |name, value| + req[name] = value + end + + unless req['Connection'] then + req.add_field 'Connection', 'keep-alive' + req.add_field 'Keep-Alive', @keep_alive + end + + req + end + + ## # Shuts down all connections for +thread+. # # Uses the current thread by default. diff --git a/lib/bundler/vendor/thor.rb b/lib/bundler/vendor/thor.rb index 9eeab586aa..6880b6d7ae 100644 --- a/lib/bundler/vendor/thor.rb +++ b/lib/bundler/vendor/thor.rb @@ -421,7 +421,7 @@ class Thor possibilities = find_command_possibilities(meth) if possibilities.size > 1 - raise ArgumentError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]" + raise AmbiguousTaskError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]" elsif possibilities.size < 1 meth = meth || default_command elsif map[meth] diff --git a/lib/bundler/vendor/thor/base.rb b/lib/bundler/vendor/thor/base.rb index e33d852e2f..272dae417b 100644 --- a/lib/bundler/vendor/thor/base.rb +++ b/lib/bundler/vendor/thor/base.rb @@ -474,10 +474,10 @@ class Thor alias handle_no_task_error handle_no_command_error def handle_argument_error(command, error, args, arity) #:nodoc: - msg = "ERROR: #{basename} #{command.name} was called with " + msg = "ERROR: \"#{basename} #{command.name}\" was called with " msg << 'no arguments' if args.empty? msg << 'arguments ' << args.inspect if !args.empty? - msg << "\nUsage: #{self.banner(command).inspect}." + msg << "\nUsage: #{self.banner(command).inspect}" raise InvocationError, msg end @@ -603,13 +603,16 @@ class Thor else value = superclass.send(method) - if value - if value.is_a?(TrueClass) || value.is_a?(Symbol) - value - else - value.dup - end + # Ruby implements `dup` on Object, but raises a `TypeError` + # if the method is called on immediates. As a result, we + # don't have a good way to check whether dup will succeed + # without calling it and rescuing the TypeError. + begin + value.dup + rescue TypeError + value end + end end diff --git a/lib/bundler/vendor/thor/error.rb b/lib/bundler/vendor/thor/error.rb index 31e0c4bc5c..3174c57eac 100644 --- a/lib/bundler/vendor/thor/error.rb +++ b/lib/bundler/vendor/thor/error.rb @@ -13,6 +13,10 @@ class Thor end UndefinedTaskError = UndefinedCommandError + class AmbiguousCommandError < Error + end + AmbiguousTaskError = AmbiguousCommandError + # Raised when a command was found, but not invoked properly. class InvocationError < Error end diff --git a/spec/bundler/dsl_spec.rb b/spec/bundler/dsl_spec.rb index 610ff0b55e..1b05eb48b4 100644 --- a/spec/bundler/dsl_spec.rb +++ b/spec/bundler/dsl_spec.rb @@ -9,7 +9,7 @@ describe Bundler::Dsl do describe "#_normalize_options" do it "converts :github to :git" do subject.gem("sparks", :github => "indirect/sparks") - github_uri = "git://github.com/indirect/sparks.git" + github_uri = "https://github.com/indirect/sparks.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end @@ -27,7 +27,7 @@ describe Bundler::Dsl do it "converts 'rails' to 'rails/rails'" do subject.gem("rails", :github => "rails") - github_uri = "git://github.com/rails/rails.git" + github_uri = "https://github.com/rails/rails.git" expect(subject.dependencies.first.source.uri).to eq(github_uri) end end diff --git a/spec/other/binstubs_spec.rb b/spec/other/binstubs_spec.rb index 9237a8d3d5..3d3ed0fc58 100644 --- a/spec/other/binstubs_spec.rb +++ b/spec/other/binstubs_spec.rb @@ -26,6 +26,30 @@ describe "bundle binstubs <gem>" do expect(bundled_app("bin/rails")).to exist end + it "does install multiple binstubs" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rails" + G + + bundle "binstubs rails rack" + + expect(bundled_app("bin/rackup")).to exist + expect(bundled_app("bin/rails")).to exist + end + + it "displays an error when used without any gem" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs", :exitstatus => true + expect(exitstatus).to eq(1) + expect(out).to eq("`bundle binstubs` needs at least one gem to run.") + end + it "does not bundle the bundler binary" do install_gemfile <<-G source "file://#{gem_repo1}" |