summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/bundler/cli.rb14
-rw-r--r--lib/bundler/cli/lock.rb36
-rw-r--r--lib/bundler/dsl.rb116
-rw-r--r--lib/bundler/friendly_errors.rb3
-rw-r--r--man/bundle.ronn6
-rw-r--r--spec/bundler/dsl_spec.rb9
-rw-r--r--spec/commands/lock_spec.rb94
-rw-r--r--spec/install/gems/simple_case_spec.rb2
8 files changed, 260 insertions, 20 deletions
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index 6a22072565..b1a19f5530 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -379,6 +379,20 @@ module Bundler
Inject.new(options, name, version, gems).run
end
+ desc "lock", "Creates a lockfile without installing"
+ method_option "update", :type => :boolean, :default => false, :banner =>
+ "ignore the existing lockfile"
+ method_option "local", :type => :boolean, :default => false, :banner =>
+ "do not attempt to fetch remote gemspecs and use the local gem cache only"
+ method_option "print", :type => :boolean, :default => false, :banner =>
+ "print the lockfile to STDOUT instead of writing to the file system"
+ method_option "lockfile", :type => :string, :default => nil, :banner =>
+ "the path the lockfile should be written to"
+ def lock
+ require 'bundler/cli/lock'
+ Lock.new(options).run
+ end
+
desc "env", "Print information about the environment Bundler is running under"
def env
Env.new.write($stdout)
diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb
new file mode 100644
index 0000000000..543deb5945
--- /dev/null
+++ b/lib/bundler/cli/lock.rb
@@ -0,0 +1,36 @@
+module Bundler
+ class CLI::Lock
+ attr_reader :options
+
+ def initialize(options)
+ @options = options
+ end
+
+ def run
+ unless Bundler.default_gemfile
+ Bundler.ui.error "Unable to find a Gemfile to lock"
+ exit 1
+ end
+
+ print = options[:print]
+ ui = Bundler.ui
+ Bundler.ui = UI::Silent.new if print
+
+ unlock = options[:update]
+ definition = Bundler.definition(unlock)
+ definition.resolve_remotely! unless options[:local]
+
+ if print
+ puts definition.to_lock
+ else
+ file = options[:lockfile]
+ file = file ? File.expand_path(file) : Bundler.default_lockfile
+ puts "Writing lockfile to #{file}"
+ definition.lock(file)
+ end
+
+ Bundler.ui = ui
+ end
+
+ end
+end
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
index e6c4b975ef..f9f09e8b00 100644
--- a/lib/bundler/dsl.rb
+++ b/lib/bundler/dsl.rb
@@ -30,14 +30,9 @@ module Bundler
def eval_gemfile(gemfile, contents = nil)
contents ||= Bundler.read_file(gemfile.to_s)
instance_eval(contents, gemfile.to_s, 1)
- rescue SyntaxError => e
- syntax_msg = e.message.gsub("#{gemfile}:", 'on line ')
- raise GemfileError, "Gemfile syntax error #{syntax_msg}"
- rescue ScriptError, RegexpError, NameError, ArgumentError, RuntimeError => e
- e.backtrace[0] = "#{e.backtrace[0]}: #{e.message} (#{e.class})"
- Bundler.ui.warn e.backtrace.join("\n ")
- raise GemfileError, "There was an error in your Gemfile," \
- " and Bundler cannot continue."
+ rescue Exception => e
+ message = "There was an error parsing `#{File.basename gemfile.to_s}`: #{e.message}"
+ raise DSLError.new(message, gemfile, e.backtrace, contents)
end
def gemspec(opts = nil)
@@ -184,9 +179,7 @@ module Bundler
end
def method_missing(name, *args)
- location = caller[0].split(':')[0..1].join(':')
- raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile\n" \
- " from #{location}"
+ raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile"
end
private
@@ -327,5 +320,106 @@ module Bundler
end
end
+ class DSLError < GemfileError
+ # @return [String] the description that should be presented to the user.
+ #
+ attr_reader :description
+
+ # @return [String] the path of the dsl file that raised the exception.
+ #
+ attr_reader :dsl_path
+
+ # @return [Exception] the backtrace of the exception raised by the
+ # evaluation of the dsl file.
+ #
+ attr_reader :backtrace
+
+ # @param [Exception] backtrace @see backtrace
+ # @param [String] dsl_path @see dsl_path
+ #
+ def initialize(description, dsl_path, backtrace, contents = nil)
+ @status_code = $!.respond_to?(:status_code) && $!.status_code
+
+ @description = description
+ @dsl_path = dsl_path
+ @backtrace = backtrace
+ @contents = contents
+ end
+
+ def status_code
+ @status_code || super
+ end
+
+ # @return [String] the contents of the DSL that cause the exception to
+ # be raised.
+ #
+ def contents
+ @contents ||= begin
+ dsl_path && File.exist?(dsl_path) && File.read(dsl_path)
+ end
+ end
+
+ # The message of the exception reports the content of podspec for the
+ # line that generated the original exception.
+ #
+ # @example Output
+ #
+ # Invalid podspec at `RestKit.podspec` - undefined method
+ # `exclude_header_search_paths=' for #<Pod::Specification for
+ # `RestKit/Network (0.9.3)`>
+ #
+ # from spec-repos/master/RestKit/0.9.3/RestKit.podspec:36
+ # -------------------------------------------
+ # # because it would break: #import <CoreData/CoreData.h>
+ # > ns.exclude_header_search_paths = 'Code/RestKit.h'
+ # end
+ # -------------------------------------------
+ #
+ # @return [String] the message of the exception.
+ #
+ def message
+ @message ||= begin
+ trace_line, description = parse_line_number_from_description
+
+ m = "\n[!] "
+ m << description
+ m << ". Bundler cannot continue.\n"
+
+ return m unless backtrace && dsl_path && contents
+
+ trace_line = backtrace.find { |l| l.include?(dsl_path.to_s) } || trace_line
+ return m unless trace_line
+ line_numer = trace_line.split(':')[1].to_i - 1
+ return m unless line_numer
+
+ lines = contents.lines.to_a
+ indent = ' # '
+ indicator = indent.gsub('#', '>')
+ first_line = (line_numer.zero?)
+ last_line = (line_numer == (lines.count - 1))
+
+ m << "\n"
+ m << "#{indent}from #{trace_line.gsub(/:in.*$/, '')}\n"
+ m << "#{indent}-------------------------------------------\n"
+ m << "#{indent}#{ lines[line_numer - 1] }" unless first_line
+ m << "#{indicator}#{ lines[line_numer] }"
+ m << "#{indent}#{ lines[line_numer + 1] }" unless last_line
+ m << "\n" unless m.end_with?("\n")
+ m << "#{indent}-------------------------------------------\n"
+ end
+ end
+
+ private
+
+ def parse_line_number_from_description
+ description = self.description
+ if dsl_path && description =~ /((#{Regexp.quote File.expand_path(dsl_path)}|#{Regexp.quote dsl_path.to_s}):\d+)/
+ trace_line = Regexp.last_match[1]
+ description = description.sub(/#{Regexp.quote trace_line}:\s*/, '').sub("\n", ' - ')
+ end
+ [trace_line, description]
+ end
+ end
+
end
end
diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb
index 1431725d3d..4bd3e53381 100644
--- a/lib/bundler/friendly_errors.rb
+++ b/lib/bundler/friendly_errors.rb
@@ -5,6 +5,9 @@ require "bundler/vendored_thor"
module Bundler
def self.with_friendly_errors
yield
+ rescue Bundler::Dsl::DSLError => e
+ Bundler.ui.error e.message
+ exit e.status_code
rescue Bundler::BundlerError => e
Bundler.ui.error e.message, :wrap => true
Bundler.ui.trace e
diff --git a/man/bundle.ronn b/man/bundle.ronn
index 7b69c6735c..f7b9e94fd4 100644
--- a/man/bundle.ronn
+++ b/man/bundle.ronn
@@ -67,6 +67,9 @@ We divide `bundle` subcommands into primary commands and utilities.
* `bundle open(1)`:
Open an installed gem in the editor
+* `bundle lock(1)`:
+ Generate a lockfile for your dependencies
+
* `bundle viz(1)`:
Generate a visual representation of your dependencies
@@ -92,7 +95,4 @@ and execute it, passing down any extra arguments to it.
These commands are obsolete and should no longer be used
-* `bundle lock(1)`
-* `bundle unlock(1)`
* `bundle cache(1)`
-
diff --git a/spec/bundler/dsl_spec.rb b/spec/bundler/dsl_spec.rb
index c2dacedd08..5c6be9efca 100644
--- a/spec/bundler/dsl_spec.rb
+++ b/spec/bundler/dsl_spec.rb
@@ -69,8 +69,7 @@ describe Bundler::Dsl do
expect(Bundler).to receive(:read_file).with("Gemfile").
and_return("unknown")
- error_msg = "Undefined local variable or method `unknown'" \
- " for Gemfile\\s+from Gemfile:1"
+ error_msg = "There was an error parsing `Gemfile`: Undefined local variable or method `unknown' for Gemfile. Bundler cannot continue."
expect { subject.eval_gemfile("Gemfile") }.
to raise_error(Bundler::GemfileError, Regexp.new(error_msg))
end
@@ -80,7 +79,7 @@ describe Bundler::Dsl do
it "handles syntax errors with a useful message" do
expect(Bundler).to receive(:read_file).with("Gemfile").and_return("}")
expect { subject.eval_gemfile("Gemfile") }.
- to raise_error(Bundler::GemfileError, /Gemfile syntax error/)
+ to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`: (syntax error, unexpected tSTRING_DEND|(compile error - )?syntax error, unexpected '}'). Bundler cannot continue./)
end
end
@@ -177,7 +176,7 @@ describe Bundler::Dsl do
it "will raise a Bundler::GemfileError" do
gemfile "gem 'foo', :path => /unquoted/string/syntax/error"
expect { Bundler::Dsl.evaluate(bundled_app("Gemfile"), nil, true) }.
- to raise_error(Bundler::GemfileError, /Gemfile syntax error/)
+ to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`:( compile error -)? unknown regexp options - trg. Bundler cannot continue./)
end
end
@@ -185,7 +184,7 @@ describe Bundler::Dsl do
it "will raise a Bundler::GemfileError" do
gemfile "s = 'foo'.freeze; s.strip!"
expect { Bundler::Dsl.evaluate(bundled_app("Gemfile"), nil, true) }.
- to raise_error(Bundler::GemfileError, /There was an error in your Gemfile/)
+ to raise_error(Bundler::GemfileError, /There was an error parsing `Gemfile`: can't modify frozen String. Bundler cannot continue./)
end
end
end
diff --git a/spec/commands/lock_spec.rb b/spec/commands/lock_spec.rb
new file mode 100644
index 0000000000..b3f4f8cb63
--- /dev/null
+++ b/spec/commands/lock_spec.rb
@@ -0,0 +1,94 @@
+require "spec_helper"
+
+describe "bundle lock" do
+ def strip_lockfile(lockfile)
+ strip_whitespace(lockfile).sub(/\n\Z/, '')
+ end
+
+ def read_lockfile(file = "Gemfile.lock")
+ strip_lockfile bundled_app(file).read
+ end
+
+ before :each do
+ gemfile <<-G
+ source "file://#{gem_repo1}"
+ gem "rails"
+ gem "with_license"
+ gem "foo"
+ G
+
+ @lockfile = strip_lockfile <<-L
+ GEM
+ remote: file:#{gem_repo1}/
+ specs:
+ actionmailer (2.3.2)
+ activesupport (= 2.3.2)
+ actionpack (2.3.2)
+ activesupport (= 2.3.2)
+ activerecord (2.3.2)
+ activesupport (= 2.3.2)
+ activeresource (2.3.2)
+ activesupport (= 2.3.2)
+ activesupport (2.3.2)
+ foo (1.0)
+ rails (2.3.2)
+ actionmailer (= 2.3.2)
+ actionpack (= 2.3.2)
+ activerecord (= 2.3.2)
+ activeresource (= 2.3.2)
+ rake (= 10.0.2)
+ rake (10.0.2)
+ with_license (1.0)
+
+ PLATFORMS
+ ruby
+
+ DEPENDENCIES
+ foo
+ rails
+ with_license
+ L
+ end
+
+ it "prints a lockfile when there is no existing lockfile with --print" do
+ bundle "lock --print"
+
+ expect(out).to eq(@lockfile)
+ end
+
+ it "prints a lockfile when there is an existing lockfile with --print" do
+ lockfile @lockfile
+
+ bundle "lock --print"
+
+ expect(out).to eq(@lockfile)
+ end
+
+ it "writes a lockfile when there is no existing lockfile" do
+ bundle "lock"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "writes a lockfile when there is an outdated lockfile using --update" do
+ lockfile @lockfile.gsub('2.3.2', '2.3.1')
+
+ bundle "lock --update"
+
+ expect(read_lockfile).to eq(@lockfile)
+ end
+
+ it "does not fetch remote specs when using the --local option" do
+ bundle "lock --update --local"
+
+ expect(out).to include("in the gems available on this machine.")
+ end
+
+ it "writes to a custom location using --lockfile" do
+ bundle "lock --lockfile=lock"
+
+ expect(out).to match(/Writing lockfile to.+lock/)
+ expect(read_lockfile "lock").to eq(@lockfile)
+ expect { read_lockfile }.to raise_error
+ end
+end
diff --git a/spec/install/gems/simple_case_spec.rb b/spec/install/gems/simple_case_spec.rb
index 759de0884f..0f05588c4d 100644
--- a/spec/install/gems/simple_case_spec.rb
+++ b/spec/install/gems/simple_case_spec.rb
@@ -17,7 +17,7 @@ describe "bundle install with gem sources" do
G
expect(err).to eq ""
- expect(out).to match(/StandardError: FAIL/)
+ expect(out).to match(/StandardError, "FAIL"/)
expect(bundled_app("Gemfile.lock")).not_to exist
end