mirror of https://github.com/sbt/sbt.git
Switch to case-app 1.0.0-SNAPSHOT (for commands)
This commit is contained in:
parent
51cc215b88
commit
09d58e9bd7
|
|
@ -49,7 +49,8 @@ lazy val commonSettings = Seq(
|
||||||
crossScalaVersions := Seq("2.10.6", "2.11.7"),
|
crossScalaVersions := Seq("2.10.6", "2.11.7"),
|
||||||
resolvers ++= Seq(
|
resolvers ++= Seq(
|
||||||
"Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases",
|
"Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases",
|
||||||
Resolver.sonatypeRepo("releases")
|
Resolver.sonatypeRepo("releases"),
|
||||||
|
Resolver.sonatypeRepo("snapshots")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -113,7 +114,7 @@ lazy val cli = project
|
||||||
.settings(
|
.settings(
|
||||||
name := "coursier-cli",
|
name := "coursier-cli",
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"com.github.alexarchambault" %% "case-app" % "0.3.0",
|
"com.github.alexarchambault" %% "case-app" % "1.0.0-SNAPSHOT",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.1.3"
|
"ch.qos.logback" % "logback-classic" % "1.1.3"
|
||||||
) ++ {
|
) ++ {
|
||||||
if (scalaVersion.value startsWith "2.10.")
|
if (scalaVersion.value startsWith "2.10.")
|
||||||
|
|
|
||||||
|
|
@ -2,329 +2,240 @@ package coursier
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
|
||||||
import caseapp._
|
import caseapp._
|
||||||
import coursier.core.{ MavenRepository, Parse, CachePolicy }
|
|
||||||
|
|
||||||
import scalaz.{ \/-, -\/ }
|
case class CommonOptions(
|
||||||
import scalaz.concurrent.Task
|
|
||||||
|
|
||||||
case class Coursier(
|
|
||||||
@HelpMessage("Keep optional dependencies (Maven)")
|
@HelpMessage("Keep optional dependencies (Maven)")
|
||||||
keepOptional: Boolean,
|
keepOptional: Boolean,
|
||||||
@HelpMessage("Fetch main artifacts (default: true if --classpath is specified and sources and javadoc are not fetched, else false)")
|
|
||||||
@ExtraName("J")
|
|
||||||
default: Boolean,
|
|
||||||
@HelpMessage("Fetch source artifacts")
|
|
||||||
@ExtraName("S")
|
|
||||||
sources: Boolean,
|
|
||||||
@HelpMessage("Fetch javadoc artifacts")
|
|
||||||
@ExtraName("D")
|
|
||||||
javadoc: Boolean,
|
|
||||||
@HelpMessage("Print java -cp compatible classpath (use like java -cp $(coursier -P ..dependencies..) )")
|
|
||||||
@ExtraName("P")
|
|
||||||
@ExtraName("cp")
|
|
||||||
classpath: Boolean,
|
|
||||||
@HelpMessage("Off-line mode: only use cache and local repositories")
|
@HelpMessage("Off-line mode: only use cache and local repositories")
|
||||||
@ExtraName("c")
|
@ExtraName("c")
|
||||||
offline: Boolean,
|
offline: Boolean,
|
||||||
@HelpMessage("Force download: for remote repositories only: re-download items, that is, don't use cache directly")
|
@HelpMessage("Force download: for remote repositories only: re-download items, that is, don't use cache directly")
|
||||||
@ExtraName("f")
|
@ExtraName("f")
|
||||||
force: Boolean,
|
force: Boolean,
|
||||||
@HelpMessage("Quiet output")
|
@HelpMessage("Quiet output")
|
||||||
@ExtraName("q")
|
@ExtraName("q")
|
||||||
quiet: Boolean,
|
quiet: Boolean,
|
||||||
@HelpMessage("Increase verbosity (specify several times to increase more)")
|
@HelpMessage("Increase verbosity (specify several times to increase more)")
|
||||||
@ExtraName("v")
|
@ExtraName("v")
|
||||||
verbose: List[Unit],
|
verbose: List[Unit],
|
||||||
@HelpMessage("Maximum number of resolution iterations (specify a negative value for unlimited, default: 100)")
|
@HelpMessage("Maximum number of resolution iterations (specify a negative value for unlimited, default: 100)")
|
||||||
@ExtraName("N")
|
@ExtraName("N")
|
||||||
maxIterations: Int = 100,
|
maxIterations: Int = 100,
|
||||||
@HelpMessage("Repositories - for multiple repositories, separate with comma and/or repeat this option (e.g. -r central,ivy2local -r sonatype-snapshots, or equivalently -r central,ivy2local,sonatype-snapshots)")
|
@HelpMessage("Repositories - for multiple repositories, separate with comma and/or repeat this option (e.g. -r central,ivy2local -r sonatype-snapshots, or equivalently -r central,ivy2local,sonatype-snapshots)")
|
||||||
@ExtraName("r")
|
@ExtraName("r")
|
||||||
repository: List[String],
|
repository: List[String],
|
||||||
@HelpMessage("Maximim number of parallel downloads (default: 6)")
|
@HelpMessage("Maximum number of parallel downloads (default: 6)")
|
||||||
@ExtraName("n")
|
@ExtraName("n")
|
||||||
parallel: Int = 6
|
parallel: Int = 6
|
||||||
) extends App {
|
) {
|
||||||
|
val verbose0 = verbose.length + (if (quiet) 1 else 0)
|
||||||
|
}
|
||||||
|
|
||||||
val verbose0 = {
|
sealed trait Command extends App
|
||||||
verbose.length +
|
|
||||||
(if (quiet) 1 else 0)
|
case class Fetch(
|
||||||
|
@HelpMessage("Fetch source artifacts")
|
||||||
|
@ExtraName("S")
|
||||||
|
sources: Boolean,
|
||||||
|
@HelpMessage("Fetch javadoc artifacts")
|
||||||
|
@ExtraName("D")
|
||||||
|
javadoc: Boolean,
|
||||||
|
@Recurse
|
||||||
|
common: CommonOptions
|
||||||
|
) extends Command {
|
||||||
|
|
||||||
|
val helper = new Helper(common, remainingArgs)
|
||||||
|
|
||||||
|
val files0 = helper.fetch(main = true, sources = false, javadoc = false)
|
||||||
|
|
||||||
|
Console.out.println(
|
||||||
|
files0
|
||||||
|
.map(_.toString)
|
||||||
|
.mkString("\n")
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Launch(
|
||||||
|
@HelpMessage("If -L or --launch is specified, the main class to launch")
|
||||||
|
@ExtraName("M")
|
||||||
|
@ExtraName("main")
|
||||||
|
mainClass: String,
|
||||||
|
@Recurse
|
||||||
|
common: CommonOptions
|
||||||
|
) extends Command {
|
||||||
|
|
||||||
|
val (rawDependencies, extraArgs) = {
|
||||||
|
val idxOpt = Some(remainingArgs.indexOf("--")).filter(_ >= 0)
|
||||||
|
idxOpt.fold((remainingArgs, Seq.empty[String])) { idx =>
|
||||||
|
val (l, r) = remainingArgs.splitAt(idx)
|
||||||
|
assert(r.nonEmpty)
|
||||||
|
(l, r.tail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def fileRepr(f: File) = f.toString
|
val helper = new Helper(common, rawDependencies)
|
||||||
|
|
||||||
def println(s: String) = Console.err.println(s)
|
val files0 = helper.fetch(main = true, sources = false, javadoc = false)
|
||||||
|
|
||||||
|
|
||||||
if (force && offline) {
|
def printParents(cl: ClassLoader): Unit =
|
||||||
println("Error: --offline (-c) and --force (-f) options can't be specified at the same time.")
|
Option(cl.getParent) match {
|
||||||
|
case None =>
|
||||||
|
case Some(cl0) =>
|
||||||
|
println(cl0.toString)
|
||||||
|
printParents(cl0)
|
||||||
|
}
|
||||||
|
|
||||||
|
printParents(Thread.currentThread().getContextClassLoader)
|
||||||
|
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
val cl = new URLClassLoader(
|
||||||
|
files0.map(_.toURI.toURL).toArray,
|
||||||
|
Thread.currentThread().getContextClassLoader // setting this to null provokes strange things (wrt terminal, ...)
|
||||||
|
)
|
||||||
|
|
||||||
|
val mainClass0 =
|
||||||
|
if (mainClass.nonEmpty)
|
||||||
|
mainClass
|
||||||
|
else {
|
||||||
|
val metaInfs = cl.findResources("META-INF/MANIFEST.MF").asScala.toVector
|
||||||
|
val mainClasses = metaInfs.flatMap { url =>
|
||||||
|
Option(new java.util.jar.Manifest(url.openStream()).getMainAttributes.getValue("Main-Class"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainClasses.isEmpty) {
|
||||||
|
println(s"No main class found. Specify one with -M or --main.")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (common.verbose0 >= 0)
|
||||||
|
println(s"Found ${mainClasses.length} main class(es):\n${mainClasses.map(" " + _).mkString("\n")}")
|
||||||
|
|
||||||
|
mainClasses.head
|
||||||
|
}
|
||||||
|
|
||||||
|
val cls =
|
||||||
|
try cl.loadClass(mainClass0)
|
||||||
|
catch { case e: ClassNotFoundException =>
|
||||||
|
println(s"Error: class $mainClass0 not found")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
val method =
|
||||||
|
try cls.getMethod("main", classOf[Array[String]])
|
||||||
|
catch { case e: NoSuchMethodError =>
|
||||||
|
println(s"Error: method main not found in $mainClass0")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (common.verbose0 >= 1)
|
||||||
|
println(s"Calling $mainClass0 ${extraArgs.mkString(" ")}")
|
||||||
|
|
||||||
|
Thread.currentThread().setContextClassLoader(cl)
|
||||||
|
method.invoke(null, extraArgs.toArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Classpath(
|
||||||
|
@Recurse
|
||||||
|
common: CommonOptions
|
||||||
|
) extends Command {
|
||||||
|
|
||||||
|
val helper = new Helper(common, remainingArgs)
|
||||||
|
|
||||||
|
val files0 = helper.fetch(main = true, sources = false, javadoc = false)
|
||||||
|
|
||||||
|
Console.out.println(
|
||||||
|
files0
|
||||||
|
.map(_.toString)
|
||||||
|
.mkString(File.pathSeparator)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: allow removing a repository (with confirmations, etc.)
|
||||||
|
case class Repository(
|
||||||
|
@ValueDescription("id:baseUrl")
|
||||||
|
@ExtraName("a")
|
||||||
|
add: List[String],
|
||||||
|
@ExtraName("L")
|
||||||
|
list: Boolean,
|
||||||
|
@ExtraName("l")
|
||||||
|
defaultList: Boolean,
|
||||||
|
ivyLike: Boolean
|
||||||
|
) extends Command {
|
||||||
|
|
||||||
|
if (add.exists(!_.contains(":"))) {
|
||||||
|
CaseApp.printUsage[Repository](err = true)
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parallel <= 0) {
|
val add0 = add
|
||||||
println(s"Error: invalid --parallel (-n) value: $parallel")
|
.map{ s =>
|
||||||
|
val Seq(id, baseUrl) = s.split(":", 2).toSeq
|
||||||
|
id -> baseUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
add0.exists(_._1.contains("/")) ||
|
||||||
|
add0.exists(_._1.startsWith(".")) ||
|
||||||
|
add0.exists(_._1.isEmpty)
|
||||||
|
) {
|
||||||
|
CaseApp.printUsage[Repository](err = true)
|
||||||
|
sys.exit(255)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def defaultLogger: MavenRepository.Logger with Files.Logger =
|
|
||||||
new MavenRepository.Logger with Files.Logger {
|
|
||||||
def downloading(url: String) =
|
|
||||||
println(s"Downloading $url")
|
|
||||||
def downloaded(url: String, success: Boolean) =
|
|
||||||
if (!success)
|
|
||||||
println(s"Failed: $url")
|
|
||||||
def readingFromCache(f: File) = {}
|
|
||||||
def puttingInCache(f: File) = {}
|
|
||||||
|
|
||||||
def foundLocally(f: File) = {}
|
|
||||||
def downloadingArtifact(url: String) =
|
|
||||||
println(s"Downloading $url")
|
|
||||||
def downloadedArtifact(url: String, success: Boolean) =
|
|
||||||
if (!success)
|
|
||||||
println(s"Failed: $url")
|
|
||||||
}
|
|
||||||
|
|
||||||
def verboseLogger: MavenRepository.Logger with Files.Logger =
|
|
||||||
new MavenRepository.Logger with Files.Logger {
|
|
||||||
def downloading(url: String) =
|
|
||||||
println(s"Downloading $url")
|
|
||||||
def downloaded(url: String, success: Boolean) =
|
|
||||||
println(
|
|
||||||
if (success) s"Downloaded $url"
|
|
||||||
else s"Failed: $url"
|
|
||||||
)
|
|
||||||
def readingFromCache(f: File) = {
|
|
||||||
println(s"Reading ${fileRepr(f)} from cache")
|
|
||||||
}
|
|
||||||
def puttingInCache(f: File) =
|
|
||||||
println(s"Writing ${fileRepr(f)} in cache")
|
|
||||||
|
|
||||||
def foundLocally(f: File) =
|
|
||||||
println(s"Found locally ${fileRepr(f)}")
|
|
||||||
def downloadingArtifact(url: String) =
|
|
||||||
println(s"Downloading $url")
|
|
||||||
def downloadedArtifact(url: String, success: Boolean) =
|
|
||||||
println(
|
|
||||||
if (success) s"Downloaded $url"
|
|
||||||
else s"Failed: $url"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val logger =
|
|
||||||
if (verbose0 < 0)
|
|
||||||
None
|
|
||||||
else if (verbose0 == 0)
|
|
||||||
Some(defaultLogger)
|
|
||||||
else
|
|
||||||
Some(verboseLogger)
|
|
||||||
|
|
||||||
implicit val cachePolicy =
|
|
||||||
if (offline)
|
|
||||||
CachePolicy.LocalOnly
|
|
||||||
else if (force)
|
|
||||||
CachePolicy.ForceDownload
|
|
||||||
else
|
|
||||||
CachePolicy.Default
|
|
||||||
|
|
||||||
val cache = Cache.default
|
val cache = Cache.default
|
||||||
cache.init(verbose = verbose0 >= 0)
|
|
||||||
|
|
||||||
val repositoryIds = {
|
|
||||||
val repository0 = repository
|
|
||||||
.flatMap(_.split(','))
|
|
||||||
.map(_.trim)
|
|
||||||
.filter(_.nonEmpty)
|
|
||||||
|
|
||||||
if (repository0.isEmpty)
|
|
||||||
cache.default()
|
|
||||||
else
|
|
||||||
repository0
|
|
||||||
}
|
|
||||||
|
|
||||||
val repoMap = cache.map()
|
|
||||||
|
|
||||||
if (repositoryIds.exists(!repoMap.contains(_))) {
|
|
||||||
val notFound = repositoryIds
|
|
||||||
.filter(!repoMap.contains(_))
|
|
||||||
|
|
||||||
Console.err.println(
|
|
||||||
(if (notFound.lengthCompare(1) == 1) "Repository" else "Repositories") +
|
|
||||||
" not found: " +
|
|
||||||
notFound.mkString(", ")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if (cache.cache.exists() && !cache.cache.isDirectory) {
|
||||||
|
Console.err.println(s"Error: ${cache.cache} not a directory")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val (repositories0, fileCaches) = repositoryIds
|
if (!cache.cache.exists())
|
||||||
.map(repoMap)
|
cache.init(verbose = true)
|
||||||
.unzip
|
|
||||||
|
|
||||||
val repositories = repositories0
|
val current = cache.list().map(_._1).toSet
|
||||||
.map(_.copy(logger = logger))
|
|
||||||
|
|
||||||
|
val alreadyAdded = add0
|
||||||
|
.map(_._1)
|
||||||
|
.filter(current)
|
||||||
|
|
||||||
val (splitDependencies, malformed) = remainingArgs.toList
|
if (alreadyAdded.nonEmpty) {
|
||||||
.map(_.split(":", 3).toSeq)
|
Console.err.println(s"Error: already added: ${alreadyAdded.mkString(", ")}")
|
||||||
.partition(_.length == 3)
|
sys.exit(1)
|
||||||
|
|
||||||
if (splitDependencies.isEmpty) {
|
|
||||||
CaseApp.printUsage[Coursier]()
|
|
||||||
sys exit 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (malformed.nonEmpty) {
|
for ((id, baseUrl0) <- add0) {
|
||||||
println(s"Malformed dependencies:\n${malformed.map(_.mkString(":")).mkString("\n")}")
|
val baseUrl =
|
||||||
sys exit 1
|
if (baseUrl0.endsWith("/"))
|
||||||
|
baseUrl0
|
||||||
|
else
|
||||||
|
baseUrl0 + "/"
|
||||||
|
|
||||||
|
cache.add(id, baseUrl, ivyLike = ivyLike)
|
||||||
}
|
}
|
||||||
|
|
||||||
val moduleVersions = splitDependencies.map{
|
if (defaultList) {
|
||||||
case Seq(org, name, version) =>
|
val map = cache.repositoryMap()
|
||||||
(Module(org, name), version)
|
|
||||||
}
|
|
||||||
|
|
||||||
val deps = moduleVersions.map{case (mod, ver) =>
|
for (id <- cache.default(withNotFound = true))
|
||||||
Dependency(mod, ver, scope = Scope.Runtime)
|
map.get(id) match {
|
||||||
}
|
case Some(repo) =>
|
||||||
|
println(s"$id: ${repo.root}" + (if (repo.ivyLike) " (Ivy-like)" else ""))
|
||||||
val startRes = Resolution(
|
case None =>
|
||||||
deps.toSet,
|
println(s"$id (not found)")
|
||||||
filter = Some(dep => keepOptional || !dep.optional)
|
|
||||||
)
|
|
||||||
|
|
||||||
val fetchQuiet = coursier.fetch(repositories)
|
|
||||||
val fetch0 =
|
|
||||||
if (verbose0 == 0) fetchQuiet
|
|
||||||
else {
|
|
||||||
modVers: Seq[(Module, String)] =>
|
|
||||||
val print = Task{
|
|
||||||
println(s"Getting ${modVers.length} project definition(s)")
|
|
||||||
}
|
|
||||||
|
|
||||||
print.flatMap(_ => fetchQuiet(modVers))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verbose0 >= 0)
|
|
||||||
println(s"Resolving\n" + moduleVersions.map{case (mod, ver) => s" $mod:$ver"}.mkString("\n"))
|
|
||||||
|
|
||||||
val res = startRes
|
|
||||||
.process
|
|
||||||
.run(fetch0, maxIterations)
|
|
||||||
.run
|
|
||||||
|
|
||||||
if (!res.isDone) {
|
|
||||||
println(s"Maximum number of iteration reached!")
|
|
||||||
sys exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
def repr(dep: Dependency) = {
|
|
||||||
// dep.version can be an interval, whereas the one from project can't
|
|
||||||
val version = res
|
|
||||||
.projectCache
|
|
||||||
.get(dep.moduleVersion)
|
|
||||||
.map(_._2.version)
|
|
||||||
.getOrElse(dep.version)
|
|
||||||
val extra =
|
|
||||||
if (version == dep.version) ""
|
|
||||||
else s" ($version for ${dep.version})"
|
|
||||||
|
|
||||||
(
|
|
||||||
Seq(
|
|
||||||
dep.module.organization,
|
|
||||||
dep.module.name,
|
|
||||||
dep.attributes.`type`
|
|
||||||
) ++
|
|
||||||
Some(dep.attributes.classifier)
|
|
||||||
.filter(_.nonEmpty)
|
|
||||||
.toSeq ++
|
|
||||||
Seq(
|
|
||||||
version
|
|
||||||
)
|
|
||||||
).mkString(":") + extra
|
|
||||||
}
|
|
||||||
|
|
||||||
val trDeps = res
|
|
||||||
.minDependencies
|
|
||||||
.toList
|
|
||||||
.sortBy(repr)
|
|
||||||
|
|
||||||
if (verbose0 >= 0) {
|
|
||||||
println("")
|
|
||||||
println(
|
|
||||||
trDeps
|
|
||||||
.map(repr)
|
|
||||||
.distinct
|
|
||||||
.mkString("\n")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.conflicts.nonEmpty) {
|
|
||||||
// Needs test
|
|
||||||
println(s"${res.conflicts.size} conflict(s):\n ${res.conflicts.toList.map(repr).sorted.mkString(" \n")}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val errors = res.errors
|
|
||||||
if (errors.nonEmpty) {
|
|
||||||
println(s"\n${errors.size} error(s):")
|
|
||||||
for ((dep, errs) <- errors) {
|
|
||||||
println(s" ${dep.module}:${dep.version}:\n${errs.map(" " + _.replace("\n", " \n")).mkString("\n")}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (classpath || default || sources || javadoc) {
|
|
||||||
println("")
|
|
||||||
|
|
||||||
val artifacts0 = res.artifacts
|
|
||||||
val default0 = default || (!sources && !javadoc)
|
|
||||||
val artifacts = artifacts0
|
|
||||||
.flatMap{ artifact =>
|
|
||||||
var l = List.empty[Artifact]
|
|
||||||
if (sources)
|
|
||||||
l = artifact.extra.get("sources").toList ::: l
|
|
||||||
if (javadoc)
|
|
||||||
l = artifact.extra.get("javadoc").toList ::: l
|
|
||||||
if (default0)
|
|
||||||
l = artifact :: l
|
|
||||||
|
|
||||||
l
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val files = {
|
|
||||||
var files0 = cache
|
|
||||||
.files()
|
|
||||||
.copy(logger = logger)
|
|
||||||
files0 = files0.copy(concurrentDownloadCount = parallel)
|
|
||||||
files0
|
|
||||||
}
|
|
||||||
|
|
||||||
val tasks = artifacts.map(artifact => files.file(artifact).run.map(artifact.->))
|
|
||||||
def printTask = Task{
|
|
||||||
if (verbose0 >= 0 && artifacts.nonEmpty)
|
|
||||||
println(s"Found ${artifacts.length} artifacts")
|
|
||||||
}
|
|
||||||
val task = printTask.flatMap(_ => Task.gatherUnordered(tasks))
|
|
||||||
|
|
||||||
val results = task.run
|
|
||||||
val errors = results.collect{case (artifact, -\/(err)) => artifact -> err }
|
|
||||||
val files0 = results.collect{case (artifact, \/-(f)) => f }
|
|
||||||
|
|
||||||
if (errors.nonEmpty) {
|
|
||||||
println(s"${errors.size} error(s):")
|
|
||||||
for ((artifact, error) <- errors) {
|
|
||||||
println(s" ${artifact.url}: $error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.out.println(
|
|
||||||
files0
|
|
||||||
.map(_.toString)
|
|
||||||
.mkString(if (classpath) File.pathSeparator else "\n")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (list)
|
||||||
|
for ((id, repo, _) <- cache.list().sortBy(_._1)) {
|
||||||
|
println(s"$id: ${repo.root}" + (if (repo.ivyLike) " (Ivy-like)" else ""))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object Coursier extends AppOf[Coursier] {
|
object CoursierApp extends CommandAppOf[Command]
|
||||||
val parser = default
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,302 @@
|
||||||
|
package coursier.cli
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
import caseapp.CaseApp
|
||||||
|
import coursier._
|
||||||
|
import coursier.core.{ CachePolicy, MavenRepository }
|
||||||
|
|
||||||
|
import scalaz.{ \/-, -\/ }
|
||||||
|
import scalaz.concurrent.Task
|
||||||
|
|
||||||
|
object Helper {
|
||||||
|
def validate(common: CommonOptions) = {
|
||||||
|
import common._
|
||||||
|
|
||||||
|
if (force && offline) {
|
||||||
|
Console.err.println("Error: --offline (-c) and --force (-f) options can't be specified at the same time.")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parallel <= 0) {
|
||||||
|
Console.err.println(s"Error: invalid --parallel (-n) value: $parallel")
|
||||||
|
sys.exit(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
???
|
||||||
|
}
|
||||||
|
|
||||||
|
def fileRepr(f: File) = f.toString
|
||||||
|
|
||||||
|
def errPrintln(s: String) = Console.err.println(s)
|
||||||
|
|
||||||
|
def defaultLogger: MavenRepository.Logger with Files.Logger =
|
||||||
|
new MavenRepository.Logger with Files.Logger {
|
||||||
|
def downloading(url: String) =
|
||||||
|
errPrintln(s"Downloading $url")
|
||||||
|
def downloaded(url: String, success: Boolean) =
|
||||||
|
if (!success)
|
||||||
|
errPrintln(s"Failed: $url")
|
||||||
|
def readingFromCache(f: File) = {}
|
||||||
|
def puttingInCache(f: File) = {}
|
||||||
|
|
||||||
|
def foundLocally(f: File) = {}
|
||||||
|
def downloadingArtifact(url: String) =
|
||||||
|
errPrintln(s"Downloading $url")
|
||||||
|
def downloadedArtifact(url: String, success: Boolean) =
|
||||||
|
if (!success)
|
||||||
|
errPrintln(s"Failed: $url")
|
||||||
|
}
|
||||||
|
|
||||||
|
def verboseLogger: MavenRepository.Logger with Files.Logger =
|
||||||
|
new MavenRepository.Logger with Files.Logger {
|
||||||
|
def downloading(url: String) =
|
||||||
|
errPrintln(s"Downloading $url")
|
||||||
|
def downloaded(url: String, success: Boolean) =
|
||||||
|
errPrintln(
|
||||||
|
if (success) s"Downloaded $url"
|
||||||
|
else s"Failed: $url"
|
||||||
|
)
|
||||||
|
def readingFromCache(f: File) = {
|
||||||
|
errPrintln(s"Reading ${fileRepr(f)} from cache")
|
||||||
|
}
|
||||||
|
def puttingInCache(f: File) =
|
||||||
|
errPrintln(s"Writing ${fileRepr(f)} in cache")
|
||||||
|
|
||||||
|
def foundLocally(f: File) =
|
||||||
|
errPrintln(s"Found locally ${fileRepr(f)}")
|
||||||
|
def downloadingArtifact(url: String) =
|
||||||
|
errPrintln(s"Downloading $url")
|
||||||
|
def downloadedArtifact(url: String, success: Boolean) =
|
||||||
|
errPrintln(
|
||||||
|
if (success) s"Downloaded $url"
|
||||||
|
else s"Failed: $url"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Helper(
|
||||||
|
common: CommonOptions,
|
||||||
|
remainingArgs: Seq[String]
|
||||||
|
) {
|
||||||
|
import common._
|
||||||
|
import Helper.errPrintln
|
||||||
|
|
||||||
|
|
||||||
|
val logger =
|
||||||
|
if (verbose0 < 0)
|
||||||
|
None
|
||||||
|
else if (verbose0 == 0)
|
||||||
|
Some(Helper.defaultLogger)
|
||||||
|
else
|
||||||
|
Some(Helper.verboseLogger)
|
||||||
|
|
||||||
|
implicit val cachePolicy =
|
||||||
|
if (offline)
|
||||||
|
CachePolicy.LocalOnly
|
||||||
|
else if (force)
|
||||||
|
CachePolicy.ForceDownload
|
||||||
|
else
|
||||||
|
CachePolicy.Default
|
||||||
|
|
||||||
|
val cache = Cache.default
|
||||||
|
cache.init(verbose = verbose0 >= 0)
|
||||||
|
|
||||||
|
val repositoryIds = {
|
||||||
|
val repositoryIds0 = repository
|
||||||
|
.flatMap(_.split(','))
|
||||||
|
.map(_.trim)
|
||||||
|
.filter(_.nonEmpty)
|
||||||
|
|
||||||
|
if (repositoryIds0.isEmpty)
|
||||||
|
cache.default()
|
||||||
|
else
|
||||||
|
repositoryIds0
|
||||||
|
}
|
||||||
|
|
||||||
|
val repoMap = cache.map()
|
||||||
|
|
||||||
|
if (repositoryIds.exists(!repoMap.contains(_))) {
|
||||||
|
val notFound = repositoryIds
|
||||||
|
.filter(!repoMap.contains(_))
|
||||||
|
|
||||||
|
errPrintln(
|
||||||
|
(if (notFound.lengthCompare(1) == 1) "Repository" else "Repositories") +
|
||||||
|
" not found: " +
|
||||||
|
notFound.mkString(", ")
|
||||||
|
)
|
||||||
|
|
||||||
|
sys.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val (repositories0, fileCaches) = repositoryIds
|
||||||
|
.map(repoMap)
|
||||||
|
.unzip
|
||||||
|
|
||||||
|
val repositories = repositories0
|
||||||
|
.map(_.copy(logger = logger))
|
||||||
|
|
||||||
|
val (rawDependencies, extraArgs) = {
|
||||||
|
val idxOpt = Some(remainingArgs.indexOf("--")).filter(_ >= 0)
|
||||||
|
idxOpt.fold((remainingArgs, Seq.empty[String])) { idx =>
|
||||||
|
val (l, r) = remainingArgs.splitAt(idx)
|
||||||
|
assert(r.nonEmpty)
|
||||||
|
(l, r.tail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val (splitDependencies, malformed) = rawDependencies.toList
|
||||||
|
.map(_.split(":", 3).toSeq)
|
||||||
|
.partition(_.length == 3)
|
||||||
|
|
||||||
|
if (splitDependencies.isEmpty) {
|
||||||
|
???
|
||||||
|
// CaseApp.printUsage[Coursier]()
|
||||||
|
sys exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (malformed.nonEmpty) {
|
||||||
|
errPrintln(s"Malformed dependencies:\n${malformed.map(_.mkString(":")).mkString("\n")}")
|
||||||
|
sys exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
val moduleVersions = splitDependencies.map{
|
||||||
|
case Seq(org, name, version) =>
|
||||||
|
(Module(org, name), version)
|
||||||
|
}
|
||||||
|
|
||||||
|
val deps = moduleVersions.map{case (mod, ver) =>
|
||||||
|
Dependency(mod, ver, scope = Scope.Runtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
val startRes = Resolution(
|
||||||
|
deps.toSet,
|
||||||
|
filter = Some(dep => keepOptional || !dep.optional)
|
||||||
|
)
|
||||||
|
|
||||||
|
val fetchQuiet = coursier.fetch(repositories)
|
||||||
|
val fetch0 =
|
||||||
|
if (verbose0 == 0) fetchQuiet
|
||||||
|
else {
|
||||||
|
modVers: Seq[(Module, String)] =>
|
||||||
|
val print = Task{
|
||||||
|
errPrintln(s"Getting ${modVers.length} project definition(s)")
|
||||||
|
}
|
||||||
|
|
||||||
|
print.flatMap(_ => fetchQuiet(modVers))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose0 >= 0)
|
||||||
|
errPrintln(s"Resolving\n" + moduleVersions.map{case (mod, ver) => s" $mod:$ver"}.mkString("\n"))
|
||||||
|
|
||||||
|
val res = startRes
|
||||||
|
.process
|
||||||
|
.run(fetch0, maxIterations)
|
||||||
|
.run
|
||||||
|
|
||||||
|
if (!res.isDone) {
|
||||||
|
errPrintln(s"Maximum number of iteration reached!")
|
||||||
|
sys.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
def repr(dep: Dependency) = {
|
||||||
|
// dep.version can be an interval, whereas the one from project can't
|
||||||
|
val version = res
|
||||||
|
.projectCache
|
||||||
|
.get(dep.moduleVersion)
|
||||||
|
.map(_._2.version)
|
||||||
|
.getOrElse(dep.version)
|
||||||
|
val extra =
|
||||||
|
if (version == dep.version) ""
|
||||||
|
else s" ($version for ${dep.version})"
|
||||||
|
|
||||||
|
(
|
||||||
|
Seq(
|
||||||
|
dep.module.organization,
|
||||||
|
dep.module.name,
|
||||||
|
dep.attributes.`type`
|
||||||
|
) ++
|
||||||
|
Some(dep.attributes.classifier)
|
||||||
|
.filter(_.nonEmpty)
|
||||||
|
.toSeq ++
|
||||||
|
Seq(
|
||||||
|
version
|
||||||
|
)
|
||||||
|
).mkString(":") + extra
|
||||||
|
}
|
||||||
|
|
||||||
|
val trDeps = res
|
||||||
|
.minDependencies
|
||||||
|
.toList
|
||||||
|
.sortBy(repr)
|
||||||
|
|
||||||
|
if (verbose0 >= 0) {
|
||||||
|
println("")
|
||||||
|
println(
|
||||||
|
trDeps
|
||||||
|
.map(repr)
|
||||||
|
.distinct
|
||||||
|
.mkString("\n")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.conflicts.nonEmpty) {
|
||||||
|
// Needs test
|
||||||
|
println(s"${res.conflicts.size} conflict(s):\n ${res.conflicts.toList.map(repr).sorted.mkString(" \n")}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val errors = res.errors
|
||||||
|
if (errors.nonEmpty) {
|
||||||
|
println(s"\n${errors.size} error(s):")
|
||||||
|
for ((dep, errs) <- errors) {
|
||||||
|
println(s" ${dep.module}:${dep.version}:\n${errs.map(" " + _.replace("\n", " \n")).mkString("\n")}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def fetch(main: Boolean, sources: Boolean, javadoc: Boolean): Seq[File] = {
|
||||||
|
println("")
|
||||||
|
|
||||||
|
val artifacts0 = res.artifacts
|
||||||
|
val main0 = main || (!sources && !javadoc)
|
||||||
|
val artifacts = artifacts0.flatMap{ artifact =>
|
||||||
|
var l = List.empty[Artifact]
|
||||||
|
if (sources)
|
||||||
|
l = artifact.extra.get("sources").toList ::: l
|
||||||
|
if (javadoc)
|
||||||
|
l = artifact.extra.get("javadoc").toList ::: l
|
||||||
|
if (main0)
|
||||||
|
l = artifact :: l
|
||||||
|
|
||||||
|
l
|
||||||
|
}
|
||||||
|
|
||||||
|
val files = {
|
||||||
|
var files0 = cache
|
||||||
|
.files()
|
||||||
|
.copy(logger = logger)
|
||||||
|
files0 = files0.copy(concurrentDownloadCount = parallel)
|
||||||
|
files0
|
||||||
|
}
|
||||||
|
|
||||||
|
val tasks = artifacts.map(artifact => files.file(artifact).run.map(artifact.->))
|
||||||
|
def printTask = Task{
|
||||||
|
if (verbose0 >= 0 && artifacts.nonEmpty)
|
||||||
|
println(s"Found ${artifacts.length} artifacts")
|
||||||
|
}
|
||||||
|
val task = printTask.flatMap(_ => Task.gatherUnordered(tasks))
|
||||||
|
|
||||||
|
val results = task.run
|
||||||
|
val errors = results.collect{case (artifact, -\/(err)) => artifact -> err }
|
||||||
|
val files0 = results.collect{case (artifact, \/-(f)) => f }
|
||||||
|
|
||||||
|
if (errors.nonEmpty) {
|
||||||
|
println(s"${errors.size} error(s):")
|
||||||
|
for ((artifact, error) <- errors) {
|
||||||
|
println(s" ${artifact.url}: $error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
files0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
package coursier.cli
|
|
||||||
|
|
||||||
import coursier.Cache
|
|
||||||
import caseapp._
|
|
||||||
|
|
||||||
// TODO: allow removing a repository (with confirmations, etc.)
|
|
||||||
case class Repositories(
|
|
||||||
@ValueDescription("id:baseUrl") @ExtraName("a") add: List[String],
|
|
||||||
@ExtraName("L") list: Boolean,
|
|
||||||
@ExtraName("l") defaultList: Boolean,
|
|
||||||
ivyLike: Boolean
|
|
||||||
) extends App {
|
|
||||||
|
|
||||||
if (add.exists(!_.contains(":"))) {
|
|
||||||
CaseApp.printUsage[Repositories](err = true)
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
|
|
||||||
val add0 = add
|
|
||||||
.map{ s =>
|
|
||||||
val Seq(id, baseUrl) = s.split(":", 2).toSeq
|
|
||||||
id -> baseUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
add0.exists(_._1.contains("/")) ||
|
|
||||||
add0.exists(_._1.startsWith(".")) ||
|
|
||||||
add0.exists(_._1.isEmpty)
|
|
||||||
) {
|
|
||||||
CaseApp.printUsage[Repositories](err = true)
|
|
||||||
sys.exit(255)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val cache = Cache.default
|
|
||||||
|
|
||||||
if (cache.cache.exists() && !cache.cache.isDirectory) {
|
|
||||||
Console.err.println(s"Error: ${cache.cache} not a directory")
|
|
||||||
sys.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cache.cache.exists())
|
|
||||||
cache.init(verbose = true)
|
|
||||||
|
|
||||||
val current = cache.list().map(_._1).toSet
|
|
||||||
|
|
||||||
val alreadyAdded = add0
|
|
||||||
.map(_._1)
|
|
||||||
.filter(current)
|
|
||||||
|
|
||||||
if (alreadyAdded.nonEmpty) {
|
|
||||||
Console.err.println(s"Error: already added: ${alreadyAdded.mkString(", ")}")
|
|
||||||
sys.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((id, baseUrl0) <- add0) {
|
|
||||||
val baseUrl =
|
|
||||||
if (baseUrl0.endsWith("/"))
|
|
||||||
baseUrl0
|
|
||||||
else
|
|
||||||
baseUrl0 + "/"
|
|
||||||
|
|
||||||
cache.add(id, baseUrl, ivyLike = ivyLike)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultList) {
|
|
||||||
val map = cache.repositoryMap()
|
|
||||||
|
|
||||||
for (id <- cache.default(withNotFound = true))
|
|
||||||
map.get(id) match {
|
|
||||||
case Some(repo) =>
|
|
||||||
println(s"$id: ${repo.root}" + (if (repo.ivyLike) " (Ivy-like)" else ""))
|
|
||||||
case None =>
|
|
||||||
println(s"$id (not found)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (list)
|
|
||||||
for ((id, repo, _) <- cache.list().sortBy(_._1)) {
|
|
||||||
println(s"$id: ${repo.root}" + (if (repo.ivyLike) " (Ivy-like)" else ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object Repositories extends AppOf[Repositories] {
|
|
||||||
val parser = default
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue