summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/bundler.rb14
-rw-r--r--lib/bundler/cli.rb4
-rw-r--r--lib/bundler/cli/exec.rb58
-rw-r--r--lib/bundler/rubygems_ext.rb2
-rw-r--r--lib/bundler/rubygems_integration.rb17
-rw-r--r--lib/bundler/settings.rb2
-rw-r--r--lib/bundler/source/git.rb4
-rw-r--r--lib/bundler/source/path.rb15
-rw-r--r--spec/bundler/bundler_spec.rb43
-rw-r--r--spec/bundler/rubygems_integration_spec.rb33
-rw-r--r--spec/commands/clean_spec.rb6
-rw-r--r--spec/commands/exec_spec.rb88
-rw-r--r--spec/commands/package_spec.rb18
-rw-r--r--spec/install/gemfile/git_spec.rb41
14 files changed, 288 insertions, 57 deletions
diff --git a/lib/bundler.rb b/lib/bundler.rb
index 5cfff3d573..2958e64cee 100644
--- a/lib/bundler.rb
+++ b/lib/bundler.rb
@@ -355,18 +355,16 @@ module Bundler
# depend on "./" relative paths.
SharedHelpers.chdir(path.dirname.to_s) do
contents = path.read
- if contents[0..2] == "---" # YAML header
- spec = eval_yaml_gemspec(path, contents)
+ spec = if contents[0..2] == "---" # YAML header
+ eval_yaml_gemspec(path, contents)
else
- spec = eval_gemspec(path, contents)
+ eval_gemspec(path, contents)
end
- Bundler.rubygems.validate(spec) if spec && validate
+ return unless spec
+ spec.loaded_from = path.expand_path.to_s
+ Bundler.rubygems.validate(spec) if validate
spec
end
- rescue Gem::InvalidSpecificationException => e
- error_message = "The gemspec at #{file} is not valid. Please fix this gemspec.\n" \
- "The validation error was '#{e.message}'\n"
- raise Gem::InvalidSpecificationException.new(error_message)
end
def clear_gemspec_cache
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index 015892ea29..0bd82fd128 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -170,7 +170,11 @@ module Bundler
map "i" => "install"
def install
require "bundler/cli/install"
+ no_install = Bundler.settings[:no_install]
+ Bundler.settings[:no_install] = false
Install.new(options.dup).run
+ ensure
+ Bundler.settings[:no_install] = no_install
end
desc "update [OPTIONS]", "update the current environment"
diff --git a/lib/bundler/cli/exec.rb b/lib/bundler/cli/exec.rb
index 77e75580f5..865ac20248 100644
--- a/lib/bundler/cli/exec.rb
+++ b/lib/bundler/cli/exec.rb
@@ -18,16 +18,30 @@ module Bundler
end
def run
- ui = Bundler.ui
- raise ArgumentError if cmd.nil?
-
+ validate_cmd!
SharedHelpers.set_bundle_environment
- bin_path = Bundler.which(@cmd)
+ if bin_path = Bundler.which(cmd)
+ kernel_load(bin_path, *args) && return if ruby_shebang?(bin_path)
+ # First, try to exec directly to something in PATH
+ kernel_exec([bin_path, cmd], *args)
+ else
+ # Just exec using the given command
+ kernel_exec(cmd, *args)
+ end
+ end
+
+ private
+
+ def validate_cmd!
+ return unless cmd.nil?
+ Bundler.ui.error "bundler: exec needs a command to run"
+ exit 128
+ end
+
+ def kernel_exec(*args)
+ ui = Bundler.ui
Bundler.ui = nil
- # First, try to exec directly to something in PATH
- Kernel.exec([bin_path, @cmd], *args) if bin_path
- # Just exec using the given command
- Kernel.exec(@cmd, *args)
+ Kernel.exec(*args)
rescue Errno::EACCES, Errno::ENOEXEC
Bundler.ui = ui
Bundler.ui.error "bundler: not executable: #{cmd}"
@@ -37,10 +51,32 @@ module Bundler
Bundler.ui.error "bundler: command not found: #{cmd}"
Bundler.ui.warn "Install missing gem executables with `bundle install`"
exit 127
- rescue ArgumentError
+ end
+
+ def kernel_load(file, *args)
+ args.pop if args.last.is_a?(Hash)
+ ARGV.replace(args)
+ $0 = file
+ ui = Bundler.ui
+ Bundler.ui = nil
+ require "bundler/setup"
+ Kernel.load(file)
+ rescue SystemExit
+ raise
+ rescue Exception => e # rubocop:disable Lint/RescueException
Bundler.ui = ui
- Bundler.ui.error "bundler: exec needs a command to run"
- exit 128
+ Bundler.ui.error "bundler: failed to load command: #{cmd} (#{file})"
+ backtrace = e.backtrace.take_while {|bt| !bt.start_with?(__FILE__) }
+ abort "#{e.class}: #{e.message}\n #{backtrace.join("\n ")}"
+ end
+
+ def ruby_shebang?(file)
+ possibilities = [
+ "#!/usr/bin/env ruby\n",
+ "#!#{Gem.ruby}\n",
+ ]
+ first_line = File.open(file, "rb") {|f| f.read(possibilities.map(&:size).max) }
+ possibilities.any? {|shebang| first_line.start_with?(shebang) }
end
end
end
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb
index 22c22a2dd0..2f36c29cd9 100644
--- a/lib/bundler/rubygems_ext.rb
+++ b/lib/bundler/rubygems_ext.rb
@@ -53,7 +53,7 @@ module Gem
if method_defined?(:extension_dir)
alias_method :rg_extension_dir, :extension_dir
def extension_dir
- @extension_dir ||= if source.respond_to?(:extension_dir_name)
+ @bundler_extension_dir ||= if source.respond_to?(:extension_dir_name)
File.expand_path(File.join(extensions_dir, source.extension_dir_name))
else
rg_extension_dir
diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb
index 0f567db12a..4e5ca7d1f8 100644
--- a/lib/bundler/rubygems_integration.rb
+++ b/lib/bundler/rubygems_integration.rb
@@ -54,10 +54,23 @@ module Bundler
def validate(spec)
Bundler.ui.silence { spec.validate(false) }
+ rescue Gem::InvalidSpecificationException => e
+ error_message = "The gemspec at #{spec.loaded_from} is not valid. Please fix this gemspec.\n" \
+ "The validation error was '#{e.message}'\n"
+ raise Gem::InvalidSpecificationException.new(error_message)
rescue Errno::ENOENT
nil
end
+ def set_installed_by_version(spec, installed_by_version = Gem::VERSION)
+ return unless spec.respond_to?(:installed_by_version=)
+ spec.installed_by_version = Gem::Version.create(installed_by_version)
+ end
+
+ def spec_missing_extensions?(spec)
+ !spec.respond_to?(:missing_extensions?) || spec.missing_extensions?
+ end
+
def path(obj)
obj.to_s
end
@@ -503,9 +516,7 @@ module Bundler
# Missing summary is downgraded to a warning in later versions,
# so we set it to an empty string to prevent an exception here.
spec.summary ||= ""
- Bundler.ui.silence { spec.validate(false) }
- rescue Errno::ENOENT
- nil
+ RubygemsIntegration.instance_method(:validate).bind(self).call(spec)
end
end
diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb
index 634d9dc14a..175c9a76db 100644
--- a/lib/bundler/settings.rb
+++ b/lib/bundler/settings.rb
@@ -3,7 +3,7 @@ require "uri"
module Bundler
class Settings
- BOOL_KEYS = %w(frozen cache_all no_prune disable_local_branch_check disable_shared_gems ignore_messages gem.mit gem.coc silence_root_warning).freeze
+ BOOL_KEYS = %w(frozen cache_all no_prune disable_local_branch_check disable_shared_gems ignore_messages gem.mit gem.coc silence_root_warning no_install).freeze
NUMBER_KEYS = %w(retry timeout redirect ssl_verify_mode).freeze
DEFAULT_CONFIG = { :retry => 3, :timeout => 10, :redirect => 5 }.freeze
diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb
index 90bbd13d34..228ab61a67 100644
--- a/lib/bundler/source/git.rb
+++ b/lib/bundler/source/git.rb
@@ -222,6 +222,10 @@ module Bundler
private
+ def build_extensions(installer)
+ super if Bundler.rubygems.spec_missing_extensions?(installer.spec)
+ end
+
def serialize_gemspecs_in(destination)
expanded_path = destination.expand_path(Bundler.root)
Dir["#{expanded_path}/#{@glob}"].each do |spec_path|
diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb
index 6bfd2e88c7..9790ba5164 100644
--- a/lib/bundler/source/path.rb
+++ b/lib/bundler/source/path.rb
@@ -135,9 +135,12 @@ module Bundler
if File.directory?(expanded_path)
# We sort depth-first since `<<` will override the earlier-found specs
Dir["#{expanded_path}/#{@glob}"].sort_by {|p| -p.split(File::SEPARATOR).size }.each do |file|
- next unless spec = Bundler.load_gemspec(file, :validate)
- spec.loaded_from = file.to_s
+ next unless spec = Bundler.load_gemspec(file)
spec.source = self
+ Bundler.rubygems.set_installed_by_version(spec)
+ # Validation causes extension_dir to be calculated, which depends
+ # on #source, so we validate here instead of load_gemspec
+ Bundler.rubygems.validate(spec)
index << spec
end
@@ -194,8 +197,7 @@ module Bundler
SharedHelpers.chdir(gem_dir) do
installer = Path::Installer.new(spec, :env_shebang => false)
run_hooks(:pre_install, installer)
- installer.build_extensions unless disable_extensions
- run_hooks(:post_build, installer)
+ build_extensions(installer) unless disable_extensions
installer.generate_bin
run_hooks(:post_install, installer)
end
@@ -213,6 +215,11 @@ module Bundler
Bundler.ui.warn "The validation message from Rubygems was:\n #{e.message}"
end
+ def build_extensions(installer)
+ installer.build_extensions
+ run_hooks(:post_build, installer)
+ end
+
def run_hooks(type, installer)
hooks_meth = "#{type}_hooks"
return unless Gem.respond_to?(hooks_meth)
diff --git a/spec/bundler/bundler_spec.rb b/spec/bundler/bundler_spec.rb
index 9d4353f718..84d2922f37 100644
--- a/spec/bundler/bundler_spec.rb
+++ b/spec/bundler/bundler_spec.rb
@@ -75,32 +75,31 @@ describe Bundler do
end
end
- context "validate is true" do
- subject { Bundler.load_gemspec_uncached(app_gemspec_path, true) }
-
- context "and there are gemspec validation errors" do
- let(:ui_shell) { double(:ui_shell) }
+ it "sets loaded_from" do
+ app_gemspec_path.open("w") do |f|
+ f.puts <<-GEMSPEC
+ Gem::Specification.new do |gem|
+ gem.name = "validated"
+ end
+ GEMSPEC
+ end
- before do
- allow(Bundler::UI::Shell).to receive(:new).and_return(ui_shell)
- allow(Bundler.rubygems).to receive(:validate) {
- raise Gem::InvalidSpecificationException.new("TODO is not an author")
- }
- end
+ expect(subject.loaded_from).to eq(app_gemspec_path.expand_path.to_s)
+ end
- it "should raise a Gem::InvalidSpecificationException and produce a helpful warning message" do
- File.open(app_gemspec_path, "wb") do |file|
- file.puts <<-GEMSPEC.gsub(/^\s+/, "")
- Gem::Specification.new do |gem|
- gem.author = "TODO"
- end
- GEMSPEC
- end
+ context "validate is true" do
+ subject { Bundler.load_gemspec_uncached(app_gemspec_path, true) }
- expect { subject }.to raise_error(Gem::InvalidSpecificationException,
- "The gemspec at #{app_gemspec_path} is not valid. "\
- "Please fix this gemspec.\nThe validation error was 'TODO is not an author'\n")
+ it "validates the specification" do
+ app_gemspec_path.open("w") do |f|
+ f.puts <<-GEMSPEC
+ Gem::Specification.new do |gem|
+ gem.name = "validated"
+ end
+ GEMSPEC
end
+ expect(Bundler.rubygems).to receive(:validate).with have_attributes(:name => "validated")
+ subject
end
end
end
diff --git a/spec/bundler/rubygems_integration_spec.rb b/spec/bundler/rubygems_integration_spec.rb
index 917d673b51..fbc49c414c 100644
--- a/spec/bundler/rubygems_integration_spec.rb
+++ b/spec/bundler/rubygems_integration_spec.rb
@@ -7,16 +7,43 @@ describe Bundler::RubygemsIntegration do
end
context "#validate" do
- let(:spec) { double("spec", :summary => "") }
+ let(:spec) do
+ Gem::Specification.new do |s|
+ s.name = "to-validate"
+ s.version = "1.0.0"
+ s.loaded_from = __FILE__
+ end
+ end
+ subject { Bundler.rubygems.validate(spec) }
it "skips overly-strict gemspec validation", :rubygems => "< 1.7" do
expect(spec).to_not receive(:validate)
- Bundler.rubygems.validate(spec)
+ subject
end
it "validates with packaging mode disabled", :rubygems => "1.7" do
expect(spec).to receive(:validate).with(false)
- Bundler.rubygems.validate(spec)
+ subject
+ end
+
+ it "should set a summary to avoid an overly-strict error", :rubygems => "~> 1.7.0" do
+ spec.summary = nil
+ expect { subject }.not_to raise_error
+ expect(spec.summary).to eq("")
+ end
+
+ context "with an invalid spec" do
+ before do
+ expect(spec).to receive(:validate).with(false).
+ and_raise(Gem::InvalidSpecificationException.new("TODO is not an author"))
+ end
+
+ it "should raise a Gem::InvalidSpecificationException and produce a helpful warning message",
+ :rubygems => "1.7" do
+ expect { subject }.to raise_error(Gem::InvalidSpecificationException,
+ "The gemspec at #{__FILE__} is not valid. "\
+ "Please fix this gemspec.\nThe validation error was 'TODO is not an author'\n")
+ end
end
end
diff --git a/spec/commands/clean_spec.rb b/spec/commands/clean_spec.rb
index 95035555d4..37f569b267 100644
--- a/spec/commands/clean_spec.rb
+++ b/spec/commands/clean_spec.rb
@@ -660,9 +660,11 @@ describe "bundle clean" do
gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}"
G
- bundle "install --path vendor/bundle"
- bundle :clean
+ bundle! "install --path vendor/bundle"
+ expect(vendored_gems("bundler/gems/extensions")).to exist
+ expect(vendored_gems("bundler/gems/very_simple_git_binary-1.0-#{revision[0..11]}")).to exist
+ bundle! :clean
expect(out).to eq("")
expect(vendored_gems("bundler/gems/extensions")).to exist
diff --git a/spec/commands/exec_spec.rb b/spec/commands/exec_spec.rb
index 16e853bb1d..be2f8aa2a5 100644
--- a/spec/commands/exec_spec.rb
+++ b/spec/commands/exec_spec.rb
@@ -363,4 +363,92 @@ describe "bundle exec" do
expect(out).to match("true")
end
end
+
+ context "`load`ing a ruby file instead of `exec`ing" do
+ let(:path) { bundled_app("ruby_executable") }
+ let(:shebang) { "#!/usr/bin/env ruby" }
+ let(:executable) { <<-RUBY.gsub(/^ */, "").strip }
+ #{shebang}
+
+ require "rack"
+ puts "EXEC: \#{caller.grep(/load/).empty? ? 'exec' : 'load'}"
+ puts "ARGS: \#{$0} \#{ARGV.join(' ')}"
+ puts "RACK: \#{RACK}"
+ RUBY
+
+ before do
+ path.open("w") {|f| f << executable }
+ path.chmod(0755)
+
+ install_gemfile <<-G
+ gem "rack"
+ G
+ end
+
+ let(:exec) { "EXEC: load" }
+ let(:args) { "ARGS: #{path} arg1 arg2" }
+ let(:rack) { "RACK: 1.0.0" }
+ let(:exit_code) { 0 }
+ let(:expected) { [exec, args, rack].join("\n") }
+ let(:expected_err) { "" }
+
+ subject { bundle "exec #{path} arg1 arg2", :expect_err => true }
+
+ shared_examples_for "it runs" do
+ it "like a normally executed executable like a normally executed executable" do
+ subject
+ expect(exitstatus).to eq(exit_code) if exitstatus
+ expect(err).to eq(expected_err)
+ expect(out).to eq(expected)
+ end
+ end
+
+ it_behaves_like "it runs"
+
+ context "the executable exits explicitly" do
+ let(:executable) { super() << "\nexit #{exit_code}\nputs 'POST_EXIT'\n" }
+
+ context "with exit 0" do
+ it_behaves_like "it runs"
+ end
+
+ context "with exit 99" do
+ let(:exit_code) { 99 }
+ it_behaves_like "it runs"
+ end
+ end
+
+ context "the executable raises" do
+ let(:executable) { super() << "\nraise 'ERROR'" }
+ let(:exit_code) { 1 }
+ let(:expected) { super() << "\nbundler: failed to load command: #{path} (#{path})" }
+ let(:expected_err) do
+ "RuntimeError: ERROR\n #{path}:7" +
+ (Bundler.current_ruby.ruby_18? ? "" : ":in `<top (required)>'")
+ end
+ it_behaves_like "it runs"
+ end
+
+ context "when the file uses the current ruby shebang" do
+ let(:shebang) { "#!#{Gem.ruby}" }
+ it_behaves_like "it runs"
+ end
+
+ context "when Bundler.setup fails" do
+ before do
+ gemfile <<-G
+ gem 'rack', '2'
+ G
+ ENV["BUNDLER_FORCE_TTY"] = "true"
+ end
+
+ let(:exit_code) { Bundler::GemNotFound.new.status_code }
+ let(:expected) { <<-EOS.strip }
+\e[31mCould not find gem 'rack (= 2)' in any of the gem sources listed in your Gemfile or available on this machine.\e[0m
+\e[33mRun `bundle install` to install missing gems.\e[0m
+ EOS
+
+ it_behaves_like "it runs"
+ end
+ end
end
diff --git a/spec/commands/package_spec.rb b/spec/commands/package_spec.rb
index 0ebb65b1a9..08b36b1e9d 100644
--- a/spec/commands/package_spec.rb
+++ b/spec/commands/package_spec.rb
@@ -38,6 +38,8 @@ describe "bundle package" do
Gem::Specification.new do |s|
s.name = "mygem"
s.version = "0.1.1"
+ s.summary = ""
+ s.authors = ["gem author"]
s.add_development_dependency "nokogiri", "=1.4.2"
end
G
@@ -50,8 +52,8 @@ describe "bundle package" do
gemspec
D
- bundle "package --all"
- sleep 20
+ bundle! "package --all"
+
expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist
expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist
@@ -86,6 +88,18 @@ describe "bundle package" do
should_not_be_installed "rack 1.0.0", :expect_err => true
expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
end
+
+ it "does not prevent installing gems with bundle install" do
+ gemfile <<-D
+ source "file://#{gem_repo1}"
+ gem 'rack'
+ D
+
+ bundle "package --no-install"
+ bundle "install"
+
+ should_be_installed "rack 1.0.0"
+ end
end
context "with --all-platforms" do
diff --git a/spec/install/gemfile/git_spec.rb b/spec/install/gemfile/git_spec.rb
index 66a721521e..5081d35fa7 100644
--- a/spec/install/gemfile/git_spec.rb
+++ b/spec/install/gemfile/git_spec.rb
@@ -980,6 +980,47 @@ describe "bundle install with git sources" do
expect(out).to include("An error occurred while installing foo (1.0)")
expect(out).not_to include("gem install foo")
end
+
+ it "does not reinstall the extension", :rubygems => ">= 2.3.0" do
+ build_git "foo" do |s|
+ s.add_dependency "rake"
+ s.extensions << "Rakefile"
+ s.write "Rakefile", <<-RUBY
+ task :default do
+ path = File.expand_path("../lib", __FILE__)
+ FileUtils.mkdir_p(path)
+ cur_time = Time.now.to_f.to_s
+ File.open("\#{path}/foo.rb", "w") do |f|
+ f.puts "FOO = \#{cur_time}"
+ end
+ end
+ RUBY
+ end
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+
+ installed_time = out
+ expect(installed_time).to match(/\d+\.\d+/)
+
+ install_gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "foo", :git => "#{lib_path("foo-1.0")}"
+ G
+
+ run <<-R
+ require 'foo'
+ puts FOO
+ R
+ expect(out).to eq(installed_time)
+ end
end
it "ignores git environment variables" do