mirror of https://github.com/sbt/sbt.git
Merge pull request #243 from alexarchambault/topic/develop
Latest developments
This commit is contained in:
commit
95d6d9e14e
|
|
@ -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
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
24
build.sbt
24
build.sbt
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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("")(" (" + _ + ")")}"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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, *******)"
|
||||
}
|
||||
|
|
@ -34,7 +34,8 @@ object Repository {
|
|||
Map.empty,
|
||||
Map.empty,
|
||||
Attributes("asc", ""),
|
||||
changing = underlying.changing
|
||||
changing = underlying.changing,
|
||||
authentication = underlying.authentication
|
||||
)
|
||||
.withDefaultChecksums
|
||||
))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
user=user
|
||||
password=pass
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
object Main extends App
|
||||
|
|
@ -0,0 +1 @@
|
|||
> coursierResolution
|
||||
|
|
@ -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"
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
object Main extends App
|
||||
|
|
@ -0,0 +1 @@
|
|||
> coursierResolution
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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/")
|
||||
|
|
|
|||
|
|
@ -1 +1,8 @@
|
|||
scalaVersion := "2.11.8"
|
||||
|
||||
coursierCachePolicies := {
|
||||
if (sys.props("os.name").startsWith("Windows"))
|
||||
coursierCachePolicies.value
|
||||
else
|
||||
Seq(coursier.CachePolicy.ForceDownload)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -70,7 +70,8 @@ object ChecksumTests extends TestSuite {
|
|||
),
|
||||
Map.empty,
|
||||
Attributes("jar"),
|
||||
changing = false
|
||||
changing = false,
|
||||
authentication = None
|
||||
)
|
||||
|
||||
val artifacts = Seq(
|
||||
|
|
|
|||
|
|
@ -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/")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue