Merge pull request #217 from alexarchambault/topic/update-changing

Fix in update changing mode, add benchmark option, use SBT logging, ...
This commit is contained in:
Alexandre Archambault 2016-04-06 22:28:49 +02:00
commit a21ef67c8b
16 changed files with 338 additions and 131 deletions

View File

@ -146,6 +146,11 @@ lazy val core = crossProject
import com.typesafe.tools.mima.core.ProblemFilters._ import com.typesafe.tools.mima.core.ProblemFilters._
Seq( Seq(
// Since 1.0.0-M11
// Extra parameter with default value added, problem for forward compatibility only
ProblemFilters.exclude[MissingMethodProblem]("coursier.core.ResolutionProcess.next"),
// method made final (for - non critical - tail recursion)
ProblemFilters.exclude[FinalMethodProblem]("coursier.core.ResolutionProcess.next"),
// Since 1.0.0-M10 // Since 1.0.0-M10
ProblemFilters.exclude[IncompatibleResultTypeProblem]("coursier.core.Resolution.withParentConfigurations"), ProblemFilters.exclude[IncompatibleResultTypeProblem]("coursier.core.Resolution.withParentConfigurations"),
// New singleton object, problem for forward compatibility only // New singleton object, problem for forward compatibility only
@ -221,6 +226,10 @@ lazy val cache = project
import com.typesafe.tools.mima.core.ProblemFilters._ import com.typesafe.tools.mima.core.ProblemFilters._
Seq( Seq(
// Since 1.0.0-M11
// Add constructor parameter on FileError - shouldn't be built by users anyway
ProblemFilters.exclude[MissingMethodProblem]("coursier.FileError.this"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.FileError#Recoverable.this"),
// Since 1.0.0-M10 // Since 1.0.0-M10
// methods that should have been private anyway // methods that should have been private anyway
ProblemFilters.exclude[MissingMethodProblem]("coursier.TermDisplay.update"), ProblemFilters.exclude[MissingMethodProblem]("coursier.TermDisplay.update"),

View File

@ -43,8 +43,8 @@ object Cache {
} }
} }
private def withLocal(artifact: Artifact, cache: File): Artifact = { private def localFile(url: String, cache: File): File = {
def local(url: String) = val path =
if (url.startsWith("file:///")) if (url.startsWith("file:///"))
url.stripPrefix("file://") url.stripPrefix("file://")
else if (url.startsWith("file:/")) else if (url.startsWith("file:/"))
@ -68,19 +68,7 @@ object Cache {
throw new Exception(s"No protocol found in URL $url") throw new Exception(s"No protocol found in URL $url")
} }
if (artifact.extra.contains("local")) new File(path)
artifact
else
artifact.copy(extra = artifact.extra + ("local" ->
artifact.copy(
url = local(artifact.url),
checksumUrls = artifact.checksumUrls
.mapValues(local)
.toVector
.toMap,
extra = Map.empty
)
))
} }
private def readFullyTo( private def readFullyTo(
@ -296,30 +284,15 @@ object Cache {
implicit val pool0 = pool implicit val pool0 = pool
val artifact0 = withLocal(artifact, cache)
.extra
.getOrElse("local", artifact)
// Reference file - if it exists, and we get not found errors on some URLs, we assume // Reference file - if it exists, and we get not found errors on some URLs, we assume
// we can keep track of these missing, and not try to get them again later. // we can keep track of these missing, and not try to get them again later.
val referenceFileOpt = { val referenceFileOpt = artifact
val referenceOpt = artifact.extra.get("metadata").map(withLocal(_, cache)) .extra
val referenceOpt0 = referenceOpt.map(a => a.extra.getOrElse("local", a)) .get("metadata")
.map(a => localFile(a.url, cache))
referenceOpt0.map(a => new File(a.url))
}
def referenceFileExists: Boolean = referenceFileOpt.exists(_.exists()) def referenceFileExists: Boolean = referenceFileOpt.exists(_.exists())
val pairs =
Seq(artifact0.url -> artifact.url) ++ {
checksums
.intersect(artifact0.checksumUrls.keySet)
.intersect(artifact.checksumUrls.keySet)
.toSeq
.map(sumType => artifact0.checksumUrls(sumType) -> artifact.checksumUrls(sumType))
}
def urlConn(url0: String) = { def urlConn(url0: String) = {
val conn = url(url0).openConnection() // FIXME Should this be closed? val conn = url(url0).openConnection() // FIXME Should this be closed?
// Dummy user-agent instead of the default "Java/...", // Dummy user-agent instead of the default "Java/...",
@ -527,7 +500,7 @@ object Cache {
} }
cachePolicy match { cachePolicy match {
case CachePolicy.FetchMissing | CachePolicy.LocalOnly => case CachePolicy.FetchMissing | CachePolicy.LocalOnly | CachePolicy.LocalUpdate | CachePolicy.LocalUpdateChanging =>
validErrFileExists.flatMap { exists => validErrFileExists.flatMap { exists =>
if (exists) if (exists)
EitherT(Task.now(FileError.NotFound(url, Some(true)).left[Unit])) EitherT(Task.now(FileError.NotFound(url, Some(true)).left[Unit]))
@ -540,7 +513,7 @@ object Cache {
} }
} }
def checkFileExists(file: File, url: String): EitherT[Task, FileError, Unit] = def checkFileExists(file: File, url: String, log: Boolean = true): EitherT[Task, FileError, Unit] =
EitherT { EitherT {
Task { Task {
if (file.exists()) { if (file.exists()) {
@ -551,9 +524,17 @@ object Cache {
} }
} }
val urls =
artifact.url +: {
checksums
.intersect(artifact.checksumUrls.keySet)
.toSeq
.map(artifact.checksumUrls)
}
val tasks = val tasks =
for ((f, url) <- pairs) yield { for (url <- urls) yield {
val file = new File(f) val file = localFile(url, cache)
val res = val res =
if (url.startsWith("file:/")) { if (url.startsWith("file:/")) {
@ -576,6 +557,8 @@ object Cache {
val cachePolicy0 = cachePolicy match { val cachePolicy0 = cachePolicy match {
case CachePolicy.UpdateChanging if !artifact.changing => case CachePolicy.UpdateChanging if !artifact.changing =>
CachePolicy.FetchMissing CachePolicy.FetchMissing
case CachePolicy.LocalUpdateChanging if !artifact.changing =>
CachePolicy.LocalOnly
case other => case other =>
other other
} }
@ -583,6 +566,10 @@ object Cache {
cachePolicy0 match { cachePolicy0 match {
case CachePolicy.LocalOnly => case CachePolicy.LocalOnly =>
checkFileExists(file, url) checkFileExists(file, url)
case CachePolicy.LocalUpdateChanging | CachePolicy.LocalUpdate =>
checkFileExists(file, url, log = false).flatMap { _ =>
update
}
case CachePolicy.UpdateChanging | CachePolicy.Update => case CachePolicy.UpdateChanging | CachePolicy.Update =>
update update
case CachePolicy.FetchMissing => case CachePolicy.FetchMissing =>
@ -631,27 +618,26 @@ object Cache {
implicit val pool0 = pool implicit val pool0 = pool
val artifact0 = withLocal(artifact, cache) val localFile0 = localFile(artifact.url, cache)
.extra
.getOrElse("local", artifact)
EitherT { EitherT {
artifact0.checksumUrls.get(sumType) match { artifact.checksumUrls.get(sumType) match {
case Some(sumFile) => case Some(sumUrl) =>
val sumFile = localFile(sumUrl, cache)
Task { Task {
val sumOpt = parseChecksum( val sumOpt = parseChecksum(
new String(NioFiles.readAllBytes(new File(sumFile).toPath), "UTF-8") new String(NioFiles.readAllBytes(sumFile.toPath), "UTF-8")
) )
sumOpt match { sumOpt match {
case None => case None =>
FileError.ChecksumFormatError(sumType, sumFile).left FileError.ChecksumFormatError(sumType, sumFile.getPath).left
case Some(sum) => case Some(sum) =>
val md = MessageDigest.getInstance(sumType) val md = MessageDigest.getInstance(sumType)
val f = new File(artifact0.url) val is = new FileInputStream(localFile0)
val is = new FileInputStream(f)
try withContent(is, md.update(_, 0, _)) try withContent(is, md.update(_, 0, _))
finally is.close() finally is.close()
@ -665,14 +651,14 @@ object Cache {
sumType, sumType,
calculatedSum.toString(16), calculatedSum.toString(16),
sum.toString(16), sum.toString(16),
artifact0.url, localFile0.getPath,
sumFile sumFile.getPath
).left ).left
} }
} }
case None => case None =>
Task.now(FileError.ChecksumNotFound(sumType, artifact0.url).left) Task.now(FileError.ChecksumNotFound(sumType, localFile0.getPath).left)
} }
} }
} }

View File

@ -44,6 +44,10 @@ object CacheParse {
s.split(',').toVector.traverseU { s.split(',').toVector.traverseU {
case "offline" => case "offline" =>
Seq(CachePolicy.LocalOnly).successNel Seq(CachePolicy.LocalOnly).successNel
case "update-local-changing" =>
Seq(CachePolicy.LocalUpdateChanging).successNel
case "update-local" =>
Seq(CachePolicy.LocalUpdate).successNel
case "update-changing" => case "update-changing" =>
Seq(CachePolicy.UpdateChanging).successNel Seq(CachePolicy.UpdateChanging).successNel
case "update" => case "update" =>

View File

@ -4,6 +4,8 @@ sealed abstract class CachePolicy extends Product with Serializable
object CachePolicy { object CachePolicy {
case object LocalOnly extends CachePolicy case object LocalOnly extends CachePolicy
case object LocalUpdateChanging extends CachePolicy
case object LocalUpdate extends CachePolicy
case object UpdateChanging extends CachePolicy case object UpdateChanging extends CachePolicy
case object Update extends CachePolicy case object Update extends CachePolicy
case object FetchMissing extends CachePolicy case object FetchMissing extends CachePolicy

View File

@ -2,26 +2,41 @@ package coursier
import java.io.File import java.io.File
sealed abstract class FileError(val message: String) extends Product with Serializable sealed abstract class FileError(
val `type`: String,
val message: String
) extends Product with Serializable
object FileError { object FileError {
final case class DownloadError(reason: String) extends FileError(s"Download error: $reason") final case class DownloadError(reason: String) extends FileError(
"download error",
reason
)
final case class NotFound( final case class NotFound(
file: String, file: String,
permanent: Option[Boolean] = None permanent: Option[Boolean] = None
) extends FileError(s"Not found: $file") ) extends FileError(
"not found",
file
)
final case class ChecksumNotFound( final case class ChecksumNotFound(
sumType: String, sumType: String,
file: String file: String
) extends FileError(s"$sumType checksum not found: $file") ) extends FileError(
"checksum not found",
file
)
final case class ChecksumFormatError( final case class ChecksumFormatError(
sumType: String, sumType: String,
file: String file: String
) extends FileError(s"Unrecognized $sumType checksum format in $file") ) extends FileError(
"checksum format error",
file
)
final case class WrongChecksum( final case class WrongChecksum(
sumType: String, sumType: String,
@ -29,10 +44,22 @@ object FileError {
expected: String, expected: String,
file: String, file: String,
sumFile: String sumFile: String
) extends FileError(s"$sumType checksum validation failed: $file") ) extends FileError(
"wrong checksum",
file
)
sealed abstract class Recoverable(message: String) extends FileError(message) sealed abstract class Recoverable(
final case class Locked(file: File) extends Recoverable(s"Locked: $file") `type`: String,
final case class ConcurrentDownload(url: String) extends Recoverable(s"Concurrent download: $url") message: String
) extends FileError(`type`, message)
final case class Locked(file: File) extends Recoverable(
"locked",
file.toString
)
final case class ConcurrentDownload(url: String) extends Recoverable(
"concurrent download",
url
)
} }

View File

@ -56,10 +56,17 @@ object Terminal {
} }
object TermDisplay { object TermDisplay {
private def defaultFallbackMode: Boolean = { def defaultFallbackMode: Boolean = {
val env = sys.env.get("COURSIER_NO_TERM").nonEmpty val env0 = sys.env.get("COURSIER_PROGRESS").map(_.toLowerCase).collect {
case "true" | "enable" | "1" => true
case "false" | "disable" | "0" => false
}
def compatibilityEnv = sys.env.get("COURSIER_NO_TERM").nonEmpty
def nonInteractive = System.console() == null def nonInteractive = System.console() == null
val env = env0.getOrElse(compatibilityEnv)
env || nonInteractive env || nonInteractive
} }
@ -480,7 +487,7 @@ class TermDisplay(
} }
} }
updateThread.removeEntry(url, !newUpdate, s"Checked $url") { updateThread.removeEntry(url, !newUpdate, s"Checked $url\n") {
case info: CheckUpdateInfo => case info: CheckUpdateInfo =>
info.copy(remoteTimeOpt = remoteTimeOpt, isDone = true) info.copy(remoteTimeOpt = remoteTimeOpt, isDone = true)
case _ => case _ =>

View File

@ -9,6 +9,7 @@ import java.util.concurrent.Executors
import coursier.ivy.IvyRepository import coursier.ivy.IvyRepository
import coursier.util.{Print, Parse} import coursier.util.{Print, Parse}
import scala.annotation.tailrec
import scalaz.{Failure, Success, \/-, -\/} import scalaz.{Failure, Success, \/-, -\/}
import scalaz.concurrent.{ Task, Strategy } import scalaz.concurrent.{ Task, Strategy }
@ -201,9 +202,15 @@ class Helper(
filter = Some(dep => keepOptional || !dep.optional) filter = Some(dep => keepOptional || !dep.optional)
) )
val loggerFallbackMode =
!progress && TermDisplay.defaultFallbackMode
val logger = val logger =
if (verbosityLevel >= 0) if (verbosityLevel >= 0)
Some(new TermDisplay(new OutputStreamWriter(System.err))) Some(new TermDisplay(
new OutputStreamWriter(System.err),
fallbackMode = loggerFallbackMode
))
else else
None None
@ -238,10 +245,108 @@ class Helper(
logger.foreach(_.init()) logger.foreach(_.init())
val res = startRes val res =
.process if (benchmark > 0) {
.run(fetch0, maxIterations) class Counter(var value: Int = 0) {
.run def add(value: Int): Unit = {
this.value += value
}
}
def timed[T](name: String, counter: Counter, f: Task[T]): Task[T] =
Task(System.currentTimeMillis()).flatMap { start =>
f.map { t =>
val end = System.currentTimeMillis()
Console.err.println(s"$name: ${end - start} ms")
counter.add((end - start).toInt)
t
}
}
def helper(proc: ResolutionProcess, counter: Counter, iteration: Int): Task[Resolution] =
if (iteration >= maxIterations)
Task.now(proc.current)
else
proc match {
case _: core.Done =>
Task.now(proc.current)
case _ =>
val iterationType = proc match {
case _: core.Missing => "IO"
case _: core.Continue => "calculations"
case _ => ???
}
timed(
s"Iteration ${iteration + 1} ($iterationType)",
counter,
proc.next(fetch0, fastForward = false)).flatMap(helper(_, counter, iteration + 1)
)
}
def res = {
val iterationCounter = new Counter
val resolutionCounter = new Counter
val res0 = timed(
"Resolution",
resolutionCounter,
helper(
startRes.process,
iterationCounter,
0
)
).run
Console.err.println(s"Overhead: ${resolutionCounter.value - iterationCounter.value} ms")
res0
}
@tailrec
def result(warmUp: Int): Resolution =
if (warmUp >= benchmark) {
Console.err.println("Benchmark resolution")
res
} else {
Console.err.println(s"Warm-up ${warmUp + 1} / $benchmark")
res
result(warmUp + 1)
}
result(0)
} else if (benchmark < 0) {
def res(index: Int) = {
val start = System.currentTimeMillis()
val res0 = startRes
.process
.run(fetch0, maxIterations)
.run
val end = System.currentTimeMillis()
Console.err.println(s"Resolution ${index + 1} / ${-benchmark}: ${end - start} ms")
res0
}
@tailrec
def result(warmUp: Int): Resolution =
if (warmUp >= -benchmark) {
Console.err.println("Benchmark resolution")
res(warmUp)
} else {
Console.err.println(s"Warm-up ${warmUp + 1} / ${-benchmark}")
res(warmUp)
result(warmUp + 1)
}
result(0)
} else
startRes
.process
.run(fetch0, maxIterations)
.run
logger.foreach(_.stop()) logger.foreach(_.stop())
@ -299,13 +404,19 @@ class Helper(
): Seq[Artifact] = { ): Seq[Artifact] = {
if (subset == null && verbosityLevel >= 1) { if (subset == null && verbosityLevel >= 1) {
val msg = cachePolicies match { def isLocal(p: CachePolicy) = p match {
case Seq(CachePolicy.LocalOnly) => case CachePolicy.LocalOnly => true
" Checking artifacts" case CachePolicy.LocalUpdate => true
case _ => case CachePolicy.LocalUpdateChanging => true
" Fetching artifacts" case _ => false
} }
val msg =
if (cachePolicies.forall(isLocal))
" Checking artifacts"
else
" Fetching artifacts"
errPrintln(msg) errPrintln(msg)
} }
@ -333,7 +444,10 @@ class Helper(
val logger = val logger =
if (verbosityLevel >= 0) if (verbosityLevel >= 0)
Some(new TermDisplay(new OutputStreamWriter(System.err))) Some(new TermDisplay(
new OutputStreamWriter(System.err),
fallbackMode = loggerFallbackMode
))
else else
None None

View File

@ -18,6 +18,9 @@ case class CommonOptions(
@Help("Increase verbosity (specify several times to increase more)") @Help("Increase verbosity (specify several times to increase more)")
@Short("v") @Short("v")
verbose: Int @@ Counter, verbose: Int @@ Counter,
@Help("Force display of progress bars")
@Short("P")
progress: Boolean,
@Help("Maximum number of resolution iterations (specify a negative value for unlimited, default: 100)") @Help("Maximum number of resolution iterations (specify a negative value for unlimited, default: 100)")
@Short("N") @Short("N")
maxIterations: Int = 100, maxIterations: Int = 100,
@ -54,6 +57,10 @@ case class CommonOptions(
@Help("Checksums") @Help("Checksums")
@Value("checksum1,checksum2,... - end with none to allow for no checksum validation if none are available") @Value("checksum1,checksum2,... - end with none to allow for no checksum validation if none are available")
checksum: List[String], checksum: List[String],
@Help("Print the duration of each iteration of the resolution")
@Short("B")
@Value("Number of warm-up resolutions - if negative, doesn't print per iteration benchmark (less overhead)")
benchmark: Int,
@Recurse @Recurse
cacheOptions: CacheOptions cacheOptions: CacheOptions
) { ) {
@ -163,7 +170,7 @@ case class BootstrapOptions(
mainClass: String, mainClass: String,
@Short("o") @Short("o")
output: String = "bootstrap", output: String = "bootstrap",
@Short("D") @Short("d")
downloadDir: String, downloadDir: String,
@Short("f") @Short("f")
force: Boolean, force: Boolean,
@ -172,7 +179,7 @@ case class BootstrapOptions(
standalone: Boolean, standalone: Boolean,
@Help("Set Java properties in the generated launcher.") @Help("Set Java properties in the generated launcher.")
@Value("key=value") @Value("key=value")
@Short("P") @Short("D")
property: List[String], property: List[String],
@Help("Set Java command-line options in the generated launcher.") @Help("Set Java command-line options in the generated launcher.")
@Value("option") @Value("option")

View File

@ -33,8 +33,10 @@ sealed abstract class ResolutionProcess {
} }
} }
def next[F[_]]( @tailrec
fetch: Fetch.Metadata[F] final def next[F[_]](
fetch: Fetch.Metadata[F],
fastForward: Boolean = true
)(implicit )(implicit
F: Monad[F] F: Monad[F]
): F[ResolutionProcess] = { ): F[ResolutionProcess] = {
@ -45,7 +47,10 @@ sealed abstract class ResolutionProcess {
case missing0 @ Missing(missing, _, _) => case missing0 @ Missing(missing, _, _) =>
F.map(fetch(missing))(result => missing0.next(result)) F.map(fetch(missing))(result => missing0.next(result))
case cont @ Continue(_, _) => case cont @ Continue(_, _) =>
cont.nextNoCont.next(fetch) if (fastForward)
cont.nextNoCont.next(fetch, fastForward = fastForward)
else
F.point(cont.next)
} }
} }

View File

@ -219,7 +219,7 @@ case class MavenRepository(
} }
F.bind(findVersioning(module, version, None, fetch).run) { eitherProj => F.bind(findVersioning(module, version, None, fetch).run) { eitherProj =>
if (eitherProj.isLeft) if (eitherProj.isLeft && version.contains("-SNAPSHOT"))
F.map(withSnapshotVersioning.run)(eitherProj0 => F.map(withSnapshotVersioning.run)(eitherProj0 =>
if (eitherProj0.isLeft) if (eitherProj0.isLeft)
eitherProj eitherProj

View File

@ -49,7 +49,7 @@ object Parse {
case Right(modVer) => values += modVer case Right(modVer) => values += modVer
} }
(errors.toSeq, values.toSeq) (errors, values)
} }
/** /**
@ -77,7 +77,7 @@ object Parse {
.map((_, version)) .map((_, version))
case _ => case _ =>
Left(s"Malformed coordinates: $s") Left(s"Malformed dependency: $s")
} }
} }
@ -107,7 +107,7 @@ object Parse {
.map((_, version, None)) .map((_, version, None))
case _ => case _ =>
Left(s"Malformed coordinates: $s") Left(s"Malformed dependency: $s")
} }
} }

View File

@ -1,7 +1,5 @@
package coursier package coursier
import java.io.File
import sbt._ import sbt._
import sbt.Keys._ import sbt.Keys._
@ -43,7 +41,7 @@ object CoursierPlugin extends AutoPlugin {
coursierFallbackDependencies <<= Tasks.coursierFallbackDependenciesTask, coursierFallbackDependencies <<= Tasks.coursierFallbackDependenciesTask,
coursierCache := Cache.default, coursierCache := Cache.default,
update <<= Tasks.updateTask(withClassifiers = false), update <<= Tasks.updateTask(withClassifiers = false),
updateClassifiers <<= Tasks.updateTask(withClassifiers = true), updateClassifiers <<= Tasks.updateTask(withClassifiers = true, ignoreArtifactErrors = true),
updateSbtClassifiers in Defaults.TaskGlobal <<= Tasks.updateTask(withClassifiers = true, sbtClassifiers = true), updateSbtClassifiers in Defaults.TaskGlobal <<= Tasks.updateTask(withClassifiers = true, sbtClassifiers = true),
coursierProject <<= Tasks.coursierProjectTask, coursierProject <<= Tasks.coursierProjectTask,
coursierProjects <<= Tasks.coursierProjectsTask, coursierProjects <<= Tasks.coursierProjectsTask,

View File

@ -133,7 +133,11 @@ object FromSbt {
) )
} }
def repository(resolver: Resolver, ivyProperties: Map[String, String]): Option[Repository] = def repository(
resolver: Resolver,
ivyProperties: Map[String, String],
log: sbt.Logger
): Option[Repository] =
resolver match { resolver match {
case sbt.MavenRepository(_, root) => case sbt.MavenRepository(_, root) =>
try { try {
@ -142,10 +146,10 @@ object FromSbt {
Some(MavenRepository(root0, sbtAttrStub = true)) Some(MavenRepository(root0, sbtAttrStub = true))
} catch { } catch {
case e: MalformedURLException => case e: MalformedURLException =>
Console.err.println( log.warn(
"Warning: error parsing Maven repository base " + "Error parsing Maven repository base " +
root + root +
Option(e.getMessage).map(" ("+_+")").mkString + Option(e.getMessage).map(" (" + _ + ")").mkString +
", ignoring it" ", ignoring it"
) )
@ -177,7 +181,7 @@ object FromSbt {
)) ))
case other => case other =>
Console.err.println(s"Warning: unrecognized repository ${other.name}, ignoring it") log.warn(s"Unrecognized repository ${other.name}, ignoring it")
None None
} }

View File

@ -4,7 +4,11 @@ import scala.util.{Failure, Success, Try}
object Settings { object Settings {
private val baseDefaultVerbosityLevel = 0 private lazy val baseDefaultVerbosityLevel =
if (System.console() == null) // non interactive mode
0
else
1
def defaultVerbosityLevel: Int = { def defaultVerbosityLevel: Int = {

View File

@ -185,10 +185,11 @@ object Tasks {
Module("org.scala-lang", "scalap") -> scalaVersion Module("org.scala-lang", "scalap") -> scalaVersion
) )
def updateTask(withClassifiers: Boolean, sbtClassifiers: Boolean = false) = Def.task { def updateTask(
withClassifiers: Boolean,
// SBT logging should be better than that most of the time... sbtClassifiers: Boolean = false,
def errPrintln(s: String): Unit = scala.Console.err.println(s) ignoreArtifactErrors: Boolean = false
) = Def.task {
def grouped[K, V](map: Seq[(K, V)]): Map[K, Seq[V]] = def grouped[K, V](map: Seq[(K, V)]): Map[K, Seq[V]] =
map.groupBy { case (k, _) => k }.map { map.groupBy { case (k, _) => k }.map {
@ -252,6 +253,8 @@ object Tasks {
val cachePolicies = coursierCachePolicies.value val cachePolicies = coursierCachePolicies.value
val cache = coursierCache.value val cache = coursierCache.value
val log = streams.value.log
val sv = scalaVersion.value // is this always defined? (e.g. for Java only projects?) val sv = scalaVersion.value // is this always defined? (e.g. for Java only projects?)
val sbv = scalaBinaryVersion.value val sbv = scalaBinaryVersion.value
@ -267,7 +270,7 @@ object Tasks {
rule.configurations.nonEmpty || rule.configurations.nonEmpty ||
rule.crossVersion != sbt.CrossVersion.Disabled rule.crossVersion != sbt.CrossVersion.Disabled
) { ) {
Console.err.println(s"Warning: unsupported exclusion rule $rule") log.warn(s"Unsupported exclusion rule $rule")
anyNonSupportedExclusionRule = true anyNonSupportedExclusionRule = true
Nil Nil
} else } else
@ -275,7 +278,7 @@ object Tasks {
}.toSet }.toSet
if (anyNonSupportedExclusionRule) if (anyNonSupportedExclusionRule)
Console.err.println(s"Only supported exclusion rule fields: organization, name") log.warn("Only supported exclusion rule fields: organization, name")
val resolvers = val resolvers =
if (sbtClassifiers) if (sbtClassifiers)
@ -312,9 +315,9 @@ object Tasks {
} }
if (verbosityLevel >= 2) { if (verbosityLevel >= 2) {
println("InterProjectRepository") log.info("InterProjectRepository")
for (p <- projects) for (p <- projects)
println(s" ${p.module}:${p.version}") log.info(s" ${p.module}:${p.version}")
} }
val globalPluginsRepo = IvyRepository( val globalPluginsRepo = IvyRepository(
@ -335,7 +338,7 @@ object Tasks {
globalPluginsRepo, globalPluginsRepo,
interProjectRepo interProjectRepo
) ++ resolvers.flatMap( ) ++ resolvers.flatMap(
FromSbt.repository(_, ivyProperties) FromSbt.repository(_, ivyProperties, log)
) ++ { ) ++ {
if (fallbackDependencies.isEmpty) if (fallbackDependencies.isEmpty)
Nil Nil
@ -376,7 +379,7 @@ object Tasks {
s"${dep.module}:${dep.version}:${dep.configuration}" s"${dep.module}:${dep.version}:${dep.configuration}"
}.sorted.distinct }.sorted.distinct
if (verbosityLevel >= 1) { if (verbosityLevel >= 2) {
val repoReprs = repositories.map { val repoReprs = repositories.map {
case r: IvyRepository => case r: IvyRepository =>
s"ivy:${r.pattern}" s"ivy:${r.pattern}"
@ -389,14 +392,17 @@ object Tasks {
r.toString r.toString
} }
errPrintln(s"Repositories:\n${repoReprs.map(" "+_).mkString("\n")}") log.info(
"Repositories:\n" +
repoReprs.map(" " + _).mkString("\n")
)
} }
if (verbosityLevel >= 0) if (verbosityLevel >= 0)
errPrintln(s"Resolving ${currentProject.module.organization}:${currentProject.module.name}:${currentProject.version}") log.info(s"Resolving ${currentProject.module.organization}:${currentProject.module.name}:${currentProject.version}")
if (verbosityLevel >= 1) if (verbosityLevel >= 2)
for (depRepr <- depsRepr(currentProject.dependencies)) for (depRepr <- depsRepr(currentProject.dependencies))
errPrintln(s" $depRepr") log.info(s" $depRepr")
resLogger.init() resLogger.init()
@ -404,26 +410,36 @@ object Tasks {
.process .process
.run(fetch, maxIterations) .run(fetch, maxIterations)
.attemptRun .attemptRun
.leftMap(ex => throw new Exception(s"Exception during resolution", ex)) .leftMap(ex => throw new Exception("Exception during resolution", ex))
.merge .merge
resLogger.stop() resLogger.stop()
if (!res.isDone) if (!res.isDone)
throw new Exception(s"Maximum number of iteration of dependency resolution reached") throw new Exception("Maximum number of iteration of dependency resolution reached")
if (res.conflicts.nonEmpty) { if (res.conflicts.nonEmpty) {
val projCache = res.projectCache.mapValues { case (_, p) => p } val projCache = res.projectCache.mapValues { case (_, p) => p }
println(s"${res.conflicts.size} conflict(s):\n ${Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache)}") log.error(
throw new Exception(s"Conflict(s) in dependency resolution") s"${res.conflicts.size} conflict(s):\n" +
" " + Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache)
)
throw new Exception("Conflict(s) in dependency resolution")
} }
if (res.errors.nonEmpty) { if (res.errors.nonEmpty) {
println(s"\n${res.errors.size} error(s):") log.error(
for ((dep, errs) <- res.errors) { s"\n${res.errors.size} error(s):\n" +
println(s" ${dep.module}:${dep.version}:\n${errs.map(" " + _.replace("\n", " \n")).mkString("\n")}") res.errors.map {
} case (dep, errs) =>
s" ${dep.module}:${dep.version}:\n" +
errs
.map(" " + _.replace("\n", " \n"))
.mkString("\n")
}.mkString("\n")
)
throw new Exception(s"Encountered ${res.errors.length} error(s) in dependency resolution") throw new Exception(s"Encountered ${res.errors.length} error(s) in dependency resolution")
} }
@ -454,8 +470,8 @@ object Tasks {
} }
if (verbosityLevel >= 0) if (verbosityLevel >= 0)
errPrintln("Resolution done") log.info("Resolution done")
if (verbosityLevel >= 1) { if (verbosityLevel >= 2) {
val finalDeps = Config.dependenciesWithConfig( val finalDeps = Config.dependenciesWithConfig(
res, res,
depsByConfig.map { case (k, l) => k -> l.toSet }, depsByConfig.map { case (k, l) => k -> l.toSet },
@ -464,7 +480,7 @@ object Tasks {
val projCache = res.projectCache.mapValues { case (_, p) => p } val projCache = res.projectCache.mapValues { case (_, p) => p }
val repr = Print.dependenciesUnknownConfigs(finalDeps.toVector, projCache) val repr = Print.dependenciesUnknownConfigs(finalDeps.toVector, projCache)
println(repr.split('\n').map(" "+_).mkString("\n")) log.info(repr.split('\n').map(" "+_).mkString("\n"))
} }
val classifiers = val classifiers =
@ -504,13 +520,13 @@ object Tasks {
} }
if (verbosityLevel >= 0) if (verbosityLevel >= 0)
errPrintln(s"Fetching artifacts") log.info("Fetching artifacts")
artifactsLogger.init() artifactsLogger.init()
val artifactFilesOrErrors = Task.gatherUnordered(artifactFileOrErrorTasks).attemptRun match { val artifactFilesOrErrors = Task.gatherUnordered(artifactFileOrErrorTasks).attemptRun match {
case -\/(ex) => case -\/(ex) =>
throw new Exception(s"Error while downloading / verifying artifacts", ex) throw new Exception("Error while downloading / verifying artifacts", ex)
case \/-(l) => case \/-(l) =>
l.toMap l.toMap
} }
@ -518,20 +534,44 @@ object Tasks {
artifactsLogger.stop() artifactsLogger.stop()
if (verbosityLevel >= 0) if (verbosityLevel >= 0)
errPrintln(s"Fetching artifacts: done") log.info("Fetching artifacts: done")
val artifactFiles = artifactFilesOrErrors.collect {
case (artifact, \/-(file)) =>
artifact -> file
}
val artifactErrors = artifactFilesOrErrors.toVector.collect {
case (_, -\/(err)) =>
err
}
if (artifactErrors.nonEmpty) {
val groupedArtifactErrors = artifactErrors
.groupBy(_.`type`)
.mapValues(_.map(_.message).sorted)
.toVector
.sortBy(_._1)
for ((type0, errors) <- groupedArtifactErrors) {
log.error(s"${errors.size} $type0")
if (!ignoreArtifactErrors || verbosityLevel >= 1)
for (err <- errors)
log.error(" " + err)
}
if (!ignoreArtifactErrors)
throw new Exception(s"Encountered ${artifactErrors.length} errors (see above messages)")
}
def artifactFileOpt(artifact: Artifact) = { def artifactFileOpt(artifact: Artifact) = {
val fileOrError = artifactFilesOrErrors.getOrElse(artifact, -\/("Not downloaded")) val res = artifactFiles.get(artifact)
fileOrError match { if (res.isEmpty)
case \/-(file) => log.error(s"${artifact.url} not downloaded (should not happen)")
if (file.toString.contains("file:/"))
throw new Exception(s"Wrong path: $file") res
Some(file)
case -\/(err) =>
errPrintln(s"${artifact.url}: $err")
None
}
} }
writeIvyFiles() writeIvyFiles()

View File

@ -1,9 +1,9 @@
addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.6.8") addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.6.8")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.7") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.8")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.1.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.1.0")
addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0") addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.4.0")
addSbtPlugin("com.github.alexarchambault" % "coursier-sbt-plugin" % "1.0.0-M8") addSbtPlugin("com.github.alexarchambault" % "coursier-sbt-plugin" % "1.0.0-M10")
addSbtPlugin("com.typesafe.sbt" % "sbt-proguard" % "0.2.2") addSbtPlugin("com.typesafe.sbt" % "sbt-proguard" % "0.2.2")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.8") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.8")
libraryDependencies += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value libraryDependencies += "org.scala-sbt" % "scripted-plugin" % sbtVersion.value