mirror of https://github.com/sbt/sbt.git
Refactor artifact API
This commit is contained in:
parent
041d0c4fb4
commit
9fd56848a8
|
|
@ -5,10 +5,9 @@ import java.io.File
|
|||
|
||||
import caseapp._
|
||||
import coursier.core.{CachePolicy, Parse}
|
||||
import coursier.core.{ArtifactDownloaderLogger, RemoteLogger, ArtifactDownloader}
|
||||
import coursier.core.MetadataFetchLogger
|
||||
|
||||
import scalaz.concurrent.Task
|
||||
import scalaz.{-\/, \/-}
|
||||
|
||||
case class Coursier(scope: List[String],
|
||||
keepOptional: Boolean,
|
||||
|
|
@ -20,13 +19,14 @@ case class Coursier(scope: List[String],
|
|||
else scope.map(Parse.scope)
|
||||
val scopes = scopes0.toSet
|
||||
|
||||
val centralCacheDir = new File(sys.props("user.home") + "/.coursier/cache/central")
|
||||
val centralCacheDir = new File(sys.props("user.home") + "/.coursier/cache/metadata/central")
|
||||
val centralFilesCacheDir = new File(sys.props("user.home") + "/.coursier/cache/files/central")
|
||||
|
||||
val base = centralCacheDir.toURI
|
||||
def fileRepr(f: File) =
|
||||
base.relativize(f.toURI).getPath
|
||||
|
||||
val logger: RemoteLogger with ArtifactDownloaderLogger = new RemoteLogger with ArtifactDownloaderLogger {
|
||||
val logger: MetadataFetchLogger with FilesLogger = new MetadataFetchLogger with FilesLogger {
|
||||
def println(s: String) = Console.err.println(s)
|
||||
|
||||
def downloading(url: String) =
|
||||
|
|
@ -53,35 +53,36 @@ case class Coursier(scope: List[String],
|
|||
)
|
||||
}
|
||||
|
||||
val cachedMavenCentral = repository.mavenCentral.copy(cache = Some(centralCacheDir), logger = Some(logger))
|
||||
val cachedMavenCentral = repository.mavenCentral.copy(
|
||||
fetchMetadata = repository.mavenCentral.fetchMetadata.copy(
|
||||
cache = Some(centralCacheDir),
|
||||
logger = Some(logger)
|
||||
)
|
||||
)
|
||||
val repositories = Seq[Repository](
|
||||
cachedMavenCentral
|
||||
)
|
||||
|
||||
lazy val downloaders = Map[Repository, ArtifactDownloader](
|
||||
cachedMavenCentral -> ArtifactDownloader(repository.mavenCentral.root, centralCacheDir, logger = Some(logger))
|
||||
)
|
||||
|
||||
val (splitArtifacts, malformed) = remainingArgs.toList
|
||||
val (splitDependencies, malformed) = remainingArgs.toList
|
||||
.map(_.split(":", 3).toSeq)
|
||||
.partition(_.length == 3)
|
||||
|
||||
if (splitArtifacts.isEmpty) {
|
||||
Console.err.println("Usage: coursier artifacts...")
|
||||
if (splitDependencies.isEmpty) {
|
||||
Console.err.println("Usage: coursier dependencies...")
|
||||
sys exit 1
|
||||
}
|
||||
|
||||
if (malformed.nonEmpty) {
|
||||
Console.err.println(s"Malformed artifacts:\n${malformed.map(_.mkString(":")).mkString("\n")}")
|
||||
Console.err.println(s"Malformed dependencies:\n${malformed.map(_.mkString(":")).mkString("\n")}")
|
||||
sys exit 1
|
||||
}
|
||||
|
||||
val modules = splitArtifacts.map{
|
||||
val moduleVersions = splitDependencies.map{
|
||||
case Seq(org, name, version) =>
|
||||
(Module(org, name), version)
|
||||
}
|
||||
|
||||
val deps = modules.map{case (mod, ver) =>
|
||||
val deps = moduleVersions.map{case (mod, ver) =>
|
||||
Dependency(mod, ver, scope = Scope.Runtime)
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +100,7 @@ case class Coursier(scope: List[String],
|
|||
|
||||
def repr(dep: Dependency) = {
|
||||
// dep.version can be an interval, whereas the one from project can't
|
||||
val version = res.projectsCache.get(dep.moduleVersion).map(_._2.version).getOrElse(dep.version)
|
||||
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})"
|
||||
|
|
@ -116,11 +117,11 @@ case class Coursier(scope: List[String],
|
|||
println(s"${res.conflicts.size} conflict(s):\n ${res.conflicts.toList.map(repr).sorted.mkString(" \n")}")
|
||||
}
|
||||
|
||||
val errDeps = trDeps.filter(dep => res.errors.contains(dep.moduleVersion))
|
||||
if (errDeps.nonEmpty) {
|
||||
println(s"${errDeps.size} error(s):")
|
||||
for (dep <- errDeps) {
|
||||
println(s" ${dep.module}:\n ${res.errors(dep.moduleVersion).mkString("\n").replace("\n", " \n")}")
|
||||
val errors = res.errors
|
||||
if (errors.nonEmpty) {
|
||||
println(s"${errors.size} error(s):")
|
||||
for ((dep, errs) <- errors) {
|
||||
println(s" ${dep.module}:\n ${errs.map(" " + _.replace("\n", " \n")).mkString("\n")}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,38 +130,11 @@ case class Coursier(scope: List[String],
|
|||
|
||||
val cachePolicy: CachePolicy = CachePolicy.Default
|
||||
|
||||
val m = res.minDependencies.groupBy(dep => res.projectsCache.get(dep.moduleVersion).map(_._1))
|
||||
val (notFound, remaining0) = m.partition(_._1.isEmpty)
|
||||
if (notFound.nonEmpty) {
|
||||
val notFound0 = notFound.values.flatten.toList.map(repr).sorted
|
||||
println(s"Not found:${notFound0.mkString("\n")}")
|
||||
}
|
||||
val artifacts = res.artifacts
|
||||
|
||||
val (remaining, downloaderNotFound) = remaining0.partition(t => downloaders.contains(t._1.get))
|
||||
if (downloaderNotFound.nonEmpty) {
|
||||
val downloaderNotFound0 = downloaderNotFound.values.flatten.toList.map(repr).sorted
|
||||
println(s"Don't know how to download:${downloaderNotFound0.mkString("\n")}")
|
||||
}
|
||||
|
||||
val sorted = remaining
|
||||
.toList
|
||||
.map{ case (Some(repo), deps) => repo -> deps.toList.sortBy(repr) }
|
||||
.sortBy(_._1.toString) // ...
|
||||
|
||||
val tasks =
|
||||
for {
|
||||
(repo, deps) <- sorted
|
||||
dl = downloaders(repo)
|
||||
dep <- deps
|
||||
(_, proj) = res.projectsCache(dep.moduleVersion)
|
||||
} yield {
|
||||
dl.artifacts(dep, proj, cachePolicy = cachePolicy).map { results =>
|
||||
val errorCount = results.count{case -\/(_) => true; case _ => false}
|
||||
val resultsRepr = results.map(_.map(fileRepr).merge).map(" " + _).mkString("\n")
|
||||
println(s"${repr(dep)} (${results.length} artifact(s)${if (errorCount > 0) s", $errorCount error(s)" else ""}):\n$resultsRepr")
|
||||
}
|
||||
}
|
||||
val files = new Files(Seq(cachedMavenCentral.fetchMetadata.root -> centralFilesCacheDir), () => ???, Some(logger))
|
||||
|
||||
val tasks = artifacts.map(files.file(_, cachePolicy).run)
|
||||
val task = Task.gatherUnordered(tasks)
|
||||
|
||||
task.run
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
package coursier
|
||||
package core
|
||||
|
||||
import org.scalajs.dom.raw.{Event, XMLHttpRequest}
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Promise, Future}
|
||||
import scalaz.{-\/, \/-, EitherT}
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
import scala.scalajs.js
|
||||
import js.Dynamic.{global => g}
|
||||
|
||||
import scala.scalajs.js.timers._
|
||||
|
||||
object DefaultFetchMetadata {
|
||||
|
||||
def encodeURIComponent(s: String): String =
|
||||
g.encodeURIComponent(s).asInstanceOf[String]
|
||||
|
||||
lazy val jsonpAvailable = !js.isUndefined(g.jsonp)
|
||||
|
||||
/** Available if we're running on node, and package xhr2 is installed */
|
||||
lazy val xhr = g.require("xhr2")
|
||||
def xhrReq() =
|
||||
js.Dynamic.newInstance(xhr)().asInstanceOf[XMLHttpRequest]
|
||||
|
||||
def fetchTimeout(target: String, p: Promise[_]) =
|
||||
setTimeout(5000) {
|
||||
if (!p.isCompleted) {
|
||||
p.failure(new Exception(s"Timeout when fetching $target"))
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME Take into account HTTP error codes from YQL response
|
||||
def proxiedJsonp(url: String)(implicit executionContext: ExecutionContext): Future[String] = {
|
||||
val url0 =
|
||||
"https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D%22" +
|
||||
encodeURIComponent(url) +
|
||||
"%22&format=jsonp&diagnostics=true"
|
||||
|
||||
val p = Promise[String]()
|
||||
|
||||
g.jsonp(url0, (res: js.Dynamic) => if (!p.isCompleted) {
|
||||
val success = !js.isUndefined(res) && !js.isUndefined(res.results)
|
||||
if (success)
|
||||
p.success(res.results.asInstanceOf[js.Array[String]].mkString("\n"))
|
||||
else
|
||||
p.failure(new Exception(s"Fetching $url ($url0)"))
|
||||
})
|
||||
|
||||
fetchTimeout(s"$url ($url0)", p)
|
||||
p.future
|
||||
}
|
||||
|
||||
def get(url: String)(implicit executionContext: ExecutionContext): Future[String] =
|
||||
if (jsonpAvailable)
|
||||
proxiedJsonp(url)
|
||||
else {
|
||||
val p = Promise[String]()
|
||||
val xhrReq0 = xhrReq()
|
||||
val f = { _: Event =>
|
||||
p.success(xhrReq0.responseText)
|
||||
}
|
||||
xhrReq0.onload = f
|
||||
|
||||
xhrReq0.open("GET", url)
|
||||
xhrReq0.send()
|
||||
|
||||
fetchTimeout(url, p)
|
||||
p.future
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
trait Logger {
|
||||
def fetching(url: String): Unit
|
||||
def fetched(url: String): Unit
|
||||
def other(url: String, msg: String): Unit
|
||||
}
|
||||
|
||||
case class DefaultFetchMetadata(root: String,
|
||||
logger: Option[Logger] = None) extends FetchMetadata {
|
||||
def apply(artifact: Artifact,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, String] = {
|
||||
|
||||
EitherT(
|
||||
Task { implicit ec =>
|
||||
DefaultFetchMetadata.get(root + artifact.url)
|
||||
.map(\/-(_))
|
||||
.recover{case e: Exception => -\/(e.getMessage)}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
package coursier
|
||||
package core
|
||||
|
||||
import org.scalajs.dom.raw.{Event, XMLHttpRequest}
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Promise, Future}
|
||||
import scalaz.{-\/, \/-, \/, EitherT}
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
import scala.scalajs.js
|
||||
import js.Dynamic.{global => g}
|
||||
|
||||
import scala.scalajs.js.timers._
|
||||
|
||||
object Remote {
|
||||
|
||||
def encodeURIComponent(s: String): String =
|
||||
g.encodeURIComponent(s).asInstanceOf[String]
|
||||
|
||||
lazy val jsonpAvailable = !js.isUndefined(g.jsonp)
|
||||
|
||||
/** Available if we're running on node, and package xhr2 is installed */
|
||||
lazy val xhr = g.require("xhr2")
|
||||
def xhrReq() =
|
||||
js.Dynamic.newInstance(xhr)().asInstanceOf[XMLHttpRequest]
|
||||
|
||||
def fetchTimeout(target: String, p: Promise[_]) =
|
||||
setTimeout(5000) {
|
||||
if (!p.isCompleted) {
|
||||
p.failure(new Exception(s"Timeout when fetching $target"))
|
||||
}
|
||||
}
|
||||
|
||||
def proxiedJsonp(url: String)(implicit executionContext: ExecutionContext): Future[String] = {
|
||||
val url0 =
|
||||
"https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D%22" +
|
||||
encodeURIComponent(url) +
|
||||
"%22&format=jsonp&diagnostics=true"
|
||||
|
||||
val p = Promise[String]()
|
||||
|
||||
g.jsonp(url0, (res: js.Dynamic) => if (!p.isCompleted) {
|
||||
val success = !js.isUndefined(res) && !js.isUndefined(res.results)
|
||||
if (success)
|
||||
p.success(res.results.asInstanceOf[js.Array[String]].mkString("\n"))
|
||||
else
|
||||
p.failure(new Exception(s"Fetching $url ($url0)"))
|
||||
})
|
||||
|
||||
fetchTimeout(s"$url ($url0)", p)
|
||||
p.future
|
||||
}
|
||||
|
||||
def get(url: String)(implicit executionContext: ExecutionContext): Future[Either[String, Xml.Node]] =
|
||||
if (jsonpAvailable)
|
||||
proxiedJsonp(url).map(compatibility.xmlParse)
|
||||
else {
|
||||
val p = Promise[Either[String, Xml.Node]]()
|
||||
val xhrReq0 = xhrReq()
|
||||
val f = { _: Event =>
|
||||
p.success(compatibility.xmlParse(xhrReq0.responseText))
|
||||
}
|
||||
xhrReq0.onload = f
|
||||
|
||||
xhrReq0.open("GET", url)
|
||||
xhrReq0.send()
|
||||
|
||||
fetchTimeout(url, p)
|
||||
p.future
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
trait Logger {
|
||||
def fetching(url: String): Unit
|
||||
def fetched(url: String): Unit
|
||||
def other(url: String, msg: String): Unit
|
||||
}
|
||||
|
||||
case class Remote(base: String, logger: Option[Logger] = None) extends MavenRepository {
|
||||
|
||||
def findNoInterval(module: Module,
|
||||
version: String,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, Project] = {
|
||||
|
||||
val path = {
|
||||
module.organization.split('.').toSeq ++ Seq(
|
||||
module.name,
|
||||
version,
|
||||
s"${module.name}-$version.pom"
|
||||
)
|
||||
} .map(Remote.encodeURIComponent)
|
||||
|
||||
val url = base + path.mkString("/")
|
||||
|
||||
EitherT(Task{ implicit ec =>
|
||||
logger.foreach(_.fetching(url))
|
||||
Remote.get(url).recover{case e: Exception => Left(e.getMessage)}.map{ eitherXml =>
|
||||
logger.foreach(_.fetched(url))
|
||||
for {
|
||||
xml <- \/.fromEither(eitherXml)
|
||||
_ = logger.foreach(_.other(url, "is XML"))
|
||||
_ <- if (xml.label == "project") \/-(()) else -\/(s"Project definition not found (got '${xml.label}')")
|
||||
_ = logger.foreach(_.other(url, "project definition found"))
|
||||
proj <- Xml.project(xml)
|
||||
_ = logger.foreach(_.other(url, "project definition ok"))
|
||||
} yield proj
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def versions(organization: String,
|
||||
name: String,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, Versions] = {
|
||||
|
||||
val path = {
|
||||
organization.split('.').toSeq ++ Seq(
|
||||
name,
|
||||
"maven-metadata.xml"
|
||||
)
|
||||
} .map(Remote.encodeURIComponent)
|
||||
|
||||
val url = base + path.mkString("/")
|
||||
|
||||
EitherT(Task{ implicit ec =>
|
||||
logger.foreach(_.fetching(url))
|
||||
Remote.get(url).recover{case e: Exception => Left(e.getMessage)}.map{ eitherXml =>
|
||||
logger.foreach(_.fetched(url))
|
||||
for {
|
||||
xml <- \/.fromEither(eitherXml)
|
||||
_ <- if (xml.label == "metadata") \/-(()) else -\/("Metadata not found")
|
||||
versions <- Xml.versions(xml)
|
||||
} yield versions
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package coursier.core
|
||||
|
||||
import scala.scalajs.js
|
||||
import js.Dynamic.{global => g}
|
||||
import org.scalajs.dom.raw.NodeList
|
||||
|
||||
package object compatibility {
|
||||
|
|
@ -94,4 +95,7 @@ package object compatibility {
|
|||
Right(doc.fold(Xml.Node.empty)(fromNode))
|
||||
}
|
||||
|
||||
def encodeURIComponent(s: String): String =
|
||||
g.encodeURIComponent(s).asInstanceOf[String]
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
package coursier
|
||||
|
||||
import coursier.core.DefaultFetchMetadata
|
||||
|
||||
package object repository {
|
||||
|
||||
type Remote = core.Remote
|
||||
val Remote: core.Remote.type = core.Remote
|
||||
val mavenCentral = MavenRepository(DefaultFetchMetadata("https://repo1.maven.org/maven2/"))
|
||||
|
||||
val mavenCentral = Remote("https://repo1.maven.org/maven2/")
|
||||
|
||||
val sonatypeReleases = Remote("https://oss.sonatype.org/content/repositories/releases/")
|
||||
val sonatypeSnapshots = Remote("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
val sonatypeReleases = MavenRepository(DefaultFetchMetadata("https://oss.sonatype.org/content/repositories/releases/"))
|
||||
val sonatypeSnapshots = MavenRepository(DefaultFetchMetadata("https://oss.sonatype.org/content/repositories/snapshots/"))
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package coursier
|
||||
package test
|
||||
|
||||
import coursier.core.Remote
|
||||
import coursier.core.DefaultFetchMetadata
|
||||
import coursier.test.compatibility._
|
||||
|
||||
import utest._
|
||||
|
|
@ -18,7 +18,8 @@ object JsTests extends TestSuite {
|
|||
}
|
||||
|
||||
'get{
|
||||
Remote.get("http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.1.3/logback-classic-1.1.3.pom")
|
||||
DefaultFetchMetadata.get("http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.1.3/logback-classic-1.1.3.pom")
|
||||
.map(core.compatibility.xmlParse)
|
||||
.map{ xml =>
|
||||
assert(xml.right.toOption.exists(_.label == "project"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
package coursier
|
||||
package core
|
||||
|
||||
import java.io._
|
||||
import java.net.URL
|
||||
|
||||
import scala.io.Codec
|
||||
import scalaz._, Scalaz._
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
trait MetadataFetchLogger {
|
||||
def downloading(url: String): Unit
|
||||
def downloaded(url: String, success: Boolean): Unit
|
||||
def readingFromCache(f: File): Unit
|
||||
def puttingInCache(f: File): Unit
|
||||
}
|
||||
|
||||
case class DefaultFetchMetadata(root: String,
|
||||
cache: Option[File] = None,
|
||||
logger: Option[MetadataFetchLogger] = None) extends FetchMetadata {
|
||||
|
||||
def apply(artifact: Artifact, cachePolicy: CachePolicy): EitherT[Task, String, String] = {
|
||||
lazy val localFile = {
|
||||
for {
|
||||
cache0 <- cache.toRightDisjunction("No cache")
|
||||
f = new File(cache0, artifact.url)
|
||||
} yield f
|
||||
}
|
||||
|
||||
def locally = {
|
||||
Task {
|
||||
for {
|
||||
f0 <- localFile
|
||||
f <- Some(f0).filter(_.exists()).toRightDisjunction("Not found in cache")
|
||||
content <- \/.fromTryCatchNonFatal{
|
||||
logger.foreach(_.readingFromCache(f))
|
||||
scala.io.Source.fromFile(f)(Codec.UTF8).mkString
|
||||
}.leftMap(_.getMessage)
|
||||
} yield content
|
||||
}
|
||||
}
|
||||
|
||||
def remote = {
|
||||
val urlStr = root + artifact.url
|
||||
val url = new URL(urlStr)
|
||||
|
||||
def log = Task(logger.foreach(_.downloading(urlStr)))
|
||||
def get = DefaultFetchMetadata.readFully(url.openStream())
|
||||
|
||||
log.flatMap(_ => get)
|
||||
}
|
||||
|
||||
def save(s: String) = {
|
||||
localFile.fold(_ => Task.now(()), f =>
|
||||
Task {
|
||||
if (!f.exists()) {
|
||||
logger.foreach(_.puttingInCache(f))
|
||||
f.getParentFile.mkdirs()
|
||||
val w = new PrintWriter(f)
|
||||
try w.write(s)
|
||||
finally w.close()
|
||||
()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
EitherT(cachePolicy.saving(locally)(remote)(save))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object DefaultFetchMetadata {
|
||||
|
||||
def readFullySync(is: InputStream) = {
|
||||
val buffer = new ByteArrayOutputStream()
|
||||
val data = Array.ofDim[Byte](16384)
|
||||
|
||||
var nRead = is.read(data, 0, data.length)
|
||||
while (nRead != -1) {
|
||||
buffer.write(data, 0, nRead)
|
||||
nRead = is.read(data, 0, data.length)
|
||||
}
|
||||
|
||||
buffer.flush()
|
||||
buffer.toByteArray
|
||||
}
|
||||
|
||||
def readFully(is: => InputStream) =
|
||||
Task {
|
||||
\/.fromTryCatchNonFatal {
|
||||
val is0 = is
|
||||
val b =
|
||||
try readFullySync(is0)
|
||||
finally is0.close()
|
||||
|
||||
new String(b, "UTF-8")
|
||||
} .leftMap(_.getMessage)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
package coursier
|
||||
package core
|
||||
|
||||
import java.io._
|
||||
import java.net.URL
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.io.Codec
|
||||
import scalaz._, Scalaz._
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
// FIXME This kind of side-effecting API is lame, we should aim at a more functional one.
|
||||
trait ArtifactDownloaderLogger {
|
||||
def foundLocally(f: File): Unit
|
||||
def downloadingArtifact(url: String): Unit
|
||||
def downloadedArtifact(url: String, success: Boolean): Unit
|
||||
}
|
||||
|
||||
case class ArtifactDownloader(root: String, cache: File, logger: Option[ArtifactDownloaderLogger] = None) {
|
||||
var bufferSize = 1024*1024
|
||||
|
||||
def artifact(module: Module,
|
||||
version: String,
|
||||
attributes: Attributes,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, File] = {
|
||||
|
||||
val relPath =
|
||||
module.organization.split('.').toSeq ++ Seq(
|
||||
module.name,
|
||||
version,
|
||||
s"${module.name}-$version${Some(attributes.classifier).filter(_.nonEmpty).map("-"+_).mkString}.${attributes.`type`}"
|
||||
)
|
||||
|
||||
val file = (cache /: relPath)(new File(_, _))
|
||||
|
||||
def locally = {
|
||||
Task {
|
||||
if (file.exists()) {
|
||||
logger.foreach(_.foundLocally(file))
|
||||
\/-(file)
|
||||
}
|
||||
else -\/("Not found in cache")
|
||||
}
|
||||
}
|
||||
|
||||
def remote = {
|
||||
// FIXME A lot of things can go wrong here and are not properly handled:
|
||||
// - checksums should be validated
|
||||
// - what if the connection gets closed during the transfer (partial file on disk)?
|
||||
// - what if someone is trying to write this file at the same time? (no locking of any kind yet)
|
||||
// - ...
|
||||
|
||||
val urlStr = root + relPath.mkString("/")
|
||||
|
||||
Task {
|
||||
try {
|
||||
file.getParentFile.mkdirs()
|
||||
|
||||
logger.foreach(_.downloadingArtifact(urlStr))
|
||||
|
||||
val url = new URL(urlStr)
|
||||
val b = Array.fill[Byte](bufferSize)(0)
|
||||
val in = new BufferedInputStream(url.openStream(), bufferSize)
|
||||
|
||||
try {
|
||||
val out = new FileOutputStream(file)
|
||||
try {
|
||||
@tailrec
|
||||
def helper(): Unit = {
|
||||
val read = in.read(b)
|
||||
if (read >= 0) {
|
||||
out.write(b, 0, read)
|
||||
helper()
|
||||
}
|
||||
}
|
||||
|
||||
helper()
|
||||
} finally out.close()
|
||||
} finally in.close()
|
||||
|
||||
logger.foreach(_.downloadedArtifact(urlStr, success = true))
|
||||
\/-(file)
|
||||
}
|
||||
catch { case e: Exception =>
|
||||
logger.foreach(_.downloadedArtifact(urlStr, success = false))
|
||||
-\/(e.getMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EitherT(cachePolicy(locally)(remote))
|
||||
}
|
||||
|
||||
def artifacts(dependency: Dependency,
|
||||
project: Project,
|
||||
cachePolicy: CachePolicy = CachePolicy.Default): Task[Seq[String \/ File]] = {
|
||||
|
||||
// Important: using version from project, as the one from dependency can be an interval
|
||||
artifact(dependency.module, project.version, dependency.attributes, cachePolicy = cachePolicy).run.map(Seq(_))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// FIXME Comment of ArtifactDownloaderLogger applies here too
|
||||
trait RemoteLogger {
|
||||
def downloading(url: String): Unit
|
||||
def downloaded(url: String, success: Boolean): Unit
|
||||
def readingFromCache(f: File): Unit
|
||||
def puttingInCache(f: File): Unit
|
||||
}
|
||||
|
||||
object Remote {
|
||||
|
||||
def readFullySync(is: InputStream) = {
|
||||
val buffer = new ByteArrayOutputStream()
|
||||
val data = Array.ofDim[Byte](16384)
|
||||
|
||||
var nRead = is.read(data, 0, data.length)
|
||||
while (nRead != -1) {
|
||||
buffer.write(data, 0, nRead)
|
||||
nRead = is.read(data, 0, data.length)
|
||||
}
|
||||
|
||||
buffer.flush()
|
||||
buffer.toByteArray
|
||||
}
|
||||
|
||||
def readFully(is: => InputStream) =
|
||||
Task {
|
||||
\/.fromTryCatchNonFatal {
|
||||
val is0 = is
|
||||
val b =
|
||||
try readFullySync(is0)
|
||||
finally is0.close()
|
||||
|
||||
new String(b, "UTF-8")
|
||||
} .leftMap(_.getMessage)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class Remote(root: String,
|
||||
cache: Option[File] = None,
|
||||
logger: Option[RemoteLogger] = None) extends MavenRepository {
|
||||
|
||||
private def get(path: Seq[String],
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, String] = {
|
||||
|
||||
lazy val localFile = {
|
||||
for {
|
||||
cache0 <- cache.toRightDisjunction("No cache")
|
||||
f = (cache0 /: path)(new File(_, _))
|
||||
} yield f
|
||||
}
|
||||
|
||||
def locally = {
|
||||
Task {
|
||||
for {
|
||||
f0 <- localFile
|
||||
f <- Some(f0).filter(_.exists()).toRightDisjunction("Not found in cache")
|
||||
content <- \/.fromTryCatchNonFatal{
|
||||
logger.foreach(_.readingFromCache(f))
|
||||
scala.io.Source.fromFile(f)(Codec.UTF8).mkString
|
||||
}.leftMap(_.getMessage)
|
||||
} yield content
|
||||
}
|
||||
}
|
||||
|
||||
def remote = {
|
||||
val urlStr = root + path.mkString("/")
|
||||
val url = new URL(urlStr)
|
||||
|
||||
def log = Task(logger.foreach(_.downloading(urlStr)))
|
||||
def get = Remote.readFully(url.openStream())
|
||||
|
||||
log.flatMap(_ => get)
|
||||
}
|
||||
|
||||
def save(s: String) = {
|
||||
localFile.fold(_ => Task.now(()), f =>
|
||||
Task {
|
||||
if (!f.exists()) {
|
||||
logger.foreach(_.puttingInCache(f))
|
||||
f.getParentFile.mkdirs()
|
||||
val w = new PrintWriter(f)
|
||||
try w.write(s)
|
||||
finally w.close()
|
||||
()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
EitherT(cachePolicy.saving(locally)(remote)(save))
|
||||
}
|
||||
|
||||
def findNoInterval(module: Module,
|
||||
version: String,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, Project] = {
|
||||
|
||||
val path =
|
||||
module.organization.split('.').toSeq ++ Seq(
|
||||
module.name,
|
||||
version,
|
||||
s"${module.name}-$version.pom"
|
||||
)
|
||||
|
||||
val task = get(path, cachePolicy).run
|
||||
.map(eitherStr =>
|
||||
for {
|
||||
str <- eitherStr
|
||||
xml <- \/.fromEither(compatibility.xmlParse(str))
|
||||
_ <- if (xml.label == "project") \/-(()) else -\/("Project definition not found")
|
||||
proj <- Xml.project(xml)
|
||||
} yield proj
|
||||
)
|
||||
|
||||
EitherT(task)
|
||||
}
|
||||
|
||||
def versions(organization: String,
|
||||
name: String,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, Versions] = {
|
||||
|
||||
val path =
|
||||
organization.split('.').toSeq ++ Seq(
|
||||
name,
|
||||
"maven-metadata.xml"
|
||||
)
|
||||
|
||||
val task = get(path, cachePolicy).run
|
||||
.map(eitherStr =>
|
||||
for {
|
||||
str <- eitherStr
|
||||
xml <- \/.fromEither(compatibility.xmlParse(str))
|
||||
_ <- if (xml.label == "metadata") \/-(()) else -\/("Metadata not found")
|
||||
versions <- Xml.versions(xml)
|
||||
} yield versions
|
||||
)
|
||||
|
||||
EitherT(task)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,4 +25,6 @@ package object compatibility {
|
|||
.map(fromNode)
|
||||
}
|
||||
|
||||
def encodeURIComponent(s: String): String =
|
||||
new java.net.URI(null, null, null, -1, s, null, null) .toASCIIString
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
package coursier
|
||||
|
||||
import coursier.core.DefaultFetchMetadata
|
||||
|
||||
package object repository {
|
||||
|
||||
type Remote = core.Remote
|
||||
val Remote: core.Remote.type = core.Remote
|
||||
val mavenCentral = MavenRepository(DefaultFetchMetadata("https://repo1.maven.org/maven2/"))
|
||||
|
||||
val mavenCentral = Remote("https://repo1.maven.org/maven2/")
|
||||
|
||||
val sonatypeReleases = Remote("https://oss.sonatype.org/content/repositories/releases/")
|
||||
val sonatypeSnapshots = Remote("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
val sonatypeReleases = MavenRepository(DefaultFetchMetadata("https://oss.sonatype.org/content/repositories/releases/"))
|
||||
val sonatypeSnapshots = MavenRepository(DefaultFetchMetadata("https://oss.sonatype.org/content/repositories/snapshots/"))
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package coursier.test
|
||||
|
||||
import coursier.core.Remote
|
||||
import coursier.core.DefaultFetchMetadata
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scalaz.concurrent.Task
|
||||
|
|
@ -17,7 +17,7 @@ package object compatibility {
|
|||
def is = getClass.getClassLoader
|
||||
.getResource(path).openStream()
|
||||
|
||||
new String(Remote.readFullySync(is), "UTF-8")
|
||||
new String(DefaultFetchMetadata.readFullySync(is), "UTF-8")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ case class Profile(id: String,
|
|||
dependencyManagement: Seq[Dependency],
|
||||
properties: Map[String, String])
|
||||
|
||||
// FIXME Move to MavenRepository?
|
||||
case class Versions(latest: String,
|
||||
release: String,
|
||||
available: List[String],
|
||||
|
|
@ -78,3 +79,27 @@ case class Versions(latest: String,
|
|||
object Versions {
|
||||
case class DateTime(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int)
|
||||
}
|
||||
|
||||
case class Artifact(url: String,
|
||||
extra: Map[String, String],
|
||||
attributes: Attributes)
|
||||
|
||||
object Artifact {
|
||||
val md5 = "md5"
|
||||
val sha1 = "sha1"
|
||||
val sig = "pgp"
|
||||
val sigMd5 = "md5-pgp"
|
||||
val sigSha1 = "sha1-pgp"
|
||||
val sources = "src"
|
||||
val sourcesMd5 = "md5-src"
|
||||
val sourcesSha1 = "sha1-src"
|
||||
val sourcesSig = "src-pgp"
|
||||
val sourcesSigMd5 = "md5-src-pgp"
|
||||
val sourcesSigSha1 = "sha1-src-pgp"
|
||||
val javadoc = "javadoc"
|
||||
val javadocMd5 = "md5-javadoc"
|
||||
val javadocSha1 = "sha1-javadoc"
|
||||
val javadocSig = "javadoc-pgp"
|
||||
val javadocSigMd5 = "md5-javadoc-pgp"
|
||||
val javadocSigSha1 = "sha1-javadoc-pgp"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ package coursier.core
|
|||
import scalaz.{-\/, \/-, \/, EitherT}
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
import coursier.core.compatibility.encodeURIComponent
|
||||
|
||||
trait Repository {
|
||||
def find(module: Module, version: String, cachePolicy: CachePolicy = CachePolicy.Default): EitherT[Task, String, Project]
|
||||
def versions(organization: String, name: String, cachePolicy: CachePolicy = CachePolicy.Default): EitherT[Task, String, Versions]
|
||||
def artifacts(dependency: Dependency, project: Project): Seq[Artifact]
|
||||
}
|
||||
|
||||
sealed trait CachePolicy {
|
||||
|
|
@ -37,7 +39,132 @@ object CachePolicy {
|
|||
}
|
||||
}
|
||||
|
||||
trait MavenRepository extends Repository {
|
||||
object Repository {
|
||||
implicit class ArtifactExtensions(val underlying: Artifact) extends AnyVal {
|
||||
def withDefaultChecksums: Artifact =
|
||||
underlying.copy(extra = underlying.extra ++ Seq(
|
||||
Artifact.md5 -> (underlying.url + ".md5"),
|
||||
Artifact.sha1 -> (underlying.url + ".sha1")
|
||||
))
|
||||
def withDefaultSignature: Artifact =
|
||||
underlying.copy(extra = underlying.extra ++ Seq(
|
||||
Artifact.sigMd5 -> (underlying.url + ".asc.md5"),
|
||||
Artifact.sigSha1 -> (underlying.url + ".asc.sha1"),
|
||||
Artifact.sig -> (underlying.url + ".asc")
|
||||
))
|
||||
def withJavadocSources: Artifact = {
|
||||
val base = underlying.url.stripSuffix(".jar")
|
||||
underlying.copy(extra = underlying.extra ++ Seq(
|
||||
Artifact.sourcesMd5 -> (base + "-sources.jar.md5"),
|
||||
Artifact.sourcesSha1 -> (base + "-sources.jar.sha1"),
|
||||
Artifact.sources -> (base + "-sources.jar"),
|
||||
Artifact.sourcesSigMd5 -> (base + "-sources.jar.asc.md5"),
|
||||
Artifact.sourcesSigSha1 -> (base + "-sources.jar.asc.sha1"),
|
||||
Artifact.sourcesSig -> (base + "-sources.jar.asc"),
|
||||
Artifact.javadocMd5 -> (base + "-javadoc.jar.md5"),
|
||||
Artifact.javadocSha1 -> (base + "-javadoc.jar.sha1"),
|
||||
Artifact.javadoc -> (base + "-javadoc.jar"),
|
||||
Artifact.javadocSigMd5 -> (base + "-javadoc.jar.asc.md5"),
|
||||
Artifact.javadocSigSha1 -> (base + "-javadoc.jar.asc.sha1"),
|
||||
Artifact.javadocSig -> (base + "-javadoc.asc.jar")
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait FetchMetadata {
|
||||
def root: String
|
||||
def apply(artifact: Artifact,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, String]
|
||||
}
|
||||
|
||||
case class MavenRepository[F <: FetchMetadata](fetchMetadata: F,
|
||||
ivyLike: Boolean = false) extends Repository {
|
||||
|
||||
import Repository._
|
||||
|
||||
def projectArtifact(module: Module, version: String): Artifact = {
|
||||
if (ivyLike) ???
|
||||
else {
|
||||
val path = (
|
||||
module.organization.split('.').toSeq ++ Seq(
|
||||
module.name,
|
||||
version,
|
||||
s"${module.name}-$version.pom"
|
||||
)
|
||||
) .map(encodeURIComponent)
|
||||
|
||||
Artifact(
|
||||
path.mkString("/"),
|
||||
Map(
|
||||
Artifact.md5 -> "",
|
||||
Artifact.sha1 -> ""
|
||||
),
|
||||
Attributes("pom", "")
|
||||
)
|
||||
.withDefaultSignature
|
||||
}
|
||||
}
|
||||
|
||||
def versionsArtifact(module: Module): Option[Artifact] =
|
||||
if (ivyLike) None
|
||||
else {
|
||||
val path = (
|
||||
module.organization.split('.').toSeq ++ Seq(
|
||||
module.name,
|
||||
"maven-metadata.xml"
|
||||
)
|
||||
) .map(encodeURIComponent)
|
||||
|
||||
val artifact =
|
||||
Artifact(
|
||||
path.mkString("/"),
|
||||
Map.empty,
|
||||
Attributes("pom", "")
|
||||
)
|
||||
.withDefaultChecksums
|
||||
|
||||
Some(artifact)
|
||||
}
|
||||
|
||||
def versions(module: Module,
|
||||
cachePolicy: CachePolicy = CachePolicy.Default): EitherT[Task, String, Versions] = {
|
||||
|
||||
EitherT(
|
||||
versionsArtifact(module) match {
|
||||
case None => Task.now(-\/("Not supported"))
|
||||
case Some(artifact) =>
|
||||
fetchMetadata(artifact, cachePolicy)
|
||||
.run
|
||||
.map(eitherStr =>
|
||||
for {
|
||||
str <- eitherStr
|
||||
xml <- \/.fromEither(compatibility.xmlParse(str))
|
||||
_ <- if (xml.label == "metadata") \/-(()) else -\/("Metadata not found")
|
||||
versions <- Xml.versions(xml)
|
||||
} yield versions
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
def findNoInterval(module: Module,
|
||||
version: String,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, Project] = {
|
||||
|
||||
EitherT {
|
||||
fetchMetadata(projectArtifact(module, version), cachePolicy)
|
||||
.run
|
||||
.map(eitherStr =>
|
||||
for {
|
||||
str <- eitherStr
|
||||
xml <- \/.fromEither(compatibility.xmlParse(str))
|
||||
_ <- if (xml.label == "project") \/-(()) else -\/("Project definition not found")
|
||||
proj <- Xml.project(xml)
|
||||
} yield proj
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def find(module: Module,
|
||||
version: String,
|
||||
|
|
@ -46,28 +173,55 @@ trait MavenRepository extends Repository {
|
|||
Parse.versionInterval(version).filter(_.isValid) match {
|
||||
case None => findNoInterval(module, version, cachePolicy)
|
||||
case Some(itv) =>
|
||||
versions(module.organization, module.name, cachePolicy).flatMap { versions0 =>
|
||||
val eitherVersion = {
|
||||
val release = Version(versions0.release)
|
||||
if (itv.contains(release)) \/-(versions0.release)
|
||||
else {
|
||||
val inInterval = versions0.available.map(Version(_)).filter(itv.contains)
|
||||
if (inInterval.isEmpty) -\/(s"No version found for $version")
|
||||
else \/-(inInterval.max.repr)
|
||||
versions(module, cachePolicy)
|
||||
.flatMap { versions0 =>
|
||||
val eitherVersion = {
|
||||
val release = Version(versions0.release)
|
||||
|
||||
if (itv.contains(release)) \/-(versions0.release)
|
||||
else {
|
||||
val inInterval = versions0.available
|
||||
.map(Version(_))
|
||||
.filter(itv.contains)
|
||||
|
||||
if (inInterval.isEmpty) -\/(s"No version found for $version")
|
||||
else \/-(inInterval.max.repr)
|
||||
}
|
||||
}
|
||||
|
||||
eitherVersion match {
|
||||
case -\/(reason) => EitherT[Task, String, Project](Task.now(-\/(reason)))
|
||||
case \/-(version0) =>
|
||||
findNoInterval(module, version0, cachePolicy)
|
||||
.map(_.copy(versions = Some(versions0)))
|
||||
}
|
||||
}
|
||||
|
||||
eitherVersion match {
|
||||
case -\/(reason) => EitherT[Task, String, Project](Task.now(-\/(reason)))
|
||||
case \/-(version0) => findNoInterval(module, version0, cachePolicy)
|
||||
.map(_.copy(versions = Some(versions0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def findNoInterval(module: Module,
|
||||
version: String,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, Project]
|
||||
def artifacts(dependency: Dependency,
|
||||
project: Project): Seq[Artifact] = {
|
||||
|
||||
}
|
||||
val path =
|
||||
dependency.module.organization.split('.').toSeq ++ Seq(
|
||||
dependency.module.name,
|
||||
project.version,
|
||||
s"${dependency.module.name}-${project.version}${Some(dependency.attributes.classifier).filter(_.nonEmpty).map("-"+_).mkString}.${dependency.attributes.`type`}"
|
||||
)
|
||||
|
||||
var artifact =
|
||||
Artifact(
|
||||
fetchMetadata.root + path.mkString("/"),
|
||||
Map.empty,
|
||||
dependency.attributes
|
||||
)
|
||||
.withDefaultChecksums
|
||||
|
||||
if (dependency.attributes.`type` == "jar")
|
||||
artifact = artifact
|
||||
.withDefaultSignature
|
||||
.withJavadocSources
|
||||
|
||||
Seq(artifact)
|
||||
}
|
||||
}
|
||||
|
|
@ -322,14 +322,14 @@ object Resolution {
|
|||
*
|
||||
* @param dependencies: current set of dependencies
|
||||
* @param conflicts: conflicting dependencies
|
||||
* @param projectsCache: cache of known projects
|
||||
* @param errors: keeps track of the modules whose project definition could not be found
|
||||
* @param projectCache: cache of known projects
|
||||
* @param errorCache: keeps track of the modules whose project definition could not be found
|
||||
*/
|
||||
case class Resolution(rootDependencies: Set[Dependency],
|
||||
dependencies: Set[Dependency],
|
||||
conflicts: Set[Dependency],
|
||||
projectsCache: Map[Resolution.ModuleVersion, (Repository, Project)],
|
||||
errors: Map[Resolution.ModuleVersion, Seq[String]],
|
||||
projectCache: Map[Resolution.ModuleVersion, (Repository, Project)],
|
||||
errorCache: Map[Resolution.ModuleVersion, Seq[String]],
|
||||
filter: Option[Dependency => Boolean],
|
||||
profileActivation: Option[(String, Activation, Map[String, String]) => Boolean]) {
|
||||
import Resolution._
|
||||
|
|
@ -337,7 +337,7 @@ case class Resolution(rootDependencies: Set[Dependency],
|
|||
private val finalDependenciesCache = new mutable.HashMap[Dependency, Seq[Dependency]]()
|
||||
private def finalDependencies0(dep: Dependency) = finalDependenciesCache.synchronized {
|
||||
finalDependenciesCache.getOrElseUpdate(dep,
|
||||
projectsCache.get(dep.moduleVersion) match {
|
||||
projectCache.get(dep.moduleVersion) match {
|
||||
case Some((_, proj)) => finalDependencies(dep, proj).filter(filter getOrElse defaultFilter)
|
||||
case None => Nil
|
||||
}
|
||||
|
|
@ -374,7 +374,7 @@ case class Resolution(rootDependencies: Set[Dependency],
|
|||
val nextModules = nextDependenciesAndConflicts._2.map(_.moduleVersion)
|
||||
|
||||
(modules ++ nextModules)
|
||||
.filterNot(mod => projectsCache.contains(mod) || errors.contains(mod))
|
||||
.filterNot(mod => projectCache.contains(mod) || errorCache.contains(mod))
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -477,7 +477,7 @@ case class Resolution(rootDependencies: Set[Dependency],
|
|||
def dependencyManagementRequirements(project: Project): Set[ModuleVersion] = {
|
||||
val approxProperties =
|
||||
project.parent
|
||||
.flatMap(projectsCache.get)
|
||||
.flatMap(projectCache.get)
|
||||
.map(_._2.properties)
|
||||
.fold(project.properties)(mergeProperties(project.properties, _))
|
||||
|
||||
|
|
@ -511,13 +511,13 @@ case class Resolution(rootDependencies: Set[Dependency],
|
|||
if (toCheck.isEmpty) missing
|
||||
else if (toCheck.exists(done)) helper(toCheck -- done, done, missing)
|
||||
else if (toCheck.exists(missing)) helper(toCheck -- missing, done, missing)
|
||||
else if (toCheck.exists(projectsCache.contains)) {
|
||||
val (checking, remaining) = toCheck.partition(projectsCache.contains)
|
||||
val directRequirements = checking.flatMap(mod => dependencyManagementRequirements(projectsCache(mod)._2))
|
||||
else if (toCheck.exists(projectCache.contains)) {
|
||||
val (checking, remaining) = toCheck.partition(projectCache.contains)
|
||||
val directRequirements = checking.flatMap(mod => dependencyManagementRequirements(projectCache(mod)._2))
|
||||
|
||||
helper(remaining ++ directRequirements, done ++ checking, missing)
|
||||
} else if (toCheck.exists(errors.contains)) {
|
||||
val (errored, remaining) = toCheck.partition(errors.contains)
|
||||
} else if (toCheck.exists(errorCache.contains)) {
|
||||
val (errored, remaining) = toCheck.partition(errorCache.contains)
|
||||
helper(remaining, done ++ errored, missing)
|
||||
} else
|
||||
helper(Set.empty, done, missing ++ toCheck)
|
||||
|
|
@ -535,8 +535,8 @@ case class Resolution(rootDependencies: Set[Dependency],
|
|||
|
||||
val approxProperties =
|
||||
project.parent
|
||||
.filter(projectsCache.contains)
|
||||
.map(projectsCache(_)._2.properties)
|
||||
.filter(projectCache.contains)
|
||||
.map(projectCache(_)._2.properties)
|
||||
.fold(project.properties)(mergeProperties(project.properties, _))
|
||||
|
||||
val profiles0 = profiles(project, approxProperties, profileActivation getOrElse defaultProfileActivation)
|
||||
|
|
@ -546,9 +546,9 @@ case class Resolution(rootDependencies: Set[Dependency],
|
|||
|
||||
val deps =
|
||||
dependencies0
|
||||
.collect{ case dep if dep.scope == Scope.Import && projectsCache.contains(dep.moduleVersion) => dep.moduleVersion } ++
|
||||
project.parent.filter(projectsCache.contains)
|
||||
val projs = deps.map(projectsCache(_)._2)
|
||||
.collect{ case dep if dep.scope == Scope.Import && projectCache.contains(dep.moduleVersion) => dep.moduleVersion } ++
|
||||
project.parent.filter(projectCache.contains)
|
||||
val projs = deps.map(projectCache(_)._2)
|
||||
|
||||
val depMgmt =
|
||||
(project.dependencyManagement +: (profiles0.map(_.dependencyManagement) ++ projs.map(_.dependencyManagement)))
|
||||
|
|
@ -560,13 +560,13 @@ case class Resolution(rootDependencies: Set[Dependency],
|
|||
dependencies = dependencies0
|
||||
.filterNot(dep => dep.scope == Scope.Import && depsSet(dep.moduleVersion)) ++
|
||||
project.parent
|
||||
.filter(projectsCache.contains)
|
||||
.filter(projectCache.contains)
|
||||
.toSeq
|
||||
.flatMap(projectsCache(_)._2.dependencies),
|
||||
.flatMap(projectCache(_)._2.dependencies),
|
||||
dependencyManagement = depMgmt.values.toSeq,
|
||||
properties = project.parent
|
||||
.filter(projectsCache.contains)
|
||||
.map(projectsCache(_)._2.properties)
|
||||
.filter(projectCache.contains)
|
||||
.map(projectCache(_)._2.properties)
|
||||
.fold(properties0)(mergeProperties(properties0, _))
|
||||
)
|
||||
}
|
||||
|
|
@ -580,7 +580,7 @@ case class Resolution(rootDependencies: Set[Dependency],
|
|||
val lookups = modules.map(dep => fetchModule(dep).run.map(dep -> _))
|
||||
val gatheredLookups = Task.gatherUnordered(lookups, exceptionCancels = true)
|
||||
gatheredLookups.flatMap{ lookupResults =>
|
||||
val errors0 = errors ++ lookupResults.collect{case (modVer, -\/(repoErrors)) => modVer -> repoErrors}
|
||||
val errors0 = errorCache ++ lookupResults.collect{case (modVer, -\/(repoErrors)) => modVer -> repoErrors}
|
||||
val newProjects = lookupResults.collect{case (modVer, \/-(proj)) => modVer -> proj}
|
||||
|
||||
/*
|
||||
|
|
@ -588,12 +588,12 @@ case class Resolution(rootDependencies: Set[Dependency],
|
|||
* dependency management / inheritance-related bits to them.
|
||||
*/
|
||||
|
||||
newProjects.foldLeft(Task.now(copy(errors = errors0))) { case (accTask, (modVer, (repo, proj))) =>
|
||||
newProjects.foldLeft(Task.now(copy(errorCache = errors0))) { case (accTask, (modVer, (repo, proj))) =>
|
||||
for {
|
||||
current <- accTask
|
||||
updated <- current.fetch(current.dependencyManagementMissing(proj).toList, fetchModule)
|
||||
proj0 = updated.withDependencyManagement(proj)
|
||||
} yield updated.copy(projectsCache = updated.projectsCache + (modVer -> (repo, proj0)))
|
||||
} yield updated.copy(projectCache = updated.projectCache + (modVer -> (repo, proj0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -615,4 +615,17 @@ case class Resolution(rootDependencies: Set[Dependency],
|
|||
|
||||
def minDependencies: Set[Dependency] =
|
||||
Orders.minDependencies(dependencies)
|
||||
|
||||
def artifacts: Seq[Artifact] =
|
||||
for {
|
||||
dep <- minDependencies.toSeq
|
||||
(repo, proj) <- projectCache.get(dep.moduleVersion).toSeq
|
||||
artifact <- repo.artifacts(dep, proj)
|
||||
} yield artifact
|
||||
|
||||
def errors: Seq[(Dependency, Seq[String])] =
|
||||
for {
|
||||
dep <- dependencies.toSeq
|
||||
err <- errorCache.get(dep.moduleVersion).toSeq
|
||||
} yield (dep, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,11 +76,11 @@ package object coursier {
|
|||
def apply(rootDependencies: Set[Dependency] = Set.empty,
|
||||
dependencies: Set[Dependency] = Set.empty,
|
||||
conflicts: Set[Dependency] = Set.empty,
|
||||
projectsCache: Map[ModuleVersion, (Repository, Project)] = Map.empty,
|
||||
errors: Map[ModuleVersion, Seq[String]] = Map.empty,
|
||||
projectCache: Map[ModuleVersion, (Repository, Project)] = Map.empty,
|
||||
errorCache: Map[ModuleVersion, Seq[String]] = Map.empty,
|
||||
filter: Option[Dependency => Boolean] = None,
|
||||
profileActivation: Option[(String, Profile.Activation, Map[String, String]) => Boolean] = None): Resolution =
|
||||
core.Resolution(rootDependencies, dependencies, conflicts, projectsCache, errors, filter, profileActivation)
|
||||
core.Resolution(rootDependencies, dependencies, conflicts, projectCache, errorCache, filter, profileActivation)
|
||||
}
|
||||
|
||||
def resolve(dependencies: Set[Dependency],
|
||||
|
|
@ -98,4 +98,16 @@ package object coursier {
|
|||
|
||||
startResolution.last(fetch, maxIterations.getOrElse(-1))
|
||||
}
|
||||
|
||||
type Artifact = core.Artifact
|
||||
object Artifact {
|
||||
def apply(url: String,
|
||||
extra: Map[String, String] = Map.empty,
|
||||
attributes: Attributes = Attributes()): Artifact =
|
||||
core.Artifact(url, extra, attributes)
|
||||
}
|
||||
|
||||
type MavenRepository[G <: core.FetchMetadata] = core.MavenRepository[G]
|
||||
val MavenRepository: core.MavenRepository.type = core.MavenRepository
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ object CentralTests extends TestSuite {
|
|||
async {
|
||||
val dep = Dependency(Module("ch.qos.logback", "logback-classic"), "1.1.3")
|
||||
val res = await(resolve(Set(dep), fetchFrom(repositories)).runF)
|
||||
.copy(projectsCache = Map.empty, errors = Map.empty) // No validating these here
|
||||
.copy(projectCache = Map.empty, errorCache = Map.empty) // No validating these here
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -51,7 +51,7 @@ object CentralTests extends TestSuite {
|
|||
async {
|
||||
val dep = Dependency(Module("org.ow2.asm", "asm-commons"), "5.0.2")
|
||||
val res = await(resolve(Set(dep), fetchFrom(repositories)).runF)
|
||||
.copy(projectsCache = Map.empty, errors = Map.empty) // No validating these here
|
||||
.copy(projectCache = Map.empty, errorCache = Map.empty) // No validating these here
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -67,7 +67,7 @@ object CentralTests extends TestSuite {
|
|||
async {
|
||||
val dep = Dependency(Module("joda-time", "joda-time"), "[2.2,2.8]")
|
||||
val res0 = await(resolve(Set(dep), fetchFrom(repositories)).runF)
|
||||
val res = res0.copy(projectsCache = Map.empty, errors = Map.empty)
|
||||
val res = res0.copy(projectCache = Map.empty, errorCache = Map.empty)
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -75,9 +75,9 @@ object CentralTests extends TestSuite {
|
|||
dep.withCompileScope))
|
||||
|
||||
assert(res == expected)
|
||||
assert(res0.projectsCache.contains(dep.moduleVersion))
|
||||
assert(res0.projectCache.contains(dep.moduleVersion))
|
||||
|
||||
val (_, proj) = res0.projectsCache(dep.moduleVersion)
|
||||
val (_, proj) = res0.projectCache(dep.moduleVersion)
|
||||
assert(proj.version == "2.8")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ object ResolutionTests extends TestSuite {
|
|||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
dependencies = Set(dep.withCompileScope),
|
||||
errors = Map(dep.moduleVersion -> Seq("Not found"))
|
||||
errorCache = Map(dep.moduleVersion -> Seq("Not found"))
|
||||
)
|
||||
|
||||
assert(res == expected)
|
||||
|
|
@ -185,7 +185,7 @@ object ResolutionTests extends TestSuite {
|
|||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
dependencies = Set(dep.withCompileScope),
|
||||
projectsCache = Map(dep.moduleVersion -> (testRepository, projectsMap(dep.moduleVersion)))
|
||||
projectCache = Map(dep.moduleVersion -> (testRepository, projectsMap(dep.moduleVersion)))
|
||||
)
|
||||
|
||||
assert(res == expected)
|
||||
|
|
@ -203,7 +203,7 @@ object ResolutionTests extends TestSuite {
|
|||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
dependencies = Set(dep.withCompileScope, trDep.withCompileScope),
|
||||
projectsCache = Map(
|
||||
projectCache = Map(
|
||||
projectsMap(dep.moduleVersion).kv,
|
||||
projectsMap(trDep.moduleVersion).kv
|
||||
)
|
||||
|
|
@ -227,7 +227,7 @@ object ResolutionTests extends TestSuite {
|
|||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope),
|
||||
projectsCache = Map(
|
||||
projectCache = Map(
|
||||
projectsMap(dep.moduleVersion).kv
|
||||
) ++ trDeps.map(trDep => projectsMap(trDep.moduleVersion).kv)
|
||||
)
|
||||
|
|
@ -252,7 +252,7 @@ object ResolutionTests extends TestSuite {
|
|||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope),
|
||||
projectsCache = Map(
|
||||
projectCache = Map(
|
||||
projectsMap(dep.moduleVersion).kv
|
||||
) ++ trDeps.map(trDep => projectsMap(trDep.moduleVersion).kv)
|
||||
)
|
||||
|
|
@ -277,7 +277,7 @@ object ResolutionTests extends TestSuite {
|
|||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
dependencies = Set(dep.withCompileScope) ++ trDeps.map(_.withCompileScope),
|
||||
projectsCache = Map(
|
||||
projectCache = Map(
|
||||
projectsMap(dep.moduleVersion).kv
|
||||
) ++ trDeps.map(trDep => projectsMap(trDep.moduleVersion).kv)
|
||||
)
|
||||
|
|
@ -297,7 +297,7 @@ object ResolutionTests extends TestSuite {
|
|||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
dependencies = Set(dep.withCompileScope),
|
||||
projectsCache = Map(
|
||||
projectCache = Map(
|
||||
projectsMap(dep.moduleVersion).kv
|
||||
)
|
||||
)
|
||||
|
|
@ -316,7 +316,7 @@ object ResolutionTests extends TestSuite {
|
|||
Set(dep),
|
||||
fetchFrom(repositories),
|
||||
filter = Some(_.scope == Scope.Compile)
|
||||
).runF).copy(filter = None, projectsCache = Map.empty)
|
||||
).runF).copy(filter = None, projectCache = Map.empty)
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -336,7 +336,7 @@ object ResolutionTests extends TestSuite {
|
|||
Set(dep),
|
||||
fetchFrom(repositories),
|
||||
filter = Some(_.scope == Scope.Compile)
|
||||
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
|
||||
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -355,7 +355,7 @@ object ResolutionTests extends TestSuite {
|
|||
Set(dep),
|
||||
fetchFrom(repositories),
|
||||
filter = Some(_.scope == Scope.Compile)
|
||||
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
|
||||
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -372,7 +372,7 @@ object ResolutionTests extends TestSuite {
|
|||
Set(dep),
|
||||
fetchFrom(repositories),
|
||||
filter = Some(_.scope == Scope.Compile)
|
||||
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
|
||||
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -391,7 +391,7 @@ object ResolutionTests extends TestSuite {
|
|||
Set(dep),
|
||||
fetchFrom(repositories),
|
||||
filter = Some(_.scope == Scope.Compile)
|
||||
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
|
||||
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -412,7 +412,7 @@ object ResolutionTests extends TestSuite {
|
|||
Set(dep),
|
||||
fetchFrom(repositories),
|
||||
filter = Some(_.scope == Scope.Compile)
|
||||
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
|
||||
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -435,7 +435,7 @@ object ResolutionTests extends TestSuite {
|
|||
Set(dep),
|
||||
fetchFrom(repositories),
|
||||
filter = Some(_.scope == Scope.Compile)
|
||||
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
|
||||
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -457,7 +457,7 @@ object ResolutionTests extends TestSuite {
|
|||
Set(dep),
|
||||
fetchFrom(repositories),
|
||||
filter = Some(_.scope == Scope.Compile)
|
||||
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
|
||||
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = Set(dep),
|
||||
|
|
@ -481,7 +481,7 @@ object ResolutionTests extends TestSuite {
|
|||
deps,
|
||||
fetchFrom(repositories),
|
||||
filter = Some(_.scope == Scope.Compile)
|
||||
).runF).copy(filter = None, projectsCache = Map.empty, errors = Map.empty)
|
||||
).runF).copy(filter = None, projectCache = Map.empty, errorCache = Map.empty)
|
||||
|
||||
val expected = Resolution(
|
||||
rootDependencies = deps,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,5 @@ class TestRepository(projects: Map[(Module, String), Project]) extends Repositor
|
|||
EitherT(Task.now(
|
||||
projects.get((module, version)).toRightDisjunction("Not found")
|
||||
))
|
||||
def versions(organization: String, name: String, cachePolicy: CachePolicy) =
|
||||
EitherT(Task.now[String \/ Versions](
|
||||
-\/("Not supported")
|
||||
))
|
||||
def artifacts(dependency: Dependency, project: Project): Seq[Artifact] = ???
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
package coursier
|
||||
|
||||
import java.net.URL
|
||||
|
||||
import coursier.core.CachePolicy
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scalaz.{-\/, \/-, \/, EitherT}
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
import java.io._
|
||||
|
||||
// FIXME This kind of side-effecting API is lame, we should aim at a more functional one.
|
||||
trait FilesLogger {
|
||||
def foundLocally(f: File): Unit
|
||||
def downloadingArtifact(url: String): Unit
|
||||
def downloadedArtifact(url: String, success: Boolean): Unit
|
||||
}
|
||||
|
||||
case class Files(cache: Seq[(String, File)],
|
||||
tmp: () => File,
|
||||
logger: Option[FilesLogger] = None) {
|
||||
|
||||
def file(artifact: Artifact,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, File] = {
|
||||
|
||||
cache.find{case (base, _) => artifact.url.startsWith(base)} match {
|
||||
case None => ???
|
||||
case Some((base, cacheDir)) =>
|
||||
val file = new File(cacheDir, artifact.url.stripPrefix(base))
|
||||
|
||||
def locally = {
|
||||
Task {
|
||||
if (file.exists()) {
|
||||
logger.foreach(_.foundLocally(file))
|
||||
\/-(file)
|
||||
}
|
||||
else -\/("Not found in cache")
|
||||
}
|
||||
}
|
||||
|
||||
def remote = {
|
||||
// FIXME A lot of things can go wrong here and are not properly handled:
|
||||
// - checksums should be validated
|
||||
// - what if the connection gets closed during the transfer (partial file on disk)?
|
||||
// - what if someone is trying to write this file at the same time? (no locking of any kind yet)
|
||||
// - ...
|
||||
|
||||
Task {
|
||||
try {
|
||||
file.getParentFile.mkdirs()
|
||||
|
||||
logger.foreach(_.downloadingArtifact(artifact.url))
|
||||
|
||||
val url = new URL(artifact.url)
|
||||
val b = Array.fill[Byte](Files.bufferSize)(0)
|
||||
val in = new BufferedInputStream(url.openStream(), Files.bufferSize)
|
||||
|
||||
try {
|
||||
val out = new FileOutputStream(file)
|
||||
try {
|
||||
@tailrec
|
||||
def helper(): Unit = {
|
||||
val read = in.read(b)
|
||||
if (read >= 0) {
|
||||
out.write(b, 0, read)
|
||||
helper()
|
||||
}
|
||||
}
|
||||
|
||||
helper()
|
||||
} finally out.close()
|
||||
} finally in.close()
|
||||
|
||||
logger.foreach(_.downloadedArtifact(artifact.url, success = true))
|
||||
\/-(file)
|
||||
}
|
||||
catch { case e: Exception =>
|
||||
logger.foreach(_.downloadedArtifact(artifact.url, success = false))
|
||||
-\/(e.getMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EitherT(cachePolicy(locally)(remote))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Files {
|
||||
|
||||
var bufferSize = 1024*1024
|
||||
|
||||
def readFullySync(is: InputStream) = {
|
||||
val buffer = new ByteArrayOutputStream()
|
||||
val data = Array.ofDim[Byte](16384)
|
||||
|
||||
var nRead = is.read(data, 0, data.length)
|
||||
while (nRead != -1) {
|
||||
buffer.write(data, 0, nRead)
|
||||
nRead = is.read(data, 0, data.length)
|
||||
}
|
||||
|
||||
buffer.flush()
|
||||
buffer.toByteArray
|
||||
}
|
||||
|
||||
def readFully(is: => InputStream) =
|
||||
Task {
|
||||
\/.fromTryCatchNonFatal {
|
||||
val is0 = is
|
||||
val b =
|
||||
try readFullySync(is0)
|
||||
finally is0.close()
|
||||
|
||||
new String(b, "UTF-8")
|
||||
} .leftMap(_.getMessage)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -101,8 +101,20 @@ object CoursierBuild extends Build {
|
|||
)
|
||||
.enablePlugins(ScalaJSPlugin)
|
||||
|
||||
lazy val cli = Project(id = "cli", base = file("cli"))
|
||||
lazy val files = Project(id = "files", base = file("files"))
|
||||
.dependsOn(coreJvm)
|
||||
.settings(commonSettings: _*)
|
||||
.settings(
|
||||
name := "coursier-files",
|
||||
libraryDependencies ++= Seq(
|
||||
"org.http4s" %% "http4s-blazeclient" % "0.8.2",
|
||||
"com.lihaoyi" %% "utest" % "0.3.0" % "test"
|
||||
),
|
||||
testFrameworks += new TestFramework("utest.runner.Framework")
|
||||
)
|
||||
|
||||
lazy val cli = Project(id = "cli", base = file("cli"))
|
||||
.dependsOn(coreJvm, files)
|
||||
.settings(commonSettings ++ packAutoSettings ++ publishPackTxzArchive ++ publishPackZipArchive: _*)
|
||||
.settings(
|
||||
packArchivePrefix := s"coursier-cli_${scalaBinaryVersion.value}",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package coursier
|
||||
package web
|
||||
|
||||
import coursier.core.{Logger, Remote}
|
||||
import coursier.core.{DefaultFetchMetadata, Logger}
|
||||
import japgolly.scalajs.react.vdom.{TagMod, Attr}
|
||||
import japgolly.scalajs.react.vdom.Attrs.dangerouslySetInnerHtml
|
||||
import japgolly.scalajs.react.{ReactEventI, ReactComponentB, BackendScope}
|
||||
|
|
@ -18,7 +18,7 @@ case class ResolutionOptions(followOptional: Boolean = false,
|
|||
keepTest: Boolean = false)
|
||||
|
||||
case class State(modules: Seq[Dependency],
|
||||
repositories: Seq[Remote],
|
||||
repositories: Seq[MavenRepository[DefaultFetchMetadata]],
|
||||
options: ResolutionOptions,
|
||||
resolutionOpt: Option[Resolution],
|
||||
editModuleIdx: Int,
|
||||
|
|
@ -71,7 +71,7 @@ class Backend($: BackendScope[Unit, State]) {
|
|||
|
||||
def updateTree(resolution: Resolution, target: String, reverse: Boolean) = {
|
||||
def depsOf(dep: Dependency) =
|
||||
resolution.projectsCache.get(dep.moduleVersion).toSeq.flatMap(t => core.Resolution.finalDependencies(dep, t._2).filter(resolution.filter getOrElse core.Resolution.defaultFilter))
|
||||
resolution.projectCache.get(dep.moduleVersion).toSeq.flatMap(t => core.Resolution.finalDependencies(dep, t._2).filter(resolution.filter getOrElse core.Resolution.defaultFilter))
|
||||
|
||||
val minDependencies = resolution.minDependencies
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ class Backend($: BackendScope[Unit, State]) {
|
|||
filter = Some(dep => (s.options.followOptional || !dep.optional) && (s.options.keepTest || dep.scope != Scope.Test))
|
||||
)
|
||||
|
||||
res.last(fetchFrom(s.repositories.map(_.copy(logger = Some(logger)))), 100)
|
||||
res.last(fetchFrom(s.repositories.map(r => r.copy(fetchMetadata = r.fetchMetadata.copy(logger = Some(logger))))), 100)
|
||||
}
|
||||
|
||||
// For reasons that are unclear to me, not delaying this when using the runNow execution context
|
||||
|
|
@ -224,7 +224,7 @@ object App {
|
|||
|
||||
def depItem(dep: Dependency, finalVersionOpt: Option[String]) = {
|
||||
<.tr(
|
||||
^.`class` := (if (res.errors.contains(dep.moduleVersion)) "danger" else ""),
|
||||
^.`class` := (if (res.errorCache.contains(dep.moduleVersion)) "danger" else ""),
|
||||
<.td(dep.module.organization),
|
||||
<.td(dep.module.name),
|
||||
<.td(finalVersionOpt.fold(dep.version)(finalVersion => s"$finalVersion (for ${dep.version})")),
|
||||
|
|
@ -234,11 +234,11 @@ object App {
|
|||
if (dep.attributes.classifier.isEmpty) Seq() else Seq(infoLabel(dep.attributes.classifier)),
|
||||
Some(dep.exclusions).filter(_.nonEmpty).map(excls => infoPopOver("Exclusions", excls.toList.sorted.map{case (org, name) => s"$org:$name"}.mkString("; "))).toSeq,
|
||||
if (dep.optional) Seq(infoLabel("optional")) else Seq(),
|
||||
res.errors.get(dep.moduleVersion).map(errs => errorPopOver("Error", errs.mkString("; "))).toSeq
|
||||
res.errorCache.get(dep.moduleVersion).map(errs => errorPopOver("Error", errs.mkString("; "))).toSeq
|
||||
)),
|
||||
<.td(Seq[Seq[TagMod]](
|
||||
res.projectsCache.get(dep.moduleVersion) match {
|
||||
case Some((repo: Remote, _)) =>
|
||||
res.projectCache.get(dep.moduleVersion) match {
|
||||
case Some((MavenRepository(fetchMetadata, _), _)) =>
|
||||
// FIXME Maven specific, generalize if/when adding support for Ivy
|
||||
val version0 = finalVersionOpt getOrElse dep.version
|
||||
val relPath =
|
||||
|
|
@ -249,10 +249,10 @@ object App {
|
|||
)
|
||||
|
||||
Seq(
|
||||
<.a(^.href := s"${repo.base}${relPath.mkString("/")}.pom",
|
||||
<.a(^.href := s"${fetchMetadata.root}${relPath.mkString("/")}.pom",
|
||||
<.span(^.`class` := "label label-info", "POM")
|
||||
),
|
||||
<.a(^.href := s"${repo.base}${relPath.mkString("/")}.jar",
|
||||
<.a(^.href := s"${fetchMetadata.root}${relPath.mkString("/")}.jar",
|
||||
<.span(^.`class` := "label label-info", "JAR")
|
||||
)
|
||||
)
|
||||
|
|
@ -277,7 +277,7 @@ object App {
|
|||
)
|
||||
),
|
||||
<.tbody(
|
||||
sortedDeps.map(dep => depItem(dep, res.projectsCache.get(dep.moduleVersion).map(_._2.version).filter(_ != dep.version)))
|
||||
sortedDeps.map(dep => depItem(dep, res.projectCache.get(dep.moduleVersion).map(_._2.version).filter(_ != dep.version)))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -386,19 +386,19 @@ object App {
|
|||
|
||||
val modules = dependenciesTable("Dependencies")
|
||||
|
||||
val repositories = ReactComponentB[Seq[Remote]]("Repositories")
|
||||
val repositories = ReactComponentB[Seq[MavenRepository[DefaultFetchMetadata]]]("Repositories")
|
||||
.render{ repos =>
|
||||
def repoItem(repo: Remote) =
|
||||
def repoItem(repo: MavenRepository[DefaultFetchMetadata]) =
|
||||
<.tr(
|
||||
<.td(
|
||||
<.a(^.href := repo.base,
|
||||
repo.base
|
||||
<.a(^.href := repo.fetchMetadata.root,
|
||||
repo.fetchMetadata.root
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val sortedRepos = repos
|
||||
.sortBy(repo => repo.base)
|
||||
.sortBy(repo => repo.fetchMetadata.root)
|
||||
|
||||
<.table(^.`class` := "table",
|
||||
<.thead(
|
||||
|
|
|
|||
Loading…
Reference in New Issue