diff options
Diffstat (limited to 'lib')
42 files changed, 909 insertions, 448 deletions
diff --git a/lib/bundler.rb b/lib/bundler.rb index 1eaf194b..6de4afc7 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -37,6 +37,7 @@ module Bundler autoload :RubyVersion, 'bundler/ruby_version' autoload :RubyDsl, 'bundler/ruby_dsl' autoload :Runtime, 'bundler/runtime' + autoload :S3Fetcher, 'bundler/s3_fetcher' autoload :Settings, 'bundler/settings' autoload :SharedHelpers, 'bundler/shared_helpers' autoload :SpecSet, 'bundler/spec_set' @@ -59,6 +60,7 @@ module Bundler class InstallHookError < BundlerError; status_code(8) ; end class PathError < BundlerError; status_code(13) ; end class GitError < BundlerError; status_code(11) ; end + class SVNError < BundlerError; status_code(23) ; end class DeprecatedError < BundlerError; status_code(12) ; end class GemspecError < BundlerError; status_code(14) ; end class InvalidOption < BundlerError; status_code(15) ; end @@ -365,10 +367,19 @@ module Bundler @git_present = Bundler.which("git") || Bundler.which("git.exe") end + def svn_present? + return @svn_present if defined?(@svn_present) + @svn_present = Bundler.which("svn") || Bundler.which("svn.exe") + end + def ruby_version @ruby_version ||= SystemRubyVersion.new end + def reset! + @definition = nil + end + private def eval_yaml_gemspec(path, contents) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index c3e5d8b4..8da526e2 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -4,6 +4,7 @@ require 'bundler/vendored_thor' module Bundler class CLI < Thor include Thor::Actions + AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean] def self.start(*) super @@ -12,11 +13,13 @@ module Bundler raise e end - def initialize(*) + def initialize(*args) super + current_cmd = args.last[:current_command].name ENV['BUNDLE_GEMFILE'] = File.expand_path(options[:gemfile]) if options[:gemfile] Bundler::Retry.attempts = options[:retry] || Bundler.settings[:retry] || Bundler::Retry::DEFAULT_ATTEMPTS Bundler.rubygems.ui = UI::RGProxy.new(Bundler.ui) + auto_install if AUTO_INSTALL_CMDS.include?(current_cmd) rescue UnknownArgumentError => e raise InvalidOption, e.message ensure @@ -30,9 +33,9 @@ module Bundler default_task :install class_option "no-color", :type => :boolean, :banner => "Disable colorization in output" - class_option "verbose", :type => :boolean, :banner => "Enable verbose output mode", :aliases => "-V" class_option "retry", :type => :numeric, :aliases => "-r", :banner => "Specify the number of times you wish to attempt network commands" + class_option "verbose", :type => :boolean, :banner => "Enable verbose output mode", :aliases => "-V" def help(cli = nil) case cli @@ -82,12 +85,12 @@ module Bundler all gems are found, Bundler prints a success message and exits with a status of 0. If not, the first missing gem is listed and Bundler exits status 1. D + method_option "dry-run", :type => :boolean, :default => false, :banner => + "Lock the Gemfile" method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" method_option "path", :type => :string, :banner => "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine" - method_option "dry-run", :type => :boolean, :default => false, :banner => - "Lock the Gemfile" def check require 'bundler/cli/check' Check.new(options).run @@ -104,41 +107,41 @@ module Bundler If the bundle has already been installed, bundler will tell you so and then exit. D - method_option "without", :type => :array, :banner => - "Exclude gems that are part of the specified named group." + method_option "binstubs", :type => :string, :lazy_default => "bin", :banner => + "Generate bin stubs for bundled gems to ./bin" + method_option "clean", :type => :boolean, :banner => + "Run bundle clean automatically after install" + method_option "deployment", :type => :boolean, :banner => + "Install using defaults tuned for deployment environments" + method_option "frozen", :type => :boolean, :banner => + "Do not allow the Gemfile.lock to be updated after this install" + method_option "full-index", :type => :boolean, :banner => + "Use the rubygems modern index instead of the API endpoint" method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" - method_option "no-prune", :type => :boolean, :banner => - "Don't remove stale gems from the cache." + method_option "jobs", :aliases => "-j", :type => :numeric, :banner => + "Specify the number of jobs to run in parallel" + method_option "local", :type => :boolean, :banner => + "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "no-cache", :type => :boolean, :banner => "Don't update the existing gem cache." + method_option "no-prune", :type => :boolean, :banner => + "Don't remove stale gems from the cache." + method_option "path", :type => :string, :banner => + "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine" method_option "quiet", :type => :boolean, :banner => "Only output warnings and errors." - method_option "local", :type => :boolean, :banner => - "Do not attempt to fetch gems remotely and use the gem cache instead" - method_option "binstubs", :type => :string, :lazy_default => "bin", :banner => - "Generate bin stubs for bundled gems to ./bin" method_option "shebang", :type => :string, :banner => "Specify a different shebang executable name than the default (usually 'ruby')" - method_option "path", :type => :string, :banner => - "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine" - method_option "system", :type => :boolean, :banner => - "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application" - method_option "frozen", :type => :boolean, :banner => - "Do not allow the Gemfile.lock to be updated after this install" - method_option "deployment", :type => :boolean, :banner => - "Install using defaults tuned for deployment environments" method_option "standalone", :type => :array, :lazy_default => [], :banner => "Make a bundle that can work without the Bundler runtime" - method_option "full-index", :type => :boolean, :banner => - "Use the rubygems modern index instead of the API endpoint" - method_option "clean", :type => :boolean, :banner => - "Run bundle clean automatically after install" + method_option "system", :type => :boolean, :banner => + "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application" method_option "trust-policy", :alias => "P", :type => :string, :banner => "Gem trust policy (like gem install -P). Must be one of " + Bundler.rubygems.security_policy_keys.join('|') - method_option "jobs", :aliases => "-j", :type => :numeric, :banner => - "Specify the number of jobs to run in parallel" + method_option "without", :type => :array, :banner => + "Exclude gems that are part of the specified named group." def install require 'bundler/cli/install' @@ -151,17 +154,18 @@ module Bundler update when you have changed the Gemfile, or if you want to get the newest possible versions of the gems in the bundle. D - method_option "source", :type => :array, :banner => "Update a specific source (and all gems associated with it)" + method_option "full-index", :type => :boolean, :banner => + "Use the rubygems modern index instead of the API endpoint" + method_option "group", :aliases => "-g", :type => :array, :banner => + "Update a specific group" + method_option "jobs", :aliases => "-j", :type => :numeric, :banner => + "Specify the number of jobs to run in parallel" 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" - method_option "jobs", :aliases => "-j", :type => :numeric, :banner => - "Specify the number of jobs to run in parallel" - method_option "group", :aliases => "-g", :type => :array, :banner => - "Update a specific group" + method_option "source", :type => :array, :banner => + "Update a specific source (and all gems associated with it)" def update(*gems) require 'bundler/cli/update' Update.new(options, gems).run @@ -180,15 +184,15 @@ module Bundler end map %w(list) => "show" - desc "binstubs GEM [OPTIONS]", "install the binstubs of the listed gem" + desc "binstubs GEM [OPTIONS]", "Install the binstubs of the listed gem" long_desc <<-D Generate binstubs for executables in [GEM]. Binstubs are put into bin, or the --binstubs directory if one has been set. D - method_option "path", :type => :string, :lazy_default => "bin", :banner => - "binstub destination directory (default bin)" method_option "force", :type => :boolean, :default => false, :banner => - "overwrite existing binstubs if they exist" + "Overwrite existing binstubs if they exist" + method_option "path", :type => :string, :lazy_default => "bin", :banner => + "Binstub destination directory (default bin)" def binstubs(*gems) require 'bundler/cli/binstubs' Binstubs.new(options, gems).run @@ -201,10 +205,10 @@ module Bundler versions of the given gems. Prerelease gems are ignored by default. If your gems are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1. D - method_option "pre", :type => :boolean, :banner => "Check for newer pre-release gems" - method_option "source", :type => :array, :banner => "Check against a specific source" method_option "local", :type => :boolean, :banner => "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "pre", :type => :boolean, :banner => "Check for newer pre-release gems" + method_option "source", :type => :array, :banner => "Check against a specific source" method_option "strict", :type => :boolean, :banner => "Only list newer versions allowed by your Gemfile requirements" def outdated(*gems) @@ -213,20 +217,21 @@ module Bundler end desc "cache [OPTIONS]", "Cache all the gems to vendor/cache", :hide => true + method_option "all", :type => :boolean, :banner => "Include all sources (including path, git and svn)." 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 require 'bundler/cli/cache' Cache.new(options).run end desc "package [OPTIONS]", "Locks and then caches all of the gems into vendor/cache" + method_option "all", :type => :boolean, :banner => "Include all sources (including path, git and svn)." + method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" + method_option "no-install", :type => :boolean, :banner => "Don't actually install the gems, just package." 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." method_option "path", :type => :string, :banner => "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME). Bundler will remember this value for future installs on this machine" - method_option "gemfile", :type => :string, :banner => "Use the specified gemfile instead of Gemfile" + 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 @@ -274,16 +279,10 @@ module Bundler Open.new(options, name).run end - CONSOLES = { - 'pry' => :Pry, - 'ripl' => :Ripl, - 'irb' => :IRB, - } - desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded" def console(group = nil) require 'bundler/cli/console' - Console.new(options, group, CONSOLES).run + Console.new(options, group).run end desc "version", "Prints the bundler's version information" @@ -313,9 +312,11 @@ module Bundler The associated gems must also be installed via 'bundle install'. D method_option :file, :type => :string, :default => 'gem_graph', :aliases => '-f', :banner => "The name to use for the generated file. see format option" - method_option :version, :type => :boolean, :default => false, :aliases => '-v', :banner => "Set to show each gem version." - method_option :requirements, :type => :boolean, :default => false, :aliases => '-r', :banner => "Set to show the version of each required dependency." method_option :format, :type => :string, :default => "png", :aliases => '-F', :banner => "This is output format option. Supported format is png, jpg, svg, dot ..." + method_option :requirements, :type => :boolean, :default => false, :aliases => '-r', :banner => "Set to show the version of each required dependency." + method_option :version, :type => :boolean, :default => false, :aliases => '-v', :banner => "Set to show each gem version." + method_option :without, :type => :array, :default => [], :banner => "Exclude gems that are part of the specified named group." + def viz require 'bundler/cli/viz' Viz.new(options).run @@ -323,12 +324,13 @@ module Bundler desc "gem GEM [OPTIONS]", "Creates a skeleton for creating a rubygem" method_option :bin, :type => :boolean, :default => false, :aliases => '-b', :banner => "Generate a binary for your library." - method_option :test, :type => :string, :lazy_default => 'rspec', :aliases => '-t', :banner => "Generate a test directory for your library: 'rspec' is the default, but 'minitest' is also supported." method_option :edit, :type => :string, :aliases => "-e", :lazy_default => [ENV['BUNDLER_EDITOR'], ENV['VISUAL'], ENV['EDITOR']].find{|e| !e.nil? && !e.empty? }, :required => false, :banner => "/path/to/your/editor", :desc => "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" - method_option :ext, :type => :boolean, :detailt => false, :banner => "Generate the boilerplate for C extension code" + method_option :ext, :type => :boolean, :default => false, :banner => "Generate the boilerplate for C extension code" + method_option :test, :type => :string, :lazy_default => 'rspec', :aliases => '-t', :banner => + "Generate a test directory for your library: 'rspec' is the default, but 'minitest' is also supported." def gem(name) require 'bundler/cli/gem' @@ -341,9 +343,9 @@ module Bundler desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory" method_option "dry-run", :type => :boolean, :default => false, :banner => - "only print out changes, do not actually clean gems" + "Only print out changes, do not actually clean gems" method_option "force", :type => :boolean, :default => false, :banner => - "forces clean even if --path is not set" + "Forces clean even if --path is not set" def clean require 'bundler/cli/clean' Clean.new(options.dup).run @@ -368,5 +370,26 @@ module Bundler Env.new.write($stdout) end + private + + # Automatically invoke `bundle install` and resume if + # Bundler.settings[:auto_install] exists. This is set through config cmd + # `bundle config auto_install 1`. + # + # Note that this method `nil`s out the global Definition object, so it + # should be called first, before you instantiate anything like an + # `Installer` that'll keep a reference to the old one instead. + def auto_install + return unless Bundler.settings[:auto_install] + + begin + Bundler.definition.specs + rescue GemNotFound + Bundler.ui.info "Automatically installing missing gems." + Bundler.reset! + invoke :install, [] + Bundler.reset! + end + end end end diff --git a/lib/bundler/cli/clean.rb b/lib/bundler/cli/clean.rb index e7a4c014..8e86e916 100644 --- a/lib/bundler/cli/clean.rb +++ b/lib/bundler/cli/clean.rb @@ -7,10 +7,16 @@ module Bundler end def run - if Bundler.settings[:path] || options[:force] - Bundler.load.clean(options[:"dry-run"]) - else - Bundler.ui.error "Can only use bundle clean when --path is set or --force is set" + reqire_path_or_force + Bundler.load.clean(options[:"dry-run"]) + end + + protected + + def reqire_path_or_force + if !Bundler.settings[:path] && !options[:force] + Bundler.ui.error "Cleaning all the gems on your system is dangerous! " \ + "To remove every gem not in this bundle, run `bundle clean --force`." exit 1 end end diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index 0548525a..83315a27 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -25,6 +25,8 @@ module Bundler else ask_for_spec_from(specs) end + rescue RegexpError + raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies) end def self.ask_for_spec_from(specs) diff --git a/lib/bundler/cli/console.rb b/lib/bundler/cli/console.rb index bf000936..f52bfb11 100644 --- a/lib/bundler/cli/console.rb +++ b/lib/bundler/cli/console.rb @@ -1,41 +1,38 @@ module Bundler class CLI::Console - attr_reader :options, :group, :consoles - def initialize(options, group, consoles) + attr_reader :options, :group + def initialize(options, group) @options = options @group = group - @consoles = consoles end def run group ? Bundler.require(:default, *(group.split.map! {|g| g.to_sym })) : Bundler.require ARGV.clear - preferred = Bundler.settings[:console] || 'irb' - - # See if console is available - begin - require preferred || true - rescue LoadError - # Is it in Gemfile? - Bundler.ui.error "Could not load the #{preferred} console" - Bundler.ui.info "Falling back on IRB..." - - require 'irb' - preferred = 'irb' - end - - constant = consoles[preferred] + console = get_console(Bundler.settings[:console] || 'irb') + load '.consolerc' if File.exists?('.consolerc') + console.start + end - console = begin - Object.const_get(constant) - rescue NameError => e - Bundler.ui.error e.inspect - Bundler.ui.error "Could not load the #{constant} console" - return - end + def get_console(name) + require name + get_constant(name) + rescue LoadError + Bundler.ui.error "Couldn't load console #{name}" + get_constant('irb') + end - console.start + def get_constant(name) + const_name = { + 'pry' => :Pry, + 'ripl' => :Ripl, + 'irb' => :IRB, + }[name] + Object.const_get(const_name) + rescue NameError + Bundler.ui.error "Could not find constant #{const_name}" + exit 1 end end diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 36663419..485721c5 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -1,34 +1,34 @@ +require 'pathname' + module Bundler class CLI::Gem - attr_reader :options, :gem_name, :thor + attr_reader :options, :gem_name, :thor, :name, :target + def initialize(options, gem_name, thor) @options = options @gem_name = gem_name @thor = thor + + @name = gem_name.chomp("/") # remove trailing slash if present + @target = Pathname.pwd.join(name) + + validate_ext_name if options[:ext] end def run - if options[:ext] && gem_name.index('-') - Bundler.ui.error "You have specified a gem name which does not conform to the \n" \ - "naming guidelines for C extensions. For more information, \n" \ - "see the 'Extension Naming' section at the following URL:\n" \ - "http://guides.rubygems.org/gems-with-extensions/\n" - exit 1 - end - - name = gem_name.chomp("/") # remove trailing slash if present underscored_name = name.tr('-', '_') namespaced_path = name.tr('-', '/') - target = File.join(Dir.pwd, name) constant_name = name.split('_').map{|p| p[0..0].upcase + p[1..-1] }.join constant_name = constant_name.split('-').map{|q| q[0..0].upcase + q[1..-1] }.join('::') if constant_name =~ /-/ constant_array = constant_name.split('::') git_user_name = `git config user.name`.chomp git_user_email = `git config user.email`.chomp + opts = { :name => name, :underscored_name => underscored_name, :namespaced_path => namespaced_path, + :makefile_path => "#{underscored_name}/#{underscored_name}", :constant_name => constant_name, :constant_array => constant_array, :author => git_user_name.empty? ? "TODO: Write your name" : git_user_name, @@ -36,42 +36,66 @@ module Bundler :test => options[:test], :ext => options[:ext] } - gemspec_dest = File.join(target, "#{name}.gemspec") - thor.template(File.join("newgem/Gemfile.tt"), File.join(target, "Gemfile"), opts) - thor.template(File.join("newgem/Rakefile.tt"), File.join(target, "Rakefile"), opts) - thor.template(File.join("newgem/LICENSE.txt.tt"), File.join(target, "LICENSE.txt"), opts) - thor.template(File.join("newgem/README.md.tt"), File.join(target, "README.md"), opts) - thor.template(File.join("newgem/gitignore.tt"), File.join(target, ".gitignore"), opts) - thor.template(File.join("newgem/newgem.gemspec.tt"), gemspec_dest, opts) - thor.template(File.join("newgem/lib/newgem.rb.tt"), File.join(target, "lib/#{namespaced_path}.rb"), opts) - thor.template(File.join("newgem/lib/newgem/version.rb.tt"), File.join(target, "lib/#{namespaced_path}/version.rb"), opts) - if options[:bin] - thor.template(File.join("newgem/bin/newgem.tt"), File.join(target, 'bin', name), opts) - end + + templates = { + "Gemfile.tt" => "Gemfile", + "gitignore.tt" => ".gitignore", + "lib/newgem.rb.tt" => "lib/#{namespaced_path}.rb", + "lib/newgem/version.rb.tt" => "lib/#{namespaced_path}/version.rb", + "LICENSE.txt.tt" => "LICENSE.txt", + "newgem.gemspec.tt" => "#{name}.gemspec", + "consolerc.tt" => ".consolerc", + "Rakefile.tt" => "Rakefile", + "README.md.tt" => "README.md" + } + + templates.merge!("bin/newgem.tt" => "bin/#{name}") if options[:bin] + templates.merge!(".travis.yml.tt" => ".travis.yml") if options[:test] + case options[:test] when 'rspec' - thor.template(File.join("newgem/rspec.tt"), File.join(target, ".rspec"), opts) - thor.template(File.join("newgem/spec/spec_helper.rb.tt"), File.join(target, "spec/spec_helper.rb"), opts) - thor.template(File.join("newgem/spec/newgem_spec.rb.tt"), File.join(target, "spec/#{namespaced_path}_spec.rb"), opts) + templates.merge!( + "rspec.tt" => ".rspec", + "spec/spec_helper.rb.tt" => "spec/spec_helper.rb", + "spec/newgem_spec.rb.tt" => "spec/#{namespaced_path}_spec.rb" + ) when 'minitest' - thor.template(File.join("newgem/test/minitest_helper.rb.tt"), File.join(target, "test/minitest_helper.rb"), opts) - thor.template(File.join("newgem/test/test_newgem.rb.tt"), File.join(target, "test/test_#{namespaced_path}.rb"), opts) - end - if options[:test] - thor.template(File.join("newgem/.travis.yml.tt"), File.join(target, ".travis.yml"), opts) + templates.merge!( + "test/minitest_helper.rb.tt" => "test/minitest_helper.rb", + "test/test_newgem.rb.tt" => "test/test_#{namespaced_path}.rb" + ) end + if options[:ext] - thor.template(File.join("newgem/ext/newgem/extconf.rb.tt"), File.join(target, "ext/#{name}/extconf.rb"), opts) - thor.template(File.join("newgem/ext/newgem/newgem.h.tt"), File.join(target, "ext/#{name}/#{underscored_name}.h"), opts) - thor.template(File.join("newgem/ext/newgem/newgem.c.tt"), File.join(target, "ext/#{name}/#{underscored_name}.c"), opts) + templates.merge!( + "ext/newgem/extconf.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h", + "ext/newgem/newgem.c.tt" => "ext/#{name}/#{underscored_name}.c" + ) + end + + templates.each do |src, dst| + thor.template("newgem/#{src}", target.join(dst), opts) end + Bundler.ui.info "Initializing git repo in #{target}" Dir.chdir(target) { `git init`; `git add .` } if options[:edit] - thor.run("#{options["edit"]} \"#{gemspec_dest}\"") # Open gemspec in editor + # Open gemspec in editor + thor.run("#{options["edit"]} \"#{target.join("#{name}.gemspec")}\"") end end + def validate_ext_name + return unless gem_name.index('-') + + Bundler.ui.error "You have specified a gem name which does not conform to the \n" \ + "naming guidelines for C extensions. For more information, \n" \ + "see the 'Extension Naming' section at the following URL:\n" \ + "http://guides.rubygems.org/gems-with-extensions/\n" + exit 1 + end + end end diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index 9bb7f681..bd808055 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -64,6 +64,7 @@ module Bundler Bundler.settings[:shebang] = options["shebang"] if options["shebang"] Bundler.settings[:jobs] = options["jobs"] if options["jobs"] Bundler.settings[:no_prune] = true if options["no-prune"] + Bundler.settings[:no_install] = true if options["no-install"] Bundler.settings[:clean] = options["clean"] if options["clean"] Bundler.settings.without = options[:without] Bundler.ui.level = "warn" if options[:quiet] diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb index ee6387ac..22c2f377 100644 --- a/lib/bundler/cli/outdated.rb +++ b/lib/bundler/cli/outdated.rb @@ -50,8 +50,8 @@ module Bundler next if active_spec.nil? gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version) - git_outdated = current_spec.git_version != active_spec.git_version - if gem_outdated || git_outdated + scm_outdated = current_spec.scm_version != active_spec.scm_version + if gem_outdated || scm_outdated if out_count == 0 if options["pre"] Bundler.ui.info "Outdated gems included in the bundle (including pre-releases):" @@ -60,8 +60,8 @@ module Bundler end end - spec_version = "#{active_spec.version}#{active_spec.git_version}" - current_version = "#{current_spec.version}#{current_spec.git_version}" + spec_version = "#{active_spec.version}#{active_spec.scm_version}" + current_version = "#{current_spec.version}#{current_spec.scm_version}" dependency_version = %|Gemfile specifies "#{dependency.requirement}"| if dependency && dependency.specific? Bundler.ui.info " * #{active_spec.name} (#{spec_version} > #{current_version}) #{dependency_version}".rstrip out_count += 1 diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb index 07ff63dc..fa4c12ad 100644 --- a/lib/bundler/cli/show.rb +++ b/lib/bundler/cli/show.rb @@ -35,7 +35,7 @@ module Bundler else Bundler.ui.info "Gems included by the bundle:" Bundler.load.specs.sort_by { |s| s.name }.each do |s| - desc = " * #{s.name} (#{s.version}#{s.git_version})" + desc = " * #{s.name} (#{s.version}#{s.scm_version})" if @options[:verbose] Bundler.ui.info "#{desc} - #{s.summary || 'No description available.'}" else diff --git a/lib/bundler/cli/viz.rb b/lib/bundler/cli/viz.rb index c404ddbb..09557e59 100644 --- a/lib/bundler/cli/viz.rb +++ b/lib/bundler/cli/viz.rb @@ -8,7 +8,7 @@ module Bundler def run require 'graphviz' output_file = File.expand_path(options[:file]) - graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format]) + graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format], options[:without]) graph.viz rescue LoadError => e Bundler.ui.error e.inspect diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index 8d012a03..648eb75e 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -87,6 +87,38 @@ module Bundler Bundler::WINDOWS end + def mswin_18? + mswin? && on_18? + end + + def mswin_19? + mswin? && on_19? + end + + def mswin_20? + mswin? && on_20? + end + + def mswin_21? + mswin? && on_21? + end + + def mswin64? + Bundler::WINDOWS && Gem::Platform.local.os == "mswin64" && Gem::Platform.local.cpu == 'x64' + end + + def mswin64_19? + mswin64? && on_19? + end + + def mswin64_20? + mswin64? && on_20? + end + + def mswin64_21? + mswin64? && on_21? + end + def mingw? Bundler::WINDOWS && Gem::Platform.local.os == "mingw32" && Gem::Platform.local.cpu != 'x64' end diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index c0e8f8bf..de2bee9a 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -24,6 +24,14 @@ module Bundler :jruby_18 => Gem::Platform::JAVA, :jruby_19 => Gem::Platform::JAVA, :mswin => Gem::Platform::MSWIN, + :mswin_18 => Gem::Platform::MSWIN, + :mswin_19 => Gem::Platform::MSWIN, + :mswin_20 => Gem::Platform::MSWIN, + :mswin_21 => Gem::Platform::MSWIN, + :mswin64 => Gem::Platform::MSWIN64, + :mswin64_19 => Gem::Platform::MSWIN64, + :mswin64_20 => Gem::Platform::MSWIN64, + :mswin64_21 => Gem::Platform::MSWIN64, :mingw => Gem::Platform::MINGW, :mingw_18 => Gem::Platform::MINGW, :mingw_19 => Gem::Platform::MINGW, @@ -89,7 +97,6 @@ module Bundler out << "\n" end - def specific? super rescue NoMethodError diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index ef5c6fba..5f27f872 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -24,7 +24,7 @@ module Bundler @platforms = [] @env = nil @ruby_version = nil - add_github_sources + add_git_sources end def eval_gemfile(gemfile, contents = nil) @@ -148,6 +148,18 @@ module Bundler with_source(@sources.add_git_source(normalize_hash(options).merge("uri" => uri)), &blk) end + def svn(uri, options = {}, source_options = {}, &blk) + unless block_given? + msg = "You can no longer specify a svn source by itself. Instead, \n" \ + "either use the :svn option on a gem, or specify the gems that \n" \ + "bundler should find in the svn source by passing a block to \n" \ + "the svn method." + raise DeprecatedError, msg + end + + with_source(@sources.add_svn_source(normalize_hash(options).merge("uri" => uri)), &blk) + end + def to_definition(lockfile, unlock) Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version) end @@ -182,13 +194,19 @@ module Bundler private - def add_github_sources + def add_git_sources git_source(:github) do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") "git://github.com/#{repo_name}.git" end git_source(:gist){ |repo_name| "https://gist.github.com/#{repo_name}.git" } + + git_source(:bitbucket) do |repo_name| + user_name, repo_name = repo_name.split '/' + repo_name ||= user_name + "https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git" + end end def with_source(source) @@ -209,13 +227,16 @@ module Bundler end def valid_keys - @valid_keys ||= %w(group groups git path name branch ref tag require submodules platform platforms type source) + @valid_keys ||= %w(group groups git svn path name branch ref tag require submodules platform platforms type source) end def normalize_options(name, version, opts) if name.is_a?(Symbol) raise GemfileError, %{You need to specify gem names as Strings. Use 'gem "#{name.to_s}"' instead.} end + if name =~ /\s/ + raise GemfileError, %{'#{name}' is not a valid gem name because it contains whitespace.} + end normalize_hash(opts) @@ -259,7 +280,7 @@ module Bundler opts["git"] = @git_sources[git_name].call(opts[git_name]) end - ["git", "path"].each do |type| + ["git", "path", "svn"].each do |type| if param = opts[type] if version.first && version.first =~ /^\s*=?\s*(\d[^\s]*)\s*$/ options = opts.merge("name" => name, "version" => $1) diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb index 8566f265..71d5e115 100644 --- a/lib/bundler/endpoint_specification.rb +++ b/lib/bundler/endpoint_specification.rb @@ -18,7 +18,7 @@ module Bundler end # needed for standalone, load required_paths from local gemspec - # after the gem in installed + # after the gem is installed def require_paths if @remote_specification @remote_specification.require_paths diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 8c8c8689..0512c768 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -98,6 +98,7 @@ module Bundler @remote_uri = Bundler::Source.mirror_for(remote_uri) @public_uri = @remote_uri.dup @public_uri.user, @public_uri.password = nil, nil # don't print these + # TODO: pull auth from config (like #retry_with_auth does) here Socket.do_not_reverse_lookup = true connection # create persistent connection @@ -241,6 +242,15 @@ module Bundler "#<#{self.class}:0x#{object_id} uri=#{uri}>" end + protected + def add_basic_auth(req) + if @remote_uri.user + user = CGI.unescape(@remote_uri.user) + password = @remote_uri.password ? CGI.unescape(@remote_uri.password) : nil + req.basic_auth(user, password) + end + end + private HTTP_ERRORS = [ @@ -276,14 +286,15 @@ module Bundler def request(uri) Bundler.ui.debug "HTTP GET #{uri}" req = Net::HTTP::Get.new uri.request_uri - if uri.user - user = CGI.unescape(uri.user) - password = uri.password ? CGI.unescape(uri.password) : nil - req.basic_auth(user, password) + add_basic_auth(req) + result = connection.request(uri, req) + + case result + when Net::HTTPUnauthorized, Net::HTTPForbidden + retry_with_auth { request(uri) } + else + result end - connection.request(uri, req) - rescue Net::HTTPUnauthorized, Net::HTTPForbidden - retry_with_auth { request(uri) } rescue OpenSSL::SSL::SSLError raise CertificateFailureError.new(uri) rescue *HTTP_ERRORS => e diff --git a/lib/bundler/gem_helper.rb b/lib/bundler/gem_helper.rb index 6b7a1760..6db31901 100644 --- a/lib/bundler/gem_helper.rb +++ b/lib/bundler/gem_helper.rb @@ -44,9 +44,22 @@ module Bundler install_gem(built_gem_path) end - desc "Create tag #{version_tag} and build and push #{name}-#{version}.gem to Rubygems" - task 'release' => 'build' do - release_gem(built_gem_path) + desc "Create tag #{version_tag} and build and push #{name}-#{version}.gem to Rubygems\n" \ + "To prevent publishing in Rubygems use `gem_push=no rake release`" + task 'release' => ['build', 'release:guard_clean', + 'release:source_control_push', 'release:rubygem_push'] do + end + + task 'release:guard_clean' do + guard_clean + end + + task 'release:source_control_push' do + tag_version { git_push } unless already_tagged? + end + + task 'release:rubygem_push' do + rubygem_push(built_gem_path) if gem_push? end GemHelper.instance = self @@ -70,13 +83,6 @@ module Bundler Bundler.ui.confirm "#{name} (#{version}) installed." end - def release_gem(built_gem_path=nil) - guard_clean - built_gem_path ||= build_gem - tag_version { git_push } unless already_tagged? - rubygem_push(built_gem_path) if gem_push? - end - protected def rubygem_push(path) if Pathname.new("~/.gem/credentials").expand_path.exist? diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb index 2040604b..a3bb5149 100644 --- a/lib/bundler/gem_helpers.rb +++ b/lib/bundler/gem_helpers.rb @@ -5,6 +5,7 @@ module Bundler GENERICS = [ [Gem::Platform.new('java'), Gem::Platform.new('java')], [Gem::Platform.new('mswin32'), Gem::Platform.new('mswin32')], + [Gem::Platform.new('mswin64'), Gem::Platform.new('mswin64')], [Gem::Platform.new('x64-mingw32'), Gem::Platform.new('x64-mingw32')], [Gem::Platform.new('x86_64-mingw32'), Gem::Platform.new('x64-mingw32')], [Gem::Platform.new('mingw32'), Gem::Platform.new('x86-mingw32')] diff --git a/lib/bundler/graph.rb b/lib/bundler/graph.rb index 92f538b9..0b5c5723 100644 --- a/lib/bundler/graph.rb +++ b/lib/bundler/graph.rb @@ -3,12 +3,13 @@ module Bundler class Graph GRAPH_NAME = :Gemfile - def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png") + def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png", without = []) @env = env @output_file = output_file @show_version = show_version @show_requirements = show_requirements @output_format = output_format + @without_groups = without.map(&:to_sym) @groups = [] @relations = Hash.new {|h, k| h[k] = Set.new} @@ -53,6 +54,8 @@ module Bundler relations = Hash.new {|h, k| h[k] = Set.new} @env.current_dependencies.each do |dependency| dependency.groups.each do |group| + next if @without_groups.include?(group) + relations[group.to_s].add(dependency) @relations[group.to_s].add(dependency.name) diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index 104bf444..9d2966b7 100644 --- a/lib/bundler/index.rb +++ b/lib/bundler/index.rb @@ -16,7 +16,7 @@ module Bundler def initialize @sources = [] @cache = {} - @specs = Hash.new { |h,k| h[k] = [] } + @specs = Hash.new { |h,k| h[k] = Hash.new } @all_specs = Hash.new { |h,k| h[k] = [] } end @@ -24,11 +24,11 @@ module Bundler super @sources = @sources.dup @cache = {} - @specs = Hash.new { |h,k| h[k] = [] } + @specs = Hash.new { |h,k| h[k] = Hash.new } @all_specs = Hash.new { |h,k| h[k] = [] } - o.specs.each do |name, array| - @specs[name] = array.dup + o.specs.each do |name, hash| + @specs[name] = hash.dup end o.all_specs.each do |name, array| @all_specs[name] = array.dup @@ -88,19 +88,14 @@ module Bundler alias [] search def <<(spec) - arr = specs_by_name(spec.name) + @specs[spec.name]["#{spec.version}-#{spec.platform}"] = spec - arr.delete_if do |s| - same_version?(s.version, spec.version) && s.platform == spec.platform - end - - arr << spec spec end def each(&blk) - specs.values.each do |specs| - specs.each(&blk) + specs.values.each do |spec_sets| + spec_sets.values.each(&blk) end end @@ -119,9 +114,9 @@ module Bundler if (dupes = search_by_spec(s)) && dupes.any? @all_specs[s.name] = [s] + dupes next unless override_dupes - @specs[s.name] -= dupes + self << s end - @specs[s.name] << s + self << s end self end @@ -151,7 +146,7 @@ module Bundler private def specs_by_name(name) - @specs[name] + @specs[name].values end def search_by_dependency(dependency, base = nil) @@ -178,9 +173,8 @@ module Bundler end def search_by_spec(spec) - specs_by_name(spec.name).select do |s| - same_version?(s.version, spec.version) && Gem::Platform.new(s.platform) == Gem::Platform.new(spec.platform) - end + spec = @specs[spec.name]["#{spec.version}-#{spec.platform}"] + spec ? [spec] : [] end if RUBY_VERSION < '1.9' diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index ed743d40..403a422a 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -1,6 +1,6 @@ require 'erb' require 'rubygems/dependency_installer' -require 'bundler/parallel_workers' +require 'bundler/worker' module Bundler class Installer < Environment @@ -84,7 +84,7 @@ module Bundler # that said, it's a rare situation (other than rake), and parallel # installation is just SO MUCH FASTER. so we let people opt in. jobs = [Bundler.settings[:jobs].to_i-1, 1].max - if jobs > 1 && can_install_parallely? + if jobs > 1 && can_install_in_parallel? install_in_parallel jobs, options[:standalone] else install_sequentially options[:standalone] @@ -191,7 +191,7 @@ module Bundler private - def can_install_parallely? + def can_install_in_parallel? min_rubygems = "2.0.7" if Bundler.current_ruby.mri? || Bundler.rubygems.provides?(">= #{min_rubygems}") true @@ -251,7 +251,7 @@ module Bundler file.puts "ruby_version = RbConfig::CONFIG[\"ruby_version\"]" file.puts "path = File.expand_path('..', __FILE__)" paths.each do |path| - file.puts %{$:.unshift File.expand_path("\#{path}/#{path}")} + file.puts %{$:.unshift "\#{path}/#{path}"} end end end @@ -274,9 +274,9 @@ module Bundler remains[spec.name] = true end - worker_pool = ParallelWorkers.worker_pool size, lambda { |name, worker| + worker_pool = Worker.new size, lambda { |name, worker_num| spec = name2spec[name] - message = install_gem_from_spec spec, standalone, worker + message = install_gem_from_spec spec, standalone, worker_num { :name => spec.name, :post_install => message } } diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 32eb43a4..0511f0fc 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -19,6 +19,7 @@ module Bundler GIT = "GIT" GEM = "GEM" PATH = "PATH" + SVN = "SVN" SPECS = " specs:" OPTIONS = /^ ([a-z]+): (.*)$/i @@ -54,12 +55,13 @@ module Bundler TYPES = { "GIT" => Bundler::Source::Git, "GEM" => Bundler::Source::Rubygems, - "PATH" => Bundler::Source::Path + "PATH" => Bundler::Source::Path, + "SVN" => Bundler::Source::SVN } def parse_source(line) case line - when GIT, GEM, PATH + when GIT, GEM, PATH, SVN @current_source = nil @opts, @type = {}, line when SPECS @@ -67,10 +69,10 @@ module Bundler when "PATH" @current_source = TYPES[@type].from_lock(@opts) @sources << @current_source - when "GIT" + when "GIT", "SVN" @current_source = TYPES[@type].from_lock(@opts) - # Strip out duplicate GIT sections - if @type == "GIT" && @sources.include?(@current_source) + # Strip out duplicate GIT / SVN sections + if @sources.include?(@current_source) @current_source = @sources.find { |s| s == @current_source } else @sources << @current_source diff --git a/lib/bundler/parallel_workers.rb b/lib/bundler/parallel_workers.rb index b28f6304..b95b744b 100644 --- a/lib/bundler/parallel_workers.rb +++ b/lib/bundler/parallel_workers.rb @@ -4,15 +4,10 @@ require "bundler/parallel_workers/worker" module Bundler module ParallelWorkers - autoload :UnixWorker, "bundler/parallel_workers/unix_worker" autoload :ThreadWorker, "bundler/parallel_workers/thread_worker" def self.worker_pool(size, job) - if Bundler.current_ruby.mswin? || Bundler.current_ruby.jruby? || Bundler.current_ruby.rbx? - ThreadWorker.new(size, job) - else - UnixWorker.new(size, job) - end + ThreadWorker.new(size, job) end end end diff --git a/lib/bundler/parallel_workers/thread_worker.rb b/lib/bundler/parallel_workers/thread_worker.rb deleted file mode 100644 index ef2c9ecd..00000000 --- a/lib/bundler/parallel_workers/thread_worker.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Bundler - module ParallelWorkers - class ThreadWorker < Worker - - private - - # On platforms where fork is not available - # use Threads for parallely downloading gems - # - # @param size [Integer] Size of thread worker pool - # @param func [Proc] Job to be run inside thread worker pool - def prepare_workers(size, func) - @threads = size.times.map do |i| - Thread.start do - loop do - obj = @request_queue.deq - break if obj.equal? POISON - begin - @response_queue.enq func.call(obj, i) - rescue Exception => e - @response_queue.enq(WrappedException.new(e)) - end - end - end - end - end - - end - end -end diff --git a/lib/bundler/parallel_workers/unix_worker.rb b/lib/bundler/parallel_workers/unix_worker.rb deleted file mode 100644 index 94a07bf9..00000000 --- a/lib/bundler/parallel_workers/unix_worker.rb +++ /dev/null @@ -1,101 +0,0 @@ -module Bundler - module ParallelWorkers - # UnixWorker is used only on platforms where fork is available. The way - # this code works is, it forks a preconfigured number of workers and then - # It starts preconfigured number of threads that write to the connected pipe. - class UnixWorker < Worker - - class JobHandler < Struct.new(:pid, :io_r, :io_w) - def work(obj) - Marshal.dump obj, io_w - Marshal.load io_r - rescue IOError, Errno::EPIPE - nil - end - end - - def initialize(size, job) - # Close the persistent connections for the main thread before forking - Net::HTTP::Persistent.new('bundler', :ENV).shutdown - super - end - - private - - # Start forked workers for downloading gems. This version of worker - # is only used on platforms where fork is available. - # - # @param size [Integer] Size of worker pool - # @param func [Proc] Job that should be executed in the worker - def prepare_workers(size, func) - @workers = size.times.map do |num| - child_read, parent_write = IO.pipe - parent_read, child_write = IO.pipe - - pid = Process.fork do - begin - parent_read.close - parent_write.close - - while !child_read.eof? - obj = Marshal.load child_read - Marshal.dump func.call(obj, num), child_write - end - rescue Exception => e - begin - Marshal.dump WrappedException.new(e), child_write - rescue Errno::EPIPE - nil - end - ensure - child_read.close - child_write.close - end - end - - child_read.close - child_write.close - JobHandler.new pid, parent_read, parent_write - end - end - - # Start the threads whose job is basically to wait for incoming messages - # on request queue and write that message to the connected pipe. Also retrieve - # messages from child worker via connected pipe and write the message to response queue - # - # @param size [Integer] Number of threads to be started - def prepare_threads(size) - @threads = size.times.map do |i| - Thread.start do - worker = @workers[i] - loop do - obj = @request_queue.deq - break if obj.equal? POISON - @response_queue.enq worker.work(obj) - end - end - end - end - - # Kill the forked workers by sending SIGINT to them - def stop_workers - @workers.each do |worker| - worker.io_r.close unless worker.io_r.closed? - worker.io_w.close unless worker.io_w.closed? - begin - Process.kill :INT, worker.pid - rescue Errno::ESRCH - nil - end - end - @workers.each do |worker| - begin - Process.waitpid worker.pid - rescue Errno::ECHILD - nil - end - end - end - end - end -end diff --git a/lib/bundler/parallel_workers/worker.rb b/lib/bundler/parallel_workers/worker.rb deleted file mode 100644 index 0e101d1c..00000000 --- a/lib/bundler/parallel_workers/worker.rb +++ /dev/null @@ -1,69 +0,0 @@ -module Bundler - module ParallelWorkers - class Worker - POISON = Object.new - - class WrappedException < StandardError - attr_reader :exception - def initialize(exn) - @exception = exn - end - end - - # Creates a worker pool of specified size - # - # @param size [Integer] Size of pool - # @param func [Proc] job to run in inside the worker pool - def initialize(size, func) - @request_queue = Queue.new - @response_queue = Queue.new - prepare_workers size, func - prepare_threads size - trap("INT") { @threads.each {|i| i.exit }; stop_workers; exit 1 } - end - - # Enqueue a request to be executed in the worker pool - # - # @param obj [String] mostly it is name of spec that should be downloaded - def enq(obj) - @request_queue.enq obj - end - - # Retrieves results of job function being executed in worker pool - def deq - result = @response_queue.deq - if result.is_a?(WrappedException) - raise result.exception - end - result - end - - # Stop the forked workers and started threads - def stop - stop_threads - stop_workers - end - - private - # Stop the worker threads by sending a poison object down the request queue - # so as worker threads after retrieving it, shut themselves down - def stop_threads - @threads.each do - @request_queue.enq POISON - end - @threads.each do |thread| - thread.join - end - end - - # To be overridden by child classes - def prepare_threads(size) - end - - # To be overridden by child classes - def stop_workers - end - - end - end -end diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb index 862dd35a..da6ebae0 100644 --- a/lib/bundler/ruby_version.rb +++ b/lib/bundler/ruby_version.rb @@ -38,7 +38,7 @@ module Bundler patchlevel == other.patchlevel end - # Returns a tuple of thsee things: + # Returns a tuple of these things: # [diff, this, other] # The priority of attributes are # 1. engine diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index dccb6fb2..8b8a1fe6 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -64,9 +64,12 @@ module Gem @groups ||= [] end - def git_version - return unless loaded_from && source.is_a?(Bundler::Source::Git) - " #{source.revision[0..6]}" + def scm_version + return unless loaded_from + case source + when Bundler::Source::Git then " #{source.revision[0..6]}" + when Bundler::Source::SVN then " #{source.revision}" + end end def to_gemfile(path = nil) @@ -147,6 +150,7 @@ module Gem class Platform JAVA = Gem::Platform.new('java') unless defined?(JAVA) MSWIN = Gem::Platform.new('mswin32') unless defined?(MSWIN) + MSWIN64 = Gem::Platform.new('mswin64') unless defined?(MSWIN64) MINGW = Gem::Platform.new('x86-mingw32') unless defined?(MINGW) X64_MINGW = Gem::Platform.new('x64-mingw32') unless defined?(X64_MINGW) diff --git a/lib/bundler/s3_fetcher.rb b/lib/bundler/s3_fetcher.rb new file mode 100644 index 00000000..38026436 --- /dev/null +++ b/lib/bundler/s3_fetcher.rb @@ -0,0 +1,43 @@ +require 'base64' +require 'openssl' + +module Bundler + class S3Fetcher < Fetcher + + def fetch(uri, counter = 0) + super(sign(uri), counter) + end + + # Instead of taking a dependency on aws-sdk, use a method modeled on + # the signing method in https://github.com/rubygems/rubygems/pull/856 + def sign(uri, expiration = default_expiration) + uri = uri.dup + unless uri.user && uri.password + raise AuthenticationRequiredError.new("credentials needed in s3 source, like s3://key:secret@bucket-name/") + end + + payload = "GET\n\n\n#{expiration}\n/#{uri.host}#{uri.path}" + digest = OpenSSL::HMAC.digest('sha1', uri.password, payload) + # URI.escape is deprecated, and there isn't yet a replacement that does quite what we want + signature = Base64.encode64(digest).gsub("\n", '').gsub(/[\+\/=]/) { |c| BASE64_URI_TRANSLATE[c] } + uri.query = [uri.query, "AWSAccessKeyId=#{uri.user}&Expires=#{expiration}&Signature=#{signature}"].compact.join('&') + uri.user = nil + uri.password = nil + uri.scheme = "https" + uri.host = [uri.host, "s3.amazonaws.com"].join('.') + + URI.parse(uri.to_s) + end + + def default_expiration + (Time.now + 3600).to_i # one hour from now + end + + BASE64_URI_TRANSLATE = { '+' => '%2B', '/' => '%2F', '=' => '%3D' }.freeze + protected + # The s3 fetcher does not use the username and password for basic auth, + # so this is a no-op + def add_basic_auth(req) + end + end +end
\ No newline at end of file diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index 04643b49..d37a6046 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -3,6 +3,7 @@ module Bundler autoload :Rubygems, 'bundler/source/rubygems' autoload :Path, 'bundler/source/path' autoload :Git, 'bundler/source/git' + autoload :SVN, 'bundler/source/svn' def self.mirror_for(uri) uri = URI(uri.to_s) unless uri.is_a?(URI) diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index 1f720f53..a044f58a 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -46,6 +46,10 @@ module Bundler out << " specs:\n" end + def hash + [self.class, uri, ref, branch, name, version, submodules].hash + end + def eql?(o) o.is_a?(Git) && uri == o.uri && diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index c80ac61f..297c7e33 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -54,7 +54,7 @@ module Bundler end def hash - self.class.hash + [self.class, expand(path), version].hash end def eql?(o) diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 635bb993..239a5194 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -6,6 +6,7 @@ module Bundler class Source class Rubygems < Source API_REQUEST_LIMIT = 100 # threshold for switching back to the modern index instead of fetching every spec + S3_SCHEME = 's3' attr_reader :remotes, :caches @@ -93,49 +94,50 @@ module Bundler spec.__swap__(s) end - path = cached_gem(spec) - if Bundler.requires_sudo? - install_path = Bundler.tmp(spec.full_name) - bin_path = install_path.join("bin") - else - install_path = Bundler.rubygems.gem_dir - bin_path = Bundler.system_bindir - end + unless Bundler.settings[:no_install] + path = cached_gem(spec) + if Bundler.requires_sudo? + install_path = Bundler.tmp(spec.full_name) + bin_path = install_path.join("bin") + else + install_path = Bundler.rubygems.gem_dir + bin_path = Bundler.system_bindir + end - installed_spec = nil - Bundler.rubygems.preserve_paths do - installed_spec = Bundler::GemInstaller.new(path, - :install_dir => install_path.to_s, - :bin_dir => bin_path.to_s, - :ignore_dependencies => true, - :wrappers => true, - :env_shebang => true - ).install - end + installed_spec = nil + Bundler.rubygems.preserve_paths do + installed_spec = Bundler::GemInstaller.new(path, + :install_dir => install_path.to_s, + :bin_dir => bin_path.to_s, + :ignore_dependencies => true, + :wrappers => true, + :env_shebang => true + ).install + end - # SUDO HAX - if Bundler.requires_sudo? - Bundler.rubygems.repository_subdirectories.each do |name| - src = File.join(install_path, name, "*") - dst = File.join(Bundler.rubygems.gem_dir, name) - if name == "extensions" && Dir.glob(src).any? - src = File.join(src, "*/*") - ext_src = Dir.glob(src).first - ext_src.gsub!(src[0..-6], '') - dst = File.dirname(File.join(dst, ext_src)) + # SUDO HAX + if Bundler.requires_sudo? + Bundler.rubygems.repository_subdirectories.each do |name| + src = File.join(install_path, name, "*") + dst = File.join(Bundler.rubygems.gem_dir, name) + if name == "extensions" && Dir.glob(src).any? + src = File.join(src, "*/*") + ext_src = Dir.glob(src).first + ext_src.gsub!(src[0..-6], '') + dst = File.dirname(File.join(dst, ext_src)) + end + Bundler.mkdir_p dst + Bundler.sudo "cp -R #{src} #{dst}" if Dir[src].any? end - Bundler.mkdir_p dst - Bundler.sudo "cp -R #{src} #{dst}" if Dir[src].any? - end - spec.executables.each do |exe| - Bundler.mkdir_p Bundler.system_bindir - Bundler.sudo "cp -R #{install_path}/bin/#{exe} #{Bundler.system_bindir}/" + spec.executables.each do |exe| + Bundler.mkdir_p Bundler.system_bindir + Bundler.sudo "cp -R #{install_path}/bin/#{exe} #{Bundler.system_bindir}/" + end end + installed_spec.loaded_from = loaded_from(spec) end - - spec.loaded_from = "#{Bundler.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec" - installed_spec.loaded_from = spec.loaded_from + spec.loaded_from = loaded_from(spec) ["Installing #{version_message(spec)}", spec.post_install_message] ensure if install_path && Bundler.requires_sudo? @@ -186,6 +188,17 @@ module Bundler end end + def fetchers + @fetchers ||= remotes.map do |uri| + case uri.scheme + when S3_SCHEME + Bundler::S3Fetcher.new(uri) + else + Bundler::Fetcher.new(uri) + end + end + end + protected def source_uris_for_spec(spec) @@ -194,6 +207,10 @@ module Bundler private + def loaded_from(spec) + "#{Bundler.rubygems.gem_dir}/specifications/#{spec.full_name}.gemspec" + end + def cached_gem(spec) cached_gem = cached_path(spec) unless cached_gem @@ -270,12 +287,6 @@ module Bundler idx end - def fetchers - @fetchers ||= remotes.map do |url| - Bundler::Fetcher.new(url) - end - end - def api_fetchers fetchers.select{|f| f.use_api } end diff --git a/lib/bundler/source/svn.rb b/lib/bundler/source/svn.rb new file mode 100644 index 00000000..a7a42ecf --- /dev/null +++ b/lib/bundler/source/svn.rb @@ -0,0 +1,259 @@ +require 'fileutils' +require 'uri' +require 'digest/sha1' + +module Bundler + class Source + + class SVN < Path + autoload :SVNProxy, 'bundler/source/svn/svn_proxy' + + attr_reader :uri, :ref, :options + + def initialize(options) + @options = options + @glob = options["glob"] || DEFAULT_GLOB + + @allow_cached = false + @allow_remote = false + + # Stringify options that could be set as symbols + %w(ref revision).each{|k| options[k] = options[k].to_s if options[k] } + + @uri = options["uri"] + @ref = options["ref"] || 'HEAD' + @name = options["name"] + @version = options["version"] + + @copied = false + @local = false + end + + def self.from_lock(options) + new(options.merge("uri" => options.delete("remote"))) + end + + def to_lock + out = "SVN\n" + out << " remote: #{@uri}\n" + out << " revision: #{revision}\n" + out << " ref: #{ref}\n" + out << " glob: #{@glob}\n" unless @glob == DEFAULT_GLOB + out << " specs:\n" + end + + def hash + [self.class, uri, ref, name, version].hash + end + + def eql?(o) + o.is_a?(SVN) && + uri == o.uri && + ref == o.ref && + name == o.name && + version == o.version + end + + alias == eql? + + def to_s + at = if local? + path + elsif options["ref"] + options["ref"] + else + ref + end + "#{uri} (at #{at})" + end + + def name + File.basename(@uri, '.svn') + end + + # This is the path which is going to contain a specific + # checkout of the svn repository. When using local svn + # repos, this is set to the local repo. + def install_path + @install_path ||= begin + svn_scope = "#{base_name}-#{revision}" + + if Bundler.requires_sudo? + Bundler.user_bundle_path.join(Bundler.ruby_scope).join(svn_scope) + else + Bundler.install_path.join(svn_scope) + end + end + end + + alias :path :install_path + + def extension_dir_name + "#{base_name}-#{revision}" + end + + def unlock! + svn_proxy.revision = nil + @unlocked = true + end + + def local_override!(path) + return false if local? + + path = Pathname.new(path) + path = path.expand_path(Bundler.root) unless path.relative? + + unless path.exist? + raise SVNError, "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 svn proxy without the cached revision + # so the Gemfile.lock always picks up the new revision. + @svn_proxy = SVNProxy.new(path, uri, ref) + true + end + + # TODO: actually cache svn specs + def specs(*) + if has_app_cache? && !local? + set_local!(app_cache_path) + end + + if requires_checkout? && !@copied + svn_proxy.checkout + svn_proxy.copy_to(install_path) + serialize_gemspecs_in(install_path) + @copied = true + end + + local_specs + end + + def install(spec) + debug = nil + if requires_checkout? && !@copied + debug = " * Checking out revision: #{ref}" + svn_proxy.copy_to(install_path) + serialize_gemspecs_in(install_path) + @copied = true + end + generate_bin(spec) + if requires_checkout? && spec.post_install_message + Installer.post_install_messages[spec.name] = spec.post_install_message + end + ["Using #{version_message(spec)} from #{to_s}", nil, debug] + end + + def cache(spec, custom_path = nil) + app_cache_path = app_cache_path(custom_path) + return unless Bundler.settings[:cache_all] + return if path == app_cache_path + cached! + FileUtils.rm_rf(app_cache_path) + svn_proxy.checkout if requires_checkout? + svn_proxy.copy_to(app_cache_path) + serialize_gemspecs_in(app_cache_path) + end + + def load_spec_files + super + rescue PathError => e + Bundler.ui.trace e + raise SVNError, "#{to_s} is not yet checked out. Run `bundle install` first." + end + + # This is the path which is going to contain a cache + # of the svn repository. When using the same svn repository + # across different projects, this cache will be shared. + # When using local svn repos, this is set to the local repo. + def cache_path + @cache_path ||= begin + svn_scope = "#{base_name}-#{uri_hash}" + + if Bundler.requires_sudo? + Bundler.user_bundle_path.join("cache/svn", svn_scope) + else + Bundler.cache.join("svn", svn_scope) + end + end + end + + def app_cache_dirname + "#{base_name}-#{(cached_revision || revision)}" + end + + def revision + svn_proxy.revision + end + + def allow_svn_ops? + @allow_remote || @allow_cached + end + + private + + def serialize_gemspecs_in(destination) + expanded_path = destination.expand_path(Bundler.root) + Dir["#{expanded_path}/#{@glob}"].each do |spec_path| + # Evaluate gemspecs and cache the result. Gemspecs + # in svn might require svn or other dependencies. + # The gemspecs we cache should already be evaluated. + spec = Bundler.load_gemspec(spec_path) + next unless spec + File.open(spec_path, 'wb') {|file| file.write(spec.to_ruby) } + end + end + + def set_local!(path) + @local = true + @local_specs = @svn_proxy = nil + @cache_path = @install_path = path + end + + def has_app_cache? + cached_revision && super + end + + def local? + @local + end + + def requires_checkout? + allow_svn_ops? && !local? + end + + def base_name + File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)?(//\w*/)?(\w*/)*},''),".svn") + end + + def uri_hash + if uri =~ %r{^\w+://(\w+@)?} + # Downcase the domain component of the URI + # and strip off a trailing slash, if one is present + input = URI.parse(uri).normalize.to_s.sub(%r{/$},'') + else + # If there is no URI scheme, assume it is an ssh/svn URI + input = uri + end + Digest::SHA1.hexdigest(input) + end + + def cached_revision + options["revision"] + end + + def cached? + cache_path.exist? + end + + def svn_proxy + @svn_proxy ||= SVNProxy.new(cache_path, uri, ref, cached_revision, self) + end + + end + + end +end diff --git a/lib/bundler/source/svn/svn_proxy.rb b/lib/bundler/source/svn/svn_proxy.rb new file mode 100644 index 00000000..c67a9372 --- /dev/null +++ b/lib/bundler/source/svn/svn_proxy.rb @@ -0,0 +1,110 @@ +module Bundler + class Source + class SVN < Path + + class SVNNotInstalledError < SVNError + def initialize + msg = "You need to install svn to be able to use gems from svn repositories. " + msg << "For help installing svn, please refer to SVNook's tutorial at http://svnbook.red-bean.com/en/1.7/svn.intro.install.html" + super msg + end + end + + class SVNNotAllowedError < SVNError + def initialize(command) + msg = "Bundler is trying to run a `svn #{command}` at runtime. You probably need to run `bundle install`. However, " + msg << "this error message could probably be more useful. Please submit a ticket at http://github.com/bundler/bundler/issues " + msg << "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}" + super msg + end + end + + class SVNCommandError < SVNError + def initialize(command, path = nil) + msg = "SVN error: command `svn #{command}` in directory #{Dir.pwd} has failed." + msg << "\nIf this error persists you could try removing the cache directory '#{path}'" if path && path.exist? + super msg + end + end + + # The SVNProxy is responsible to interact with svn repositories. + # All actions required by the SVN source is encapsulated in this + # object. + class SVNProxy + attr_accessor :path, :uri, :ref + attr_writer :revision + + def initialize(path, uri, ref, revision = nil, svn = nil) + @path = path + @uri = uri + @ref = ref + @revision = revision + @svn = svn + raise SVNNotInstalledError.new if allow? && !Bundler.svn_present? + end + + def revision + @revision ||= svn("info --revision #{ref} #{uri_escaped} | grep \"Revision\" | awk '{print $2}'").strip + end + + def checkout + if path.exist? + Bundler.ui.confirm "Updating #{uri}" + in_path do + svn_retry %|update --force --quiet --revision #{revision}| + end + else + Bundler.ui.info "Fetching #{uri}" + FileUtils.mkdir_p(path.dirname) + svn_retry %|checkout --revision #{revision} #{uri_escaped} "#{path}"| + end + end + + def copy_to(destination) + FileUtils.mkdir_p(destination.dirname) + FileUtils.rm_rf(destination) + FileUtils.cp_r(path, destination) + File.chmod((0777 & ~File.umask), destination) + end + + private + + def svn_retry(command) + Bundler::Retry.new("svn #{command}", SVNNotAllowedError).attempts do + svn(command) + end + end + + def svn(command, check_errors=true) + raise SVNNotAllowedError.new(command) unless allow? + out = %x{svn #{command}} + raise SVNCommandError.new(command, path) if check_errors && !$?.success? + out + end + + # Escape the URI for svn 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? + @svn ? @svn.allow_svn_ops? : true + end + + def in_path(&blk) + checkout unless path.exist? + SharedHelpers.chdir(path, &blk) + end + end + + end + end +end diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 4f537c4a..235f75a2 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -2,11 +2,13 @@ module Bundler class SourceList attr_reader :path_sources, :git_sources, + :svn_sources, :rubygems_sources def initialize @path_sources = [] @git_sources = [] + @svn_sources = [] @rubygems_aggregate = Source::Rubygems.new @rubygems_sources = [@rubygems_aggregate] end @@ -19,6 +21,10 @@ module Bundler add_source_to_list Source::Git.new(options), git_sources end + def add_svn_source(options = {}) + add_source_to_list Source::SVN.new(options), svn_sources + end + def add_rubygems_source(options = {}) add_source_to_list Source::Rubygems.new(options), @rubygems_sources end @@ -29,7 +35,7 @@ module Bundler end def all_sources - path_sources + git_sources + rubygems_sources + path_sources + git_sources + svn_sources + rubygems_sources end def get(source) @@ -37,11 +43,11 @@ module Bundler end def lock_sources - (path_sources + git_sources) << combine_rubygems_sources + (path_sources + git_sources + svn_sources) << combine_rubygems_sources end def replace_sources!(replacement_sources) - [path_sources, git_sources, rubygems_sources].each do |source_list| + [path_sources, git_sources, svn_sources, rubygems_sources].each do |source_list| source_list.map! do |source| replacement_sources.find { |s| s == source } || source end @@ -66,6 +72,7 @@ module Bundler def source_list_for(source) case source when Source::Git then git_sources + when Source::SVN then svn_sources when Source::Path then path_sources when Source::Rubygems then rubygems_sources else raise ArgumentError, "Invalid source: #{source.inspect}" diff --git a/lib/bundler/templates/newgem/LICENSE.txt.tt b/lib/bundler/templates/newgem/LICENSE.txt.tt index a9f52e6b..de8e4d71 100644 --- a/lib/bundler/templates/newgem/LICENSE.txt.tt +++ b/lib/bundler/templates/newgem/LICENSE.txt.tt @@ -1,4 +1,4 @@ -Copyright (c) <%=Time.now.year%> <%=config[:author]%> +Copyright <%=Time.now.year%> <%=config[:author]%> MIT License diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index f4fc3c6e..d4848741 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -18,6 +18,8 @@ task :default => :spec <% if config[:ext] -%> require "rake/extensiontask" +task :build => :compile + Rake::ExtensionTask.new("<%=config[:underscored_name]%>") do |ext| ext.lib_dir = "lib/<%=config[:namespaced_path]%>" end diff --git a/lib/bundler/templates/newgem/consolerc.tt b/lib/bundler/templates/newgem/consolerc.tt new file mode 100644 index 00000000..7ed81db6 --- /dev/null +++ b/lib/bundler/templates/newgem/consolerc.tt @@ -0,0 +1,3 @@ +# This file is automatically loaded when `bundle console` is run. You can add +# fixtures and/or initialization code here to make experimenting with your gem +# easier. diff --git a/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt b/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt index 3fd81816..8cfc828f 100644 --- a/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt +++ b/lib/bundler/templates/newgem/ext/newgem/extconf.rb.tt @@ -1,3 +1,3 @@ require "mkmf" -create_makefile(<%=config[:underscored_name].inspect%>) +create_makefile(<%= config[:makefile_path].inspect %>) diff --git a/lib/bundler/templates/newgem/gitignore.tt b/lib/bundler/templates/newgem/gitignore.tt index ae3fdc29..ebff7ac5 100644 --- a/lib/bundler/templates/newgem/gitignore.tt +++ b/lib/bundler/templates/newgem/gitignore.tt @@ -7,8 +7,10 @@ /pkg/ /spec/reports/ /tmp/ +<%- if config[:ext] -%> *.bundle *.so *.o *.a mkmf.log +<%- end -%> diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index 74d253ec..342f4fe4 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -8,12 +8,18 @@ Gem::Specification.new do |spec| spec.version = <%=config[:constant_name]%>::VERSION spec.authors = [<%=config[:author].inspect%>] spec.email = [<%=config[:email].inspect%>] -<% if config[:ext] -%> - spec.extensions = ["ext/<%=config[:underscored_name]%>/extconf.rb"] + +<%- if ::Gem::Requirement.new(">= 2.0").satisfied_by? ::Gem::Version.new(::Gem::VERSION) -%> + spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com' to prevent pushes to rubygems.org, or delete to allow pushes to any server." + spec.required_rubygems_version = ">= 2.0" <% end -%> - spec.summary = %q{TODO: Write a short summary. Required.} - spec.description = %q{TODO: Write a longer description. Optional.} - spec.homepage = "" + +<%- if config[:ext] -%> + spec.extensions = ["ext/<%=config[:underscored_name]%>/extconf.rb"] +<%- end -%> + spec.summary = %q{TODO: Write a short summary, because Rubygems requires one.} + spec.description = %q{TODO: Write a longer description or delete this line.} + spec.homepage = "TODO: Put your gem's website or public repo URL here." spec.license = "MIT" spec.files = `git ls-files -z`.split("\x0") @@ -23,10 +29,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", "~> <%= Bundler::VERSION.split(".")[0..1].join(".") %>" spec.add_development_dependency "rake", "~> 10.0" -<% if config[:ext] -%> +<%- if config[:ext] -%> spec.add_development_dependency "rake-compiler" -<% end -%> -<% if config[:test] -%> +<%- end -%> +<%- if config[:test] -%> spec.add_development_dependency "<%=config[:test]%>" -<% end -%> +<%- end -%> end diff --git a/lib/bundler/worker.rb b/lib/bundler/worker.rb new file mode 100644 index 00000000..49615883 --- /dev/null +++ b/lib/bundler/worker.rb @@ -0,0 +1,73 @@ +require 'thread' + +module Bundler + class Worker + POISON = Object.new + + class WrappedException < StandardError + attr_reader :exception + def initialize(exn) + @exception = exn + end + end + + # Creates a worker pool of specified size + # + # @param size [Integer] Size of pool + # @param func [Proc] job to run in inside the worker pool + def initialize(size, func) + @request_queue = Queue.new + @response_queue = Queue.new + @func = func + @threads = size.times.map { |i| Thread.start { process_queue(i) } } + trap("INT") { abort_threads } + end + + # Enqueue a request to be executed in the worker pool + # + # @param obj [String] mostly it is name of spec that should be downloaded + def enq(obj) + @request_queue.enq obj + end + + # Retrieves results of job function being executed in worker pool + def deq + result = @response_queue.deq + raise result.exception if result.is_a?(WrappedException) + result + end + + def stop + stop_threads + end + + private + + def process_queue(i) + loop do + obj = @request_queue.deq + break if obj.equal? POISON + @response_queue.enq apply_func(obj, i) + end + end + + def apply_func(obj, i) + @func.call(obj, i) + rescue Exception => e + WrappedException.new(e) + end + + # Stop the worker threads by sending a poison object down the request queue + # so as worker threads after retrieving it, shut themselves down + def stop_threads + @threads.each { @request_queue.enq POISON } + @threads.each { |thread| thread.join } + end + + def abort_threads + @threads.each {|i| i.exit } + exit 1 + end + + end +end |