sbt/cli/src/main/scala-2.11/coursier/cli/Helper.scala

489 lines
13 KiB
Scala

package coursier
package cli
import java.io.{ OutputStreamWriter, File }
import java.net.URL
import java.util.jar.{ Manifest => JManifest }
import java.util.concurrent.Executors
import coursier.ivy.IvyRepository
import coursier.util.{Print, Parse}
import scala.annotation.tailrec
import scalaz.{Failure, Success, \/-, -\/}
import scalaz.concurrent.{ Task, Strategy }
object Helper {
def fileRepr(f: File) = f.toString
def errPrintln(s: String) = Console.err.println(s)
private val manifestPath = "META-INF/MANIFEST.MF"
def mainClasses(cl: ClassLoader): Map[(String, String), String] = {
import scala.collection.JavaConverters._
val parentMetaInfs = Option(cl.getParent).fold(Set.empty[URL]) { parent =>
parent.getResources(manifestPath).asScala.toSet
}
val allMetaInfs = cl.getResources(manifestPath).asScala.toVector
val metaInfs = allMetaInfs.filterNot(parentMetaInfs)
val mainClasses = metaInfs.flatMap { url =>
val attributes = new JManifest(url.openStream()).getMainAttributes
def attributeOpt(name: String) =
Option(attributes.getValue(name))
val vendor = attributeOpt("Specification-Vendor").getOrElse("")
val title = attributeOpt("Specification-Title").getOrElse("")
val mainClass = attributeOpt("Main-Class")
mainClass.map((vendor, title) -> _)
}
mainClasses.toMap
}
}
object Util {
def prematureExit(msg: String): Nothing = {
Console.err.println(msg)
sys.exit(255)
}
def prematureExitIf(cond: Boolean)(msg: => String): Unit =
if (cond)
prematureExit(msg)
def exit(msg: String): Nothing = {
Console.err.println(msg)
sys.exit(1)
}
def exitIf(cond: Boolean)(msg: => String): Unit =
if (cond)
exit(msg)
}
class Helper(
common: CommonOptions,
rawDependencies: Seq[String],
printResultStdout: Boolean = false,
ignoreErrors: Boolean = false
) {
import common._
import Helper.errPrintln
import Util._
val cachePoliciesValidation = CacheParse.cachePolicies(common.mode)
val cachePolicies = cachePoliciesValidation match {
case Success(cp) => cp
case Failure(errors) =>
prematureExit(
s"Error parsing modes:\n${errors.list.map(" "+_).mkString("\n")}"
)
}
val cache = new File(cacheOptions.cache)
val pool = Executors.newFixedThreadPool(parallel, Strategy.DefaultDaemonThreadFactory)
val defaultRepositories = Seq(
Cache.ivy2Local,
MavenRepository("https://repo1.maven.org/maven2")
)
val repositoriesValidation = CacheParse.repositories(common.repository).map { repos0 =>
var repos = (if (common.noDefault) Nil else defaultRepositories) ++ repos0
if (common.sbtPluginHack)
repos = repos.map {
case m: MavenRepository => m.copy(sbtAttrStub = true)
case other => other
}
if (common.dropInfoAttr)
repos = repos.map {
case m: IvyRepository => m.copy(dropInfoAttributes = true)
case other => other
}
repos
}
val repositories = repositoriesValidation match {
case Success(repos) => repos
case Failure(errors) =>
prematureExit(
s"Error parsing repositories:\n${errors.list.map(" "+_).mkString("\n")}"
)
}
val (modVerCfgErrors, moduleVersionConfigs) = Parse.moduleVersionConfigs(rawDependencies)
prematureExitIf(modVerCfgErrors.nonEmpty) {
s"Cannot parse dependencies:\n" + modVerCfgErrors.map(" "+_).mkString("\n")
}
val (forceVersionErrors, forceVersions0) = Parse.moduleVersions(forceVersion)
prematureExitIf(forceVersionErrors.nonEmpty) {
s"Cannot parse forced versions:\n" + forceVersionErrors.map(" "+_).mkString("\n")
}
val forceVersions = {
val grouped = forceVersions0
.groupBy { case (mod, _) => mod }
.map { case (mod, l) => mod -> l.map { case (_, version) => version } }
for ((mod, forcedVersions) <- grouped if forcedVersions.distinct.lengthCompare(1) > 0)
errPrintln(s"Warning: version of $mod forced several times, using only the last one (${forcedVersions.last})")
grouped.map { case (mod, versions) => mod -> versions.last }
}
val (excludeErrors, excludes0) = Parse.modules(exclude)
prematureExitIf(excludeErrors.nonEmpty) {
s"Cannot parse excluded modules:\n" +
excludeErrors
.map(" " + _)
.mkString("\n")
}
val (excludesNoAttr, excludesWithAttr) = excludes0.partition(_.attributes.isEmpty)
prematureExitIf(excludesWithAttr.nonEmpty) {
s"Excluded modules with attributes not supported:\n" +
excludesWithAttr
.map(" " + _)
.mkString("\n")
}
val excludes = excludesNoAttr.map { mod =>
(mod.organization, mod.name)
}.toSet
val dependencies = moduleVersionConfigs.map {
case (module, version, configOpt) =>
Dependency(
module,
version,
configuration = configOpt.getOrElse(defaultConfiguration),
exclusions = excludes,
transitive = !intransitive
)
}
val checksums = {
val splitChecksumArgs = checksum.flatMap(_.split(',')).filter(_.nonEmpty)
if (splitChecksumArgs.isEmpty)
Cache.defaultChecksums
else
splitChecksumArgs.map {
case none if none.toLowerCase == "none" => None
case sumType => Some(sumType)
}
}
val startRes = Resolution(
dependencies.toSet,
forceVersions = forceVersions,
filter = Some(dep => keepOptional || !dep.optional)
)
val loggerFallbackMode =
!progress && TermDisplay.defaultFallbackMode
val logger =
if (verbosityLevel >= 0)
Some(new TermDisplay(
new OutputStreamWriter(System.err),
fallbackMode = loggerFallbackMode
))
else
None
val fetchs = cachePolicies.map(p =>
Cache.fetch(cache, p, checksums = checksums, logger = logger, pool = pool)
)
val fetchQuiet = coursier.Fetch.from(
repositories,
fetchs.head,
fetchs.tail: _*
)
val fetch0 =
if (verbosityLevel >= 2) {
modVers: Seq[(Module, String)] =>
val print = Task {
errPrintln(s"Getting ${modVers.length} project definition(s)")
}
print.flatMap(_ => fetchQuiet(modVers))
} else
fetchQuiet
if (verbosityLevel >= 1) {
errPrintln(s" Dependencies:\n${Print.dependenciesUnknownConfigs(dependencies, Map.empty)}")
if (forceVersions.nonEmpty) {
errPrintln(" Force versions:")
for ((mod, ver) <- forceVersions.toVector.sortBy { case (mod, _) => mod.toString })
errPrintln(s"$mod:$ver")
}
}
logger.foreach(_.init())
val res =
if (benchmark > 0) {
class Counter(var value: Int = 0) {
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())
val trDeps = res.minDependencies.toVector
lazy val projCache = res.projectCache.mapValues { case (_, p) => p }
if (printResultStdout || verbosityLevel >= 1 || tree || reverseTree) {
if ((printResultStdout && verbosityLevel >= 1) || verbosityLevel >= 2 || tree || reverseTree)
errPrintln(s" Result:")
val depsStr =
if (reverseTree || tree)
Print.dependencyTree(dependencies, res, printExclusions = verbosityLevel >= 1, reverse = reverseTree)
else
Print.dependenciesUnknownConfigs(trDeps, projCache)
if (printResultStdout)
println(depsStr)
else
errPrintln(depsStr)
}
var anyError = false
if (!res.isDone) {
anyError = true
errPrintln("\nMaximum number of iterations reached!")
}
if (res.errors.nonEmpty) {
anyError = true
errPrintln(
s"\nError:\n" +
res.errors.map {
case (dep, errs) =>
s" ${dep.module}:${dep.version}:\n${errs.map(" " + _.replace("\n", " \n")).mkString("\n")}"
}.mkString("\n")
)
}
if (res.conflicts.nonEmpty) {
anyError = true
errPrintln(
s"\nConflict:\n" +
Print.dependenciesUnknownConfigs(res.conflicts.toVector, projCache)
)
}
if (anyError) {
if (ignoreErrors)
errPrintln("Ignoring errors")
else
sys.exit(1)
}
def artifacts(
sources: Boolean,
javadoc: Boolean,
subset: Set[Dependency] = null
): Seq[Artifact] = {
if (subset == null && verbosityLevel >= 1) {
def isLocal(p: CachePolicy) = p match {
case CachePolicy.LocalOnly => true
case CachePolicy.LocalUpdate => true
case CachePolicy.LocalUpdateChanging => true
case _ => false
}
val msg =
if (cachePolicies.forall(isLocal))
" Checking artifacts"
else
" Fetching artifacts"
errPrintln(msg)
}
val res0 = Option(subset).fold(res)(res.subset)
if (classifier0.nonEmpty || sources || javadoc) {
var classifiers = classifier0
if (sources)
classifiers = classifiers :+ "sources"
if (javadoc)
classifiers = classifiers :+ "javadoc"
res0.classifiersArtifacts(classifiers.distinct)
} else
res0.artifacts
}
def fetch(
sources: Boolean,
javadoc: Boolean,
subset: Set[Dependency] = null
): Seq[File] = {
val artifacts0 = artifacts(sources, javadoc, subset)
val logger =
if (verbosityLevel >= 0)
Some(new TermDisplay(
new OutputStreamWriter(System.err),
fallbackMode = loggerFallbackMode
))
else
None
if (verbosityLevel >= 1 && artifacts0.nonEmpty)
println(s" Found ${artifacts0.length} artifacts")
val tasks = artifacts0.map(artifact =>
(Cache.file(artifact, cache, cachePolicies.head, checksums = checksums, logger = logger, pool = pool) /: cachePolicies.tail)(
_ orElse Cache.file(artifact, cache, _, checksums = checksums, logger = logger, pool = pool)
).run.map(artifact.->)
)
logger.foreach(_.init())
val task = Task.gatherUnordered(tasks)
val results = task.run
val errors = results.collect{case (artifact, -\/(err)) => artifact -> err }
val files0 = results.collect{case (artifact, \/-(f)) => f }
logger.foreach(_.stop())
exitIf(errors.nonEmpty) {
s" Error:\n" +
errors.map { case (artifact, error) =>
s"${artifact.url}: $error"
}.mkString("\n")
}
files0
}
}