mirror of https://github.com/sbt/sbt.git
commit
9a164f7b75
|
|
@ -4,32 +4,69 @@ package cli
|
|||
import java.io.File
|
||||
|
||||
import caseapp._
|
||||
import coursier.core.{Fetch, Parse, Repository}, Repository.CachePolicy
|
||||
import coursier.core.{ MavenRepository, Parse, CachePolicy }
|
||||
|
||||
import scalaz.{ \/-, -\/ }
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
case class Coursier(scope: List[String],
|
||||
keepOptional: Boolean,
|
||||
fetch: Boolean,
|
||||
@ExtraName("v") verbose: List[Unit],
|
||||
@ExtraName("N") maxIterations: Int = 100) extends App {
|
||||
case class Coursier(
|
||||
scope: List[String],
|
||||
keepOptional: Boolean,
|
||||
fetch: Boolean,
|
||||
@ExtraName("J") default: Boolean,
|
||||
@ExtraName("S") sources: Boolean,
|
||||
@ExtraName("D") javadoc: Boolean,
|
||||
@ExtraName("P") @ExtraName("cp") classpath: Boolean,
|
||||
@ExtraName("c") offline: Boolean,
|
||||
@ExtraName("f") force: Boolean,
|
||||
@ExtraName("q") quiet: Boolean,
|
||||
@ExtraName("v") verbose: List[Unit],
|
||||
@ExtraName("N") maxIterations: Int = 100,
|
||||
@ExtraName("r") repository: List[String],
|
||||
@ExtraName("n") parallel: Option[Int]
|
||||
) extends App {
|
||||
|
||||
val verbose0 = verbose.length
|
||||
val verbose0 = {
|
||||
verbose.length +
|
||||
(if (quiet) 1 else 0)
|
||||
}
|
||||
|
||||
val scopes0 =
|
||||
if (scope.isEmpty) List(Scope.Compile, Scope.Runtime)
|
||||
else scope.map(Parse.scope)
|
||||
val scopes = scopes0.toSet
|
||||
|
||||
val centralCacheDir = new File(sys.props("user.home") + "/.coursier/cache/metadata/central")
|
||||
val centralFilesCacheDir = new File(sys.props("user.home") + "/.coursier/cache/files/central")
|
||||
|
||||
def fileRepr(f: File) = f.toString
|
||||
|
||||
val logger: Fetch.Logger with FilesLogger =
|
||||
new Fetch.Logger with FilesLogger {
|
||||
def println(s: String) = Console.err.println(s)
|
||||
def println(s: String) = Console.err.println(s)
|
||||
|
||||
|
||||
if (force && offline) {
|
||||
println("Error: --offline (-c) and --force (-f) options can't be specified at the same time.")
|
||||
sys.exit(255)
|
||||
}
|
||||
|
||||
|
||||
def defaultLogger: MavenRepository.Logger with Files.Logger =
|
||||
new MavenRepository.Logger with Files.Logger {
|
||||
def downloading(url: String) =
|
||||
println(s"Downloading $url")
|
||||
def downloaded(url: String, success: Boolean) =
|
||||
if (!success)
|
||||
println(s"Failed to download $url")
|
||||
def readingFromCache(f: File) = {}
|
||||
def puttingInCache(f: File) = {}
|
||||
|
||||
def foundLocally(f: File) = {}
|
||||
def downloadingArtifact(url: String) =
|
||||
println(s"Downloading $url")
|
||||
def downloadedArtifact(url: String, success: Boolean) =
|
||||
if (!success)
|
||||
println(s"Failed to download $url")
|
||||
}
|
||||
|
||||
def verboseLogger: MavenRepository.Logger with Files.Logger =
|
||||
new MavenRepository.Logger with Files.Logger {
|
||||
def downloading(url: String) =
|
||||
println(s"Downloading $url")
|
||||
def downloaded(url: String, success: Boolean) =
|
||||
|
|
@ -54,20 +91,62 @@ case class Coursier(scope: List[String],
|
|||
)
|
||||
}
|
||||
|
||||
val cachedMavenCentral = Repository.mavenCentral.copy(
|
||||
fetch = Repository.mavenCentral.fetch.copy(
|
||||
cache = Some(centralCacheDir),
|
||||
logger = if (verbose0 <= 1) None else Some(logger)
|
||||
val logger =
|
||||
if (verbose0 < 0)
|
||||
None
|
||||
else if (verbose0 == 0)
|
||||
Some(defaultLogger)
|
||||
else
|
||||
Some(verboseLogger)
|
||||
|
||||
implicit val cachePolicy =
|
||||
if (offline)
|
||||
CachePolicy.LocalOnly
|
||||
else if (force)
|
||||
CachePolicy.ForceDownload
|
||||
else
|
||||
CachePolicy.Default
|
||||
|
||||
val cache = Cache.default
|
||||
|
||||
val repositoryIds = {
|
||||
val repository0 = repository
|
||||
.flatMap(_.split(','))
|
||||
.map(_.trim)
|
||||
.filter(_.nonEmpty)
|
||||
|
||||
if (repository0.isEmpty)
|
||||
cache.default()
|
||||
else
|
||||
repository0
|
||||
}
|
||||
|
||||
val existingRepo = cache
|
||||
.list()
|
||||
.map(_._1)
|
||||
.toSet
|
||||
if (repositoryIds.exists(!existingRepo(_))) {
|
||||
val notFound = repositoryIds
|
||||
.filter(!existingRepo(_))
|
||||
|
||||
Console.err.println(
|
||||
(if (notFound.lengthCompare(1) == 1) "Repository" else "Repositories") +
|
||||
" not found: " +
|
||||
notFound.mkString(", ")
|
||||
)
|
||||
)
|
||||
val repositories = Seq[Repository](
|
||||
cachedMavenCentral,
|
||||
Repository.ivy2Local.copy(
|
||||
fetch = Repository.ivy2Local.fetch.copy(
|
||||
logger = if (verbose0 <= 1) None else Some(logger)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
|
||||
val (repositories0, fileCaches) = cache
|
||||
.list()
|
||||
.map{case (_, repo, cacheEntry) => (repo, cacheEntry)}
|
||||
.unzip
|
||||
|
||||
val repositories = repositories0
|
||||
.map(_.copy(logger = logger))
|
||||
|
||||
|
||||
val (splitDependencies, malformed) = remainingArgs.toList
|
||||
.map(_.split(":", 3).toSeq)
|
||||
|
|
@ -79,7 +158,7 @@ case class Coursier(scope: List[String],
|
|||
}
|
||||
|
||||
if (malformed.nonEmpty) {
|
||||
Console.err.println(s"Malformed dependencies:\n${malformed.map(_.mkString(":")).mkString("\n")}")
|
||||
println(s"Malformed dependencies:\n${malformed.map(_.mkString(":")).mkString("\n")}")
|
||||
sys exit 1
|
||||
}
|
||||
|
||||
|
|
@ -109,13 +188,16 @@ case class Coursier(scope: List[String],
|
|||
print.flatMap(_ => fetchQuiet(modVers))
|
||||
}
|
||||
|
||||
if (verbose0 >= 0)
|
||||
println(s"Resolving\n" + moduleVersions.map{case (mod, ver) => s" $mod:$ver"}.mkString("\n"))
|
||||
|
||||
val res = startRes
|
||||
.process
|
||||
.run(fetch0, maxIterations)
|
||||
.run
|
||||
|
||||
if (!res.isDone) {
|
||||
Console.err.println(s"Maximum number of iteration reached!")
|
||||
println(s"Maximum number of iteration reached!")
|
||||
sys exit 1
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +213,8 @@ case class Coursier(scope: List[String],
|
|||
|
||||
val trDeps = res.minDependencies.toList.sortBy(repr)
|
||||
|
||||
println("\n" + trDeps.map(repr).distinct.mkString("\n"))
|
||||
if (verbose0 >= 0)
|
||||
println("\n" + trDeps.map(repr).distinct.mkString("\n"))
|
||||
|
||||
if (res.conflicts.nonEmpty) {
|
||||
// Needs test
|
||||
|
|
@ -146,25 +229,56 @@ case class Coursier(scope: List[String],
|
|||
}
|
||||
}
|
||||
|
||||
if (fetch) {
|
||||
println()
|
||||
if (fetch || classpath) {
|
||||
println("")
|
||||
|
||||
val cachePolicy: CachePolicy = CachePolicy.Default
|
||||
val artifacts0 = res.artifacts
|
||||
val default0 = default || (!sources && !javadoc)
|
||||
val artifacts = artifacts0
|
||||
.flatMap{ artifact =>
|
||||
var l = List.empty[Artifact]
|
||||
if (sources)
|
||||
l = artifact.extra.get("sources").toList ::: l
|
||||
if (javadoc)
|
||||
l = artifact.extra.get("javadoc").toList ::: l
|
||||
if (default0)
|
||||
l = artifact :: l
|
||||
|
||||
val artifacts = res.artifacts
|
||||
l
|
||||
}
|
||||
|
||||
val files = new Files(
|
||||
Seq(
|
||||
cachedMavenCentral.fetch.root -> centralFilesCacheDir
|
||||
),
|
||||
() => ???,
|
||||
if (verbose0 <= 0) None else Some(logger)
|
||||
val files = {
|
||||
var files0 = cache
|
||||
.files()
|
||||
.copy(logger = logger)
|
||||
for (n <- parallel)
|
||||
files0 = files0.copy(concurrentDownloadCount = n)
|
||||
files0
|
||||
}
|
||||
|
||||
val tasks = artifacts.map(artifact => files.file(artifact, cachePolicy).run.map(artifact.->))
|
||||
def printTask = Task{
|
||||
if (verbose0 >= 0 && artifacts.nonEmpty)
|
||||
println(s"Found ${artifacts.length} artifacts")
|
||||
}
|
||||
val task = printTask.flatMap(_ => Task.gatherUnordered(tasks))
|
||||
|
||||
val results = task.run
|
||||
val errors = results.collect{case (artifact, -\/(err)) => artifact -> err }
|
||||
val files0 = results.collect{case (artifact, \/-(f)) => f }
|
||||
|
||||
if (errors.nonEmpty) {
|
||||
println(s"${errors.size} error(s):")
|
||||
for ((artifact, error) <- errors) {
|
||||
println(s" ${artifact.url}: $error")
|
||||
}
|
||||
}
|
||||
|
||||
Console.out.println(
|
||||
files0
|
||||
.map(_.toString)
|
||||
.mkString(if (classpath) File.pathSeparator else "\n")
|
||||
)
|
||||
|
||||
val tasks = artifacts.map(files.file(_, cachePolicy).run)
|
||||
val task = Task.gatherUnordered(tasks)
|
||||
|
||||
task.run
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
package coursier.cli
|
||||
|
||||
import coursier.Cache
|
||||
import caseapp._
|
||||
|
||||
// TODO: allow removing a repository (with confirmations, etc.)
|
||||
case class Repositories(
|
||||
@ValueDescription("id:baseUrl") @ExtraName("a") add: List[String],
|
||||
@ExtraName("L") list: Boolean,
|
||||
@ExtraName("l") defaultList: Boolean,
|
||||
ivyLike: Boolean
|
||||
) extends App {
|
||||
|
||||
if (add.exists(!_.contains(":"))) {
|
||||
CaseApp.printUsage[Repositories](err = true)
|
||||
sys.exit(255)
|
||||
}
|
||||
|
||||
val add0 = add
|
||||
.map{ s =>
|
||||
val Seq(id, baseUrl) = s.split(":", 2).toSeq
|
||||
id -> baseUrl
|
||||
}
|
||||
|
||||
if (
|
||||
add0.exists(_._1.contains("/")) ||
|
||||
add0.exists(_._1.startsWith(".")) ||
|
||||
add0.exists(_._1.isEmpty)
|
||||
) {
|
||||
CaseApp.printUsage[Repositories](err = true)
|
||||
sys.exit(255)
|
||||
}
|
||||
|
||||
|
||||
val cache = Cache.default
|
||||
|
||||
if (cache.cache.exists() && !cache.cache.isDirectory) {
|
||||
Console.err.println(s"Error: ${cache.cache} not a directory")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if (!cache.cache.exists())
|
||||
cache.init()
|
||||
|
||||
val current = cache.list().map(_._1).toSet
|
||||
|
||||
val alreadyAdded = add0
|
||||
.map(_._1)
|
||||
.filter(current)
|
||||
|
||||
if (alreadyAdded.nonEmpty) {
|
||||
Console.err.println(s"Error: already added: ${alreadyAdded.mkString(", ")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
for ((id, baseUrl0) <- add0) {
|
||||
val baseUrl =
|
||||
if (baseUrl0.endsWith("/"))
|
||||
baseUrl0
|
||||
else
|
||||
baseUrl0 + "/"
|
||||
|
||||
cache.add(id, baseUrl, ivyLike = ivyLike)
|
||||
}
|
||||
|
||||
if (defaultList) {
|
||||
val map = cache.repositoryMap()
|
||||
|
||||
for (id <- cache.default(withNotFound = true))
|
||||
map.get(id) match {
|
||||
case Some(repo) =>
|
||||
println(s"$id: ${repo.root}" + (if (repo.ivyLike) " (Ivy-like)" else ""))
|
||||
case None =>
|
||||
println(s"$id (not found)")
|
||||
}
|
||||
}
|
||||
|
||||
if (list)
|
||||
for ((id, repo, _) <- cache.list().sortBy(_._1)) {
|
||||
println(s"$id: ${repo.root}" + (if (repo.ivyLike) " (Ivy-like)" else ""))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Repositories extends AppOf[Repositories] {
|
||||
val parser = default
|
||||
}
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
package coursier
|
||||
package core
|
||||
|
||||
import org.scalajs.dom.raw.{Event, XMLHttpRequest}
|
||||
import org.scalajs.dom.raw.{ Event, XMLHttpRequest }
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Promise, Future}
|
||||
import scalaz.{-\/, \/-, EitherT}
|
||||
import scala.concurrent.{ ExecutionContext, Promise, Future }
|
||||
import scalaz.{ -\/, \/-, EitherT }
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
import scala.scalajs.js
|
||||
import js.Dynamic.{global => g}
|
||||
import js.Dynamic.{ global => g }
|
||||
|
||||
import scala.scalajs.js.timers._
|
||||
|
||||
object Fetch {
|
||||
object MavenRepository {
|
||||
|
||||
def encodeURIComponent(s: String): String =
|
||||
g.encodeURIComponent(s).asInstanceOf[String]
|
||||
|
|
@ -84,18 +84,24 @@ object Fetch {
|
|||
|
||||
}
|
||||
|
||||
case class Fetch(root: String,
|
||||
logger: Option[Fetch.Logger] = None) {
|
||||
case class MavenRepository(
|
||||
root: String,
|
||||
ivyLike: Boolean = false,
|
||||
logger: Option[MavenRepository.Logger] = None
|
||||
) extends BaseMavenRepository(root, ivyLike) {
|
||||
|
||||
def apply(artifact: Artifact,
|
||||
cachePolicy: Repository.CachePolicy): EitherT[Task, String, String] = {
|
||||
|
||||
def fetch(
|
||||
artifact: Artifact,
|
||||
cachePolicy: CachePolicy
|
||||
): EitherT[Task, String, String] = {
|
||||
|
||||
val url0 = root + artifact.url
|
||||
|
||||
EitherT(
|
||||
Task { implicit ec =>
|
||||
Future(logger.foreach(_.fetching(url0)))
|
||||
.flatMap(_ => Fetch.get(url0))
|
||||
.flatMap(_ => MavenRepository.get(url0))
|
||||
.map{ s => logger.foreach(_.fetched(url0)); \/-(s) }
|
||||
.recover{case e: Exception =>
|
||||
logger.foreach(_.other(url0, e.getMessage))
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package coursier.core
|
||||
|
||||
import scala.scalajs.js
|
||||
import js.Dynamic.{global => g}
|
||||
import js.Dynamic.{ global => g }
|
||||
import org.scalajs.dom.raw.NodeList
|
||||
|
||||
package object compatibility {
|
||||
|
|
@ -21,25 +21,16 @@ package object compatibility {
|
|||
def letter: Boolean = between(c, 'a', 'z') || between(c, 'A', 'Z')
|
||||
}
|
||||
|
||||
lazy val DOMParser = {
|
||||
import js.Dynamic.{global => g}
|
||||
import js.DynamicImplicits._
|
||||
def newFromXmlDomOrGlobal(name: String) = {
|
||||
var defn = g.selectDynamic(name)
|
||||
if (js.isUndefined(defn))
|
||||
defn = g.require("xmldom").selectDynamic(name)
|
||||
|
||||
val defn =
|
||||
if (js.isUndefined(g.DOMParser)) g.require("xmldom").DOMParser
|
||||
else g.DOMParser
|
||||
js.Dynamic.newInstance(defn)()
|
||||
}
|
||||
|
||||
lazy val XMLSerializer = {
|
||||
import js.Dynamic.{global => g}
|
||||
import js.DynamicImplicits._
|
||||
|
||||
val defn =
|
||||
if (js.isUndefined(g.XMLSerializer)) g.require("xmldom").XMLSerializer
|
||||
else g.XMLSerializer
|
||||
js.Dynamic.newInstance(defn)()
|
||||
}
|
||||
lazy val DOMParser = newFromXmlDomOrGlobal("DOMParser")
|
||||
lazy val XMLSerializer = newFromXmlDomOrGlobal("XMLSerializer")
|
||||
|
||||
// Can't find these from node
|
||||
val ELEMENT_NODE = 1 // org.scalajs.dom.raw.Node.ELEMENT_NODE
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package scalaz
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.concurrent.{ ExecutionContext, Future }
|
||||
|
||||
/** Minimal Future-based Task */
|
||||
package object concurrent {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package coursier
|
||||
package test
|
||||
|
||||
import coursier.core.{Fetch, Repository}
|
||||
import coursier.core.{Repository, MavenRepository}
|
||||
import coursier.test.compatibility._
|
||||
|
||||
import utest._
|
||||
|
|
@ -18,7 +18,7 @@ object JsTests extends TestSuite {
|
|||
}
|
||||
|
||||
'get{
|
||||
Fetch.get("http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.1.3/logback-classic-1.1.3.pom")
|
||||
MavenRepository.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"))
|
||||
|
|
@ -26,6 +26,8 @@ object JsTests extends TestSuite {
|
|||
}
|
||||
|
||||
'getProj{
|
||||
implicit val cachePolicy = CachePolicy.Default
|
||||
|
||||
Repository.mavenCentral
|
||||
.find(Module("ch.qos.logback", "logback-classic"), "1.1.3")
|
||||
.map{case (_, proj) =>
|
||||
|
|
|
|||
|
|
@ -2,20 +2,25 @@ package coursier
|
|||
package core
|
||||
|
||||
import java.io._
|
||||
import java.net.{URI, URL}
|
||||
import java.net.{ URI, URL }
|
||||
|
||||
import scala.io.Codec
|
||||
import scalaz._, Scalaz._
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
case class Fetch(root: String,
|
||||
cache: Option[File] = None,
|
||||
logger: Option[Fetch.Logger] = None) {
|
||||
case class MavenRepository(
|
||||
root: String,
|
||||
cache: Option[File] = None,
|
||||
ivyLike: Boolean = false,
|
||||
logger: Option[MavenRepository.Logger] = None
|
||||
) extends BaseMavenRepository(root, ivyLike) {
|
||||
|
||||
val isLocal = root.startsWith("file:///")
|
||||
|
||||
def apply(artifact: Artifact,
|
||||
cachePolicy: Repository.CachePolicy): EitherT[Task, String, String] = {
|
||||
def fetch(
|
||||
artifact: Artifact,
|
||||
cachePolicy: CachePolicy
|
||||
): EitherT[Task, String, String] = {
|
||||
|
||||
def locally(eitherFile: String \/ File) = {
|
||||
Task {
|
||||
|
|
@ -44,7 +49,7 @@ case class Fetch(root: String,
|
|||
val url = new URL(urlStr)
|
||||
|
||||
def log = Task(logger.foreach(_.downloading(urlStr)))
|
||||
def get = Fetch.readFully(url.openStream())
|
||||
def get = MavenRepository.readFully(url.openStream())
|
||||
|
||||
log.flatMap(_ => get)
|
||||
}
|
||||
|
|
@ -70,7 +75,7 @@ case class Fetch(root: String,
|
|||
|
||||
}
|
||||
|
||||
object Fetch {
|
||||
object MavenRepository {
|
||||
|
||||
trait Logger {
|
||||
def downloading(url: String): Unit
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package coursier.test
|
||||
|
||||
import coursier.core.Fetch
|
||||
import coursier.core.MavenRepository
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scalaz.concurrent.Task
|
||||
|
|
@ -14,10 +14,12 @@ package object compatibility {
|
|||
}
|
||||
|
||||
def textResource(path: String)(implicit ec: ExecutionContext): Future[String] = Future {
|
||||
def is = getClass.getClassLoader
|
||||
.getResource(path).openStream()
|
||||
def is = getClass
|
||||
.getClassLoader
|
||||
.getResource(path)
|
||||
.openStream()
|
||||
|
||||
new String(Fetch.readFullySync(is), "UTF-8")
|
||||
new String(MavenRepository.readFullySync(is), "UTF-8")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,29 +101,12 @@ object Versions {
|
|||
|
||||
case class Artifact(
|
||||
url: String,
|
||||
extra: Map[String, String],
|
||||
checksumUrls: Map[String, String],
|
||||
extra: Map[String, Artifact],
|
||||
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"
|
||||
|
||||
trait Source {
|
||||
def artifacts(dependency: Dependency, project: Project): Seq[Artifact]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
package coursier.core
|
||||
|
||||
import coursier.core.Resolution.ModuleVersion
|
||||
|
||||
import scalaz.{-\/, \/-, \/, EitherT}
|
||||
import scalaz.{ -\/, \/-, \/, EitherT }
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
import coursier.core.compatibility.encodeURIComponent
|
||||
|
||||
trait Repository {
|
||||
def find(module: Module,
|
||||
version: String,
|
||||
cachePolicy: Repository.CachePolicy = Repository.CachePolicy.Default): EitherT[Task, String, (Artifact.Source, Project)]
|
||||
def find(
|
||||
module: Module,
|
||||
version: String
|
||||
)(implicit
|
||||
cachePolicy: CachePolicy
|
||||
): EitherT[Task, String, (Artifact.Source, Project)]
|
||||
}
|
||||
|
||||
object Repository {
|
||||
|
||||
val mavenCentral = MavenRepository(Fetch("https://repo1.maven.org/maven2/"))
|
||||
val mavenCentral = MavenRepository("https://repo1.maven.org/maven2/")
|
||||
|
||||
val sonatypeReleases = MavenRepository(Fetch("https://oss.sonatype.org/content/repositories/releases/"))
|
||||
val sonatypeSnapshots = MavenRepository(Fetch("https://oss.sonatype.org/content/repositories/snapshots/"))
|
||||
val sonatypeReleases = MavenRepository("https://oss.sonatype.org/content/repositories/releases/")
|
||||
val sonatypeSnapshots = MavenRepository("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
|
||||
lazy val ivy2Local = MavenRepository(Fetch("file://" + sys.props("user.home") + "/.ivy2/local/"), ivyLike = true)
|
||||
lazy val ivy2Local = MavenRepository("file://" + sys.props("user.home") + "/.ivy2/local/", ivyLike = true)
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -34,111 +35,144 @@ object Repository {
|
|||
* version (e.g. version interval). Which version get chosen depends on
|
||||
* the repository implementation.
|
||||
*/
|
||||
def find(repositories: Seq[Repository],
|
||||
module: Module,
|
||||
version: String): EitherT[Task, Seq[String], (Artifact.Source, Project)] = {
|
||||
def find(
|
||||
repositories: Seq[Repository],
|
||||
module: Module,
|
||||
version: String
|
||||
)(implicit
|
||||
cachePolicy: CachePolicy
|
||||
): EitherT[Task, Seq[String], (Artifact.Source, Project)] = {
|
||||
|
||||
val lookups = repositories.map(repo => repo -> repo.find(module, version).run)
|
||||
val task = lookups.foldLeft(Task.now(-\/(Nil)): Task[Seq[String] \/ (Artifact.Source, Project)]) {
|
||||
case (acc, (repo, t)) =>
|
||||
acc.flatMap {
|
||||
case -\/(errors) =>
|
||||
t.map(res => res
|
||||
.flatMap{case (source, project) =>
|
||||
if (project.module == module) \/-((source, project))
|
||||
else -\/(s"Wrong module returned (expected: $module, got: ${project.module})")
|
||||
}
|
||||
.leftMap(error => error +: errors)
|
||||
)
|
||||
val lookups = repositories
|
||||
.map(repo => repo -> repo.find(module, version).run)
|
||||
|
||||
case res @ \/-(_) =>
|
||||
Task.now(res)
|
||||
}
|
||||
}
|
||||
val task = lookups
|
||||
.foldLeft(Task.now(-\/(Nil)): Task[Seq[String] \/ (Artifact.Source, Project)]) {
|
||||
case (acc, (repo, eitherProjTask)) =>
|
||||
acc
|
||||
.flatMap {
|
||||
case -\/(errors) =>
|
||||
eitherProjTask
|
||||
.map(res => res
|
||||
.flatMap{case (source, project) =>
|
||||
if (project.module == module) \/-((source, project))
|
||||
else -\/(s"Wrong module returned (expected: $module, got: ${project.module})")
|
||||
}
|
||||
.leftMap(error => error +: errors)
|
||||
)
|
||||
|
||||
EitherT(task.map(_.leftMap(_.reverse))).map {case x @ (_, proj) =>
|
||||
assert(proj.module == module)
|
||||
x
|
||||
}
|
||||
}
|
||||
case res @ \/-(_) =>
|
||||
Task.now(res)
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait CachePolicy {
|
||||
def apply[E,T](local: => Task[E \/ T])
|
||||
(remote: => Task[E \/ T]): Task[E \/ T]
|
||||
|
||||
def saving[E,T](local: => Task[E \/ T])
|
||||
(remote: => Task[E \/ T])
|
||||
(save: => T => Task[Unit]): Task[E \/ T] =
|
||||
apply(local)(CachePolicy.saving(remote)(save))
|
||||
}
|
||||
|
||||
object CachePolicy {
|
||||
def saving[E,T](remote: => Task[E \/ T])
|
||||
(save: T => Task[Unit]): Task[E \/ T] = {
|
||||
for {
|
||||
res <- remote
|
||||
_ <- res.fold(_ => Task.now(()), t => save(t))
|
||||
} yield res
|
||||
}
|
||||
|
||||
case object Default extends CachePolicy {
|
||||
def apply[E,T](local: => Task[E \/ T])
|
||||
(remote: => Task[E \/ T]): Task[E \/ T] =
|
||||
local
|
||||
.flatMap(res => if (res.isLeft) remote else Task.now(res))
|
||||
}
|
||||
case object LocalOnly extends CachePolicy {
|
||||
def apply[E,T](local: => Task[E \/ T])
|
||||
(remote: => Task[E \/ T]): Task[E \/ T] =
|
||||
local
|
||||
}
|
||||
case object ForceDownload extends CachePolicy {
|
||||
def apply[E,T](local: => Task[E \/ T])
|
||||
(remote: => Task[E \/ T]): Task[E \/ T] =
|
||||
remote
|
||||
}
|
||||
EitherT(task.map(_.leftMap(_.reverse)))
|
||||
.map {case x @ (_, proj) =>
|
||||
assert(proj.module == module)
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
underlying.copy(checksumUrls = underlying.checksumUrls ++ Seq(
|
||||
"md5" -> (underlying.url + ".md5"),
|
||||
"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")
|
||||
"sig" ->
|
||||
Artifact(underlying.url + ".asc", Map.empty, Map.empty, Attributes("asc", ""))
|
||||
.withDefaultChecksums
|
||||
))
|
||||
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.jar.asc")
|
||||
"sources" -> Artifact(base + "-sources.jar", Map.empty, Map.empty, Attributes("jar", "src")) // Are these the right attributes?
|
||||
.withDefaultChecksums
|
||||
.withDefaultSignature,
|
||||
"javadoc" -> Artifact(base + "-javadoc.jar", Map.empty, Map.empty, Attributes("jar", "javadoc")) // Same comment as above
|
||||
.withDefaultChecksums
|
||||
.withDefaultSignature
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MavenRepository {
|
||||
case class MavenSource(root: String, ivyLike: Boolean) extends Artifact.Source {
|
||||
import Repository._
|
||||
|
||||
def ivyLikePath(org: String,
|
||||
name: String,
|
||||
version: String,
|
||||
subDir: String,
|
||||
baseSuffix: String,
|
||||
ext: String) =
|
||||
def artifacts(
|
||||
dependency: Dependency,
|
||||
project: Project
|
||||
): Seq[Artifact] = {
|
||||
|
||||
def ivyLikePath0(subDir: String, baseSuffix: String, ext: String) =
|
||||
BaseMavenRepository.ivyLikePath(
|
||||
dependency.module.organization,
|
||||
dependency.module.name,
|
||||
project.version,
|
||||
subDir,
|
||||
baseSuffix,
|
||||
ext
|
||||
)
|
||||
|
||||
val path =
|
||||
if (ivyLike)
|
||||
ivyLikePath0(dependency.attributes.`type` + "s", "", dependency.attributes.`type`)
|
||||
else
|
||||
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(
|
||||
root + path.mkString("/"),
|
||||
Map.empty,
|
||||
Map.empty,
|
||||
dependency.attributes
|
||||
)
|
||||
.withDefaultChecksums
|
||||
|
||||
if (dependency.attributes.`type` == "jar") {
|
||||
artifact = artifact.withDefaultSignature
|
||||
|
||||
artifact =
|
||||
if (ivyLike) {
|
||||
val srcPath = root + ivyLikePath0("srcs", "-sources", "jar").mkString("/")
|
||||
val javadocPath = root + ivyLikePath0("docs", "-javadoc", "jar").mkString("/")
|
||||
|
||||
artifact
|
||||
.copy(
|
||||
extra = artifact.extra ++ Map(
|
||||
"sources" -> Artifact(srcPath, Map.empty, Map.empty, Attributes("jar", "src")) // Are these the right attributes?
|
||||
.withDefaultChecksums
|
||||
.withDefaultSignature,
|
||||
"javadoc" -> Artifact(javadocPath, Map.empty, Map.empty, Attributes("jar", "javadoc")) // Same comment as above
|
||||
.withDefaultChecksums
|
||||
.withDefaultSignature
|
||||
))
|
||||
} else
|
||||
artifact
|
||||
.withJavadocSources
|
||||
}
|
||||
|
||||
Seq(artifact)
|
||||
}
|
||||
}
|
||||
|
||||
object BaseMavenRepository {
|
||||
|
||||
def ivyLikePath(
|
||||
org: String,
|
||||
name: String,
|
||||
version: String,
|
||||
subDir: String,
|
||||
baseSuffix: String,
|
||||
ext: String
|
||||
) =
|
||||
Seq(
|
||||
org,
|
||||
name,
|
||||
|
|
@ -147,74 +181,25 @@ object MavenRepository {
|
|||
s"$name$baseSuffix.$ext"
|
||||
)
|
||||
|
||||
case class Source(root: String, ivyLike: Boolean) extends Artifact.Source {
|
||||
import Repository._
|
||||
|
||||
def artifacts(dependency: Dependency,
|
||||
project: Project): Seq[Artifact] = {
|
||||
|
||||
def ivyLikePath0(subDir: String, baseSuffix: String, ext: String) =
|
||||
MavenRepository.ivyLikePath(dependency.module.organization, dependency.module.name, project.version, subDir, baseSuffix, ext)
|
||||
|
||||
val path =
|
||||
if (ivyLike)
|
||||
ivyLikePath0(dependency.attributes.`type` + "s", "", dependency.attributes.`type`)
|
||||
else
|
||||
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(
|
||||
root + path.mkString("/"),
|
||||
Map.empty,
|
||||
dependency.attributes
|
||||
)
|
||||
.withDefaultChecksums
|
||||
|
||||
if (dependency.attributes.`type` == "jar") {
|
||||
artifact = artifact.withDefaultSignature
|
||||
|
||||
artifact =
|
||||
if (ivyLike) {
|
||||
val srcPath = root + ivyLikePath0("srcs", "-sources", "jar").mkString("/")
|
||||
val javadocPath = root + ivyLikePath0("docs", "-javadoc", "jar").mkString("/")
|
||||
|
||||
artifact
|
||||
.copy(extra = artifact.extra ++ Map(
|
||||
Artifact.sourcesMd5 -> (srcPath + ".md5"),
|
||||
Artifact.sourcesSha1 -> (srcPath + ".sha1"),
|
||||
Artifact.sources -> srcPath,
|
||||
Artifact.sourcesSigMd5 -> (srcPath + ".asc.md5"),
|
||||
Artifact.sourcesSigSha1 -> (srcPath + ".asc.sha1"),
|
||||
Artifact.sourcesSig -> (srcPath + ".asc"),
|
||||
Artifact.javadocMd5 -> (javadocPath + ".md5"),
|
||||
Artifact.javadocSha1 -> (javadocPath + ".sha1"),
|
||||
Artifact.javadoc -> javadocPath,
|
||||
Artifact.javadocSigMd5 -> (javadocPath + ".asc.md5"),
|
||||
Artifact.javadocSigSha1 -> (javadocPath + ".asc.sha1"),
|
||||
Artifact.javadocSig -> (javadocPath + ".asc")
|
||||
))
|
||||
} else artifact.withJavadocSources
|
||||
}
|
||||
|
||||
Seq(artifact)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case class MavenRepository(fetch: Fetch,
|
||||
ivyLike: Boolean = false) extends Repository {
|
||||
abstract class BaseMavenRepository(
|
||||
root: String,
|
||||
ivyLike: Boolean
|
||||
) extends Repository {
|
||||
|
||||
def fetch(
|
||||
artifact: Artifact,
|
||||
cachePolicy: CachePolicy
|
||||
): EitherT[Task, String, String]
|
||||
|
||||
import Repository._
|
||||
import MavenRepository._
|
||||
import BaseMavenRepository._
|
||||
|
||||
val source = MavenRepository.Source(fetch.root, ivyLike)
|
||||
val source = MavenSource(root, ivyLike)
|
||||
|
||||
def projectArtifact(module: Module, version: String): Artifact = {
|
||||
|
||||
val path = (
|
||||
if (ivyLike)
|
||||
ivyLikePath(module.organization, module.name, version, "poms", "", "pom")
|
||||
|
|
@ -228,10 +213,8 @@ case class MavenRepository(fetch: Fetch,
|
|||
|
||||
Artifact(
|
||||
path.mkString("/"),
|
||||
Map(
|
||||
Artifact.md5 -> "",
|
||||
Artifact.sha1 -> ""
|
||||
),
|
||||
Map.empty,
|
||||
Map.empty,
|
||||
Attributes("pom", "")
|
||||
)
|
||||
.withDefaultSignature
|
||||
|
|
@ -251,6 +234,7 @@ case class MavenRepository(fetch: Fetch,
|
|||
Artifact(
|
||||
path.mkString("/"),
|
||||
Map.empty,
|
||||
Map.empty,
|
||||
Attributes("pom", "")
|
||||
)
|
||||
.withDefaultChecksums
|
||||
|
|
@ -258,8 +242,10 @@ case class MavenRepository(fetch: Fetch,
|
|||
Some(artifact)
|
||||
}
|
||||
|
||||
def versions(module: Module,
|
||||
cachePolicy: CachePolicy = CachePolicy.Default): EitherT[Task, String, Versions] = {
|
||||
def versions(
|
||||
module: Module,
|
||||
cachePolicy: CachePolicy = CachePolicy.Default
|
||||
): EitherT[Task, String, Versions] = {
|
||||
|
||||
EitherT(
|
||||
versionsArtifact(module) match {
|
||||
|
|
@ -279,9 +265,11 @@ case class MavenRepository(fetch: Fetch,
|
|||
)
|
||||
}
|
||||
|
||||
def findNoInterval(module: Module,
|
||||
version: String,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, Project] = {
|
||||
def findNoInterval(
|
||||
module: Module,
|
||||
version: String,
|
||||
cachePolicy: CachePolicy
|
||||
): EitherT[Task, String, Project] = {
|
||||
|
||||
EitherT {
|
||||
fetch(projectArtifact(module, version), cachePolicy)
|
||||
|
|
@ -297,38 +285,80 @@ case class MavenRepository(fetch: Fetch,
|
|||
}
|
||||
}
|
||||
|
||||
def find(module: Module,
|
||||
version: String,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, (Artifact.Source, Project)] = {
|
||||
def find(
|
||||
module: Module,
|
||||
version: String
|
||||
)(implicit
|
||||
cachePolicy: CachePolicy
|
||||
): EitherT[Task, String, (Artifact.Source, Project)] = {
|
||||
|
||||
Parse.versionInterval(version).filter(_.isValid) match {
|
||||
case None => findNoInterval(module, version, cachePolicy).map((source, _))
|
||||
case Some(itv) =>
|
||||
versions(module, cachePolicy)
|
||||
.flatMap { versions0 =>
|
||||
val eitherVersion = {
|
||||
val release = Version(versions0.release)
|
||||
Parse.versionInterval(version)
|
||||
.filter(_.isValid) match {
|
||||
case None =>
|
||||
findNoInterval(module, version, cachePolicy).map((source, _))
|
||||
case Some(itv) =>
|
||||
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 (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)
|
||||
if (inInterval.isEmpty) -\/(s"No version found for $version")
|
||||
else \/-(inInterval.max.repr)
|
||||
}
|
||||
}
|
||||
|
||||
eitherVersion match {
|
||||
case -\/(reason) => EitherT[Task, String, (Artifact.Source, Project)](Task.now(-\/(reason)))
|
||||
case \/-(version0) =>
|
||||
findNoInterval(module, version0, cachePolicy)
|
||||
.map(_.copy(versions = Some(versions0)))
|
||||
.map((source, _))
|
||||
}
|
||||
}
|
||||
|
||||
eitherVersion match {
|
||||
case -\/(reason) => EitherT[Task, String, (Artifact.Source, Project)](Task.now(-\/(reason)))
|
||||
case \/-(version0) =>
|
||||
findNoInterval(module, version0, cachePolicy)
|
||||
.map(_.copy(versions = Some(versions0)))
|
||||
.map((source, _))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait CachePolicy {
|
||||
def apply[E,T](local: => Task[E \/ T])
|
||||
(remote: => Task[E \/ T]): Task[E \/ T]
|
||||
|
||||
def saving[E,T](local: => Task[E \/ T])
|
||||
(remote: => Task[E \/ T])
|
||||
(save: => T => Task[Unit]): Task[E \/ T] =
|
||||
apply(local)(CachePolicy.saving(remote)(save))
|
||||
}
|
||||
|
||||
object CachePolicy {
|
||||
def saving[E,T](remote: => Task[E \/ T])
|
||||
(save: T => Task[Unit]): Task[E \/ T] = {
|
||||
for {
|
||||
res <- remote
|
||||
_ <- res.fold(_ => Task.now(()), t => save(t))
|
||||
} yield res
|
||||
}
|
||||
|
||||
case object Default extends CachePolicy {
|
||||
def apply[E,T](local: => Task[E \/ T])
|
||||
(remote: => Task[E \/ T]): Task[E \/ T] =
|
||||
local
|
||||
.flatMap(res => if (res.isLeft) remote else Task.now(res))
|
||||
}
|
||||
case object LocalOnly extends CachePolicy {
|
||||
def apply[E,T](local: => Task[E \/ T])
|
||||
(remote: => Task[E \/ T]): Task[E \/ T] =
|
||||
local
|
||||
}
|
||||
case object ForceDownload extends CachePolicy {
|
||||
def apply[E,T](local: => Task[E \/ T])
|
||||
(remote: => Task[E \/ T]): Task[E \/ T] =
|
||||
remote
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ package object coursier {
|
|||
type Scope = core.Scope
|
||||
val Scope: core.Scope.type = core.Scope
|
||||
|
||||
type CachePolicy = core.CachePolicy
|
||||
val CachePolicy: core.CachePolicy.type = core.CachePolicy
|
||||
|
||||
type Repository = core.Repository
|
||||
val Repository: core.Repository.type = core.Repository
|
||||
|
||||
|
|
@ -91,6 +94,8 @@ package object coursier {
|
|||
|
||||
implicit def fetch(
|
||||
repositories: Seq[core.Repository]
|
||||
)(implicit
|
||||
cachePolicy: CachePolicy
|
||||
): ResolutionProcess.Fetch[Task] = {
|
||||
|
||||
modVers =>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ object CentralTests extends TestSuite {
|
|||
Repository.mavenCentral
|
||||
)
|
||||
|
||||
implicit val cachePolicy = CachePolicy.Default
|
||||
|
||||
def resolve(deps: Set[Dependency], filter: Option[Dependency => Boolean] = None, extraRepo: Option[Repository] = None) = {
|
||||
val repositories0 = extraRepo.toSeq ++ repositories
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import coursier.test.compatibility._
|
|||
|
||||
object ResolutionTests extends TestSuite {
|
||||
|
||||
implicit val cachePolicy = CachePolicy.Default
|
||||
|
||||
def resolve0(deps: Set[Dependency], filter: Option[Dependency => Boolean] = None) = {
|
||||
Resolution(deps, filter = filter)
|
||||
.process
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class TestRepository(projects: Map[(Module, String), Project]) extends Repositor
|
|||
val source = new core.Artifact.Source {
|
||||
def artifacts(dependency: Dependency, project: Project) = ???
|
||||
}
|
||||
def find(module: Module, version: String, cachePolicy: Repository.CachePolicy) =
|
||||
def find(module: Module, version: String)(implicit cachePolicy: CachePolicy) =
|
||||
EitherT(Task.now(
|
||||
projects.get((module, version)).map((source, _)).toRightDisjunction("Not found")
|
||||
))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
package coursier
|
||||
|
||||
import java.io.{PrintWriter, File}
|
||||
|
||||
import coursier.core.MavenRepository
|
||||
|
||||
import scala.io.Source
|
||||
|
||||
object Cache {
|
||||
|
||||
def mavenRepository(lines: Seq[String]): Option[MavenRepository] = {
|
||||
def isMaven =
|
||||
lines
|
||||
.find(_.startsWith("maven:"))
|
||||
.map(_.stripPrefix("maven:").trim)
|
||||
.toSeq
|
||||
.contains("true")
|
||||
|
||||
def ivyLike =
|
||||
lines
|
||||
.find(_.startsWith("ivy-like:"))
|
||||
.map(_.stripPrefix("ivy-like:").trim)
|
||||
.toSeq
|
||||
.contains("true")
|
||||
|
||||
def base =
|
||||
lines
|
||||
.find(_.startsWith("base:"))
|
||||
.map(_.stripPrefix("base:").trim)
|
||||
.filter(_.nonEmpty)
|
||||
|
||||
if (isMaven)
|
||||
base.map(MavenRepository(_, ivyLike = ivyLike))
|
||||
else
|
||||
None
|
||||
}
|
||||
|
||||
lazy val default = Cache(new File(sys.props("user.home") + "/.coursier/cache"))
|
||||
|
||||
}
|
||||
|
||||
case class Cache(cache: File) {
|
||||
|
||||
import Cache._
|
||||
|
||||
lazy val repoDir = new File(cache, "repositories")
|
||||
lazy val metadataBase = new File(cache, "metadata")
|
||||
lazy val fileBase = new File(cache, "files")
|
||||
|
||||
lazy val defaultFile = new File(repoDir, ".default")
|
||||
|
||||
def add(id: String, base: String, ivyLike: Boolean): Unit = {
|
||||
repoDir.mkdirs()
|
||||
val f = new File(repoDir, id)
|
||||
val w = new PrintWriter(f)
|
||||
try w.println((Seq("maven: true", s"base: $base") ++ (if (ivyLike) Seq("ivy-like: true") else Nil)).mkString("\n"))
|
||||
finally w.close()
|
||||
}
|
||||
|
||||
def addCentral(): Unit =
|
||||
add("central", "https://repo1.maven.org/maven2/", ivyLike = false)
|
||||
|
||||
def addIvy2Local(): Unit =
|
||||
add("ivy2local", "file://" + sys.props("user.home") + "/.ivy2/local/", ivyLike = true)
|
||||
|
||||
def init(ifEmpty: Boolean = true): Unit =
|
||||
if (!ifEmpty || !cache.exists()) {
|
||||
repoDir.mkdirs()
|
||||
metadataBase.mkdirs()
|
||||
fileBase.mkdirs()
|
||||
addCentral()
|
||||
addIvy2Local()
|
||||
setDefault("ivy2local", "central")
|
||||
}
|
||||
|
||||
def setDefault(ids: String*): Unit = {
|
||||
defaultFile.getParentFile.mkdirs()
|
||||
val w = new PrintWriter(defaultFile)
|
||||
try w.println(ids.mkString("\n"))
|
||||
finally w.close()
|
||||
}
|
||||
|
||||
def list(): Seq[(String, MavenRepository, (String, File))] =
|
||||
Option(repoDir.listFiles())
|
||||
.map(_.toSeq)
|
||||
.getOrElse(Nil)
|
||||
.filter(f => f.isFile && !f.getName.startsWith("."))
|
||||
.flatMap { f =>
|
||||
val name = f.getName
|
||||
val lines = Source.fromFile(f).getLines().toList
|
||||
mavenRepository(lines)
|
||||
.map(repo =>
|
||||
(name, repo.copy(cache = Some(new File(metadataBase, name))), (repo.root, new File(fileBase, name)))
|
||||
)
|
||||
}
|
||||
|
||||
def map(): Map[String, (MavenRepository, (String, File))] =
|
||||
list()
|
||||
.map{case (id, repo, fileCache) => id -> (repo, fileCache) }
|
||||
.toMap
|
||||
|
||||
|
||||
def repositories(): Seq[MavenRepository] =
|
||||
list().map(_._2)
|
||||
|
||||
def repositoryMap(): Map[String, MavenRepository] =
|
||||
list()
|
||||
.map{case (id, repo, _) => id -> repo}
|
||||
.toMap
|
||||
|
||||
def fileCaches(): Seq[(String, File)] =
|
||||
list().map(_._3)
|
||||
|
||||
def default(withNotFound: Boolean = false): Seq[String] =
|
||||
if (defaultFile.exists()) {
|
||||
val default0 =
|
||||
Source.fromFile(defaultFile)
|
||||
.getLines()
|
||||
.map(_.trim)
|
||||
.filter(_.nonEmpty)
|
||||
.toList
|
||||
|
||||
val found = list()
|
||||
.map(_._1)
|
||||
.toSet
|
||||
|
||||
default0
|
||||
.filter(found)
|
||||
} else
|
||||
Nil
|
||||
|
||||
def files(): Files = {
|
||||
val map0 = map()
|
||||
val default0 = default()
|
||||
|
||||
new Files(default0.map(map0(_)._2), () => ???)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,28 +1,30 @@
|
|||
package coursier
|
||||
|
||||
import java.net.{URI, URL}
|
||||
|
||||
import coursier.core.Repository.CachePolicy
|
||||
import java.net.{ URI, URL }
|
||||
import java.util.concurrent.{ Executors, ExecutorService }
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scalaz.{-\/, \/-, \/, EitherT}
|
||||
import scalaz.concurrent.Task
|
||||
import scalaz.{ -\/, \/-, \/, EitherT }
|
||||
import scalaz.concurrent.{ Task, Strategy }
|
||||
|
||||
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[Files.Logger] = None,
|
||||
concurrentDownloadCount: Int = Files.defaultConcurrentDownloadCount
|
||||
) {
|
||||
|
||||
case class Files(cache: Seq[(String, File)],
|
||||
tmp: () => File,
|
||||
logger: Option[FilesLogger] = None) {
|
||||
lazy val defaultPool =
|
||||
Executors.newFixedThreadPool(concurrentDownloadCount, Strategy.DefaultDaemonThreadFactory)
|
||||
|
||||
def file(artifact: Artifact,
|
||||
cachePolicy: CachePolicy): EitherT[Task, String, File] = {
|
||||
def file(
|
||||
artifact: Artifact,
|
||||
cachePolicy: CachePolicy
|
||||
)(implicit
|
||||
pool: ExecutorService = defaultPool
|
||||
): EitherT[Task, String, File] = {
|
||||
|
||||
if (artifact.url.startsWith("file:///")) {
|
||||
val f = new File(new URI(artifact.url) .getPath)
|
||||
|
|
@ -99,6 +101,15 @@ case class Files(cache: Seq[(String, File)],
|
|||
}
|
||||
|
||||
object Files {
|
||||
|
||||
val defaultConcurrentDownloadCount = 6
|
||||
|
||||
// FIXME This kind of side-effecting API is lame, we should aim at a more functional one.
|
||||
trait Logger {
|
||||
def foundLocally(f: File): Unit
|
||||
def downloadingArtifact(url: String): Unit
|
||||
def downloadedArtifact(url: String, success: Boolean): Unit
|
||||
}
|
||||
|
||||
var bufferSize = 1024*1024
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ object CoursierBuild extends Build {
|
|||
organization := "com.github.alexarchambault",
|
||||
scalaVersion := "2.11.6",
|
||||
crossScalaVersions := Seq("2.10.5", "2.11.6"),
|
||||
resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases"
|
||||
resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases",
|
||||
resolvers += Resolver.sonatypeRepo("snapshots")
|
||||
) ++ publishingSettings
|
||||
|
||||
private lazy val commonCoreSettings = commonSettings ++ Seq[Setting[_]](
|
||||
|
|
@ -123,7 +124,7 @@ object CoursierBuild extends Build {
|
|||
.settings(
|
||||
name := "coursier-cli",
|
||||
libraryDependencies ++= Seq(
|
||||
"com.github.alexarchambault" %% "case-app" % "0.2.2",
|
||||
"com.github.alexarchambault" %% "case-app" % "0.3.0-SNAPSHOT",
|
||||
"ch.qos.logback" % "logback-classic" % "1.1.3"
|
||||
) ++ {
|
||||
if (scalaVersion.value startsWith "2.10.")
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package coursier
|
||||
package web
|
||||
|
||||
import coursier.core.{Repository, MavenRepository, Fetch}
|
||||
import japgolly.scalajs.react.vdom.{TagMod, Attr}
|
||||
import coursier.core.{ Repository, MavenRepository, MavenSource }
|
||||
import japgolly.scalajs.react.vdom.{ TagMod, Attr }
|
||||
import japgolly.scalajs.react.vdom.Attrs.dangerouslySetInnerHtml
|
||||
import japgolly.scalajs.react.{ReactEventI, ReactComponentB, BackendScope}
|
||||
import japgolly.scalajs.react.{ ReactEventI, ReactComponentB, BackendScope }
|
||||
import japgolly.scalajs.react.vdom.prefix_<^._
|
||||
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
|
||||
import org.scalajs.jquery.jQuery
|
||||
|
|
@ -12,7 +12,7 @@ import org.scalajs.jquery.jQuery
|
|||
import scala.concurrent.Future
|
||||
|
||||
import scala.scalajs.js
|
||||
import js.Dynamic.{global => g}
|
||||
import js.Dynamic.{ global => g }
|
||||
|
||||
case class ResolutionOptions(followOptional: Boolean = false,
|
||||
keepTest: Boolean = false)
|
||||
|
|
@ -111,7 +111,7 @@ class Backend($: BackendScope[Unit, State]) {
|
|||
g.$("#resLogTab a:last").tab("show")
|
||||
$.modState(_.copy(resolving = true, log = Nil))
|
||||
|
||||
val logger: Fetch.Logger = new Fetch.Logger {
|
||||
val logger: MavenRepository.Logger = new MavenRepository.Logger {
|
||||
def fetched(url: String) = {
|
||||
println(s"<- $url")
|
||||
$.modState(s => s.copy(log = s"<- $url" +: s.log))
|
||||
|
|
@ -133,9 +133,11 @@ class Backend($: BackendScope[Unit, State]) {
|
|||
filter = Some(dep => (s.options.followOptional || !dep.optional) && (s.options.keepTest || dep.scope != Scope.Test))
|
||||
)
|
||||
|
||||
implicit val cachePolicy = CachePolicy.Default
|
||||
|
||||
res
|
||||
.process
|
||||
.run(fetch(s.repositories.map(r => r.copy(fetch = r.fetch.copy(logger = Some(logger))))), 100)
|
||||
.run(s.repositories.map(r => r.copy(logger = Some(logger))), 100)
|
||||
}
|
||||
|
||||
// For reasons that are unclear to me, not delaying this when using the runNow execution context
|
||||
|
|
@ -246,7 +248,7 @@ object App {
|
|||
)),
|
||||
<.td(Seq[Seq[TagMod]](
|
||||
res.projectCache.get(dep.moduleVersion) match {
|
||||
case Some((source: MavenRepository.Source, proj)) if !source.ivyLike =>
|
||||
case Some((source: MavenSource, proj)) if !source.ivyLike =>
|
||||
// FIXME Maven specific, generalize with source.artifacts
|
||||
val version0 = finalVersionOpt getOrElse dep.version
|
||||
val relPath =
|
||||
|
|
@ -401,14 +403,14 @@ object App {
|
|||
def repoItem(repo: MavenRepository) =
|
||||
<.tr(
|
||||
<.td(
|
||||
<.a(^.href := repo.fetch.root,
|
||||
repo.fetch.root
|
||||
<.a(^.href := repo.root,
|
||||
repo.root
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val sortedRepos = repos
|
||||
.sortBy(repo => repo.fetch.root)
|
||||
.sortBy(repo => repo.root)
|
||||
|
||||
<.table(^.`class` := "table",
|
||||
<.thead(
|
||||
|
|
|
|||
Loading…
Reference in New Issue