Merge pull request #270 from alexarchambault/topic/plugin-errors

Better plugin errors
This commit is contained in:
Alexandre Archambault 2016-05-31 19:44:42 +02:00
commit e1d6f352f5
3 changed files with 152 additions and 42 deletions

View File

@ -0,0 +1,111 @@
package coursier
import scala.collection.mutable.ArrayBuffer
sealed abstract class ResolutionError extends Product with Serializable {
def cause: Option[Throwable] = this match {
case ResolutionError.MaximumIterationsReached => None
case ResolutionError.UnknownException(ex) => Some(ex)
case ResolutionError.UnknownDownloadException(ex) => Some(ex)
case _: ResolutionError.Conflicts => None
case _: ResolutionError.MetadataDownloadErrors => None
case _: ResolutionError.DownloadErrors => None
}
def message: String = this match {
case ResolutionError.MaximumIterationsReached =>
"Maximum number of iteration of dependency resolution reached"
case ResolutionError.UnknownException(ex) =>
"Exception during resolution"
case ResolutionError.UnknownDownloadException(ex) =>
"Error while downloading / verifying artifacts"
case ResolutionError.Conflicts(description) =>
description
case err: ResolutionError.MetadataDownloadErrors =>
err.description()
case err: ResolutionError.DownloadErrors =>
err.description(verbose = true)
}
def exception(): ResolutionException =
new ResolutionException(this)
def throwException(): Nothing =
throw exception()
}
object ResolutionError {
case object MaximumIterationsReached extends ResolutionError
case class UnknownException(ex: Throwable) extends ResolutionError
case class UnknownDownloadException(ex: Throwable) extends ResolutionError
case class Conflicts(description: String) extends ResolutionError
case class MetadataDownloadErrors(errors: Seq[(Dependency, Seq[String])]) extends ResolutionError {
def description(): String = {
def grouped(errs: Seq[String]) =
errs
.map { s =>
val idx = s.indexOf(": ")
if (idx >= 0)
(s.take(idx), s.drop(idx + ": ".length))
else
("", s)
}
.groupBy(_._1)
.mapValues(_.map(_._2))
.toVector
.sortBy(_._1)
val lines = new ArrayBuffer[String]
lines += s"Encountered ${errors.length} error(s) in dependency resolution:"
for ((dep, errs) <- errors) {
lines += s" ${dep.module}:${dep.version}:"
for ((type0, errs0) <- grouped(errs))
if (type0.isEmpty)
for (err <- errs0)
lines += s" $err"
else
errs0 match {
case Seq(err) =>
lines += s" $type0: $err"
case _ =>
lines += s" $type0:"
for (err <- errs0)
lines += s" $err"
}
}
lines.mkString("\n")
}
}
case class DownloadErrors(errors: Seq[FileError]) extends ResolutionError {
def description(verbose: Boolean): String = {
val groupedArtifactErrors = errors
.groupBy(_.`type`)
.mapValues(_.map(_.message).sorted)
.toVector
.sortBy(_._1)
val b = new StringBuilder
for ((type0, errors) <- groupedArtifactErrors) {
b ++= s"${errors.size} $type0"
if (verbose)
for (err <- errors)
b ++= " " + err
}
b.result()
}
}
}

View File

@ -0,0 +1,10 @@
package coursier
final class ResolutionException(
val error: ResolutionError
) extends Exception(
error.message,
error.cause.orNull,
true,
false // don't keep stack trace around (improves readability from the SBT console)
)

View File

@ -440,8 +440,10 @@ object Tasks {
}
}
val internalRepositories = Seq(globalPluginsRepo, interProjectRepo)
val repositories =
Seq(globalPluginsRepo, interProjectRepo) ++
internalRepositories ++
sourceRepositories0 ++
resolvers.flatMap { resolver =>
FromSbt.repository(
@ -507,37 +509,42 @@ object Tasks {
.process
.run(fetch, maxIterations)
.attemptRun
.leftMap(ex => throw new Exception("Exception during resolution", ex))
.leftMap(ex =>
ResolutionError.UnknownException(ex)
.throwException()
)
.merge
resLogger.stop()
if (!res.isDone)
throw new Exception("Maximum number of iteration of dependency resolution reached")
ResolutionError.MaximumIterationsReached
.throwException()
if (res.conflicts.nonEmpty) {
val projCache = res.projectCache.mapValues { case (_, p) => p }
log.error(
s"${res.conflicts.size} conflict(s):\n" +
" " + Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache)
)
throw new Exception("Conflict(s) in dependency resolution")
ResolutionError.Conflicts(
"Conflict(s) in dependency resolution:\n " +
Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache)
).throwException()
}
if (res.errors.nonEmpty) {
log.error(
s"\n${res.errors.size} error(s):\n" +
val internalRepositoriesLen = internalRepositories.length
val errors =
if (repositories.length > internalRepositoriesLen)
// drop internal repository errors
res.errors.map {
case (dep, errs) =>
s" ${dep.module}:${dep.version}:\n" +
errs
.map(" " + _.replace("\n", " \n"))
.mkString("\n")
}.mkString("\n")
)
dep -> errs.drop(internalRepositoriesLen)
}
else
res.errors
throw new Exception(s"Encountered ${res.errors.length} error(s) in dependency resolution")
ResolutionError.MetadataDownloadErrors(errors)
.throwException()
}
if (verbosityLevel >= 0)
@ -709,7 +716,8 @@ object Tasks {
val artifactFilesOrErrors = Task.gatherUnordered(artifactFileOrErrorTasks).attemptRun match {
case -\/(ex) =>
throw new Exception("Error while downloading / verifying artifacts", ex)
ResolutionError.UnknownDownloadException(ex)
.throwException()
case \/-(l) =>
l.toMap
}
@ -733,31 +741,12 @@ object Tasks {
}
if (artifactErrors.nonEmpty) {
val groupedArtifactErrors = artifactErrors
.groupBy(_.`type`)
.mapValues(_.map(_.message).sorted)
.toVector
.sortBy(_._1)
val error = ResolutionError.DownloadErrors(artifactErrors)
for ((type0, errors) <- groupedArtifactErrors) {
def msg = s"${errors.size} $type0"
if (ignoreArtifactErrors)
log.warn(msg)
else
log.error(msg)
if (!ignoreArtifactErrors || verbosityLevel >= 1) {
if (ignoreArtifactErrors)
for (err <- errors)
log.warn(" " + err)
else
for (err <- errors)
log.error(" " + err)
}
}
if (!ignoreArtifactErrors)
throw new Exception(s"Encountered ${artifactErrors.length} errors (see above messages)")
if (ignoreArtifactErrors)
log.warn(error.description(verbosityLevel >= 1))
else
error.throwException()
}
// can be non empty only if ignoreArtifactErrors is true