mirror of https://github.com/sbt/sbt.git
Add SBT plugin
This commit is contained in:
parent
46732be5c9
commit
842de76ca6
17
build.sbt
17
build.sbt
|
|
@ -72,7 +72,6 @@ lazy val commonSettings = baseCommonSettings ++ Seq(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
lazy val core = crossProject
|
lazy val core = crossProject
|
||||||
.settings(commonSettings: _*)
|
.settings(commonSettings: _*)
|
||||||
.settings(publishingSettings: _*)
|
.settings(publishingSettings: _*)
|
||||||
|
|
@ -163,7 +162,7 @@ lazy val bootstrap = project
|
||||||
lazy val cli = project
|
lazy val cli = project
|
||||||
.dependsOn(coreJvm, files)
|
.dependsOn(coreJvm, files)
|
||||||
.settings(commonSettings)
|
.settings(commonSettings)
|
||||||
.settings(noPublishSettings)
|
.settings(publishingSettings)
|
||||||
.settings(packAutoSettings)
|
.settings(packAutoSettings)
|
||||||
.settings(
|
.settings(
|
||||||
name := "coursier-cli",
|
name := "coursier-cli",
|
||||||
|
|
@ -171,10 +170,7 @@ lazy val cli = project
|
||||||
"com.github.alexarchambault" %% "case-app" % "1.0.0-SNAPSHOT",
|
"com.github.alexarchambault" %% "case-app" % "1.0.0-SNAPSHOT",
|
||||||
"com.lihaoyi" %% "ammonite-terminal" % "0.5.0",
|
"com.lihaoyi" %% "ammonite-terminal" % "0.5.0",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.1.3"
|
"ch.qos.logback" % "logback-classic" % "1.1.3"
|
||||||
),
|
)
|
||||||
resourceGenerators in Compile += packageBin.in(bootstrap).in(Compile).map { jar =>
|
|
||||||
Seq(jar)
|
|
||||||
}.taskValue
|
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val web = project
|
lazy val web = project
|
||||||
|
|
@ -208,6 +204,15 @@ lazy val web = project
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Don't try to compile that if you're not in 2.10
|
||||||
|
lazy val plugin = project
|
||||||
|
.dependsOn(coreJvm, files, cli)
|
||||||
|
.settings(baseCommonSettings)
|
||||||
|
.settings(
|
||||||
|
name := "coursier-sbt-plugin",
|
||||||
|
sbtPlugin := true
|
||||||
|
)
|
||||||
|
|
||||||
lazy val `coursier` = project.in(file("."))
|
lazy val `coursier` = project.in(file("."))
|
||||||
.aggregate(coreJvm, coreJs, `fetch-js`, testsJvm, testsJs, files, bootstrap, cli, web)
|
.aggregate(coreJvm, coreJs, `fetch-js`, testsJvm, testsJs, files, bootstrap, cli, web)
|
||||||
.settings(commonSettings)
|
.settings(commonSettings)
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,6 @@ class TermDisplay(out: Writer) extends Logger {
|
||||||
case Some(Right(())) =>
|
case Some(Right(())) =>
|
||||||
// update display
|
// update display
|
||||||
|
|
||||||
for (_ <- 0 until lineCount) {
|
|
||||||
ansi.up(1)
|
|
||||||
ansi.clearLine(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
val downloads0 = downloads.synchronized {
|
val downloads0 = downloads.synchronized {
|
||||||
downloads
|
downloads
|
||||||
.toVector
|
.toVector
|
||||||
|
|
@ -71,9 +66,13 @@ class TermDisplay(out: Writer) extends Logger {
|
||||||
} else
|
} else
|
||||||
(url, extra)
|
(url, extra)
|
||||||
|
|
||||||
|
ansi.clearLine(2)
|
||||||
out.write(s"$url0 $extra0\n")
|
out.write(s"$url0 $extra0\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (_ <- downloads0.indices)
|
||||||
|
ansi.up(1)
|
||||||
|
|
||||||
out.flush()
|
out.flush()
|
||||||
Thread.sleep(refreshInterval)
|
Thread.sleep(refreshInterval)
|
||||||
helper(downloads0.length)
|
helper(downloads0.length)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ package object compatibility {
|
||||||
def isText = node match { case _: scala.xml.Text => true; case _ => false }
|
def isText = node match { case _: scala.xml.Text => true; case _ => false }
|
||||||
def textContent = node.text
|
def textContent = node.text
|
||||||
def isElement = node match { case _: scala.xml.Elem => true; case _ => false }
|
def isElement = node match { case _: scala.xml.Elem => true; case _ => false }
|
||||||
|
|
||||||
|
override def toString = node.toString
|
||||||
}
|
}
|
||||||
|
|
||||||
parse.right
|
parse.right
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,10 @@ case class Project(
|
||||||
publications: Seq[(String, Publication)]
|
publications: Seq[(String, Publication)]
|
||||||
) {
|
) {
|
||||||
def moduleVersion = (module, version)
|
def moduleVersion = (module, version)
|
||||||
|
|
||||||
|
/** All configurations that each configuration extends, including the ones it extends transitively */
|
||||||
|
lazy val allConfigurations: Map[String, Set[String]] =
|
||||||
|
Orders.allConfigurations(configurations)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maven-specific
|
// Maven-specific
|
||||||
|
|
|
||||||
|
|
@ -8,34 +8,39 @@ object Orders {
|
||||||
.exists(_ <= 0)
|
.exists(_ <= 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** All configurations that each configuration extends, including the ones it extends transitively */
|
||||||
|
def allConfigurations(configurations: Map[String, Seq[String]]): Map[String, Set[String]] = {
|
||||||
|
def allParents(config: String): Set[String] = {
|
||||||
|
def helper(configs: Set[String], acc: Set[String]): Set[String] =
|
||||||
|
if (configs.isEmpty)
|
||||||
|
acc
|
||||||
|
else if (configs.exists(acc))
|
||||||
|
helper(configs -- acc, acc)
|
||||||
|
else if (configs.exists(!configurations.contains(_))) {
|
||||||
|
val (remaining, notFound) = configs.partition(configurations.contains)
|
||||||
|
helper(remaining, acc ++ notFound)
|
||||||
|
} else {
|
||||||
|
val extraConfigs = configs.flatMap(configurations)
|
||||||
|
helper(extraConfigs, acc ++ configs)
|
||||||
|
}
|
||||||
|
|
||||||
|
helper(Set(config), Set.empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations
|
||||||
|
.keys
|
||||||
|
.toList
|
||||||
|
.map(config => config -> (allParents(config) - config))
|
||||||
|
.toMap
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only relations:
|
* Only relations:
|
||||||
* Compile < Runtime < Test
|
* Compile < Runtime < Test
|
||||||
*/
|
*/
|
||||||
def configurationPartialOrder(configurations: Map[String, Seq[String]]): PartialOrdering[String] =
|
def configurationPartialOrder(configurations: Map[String, Seq[String]]): PartialOrdering[String] =
|
||||||
new PartialOrdering[String] {
|
new PartialOrdering[String] {
|
||||||
def allParents(config: String): Set[String] = {
|
val allParentsMap = allConfigurations(configurations)
|
||||||
def helper(configs: Set[String], acc: Set[String]): Set[String] =
|
|
||||||
if (configs.isEmpty)
|
|
||||||
acc
|
|
||||||
else if (configs.exists(acc))
|
|
||||||
helper(configs -- acc, acc)
|
|
||||||
else if (configs.exists(!configurations.contains(_))) {
|
|
||||||
val (remaining, notFound) = configs.partition(configurations.contains)
|
|
||||||
helper(remaining, acc ++ notFound)
|
|
||||||
} else {
|
|
||||||
val extraConfigs = configs.flatMap(configurations)
|
|
||||||
helper(extraConfigs, acc ++ configs)
|
|
||||||
}
|
|
||||||
|
|
||||||
helper(Set(config), Set.empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
val allParentsMap = configurations
|
|
||||||
.keys
|
|
||||||
.toList
|
|
||||||
.map(config => config -> (allParents(config) - config))
|
|
||||||
.toMap
|
|
||||||
|
|
||||||
def tryCompare(x: String, y: String) =
|
def tryCompare(x: String, y: String) =
|
||||||
if (x == y)
|
if (x == y)
|
||||||
|
|
|
||||||
|
|
@ -777,10 +777,13 @@ case class Resolution(
|
||||||
def part(dependencies: Set[Dependency]): Resolution = {
|
def part(dependencies: Set[Dependency]): Resolution = {
|
||||||
val (_, _, finalVersions) = nextDependenciesAndConflicts
|
val (_, _, finalVersions) = nextDependenciesAndConflicts
|
||||||
|
|
||||||
|
def updateVersion(dep: Dependency): Dependency =
|
||||||
|
dep.copy(version = finalVersions.getOrElse(dep.module, dep.version))
|
||||||
|
|
||||||
@tailrec def helper(current: Set[Dependency]): Set[Dependency] = {
|
@tailrec def helper(current: Set[Dependency]): Set[Dependency] = {
|
||||||
val newDeps = current ++ current
|
val newDeps = current ++ current
|
||||||
.flatMap(finalDependencies0)
|
.flatMap(finalDependencies0)
|
||||||
.map(dep => dep.copy(version = finalVersions.getOrElse(dep.module, dep.version)))
|
.map(updateVersion)
|
||||||
|
|
||||||
val anyNewDep = (newDeps -- current).nonEmpty
|
val anyNewDep = (newDeps -- current).nonEmpty
|
||||||
|
|
||||||
|
|
@ -792,7 +795,7 @@ case class Resolution(
|
||||||
|
|
||||||
copy(
|
copy(
|
||||||
rootDependencies = dependencies,
|
rootDependencies = dependencies,
|
||||||
dependencies = helper(dependencies)
|
dependencies = helper(dependencies.map(updateVersion))
|
||||||
// don't know if something should be done about conflicts
|
// don't know if something should be done about conflicts
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ import java.util.regex.Pattern.quote
|
||||||
|
|
||||||
object IvyRepository {
|
object IvyRepository {
|
||||||
|
|
||||||
val optionalPartRegex = (quote("(") + "[^" + quote("()") + "]*" + quote(")")).r
|
val optionalPartRegex = (quote("(") + "[^" + quote("{()}") + "]*" + quote(")")).r
|
||||||
val variableRegex = (quote("[") + "[^" + quote("[()]") + "]*" + quote("]")).r
|
val variableRegex = (quote("[") + "[^" + quote("{[()]}") + "]*" + quote("]")).r
|
||||||
|
val propertyRegex = (quote("${") + "[^" + quote("{[()]}") + "]*" + quote("}")).r
|
||||||
|
|
||||||
sealed abstract class PatternPart(val effectiveStart: Int, val effectiveEnd: Int) extends Product with Serializable {
|
sealed abstract class PatternPart(val effectiveStart: Int, val effectiveEnd: Int) extends Product with Serializable {
|
||||||
require(effectiveStart <= effectiveEnd)
|
require(effectiveStart <= effectiveEnd)
|
||||||
|
|
@ -67,19 +68,32 @@ object IvyRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def substituteProperties(s: String, properties: Map[String, String]): String =
|
||||||
|
propertyRegex.findAllMatchIn(s).toVector.foldRight(s) { case (m, s0) =>
|
||||||
|
val key = s0.substring(m.start + "${".length, m.end - "}".length)
|
||||||
|
val value = properties.getOrElse(key, "")
|
||||||
|
s0.take(m.start) + value + s0.drop(m.end)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case class IvyRepository(pattern: String, changing: Option[Boolean] = None) extends Repository {
|
case class IvyRepository(
|
||||||
|
pattern: String,
|
||||||
|
changing: Option[Boolean] = None,
|
||||||
|
properties: Map[String, String] = Map.empty
|
||||||
|
) extends Repository {
|
||||||
|
|
||||||
import Repository._
|
import Repository._
|
||||||
import IvyRepository._
|
import IvyRepository._
|
||||||
|
|
||||||
|
private val pattern0 = substituteProperties(pattern, properties)
|
||||||
|
|
||||||
val parts = {
|
val parts = {
|
||||||
val optionalParts = optionalPartRegex.findAllMatchIn(pattern).toList.map { m =>
|
val optionalParts = optionalPartRegex.findAllMatchIn(pattern0).toList.map { m =>
|
||||||
PatternPart.Optional(m.start, m.end)
|
PatternPart.Optional(m.start, m.end)
|
||||||
}
|
}
|
||||||
|
|
||||||
val len = pattern.length
|
val len = pattern0.length
|
||||||
|
|
||||||
@tailrec
|
@tailrec
|
||||||
def helper(
|
def helper(
|
||||||
|
|
@ -105,16 +119,16 @@ case class IvyRepository(pattern: String, changing: Option[Boolean] = None) exte
|
||||||
helper(0, optionalParts, Nil)
|
helper(0, optionalParts, Nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(pattern.isEmpty == parts.isEmpty)
|
assert(pattern0.isEmpty == parts.isEmpty)
|
||||||
if (pattern.nonEmpty) {
|
if (pattern0.nonEmpty) {
|
||||||
for ((a, b) <- parts.zip(parts.tail))
|
for ((a, b) <- parts.zip(parts.tail))
|
||||||
assert(a.end == b.start)
|
assert(a.end == b.start)
|
||||||
assert(parts.head.start == 0)
|
assert(parts.head.start == 0)
|
||||||
assert(parts.last.end == pattern.length)
|
assert(parts.last.end == pattern0.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val substituteHelpers = parts.map { part =>
|
private val substituteHelpers = parts.map { part =>
|
||||||
part(pattern.substring(part.effectiveStart, part.effectiveEnd))
|
part(pattern0.substring(part.effectiveStart, part.effectiveEnd))
|
||||||
}
|
}
|
||||||
|
|
||||||
def substitute(variables: Map[String, String]): String \/ String =
|
def substitute(variables: Map[String, String]): String \/ String =
|
||||||
|
|
@ -154,7 +168,13 @@ case class IvyRepository(pattern: String, changing: Option[Boolean] = None) exte
|
||||||
def artifacts(dependency: Dependency, project: Project) =
|
def artifacts(dependency: Dependency, project: Project) =
|
||||||
project
|
project
|
||||||
.publications
|
.publications
|
||||||
.collect { case (conf, p) if conf == "*" || conf == dependency.configuration => p }
|
.collect {
|
||||||
|
case (conf, p)
|
||||||
|
if conf == "*" ||
|
||||||
|
conf == dependency.configuration ||
|
||||||
|
project.allConfigurations.getOrElse(dependency.configuration, Set.empty).contains(conf) =>
|
||||||
|
p
|
||||||
|
}
|
||||||
.flatMap { p =>
|
.flatMap { p =>
|
||||||
substitute(variables(
|
substitute(variables(
|
||||||
dependency.module.organization,
|
dependency.module.organization,
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ object IvyXml {
|
||||||
private def publications(node: Node): Map[String, Seq[Publication]] =
|
private def publications(node: Node): Map[String, Seq[Publication]] =
|
||||||
node.children
|
node.children
|
||||||
.filter(_.label == "artifact")
|
.filter(_.label == "artifact")
|
||||||
.flatMap { node0 =>
|
.flatMap { node =>
|
||||||
val name = node.attribute("name").getOrElse("")
|
val name = node.attribute("name").getOrElse("")
|
||||||
val type0 = node.attribute("type").getOrElse("jar")
|
val type0 = node.attribute("type").getOrElse("jar")
|
||||||
val ext = node.attribute("ext").getOrElse(type0)
|
val ext = node.attribute("ext").getOrElse(type0)
|
||||||
|
|
@ -116,12 +116,14 @@ object IvyXml {
|
||||||
if (publicationsOpt.isEmpty)
|
if (publicationsOpt.isEmpty)
|
||||||
// no publications node -> default JAR artifact
|
// no publications node -> default JAR artifact
|
||||||
Seq("*" -> Publication(module.name, "jar", "jar"))
|
Seq("*" -> Publication(module.name, "jar", "jar"))
|
||||||
else
|
else {
|
||||||
// publications node is there -> only its content (if it is empty, no artifacts,
|
// publications node is there -> only its content (if it is empty, no artifacts,
|
||||||
// as per the Ivy manual)
|
// as per the Ivy manual)
|
||||||
|
val inAllConfs = publicationsOpt.flatMap(_.get("*")).getOrElse(Nil)
|
||||||
configurations0.flatMap { case (conf, _) =>
|
configurations0.flatMap { case (conf, _) =>
|
||||||
publicationsOpt.flatMap(_.get(conf)).getOrElse(Nil).map(conf -> _)
|
(publicationsOpt.flatMap(_.get(conf)).getOrElse(Nil) ++ inAllConfs).map(conf -> _)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,257 @@
|
||||||
|
package coursier
|
||||||
|
|
||||||
|
import java.io.{ File, OutputStreamWriter }
|
||||||
|
|
||||||
|
import coursier.cli.TermDisplay
|
||||||
|
import sbt.{ MavenRepository => _, _ }
|
||||||
|
import sbt.Keys._
|
||||||
|
|
||||||
|
import scalaz.{ -\/, \/- }
|
||||||
|
import scalaz.concurrent.Task
|
||||||
|
|
||||||
|
object CoursierPlugin extends AutoPlugin {
|
||||||
|
|
||||||
|
override def trigger = allRequirements
|
||||||
|
|
||||||
|
override def requires = sbt.plugins.IvyPlugin
|
||||||
|
|
||||||
|
private def errPrintln(s: String): Unit = scala.Console.err.println(s)
|
||||||
|
|
||||||
|
object autoImport {
|
||||||
|
val coursierParallelDownloads = Keys.coursierParallelDownloads
|
||||||
|
val coursierMaxIterations = Keys.coursierMaxIterations
|
||||||
|
val coursierChecksums = Keys.coursierChecksums
|
||||||
|
val coursierCachePolicy = Keys.coursierCachePolicy
|
||||||
|
val coursierResolvers = Keys.coursierResolvers
|
||||||
|
val coursierCache = Keys.coursierCache
|
||||||
|
val coursierProject = Keys.coursierProject
|
||||||
|
val coursierProjects = Keys.coursierProjects
|
||||||
|
}
|
||||||
|
|
||||||
|
import autoImport._
|
||||||
|
|
||||||
|
|
||||||
|
private val ivyProperties = Map(
|
||||||
|
"ivy.home" -> s"${sys.props("user.home")}/.ivy2"
|
||||||
|
) ++ sys.props
|
||||||
|
|
||||||
|
private def createLogger() = Some {
|
||||||
|
if (sys.env.get("COURSIER_NO_TERM").nonEmpty)
|
||||||
|
new coursier.Files.Logger {
|
||||||
|
override def downloadingArtifact(url: String, file: File): Unit = {
|
||||||
|
println(s"$url\n -> $file")
|
||||||
|
}
|
||||||
|
override def downloadedArtifact(url: String, success: Boolean): Unit = {
|
||||||
|
println(s"$url: ${if (success) "Success" else "Failed"}")
|
||||||
|
}
|
||||||
|
def init() = {}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
new TermDisplay(new OutputStreamWriter(System.err))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private def task = Def.task {
|
||||||
|
// let's update only one module at once, for a better output
|
||||||
|
// Downloads are already parallel, no need to parallelize further anyway
|
||||||
|
synchronized {
|
||||||
|
|
||||||
|
val (currentProject, _) = coursierProject.value
|
||||||
|
val projects = coursierProjects.value
|
||||||
|
|
||||||
|
val parallelDownloads = coursierParallelDownloads.value
|
||||||
|
val checksums = coursierChecksums.value
|
||||||
|
val maxIterations = coursierMaxIterations.value
|
||||||
|
val cachePolicy = coursierCachePolicy.value
|
||||||
|
val cacheDir = coursierCache.value
|
||||||
|
|
||||||
|
val resolvers = coursierResolvers.value
|
||||||
|
|
||||||
|
|
||||||
|
val startRes = Resolution(
|
||||||
|
currentProject.dependencies.map { case (_, dep) => dep }.toSet,
|
||||||
|
filter = Some(dep => !dep.optional),
|
||||||
|
forceVersions = projects.map { case (proj, _) => proj.moduleVersion }.toMap
|
||||||
|
)
|
||||||
|
|
||||||
|
val interProjectRepo = InterProjectRepository(projects)
|
||||||
|
val repositories = interProjectRepo +: resolvers.flatMap(FromSbt.repository(_, ivyProperties))
|
||||||
|
|
||||||
|
val files = Files(
|
||||||
|
Seq("http://" -> new File(cacheDir, "http"), "https://" -> new File(cacheDir, "https")),
|
||||||
|
() => ???,
|
||||||
|
concurrentDownloadCount = parallelDownloads
|
||||||
|
)
|
||||||
|
|
||||||
|
val logger = createLogger()
|
||||||
|
logger.foreach(_.init())
|
||||||
|
val fetch = coursier.Fetch(
|
||||||
|
repositories,
|
||||||
|
files.fetch(checksums = checksums, logger = logger)(cachePolicy = CachePolicy.LocalOnly),
|
||||||
|
files.fetch(checksums = checksums, logger = logger)(cachePolicy = cachePolicy)
|
||||||
|
)
|
||||||
|
|
||||||
|
def depsRepr = currentProject.dependencies.map { case (config, dep) =>
|
||||||
|
s"${dep.module}:${dep.version}:$config->${dep.configuration}"
|
||||||
|
}.sorted
|
||||||
|
|
||||||
|
errPrintln(s"Resolving ${currentProject.module.organization}:${currentProject.module.name}:${currentProject.version}")
|
||||||
|
for (depRepr <- depsRepr)
|
||||||
|
errPrintln(s" $depRepr")
|
||||||
|
|
||||||
|
val res = startRes
|
||||||
|
.process
|
||||||
|
.run(fetch, maxIterations)
|
||||||
|
.attemptRun
|
||||||
|
.leftMap(ex => throw new Exception(s"Exception during resolution", ex))
|
||||||
|
.merge
|
||||||
|
|
||||||
|
if (!res.isDone)
|
||||||
|
throw new Exception(s"Maximum number of iteration reached!")
|
||||||
|
|
||||||
|
errPrintln("Resolution done")
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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")}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val trDepsWithArtifactsTasks = res.artifacts
|
||||||
|
.toVector
|
||||||
|
.map { a =>
|
||||||
|
files.file(a, checksums = checksums, logger = logger)(cachePolicy = cachePolicy).run.map((a, _))
|
||||||
|
}
|
||||||
|
|
||||||
|
errPrintln(s"Fetching artifacts")
|
||||||
|
// rename
|
||||||
|
val trDepsWithArtifacts = Task.gatherUnordered(trDepsWithArtifactsTasks).attemptRun match {
|
||||||
|
case -\/(ex) =>
|
||||||
|
throw new Exception(s"Error while downloading / verifying artifacts", ex)
|
||||||
|
case \/-(l) => l.toMap
|
||||||
|
}
|
||||||
|
errPrintln(s"Fetching artifacts: done")
|
||||||
|
|
||||||
|
val configs = ivyConfigurations.value.map(c => c.name -> c.extendsConfigs.map(_.name)).toMap
|
||||||
|
def allExtends(c: String) = {
|
||||||
|
// possibly bad complexity
|
||||||
|
def helper(current: Set[String]): Set[String] = {
|
||||||
|
val newSet = current ++ current.flatMap(configs.getOrElse(_, Nil))
|
||||||
|
if ((newSet -- current).nonEmpty)
|
||||||
|
helper(newSet)
|
||||||
|
else
|
||||||
|
newSet
|
||||||
|
}
|
||||||
|
|
||||||
|
helper(Set(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
val depsByConfig = currentProject
|
||||||
|
.dependencies
|
||||||
|
.groupBy { case (c, _) => c }
|
||||||
|
.map { case (c, l) =>
|
||||||
|
c -> l.map { case (_, d) => d }
|
||||||
|
}
|
||||||
|
|
||||||
|
val sbtModuleReportsPerScope = configs.map { case (c, _) => c -> {
|
||||||
|
val a = allExtends(c).flatMap(depsByConfig.getOrElse(_, Nil))
|
||||||
|
res.part(a)
|
||||||
|
.dependencyArtifacts
|
||||||
|
.groupBy { case (dep, _) => dep }
|
||||||
|
.map { case (dep, l) => dep -> l.map { case (_, a) => a } }
|
||||||
|
.map { case (dep, artifacts) =>
|
||||||
|
val fe = artifacts.map { a =>
|
||||||
|
a -> trDepsWithArtifacts.getOrElse(a, -\/("Not downloaded"))
|
||||||
|
}
|
||||||
|
new ModuleReport(
|
||||||
|
ModuleID(dep.module.organization, dep.module.name, dep.version, configurations = Some(dep.configuration)),
|
||||||
|
fe.collect { case (artifact, \/-(file)) =>
|
||||||
|
if (file.toString.contains("file:/"))
|
||||||
|
throw new Exception(s"Wrong path: $file")
|
||||||
|
ToSbt.artifact(dep.module, artifact) -> file
|
||||||
|
},
|
||||||
|
fe.collect { case (artifact, -\/(e)) =>
|
||||||
|
errPrintln(s"${artifact.url}: $e")
|
||||||
|
ToSbt.artifact(dep.module, artifact)
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Map.empty,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Nil,
|
||||||
|
Nil,
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
|
new UpdateReport(
|
||||||
|
null,
|
||||||
|
sbtModuleReportsPerScope.toVector.map { case (c, r) =>
|
||||||
|
new ConfigurationReport(
|
||||||
|
c,
|
||||||
|
r.toVector,
|
||||||
|
Nil,
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
},
|
||||||
|
new UpdateStats(-1L, -1L, -1L, cached = false),
|
||||||
|
Map.empty
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override lazy val projectSettings = Seq(
|
||||||
|
coursierParallelDownloads := 6,
|
||||||
|
coursierMaxIterations := 50,
|
||||||
|
coursierChecksums := Seq(Some("SHA-1"), Some("MD5")),
|
||||||
|
coursierCachePolicy := CachePolicy.FetchMissing,
|
||||||
|
coursierResolvers <<= Tasks.coursierResolversTask,
|
||||||
|
coursierCache := new File(sys.props("user.home") + "/.coursier/sbt"),
|
||||||
|
update <<= task,
|
||||||
|
coursierProject <<= Tasks.coursierProjectTask,
|
||||||
|
coursierProjects <<= Tasks.coursierProjectsTask
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
package coursier
|
||||||
|
|
||||||
|
import coursier.ivy.IvyRepository
|
||||||
|
import sbt.{ Resolver, CrossVersion, ModuleID }
|
||||||
|
|
||||||
|
object FromSbt {
|
||||||
|
|
||||||
|
def sbtModuleIdName(
|
||||||
|
moduleId: ModuleID,
|
||||||
|
scalaVersion: => String,
|
||||||
|
scalaBinaryVersion: => String
|
||||||
|
): String = moduleId.crossVersion match {
|
||||||
|
case CrossVersion.Disabled => moduleId.name
|
||||||
|
case f: CrossVersion.Full => moduleId.name + "_" + f.remapVersion(scalaVersion)
|
||||||
|
case f: CrossVersion.Binary => moduleId.name + "_" + f.remapVersion(scalaBinaryVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
def mappings(mapping: String): Seq[(String, String)] =
|
||||||
|
mapping.split(';').flatMap { m =>
|
||||||
|
val (froms, tos) = m.split("->", 2) match {
|
||||||
|
case Array(from) => (from, "default(compile)")
|
||||||
|
case Array(from, to) => (from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
from <- froms.split(',')
|
||||||
|
to <- tos.split(',')
|
||||||
|
} yield (from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
def dependencies(
|
||||||
|
module: ModuleID,
|
||||||
|
scalaVersion: String,
|
||||||
|
scalaBinaryVersion: String
|
||||||
|
): Seq[(String, Dependency)] = {
|
||||||
|
|
||||||
|
// TODO Warn about unsupported properties in `module`
|
||||||
|
|
||||||
|
val fullName = sbtModuleIdName(module, scalaVersion, scalaBinaryVersion)
|
||||||
|
|
||||||
|
val dep = Dependency(
|
||||||
|
Module(module.organization, fullName),
|
||||||
|
module.revision,
|
||||||
|
exclusions = module.exclusions.map { rule =>
|
||||||
|
// FIXME Other `rule` fields are ignored here
|
||||||
|
(rule.organization, rule.name)
|
||||||
|
}.toSet
|
||||||
|
)
|
||||||
|
|
||||||
|
val mapping = module.configurations.getOrElse("compile")
|
||||||
|
val allMappings = mappings(mapping)
|
||||||
|
|
||||||
|
val attributes =
|
||||||
|
if (module.explicitArtifacts.isEmpty)
|
||||||
|
Seq(Attributes())
|
||||||
|
else
|
||||||
|
module.explicitArtifacts.map { a =>
|
||||||
|
Attributes(`type` = a.extension, classifier = a.classifier.getOrElse(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
(from, to) <- allMappings.toSeq
|
||||||
|
attr <- attributes
|
||||||
|
} yield from -> dep.copy(configuration = to, attributes = attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
def project(
|
||||||
|
projectID: ModuleID,
|
||||||
|
allDependencies: Seq[ModuleID],
|
||||||
|
ivyConfigurations: Map[String, Seq[String]],
|
||||||
|
scalaVersion: String,
|
||||||
|
scalaBinaryVersion: String
|
||||||
|
): Project = {
|
||||||
|
|
||||||
|
// FIXME Ignored for now
|
||||||
|
// val sbtDepOverrides = dependencyOverrides.value
|
||||||
|
// val sbtExclusions = excludeDependencies.value
|
||||||
|
|
||||||
|
val deps = allDependencies.flatMap(dependencies(_, scalaVersion, scalaBinaryVersion))
|
||||||
|
|
||||||
|
Project(
|
||||||
|
Module(projectID.organization, sbtModuleIdName(projectID, scalaVersion, scalaBinaryVersion)),
|
||||||
|
projectID.revision,
|
||||||
|
deps,
|
||||||
|
ivyConfigurations,
|
||||||
|
None,
|
||||||
|
Nil,
|
||||||
|
Map.empty,
|
||||||
|
Nil,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def repository(resolver: Resolver, ivyProperties: Map[String, String]): Option[Repository] =
|
||||||
|
resolver match {
|
||||||
|
case sbt.MavenRepository(_, root) =>
|
||||||
|
if (root.startsWith("http://") || root.startsWith("https://")) {
|
||||||
|
val root0 = if (root.endsWith("/")) root else root + "/"
|
||||||
|
Some(MavenRepository(root0))
|
||||||
|
} else {
|
||||||
|
Console.err.println(s"Warning: unrecognized Maven repository protocol in $root, ignoring it")
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
case sbt.FileRepository(_, _, patterns)
|
||||||
|
if patterns.ivyPatterns.lengthCompare(1) == 0 &&
|
||||||
|
patterns.ivyPatterns == patterns.artifactPatterns =>
|
||||||
|
|
||||||
|
Some(IvyRepository(
|
||||||
|
"file://" + patterns.ivyPatterns.head,
|
||||||
|
changing = Some(true),
|
||||||
|
properties = ivyProperties
|
||||||
|
))
|
||||||
|
|
||||||
|
case sbt.URLRepository(_, patterns)
|
||||||
|
if patterns.ivyPatterns.lengthCompare(1) == 0 &&
|
||||||
|
patterns.ivyPatterns == patterns.artifactPatterns =>
|
||||||
|
|
||||||
|
Some(IvyRepository(
|
||||||
|
patterns.ivyPatterns.head,
|
||||||
|
changing = None,
|
||||||
|
properties = ivyProperties
|
||||||
|
))
|
||||||
|
|
||||||
|
case other =>
|
||||||
|
Console.err.println(s"Warning: unrecognized repository ${other.name}, ignoring it")
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package coursier
|
||||||
|
|
||||||
|
import scalaz.{ -\/, \/-, Monad, EitherT }
|
||||||
|
|
||||||
|
case class InterProjectSource(artifacts: Map[(Module, String), Map[String, Seq[Artifact]]]) extends Artifact.Source {
|
||||||
|
def artifacts(dependency: Dependency, project: Project): Seq[Artifact] =
|
||||||
|
artifacts
|
||||||
|
.get(dependency.moduleVersion)
|
||||||
|
.toSeq
|
||||||
|
.flatMap(_.get(dependency.configuration))
|
||||||
|
.flatten
|
||||||
|
}
|
||||||
|
|
||||||
|
case class InterProjectRepository(projects: Seq[(Project, Seq[(String, Seq[Artifact])])]) extends Repository {
|
||||||
|
|
||||||
|
Console.err.println("InterProjectRepository")
|
||||||
|
for ((p, _) <- projects)
|
||||||
|
Console.err.println(s" ${p.module}:${p.version}")
|
||||||
|
|
||||||
|
private val map = projects
|
||||||
|
.map { case (proj, a) => proj.moduleVersion -> proj }
|
||||||
|
.toMap
|
||||||
|
|
||||||
|
val source = InterProjectSource(
|
||||||
|
projects.map { case (proj, a) =>
|
||||||
|
val artifacts = a.toMap
|
||||||
|
val allArtifacts = proj.allConfigurations.map { case (c, ext) =>
|
||||||
|
c -> ext.toSeq.flatMap(artifacts.getOrElse(_, Nil))
|
||||||
|
}
|
||||||
|
proj.moduleVersion -> allArtifacts
|
||||||
|
}.toMap
|
||||||
|
)
|
||||||
|
|
||||||
|
def find[F[_]](
|
||||||
|
module: Module,
|
||||||
|
version: String,
|
||||||
|
fetch: Fetch.Content[F]
|
||||||
|
)(implicit
|
||||||
|
F: Monad[F]
|
||||||
|
): EitherT[F, String, (Artifact.Source, Project)] = {
|
||||||
|
val res = map.get((module, version)) match {
|
||||||
|
case Some(proj) =>
|
||||||
|
\/-((source, proj))
|
||||||
|
case None =>
|
||||||
|
-\/(s"Project not found: $module:$version")
|
||||||
|
}
|
||||||
|
|
||||||
|
EitherT(F.point(res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package coursier
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import sbt.{ Resolver, SettingKey, TaskKey }
|
||||||
|
|
||||||
|
object Keys {
|
||||||
|
val coursierParallelDownloads = SettingKey[Int]("coursier-parallel-downloads", "") // 6
|
||||||
|
val coursierMaxIterations = SettingKey[Int]("coursier-max-iterations", "") // 50
|
||||||
|
val coursierChecksums = SettingKey[Seq[Option[String]]]("coursier-checksums", "") //Seq(Some("SHA-1"), Some("MD5"))
|
||||||
|
val coursierCachePolicy = SettingKey[CachePolicy]("coursier-cache-policy", "") // = CachePolicy.FetchMissing
|
||||||
|
|
||||||
|
val coursierResolvers = TaskKey[Seq[Resolver]]("coursier-resolvers", "")
|
||||||
|
|
||||||
|
val coursierCache = SettingKey[File]("coursier-cache", "")
|
||||||
|
|
||||||
|
val coursierProject = TaskKey[(Project, Seq[(String, Seq[Artifact])])]("coursier-project", "")
|
||||||
|
val coursierProjects = TaskKey[Seq[(Project, Seq[(String, Seq[Artifact])])]]("coursier-projects", "")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package coursier
|
||||||
|
|
||||||
|
import sbt._
|
||||||
|
|
||||||
|
// things from sbt-structure
|
||||||
|
object Structure {
|
||||||
|
import Def.Initialize._
|
||||||
|
|
||||||
|
def structure(state: State): Load.BuildStructure =
|
||||||
|
sbt.Project.structure(state)
|
||||||
|
|
||||||
|
implicit def `enrich SettingKey`[T](key: SettingKey[T]) = new {
|
||||||
|
def find(state: State): Option[T] =
|
||||||
|
key.get(structure(state).data)
|
||||||
|
|
||||||
|
def get(state: State): T =
|
||||||
|
find(state).get
|
||||||
|
|
||||||
|
def getOrElse(state: State, default: => T): T =
|
||||||
|
find(state).getOrElse(default)
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit def `enrich TaskKey`[T](key: TaskKey[T]) = new {
|
||||||
|
def find(state: State): Option[sbt.Task[T]] =
|
||||||
|
key.get(structure(state).data)
|
||||||
|
|
||||||
|
def get(state: State): sbt.Task[T] =
|
||||||
|
find(state).get
|
||||||
|
|
||||||
|
def forAllProjects(state: State, projects: Seq[ProjectRef]): sbt.Task[Map[ProjectRef, T]] = {
|
||||||
|
val tasks = projects.flatMap(p => key.in(p).get(structure(state).data).map(_.map(it => (p, it))))
|
||||||
|
std.TaskExtra.joinTasks(tasks).join.map(_.toMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
def forAllConfigurations(state: State, configurations: Seq[sbt.Configuration]): sbt.Task[Map[sbt.Configuration, T]] = {
|
||||||
|
val tasks = configurations.flatMap(c => key.in(c).get(structure(state).data).map(_.map(it => (c, it))))
|
||||||
|
std.TaskExtra.joinTasks(tasks).join.map(_.toMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package coursier
|
||||||
|
|
||||||
|
import sbt.{Classpaths, Resolver, Def}
|
||||||
|
import Structure._
|
||||||
|
import Keys._
|
||||||
|
import sbt.Keys._
|
||||||
|
|
||||||
|
object Tasks {
|
||||||
|
|
||||||
|
def coursierResolversTask: Def.Initialize[sbt.Task[Seq[Resolver]]] = Def.task {
|
||||||
|
var l = externalResolvers.value
|
||||||
|
if (sbtPlugin.value)
|
||||||
|
l = Seq(
|
||||||
|
sbtResolver.value,
|
||||||
|
Classpaths.sbtPluginReleases
|
||||||
|
) ++ l
|
||||||
|
l
|
||||||
|
}
|
||||||
|
|
||||||
|
def coursierProjectTask: Def.Initialize[sbt.Task[(Project, Seq[(String, Seq[Artifact])])]] =
|
||||||
|
(
|
||||||
|
sbt.Keys.state,
|
||||||
|
sbt.Keys.thisProjectRef
|
||||||
|
).flatMap { (state, projectRef) =>
|
||||||
|
|
||||||
|
// should projectID.configurations be used instead?
|
||||||
|
val configurations = ivyConfigurations.in(projectRef).get(state)
|
||||||
|
|
||||||
|
// exportedProducts looks like what we want, but depends on the update task, which
|
||||||
|
// make the whole thing run into cycles...
|
||||||
|
val artifacts = configurations.map { cfg =>
|
||||||
|
cfg.name -> Option(classDirectory.in(projectRef).in(cfg).getOrElse(state, null))
|
||||||
|
}.collect { case (name, Some(classDir)) =>
|
||||||
|
name -> Seq(
|
||||||
|
Artifact(
|
||||||
|
classDir.toURI.toString,
|
||||||
|
Map.empty,
|
||||||
|
Map.empty,
|
||||||
|
Attributes(),
|
||||||
|
changing = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val allDependenciesTask = allDependencies.in(projectRef).get(state)
|
||||||
|
|
||||||
|
for {
|
||||||
|
allDependencies <- allDependenciesTask
|
||||||
|
} yield {
|
||||||
|
|
||||||
|
val proj = FromSbt.project(
|
||||||
|
projectID.in(projectRef).get(state),
|
||||||
|
allDependencies,
|
||||||
|
configurations.map { cfg => cfg.name -> cfg.extendsConfigs.map(_.name) }.toMap,
|
||||||
|
scalaVersion.in(projectRef).get(state),
|
||||||
|
scalaBinaryVersion.in(projectRef).get(state)
|
||||||
|
)
|
||||||
|
|
||||||
|
(proj, artifacts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def coursierProjectsTask: Def.Initialize[sbt.Task[Seq[(Project, Seq[(String, Seq[Artifact])])]]] =
|
||||||
|
sbt.Keys.state.flatMap { state =>
|
||||||
|
val projects = structure(state).allProjectRefs
|
||||||
|
coursierProject.forAllProjects(state, projects).map(_.values.toVector)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package coursier
|
||||||
|
|
||||||
|
import sbt._
|
||||||
|
|
||||||
|
object ToSbt {
|
||||||
|
|
||||||
|
def artifact(module: Module, artifact: Artifact): sbt.Artifact =
|
||||||
|
sbt.Artifact(
|
||||||
|
s"${module.organization}:${module.name}",
|
||||||
|
artifact.attributes.`type`,
|
||||||
|
"jar",
|
||||||
|
Some(artifact.attributes.classifier),
|
||||||
|
Nil,
|
||||||
|
Some(url(artifact.url)),
|
||||||
|
Map.empty
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue