Merge pull request #243 from alexarchambault/topic/develop

Latest developments
This commit is contained in:
Alexandre Archambault 2016-05-10 14:14:51 +02:00
commit 95d6d9e14e
48 changed files with 1457 additions and 277 deletions

View File

@ -28,7 +28,16 @@ function isMasterOrDevelop() {
}
# Required for ~/.ivy2/local repo tests
~/sbt coreJVM/publish-local
~/sbt coreJVM/publishLocal simple-web-server/publishLocal
# Required for HTTP authentication tests
./coursier launch \
io.get-coursier:simple-web-server_2.11:1.0.0-SNAPSHOT \
-r http://dl.bintray.com/scalaz/releases \
-- \
-d tests/jvm/src/test/resources/test-repo/http/abc.com \
-u user -P pass -r realm \
-v &
# TODO Add coverage once https://github.com/scoverage/sbt-scoverage/issues/111 is fixed

View File

@ -6,7 +6,7 @@ install:
os:
- osx
script:
- project/travis.sh "${TRAVIS_SCALA_VERSION:-2.11.8}" "$TRAVIS_PULL_REQUEST" "$TRAVIS_BRANCH" "$PUBLISH"
- .ci/travis.sh "${TRAVIS_SCALA_VERSION:-2.11.8}" "$TRAVIS_PULL_REQUEST" "$TRAVIS_BRANCH" "$PUBLISH"
# Uncomment once https://github.com/scoverage/sbt-scoverage/issues/111 is fixed
# after_success:
# - bash <(curl -s https://codecov.io/bash)

View File

@ -15,10 +15,11 @@ install:
- cmd: SET SBT_OPTS=-XX:MaxPermSize=2g -Xmx4g
- cmd: SET COURSIER_NO_TERM=1
build_script:
- sbt ++2.11.8 clean compile coreJVM/publishLocal
- sbt ++2.11.8 clean compile coreJVM/publishLocal simple-web-server/publishLocal
- sbt ++2.10.6 clean compile
- sbt ++2.10.6 coreJVM/publishLocal cache/publishLocal # to make the scripted tests happy
test_script:
- ps: Start-Job { & java -jar -noverify C:\projects\coursier\coursier launch -r http://dl.bintray.com/scalaz/releases io.get-coursier:simple-web-server_2.11:1.0.0-SNAPSHOT -- -d /C:/projects/coursier/tests/jvm/src/test/resources/test-repo/http/abc.com -u user -P pass -r realm -v }
- sbt ++2.11.8 testsJVM/test # Would node be around for testsJS/test?
- sbt ++2.10.6 testsJVM/test plugin/scripted
cache:

View File

@ -146,6 +146,21 @@ lazy val core = crossProject
import com.typesafe.tools.mima.core.ProblemFilters._
Seq(
// Since 1.0.0-M12
// Extra `authentication` field
ProblemFilters.exclude[MissingMethodProblem]("coursier.core.Artifact.apply"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.core.Artifact.copy"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.core.Artifact.this"),
ProblemFilters.exclude[MissingTypesProblem]("coursier.ivy.IvyRepository$"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.ivy.IvyRepository.apply"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.ivy.IvyRepository.copy"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.ivy.IvyRepository.this"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.maven.MavenRepository.copy"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.maven.MavenRepository.this"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.maven.MavenSource.apply"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.maven.MavenRepository.apply"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.maven.MavenSource.copy"),
ProblemFilters.exclude[MissingMethodProblem]("coursier.maven.MavenSource.this"),
// Since 1.0.0-M11
// Extra parameter with default value added, problem for forward compatibility only
ProblemFilters.exclude[MissingMethodProblem]("coursier.core.ResolutionProcess.next"),
@ -226,6 +241,9 @@ lazy val cache = project
import com.typesafe.tools.mima.core.ProblemFilters._
Seq(
// Since 1.0.0-M12
// Remove deprecated / unused helper method
ProblemFilters.exclude[MissingMethodProblem]("coursier.Cache.readFully"),
// Since 1.0.0-M11
// Add constructor parameter on FileError - shouldn't be built by users anyway
ProblemFilters.exclude[MissingMethodProblem]("coursier.FileError.this"),
@ -488,13 +506,15 @@ lazy val plugin = project
scriptedBufferLog := false
)
val http4sVersion = "0.8.6"
lazy val `simple-web-server` = project
.settings(commonSettings)
.settings(packAutoSettings)
.settings(
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-blaze-server" % "0.13.2",
"org.http4s" %% "http4s-dsl" % "0.13.2",
"org.http4s" %% "http4s-blazeserver" % http4sVersion,
"org.http4s" %% "http4s-dsl" % http4sVersion,
"org.slf4j" % "slf4j-nop" % "1.7.19",
"com.github.alexarchambault" %% "case-app" % "1.0.0-RC2"
)

View File

@ -8,7 +8,9 @@ import java.security.MessageDigest
import java.util.concurrent.{ ConcurrentHashMap, Executors, ExecutorService }
import java.util.regex.Pattern
import coursier.core.Authentication
import coursier.ivy.IvyRepository
import coursier.util.Base64.Encoder
import scala.annotation.tailrec
@ -18,6 +20,10 @@ import scalaz.concurrent.{ Task, Strategy }
import java.io.{ Serializable => _, _ }
trait AuthenticatedURLConnection extends URLConnection {
def authenticate(authentication: Authentication): Unit
}
object Cache {
// Check SHA-1 if available, else be fine with no checksum
@ -43,7 +49,7 @@ object Cache {
}
}
private def localFile(url: String, cache: File): File = {
private def localFile(url: String, cache: File, user: Option[String]): File = {
val path =
if (url.startsWith("file:///"))
url.stripPrefix("file://")
@ -62,7 +68,10 @@ object Cache {
else
throw new Exception(s"URL $url doesn't contain an absolute path")
new File(cache, escape(protocol + "/" + remaining0)) .toString
new File(
cache,
escape(protocol + "/" + user.fold("")(_ + "@") + remaining0.dropWhile(_ == '/'))
).toString
case _ =>
throw new Exception(s"No protocol found in URL $url")
@ -204,7 +213,7 @@ object Cache {
-\/(FileError.ConcurrentDownload(url))
}
catch { case e: Exception =>
-\/(FileError.DownloadError(s"Caught $e (${e.getMessage})"))
-\/(FileError.DownloadError(s"Caught $e${Option(e.getMessage).fold("")(" (" + _ + ")")}"))
}
private def temporaryFile(file: File): File = {
@ -232,7 +241,7 @@ object Cache {
def printError(e: Exception): Unit =
scala.Console.err.println(
s"Cannot instantiate $clsName: $e${Option(e.getMessage).map(" ("+_+")")}"
s"Cannot instantiate $clsName: $e${Option(e.getMessage).fold("")(" ("+_+")")}"
)
val handlerOpt = clsOpt.flatMap {
@ -259,6 +268,17 @@ object Cache {
}
}
private val BasicRealm = (
"^" +
Pattern.quote("Basic realm=\"") +
"([^" + Pattern.quote("\"") + "]*)" +
Pattern.quote("\"") +
"$"
).r
private def basicAuthenticationEncode(user: String, password: String): String =
(user + ":" + password).getBytes("UTF-8").toBase64
/**
* Returns a `java.net.URL` for `s`, possibly using the custom protocol handlers found under the
* `coursier.cache.protocol` namespace.
@ -289,7 +309,7 @@ object Cache {
val referenceFileOpt = artifact
.extra
.get("metadata")
.map(a => localFile(a.url, cache))
.map(a => localFile(a.url, cache, a.authentication.map(_.user)))
def referenceFileExists: Boolean = referenceFileOpt.exists(_.exists())
@ -300,6 +320,20 @@ object Cache {
// (Maven 2 compatibility? - happens for snapshot versioning metadata,
// this is SO FSCKING CRAZY)
conn.setRequestProperty("User-Agent", "")
for (auth <- artifact.authentication)
conn match {
case authenticated: AuthenticatedURLConnection =>
authenticated.authenticate(auth)
case conn0: HttpURLConnection =>
conn0.setRequestProperty(
"Authorization",
"Basic " + basicAuthenticationEncode(auth.user, auth.password)
)
case _ =>
// FIXME Authentication is ignored
}
conn
}
@ -384,15 +418,28 @@ object Cache {
}
}
def is404(conn: URLConnection) =
def responseCode(conn: URLConnection): Option[Int] =
conn match {
case conn0: HttpURLConnection =>
conn0.getResponseCode == 404
Some(conn0.getResponseCode)
case _ =>
false
None
}
def remote(file: File, url: String): EitherT[Task, FileError, Unit] =
def realm(conn: URLConnection): Option[String] =
conn match {
case conn0: HttpURLConnection =>
Option(conn0.getHeaderField("WWW-Authenticate")).collect {
case BasicRealm(realm) => realm
}
case _ =>
None
}
def remote(
file: File,
url: String
): EitherT[Task, FileError, Unit] =
EitherT {
Task {
withLockFor(cache, file) {
@ -421,8 +468,10 @@ object Cache {
case _ => (false, conn0)
}
if (is404(conn))
if (responseCode(conn) == Some(404))
FileError.NotFound(url, permanent = Some(true)).left
else if (responseCode(conn) == Some(401))
FileError.Unauthorized(url, realm = realm(conn)).left
else {
for (len0 <- Option(conn.getContentLengthLong) if len0 >= 0L) {
val len = len0 + (if (partialDownload) alreadyDownloaded else 0L)
@ -534,7 +583,7 @@ object Cache {
val tasks =
for (url <- urls) yield {
val file = localFile(url, cache)
val file = localFile(url, cache, artifact.authentication.map(_.user))
val res =
if (url.startsWith("file:/")) {
@ -618,12 +667,12 @@ object Cache {
implicit val pool0 = pool
val localFile0 = localFile(artifact.url, cache)
val localFile0 = localFile(artifact.url, cache, artifact.authentication.map(_.user))
EitherT {
artifact.checksumUrls.get(sumType) match {
case Some(sumUrl) =>
val sumFile = localFile(sumUrl, cache)
val sumFile = localFile(sumUrl, cache, artifact.authentication.map(_.user))
Task {
val sumOpt = parseChecksum(
@ -730,7 +779,7 @@ object Cache {
checksums = checksums,
logger = logger,
pool = pool
).leftMap(_.message).map { f =>
).leftMap(_.describe).map { f =>
// FIXME Catch error here?
new String(NioFiles.readAllBytes(f.toPath), "UTF-8")
}
@ -811,18 +860,6 @@ object Cache {
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)
}
def withContent(is: InputStream, f: (Array[Byte], Int) => Unit): Unit = {
val data = Array.ofDim[Byte](16384)

View File

@ -2,6 +2,7 @@ package coursier
import java.net.MalformedURLException
import coursier.core.Authentication
import coursier.ivy.IvyRepository
import coursier.util.Parse
@ -26,12 +27,48 @@ object CacheParse {
sys.error(s"Unrecognized repository: $r")
}
try {
Cache.url(url)
repo.success
val validatedUrl = try {
Cache.url(url).success
} catch {
case e: MalformedURLException =>
("Error parsing URL " + url + Option(e.getMessage).map(" (" + _ + ")").mkString).failure
("Error parsing URL " + url + Option(e.getMessage).fold("")(" (" + _ + ")")).failure
}
validatedUrl.flatMap { url =>
Option(url.getUserInfo) match {
case None =>
repo.success
case Some(userInfo) =>
userInfo.split(":", 2) match {
case Array(user, password) =>
val baseUrl = new java.net.URL(
url.getProtocol,
url.getHost,
url.getPort,
url.getFile
).toString
val repo0 = repo match {
case m: MavenRepository =>
m.copy(
root = baseUrl,
authentication = Some(Authentication(user, password))
)
case i: IvyRepository =>
i.copy(
pattern = baseUrl,
authentication = Some(Authentication(user, password))
)
case r =>
sys.error(s"Unrecognized repository: $r")
}
repo0.success
case _ =>
s"No password found in user info of URL $url".failure
}
}
}
}

View File

@ -5,7 +5,9 @@ import java.io.File
sealed abstract class FileError(
val `type`: String,
val message: String
) extends Product with Serializable
) extends Product with Serializable {
def describe: String = s"${`type`}: $message"
}
object FileError {
@ -22,6 +24,14 @@ object FileError {
file
)
final case class Unauthorized(
file: String,
realm: Option[String]
) extends FileError(
"unauthorized",
file + realm.fold("")(" (" + _ + ")")
)
final case class ChecksumNotFound(
sumType: String,
file: String

View File

@ -32,10 +32,10 @@ object Platform {
new String(b, "UTF-8")
} .leftMap{
case e: java.io.FileNotFoundException =>
case e: java.io.FileNotFoundException if e.getMessage != null =>
s"Not found: ${e.getMessage}"
case e =>
s"$e: ${e.getMessage}"
s"$e${Option(e.getMessage).fold("")(" (" + _ + ")")}"
}
}

View File

@ -0,0 +1,106 @@
package coursier.util
import scala.collection.mutable.ArrayBuilder
/**
* Base64 encoder
* @author Mark Lister
* This software is distributed under the 2-Clause BSD license. See the
* LICENSE file in the root of the repository.
*
* Copyright (c) 2014 - 2015 Mark Lister
*
* The repo for this Base64 encoder lives at https://github.com/marklister/base64
* Please send your issues, suggestions and pull requests there.
*/
object Base64 {
case class B64Scheme(encodeTable: Array[Char], strictPadding: Boolean = true,
postEncode: String => String = identity,
preDecode: String => String = identity) {
lazy val decodeTable = {
val b: Array[Int] = new Array[Int](256)
for (x <- encodeTable.zipWithIndex) {
b(x._1) = x._2.toInt
}
b
}
}
val base64 = new B64Scheme((('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') ++ Seq('+', '/')).toArray)
val base64Url = new B64Scheme(base64.encodeTable.dropRight(2) ++ Seq('-', '_'), false,
_.replaceAllLiterally("=", "%3D"),
_.replaceAllLiterally("%3D", "="))
implicit class SeqEncoder(s: Seq[Byte]) {
def toBase64(implicit scheme: B64Scheme = base64): String = Encoder(s.toArray).toBase64
}
implicit class Encoder(b: Array[Byte]) {
val r = new StringBuilder((b.length + 3) * 4 / 3)
lazy val pad = (3 - b.length % 3) % 3
def toBase64(implicit scheme: B64Scheme = base64): String = {
def sixBits(x: Byte, y: Byte, z: Byte): Unit = {
val zz = (x & 0xff) << 16 | (y & 0xff) << 8 | (z & 0xff)
r += scheme.encodeTable(zz >> 18)
r += scheme.encodeTable(zz >> 12 & 0x3f)
r += scheme.encodeTable(zz >> 6 & 0x3f)
r += scheme.encodeTable(zz & 0x3f)
}
for (p <- 0 until b.length - 2 by 3) {
sixBits(b(p), b(p + 1), b(p + 2))
}
pad match {
case 0 =>
case 1 => sixBits(b(b.length - 2), b(b.length - 1), 0)
case 2 => sixBits(b(b.length - 1), 0, 0)
}
r.length = (r.length - pad)
r ++= "=" * pad
scheme.postEncode(r.toString())
}
}
implicit class Decoder(s: String) {
def toByteArray(implicit scheme: B64Scheme = base64): Array[Byte] = {
val pre = scheme.preDecode(s)
val cleanS = pre.replaceAll("=+$", "")
val pad = pre.length - cleanS.length
val computedPad = (4 - (cleanS.length % 4)) % 4
val r = new ArrayBuilder.ofByte
def threeBytes(a: Int, b: Int, c: Int, d: Int): Unit = {
val i = a << 18 | b << 12 | c << 6 | d
r += ((i >> 16).toByte)
r += ((i >> 8).toByte)
r += (i.toByte)
}
if (scheme.strictPadding) {
if (pad > 2) throw new java.lang.IllegalArgumentException("Invalid Base64 String: (excessive padding) " + s)
if (s.length % 4 != 0) throw new java.lang.IllegalArgumentException("Invalid Base64 String: (padding problem) " + s)
}
if (computedPad == 3) throw new java.lang.IllegalArgumentException("Invalid Base64 String: (string length) " + s)
try {
val s = (cleanS + "A" * computedPad)
for (x <- 0 until s.length - 1 by 4) {
val i = scheme.decodeTable(s.charAt(x)) << 18 |
scheme.decodeTable(s.charAt(x + 1)) << 12 |
scheme.decodeTable(s.charAt(x + 2)) << 6 |
scheme.decodeTable(s.charAt(x + 3))
r += ((i >> 16).toByte)
r += ((i >> 8).toByte)
r += (i.toByte)
}
} catch {
case e: NoSuchElementException => throw new java.lang.IllegalArgumentException("Invalid Base64 String: (invalid character)" + e.getMessage + s)
}
val res = r.result
res.slice(0, res.length - computedPad)
}
}
}

View File

@ -193,7 +193,7 @@ case class Bootstrap(
try Files.write(output0.toPath, shellPreamble.getBytes("UTF-8") ++ buffer.toByteArray)
catch { case e: IOException =>
Console.err.println(s"Error while writing $output0: ${e.getMessage}")
Console.err.println(s"Error while writing $output0${Option(e.getMessage).fold("")(" (" + _ + ")")}")
sys.exit(1)
}
@ -218,7 +218,10 @@ case class Bootstrap(
case e: UnsupportedOperationException =>
// Ignored
case e: IOException =>
Console.err.println(s"Error while making $output0 executable: ${e.getMessage}")
Console.err.println(
s"Error while making $output0 executable" +
Option(e.getMessage).fold("")(" (" + _ + ")")
)
sys.exit(1)
}

View File

@ -99,6 +99,18 @@ class Helper(
MavenRepository("https://repo1.maven.org/maven2")
)
val sourceDirectories = common.sources.map { path =>
val subDir = "target/repository"
val dir = new File(path)
val repoDir = new File(dir, subDir)
if (!dir.exists())
Console.err.println(s"Warning: sources $path not found")
else if (!repoDir.exists())
Console.err.println(s"Warning: directory $subDir not found under sources path $path")
repoDir
}
val repositoriesValidation = CacheParse.repositories(common.repository).map { repos0 =>
var repos = (if (common.noDefault) Nil else defaultRepositories) ++ repos0
@ -119,10 +131,14 @@ class Helper(
}
val repositories = repositoriesValidation match {
case Success(repos) => repos
case Success(repos) =>
val sourceRepositories = sourceDirectories.map(dir =>
MavenRepository(dir.toURI.toString, changing = Some(true))
)
sourceRepositories ++ repos
case Failure(errors) =>
prematureExit(
s"Error parsing repositories:\n${errors.list.map(" "+_).mkString("\n")}"
s"Error with repositories:\n${errors.list.map(" "+_).mkString("\n")}"
)
}
@ -140,8 +156,36 @@ class Helper(
s"Cannot parse forced versions:\n" + forceVersionErrors.map(" "+_).mkString("\n")
}
val sourceRepositoryForceVersions = sourceDirectories.flatMap { base =>
// FIXME Also done in the plugin module
def pomDirComponents(f: File, components: Vector[String]): Stream[Vector[String]] =
if (f.isDirectory) {
val components0 = components :+ f.getName
Option(f.listFiles()).toStream.flatten.flatMap(pomDirComponents(_, components0))
} else if (f.getName.endsWith(".pom"))
Stream(components)
else
Stream.empty
Option(base.listFiles())
.toVector
.flatten
.flatMap(pomDirComponents(_, Vector()))
// at least 3 for org / name / version - the contrary should not happen, but who knows
.filter(_.length >= 3)
.map { components =>
val org = components.dropRight(2).mkString(".")
val name = components(components.length - 2)
val version = components.last
Module(org, name) -> version
}
}
val forceVersions = {
val grouped = forceVersions0
val grouped = (forceVersions0 ++ sourceRepositoryForceVersions)
.groupBy { case (mod, _) => mod }
.map { case (mod, l) => mod -> l.map { case (_, version) => version } }
@ -354,10 +398,16 @@ class Helper(
lazy val projCache = res.projectCache.mapValues { case (_, p) => p }
if (printResultStdout || verbosityLevel >= 1) {
if ((printResultStdout && verbosityLevel >= 1) || verbosityLevel >= 2)
if (printResultStdout || verbosityLevel >= 1 || tree || reverseTree) {
if ((printResultStdout && verbosityLevel >= 1) || verbosityLevel >= 2 || tree || reverseTree)
errPrintln(s" Result:")
val depsStr = Print.dependenciesUnknownConfigs(trDeps, projCache)
val depsStr =
if (reverseTree || tree)
Print.dependencyTree(dependencies, res, printExclusions = verbosityLevel >= 1, reverse = reverseTree)
else
Print.dependenciesUnknownConfigs(trDeps, projCache)
if (printResultStdout)
println(depsStr)
else

View File

@ -24,9 +24,12 @@ case class CommonOptions(
@Help("Maximum number of resolution iterations (specify a negative value for unlimited, default: 100)")
@Short("N")
maxIterations: Int = 100,
@Help("Repositories - for multiple repositories, separate with comma and/or repeat this option (e.g. -r central,ivy2local -r sonatype-snapshots, or equivalently -r central,ivy2local,sonatype-snapshots)")
@Help("Repository - for multiple repositories, separate with comma and/or add this option multiple times (e.g. -r central,ivy2local -r sonatype-snapshots, or equivalently -r central,ivy2local,sonatype-snapshots)")
@Short("r")
repository: List[String],
@Help("Source repository - for multiple repositories, separate with comma and/or add this option multiple times")
@Short("R")
sources: List[String],
@Help("Do not add default repositories (~/.ivy2/local, and Central)")
noDefault: Boolean = false,
@Help("Modify names in Maven repository paths for SBT plugins")
@ -61,6 +64,12 @@ case class CommonOptions(
@Short("B")
@Value("Number of warm-up resolutions - if negative, doesn't print per iteration benchmark (less overhead)")
benchmark: Int,
@Help("Print dependencies as a tree")
@Short("t")
tree: Boolean,
@Help("Print dependencies as an inversed tree (dependees as children)")
@Short("T")
reverseTree: Boolean,
@Recurse
cacheOptions: CacheOptions
) {

View File

@ -14,7 +14,7 @@ package object compatibility {
def xmlParse(s: String): Either[String, Xml.Node] = {
def parse =
try Right(scala.xml.XML.loadString(s))
catch { case e: Exception => Left(e.getMessage) }
catch { case e: Exception => Left(e.toString + Option(e.getMessage).fold("")(" (" + _ + ")")) }
def fromNode(node: scala.xml.Node): Xml.Node =
new Xml.Node {

View File

@ -0,0 +1,77 @@
package coursier.maven
import coursier.core.{ Dependency, Project }
object WritePom {
def project(proj: Project, packaging: Option[String]) = {
def dependencyNode(config: String, dep: Dependency) = {
<dependency>
<groupId>{dep.module.organization}</groupId>
<artifactId>{dep.module.name}</artifactId>
{
if (dep.version.isEmpty)
Nil
else
Seq(<version>{dep.version}</version>)
}
{
if (config.isEmpty)
Nil
else
Seq(<scope>{config}</scope>)
}
</dependency>
}
<project>
// parent
<groupId>{proj.module.organization}</groupId>
<artifactId>{proj.module.name}</artifactId>
{
packaging
.map(p => <packaging>{p}</packaging>)
.toSeq
}
<description>{proj.info.description}</description>
<url>{proj.info.homePage}</url>
<version>{proj.version}</version>
// licenses
<name>{proj.module.name}</name>
<organization>
<name>{proj.module.name}</name>
<url>{proj.info.homePage}</url>
</organization>
// SCM
// developers
{
if (proj.dependencies.isEmpty)
Nil
else
<dependencies>{
proj.dependencies.map {
case (config, dep) =>
dependencyNode(config, dep)
}
}</dependencies>
}
{
if (proj.dependencyManagement.isEmpty)
Nil
else
<dependencyManagement>
<dependencies>{
proj.dependencyManagement.map {
case (config, dep) =>
dependencyNode(config, dep)
}
}</dependencies>
</dependencyManagement>
}
// properties
// repositories
</project>
}
}

View File

@ -183,7 +183,8 @@ final case class Artifact(
checksumUrls: Map[String, String],
extra: Map[String, Artifact],
attributes: Attributes,
changing: Boolean
changing: Boolean,
authentication: Option[Authentication]
)
object Artifact {
@ -205,3 +206,11 @@ object Artifact {
}
}
}
case class Authentication(
user: String,
password: String
) {
override def toString: String =
s"Authentication($user, *******)"
}

View File

@ -34,7 +34,8 @@ object Repository {
Map.empty,
Map.empty,
Attributes("asc", ""),
changing = underlying.changing
changing = underlying.changing,
authentication = underlying.authentication
)
.withDefaultChecksums
))

View File

@ -529,6 +529,16 @@ final case class Resolution(
} else
Nil
def dependenciesOf(dep: Dependency, withReconciledVersions: Boolean = true): Seq[Dependency] =
if (withReconciledVersions)
finalDependencies0(dep).map { trDep =>
trDep.copy(
version = reconciledVersions.getOrElse(trDep.module, trDep.version)
)
}
else
finalDependencies0(dep)
/**
* Transitive dependencies of the current dependencies, according to
* what there currently is in cache.
@ -558,6 +568,9 @@ final case class Resolution(
forceVersions
)
def reconciledVersions: Map[Module, String] =
nextDependenciesAndConflicts._3
/**
* The modules we miss some info about.
*/
@ -974,10 +987,9 @@ final case class Resolution(
* @param dependencies: the dependencies to keep from this `Resolution`
*/
def subset(dependencies: Set[Dependency]): Resolution = {
val (_, _, finalVersions) = nextDependenciesAndConflicts
def updateVersion(dep: Dependency): Dependency =
dep.copy(version = finalVersions.getOrElse(dep.module, dep.version))
dep.copy(version = reconciledVersions.getOrElse(dep.module, dep.version))
@tailrec def helper(current: Set[Dependency]): Set[Dependency] = {
val newDeps = current ++ current

View File

@ -14,7 +14,8 @@ case class IvyRepository(
withSignatures: Boolean = true,
withArtifacts: Boolean = true,
// hack for SBT putting infos in properties
dropInfoAttributes: Boolean = false
dropInfoAttributes: Boolean = false,
authentication: Option[Authentication] = None
) extends Repository {
def metadataPattern: String = metadataPatternOpt.getOrElse(pattern)
@ -92,7 +93,8 @@ case class IvyRepository(
Map.empty,
Map.empty,
p.attributes,
changing = changing.getOrElse(project.version.contains("-SNAPSHOT")) // could be more reliable
changing = changing.getOrElse(project.version.contains("-SNAPSHOT")), // could be more reliable
authentication = authentication
)
if (withChecksums)
@ -127,7 +129,8 @@ case class IvyRepository(
Map.empty,
Map.empty,
Attributes("ivy", ""),
changing = changing.getOrElse(version.contains("-SNAPSHOT"))
changing = changing.getOrElse(version.contains("-SNAPSHOT")),
authentication = authentication
)
if (withChecksums)

View File

@ -70,14 +70,15 @@ case class MavenRepository(
root: String,
changing: Option[Boolean] = None,
/** Hackish hack for sbt plugins mainly - what this does really sucks */
sbtAttrStub: Boolean = false
sbtAttrStub: Boolean = false,
authentication: Option[Authentication] = None
) extends Repository {
import Repository._
import MavenRepository._
val root0 = if (root.endsWith("/")) root else root + "/"
val source = MavenSource(root0, changing, sbtAttrStub)
val source = MavenSource(root0, changing, sbtAttrStub, authentication)
def projectArtifact(
module: Module,
@ -96,7 +97,8 @@ case class MavenRepository(
Map.empty,
Map.empty,
Attributes("pom", ""),
changing = changing.getOrElse(version.contains("-SNAPSHOT"))
changing = changing.getOrElse(version.contains("-SNAPSHOT")),
authentication = authentication
)
.withDefaultChecksums
.withDefaultSignature
@ -115,7 +117,8 @@ case class MavenRepository(
Map.empty,
Map.empty,
Attributes("pom", ""),
changing = true
changing = true,
authentication = authentication
)
.withDefaultChecksums
.withDefaultSignature
@ -140,7 +143,8 @@ case class MavenRepository(
Map.empty,
Map.empty,
Attributes("pom", ""),
changing = true
changing = true,
authentication = authentication
)
.withDefaultChecksums
.withDefaultSignature

View File

@ -6,7 +6,8 @@ case class MavenSource(
root: String,
changing: Option[Boolean] = None,
/** See doc on MavenRepository */
sbtAttrStub: Boolean
sbtAttrStub: Boolean,
authentication: Option[Authentication]
) extends Artifact.Source {
import Repository._
@ -21,7 +22,8 @@ case class MavenSource(
Map.empty,
Map.empty,
Attributes("jar", "src"), // Are these the right attributes?
changing = underlying.changing
changing = underlying.changing,
authentication = authentication
)
.withDefaultChecksums
.withDefaultSignature,
@ -30,7 +32,8 @@ case class MavenSource(
Map.empty,
Map.empty,
Attributes("jar", "javadoc"), // Same comment as above
changing = underlying.changing
changing = underlying.changing,
authentication = authentication
)
.withDefaultChecksums
.withDefaultSignature
@ -65,7 +68,8 @@ case class MavenSource(
Map.empty,
Map.empty,
publication.attributes,
changing = changing0
changing = changing0,
authentication = authentication
)
.withDefaultChecksums

View File

@ -1,6 +1,6 @@
package coursier.util
import coursier.core.{Module, Project, Orders, Dependency}
import coursier.core.{ Attributes, Dependency, Module, Orders, Project, Resolution }
object Print {
@ -41,4 +41,154 @@ object Print {
deps1.map(dependency).mkString("\n")
}
private def compatibleVersions(first: String, second: String): Boolean = {
// too loose for now
// e.g. RCs and milestones should not be considered compatible with subsequent non-RC or
// milestone versions - possibly not with each other either
first.split('.').take(2).toSeq == second.split('.').take(2).toSeq
}
def dependencyTree(
roots: Seq[Dependency],
resolution: Resolution,
printExclusions: Boolean,
reverse: Boolean
): String = {
case class Elem(dep: Dependency, excluded: Boolean) {
lazy val reconciledVersion = resolution.reconciledVersions
.getOrElse(dep.module, dep.version)
lazy val repr =
if (excluded)
resolution.reconciledVersions.get(dep.module) match {
case None =>
s"${Console.YELLOW}(excluded)${Console.RESET} ${dep.module}:${dep.version}"
case Some(version) =>
val versionMsg =
if (version == dep.version)
"this version"
else
s"version $version"
s"${dep.module}:${dep.version} " +
s"${Console.RED}(excluded, $versionMsg present anyway)${Console.RESET}"
}
else {
val versionStr =
if (reconciledVersion == dep.version)
dep.version
else {
val assumeCompatibleVersions = compatibleVersions(dep.version, reconciledVersion)
(if (assumeCompatibleVersions) Console.YELLOW else Console.RED) +
s"${dep.version} -> $reconciledVersion" +
Console.RESET
}
s"${dep.module}:$versionStr"
}
lazy val children: Seq[Elem] =
if (excluded)
Nil
else {
val dep0 = dep.copy(version = reconciledVersion)
val dependencies = resolution.dependenciesOf(
dep0,
withReconciledVersions = false
).sortBy { trDep =>
(trDep.module.organization, trDep.module.name, trDep.version)
}
def excluded = resolution
.dependenciesOf(
dep0.copy(exclusions = Set.empty),
withReconciledVersions = false
)
.sortBy { trDep =>
(trDep.module.organization, trDep.module.name, trDep.version)
}
.map(_.moduleVersion)
.filterNot(dependencies.map(_.moduleVersion).toSet).map {
case (mod, ver) =>
Elem(
Dependency(mod, ver, "", Set.empty, Attributes("", ""), false, false),
excluded = true
)
}
dependencies.map(Elem(_, excluded = false)) ++
(if (printExclusions) excluded else Nil)
}
}
if (reverse) {
case class Parent(
module: Module,
version: String,
dependsOn: Module,
wantVersion: String,
gotVersion: String,
excluding: Boolean
) {
lazy val repr: String =
if (excluding)
s"${Console.YELLOW}(excluded by)${Console.RESET} $module:$version"
else if (wantVersion == gotVersion)
s"$module:$version"
else {
val assumeCompatibleVersions = compatibleVersions(wantVersion, gotVersion)
s"$module:$version " +
(if (assumeCompatibleVersions) Console.YELLOW else Console.RED) +
s"(wants $dependsOn:$wantVersion, got $gotVersion)" +
Console.RESET
}
}
val parents: Map[Module, Seq[Parent]] = {
val links = for {
dep <- resolution.dependencies.toVector
elem <- Elem(dep, excluded = false).children
}
yield elem.dep.module -> Parent(
dep.module,
dep.version,
elem.dep.module,
elem.dep.version,
elem.reconciledVersion,
elem.excluded
)
links
.groupBy(_._1)
.mapValues(_.map(_._2).distinct.sortBy(par => (par.module.organization, par.module.name)))
.iterator
.toMap
}
def children(par: Parent) =
if (par.excluding)
Nil
else
parents.getOrElse(par.module, Nil)
Tree(
resolution
.dependencies
.toVector
.sortBy(dep => (dep.module.organization, dep.module.name, dep.version))
.map(dep =>
Parent(dep.module, dep.version, dep.module, dep.version, dep.version, excluding = false)
)
)(children, _.repr)
} else
Tree(roots.toVector.map(Elem(_, excluded = false)))(_.children, _.repr)
}
}

View File

@ -0,0 +1,30 @@
package coursier.util
import scala.collection.mutable.ArrayBuffer
object Tree {
def apply[T](roots: IndexedSeq[T])(children: T => Seq[T], print: T => String): String = {
def helper(elems: Seq[T], prefix: String, acc: String => Unit): Unit =
for ((elem, idx) <- elems.zipWithIndex) {
val isLast = idx == elems.length - 1
val tee = if (isLast) "└─ " else "├─ "
acc(prefix + tee + print(elem))
val children0 = children(elem)
if (children0.nonEmpty) {
val extraPrefix = if (isLast) " " else "| "
helper(children0, prefix + extraPrefix, acc)
}
}
val b = new ArrayBuffer[String]
helper(roots, "", b += _)
b.mkString("\n")
}
}

View File

@ -80,7 +80,7 @@ object Platform {
get(artifact.url)
.map(\/-(_))
.recover { case e: Exception =>
-\/(e.getMessage)
-\/(e.toString + Option(e.getMessage).fold("")(" (" + _ + ")"))
}
}
)
@ -104,9 +104,10 @@ object Platform {
.flatMap(_ => get(artifact.url))
.map { s => logger.fetched(artifact.url); \/-(s) }
.recover { case e: Exception =>
logger.other(artifact.url, e.getMessage)
-\/(e.getMessage)
}
val msg = e.toString + Option(e.getMessage).fold("")(" (" + _ + ")")
logger.other(artifact.url, msg)
-\/(msg)
}
}
)
}

View File

@ -16,18 +16,41 @@ object CoursierPlugin extends AutoPlugin {
val coursierArtifactsChecksums = Keys.coursierArtifactsChecksums
val coursierCachePolicies = Keys.coursierCachePolicies
val coursierVerbosity = Keys.coursierVerbosity
val coursierSourceRepositories = Keys.coursierSourceRepositories
val coursierResolvers = Keys.coursierResolvers
val coursierSbtResolvers = Keys.coursierSbtResolvers
val coursierCredentials = Keys.coursierCredentials
val coursierFallbackDependencies = Keys.coursierFallbackDependencies
val coursierCache = Keys.coursierCache
val coursierProject = Keys.coursierProject
val coursierProjects = Keys.coursierProjects
val coursierPublications = Keys.coursierPublications
val coursierSbtClassifiersModule = Keys.coursierSbtClassifiersModule
val coursierConfigurations = Keys.coursierConfigurations
val coursierResolution = Keys.coursierResolution
val coursierSbtClassifiersResolution = Keys.coursierSbtClassifiersResolution
val coursierDependencyTree = Keys.coursierDependencyTree
val coursierDependencyInverseTree = Keys.coursierDependencyInverseTree
val coursierExport = Keys.coursierExport
val coursierExportDirectory = Keys.coursierExportDirectory
val coursierExportJavadoc = Keys.coursierExportJavadoc
val coursierExportSources = Keys.coursierExportSources
}
import autoImport._
lazy val treeSettings = Seq(
coursierDependencyTree <<= Tasks.coursierDependencyTreeTask(
inverse = false
),
coursierDependencyInverseTree <<= Tasks.coursierDependencyTreeTask(
inverse = true
)
)
override lazy val projectSettings = Seq(
coursierParallelDownloads := 6,
@ -36,8 +59,10 @@ object CoursierPlugin extends AutoPlugin {
coursierArtifactsChecksums := Seq(None),
coursierCachePolicies := Settings.defaultCachePolicies,
coursierVerbosity := Settings.defaultVerbosityLevel,
coursierSourceRepositories := Nil,
coursierResolvers <<= Tasks.coursierResolversTask,
coursierSbtResolvers <<= externalResolvers in updateSbtClassifiers,
coursierCredentials := Map.empty,
coursierFallbackDependencies <<= Tasks.coursierFallbackDependenciesTask,
coursierCache := Cache.default,
update <<= Tasks.updateTask(withClassifiers = false),
@ -53,7 +78,18 @@ object CoursierPlugin extends AutoPlugin {
coursierProject <<= Tasks.coursierProjectTask,
coursierProjects <<= Tasks.coursierProjectsTask,
coursierPublications <<= Tasks.coursierPublicationsTask,
coursierSbtClassifiersModule <<= classifiersModule in updateSbtClassifiers
)
coursierSbtClassifiersModule <<= classifiersModule in updateSbtClassifiers,
coursierConfigurations <<= Tasks.coursierConfigurationsTask,
coursierResolution <<= Tasks.resolutionTask(),
coursierSbtClassifiersResolution <<= Tasks.resolutionTask(
sbtClassifiers = true
),
coursierExport <<= Tasks.coursierExportTask,
coursierExportDirectory := baseDirectory.in(ThisBuild).value / "target" / "repository",
coursierExportJavadoc := false,
coursierExportSources := false
) ++
inConfig(Compile)(treeSettings) ++
inConfig(Test)(treeSettings)
}

View File

@ -0,0 +1,57 @@
package coursier
import java.io.{File, FileInputStream}
import java.util.Properties
import coursier.core.Authentication
sealed abstract class Credentials extends Product with Serializable {
def user: String
def password: String
def authentication: Authentication =
Authentication(user, password)
}
object Credentials {
case class Direct(user: String, password: String) extends Credentials {
override def toString = s"Direct($user, ******)"
}
case class FromFile(file: File) extends Credentials {
private lazy val props = {
val p = new Properties()
p.load(new FileInputStream(file))
p
}
private def findKey(keys: Seq[String]) = keys
.iterator
.map(props.getProperty)
.filter(_ != null)
.toStream
.headOption
.getOrElse {
throw new NoSuchElementException(s"${keys.head} key in $file")
}
lazy val user: String = findKey(FromFile.fileUserKeys)
lazy val password: String = findKey(FromFile.filePasswordKeys)
}
object FromFile {
// from sbt.Credentials
private val fileUserKeys = Seq("user", "user.name", "username")
private val filePasswordKeys = Seq("password", "pwd", "pass", "passwd")
}
def apply(user: String, password: String): Credentials =
Direct(user, password)
def apply(file: File): Credentials =
FromFile(file)
}

View File

@ -18,7 +18,7 @@ case class FallbackDependenciesRepository(
case None => Nil
case Some((url, changing)) =>
Seq(
Artifact(url.toString, Map.empty, Map.empty, Attributes("jar", ""), changing)
Artifact(url.toString, Map.empty, Map.empty, Attributes("jar", ""), changing, None)
)
}
}

View File

@ -4,6 +4,7 @@ import coursier.ivy.{ IvyXml, IvyRepository }
import java.net.{ MalformedURLException, URL }
import coursier.core.Authentication
import sbt.{ Resolver, CrossVersion, ModuleID }
import sbt.mavenint.SbtPomExtraProperties
@ -145,17 +146,27 @@ object FromSbt {
} else
None
private def mavenRepositoryOpt(root: String, log: sbt.Logger): Option[MavenRepository] =
private def mavenRepositoryOpt(
root: String,
log: sbt.Logger,
authentication: Option[Authentication]
): Option[MavenRepository] =
try {
Cache.url(root) // ensure root is a URL whose protocol can be handled here
val root0 = if (root.endsWith("/")) root else root + "/"
Some(MavenRepository(root0, sbtAttrStub = true))
Some(
MavenRepository(
root0,
sbtAttrStub = true,
authentication = authentication
)
)
} catch {
case e: MalformedURLException =>
log.warn(
"Error parsing Maven repository base " +
root +
Option(e.getMessage).map(" (" + _ + ")").mkString +
Option(e.getMessage).fold("")(" (" + _ + ")") +
", ignoring it"
)
@ -165,11 +176,12 @@ object FromSbt {
def repository(
resolver: Resolver,
ivyProperties: Map[String, String],
log: sbt.Logger
log: sbt.Logger,
authentication: Option[Authentication]
): Option[Repository] =
resolver match {
case sbt.MavenRepository(_, root) =>
mavenRepositoryOpt(root, log)
mavenRepositoryOpt(root, log, authentication)
case sbt.FileRepository(_, _, patterns)
if patterns.ivyPatterns.lengthCompare(1) == 0 &&
@ -184,10 +196,11 @@ object FromSbt {
metadataPatternOpt = Some("file://" + patterns.ivyPatterns.head),
changing = Some(true),
properties = ivyProperties,
dropInfoAttributes = true
dropInfoAttributes = true,
authentication = authentication
))
case Some(mavenCompatibleBase) =>
mavenRepositoryOpt("file://" + mavenCompatibleBase, log)
mavenRepositoryOpt("file://" + mavenCompatibleBase, log, authentication)
}
case sbt.URLRepository(_, patterns)
@ -203,10 +216,11 @@ object FromSbt {
metadataPatternOpt = Some(patterns.ivyPatterns.head),
changing = None,
properties = ivyProperties,
dropInfoAttributes = true
dropInfoAttributes = true,
authentication = authentication
))
case Some(mavenCompatibleBase) =>
mavenRepositoryOpt(mavenCompatibleBase, log)
mavenRepositoryOpt(mavenCompatibleBase, log, authentication)
}
case other =>

View File

@ -7,24 +7,57 @@ import coursier.core.Publication
import sbt.{ GetClassifiersModule, Resolver, SettingKey, TaskKey }
object Keys {
val coursierParallelDownloads = SettingKey[Int]("coursier-parallel-downloads", "")
val coursierMaxIterations = SettingKey[Int]("coursier-max-iterations", "")
val coursierChecksums = SettingKey[Seq[Option[String]]]("coursier-checksums", "")
val coursierArtifactsChecksums = SettingKey[Seq[Option[String]]]("coursier-artifacts-checksums", "")
val coursierCachePolicies = SettingKey[Seq[CachePolicy]]("coursier-cache-policies", "")
val coursierParallelDownloads = SettingKey[Int]("coursier-parallel-downloads")
val coursierMaxIterations = SettingKey[Int]("coursier-max-iterations")
val coursierChecksums = SettingKey[Seq[Option[String]]]("coursier-checksums")
val coursierArtifactsChecksums = SettingKey[Seq[Option[String]]]("coursier-artifacts-checksums")
val coursierCachePolicies = SettingKey[Seq[CachePolicy]]("coursier-cache-policies")
val coursierVerbosity = SettingKey[Int]("coursier-verbosity", "")
val coursierVerbosity = SettingKey[Int]("coursier-verbosity")
val coursierResolvers = TaskKey[Seq[Resolver]]("coursier-resolvers", "")
val coursierSbtResolvers = TaskKey[Seq[Resolver]]("coursier-sbt-resolvers", "")
val coursierSourceRepositories = SettingKey[Seq[File]]("coursier-source-repositories")
val coursierResolvers = TaskKey[Seq[Resolver]]("coursier-resolvers")
val coursierSbtResolvers = TaskKey[Seq[Resolver]]("coursier-sbt-resolvers")
val coursierCredentials = TaskKey[Map[String, Credentials]]("coursier-credentials")
val coursierCache = SettingKey[File]("coursier-cache", "")
val coursierCache = SettingKey[File]("coursier-cache")
val coursierFallbackDependencies = TaskKey[Seq[(Module, String, URL, Boolean)]]("coursier-fallback-dependencies", "")
val coursierFallbackDependencies = TaskKey[Seq[(Module, String, URL, Boolean)]]("coursier-fallback-dependencies")
val coursierProject = TaskKey[Project]("coursier-project", "")
val coursierProjects = TaskKey[Seq[Project]]("coursier-projects", "")
val coursierPublications = TaskKey[Seq[(String, Publication)]]("coursier-publications", "")
val coursierProject = TaskKey[Project]("coursier-project")
val coursierProjects = TaskKey[Seq[Project]]("coursier-projects")
val coursierPublications = TaskKey[Seq[(String, Publication)]]("coursier-publications")
val coursierSbtClassifiersModule = TaskKey[GetClassifiersModule]("coursier-sbt-classifiers-module", "")
val coursierSbtClassifiersModule = TaskKey[GetClassifiersModule]("coursier-sbt-classifiers-module")
val coursierConfigurations = TaskKey[Map[String, Set[String]]]("coursier-configurations")
val coursierResolution = TaskKey[Resolution]("coursier-resolution")
val coursierSbtClassifiersResolution = TaskKey[Resolution]("coursier-sbt-classifiers-resolution")
val coursierDependencyTree = TaskKey[Unit](
"coursier-dependency-tree",
"Prints dependencies and transitive dependencies as a tree"
)
val coursierDependencyInverseTree = TaskKey[Unit](
"coursier-dependency-inverse-tree",
"Prints dependencies and transitive dependencies as an inverted tree (dependees as children)"
)
val coursierExport = TaskKey[Option[File]](
"coursier-export",
"Generates files allowing using these sources as a source dependency repository"
)
val coursierExportDirectory = TaskKey[File](
"coursier-export-directory",
"Base directory for the products of coursierExport"
)
val coursierExportJavadoc = SettingKey[Boolean](
"coursier-export-javadoc",
"Build javadoc packages for the coursier source dependency repository"
)
val coursierExportSources = SettingKey[Boolean](
"coursier-export-sources",
"Build sources packages for the coursier source dependency repository"
)
}

View File

@ -9,6 +9,7 @@ import coursier.core.Publication
import coursier.ivy.IvyRepository
import coursier.Keys._
import coursier.Structure._
import coursier.maven.WritePom
import coursier.util.{ Config, Print }
import org.apache.ivy.core.module.id.ModuleRevisionId
@ -166,16 +167,49 @@ object Tasks {
sbtArtifactsPublication ++ extraSbtArtifactsPublication
}
// FIXME More things should possibly be put here too (resolvers, etc.)
private case class CacheKey(
def coursierConfigurationsTask = Def.task {
val configs0 = ivyConfigurations.value.map { config =>
config.name -> config.extendsConfigs.map(_.name)
}.toMap
def allExtends(c: String) = {
// possibly bad complexity
def helper(current: Set[String]): Set[String] = {
val newSet = current ++ current.flatMap(configs0.getOrElse(_, Nil))
if ((newSet -- current).nonEmpty)
helper(newSet)
else
newSet
}
helper(Set(c))
}
configs0.map {
case (config, _) =>
config -> allExtends(config)
}
}
private case class ResolutionCacheKey(
project: Project,
repositories: Seq[Repository],
resolution: Resolution,
sbtClassifiers: Boolean
)
private case class ReportCacheKey(
project: Project,
resolution: Resolution,
withClassifiers: Boolean,
sbtClassifiers: Boolean
)
private val resolutionsCache = new mutable.HashMap[CacheKey, UpdateReport]
private val resolutionsCache = new mutable.HashMap[ResolutionCacheKey, Resolution]
// these may actually not need to be cached any more, now that the resolutions
// are cached
private val reportsCache = new mutable.HashMap[ReportCacheKey, UpdateReport]
private def forcedScalaModules(scalaVersion: String): Map[Module, String] =
Map(
@ -185,17 +219,12 @@ object Tasks {
Module("org.scala-lang", "scalap") -> scalaVersion
)
def updateTask(
withClassifiers: Boolean,
sbtClassifiers: Boolean = false,
ignoreArtifactErrors: Boolean = false
) = Def.task {
private def projectDescription(project: Project) =
s"${project.module.organization}:${project.module.name}:${project.version}"
def grouped[K, V](map: Seq[(K, V)]): Map[K, Seq[V]] =
map.groupBy { case (k, _) => k }.map {
case (k, l) =>
k -> l.map { case (_, v) => v }
}
def resolutionTask(
sbtClassifiers: Boolean = false
) = Def.task {
// let's update only one module at once, for a better output
// Downloads are already parallel, no need to parallelize further anyway
@ -230,25 +259,10 @@ object Tasks {
(proj.copy(publications = publications), fallbackDeps)
}
val ivySbt0 = ivySbt.value
val ivyCacheManager = ivySbt0.withIvy(streams.value.log)(ivy =>
ivy.getResolutionCacheManager
)
val ivyModule = ModuleRevisionId.newInstance(
currentProject.module.organization,
currentProject.module.name,
currentProject.version,
currentProject.module.attributes.asJava
)
val cacheIvyFile = ivyCacheManager.getResolvedIvyFileInCache(ivyModule)
val cacheIvyPropertiesFile = ivyCacheManager.getResolvedIvyPropertiesInCache(ivyModule)
val projects = coursierProjects.value
val parallelDownloads = coursierParallelDownloads.value
val checksums = coursierChecksums.value
val artifactsChecksums = coursierArtifactsChecksums.value
val maxIterations = coursierMaxIterations.value
val cachePolicies = coursierCachePolicies.value
val cache = coursierCache.value
@ -286,6 +300,40 @@ object Tasks {
else
coursierResolvers.value
val sourceRepositories = coursierSourceRepositories.value.map { dir =>
// FIXME Don't hardcode this path?
new File(dir, "target/repository")
}
val sourceRepositoriesForcedDependencies = sourceRepositories.flatMap {
base =>
def pomDirComponents(f: File, components: Vector[String]): Stream[Vector[String]] =
if (f.isDirectory) {
val components0 = components :+ f.getName
Option(f.listFiles()).toStream.flatten.flatMap(pomDirComponents(_, components0))
} else if (f.getName.endsWith(".pom"))
Stream(components)
else
Stream.empty
Option(base.listFiles())
.toVector
.flatten
.flatMap(pomDirComponents(_, Vector()))
// at least 3 for org / name / version - the contrary should not happen, but who knows
.filter(_.length >= 3)
.map { components =>
val org = components.dropRight(2).mkString(".")
val name = components(components.length - 2)
val version = components.last
Module(org, name) -> version
}
}
// TODO Warn about possible duplicated modules from source repositories?
val verbosityLevel = coursierVerbosity.value
@ -295,25 +343,14 @@ object Tasks {
dep.copy(exclusions = dep.exclusions ++ exclusions)
}.toSet,
filter = Some(dep => !dep.optional),
forceVersions = userForceVersions ++ forcedScalaModules(sv) ++ projects.map(_.moduleVersion)
forceVersions =
// order matters here
userForceVersions ++
sourceRepositoriesForcedDependencies ++
forcedScalaModules(sv) ++
projects.map(_.moduleVersion)
)
// required for publish to be fine, later on
def writeIvyFiles() = {
val printer = new scala.xml.PrettyPrinter(80, 2)
val b = new StringBuilder
b ++= """<?xml version="1.0" encoding="UTF-8"?>"""
b += '\n'
b ++= printer.format(MakeIvyXml(currentProject))
cacheIvyFile.getParentFile.mkdirs()
Files.write(cacheIvyFile.toPath, b.result().getBytes("UTF-8"))
// Just writing an empty file here... Are these only used?
cacheIvyPropertiesFile.getParentFile.mkdirs()
Files.write(cacheIvyPropertiesFile.toPath, "".getBytes("UTF-8"))
}
if (verbosityLevel >= 2) {
log.info("InterProjectRepository")
for (p <- projects)
@ -334,12 +371,14 @@ object Tasks {
"ivy.home" -> (new File(sys.props("user.home")).toURI.getPath + ".ivy2")
) ++ sys.props
val repositories = Seq(
globalPluginsRepo,
interProjectRepo
) ++ resolvers.flatMap(
FromSbt.repository(_, ivyProperties, log)
) ++ {
val credentials = coursierCredentials.value
val sourceRepositories0 = sourceRepositories.map {
base =>
MavenRepository(base.toURI.toString, changing = Some(true))
}
val fallbackDependenciesRepositories =
if (fallbackDependencies.isEmpty)
Nil
else {
@ -352,9 +391,21 @@ object Tasks {
FallbackDependenciesRepository(map)
)
}
}
def report = {
val repositories =
Seq(globalPluginsRepo, interProjectRepo) ++
sourceRepositories0 ++
resolvers.flatMap { resolver =>
FromSbt.repository(
resolver,
ivyProperties,
log,
credentials.get(resolver.name).map(_.authentication)
)
} ++
fallbackDependenciesRepositories
def resolution = {
val pool = Executors.newFixedThreadPool(parallelDownloads, Strategy.DefaultDaemonThreadFactory)
def createLogger() = new TermDisplay(new OutputStreamWriter(System.err))
@ -374,11 +425,6 @@ object Tasks {
s"${dep.module}:${dep.version}:$config->${dep.configuration}"
}.sorted.distinct
def depsRepr0(deps: Seq[Dependency]) =
deps.map { dep =>
s"${dep.module}:${dep.version}:${dep.configuration}"
}.sorted.distinct
if (verbosityLevel >= 2) {
val repoReprs = repositories.map {
case r: IvyRepository =>
@ -399,7 +445,10 @@ object Tasks {
}
if (verbosityLevel >= 0)
log.info(s"Updating ${currentProject.module.organization}:${currentProject.module.name}:${currentProject.version}")
log.info(
s"Updating ${projectDescription(currentProject)}" +
(if (sbtClassifiers) " (sbt classifiers)" else "")
)
if (verbosityLevel >= 2)
for (depRepr <- depsRepr(currentProject.dependencies))
log.info(s" $depRepr")
@ -443,34 +492,115 @@ object Tasks {
throw new Exception(s"Encountered ${res.errors.length} error(s) in dependency resolution")
}
val depsByConfig = grouped(currentProject.dependencies)
if (verbosityLevel >= 0)
log.info(s"Resolved ${projectDescription(currentProject)} dependencies")
val configs = {
val configs0 = ivyConfigurations.value.map { config =>
config.name -> config.extendsConfigs.map(_.name)
}.toMap
res
}
def allExtends(c: String) = {
// possibly bad complexity
def helper(current: Set[String]): Set[String] = {
val newSet = current ++ current.flatMap(configs0.getOrElse(_, Nil))
if ((newSet -- current).nonEmpty)
helper(newSet)
else
newSet
}
resolutionsCache.getOrElseUpdate(
ResolutionCacheKey(
currentProject,
repositories,
startRes.copy(filter = None),
sbtClassifiers
),
resolution
)
}
}
helper(Set(c))
}
def updateTask(
withClassifiers: Boolean,
sbtClassifiers: Boolean = false,
ignoreArtifactErrors: Boolean = false
) = Def.task {
configs0.map {
case (config, _) =>
config -> allExtends(config)
}
def grouped[K, V](map: Seq[(K, V)]): Map[K, Seq[V]] =
map.groupBy { case (k, _) => k }.map {
case (k, l) =>
k -> l.map { case (_, v) => v }
}
// let's update only one module at once, for a better output
// Downloads are already parallel, no need to parallelize further anyway
synchronized {
lazy val cm = coursierSbtClassifiersModule.value
val currentProject =
if (sbtClassifiers) {
val sv = scalaVersion.value
val sbv = scalaBinaryVersion.value
FromSbt.project(
cm.id,
cm.modules,
cm.configurations.map(cfg => cfg.name -> cfg.extendsConfigs.map(_.name)).toMap,
sv,
sbv
)
} else {
val proj = coursierProject.value
val publications = coursierPublications.value
proj.copy(publications = publications)
}
if (verbosityLevel >= 0)
log.info("Resolution done")
val ivySbt0 = ivySbt.value
val ivyCacheManager = ivySbt0.withIvy(streams.value.log)(ivy =>
ivy.getResolutionCacheManager
)
val ivyModule = ModuleRevisionId.newInstance(
currentProject.module.organization,
currentProject.module.name,
currentProject.version,
currentProject.module.attributes.asJava
)
val cacheIvyFile = ivyCacheManager.getResolvedIvyFileInCache(ivyModule)
val cacheIvyPropertiesFile = ivyCacheManager.getResolvedIvyPropertiesInCache(ivyModule)
val parallelDownloads = coursierParallelDownloads.value
val artifactsChecksums = coursierArtifactsChecksums.value
val cachePolicies = coursierCachePolicies.value
val cache = coursierCache.value
val log = streams.value.log
val verbosityLevel = coursierVerbosity.value
// required for publish to be fine, later on
def writeIvyFiles() = {
val printer = new scala.xml.PrettyPrinter(80, 2)
val b = new StringBuilder
b ++= """<?xml version="1.0" encoding="UTF-8"?>"""
b += '\n'
b ++= printer.format(MakeIvyXml(currentProject))
cacheIvyFile.getParentFile.mkdirs()
Files.write(cacheIvyFile.toPath, b.result().getBytes("UTF-8"))
// Just writing an empty file here... Are these only used?
cacheIvyPropertiesFile.getParentFile.mkdirs()
Files.write(cacheIvyPropertiesFile.toPath, "".getBytes("UTF-8"))
}
val res = {
if (withClassifiers && sbtClassifiers)
coursierSbtClassifiersResolution
else
coursierResolution
}.value
def report = {
val pool = Executors.newFixedThreadPool(parallelDownloads, Strategy.DefaultDaemonThreadFactory)
def createLogger() = new TermDisplay(new OutputStreamWriter(System.err))
val depsByConfig = grouped(currentProject.dependencies)
val configs = coursierConfigurations.value
if (verbosityLevel >= 2) {
val finalDeps = Config.dependenciesWithConfig(
res,
@ -520,7 +650,10 @@ object Tasks {
}
if (verbosityLevel >= 0)
log.info("Fetching artifacts")
log.info(
s"Fetching artifacts of ${projectDescription(currentProject)}" +
(if (sbtClassifiers) " (sbt classifiers)" else "")
)
artifactsLogger.init()
@ -534,7 +667,10 @@ object Tasks {
artifactsLogger.stop()
if (verbosityLevel >= 0)
log.info("Fetching artifacts: done")
log.info(
s"Fetched artifacts of ${projectDescription(currentProject)}" +
(if (sbtClassifiers) " (sbt classifiers)" else "")
)
val artifactFiles = artifactFilesOrErrors.collect {
case (artifact, \/-(file)) =>
@ -602,11 +738,10 @@ object Tasks {
)
}
resolutionsCache.getOrElseUpdate(
CacheKey(
reportsCache.getOrElseUpdate(
ReportCacheKey(
currentProject,
repositories,
startRes.copy(filter = None),
res,
withClassifiers,
sbtClassifiers
),
@ -615,4 +750,145 @@ object Tasks {
}
}
def coursierDependencyTreeTask(
inverse: Boolean,
sbtClassifiers: Boolean = false,
ignoreArtifactErrors: Boolean = false
) = Def.task {
val currentProject =
if (sbtClassifiers) {
val cm = coursierSbtClassifiersModule.value
val sv = scalaVersion.value
val sbv = scalaBinaryVersion.value
FromSbt.project(
cm.id,
cm.modules,
cm.configurations.map(cfg => cfg.name -> cfg.extendsConfigs.map(_.name)).toMap,
sv,
sbv
)
} else {
val proj = coursierProject.value
val publications = coursierPublications.value
proj.copy(publications = publications)
}
val res = {
if (sbtClassifiers)
coursierSbtClassifiersResolution
else
coursierResolution
}.value
val config = classpathConfiguration.value.name
val configs = coursierConfigurations.value
val includedConfigs = configs.getOrElse(config, Set.empty) + config
val dependencies0 = currentProject.dependencies.collect {
case (cfg, dep) if includedConfigs(cfg) => dep
}.sortBy { dep =>
(dep.module.organization, dep.module.name, dep.version)
}
val subRes = res.subset(dependencies0.toSet)
// use sbt logging?
println(
projectDescription(currentProject) + "\n" +
Print.dependencyTree(dependencies0, subRes, printExclusions = true, inverse)
)
}
def coursierExportTask =
(
sbt.Keys.state,
sbt.Keys.thisProjectRef,
sbt.Keys.projectID,
sbt.Keys.scalaVersion,
sbt.Keys.scalaBinaryVersion,
sbt.Keys.ivyConfigurations,
streams,
coursierProject,
coursierExportDirectory,
coursierExportJavadoc,
coursierExportSources
).flatMap { (state, projectRef, projId, sv, sbv, ivyConfs, streams, proj, exportDir, exportJavadoc, exportSources) =>
val javadocPackageTasks =
if (exportJavadoc)
Seq(Some("javadoc") -> packageDoc)
else
Nil
val sourcesPackageTasks =
if (exportJavadoc)
Seq(Some("sources") -> packageSrc)
else
Nil
val packageTasks = Seq(None -> packageBin) ++ javadocPackageTasks ++ sourcesPackageTasks
val configs = Seq(None -> Compile, Some("tests") -> Test)
val productTasks =
for {
(classifierOpt, pkgTask) <- packageTasks
(classifierPrefixOpt, config) <- configs
if publishArtifact.in(projectRef).in(pkgTask).in(config).getOrElse(state, false)
} yield {
val classifier = (classifierPrefixOpt.toSeq ++ classifierOpt.toSeq).mkString("-")
pkgTask.in(projectRef).in(config).get(state).map((classifier, _))
}
val productTask = sbt.std.TaskExtra.joinTasks(productTasks).join
val dir = new File(
exportDir,
s"${proj.module.organization.replace('.', '/')}/${proj.module.name}/${proj.version}"
)
def pom = "<?xml version='1.0' encoding='UTF-8'?>\n" + WritePom.project(proj, Some("jar"))
val log = streams.log
productTask.map { products =>
if (products.isEmpty)
None
else {
dir.mkdirs()
val pomFile = new File(dir, s"${proj.module.name}-${proj.version}.pom")
Files.write(pomFile.toPath, pom.getBytes("UTF-8"))
log.info(s"Wrote POM file to $pomFile")
for ((classifier, f) <- products) {
val suffix = if (classifier.isEmpty) "" else "-" + classifier
val jarPath = new File(dir, s"${proj.module.name}-${proj.version}$suffix.jar")
if (jarPath.exists()) {
if (!jarPath.delete())
log.warn(s"Cannot remove $jarPath")
}
Files.createSymbolicLink(
jarPath.toPath,
dir.toPath.relativize(f.toPath)
)
log.info(s"Created symbolic link $jarPath -> $f")
}
// TODO Clean extra files in dir
Some(exportDir)
}
}
}
}

View File

@ -0,0 +1,14 @@
scalaVersion := "2.11.8"
resolvers += "authenticated" at "http://localhost:8080"
coursierCredentials += "authenticated" -> coursier.Credentials(file("credentials"))
coursierCachePolicies := {
if (sys.props("os.name").startsWith("Windows"))
coursierCachePolicies.value
else
Seq(coursier.CachePolicy.ForceDownload)
}
libraryDependencies += "com.abc" % "test" % "0.1"

View File

@ -0,0 +1,2 @@
user=user
password=pass

View File

@ -0,0 +1,11 @@
{
val pluginVersion = sys.props.getOrElse(
"plugin.version",
throw new RuntimeException(
"""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin
)
)
addSbtPlugin("io.get-coursier" % "sbt-coursier" % pluginVersion)
}

View File

@ -0,0 +1 @@
object Main extends App

View File

@ -0,0 +1 @@
> coursierResolution

View File

@ -0,0 +1,14 @@
scalaVersion := "2.11.8"
resolvers += "authenticated" at "http://localhost:8080"
coursierCredentials += "authenticated" -> coursier.Credentials("user", "pass")
coursierCachePolicies := {
if (sys.props("os.name").startsWith("Windows"))
coursierCachePolicies.value
else
Seq(coursier.CachePolicy.ForceDownload)
}
libraryDependencies += "com.abc" % "test" % "0.1"

View File

@ -0,0 +1,11 @@
{
val pluginVersion = sys.props.getOrElse(
"plugin.version",
throw new RuntimeException(
"""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin
)
)
addSbtPlugin("io.get-coursier" % "sbt-coursier" % pluginVersion)
}

View File

@ -0,0 +1 @@
object Main extends App

View File

@ -0,0 +1 @@
> coursierResolution

View File

@ -1,5 +1,12 @@
scalaVersion := "2.11.8"
coursierCachePolicies := {
if (sys.props("os.name").startsWith("Windows"))
coursierCachePolicies.value
else
Seq(coursier.CachePolicy.ForceDownload)
}
libraryDependencies += "com.github.alexarchambault" %% "argonaut-shapeless_6.1" % "1.0.0-RC1"
excludeDependencies += SbtExclusionRule("com.chuusai", "shapeless_2.11")

View File

@ -1,5 +1,12 @@
scalaVersion := "2.11.8"
coursierCachePolicies := {
if (sys.props("os.name").startsWith("Windows"))
coursierCachePolicies.value
else
Seq(coursier.CachePolicy.ForceDownload)
}
libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.41" from {
val f = file(sys.props("sbttest.base")) / "sbt-coursier" / "from" / "shapeless_2.11-2.3.0.jar"

View File

@ -1,5 +1,12 @@
scalaVersion := "2.11.8"
coursierCachePolicies := {
if (sys.props("os.name").startsWith("Windows"))
coursierCachePolicies.value
else
Seq(coursier.CachePolicy.ForceDownload)
}
resolvers += Resolver.url(
"webjars-bintray",
new URL("https://dl.bintray.com/scalaz/releases/")

View File

@ -1 +1,8 @@
scalaVersion := "2.11.8"
coursierCachePolicies := {
if (sys.props("os.name").startsWith("Windows"))
coursierCachePolicies.value
else
Seq(coursier.CachePolicy.ForceDownload)
}

View File

@ -1,15 +1,19 @@
package coursier
import java.io.{ File, FileOutputStream }
import java.net.NetworkInterface
import java.nio.channels.{ FileLock, OverlappingFileLockException }
import org.http4s.dsl._
import org.http4s.headers.Authorization
import org.http4s.server.HttpService
import org.http4s.server.blaze.BlazeBuilder
import org.http4s.{ BasicCredentials, Challenge, HttpService, Request, Response }
import org.http4s.{ BasicCredentials, Challenge, EmptyBody, Request, Response }
import caseapp._
import scala.collection.JavaConverters._
import scalaz.concurrent.Task
case class SimpleHttpServerApp(
@ -22,15 +26,17 @@ case class SimpleHttpServerApp(
@ExtraName("p")
@ValueDescription("port")
port: Int = 8080,
@ExtraName("P")
@ExtraName("s")
acceptPost: Boolean,
@ExtraName("t")
acceptPut: Boolean,
@ExtraName("w")
@HelpMessage("Accept write requests. Equivalent to -P -t")
@HelpMessage("Accept write requests. Equivalent to -s -t")
acceptWrite: Boolean,
@ExtraName("v")
verbose: Int @@ Counter,
@ExtraName("q")
quiet: Boolean,
@ExtraName("u")
@ValueDescription("user")
user: String,
@ -44,7 +50,7 @@ case class SimpleHttpServerApp(
val baseDir = new File(if (directory.isEmpty) "." else directory)
val verbosityLevel = Tag.unwrap(verbose)
val verbosityLevel = Tag.unwrap(verbose) - (if (quiet) 1 else 0)
def write(path: Seq[String], req: Request): Boolean = {
@ -108,16 +114,37 @@ case class SimpleHttpServerApp(
else
HttpService {
case req =>
def warn(msg: => String) =
if (verbosityLevel >= 1)
Console.err.println(s"${req.method.name} ${req.uri.path}: $msg")
req.headers.get(Authorization) match {
case None =>
warn("no authentication provided")
unauthorized
case Some(auth) =>
auth.credentials match {
case basic: BasicCredentials =>
if (basic.username == user && basic.password == password)
service.run(req)
else
service.run(req).flatMap {
case Some(v) => Task.now(v)
case None => NotFound()
}
else {
warn {
val msg =
if (basic.username == user)
"wrong password"
else
s"unrecognized user ${basic.username}"
s"authentication failed ($msg)"
}
unauthorized
}
case _ =>
warn("no basic credentials found")
unauthorized
}
}
}
@ -145,15 +172,22 @@ case class SimpleHttpServerApp(
}
def getService = authenticated {
case GET -> path =>
case (method @ (GET | HEAD)) -> path =>
if (verbosityLevel >= 1)
Console.err.println(s"GET $path")
Console.err.println(s"${method.name} $path")
val f = new File(baseDir, path.toList.mkString("/"))
if (f.exists())
val resp = if (f.exists())
Ok(f)
else
NotFound()
method match {
case HEAD =>
resp.map(_.copy(body = EmptyBody))
case _ =>
resp
}
}
val builder = {
@ -169,6 +203,16 @@ case class SimpleHttpServerApp(
b
}
if (verbosityLevel >= 0) {
Console.err.println(s"Listening on http://$host:$port")
if (verbosityLevel >= 1 && host == "0.0.0.0") {
Console.err.println(s"Listening on addresses")
for (itf <- NetworkInterface.getNetworkInterfaces.asScala; addr <- itf.getInetAddresses.asScala)
Console.err.println(s" ${addr.getHostAddress} (${itf.getName})")
}
}
builder
.run
.awaitShutdown()

View File

@ -0,0 +1,147 @@
package coursier
package test
import java.io.File
import java.nio.file.Files
import coursier.cache.protocol.TestprotocolHandler
import coursier.core.Authentication
import utest._
import scala.util.Try
object CacheFetchTests extends TestSuite {
val tests = TestSuite {
def check(extraRepo: Repository): Unit = {
val tmpDir = Files.createTempDirectory("coursier-cache-fetch-tests").toFile
def cleanTmpDir() = {
def delete(f: File): Boolean =
if (f.isDirectory) {
val removedContent = Option(f.listFiles()).toSeq.flatten.map(delete).forall(x => x)
val removedDir = f.delete()
removedContent && removedDir
} else
f.delete()
if (!delete(tmpDir))
Console.err.println(s"Warning: unable to remove temporary directory $tmpDir")
}
val res = try {
val fetch = Fetch.from(
Seq(
extraRepo,
MavenRepository("https://repo1.maven.org/maven2")
),
Cache.fetch(
tmpDir
)
)
val startRes = Resolution(
Set(
Dependency(
Module("com.github.alexarchambault", "coursier_2.11"), "1.0.0-M9-test"
)
)
)
startRes.process.run(fetch).run
} finally {
cleanTmpDir()
}
val errors = res.errors
assert(errors.isEmpty)
}
// using scala-test would allow to put the below comments in the test names...
* - {
// test that everything's fine with basic file protocol
val repoPath = new File(getClass.getResource("/test-repo/http/abc.com").getPath)
check(MavenRepository(repoPath.toURI.toString))
}
'customProtocol - {
* - {
// test the Cache.url method
val shouldFail = Try(Cache.url("notfoundzzzz://foo/bar"))
assert(shouldFail.isFailure)
Cache.url("testprotocol://foo/bar")
}
* - {
// the real custom protocol test
check(MavenRepository(s"${TestprotocolHandler.protocol}://foo/"))
}
}
'httpAuthentication - {
// requires an authenticated HTTP server to be running on localhost:8080 with user 'user'
// and password 'pass'
val address = "localhost:8080"
val user = "user"
val password = "pass"
def printErrorMessage() =
Console.err.println(
Console.RED +
s"HTTP authentication tests require a running HTTP server on $address, requiring " +
s"basic authentication with user '$user' and password '$password', serving the right " +
"files.\n" + Console.RESET +
"Run one from the coursier sources with\n" +
" ./coursier launch -r http://dl.bintray.com/scalaz/releases " +
"io.get-coursier:simple-web-server_2.11:1.0.0-M12 -- " +
"-d tests/jvm/src/test/resources/test-repo/http/abc.com -u user -P pass -r realm -v"
)
* - {
// no authentication -> should fail
val failed = try {
check(
MavenRepository(
s"http://$address"
)
)
printErrorMessage()
false
} catch {
case e: Throwable =>
true
}
assert(failed)
}
* - {
// with authentication -> should work
try {
check(
MavenRepository(
s"http://$address",
authentication = Some(Authentication(user, password))
)
)
} catch {
case e: Throwable =>
printErrorMessage()
throw e
}
}
}
}
}

View File

@ -70,7 +70,8 @@ object ChecksumTests extends TestSuite {
),
Map.empty,
Attributes("jar"),
changing = false
changing = false,
authentication = None
)
val artifacts = Seq(

View File

@ -1,85 +0,0 @@
package coursier
package test
import java.io.File
import java.nio.file.Files
import coursier.cache.protocol.TestprotocolHandler
import utest._
import scala.util.Try
object CustomProtocolTests extends TestSuite {
val tests = TestSuite {
def check(extraMavenRepo: String): Unit = {
val tmpDir = Files.createTempDirectory("coursier-protocol-tests").toFile
def cleanTmpDir() = {
def delete(f: File): Boolean =
if (f.isDirectory) {
val removedContent = f.listFiles().map(delete).forall(x => x)
val removedDir = f.delete()
removedContent && removedDir
} else
f.delete()
if (!delete(tmpDir))
Console.err.println(s"Warning: unable to remove temporary directory $tmpDir")
}
val res = try {
val fetch = Fetch.from(
Seq(
MavenRepository(extraMavenRepo),
MavenRepository("https://repo1.maven.org/maven2")
),
Cache.fetch(
tmpDir
)
)
val startRes = Resolution(
Set(
Dependency(
Module("com.github.alexarchambault", "coursier_2.11"), "1.0.0-M9-test"
)
)
)
startRes.process.run(fetch).run
} finally {
cleanTmpDir()
}
val errors = res.errors
assert(errors.isEmpty)
}
// using scala-test would allow to put the below comments in the test names...
* - {
// test that everything's fine with standard protocols
val repoPath = new File(getClass.getResource("/test-repo/http/abc.com").getPath)
check(repoPath.toURI.toString)
}
* - {
// test the Cache.url method
val shouldFail = Try(Cache.url("notfoundzzzz://foo/bar"))
assert(shouldFail.isFailure)
Cache.url("testprotocol://foo/bar")
}
* - {
// the real custom protocol test
check(s"${TestprotocolHandler.protocol}://foo/")
}
}
}