diff --git a/src/cli.cr b/src/cli.cr index beb8ad24..26437e08 100644 --- a/src/cli.cr +++ b/src/cli.cr @@ -166,6 +166,6 @@ rescue ex : Shards::ParseError ex.to_s(STDERR) exit 1 rescue ex : Shards::Error - Shards::Log.error { ex.message } + Shards::Log.error { ex.message.to_s } exit 1 end diff --git a/src/commands/command.cr b/src/commands/command.cr index 3695a22e..8c2ef279 100644 --- a/src/commands/command.cr +++ b/src/commands/command.cr @@ -1,6 +1,7 @@ require "../lock" require "../spec" require "../override" +require "levenshtein" module Shards abstract class Command @@ -73,8 +74,49 @@ module Shards Shards::Lock.write(packages, override_path, LOCK_FILENAME) end - def handle_resolver_errors(&) + private def log_available_tags(conflicts) + String.build do |str| + shard_source_dependencys = conflicts.flat_map { |k, v| v.requirements.flat_map { |source, deps| deps.map { |dep| {k, source, dep} } } } + if shard_source_dependencys.size > 1 + str << "Unable to satisfy the following requirements:\n\n" + shard_source_dependencys.each do |shard, source, dependency| + str << "- `#{shard} (#{dependency.requirement})` required by `#{source}`\n" + end + else + str << "Unable to satisfy the following requirement:\n\n" + shard_source_dependencys.each do |shard, source, dependency| + resolver = dependency.resolver + tags = resolver.available_tags.reverse!.first(5) + releases = resolver.available_releases.map(&.to_s).reverse + req = dependency.requirement + + str << "- `#{shard} (#{req})` required by `#{source}`: " + if releases.empty? + str << "It doesn't have any release. " + if tags.empty? + str << "And it doesn't have any tags either." + else + str << "These are the latest tags: #{tags.join(", ")}." + end + elsif req.is_a?(Version) || (req.is_a?(VersionReq) && req.patterns.size == 1 && req.patterns[0] !~ /^(<|>|=)/) + req = req.to_s + found = Levenshtein.find(req, releases, 6) || "none" + info = "These are the latest tags: #{tags.join(", ")}." + str << "The closest available release to #{req} is: #{found}. #{info}" + else + str << "The last available releases are #{releases.first(5).join(", ")}." + end + str << "\n" + end + end + end + end + + def handle_resolver_errors(solver, &) yield + rescue e : Molinillo::VersionConflict(Shards::Dependency, Shards::Spec) + Log.error { log_available_tags(e.conflicts) } + raise Shards::Error.new("Failed to resolve dependencies") rescue e : Molinillo::ResolverError Log.error { e.message } raise Shards::Error.new("Failed to resolve dependencies") diff --git a/src/commands/install.cr b/src/commands/install.cr index 515e24fe..42a9b668 100644 --- a/src/commands/install.cr +++ b/src/commands/install.cr @@ -21,7 +21,7 @@ module Shards solver.prepare(development: Shards.with_development?) - packages = handle_resolver_errors { solver.solve } + packages = handle_resolver_errors(solver) { solver.solve } if Shards.frozen? validate(packages) diff --git a/src/commands/lock.cr b/src/commands/lock.cr index b69c5a1d..90342cb5 100644 --- a/src/commands/lock.cr +++ b/src/commands/lock.cr @@ -26,7 +26,7 @@ module Shards solver.prepare(development: Shards.with_development?) - packages = handle_resolver_errors { solver.solve } + packages = handle_resolver_errors(solver) { solver.solve } return if packages.empty? if print diff --git a/src/commands/outdated.cr b/src/commands/outdated.cr index 89794706..ad1c144b 100644 --- a/src/commands/outdated.cr +++ b/src/commands/outdated.cr @@ -18,7 +18,7 @@ module Shards solver = MolinilloSolver.new(spec, override, prereleases: @prereleases) solver.prepare(development: Shards.with_development?) - packages = handle_resolver_errors { solver.solve } + packages = handle_resolver_errors(solver) { solver.solve } packages.each { |package| analyze(package) } if @up_to_date diff --git a/src/commands/update.cr b/src/commands/update.cr index 00364be0..3acd1dca 100644 --- a/src/commands/update.cr +++ b/src/commands/update.cr @@ -19,7 +19,7 @@ module Shards solver.prepare(development: Shards.with_development?) - packages = handle_resolver_errors { solver.solve } + packages = handle_resolver_errors(solver) { solver.solve } install(packages) if generate_lockfile?(packages) diff --git a/src/resolvers/crystal.cr b/src/resolvers/crystal.cr index a7570a1d..b6e8192b 100644 --- a/src/resolvers/crystal.cr +++ b/src/resolvers/crystal.cr @@ -10,6 +10,10 @@ module Shards [Version.new Shards.crystal_version] end + def available_tags : Array(String) + [Shards.crystal_version] + end + def read_spec(version : Version) : String? nil end diff --git a/src/resolvers/fossil.cr b/src/resolvers/fossil.cr index b568994c..85361787 100644 --- a/src/resolvers/fossil.cr +++ b/src/resolvers/fossil.cr @@ -212,10 +212,13 @@ module Shards end end - protected def versions_from_tags + def available_tags : Array(String) capture("fossil tag list -R #{Process.quote(local_fossil_file)}") - .split('\n') - .compact_map { |tag| Version.new($1) if tag =~ VERSION_TAG } + .lines.reject!(&.empty?) + end + + protected def versions_from_tags + available_tags.compact_map { |tag| Version.new($1) if tag =~ VERSION_TAG } end def install_sources(version : Version, install_path : String) diff --git a/src/resolvers/git.cr b/src/resolvers/git.cr index c1f7d652..6308e554 100644 --- a/src/resolvers/git.cr +++ b/src/resolvers/git.cr @@ -203,10 +203,13 @@ module Shards end end - protected def versions_from_tags + def available_tags : Array(String) capture("git tag --list #{GitResolver.git_column_never}") - .split('\n') - .compact_map { |tag| Version.new($1) if tag =~ VERSION_TAG } + .lines.reject!(&.empty?) + end + + protected def versions_from_tags + available_tags.compact_map { |tag| Version.new($1) if tag =~ VERSION_TAG } end def install_sources(version : Version, install_path : String) diff --git a/src/resolvers/hg.cr b/src/resolvers/hg.cr index 7568b09d..e996cef3 100644 --- a/src/resolvers/hg.cr +++ b/src/resolvers/hg.cr @@ -186,6 +186,14 @@ module Shards rescue Error end + def available_tags : Array(String) + tags = capture("hg tags --template #{Process.quote("{tag}\n")}") + .lines + .sort! + + tags.reject!(&.empty?) + end + def available_releases : Array(Version) update_local_cache versions_from_tags @@ -221,10 +229,11 @@ module Shards end protected def versions_from_tags - capture("hg tags --template #{Process.quote("{tag}\n")}") + tags = capture("hg tags --template #{Process.quote("{tag}\n")}") .lines .sort! - .compact_map { |tag| Version.new($1) if tag =~ VERSION_TAG } + + tags.compact_map { |tag| Version.new($1) if tag =~ VERSION_TAG } end def install_sources(version : Version, install_path : String) diff --git a/src/resolvers/path.cr b/src/resolvers/path.cr index 16915edd..9e36dfeb 100644 --- a/src/resolvers/path.cr +++ b/src/resolvers/path.cr @@ -24,6 +24,10 @@ module Shards [spec(nil).version] end + def available_tags : Array(String) + [spec(nil).version.to_s] + end + def local_path source end diff --git a/src/resolvers/resolver.cr b/src/resolvers/resolver.cr index 9adfe7cd..07aa080d 100644 --- a/src/resolvers/resolver.cr +++ b/src/resolvers/resolver.cr @@ -54,6 +54,8 @@ module Shards end end + abstract def available_tags : Array(String) + abstract def available_releases : Array(Version) def latest_version_for_ref(ref : Ref?) : Version