diff --git a/cache/src/main/scala/coursier/TermDisplay.scala b/cache/src/main/scala/coursier/TermDisplay.scala index 7823a3a9d..202a5848c 100644 --- a/cache/src/main/scala/coursier/TermDisplay.scala +++ b/cache/src/main/scala/coursier/TermDisplay.scala @@ -162,6 +162,7 @@ object TermDisplay { } private class UpdateDisplayRunnable( + beforeOutput: => Unit, out: Writer, width: Int, fallbackMode: Boolean @@ -170,6 +171,9 @@ object TermDisplay { import Terminal.Ansi private var currentHeight = 0 + private var printedAnything0 = false + + def printedAnything() = printedAnything0 private val needsUpdate = new AtomicBoolean(false) @@ -305,6 +309,11 @@ object TermDisplay { for ((url, info) <- done0 ++ downloads0) { assert(info != null, s"Incoherent state ($url)") + if (!printedAnything0) { + beforeOutput + printedAnything0 = true + } + truncatedPrintln(url) out.clearLine(2) out.write(s" ${info.display()}\n") @@ -422,19 +431,36 @@ class TermDisplay( else 1000L / 60 - def init(): Unit = { - updateRunnableOpt = Some(new UpdateDisplayRunnable(out, width, fallbackMode0)) + /*** + * + * @param beforeOutput: called before any output is printed, iff something else is outputed. + * (That is, if that `TermDisplay` doesn't print any progress, + * `initialMessage` won't be printed either.) + */ + def init(beforeOutput: => Unit): Unit = { + updateRunnableOpt = Some(new UpdateDisplayRunnable(beforeOutput, out, width, fallbackMode0)) updateRunnable.init() scheduler.scheduleAtFixedRate(updateRunnable, 0L, refreshInterval, TimeUnit.MILLISECONDS) } - def stop(): Unit = { + def init(): Unit = + init(()) + + /** + * + * @return: whether any message was printed by this `TermDisplay` + */ + def stopDidPrintSomething(): Boolean = { scheduler.shutdown() scheduler.awaitTermination(refreshInterval, TimeUnit.MILLISECONDS) updateRunnable.cleanDisplay() + updateRunnable.printedAnything() } + def stop(): Unit = + stopDidPrintSomething() + override def downloadingArtifact(url: String, file: File): Unit = updateRunnable.newEntry( url, diff --git a/plugin/src/main/scala-2.10/coursier/Tasks.scala b/plugin/src/main/scala-2.10/coursier/Tasks.scala index c1d773d81..4e2cedc5d 100644 --- a/plugin/src/main/scala-2.10/coursier/Tasks.scala +++ b/plugin/src/main/scala-2.10/coursier/Tasks.scala @@ -579,7 +579,7 @@ object Tasks { var pool: ExecutorService = null var resLogger: TermDisplay = null - try { + val res = try { pool = Executors.newFixedThreadPool(parallelDownloads, Strategy.DefaultDaemonThreadFactory) resLogger = createLogger() @@ -615,17 +615,26 @@ object Tasks { ) } - if (verbosityLevel >= 0) - log.info( - s"Updating $projectName" + (if (sbtClassifiers) " (sbt classifiers)" else "") - ) - if (verbosityLevel >= 2) - for (depRepr <- depsRepr(currentProject.dependencies)) - log.info(s" $depRepr") + val initialMessage = + Seq( + if (verbosityLevel >= 0) + Seq(s"Updating $projectName" + (if (sbtClassifiers) " (sbt classifiers)" else "")) + else + Nil, + if (verbosityLevel >= 2) + depsRepr(currentProject.dependencies).map(depRepr => + s" $depRepr" + ) + else + Nil + ).flatten.mkString("\n") - resLogger.init() + if (verbosityLevel >= 1) + log.info(initialMessage) - val res = startRes + resLogger.init(if (verbosityLevel < 1) log.info(initialMessage)) + + startRes .process .run(fetch, maxIterations) .attemptRun @@ -634,46 +643,44 @@ object Tasks { .throwException() ) .merge - - if (!res.isDone) - ResolutionError.MaximumIterationsReached - .throwException() - - if (res.conflicts.nonEmpty) { - val projCache = res.projectCache.mapValues { case (_, p) => p } - - ResolutionError.Conflicts( - "Conflict(s) in dependency resolution:\n " + - Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache) - ).throwException() - } - - 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 $projectName dependencies") - - res } finally { if (pool != null) pool.shutdown() if (resLogger != null) - resLogger.stop() + if ((resLogger.stopDidPrintSomething() && verbosityLevel >= 0) || verbosityLevel >= 1) + log.info(s"Resolved $projectName dependencies") } + + if (!res.isDone) + ResolutionError.MaximumIterationsReached + .throwException() + + if (res.conflicts.nonEmpty) { + val projCache = res.projectCache.mapValues { case (_, p) => p } + + ResolutionError.Conflicts( + "Conflict(s) in dependency resolution:\n " + + Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache) + ).throwException() + } + + 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() + } + + res } resolutionsCache.getOrElseUpdate( @@ -777,45 +784,45 @@ object Tasks { }.value def report = { + + val depsByConfig = grouped(currentProject.dependencies) + + val configs = coursierConfigurations.value + + if (verbosityLevel >= 2) { + val finalDeps = Config.dependenciesWithConfig( + res, + depsByConfig.map { case (k, l) => k -> l.toSet }, + configs + ) + + val projCache = res.projectCache.mapValues { case (_, p) => p } + val repr = Print.dependenciesUnknownConfigs(finalDeps.toVector, projCache) + log.info(repr.split('\n').map(" " + _).mkString("\n")) + } + + val classifiers = + if (withClassifiers) + Some { + if (sbtClassifiers) + cm.classifiers + else + transitiveClassifiers.value + } + else + None + + val allArtifacts = + classifiers match { + case None => res.artifacts + case Some(cl) => res.classifiersArtifacts(cl) + } + var pool: ExecutorService = null var artifactsLogger: TermDisplay = null - try { + val artifactFilesOrErrors = try { pool = Executors.newFixedThreadPool(parallelDownloads, Strategy.DefaultDaemonThreadFactory) - - val depsByConfig = grouped(currentProject.dependencies) - - val configs = coursierConfigurations.value - - if (verbosityLevel >= 2) { - val finalDeps = Config.dependenciesWithConfig( - res, - depsByConfig.map { case (k, l) => k -> l.toSet }, - configs - ) - - val projCache = res.projectCache.mapValues { case (_, p) => p } - val repr = Print.dependenciesUnknownConfigs(finalDeps.toVector, projCache) - log.info(repr.split('\n').map(" " + _).mkString("\n")) - } - - val classifiers = - if (withClassifiers) - Some { - if (sbtClassifiers) - cm.classifiers - else - transitiveClassifiers.value - } - else - None - - val allArtifacts = - classifiers match { - case None => res.artifacts - case Some(cl) => res.classifiersArtifacts(cl) - } - artifactsLogger = createLogger() val artifactFileOrErrorTasks = allArtifacts.toVector.map { a => @@ -836,79 +843,81 @@ object Tasks { .map((a, _)) } - if (verbosityLevel >= 0) - log.info( + val artifactInitialMessage = + if (verbosityLevel >= 0) s"Fetching artifacts of $projectName" + (if (sbtClassifiers) " (sbt classifiers)" else "") - ) + else + "" - artifactsLogger.init() + if (verbosityLevel >= 1) + log.info(artifactInitialMessage) - val artifactFilesOrErrors = Task.gatherUnordered(artifactFileOrErrorTasks).attemptRun match { + artifactsLogger.init(if (verbosityLevel < 1) log.info(artifactInitialMessage)) + + Task.gatherUnordered(artifactFileOrErrorTasks).attemptRun match { case -\/(ex) => ResolutionError.UnknownDownloadException(ex) .throwException() case \/-(l) => l.toMap } - - if (verbosityLevel >= 0) - log.info( - s"Fetched artifacts of $projectName" + - (if (sbtClassifiers) " (sbt classifiers)" else "") - ) - - val artifactFiles = artifactFilesOrErrors.collect { - case (artifact, \/-(file)) => - artifact -> file - } - - val artifactErrors = artifactFilesOrErrors.toVector.collect { - case (_, -\/(err)) => - err - } - - if (artifactErrors.nonEmpty) { - val error = ResolutionError.DownloadErrors(artifactErrors) - - if (ignoreArtifactErrors) - log.warn(error.description(verbosityLevel >= 1)) - else - error.throwException() - } - - // can be non empty only if ignoreArtifactErrors is true - val erroredArtifacts = artifactFilesOrErrors.collect { - case (artifact, -\/(_)) => - artifact - }.toSet - - def artifactFileOpt(artifact: Artifact) = { - val artifact0 = artifact - .copy(attributes = Attributes()) // temporary hack :-( - val res = artifactFiles.get(artifact0) - - if (res.isEmpty && !erroredArtifacts(artifact0)) - log.error(s"${artifact.url} not downloaded (should not happen)") - - res - } - - writeIvyFiles() - - ToSbt.updateReport( - depsByConfig, - res, - configs, - classifiers, - artifactFileOpt - ) } finally { if (pool != null) pool.shutdown() if (artifactsLogger != null) - artifactsLogger.stop() + if ((artifactsLogger.stopDidPrintSomething() && verbosityLevel >= 0) || verbosityLevel >= 1) + log.info( + s"Fetched artifacts of $projectName" + + (if (sbtClassifiers) " (sbt classifiers)" else "") + ) } + + val artifactFiles = artifactFilesOrErrors.collect { + case (artifact, \/-(file)) => + artifact -> file + } + + val artifactErrors = artifactFilesOrErrors.toVector.collect { + case (_, -\/(err)) => + err + } + + if (artifactErrors.nonEmpty) { + val error = ResolutionError.DownloadErrors(artifactErrors) + + if (ignoreArtifactErrors) + log.warn(error.description(verbosityLevel >= 1)) + else + error.throwException() + } + + // can be non empty only if ignoreArtifactErrors is true + val erroredArtifacts = artifactFilesOrErrors.collect { + case (artifact, -\/(_)) => + artifact + }.toSet + + def artifactFileOpt(artifact: Artifact) = { + val artifact0 = artifact + .copy(attributes = Attributes()) // temporary hack :-( + val res = artifactFiles.get(artifact0) + + if (res.isEmpty && !erroredArtifacts(artifact0)) + log.error(s"${artifact.url} not downloaded (should not happen)") + + res + } + + writeIvyFiles() + + ToSbt.updateReport( + depsByConfig, + res, + configs, + classifiers, + artifactFileOpt + ) } reportsCache.getOrElseUpdate(