Skip to content

Commit

Permalink
Track and report more information about why a gem couldn't be installed
Browse files Browse the repository at this point in the history
git-svn-id: svn+ssh://rubyforge.org/var/svn/rubygems/trunk@2495 3d4018f9-ac1a-0410-99e9-8a154d859a19
  • Loading branch information
evanphx committed Apr 21, 2010
1 parent 5ffc6d6 commit d7f57cb
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 19 deletions.
12 changes: 12 additions & 0 deletions lib/rubygems/command.rb
Expand Up @@ -145,6 +145,18 @@ def execute
raise Gem::Exception, "generic command has no actions"
end

##
#
# Display to the user that a gem couldn't be found and reasons why
def show_lookup_failure(gem_name, version, errors=nil)
if errors and !errors.empty?
alert_error "Could not find a valid gem '#{gem_name}' (#{version}), here is why:"
errors.each { |x| say " #{x.wordy}" }
else
alert_error "Could not find a valid gem '#{gem_name}' (#{version}) in any repository"
end
end

##
# Get all gem names from the command line.

Expand Down
6 changes: 5 additions & 1 deletion lib/rubygems/commands/fetch_command.rb
Expand Up @@ -45,10 +45,14 @@ def execute
specs_and_sources = Gem::SpecFetcher.fetcher.fetch(dep, all, true,
dep.prerelease?)

specs_and_sources, errors =
Gem::SpecFetcher.fetcher.fetch_with_errors(dep, all, true,
dep.prerelease?)

spec, source_uri = specs_and_sources.sort_by { |s,| s.version }.last

if spec.nil? then
alert_error "Could not find #{gem_name} in any repository"
show_lookup_failure gem_name, version, errors
next
end

Expand Down
3 changes: 2 additions & 1 deletion lib/rubygems/commands/install_command.rb
Expand Up @@ -127,7 +127,8 @@ def execute
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
exit_code |= 1
rescue Gem::GemNotFoundException => e
alert_error e.message
show_lookup_failure e.name, e.version, e.errors

exit_code |= 2
end
end
Expand Down
13 changes: 10 additions & 3 deletions lib/rubygems/dependency_installer.rb
Expand Up @@ -69,6 +69,10 @@ def initialize(options = {})

@install_dir = options[:install_dir] || Gem.dir
@cache_dir = options[:cache_dir] || @install_dir

# Set with any errors that SpecFetcher finds while search through
# gemspecs for a dep
@errors = nil
end

##
Expand All @@ -78,6 +82,8 @@ def initialize(options = {})
# local gems preferred over remote gems.

def find_gems_with_sources(dep)
# Reset the errors
@errors = nil
gems_and_sources = []

if @domain == :both or @domain == :local then
Expand All @@ -99,7 +105,7 @@ def find_gems_with_sources(dep)
(requirements.length > 1 or
(requirements.first != ">=" and requirements.first != ">"))

found = Gem::SpecFetcher.fetcher.fetch dep, all, true, dep.prerelease?
found, @errors = Gem::SpecFetcher.fetcher.fetch_with_errors dep, all, true, dep.prerelease?

gems_and_sources.push(*found)

Expand Down Expand Up @@ -204,8 +210,9 @@ def find_spec_by_name_and_version(gem_name,
end

if spec_and_source.nil? then
raise Gem::GemNotFoundException,
"could not find gem #{gem_name} locally or in a repository"
raise Gem::GemNotFoundException.new(
"Could not find a valid gem '#{gem_name}' (#{version}) locally or in a repository",
gem_name, version, @errors)
end

@specs_and_sources = [spec_and_source]
Expand Down
35 changes: 35 additions & 0 deletions lib/rubygems/errors.rb
@@ -0,0 +1,35 @@
class Gem::ErrorReason; end

# Generated when trying to lookup a gem to indicate that the gem
# was found, but that it isn't usable on the current platform.
#
# fetch and install read these and report them to the user to aid
# in figuring out why a gem couldn't be installed.
#
class Gem::PlatformMismatch < Gem::ErrorReason

attr_reader :name
attr_reader :version
attr_reader :platforms

def initialize(name, version)
@name = name
@version = version
@platforms = []
end

def add_platform(platform)
@platforms << platform
end

def wordy
prefix = "Found #{@name} (#{@version})"

if @platforms.size == 1
"#{prefix}, but was for platform #{@platforms[0]}"
else
"#{prefix}, but was for platforms #{@platforms.join(' ,')}"
end
end

end
11 changes: 10 additions & 1 deletion lib/rubygems/exceptions.rb
Expand Up @@ -37,7 +37,16 @@ class Gem::FormatException < Gem::Exception
attr_accessor :file_path
end

class Gem::GemNotFoundException < Gem::Exception; end
class Gem::GemNotFoundException < Gem::Exception
def initialize(msg, name=nil, version=nil, errors=nil)
super msg
@name = name
@version = version
@errors = errors
end

attr_reader :name, :version, :errors
end

class Gem::InstallError < Gem::Exception; end

Expand Down
40 changes: 31 additions & 9 deletions lib/rubygems/spec_fetcher.rb
Expand Up @@ -3,6 +3,7 @@

require 'rubygems/remote_fetcher'
require 'rubygems/user_interaction'
require 'rubygems/errors'

##
# SpecFetcher handles metadata updates from remote gem repositories.
Expand Down Expand Up @@ -65,22 +66,28 @@ def cache_dir(uri)
# false, all platforms are returned. If +prerelease+ is true,
# prerelease versions are included.

def fetch(dependency, all = false, matching_platform = true, prerelease = false)
specs_and_sources = find_matching dependency, all, matching_platform, prerelease
def fetch_with_errors(dependency, all = false, matching_platform = true, prerelease = false)
specs_and_sources, errors = find_matching_with_errors dependency, all, matching_platform, prerelease

specs_and_sources.map do |spec_tuple, source_uri|
ss = specs_and_sources.map do |spec_tuple, source_uri|
[fetch_spec(spec_tuple, URI.parse(source_uri)), source_uri]
end

return [ss, errors]

rescue Gem::RemoteFetcher::FetchError => e
raise unless warn_legacy e do
require 'rubygems/source_info_cache'

return Gem::SourceInfoCache.search_with_source(dependency,
matching_platform, all)
return [Gem::SourceInfoCache.search_with_source(dependency,
matching_platform, all), nil]
end
end

def fetch(*args)
fetch_with_errors(*args).first
end

def fetch_spec(spec, source_uri)
spec = spec - [nil, 'ruby', '']
spec_file_name = "#{spec.join '-'}.gemspec"
Expand Down Expand Up @@ -117,24 +124,39 @@ def fetch_spec(spec, source_uri)
# matching released versions are returned. If +matching_platform+
# is false, gems for all platforms are returned.

def find_matching(dependency, all = false, matching_platform = true, prerelease = false)
def find_matching_with_errors(dependency, all = false, matching_platform = true, prerelease = false)
found = {}

rejected_specs = {}

list(all, prerelease).each do |source_uri, specs|
found[source_uri] = specs.select do |spec_name, version, spec_platform|
dependency.match?(spec_name, version) and
(not matching_platform or Gem::Platform.match(spec_platform))
if dependency.match?(spec_name, version)
if matching_platform and !Gem::Platform.match(spec_platform)
pm = (rejected_specs[dependency] ||= Gem::PlatformMismatch.new(spec_name, version))
pm.add_platform spec_platform
false
else
true
end
end
end
end

errors = rejected_specs.values

specs_and_sources = []

found.each do |source_uri, specs|
uri_str = source_uri.to_s
specs_and_sources.push(*specs.map { |spec| [spec, uri_str] })
end

specs_and_sources
[specs_and_sources, errors]
end

def find_matching(*args)
find_matching_with_errors(*args).first
end

##
Expand Down
6 changes: 2 additions & 4 deletions test/test_gem_commands_install_command.rb
Expand Up @@ -145,8 +145,7 @@ def test_execute_local_missing
end

# HACK no repository was checked
assert_equal "ERROR: could not find gem no_such_gem locally or in a repository\n",
@ui.error
assert_match(/ould not find a valid gem 'no_such_gem'/, @ui.error)
end

def test_execute_no_gem
Expand All @@ -170,8 +169,7 @@ def test_execute_nonexistent
assert_equal 2, e.exit_code
end

assert_equal "ERROR: could not find gem nonexistent locally or in a repository\n",
@ui.error
assert_match(/ould not find a valid gem 'nonexistent'/, @ui.error)
end

def test_execute_prerelease
Expand Down
42 changes: 42 additions & 0 deletions test/test_gem_spec_fetcher.rb
Expand Up @@ -115,6 +115,21 @@ def test_fetch_platform
assert_equal [[@pl1.full_name, @gem_repo]], spec_names
end

def test_fetch_with_errors_mismatched_platform
util_set_arch 'hrpa-989'

@fetcher.data["#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@pl1.original_name}.gemspec.rz"] =
util_zip(Marshal.dump(@pl1))

dep = Gem::Dependency.new 'pl', 1
specs_and_sources, errors = @sf.fetch_with_errors dep

assert_equal 0, specs_and_sources.size
assert_equal 1, errors.size

assert_equal "i386-linux", errors[0].platforms.first
end

def test_fetch_spec
spec_uri = "#{@gem_repo}#{Gem::MARSHAL_SPEC_DIR}#{@a1.spec_name}"
@fetcher.data["#{spec_uri}.rz"] = util_zip(Marshal.dump(@a1))
Expand Down Expand Up @@ -220,6 +235,33 @@ def test_find_matching_platform
assert_equal [], specs
end

def test_find_matching_with_errors_matched_platform
util_set_arch 'i386-linux'

dep = Gem::Dependency.new 'pl', 1
specs, errors = @sf.find_matching_with_errors dep

expected = [
[['pl', Gem::Version.new(1), 'i386-linux'], @gem_repo],
]

assert_equal expected, specs
assert_equal 0, errors.size
end

def test_find_matching_with_errors_invalid_platform
util_set_arch 'hrpa-899'

dep = Gem::Dependency.new 'pl', 1
specs, errors = @sf.find_matching_with_errors dep

assert_equal 0, specs.size

assert_equal 1, errors.size

assert_equal "i386-linux", errors[0].platforms.first
end

def test_find_all_platforms
util_set_arch 'i386-freebsd6'

Expand Down

0 comments on commit d7f57cb

Please sign in to comment.