From 201ef286db9fc59d74e5b35d0226f35815a49d61 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Tue, 31 May 2016 13:43:23 +0200 Subject: [PATCH 1/3] Stacktrace-less exceptions in SBT plugin --- .../coursier/ResolutionException.scala | 11 +++++ .../src/main/scala-2.10/coursier/Tasks.scala | 49 +++++++++---------- 2 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 plugin/src/main/scala-2.10/coursier/ResolutionException.scala diff --git a/plugin/src/main/scala-2.10/coursier/ResolutionException.scala b/plugin/src/main/scala-2.10/coursier/ResolutionException.scala new file mode 100644 index 000000000..f59605eda --- /dev/null +++ b/plugin/src/main/scala-2.10/coursier/ResolutionException.scala @@ -0,0 +1,11 @@ +package coursier + +final class ResolutionException( + val message: String, + val cause: Throwable = null +) extends Exception( + message, + cause, + true, + false // don't keep stack trace around (improves readability from the SBT console) +) diff --git a/plugin/src/main/scala-2.10/coursier/Tasks.scala b/plugin/src/main/scala-2.10/coursier/Tasks.scala index bfd3c2ca0..21c786708 100644 --- a/plugin/src/main/scala-2.10/coursier/Tasks.scala +++ b/plugin/src/main/scala-2.10/coursier/Tasks.scala @@ -506,27 +506,27 @@ object Tasks { .process .run(fetch, maxIterations) .attemptRun - .leftMap(ex => throw new Exception("Exception during resolution", ex)) + .leftMap(ex => throw new ResolutionException("Exception during resolution", ex)) .merge resLogger.stop() if (!res.isDone) - throw new Exception("Maximum number of iteration of dependency resolution reached") + throw new ResolutionException("Maximum number of iteration of dependency resolution reached") 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 ResolutionException( + "Conflict(s) in dependency resolution:\n " + + Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache) ) - throw new Exception("Conflict(s) in dependency resolution") } - if (res.errors.nonEmpty) { - log.error( - s"\n${res.errors.size} error(s):\n" + + if (res.errors.nonEmpty) + throw new ResolutionException( + s"Encountered ${res.errors.length} error(s) in dependency resolution:\n" + res.errors.map { case (dep, errs) => s" ${dep.module}:${dep.version}:\n" + @@ -536,9 +536,6 @@ object Tasks { }.mkString("\n") ) - throw new Exception(s"Encountered ${res.errors.length} error(s) in dependency resolution") - } - if (verbosityLevel >= 0) log.info(s"Resolved ${projectDescription(currentProject)} dependencies") @@ -706,7 +703,7 @@ object Tasks { val artifactFilesOrErrors = Task.gatherUnordered(artifactFileOrErrorTasks).attemptRun match { case -\/(ex) => - throw new Exception("Error while downloading / verifying artifacts", ex) + throw new ResolutionException("Error while downloading / verifying artifacts", ex) case \/-(l) => l.toMap } @@ -736,25 +733,23 @@ object Tasks { .toVector .sortBy(_._1) + val b = new StringBuilder + for ((type0, errors) <- groupedArtifactErrors) { def msg = s"${errors.size} $type0" - if (ignoreArtifactErrors) - log.warn(msg) - else - log.error(msg) + b ++= msg - if (!ignoreArtifactErrors || verbosityLevel >= 1) { - if (ignoreArtifactErrors) - for (err <- errors) - log.warn(" " + err) - else - for (err <- errors) - log.error(" " + err) - } + if (!ignoreArtifactErrors || verbosityLevel >= 1) + for (err <- errors) + b ++= " " + err } - if (!ignoreArtifactErrors) - throw new Exception(s"Encountered ${artifactErrors.length} errors (see above messages)") + if (ignoreArtifactErrors) + log.warn(b.result()) + else + throw new ResolutionException( + s"Encountered ${artifactErrors.length} errors:\n" + b.result() + ) } // can be non empty only if ignoreArtifactErrors is true From e688828f7acab254f469f9c5a29b63ac73a5a0c7 Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Tue, 31 May 2016 13:43:27 +0200 Subject: [PATCH 2/3] Keep resolution error infos in ad hoc ADT --- .../scala-2.10/coursier/ResolutionError.scala | 74 +++++++++++++++++++ .../coursier/ResolutionException.scala | 7 +- .../src/main/scala-2.10/coursier/Tasks.scala | 50 ++++--------- 3 files changed, 92 insertions(+), 39 deletions(-) create mode 100644 plugin/src/main/scala-2.10/coursier/ResolutionError.scala diff --git a/plugin/src/main/scala-2.10/coursier/ResolutionError.scala b/plugin/src/main/scala-2.10/coursier/ResolutionError.scala new file mode 100644 index 000000000..632249a95 --- /dev/null +++ b/plugin/src/main/scala-2.10/coursier/ResolutionError.scala @@ -0,0 +1,74 @@ +package coursier + +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 ResolutionError.MetadataDownloadErrors(errors) => + s"Encountered ${errors.length} error(s) in dependency resolution:\n" + + errors.map { + case (dep, errs) => + s" ${dep.module}:${dep.version}:\n" + + errs + .map(" " + _.replace("\n", " \n")) + .mkString("\n") + }.mkString("\n") + + 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 + + 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() + } + } +} diff --git a/plugin/src/main/scala-2.10/coursier/ResolutionException.scala b/plugin/src/main/scala-2.10/coursier/ResolutionException.scala index f59605eda..9f8925a91 100644 --- a/plugin/src/main/scala-2.10/coursier/ResolutionException.scala +++ b/plugin/src/main/scala-2.10/coursier/ResolutionException.scala @@ -1,11 +1,10 @@ package coursier final class ResolutionException( - val message: String, - val cause: Throwable = null + val error: ResolutionError ) extends Exception( - message, - cause, + error.message, + error.cause.orNull, true, false // don't keep stack trace around (improves readability from the SBT console) ) diff --git a/plugin/src/main/scala-2.10/coursier/Tasks.scala b/plugin/src/main/scala-2.10/coursier/Tasks.scala index 21c786708..ed25b114f 100644 --- a/plugin/src/main/scala-2.10/coursier/Tasks.scala +++ b/plugin/src/main/scala-2.10/coursier/Tasks.scala @@ -506,35 +506,31 @@ object Tasks { .process .run(fetch, maxIterations) .attemptRun - .leftMap(ex => throw new ResolutionException("Exception during resolution", ex)) + .leftMap(ex => + ResolutionError.UnknownException(ex) + .throwException() + ) .merge resLogger.stop() if (!res.isDone) - throw new ResolutionException("Maximum number of iteration of dependency resolution reached") + ResolutionError.MaximumIterationsReached + .throwException() if (res.conflicts.nonEmpty) { val projCache = res.projectCache.mapValues { case (_, p) => p } - throw new ResolutionException( + ResolutionError.Conflicts( "Conflict(s) in dependency resolution:\n " + Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache) - ) + ).throwException() } if (res.errors.nonEmpty) - throw new ResolutionException( - s"Encountered ${res.errors.length} error(s) in dependency resolution:\n" + - res.errors.map { - case (dep, errs) => - s" ${dep.module}:${dep.version}:\n" + - errs - .map(" " + _.replace("\n", " \n")) - .mkString("\n") - }.mkString("\n") - ) + ResolutionError.MetadataDownloadErrors(res.errors) + .throwException() if (verbosityLevel >= 0) log.info(s"Resolved ${projectDescription(currentProject)} dependencies") @@ -703,7 +699,8 @@ object Tasks { val artifactFilesOrErrors = Task.gatherUnordered(artifactFileOrErrorTasks).attemptRun match { case -\/(ex) => - throw new ResolutionException("Error while downloading / verifying artifacts", ex) + ResolutionError.UnknownDownloadException(ex) + .throwException() case \/-(l) => l.toMap } @@ -727,29 +724,12 @@ object Tasks { } if (artifactErrors.nonEmpty) { - val groupedArtifactErrors = artifactErrors - .groupBy(_.`type`) - .mapValues(_.map(_.message).sorted) - .toVector - .sortBy(_._1) - - val b = new StringBuilder - - for ((type0, errors) <- groupedArtifactErrors) { - def msg = s"${errors.size} $type0" - b ++= msg - - if (!ignoreArtifactErrors || verbosityLevel >= 1) - for (err <- errors) - b ++= " " + err - } + val error = ResolutionError.DownloadErrors(artifactErrors) if (ignoreArtifactErrors) - log.warn(b.result()) + log.warn(error.description(verbosityLevel >= 1)) else - throw new ResolutionException( - s"Encountered ${artifactErrors.length} errors:\n" + b.result() - ) + error.throwException() } // can be non empty only if ignoreArtifactErrors is true From 6e27a7fc6ebe0291d324bc3acd4de9ec6aed9dfe Mon Sep 17 00:00:00 2001 From: Alexandre Archambault Date: Tue, 31 May 2016 13:43:30 +0200 Subject: [PATCH 3/3] Slightly better error messages from plugin --- .../scala-2.10/coursier/ResolutionError.scala | 57 +++++++++++++++---- .../src/main/scala-2.10/coursier/Tasks.scala | 20 ++++++- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/plugin/src/main/scala-2.10/coursier/ResolutionError.scala b/plugin/src/main/scala-2.10/coursier/ResolutionError.scala index 632249a95..7ec8f313f 100644 --- a/plugin/src/main/scala-2.10/coursier/ResolutionError.scala +++ b/plugin/src/main/scala-2.10/coursier/ResolutionError.scala @@ -1,5 +1,7 @@ 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 @@ -20,15 +22,8 @@ sealed abstract class ResolutionError extends Product with Serializable { case ResolutionError.Conflicts(description) => description - case ResolutionError.MetadataDownloadErrors(errors) => - s"Encountered ${errors.length} error(s) in dependency resolution:\n" + - errors.map { - case (dep, errs) => - s" ${dep.module}:${dep.version}:\n" + - errs - .map(" " + _.replace("\n", " \n")) - .mkString("\n") - }.mkString("\n") + case err: ResolutionError.MetadataDownloadErrors => + err.description() case err: ResolutionError.DownloadErrors => err.description(verbose = true) @@ -47,7 +42,49 @@ object 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 + + 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 { diff --git a/plugin/src/main/scala-2.10/coursier/Tasks.scala b/plugin/src/main/scala-2.10/coursier/Tasks.scala index ed25b114f..49de2ee26 100644 --- a/plugin/src/main/scala-2.10/coursier/Tasks.scala +++ b/plugin/src/main/scala-2.10/coursier/Tasks.scala @@ -439,8 +439,10 @@ object Tasks { } } + val internalRepositories = Seq(globalPluginsRepo, interProjectRepo) + val repositories = - Seq(globalPluginsRepo, interProjectRepo) ++ + internalRepositories ++ sourceRepositories0 ++ resolvers.flatMap { resolver => FromSbt.repository( @@ -528,9 +530,21 @@ object Tasks { ).throwException() } - if (res.errors.nonEmpty) - ResolutionError.MetadataDownloadErrors(res.errors) + if (res.errors.nonEmpty) { + val internalRepositoriesLen = internalRepositories.length + val errors = + if (repositories.length > internalRepositoriesLen) + // drop internal repository errors + res.errors.map { + case (dep, errs) => + dep -> errs.drop(internalRepositoriesLen) + } + else + res.errors + + ResolutionError.MetadataDownloadErrors(errors) .throwException() + } if (verbosityLevel >= 0) log.info(s"Resolved ${projectDescription(currentProject)} dependencies")