Merge remote-tracking branch 'coursier/sbt-2.x' into 2.x-lm-coursier

This commit is contained in:
Adrien Piquerez 2024-10-09 09:13:25 +02:00
commit 7a325bc414
383 changed files with 9461 additions and 17 deletions

View File

@ -220,3 +220,32 @@ jobs:
find $HOME/.ivy2/cache -name "ivydata-*.properties" -delete || true
find $HOME/.cache/coursier/v1 -name "ivydata-*.properties" -delete || true
find $HOME/.sbt -name "*.lock" -delete || true
coursier-test:
name: ${{ matrix.os }} ${{ matrix.PLUGIN }} group ${{ matrix.TEST_GROUP }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
PLUGIN: [sbt-lm-coursier] # , sbt-coursier]
TEST_GROUP: [1, 2]
steps:
- name: Don't convert LF to CRLF during checkout
if: runner.os == 'Windows'
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- uses: coursier/cache-action@v6.4
- uses: coursier/setup-action@v1.3
with:
jvm: 8
apps: sbt
- run: scripts/ci.sh
shell: bash
env:
PLUGIN: ${{ matrix.PLUGIN }}
TEST_GROUP: ${{ matrix.TEST_GROUP }}

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "metadata"]
path = metadata
url = https://github.com/coursier/handmade-metadata.git

3
.scala-steward.conf Normal file
View File

@ -0,0 +1,3 @@
updates.pin = [
{ groupId = "org.slf4j", artifactId="slf4j-api", version = "1." }
]

231
build.sbt
View File

@ -7,8 +7,6 @@ import java.nio.file.{ Files, Path => JPath }
import java.util.Locale
import sbt.internal.inc.Analysis
import scala.util.Try
// ThisBuild settings take lower precedence,
// but can be shared across the multi projects.
ThisBuild / version := {
@ -50,11 +48,9 @@ ThisBuild / libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml"
Global / semanticdbEnabled := !(Global / insideCI).value
// Change main/src/main/scala/sbt/plugins/SemanticdbPlugin.scala too, if you change this.
Global / semanticdbVersion := "4.7.8"
val excludeLint = SettingKey[Set[Def.KeyedInitialize[_]]]("excludeLintKeys")
Global / excludeLint := (Global / excludeLint).?.value.getOrElse(Set.empty)
Global / excludeLint += componentID
Global / excludeLint += scriptedBufferLog
Global / excludeLint += checkPluginCross
Global / excludeLintKeys += componentID
Global / excludeLintKeys += scriptedBufferLog
Global / excludeLintKeys += checkPluginCross
ThisBuild / evictionErrorLevel := Level.Info
def commonSettings: Seq[Setting[_]] = Def.settings(
@ -208,9 +204,8 @@ lazy val sbtRoot: Project = (project in file("."))
scalacOptions += "-Ymacro-expand:none", // for both sxr and doc
Util.publishPomSettings,
otherRootSettings,
publish := {},
dontPublish,
publishLocal := {},
publish / skip := true,
Global / commands += Command
.single("sbtOn")((state, dir) => s"sbtProj/test:runMain sbt.RunFromSourceMain $dir" :: state),
mimaSettings,
@ -935,7 +930,6 @@ lazy val mainProj = (project in file("main"))
sjsonNewCore.value,
launcherInterface,
caffeine,
lmCoursierShaded,
) ++ log4jModules),
libraryDependencies ++= (scalaVersion.value match {
case v if v.startsWith("2.12.") => List()
@ -952,7 +946,7 @@ lazy val mainProj = (project in file("main"))
// mimaSettings,
// mimaBinaryIssueFilters ++= Vector(),
)
.dependsOn(lmCore, lmIvy)
.dependsOn(lmCore, lmIvy, lmCoursierShaded)
.configure(addSbtIO, addSbtCompilerInterface, addSbtZincCompileCore)
// Strictly for bringing implicits and aliases from subsystems into the top-level sbt namespace through a single package object
@ -993,7 +987,7 @@ lazy val serverTestProj = (project in file("server-test"))
.dependsOn(sbtProj % "compile->test", scriptedSbtProj % "compile->test")
.settings(
testedBaseSettings,
publish / skip := true,
dontPublish,
// make server tests serial
Test / watchTriggers += baseDirectory.value.toGlob / "src" / "server-test" / **,
Test / parallelExecution := false,
@ -1039,7 +1033,7 @@ lazy val sbtClientProj = (project in file("client"))
.dependsOn(commandProj)
.settings(
commonSettings,
publish / skip := true,
dontPublish,
name := "sbt-client",
mimaPreviousArtifacts := Set.empty,
crossPaths := false,
@ -1124,7 +1118,7 @@ lazy val sbtBig = (project in file(".big"))
lazy val lowerUtils = (project in (file("internal") / "lower"))
.aggregate(lowerUtilProjects.map(p => LocalProject(p.id)): _*)
.settings(
publish / skip := true
dontPublish
)
lazy val upperModules = (project in (file("internal") / "upper"))
@ -1133,7 +1127,7 @@ lazy val upperModules = (project in (file("internal") / "upper"))
diff Seq(bundledLauncherProj)).map(p => LocalProject(p.id)): _*
)
.settings(
publish / skip := true
dontPublish
)
lazy val sbtIgnoredProblems = {
@ -1224,6 +1218,9 @@ def allProjects =
remoteCacheProj,
lmCore,
lmIvy,
definitions,
lmCoursier,
lmCoursierShaded,
) ++ lowerUtilProjects
// These need to be cross published to 2.12 and 2.13 for Zinc
@ -1430,3 +1427,207 @@ lazy val lmIvy = (project in file("lm-ivy"))
Compile / generateContrabands / contrabandFormatsForType := DatatypeConfig.getFormats,
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat
)
def sbtCoursierSettings: Seq[Setting[_]] = Def.settings(
developers +=
Developer(
"alexarchambault",
"Alexandre Archambault",
"",
url("https://github.com/alexarchambault")
),
scalafixDependencies += "net.hamnaberg" %% "dataclass-scalafix" % dataclassScalafixVersion,
assemblyMergeStrategy := {
case PathList("lmcoursier", "internal", "shaded", "org", "fusesource", xs @ _*) => MergeStrategy.first
// case PathList("lmcoursier", "internal", "shaded", "package.class") => MergeStrategy.first
// case PathList("lmcoursier", "internal", "shaded", "package$.class") => MergeStrategy.first
case PathList("com", "github") => MergeStrategy.discard
case PathList("com", "jcraft") => MergeStrategy.discard
case PathList("com", "lmax") => MergeStrategy.discard
case PathList("com", "sun") => MergeStrategy.discard
case PathList("com", "swoval") => MergeStrategy.discard
case PathList("com", "typesafe") => MergeStrategy.discard
case PathList("gigahorse") => MergeStrategy.discard
case PathList("jline") => MergeStrategy.discard
case PathList("scala") => MergeStrategy.discard
case PathList("sjsonnew") => MergeStrategy.discard
case PathList("xsbti") => MergeStrategy.discard
case PathList("META-INF", "native", xs @ _*) => MergeStrategy.first
case x =>
val oldStrategy = (ThisBuild / assemblyMergeStrategy).value
oldStrategy(x)
}
)
def dataclassGen(data: Reference) = Def.taskDyn {
val root = (ThisBuild / baseDirectory).value.toURI.toString
val from = (data / Compile / sourceDirectory).value
val to = (Compile / sourceManaged).value
val outFrom = from.toURI.toString.stripSuffix("/").stripPrefix(root)
val outTo = to.toURI.toString.stripSuffix("/").stripPrefix(root)
(data / Compile / compile).value
Def.task {
(data / Compile / scalafix)
.toTask(s" --rules GenerateDataClass --out-from=$outFrom --out-to=$outTo")
.value
(to ** "*.scala").get
}
}
lazy val preTest = taskKey[Unit]("prep steps before tests")
lazy val definitions = project
.in(file("modules/definitions"))
.disablePlugins(MimaPlugin)
.settings(
scalaVersion := scala3,
crossScalaVersions := Seq(scala212, scala213, scala3),
libraryDependencies ++= Seq(
coursierDep,
"net.hamnaberg" %% "dataclass-annotation" % dataclassScalafixVersion % Provided,
lmIvy.value % Provided,
),
conflictWarning := ConflictWarning.disable,
dontPublish,
)
lazy val lmCoursier = project
.in(file("modules/lm-coursier"))
.settings(
shared,
crossScalaVersions := Seq(scala212, scala213, scala3),
Mima.settings,
Mima.lmCoursierFilters,
libraryDependencies ++= Seq(
coursierDep,
coursierSbtMavenRepoDep,
"io.get-coursier.jniutils" % "windows-jni-utils-lmcoursier" % jniUtilsVersion,
"net.hamnaberg" %% "dataclass-annotation" % dataclassScalafixVersion % Provided,
// We depend on librarymanagement-ivy rather than just
// librarymanagement-core to handle the ModuleDescriptor passed
// to DependencyResolutionInterface.update, which is an
// IvySbt#Module (seems DependencyResolutionInterface.moduleDescriptor is ignored).
lmIvy.value,
"org.scalatest" %% "scalatest" % "3.2.19" % Test
),
excludeDependencies ++= coursierExcludedDependencies,
Test / exportedProducts := {
(Test / preTest).value
(Test / exportedProducts).value
},
Test / preTest := {
(customProtocolForTest212 / publishLocal).value
(customProtocolForTest213 / publishLocal).value
(customProtocolJavaForTest / publishLocal).value
},
Compile / sourceGenerators += dataclassGen(definitions).taskValue,
)
lazy val lmCoursierShadedPublishing = project
.in(file("modules/lm-coursier/target/shaded-publishing-module"))
.settings(
name := "librarymanagement-coursier",
crossScalaVersions := Seq(scala212, scala213, scala3),
Compile / packageBin := (lmCoursierShaded / assembly).value,
)
lazy val lmCoursierShaded = project
.in(file("modules/lm-coursier/target/shaded-module"))
.settings(
shared,
crossScalaVersions := Seq(scala212, scala213, scala3),
Mima.settings,
Mima.lmCoursierFilters,
Mima.lmCoursierShadedFilters,
Compile / sources := (lmCoursier / Compile / sources).value,
// shadedModules ++= Set(
// "io.get-coursier" %% "coursier",
// "io.get-coursier" %% "coursier-sbt-maven-repository",
// "io.get-coursier.jniutils" % "windows-jni-utils-lmcoursier"
// ),
// validNamespaces += "lmcoursier",
// validEntries ++= Set(
// // FIXME Ideally, we should just strip those from the resulting JAR…
// "README", // from google-collections via plexus-archiver (see below)
// // from plexus-util via plexus-archiver (see below)
// "licenses/extreme.indiana.edu.license.TXT",
// "licenses/javolution.license.TXT",
// "licenses/thoughtworks.TXT",
// "licenses/",
// ),
assemblyShadeRules := {
val toShade = Seq(
"coursier",
"org.fusesource",
"macrocompat",
"io.github.alexarchambault.windowsansi",
"concurrentrefhashmap",
"com.github.ghik",
// pulled by the plexus-archiver stuff that coursier-cache
// depends on for now… can hopefully be removed in the future
"com.google.common",
"com.jcraft",
"com.lmax",
"org.apache.commons",
"org.apache.xbean",
"org.codehaus",
"org.iq80",
"org.tukaani",
"com.github.plokhotnyuk.jsoniter_scala",
"scala.cli",
"com.github.luben.zstd",
"javax.inject" // hope shading this is fine… It's probably pulled via plexus-archiver, that sbt shouldn't use anyway…
)
for (ns <- toShade)
yield ShadeRule.rename(ns + ".**" -> s"lmcoursier.internal.shaded.$ns.@1").inAll
},
libraryDependencies ++= Seq(
coursierDep,
coursierSbtMavenRepoDep,
"io.get-coursier.jniutils" % "windows-jni-utils-lmcoursier" % jniUtilsVersion,
"net.hamnaberg" %% "dataclass-annotation" % dataclassScalafixVersion % Provided,
lmIvy.value % Provided,
"org.scalatest" %% "scalatest" % "3.2.19" % Test,
),
excludeDependencies ++= excludedDependencies,
conflictWarning := ConflictWarning.disable,
dontPublish,
)
lazy val customProtocolForTest212 = project
.in(file("modules/custom-protocol-for-test-2-12"))
.settings(
sourceDirectory := file("modules/custom-protocol-for-test/src").toPath.toAbsolutePath.toFile,
scalaVersion := scala212,
organization := "org.example",
moduleName := "customprotocol-handler",
version := "0.1.0",
dontPublish
)
lazy val customProtocolForTest213 = project
.in(file("modules/custom-protocol-for-test-2-13"))
.settings(
sourceDirectory := file("modules/custom-protocol-for-test/src").toPath.toAbsolutePath.toFile,
scalaVersion := scala213,
organization := "org.example",
moduleName := "customprotocol-handler",
version := "0.1.0",
dontPublish
)
lazy val customProtocolJavaForTest = project
.in(file("modules/custom-protocol-java-for-test"))
.settings(
crossPaths := false,
organization := "org.example",
moduleName := "customprotocoljava-handler",
version := "0.1.0",
dontPublish
)
lazy val dontPublish = Seq(
publish := {},
publish / skip := true,
)

1
metadata Submodule

@ -0,0 +1 @@
Subproject commit 95874ca5bd90277c302f5a4d5c9b8119d91730af

View File

@ -0,0 +1,10 @@
package coursier.cache.protocol
import java.net.{URL, URLConnection, URLStreamHandler, URLStreamHandlerFactory}
class CustomprotocolHandler extends URLStreamHandlerFactory {
def createURLStreamHandler(protocol: String): URLStreamHandler = new URLStreamHandler {
protected def openConnection(url: URL): URLConnection =
new URL("https://repo1.maven.org/maven2" + url.getPath()).openConnection()
}
}

View File

@ -0,0 +1,18 @@
package coursier.cache.protocol;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.io.IOException;
public class CustomprotocoljavaHandler implements URLStreamHandlerFactory {
public URLStreamHandler createURLStreamHandler(String protocol) {
return new URLStreamHandler() {
protected URLConnection openConnection(URL url) throws IOException {
return new URL("https://repo1.maven.org/maven2" + url.getPath()).openConnection();
}
};
}
}

View File

@ -0,0 +1,64 @@
package lmcoursier
import java.io.File
import dataclass.{data, since}
import coursier.cache.CacheDefaults
import coursier.params.rule.{Rule, RuleResolution}
import lmcoursier.credentials.Credentials
import lmcoursier.definitions.{Authentication, CacheLogger, CachePolicy, FromCoursier, Module, ModuleMatchers, Project, Reconciliation, Strict}
import sbt.librarymanagement.{CrossVersion, InclExclRule, ModuleDescriptorConfiguration, ModuleID, ModuleInfo, Resolver, UpdateConfiguration}
import xsbti.Logger
import scala.concurrent.duration.{Duration, FiniteDuration}
import java.net.URL
import java.net.URLClassLoader
@data class CoursierConfiguration(
log: Option[Logger] = None,
resolvers: Vector[Resolver] = Resolver.defaults,
parallelDownloads: Int = 6,
maxIterations: Int = 100,
sbtScalaOrganization: Option[String] = None,
sbtScalaVersion: Option[String] = None,
sbtScalaJars: Vector[File] = Vector.empty,
interProjectDependencies: Vector[Project] = Vector.empty,
excludeDependencies: Vector[(String, String)] = Vector.empty,
fallbackDependencies: Vector[FallbackDependency] = Vector.empty,
autoScalaLibrary: Boolean = true,
hasClassifiers: Boolean = false,
classifiers: Vector[String] = Vector.empty,
mavenProfiles: Vector[String] = Vector.empty,
scalaOrganization: Option[String] = None,
scalaVersion: Option[String] = None,
authenticationByRepositoryId: Vector[(String, Authentication)] = Vector.empty,
credentials: Seq[Credentials] = Vector.empty,
logger: Option[CacheLogger] = None,
cache: Option[File] = None,
@since
ivyHome: Option[File] = None,
@since
followHttpToHttpsRedirections: Option[Boolean] = None,
@since
strict: Option[Strict] = None,
extraProjects: Vector[Project] = Vector.empty,
forceVersions: Vector[(Module, String)] = Vector.empty,
@since
reconciliation: Vector[(ModuleMatchers, Reconciliation)] = Vector.empty,
@since
classpathOrder: Boolean = true,
@since
verbosityLevel: Int = 0,
ttl: Option[Duration] = CacheDefaults.ttl,
checksums: Vector[Option[String]] = CacheDefaults.checksums.toVector,
cachePolicies: Vector[CachePolicy] = CacheDefaults.cachePolicies.toVector.map(FromCoursier.cachePolicy),
@since
missingOk: Boolean = false,
@since
sbtClassifiers: Boolean = false,
@since
providedInCompile: Boolean = false, // unused, kept for binary compatibility
@since
protocolHandlerDependencies: Seq[ModuleID] = Vector.empty,
retry: Option[(FiniteDuration, Int)] = None,
sameVersions: Seq[Set[InclExclRule]] = Nil,
)

View File

@ -0,0 +1,13 @@
package lmcoursier
import java.net.URL
import dataclass.data
import lmcoursier.definitions.Module
//FIXME use URI instead of URL
@data class FallbackDependency(
module: Module,
version: String,
url: URL,
changing: Boolean
)

View File

@ -0,0 +1 @@
../../../../../../lm-coursier/src/main/scala/lmcoursier/credentials/Credentials.scala

View File

@ -0,0 +1,20 @@
package lmcoursier.credentials
import dataclass._
@data class DirectCredentials(
host: String = "",
username: String = "",
password: String = "",
@since("1.0")
realm: Option[String] = None,
@since("1.1")
optional: Boolean = true,
@since("1.2")
matchHost: Boolean = false,
@since("1.3")
httpsOnly: Boolean = true
) extends Credentials {
override def toString(): String = s"DirectCredentials(host=$host, username=$username)"
}

View File

@ -0,0 +1,8 @@
package lmcoursier.credentials
import dataclass.data
@data class FileCredentials(
path: String,
optional: Boolean = true
) extends Credentials

View File

@ -0,0 +1,8 @@
package lmcoursier.definitions
import dataclass.data
@data class Attributes(
`type`: Type,
classifier: Classifier
)

View File

@ -0,0 +1,19 @@
package lmcoursier.definitions
import dataclass._
@data class Authentication(
user: String,
password: String,
optional: Boolean = false,
realmOpt: Option[String] = None,
@since("1.0")
headers: Seq[(String,String)] = Nil,
@since("1.1")
httpsOnly: Boolean = true,
@since("1.2")
passOnRedirect: Boolean = false
) {
override def toString(): String =
s"Authentication(user=$user)"
}

View File

@ -0,0 +1 @@
../../../../../../lm-coursier/src/main/scala/lmcoursier/definitions/CacheLogger.scala

View File

@ -0,0 +1 @@
../../../../../../lm-coursier/src/main/scala/lmcoursier/definitions/CachePolicy.scala

View File

@ -0,0 +1,12 @@
package lmcoursier.definitions
import dataclass.data
@data class DateTime(
year: Int,
month: Int,
day: Int,
hour: Int,
minute: Int,
second: Int
)

View File

@ -0,0 +1 @@
../../../../../../lm-coursier/src/main/scala/lmcoursier/definitions/Definitions.scala

View File

@ -0,0 +1,13 @@
package lmcoursier.definitions
import dataclass.data
@data class Dependency(
module: Module,
version: String,
configuration: Configuration,
exclusions: Set[(Organization, ModuleName)],
publication: Publication,
optional: Boolean,
transitive: Boolean
)

View File

@ -0,0 +1,9 @@
package lmcoursier.definitions
import dataclass.data
@data class Developer(
id: String,
name: String,
url: String
)

View File

@ -0,0 +1 @@
../../../../../../lm-coursier/src/main/scala/lmcoursier/definitions/FromCoursier.scala

View File

@ -0,0 +1,11 @@
package lmcoursier.definitions
import dataclass.data
@data class Info(
description: String,
homePage: String,
licenses: Seq[(String, Option[String])],
developers: Seq[Developer],
publication: Option[DateTime]
)

View File

@ -0,0 +1,8 @@
package lmcoursier.definitions
import dataclass.data
@data class Module(
organization: Organization,
name: ModuleName,
attributes: Map[String, String]
)

View File

@ -0,0 +1,13 @@
package lmcoursier.definitions
import dataclass.data
/**
* @param exclude Use "*" in either organization or name to match any.
* @param include Use "*" in either organization or name to match any.
*/
@data class ModuleMatchers(
exclude: Set[Module],
include: Set[Module],
includeByDefault: Boolean = true
)

View File

@ -0,0 +1,14 @@
package lmcoursier.definitions
import dataclass.data
@data class Project(
module: Module,
version: String,
dependencies: Seq[(Configuration, Dependency)],
configurations: Map[Configuration, Seq[Configuration]],
properties: Seq[(String, String)],
packagingOpt: Option[Type],
publications: Seq[(Configuration, Publication)],
info: Info
)

View File

@ -0,0 +1,10 @@
package lmcoursier.definitions
import dataclass.data
@data class Publication(
name: String,
`type`: Type,
ext: Extension,
classifier: Classifier
)

View File

@ -0,0 +1 @@
../../../../../../lm-coursier/src/main/scala/lmcoursier/definitions/Reconciliation.scala

View File

@ -0,0 +1,12 @@
package lmcoursier.definitions
import dataclass._
@data class Strict(
include: Set[(String, String)] = Set(("*", "*")),
exclude: Set[(String, String)] = Set.empty,
ignoreIfForcedVersion: Boolean = true,
@since
includeByDefault: Boolean = false,
semVer: Boolean = false
)

View File

@ -0,0 +1,374 @@
package lmcoursier
import java.io.File
import java.net.{URL, URLClassLoader, URLConnection, MalformedURLException}
import coursier.{Organization, Resolution, organizationString}
import coursier.core.{Classifier, Configuration}
import coursier.cache.{CacheDefaults, CachePolicy}
import coursier.util.Artifact
import coursier.internal.Typelevel
import lmcoursier.definitions.ToCoursier
import lmcoursier.internal.{ArtifactsParams, ArtifactsRun, CoursierModuleDescriptor, InterProjectRepository, ResolutionParams, ResolutionRun, Resolvers, SbtBootJars, UpdateParams, UpdateRun}
import lmcoursier.syntax._
import sbt.internal.librarymanagement.IvySbt
import sbt.librarymanagement._
import sbt.util.Logger
import coursier.core.Dependency
import coursier.core.Publication
import scala.util.{Try, Failure}
class CoursierDependencyResolution(
conf: CoursierConfiguration,
protocolHandlerConfiguration: Option[CoursierConfiguration],
bootstrappingProtocolHandler: Boolean
) extends DependencyResolutionInterface {
def this(conf: CoursierConfiguration) =
this(
conf,
protocolHandlerConfiguration = None,
bootstrappingProtocolHandler = true
)
private var protocolHandlerClassLoader: Option[ClassLoader] = None
private val protocolHandlerClassLoaderLock = new Object
private def fetchProtocolHandlerClassLoader(
configuration: UpdateConfiguration,
uwconfig: UnresolvedWarningConfiguration,
log: Logger
): ClassLoader = {
val conf0 = protocolHandlerConfiguration.getOrElse(conf)
def isUnknownProtocol(rawURL: String): Boolean = {
Try(new URL(rawURL)) match {
case Failure(ex) if ex.getMessage.startsWith("unknown protocol: ") => true
case _ => false
}
}
val confWithoutUnknownProtocol =
conf0.withResolvers(
conf0.resolvers.filter {
case maven: MavenRepository =>
!isUnknownProtocol(maven.root)
case _ =>
true
}
)
val resolution = new CoursierDependencyResolution(
conf = confWithoutUnknownProtocol,
protocolHandlerConfiguration = None,
bootstrappingProtocolHandler = false
)
val fakeModule =
ModuleDescriptorConfiguration(
ModuleID("lmcoursier", "lmcoursier", "0.1.0"),
ModuleInfo("protocol-handler")
)
.withDependencies(conf0.protocolHandlerDependencies.toVector)
val reportOrUnresolved = resolution.update(moduleDescriptor(fakeModule), configuration, uwconfig, log)
val report = reportOrUnresolved match {
case Right(report0) =>
report0
case Left(unresolvedWarning) =>
import sbt.util.ShowLines._
unresolvedWarning.lines.foreach(log.warn(_))
throw unresolvedWarning.resolveException
}
val jars =
for {
reportConfiguration <- report.configurations.filter(_.configuration.name == "runtime")
module <- reportConfiguration.modules
(_, jar) <- module.artifacts
} yield jar
new URLClassLoader(jars.map(_.toURI().toURL()).toArray)
}
/*
* Based on earlier implementations by @leonardehrenfried (https://github.com/sbt/librarymanagement/pull/190)
* and @andreaTP (https://github.com/sbt/librarymanagement/pull/270), then adapted to the code from the former
* sbt-coursier, that was moved to this module.
*/
def moduleDescriptor(moduleSetting: ModuleDescriptorConfiguration): ModuleDescriptor =
CoursierModuleDescriptor(moduleSetting, conf)
def update(
module: ModuleDescriptor,
configuration: UpdateConfiguration,
uwconfig: UnresolvedWarningConfiguration,
log: Logger
): Either[UnresolvedWarning, UpdateReport] = {
if (bootstrappingProtocolHandler && protocolHandlerClassLoader.isEmpty)
protocolHandlerClassLoaderLock.synchronized {
if (bootstrappingProtocolHandler && protocolHandlerClassLoader.isEmpty) {
val classLoader = fetchProtocolHandlerClassLoader(configuration, uwconfig, log)
protocolHandlerClassLoader = Some(classLoader)
}
}
val conf = this.conf.withUpdateConfiguration(configuration)
// TODO Take stuff in configuration into account? uwconfig too?
val module0 = module match {
case c: CoursierModuleDescriptor =>
// seems not to happen, not sure what DependencyResolutionInterface.moduleDescriptor is for
c.descriptor
case i: IvySbt#Module =>
i.moduleSettings match {
case d: ModuleDescriptorConfiguration => d
case other => sys.error(s"unrecognized module settings: $other")
}
case _ =>
sys.error(s"unrecognized ModuleDescriptor type: $module")
}
val so = conf.scalaOrganization.map(Organization(_))
.orElse(module0.scalaModuleInfo.map(m => Organization(m.scalaOrganization)))
.getOrElse(Organization("org.scala-lang"))
val sv = conf.scalaVersion
.orElse(module0.scalaModuleInfo.map(_.scalaFullVersion))
// FIXME Manage to do stuff below without a scala version?
.getOrElse(scala.util.Properties.versionNumberString)
val sbv = module0.scalaModuleInfo.map(_.scalaBinaryVersion).getOrElse {
sv.split('.').take(2).mkString(".")
}
val projectPlatform = module0.scalaModuleInfo.flatMap(_.platform)
val (mod, ver) = FromSbt.moduleVersion(module0.module, sv, sbv, optionalCrossVer = true, projectPlatform = projectPlatform)
val interProjectDependencies = {
val needed = conf.interProjectDependencies.exists { p =>
p.module == mod && p.version == ver
}
if (needed)
conf.interProjectDependencies.map(ToCoursier.project)
else
Vector.empty[coursier.core.Project]
}
val extraProjects = conf.extraProjects.map(ToCoursier.project)
val verbosityLevel = conf.verbosityLevel
val ttl = conf.ttl
val loggerOpt = conf.logger.map(ToCoursier.cacheLogger)
val cache = conf.cache.getOrElse(CacheDefaults.location)
val cachePolicies = conf.cachePolicies.map(ToCoursier.cachePolicy)
val checksums = conf.checksums
val projectName = module0.module.name
val ivyProperties = ResolutionParams.defaultIvyProperties(conf.ivyHome)
val classifiers =
if (conf.hasClassifiers)
Some(conf.classifiers.map(Classifier(_)))
else
None
val authenticationByRepositoryId = conf.authenticationByRepositoryId.toMap
val mainRepositories = conf
.resolvers
.flatMap { resolver =>
Resolvers.repository(
resolver,
ivyProperties,
log,
authenticationByRepositoryId.get(resolver.name).map(ToCoursier.authentication),
protocolHandlerClassLoader.toSeq,
)
}
val interProjectRepo = InterProjectRepository(interProjectDependencies)
val extraProjectsRepo = InterProjectRepository(extraProjects)
val dependencies = module0
.dependencies
.flatMap { d =>
// crossVersion sometimes already taken into account (when called via the update task), sometimes not
// (e.g. sbt-dotty 0.13.0-RC1)
FromSbt.dependencies(d, sv, sbv, optionalCrossVer = true)
}
.map {
case (config, dep) =>
(ToCoursier.configuration(config), ToCoursier.dependency(dep))
}
val orderedConfigs = Inputs.orderedConfigurations(Inputs.configExtendsSeq(module0.configurations))
.map {
case (config, extends0) =>
(ToCoursier.configuration(config), extends0.map(ToCoursier.configuration))
}
val typelevel = so == Typelevel.typelevelOrg
val cache0 = coursier.cache.FileCache()
.withLocation(cache)
.withCachePolicies(cachePolicies)
.withTtl(ttl)
.withChecksums(checksums)
.withCredentials(conf.credentials.map(ToCoursier.credentials))
.withFollowHttpToHttpsRedirections(conf.followHttpToHttpsRedirections.getOrElse(true))
val excludeDependencies = conf
.excludeDependencies
.map {
case (strOrg, strName) =>
(coursier.Organization(strOrg), coursier.ModuleName(strName))
}
.toSet
val resolutionParams = ResolutionParams(
dependencies = dependencies,
fallbackDependencies = conf.fallbackDependencies,
orderedConfigs = orderedConfigs,
autoScalaLibOpt = if (conf.autoScalaLibrary) Some((so, sv)) else None,
mainRepositories = mainRepositories,
parentProjectCache = Map.empty,
interProjectDependencies = interProjectDependencies,
internalRepositories = Seq(interProjectRepo, extraProjectsRepo),
sbtClassifiers = conf.sbtClassifiers,
projectName = projectName,
loggerOpt = loggerOpt,
cache = cache0,
parallel = conf.parallelDownloads,
params = coursier.params.ResolutionParams()
.withMaxIterations(conf.maxIterations)
.withProfiles(conf.mavenProfiles.toSet)
.withForceVersion(conf.forceVersions.map { case (k, v) => (ToCoursier.module(k), v) }.toMap)
.withTypelevel(typelevel)
.withReconciliation(ToCoursier.reconciliation(conf.reconciliation))
.withExclusions(excludeDependencies)
.withRules(ToCoursier.sameVersions(conf.sameVersions)),
strictOpt = conf.strict.map(ToCoursier.strict),
missingOk = conf.missingOk,
retry = conf.retry.getOrElse(ResolutionParams.defaultRetry),
)
def artifactsParams(resolutions: Map[Configuration, Resolution]): ArtifactsParams =
ArtifactsParams(
classifiers = classifiers,
resolutions = resolutions.values.toSeq.distinct,
includeSignatures = false,
loggerOpt = loggerOpt,
projectName = projectName,
sbtClassifiers = conf.sbtClassifiers,
cache = cache0,
parallel = conf.parallelDownloads,
classpathOrder = conf.classpathOrder,
missingOk = conf.missingOk
)
val sbtBootJarOverrides = SbtBootJars(
conf.sbtScalaOrganization.fold(Organization("org.scala-lang"))(Organization(_)),
conf.sbtScalaVersion.getOrElse(sv),
conf.sbtScalaJars
)
val configs = Inputs.coursierConfigurationsMap(module0.configurations).map {
case (k, l) =>
ToCoursier.configuration(k) -> l.map(ToCoursier.configuration)
}
def updateParams(
resolutions: Map[Configuration, Resolution],
artifacts: Seq[(Dependency, Publication, Artifact, Option[File])]
) =
UpdateParams(
thisModule = (ToCoursier.module(mod), ver),
artifacts = artifacts.collect { case (d, p, a, Some(f)) => a -> f }.toMap,
fullArtifacts = Some(artifacts.map { case (d, p, a, f) => (d, p, a) -> f }.toMap),
classifiers = classifiers,
configs = configs,
dependencies = dependencies,
forceVersions = conf.forceVersions.map { case (m, v) => (ToCoursier.module(m), v) }.toMap,
interProjectDependencies = interProjectDependencies,
res = resolutions,
includeSignatures = false,
sbtBootJarOverrides = sbtBootJarOverrides,
classpathOrder = conf.classpathOrder,
missingOk = conf.missingOk,
classLoaders = protocolHandlerClassLoader.toSeq,
)
val e = for {
resolutions <- ResolutionRun.resolutions(resolutionParams, verbosityLevel, log)
artifactsParams0 = artifactsParams(resolutions)
artifacts <- ArtifactsRun(artifactsParams0, verbosityLevel, log)
} yield {
val updateParams0 = updateParams(resolutions, artifacts.fullDetailedArtifacts)
UpdateRun.update(updateParams0, verbosityLevel, log)
}
e.left.map(unresolvedWarningOrThrow(uwconfig, _))
}
private def unresolvedWarningOrThrow(
uwconfig: UnresolvedWarningConfiguration,
ex: coursier.error.CoursierError
): UnresolvedWarning = {
// TODO Take coursier.error.FetchError.DownloadingArtifacts into account
val downloadErrors = ex match {
case ex0: coursier.error.ResolutionError =>
ex0.errors.collect {
case err: coursier.error.ResolutionError.CantDownloadModule => err
}
case _ =>
Nil
}
val otherErrors = ex match {
case ex0: coursier.error.ResolutionError =>
ex0.errors.flatMap {
case _: coursier.error.ResolutionError.CantDownloadModule => None
case err => Some(err)
}
case _ =>
Seq(ex)
}
if (otherErrors.isEmpty) {
val r = new ResolveException(
downloadErrors.map(_.getMessage),
downloadErrors.map { err =>
ModuleID(err.module.organization.value, err.module.name.value, err.version)
.withExtraAttributes(err.module.attributes)
}
)
UnresolvedWarning(r, uwconfig)
} else
throw ex
}
}
object CoursierDependencyResolution {
def apply(configuration: CoursierConfiguration): DependencyResolution =
DependencyResolution(new CoursierDependencyResolution(configuration))
def apply(configuration: CoursierConfiguration,
protocolHandlerConfiguration: Option[CoursierConfiguration]): DependencyResolution =
DependencyResolution(
new CoursierDependencyResolution(
configuration,
protocolHandlerConfiguration,
bootstrappingProtocolHandler = true
)
)
def defaultCacheLocation: File =
CacheDefaults.location
}

View File

@ -0,0 +1,183 @@
package lmcoursier
import coursier.ivy.IvyXml.{mappings => ivyXmlMappings}
import lmcoursier.definitions.{Classifier, Configuration, Dependency, Extension, Info, Module, ModuleName, Organization, Project, Publication, Type}
import sbt.internal.librarymanagement.mavenint.SbtPomExtraProperties
import sbt.librarymanagement.{Configuration => _, MavenRepository => _, _}
object FromSbt {
private def sbtModuleIdName(
moduleId: ModuleID,
scalaVersion: => String,
scalaBinaryVersion: => String,
optionalCrossVer: Boolean = false,
projectPlatform: Option[String],
): String = {
val name0 = moduleId.name
val name1 =
moduleId.crossVersion match
case _: Disabled => name0
case _ => addPlatformSuffix(name0, moduleId.platformOpt, projectPlatform)
val updatedName = CrossVersion(moduleId.crossVersion, scalaVersion, scalaBinaryVersion)
.fold(name1)(_(name1))
if (!optionalCrossVer || updatedName.length <= name0.length)
updatedName
else {
val suffix = updatedName.substring(name0.length)
if (name0.endsWith(suffix))
name0
else
updatedName
}
}
private def addPlatformSuffix(name: String, platformOpt: Option[String], projectPlatform: Option[String]): String = {
def addSuffix(platformName: String): String =
platformName match {
case "" | "jvm" => name
case _ => s"${name}_$platformName"
}
(platformOpt, projectPlatform) match {
case (Some(p), None) => addSuffix(p)
case (_, Some(p)) => addSuffix(p)
case _ => name
}
}
private def attributes(attr: Map[String, String]): Map[String, String] =
attr.map { case (k, v) =>
k.stripPrefix("e:") -> v
}.filter { case (k, _) =>
!k.startsWith(SbtPomExtraProperties.POM_INFO_KEY_PREFIX)
}
def moduleVersion(
module: ModuleID,
scalaVersion: String,
scalaBinaryVersion: String,
optionalCrossVer: Boolean,
projectPlatform: Option[String],
): (Module, String) = {
val fullName = sbtModuleIdName(module, scalaVersion, scalaBinaryVersion, optionalCrossVer, projectPlatform)
val module0 = Module(Organization(module.organization), ModuleName(fullName), attributes(module.extraDependencyAttributes))
val version = module.revision
(module0, version)
}
def moduleVersion(
module: ModuleID,
scalaVersion: String,
scalaBinaryVersion: String
): (Module, String) =
moduleVersion(module, scalaVersion, scalaBinaryVersion, optionalCrossVer = false, projectPlatform = None)
def dependencies(
module: ModuleID,
scalaVersion: String,
scalaBinaryVersion: String,
optionalCrossVer: Boolean = false,
projectPlatform: Option[String] = None,
): Seq[(Configuration, Dependency)] = {
// TODO Warn about unsupported properties in `module`
val (module0, version) = moduleVersion(module, scalaVersion, scalaBinaryVersion, optionalCrossVer, projectPlatform)
val dep = Dependency(
module0,
version,
Configuration(""),
exclusions = module.exclusions.map { rule =>
// FIXME Other `rule` fields are ignored here
(Organization(rule.organization), ModuleName(rule.name))
}.toSet,
Publication("", Type(""), Extension(""), Classifier("")),
optional = false,
transitive = module.isTransitive
)
val mapping = module.configurations.getOrElse("compile")
val allMappings = ivyXmlMappings(mapping).map {
case (from, to) =>
(Configuration(from.value), Configuration(to.value))
}
val publications =
if (module.explicitArtifacts.isEmpty)
Seq(Publication("", Type(""), Extension(""), Classifier("")))
else
module
.explicitArtifacts
.map { a =>
Publication(
name = a.name,
`type` = Type(a.`type`),
ext = Extension(a.extension),
classifier = a.classifier.fold(Classifier(""))(Classifier(_))
)
}
for {
(from, to) <- allMappings.distinct
pub <- publications.distinct
} yield {
val dep0 = dep
.withConfiguration(to)
.withPublication(pub)
from -> dep0
}
}
def fallbackDependencies(
allDependencies: Seq[ModuleID],
scalaVersion: String,
scalaBinaryVersion: String
): Seq[FallbackDependency] =
for {
module <- allDependencies
artifact <- module.explicitArtifacts
url <- artifact.url.toSeq
} yield {
val (module0, version) = moduleVersion(module, scalaVersion, scalaBinaryVersion)
FallbackDependency(module0, version, url.toURL, module.isChanging)
}
def project(
projectID: ModuleID,
allDependencies: Seq[ModuleID],
ivyConfigurations: Map[Configuration, Seq[Configuration]],
scalaVersion: String,
scalaBinaryVersion: String,
projectPlatform: Option[String],
): Project = {
val deps = allDependencies.flatMap(dependencies(_, scalaVersion, scalaBinaryVersion, projectPlatform = projectPlatform))
val prefix = "e:" + SbtPomExtraProperties.POM_INFO_KEY_PREFIX
val properties = projectID
.extraAttributes
.filterKeys(_.startsWith(prefix))
.toSeq
.map { case (k, v) => (k.stripPrefix("e:"), v) }
.sortBy(_._1)
Project(
Module(
Organization(projectID.organization),
ModuleName(sbtModuleIdName(projectID, scalaVersion, scalaBinaryVersion, projectPlatform = projectPlatform)),
attributes(projectID.extraDependencyAttributes)
),
projectID.revision,
deps,
ivyConfigurations,
properties,
None,
Nil,
Info("", "", Nil, Nil, None)
)
}
}

View File

@ -0,0 +1,157 @@
package lmcoursier
import coursier.ivy.IvyXml.{mappings => initialIvyXmlMappings}
import lmcoursier.definitions.{Configuration, Module, ModuleName, Organization, ToCoursier}
import sbt.librarymanagement.{CrossVersion, InclExclRule, ModuleID}
import sbt.util.Logger
import scala.collection.mutable
object Inputs {
def ivyXmlMappings(mapping: String): Seq[(Configuration, Configuration)] =
initialIvyXmlMappings(mapping).map {
case (from, to) =>
Configuration(from.value) -> Configuration(to.value)
}
def configExtendsSeq(configurations: Seq[sbt.librarymanagement.Configuration]): Seq[(Configuration, Seq[Configuration])] =
configurations
.map(cfg => Configuration(cfg.name) -> cfg.extendsConfigs.map(c => Configuration(c.name)))
@deprecated("Now unused internally, to be removed in the future", "2.0.0-RC6-5")
def configExtends(configurations: Seq[sbt.librarymanagement.Configuration]): Map[Configuration, Seq[Configuration]] =
configurations
.map(cfg => Configuration(cfg.name) -> cfg.extendsConfigs.map(c => Configuration(c.name)))
.toMap
@deprecated("Use coursierConfigurationsMap instead", "2.0.0-RC6-5")
def coursierConfigurations(configurations: Seq[sbt.librarymanagement.Configuration], shadedConfigOpt: Option[String] = None): Map[Configuration, Set[Configuration]] =
coursierConfigurationsMap(configurations)
def coursierConfigurationsMap(configurations: Seq[sbt.librarymanagement.Configuration]): Map[Configuration, Set[Configuration]] = {
val configs0 = configExtendsSeq(configurations).toMap
def allExtends(c: Configuration) = {
// possibly bad complexity
def helper(current: Set[Configuration]): Set[Configuration] = {
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)
}
}
def orderedConfigurations(
configurations: Seq[(Configuration, Seq[Configuration])]
): Seq[(Configuration, Seq[Configuration])] = {
val map = configurations.toMap
def helper(done: Set[Configuration], toAdd: List[Configuration]): Stream[(Configuration, Seq[Configuration])] =
toAdd match {
case Nil => Stream.empty
case config :: rest =>
val extends0 = map.getOrElse(config, Nil)
val missingExtends = extends0.filterNot(done)
if (missingExtends.isEmpty)
(config, extends0) #:: helper(done + config, rest)
else
helper(done, missingExtends.toList ::: toAdd)
}
helper(Set.empty, configurations.map(_._1).toList)
.toVector
}
@deprecated("Now unused internally, to be removed in the future", "2.0.0-RC6-5")
def ivyGraphs(configurations: Map[Configuration, Seq[Configuration]]): Seq[Set[Configuration]] = {
// probably bad complexity, but that shouldn't matter given the size of the graphs involved...
final class Wrapper(val set: mutable.HashSet[Configuration]) {
def ++=(other: Wrapper): this.type = {
set ++= other.set
this
}
}
val sets =
new mutable.HashMap[Configuration, Wrapper] ++= configurations.map {
case (k, l) =>
val s = new mutable.HashSet[Configuration]
s ++= l
s += k
k -> new Wrapper(s)
}
for (k <- configurations.keys) {
val s = sets(k)
var foundNew = true
while (foundNew) {
foundNew = false
for (other <- s.set.toVector) {
val otherS = sets(other)
if (!otherS.eq(s)) {
s ++= otherS
sets += other -> s
foundNew = true
}
}
}
}
sets.values.toVector.distinct.map(_.set.toSet)
}
def exclusionsSeq(
excludeDeps: Seq[InclExclRule],
sv: String,
sbv: String,
log: Logger
): Seq[(Organization, ModuleName)] = {
var anyNonSupportedExclusionRule = false
val res = excludeDeps
.flatMap { rule =>
if (rule.artifact != "*" || rule.configurations.nonEmpty) {
log.warn(s"Unsupported exclusion rule $rule")
anyNonSupportedExclusionRule = true
Nil
} else {
val name = CrossVersion(rule.crossVersion, sv, sbv)
.fold(rule.name)(_(rule.name))
Seq((Organization(rule.organization), ModuleName(name)))
}
}
if (anyNonSupportedExclusionRule)
log.warn("Only supported exclusion rule fields: organization, name")
res
}
def exclusions(
excludeDeps: Seq[InclExclRule],
sv: String,
sbv: String,
log: Logger
): Set[(Organization, ModuleName)] =
exclusionsSeq(excludeDeps, sv, sbv, log).toSet
def forceVersions(depOverrides: Seq[ModuleID], sv: String, sbv: String): Seq[(Module, String)] =
depOverrides.map(FromSbt.moduleVersion(_, sv, sbv))
}

View File

@ -0,0 +1,128 @@
package lmcoursier
import lmcoursier.definitions.{Configuration, Project}
import scala.xml.{Node, PrefixedAttribute}
object IvyXml {
@deprecated("Use the override accepting 3 arguments", "2.0.0-RC6-6")
def apply(
currentProject: Project,
exclusions: Seq[(String, String)]
): String =
apply(currentProject, exclusions, Nil)
def apply(
currentProject: Project,
exclusions: Seq[(String, String)],
overrides: Seq[(String, String, String)]
): String = {
// Important: width = Int.MaxValue, so that no tag gets truncated.
// In particular, that prevents things like <foo /> to be split to
// <foo>
// </foo>
// by the pretty-printer.
// See https://github.com/sbt/sbt/issues/3412.
val printer = new scala.xml.PrettyPrinter(Int.MaxValue, 2)
"""<?xml version="1.0" encoding="UTF-8"?>""" + '\n' +
printer.format(content(currentProject, exclusions, overrides))
}
// These are required for publish to be fine, later on.
private def content(
project: Project,
exclusions: Seq[(String, String)],
overrides: Seq[(String, String, String)]
): Node = {
val props = project.module.attributes.toSeq ++ project.properties
val infoAttrs = props.foldLeft[xml.MetaData](xml.Null) {
case (acc, (k, v)) =>
new PrefixedAttribute("e", k, v, acc)
}
val licenseElems = project.info.licenses.map {
case (name, urlOpt) =>
val n = <license name={name} />
urlOpt.fold(n) { url =>
n % <x url={url} />.attributes
}
}
val infoElem = {
<info
organisation={project.module.organization.value}
module={project.module.name.value}
revision={project.version}
>
{licenseElems}
<description>{project.info.description}</description>
</info>
} % infoAttrs
val confElems = project.configurations.toVector.collect {
case (name, extends0) =>
val n = <conf name={name.value} visibility="public" description="" />
if (extends0.nonEmpty)
n % <x extends={extends0.map(_.value).mkString(",")} />.attributes
else
n
}
val publications = project
.publications
.groupBy { case (_, p) => p }
.mapValues { _.map { case (cfg, _) => cfg } }
val publicationElems = publications.map {
case (pub, configs) =>
val n = <artifact name={pub.name} type={pub.`type`.value} ext={pub.ext.value} conf={configs.map(_.value).mkString(",")} />
if (pub.classifier.value.nonEmpty)
n % <x e:classifier={pub.classifier.value} />.attributes
else
n
}
val dependencyElems = project.dependencies.toVector.map {
case (conf, dep) =>
val excludes = dep.exclusions.toSeq.map {
case (org, name) =>
<exclude org={org.value} module={name.value} name="*" type="*" ext="*" conf="" matcher="exact"/>
}
val n = <dependency org={dep.module.organization.value} name={dep.module.name.value} rev={dep.version} conf={s"${conf.value}->${dep.configuration.value}"}>
{excludes}
</dependency>
val moduleAttrs = dep.module.attributes.foldLeft[xml.MetaData](xml.Null) {
case (acc, (k, v)) =>
new PrefixedAttribute("e", k, v, acc)
}
n % moduleAttrs
}
val excludeElems = exclusions.toVector.map {
case (org, name) =>
<exclude org={org} module={name} artifact="*" type="*" ext="*" matcher="exact"/>
}
val overrideElems = overrides.toVector.map {
case (org, name, ver) =>
<override org={org} module={name} rev={ver} matcher="exact"/>
}
<ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
{infoElem}
<configurations>{confElems}</configurations>
<publications>{publicationElems}</publications>
<dependencies>{dependencyElems}{excludeElems}{overrideElems}</dependencies>
</ivy-module>
}
}

View File

@ -0,0 +1,7 @@
package lmcoursier.credentials
import java.io.File
abstract class Credentials extends Serializable
object Credentials

View File

@ -0,0 +1,29 @@
package lmcoursier.definitions
abstract class CacheLogger {
def foundLocally(url: String): Unit = {}
def downloadingArtifact(url: String): Unit = {}
def downloadProgress(url: String, downloaded: Long): Unit = {}
def downloadedArtifact(url: String, success: Boolean): Unit = {}
def checkingUpdates(url: String, currentTimeOpt: Option[Long]): Unit = {}
def checkingUpdatesResult(url: String, currentTimeOpt: Option[Long], remoteTimeOpt: Option[Long]): Unit = {}
def downloadLength(url: String, totalLength: Long, alreadyDownloaded: Long, watching: Boolean): Unit = {}
def gettingLength(url: String): Unit = {}
def gettingLengthResult(url: String, length: Option[Long]): Unit = {}
def removedCorruptFile(url: String, reason: Option[String]): Unit = {}
// sizeHint: estimated # of artifacts to be downloaded (doesn't include side stuff like checksums)
def init(sizeHint: Option[Int] = None): Unit = {}
def stop(): Unit = {}
}
object CacheLogger {
def nop: CacheLogger =
new CacheLogger {}
}

View File

@ -0,0 +1,72 @@
package lmcoursier.definitions
sealed abstract class CachePolicy extends Serializable
object CachePolicy {
/* NOTE: the following comments are copied from coursier.cache.CachePolicy for the benefit of users within an IDE
that reads the javadocs. Please keep in sync from the original ADT.
*/
/** Only pick local files, possibly from the cache. Don't try to download anything. */
case object LocalOnly extends CachePolicy
/** Only pick local files, possibly from the cache. Don't return changing artifacts (whose last check is) older than TTL */
case object LocalOnlyIfValid extends CachePolicy
/**
* Only pick local files. If one of these local files corresponds to a changing artifact, check
* for updates, and download these if needed.
*
* If no local file is found, *don't* try download it. Updates are only checked for files already
* in cache.
*
* Follows the TTL parameter (assumes no update is needed if the last one is recent enough).
*/
case object LocalUpdateChanging extends CachePolicy
/**
* Only pick local files, check if any update is available for them, and download these if needed.
*
* If no local file is found, *don't* try download it. Updates are only checked for files already
* in cache.
*
* Follows the TTL parameter (assumes no update is needed if the last one is recent enough).
*
* Unlike `LocalUpdateChanging`, all found local files are checked for updates, not just the
* changing ones.
*/
case object LocalUpdate extends CachePolicy
/**
* Pick local files, and download the missing ones.
*
* For changing ones, check for updates, and download those if any.
*
* Follows the TTL parameter (assumes no update is needed if the last one is recent enough).
*/
case object UpdateChanging extends CachePolicy
/**
* Pick local files, download the missing ones, check for updates and download those if any.
*
* Follows the TTL parameter (assumes no update is needed if the last one is recent enough).
*
* Unlike `UpdateChanging`, all found local files are checked for updates, not just the changing
* ones.
*/
case object Update extends CachePolicy
/**
* Pick local files, download the missing ones.
*
* No updates are checked for files already downloaded.
*/
case object FetchMissing extends CachePolicy
/**
* (Re-)download all files.
*
* Erases files already in cache.
*/
case object ForceDownload extends CachePolicy
}

View File

@ -0,0 +1,13 @@
package lmcoursier.definitions
final case class Classifier(value: String) extends AnyVal
final case class Configuration(value: String) extends AnyVal
final case class Extension(value: String) extends AnyVal
final case class ModuleName(value: String) extends AnyVal
final case class Organization(value: String) extends AnyVal
final case class Type(value: String) extends AnyVal

View File

@ -0,0 +1,18 @@
package lmcoursier.definitions
// TODO Make private[lmcoursier]
// private[coursier]
object FromCoursier {
def cachePolicy(r: coursier.cache.CachePolicy): CachePolicy =
r match {
case coursier.cache.CachePolicy.LocalOnly => CachePolicy.LocalOnly
case coursier.cache.CachePolicy.LocalOnlyIfValid => CachePolicy.LocalOnlyIfValid
case coursier.cache.CachePolicy.LocalUpdateChanging => CachePolicy.LocalUpdateChanging
case coursier.cache.CachePolicy.LocalUpdate => CachePolicy.LocalUpdate
case coursier.cache.CachePolicy.UpdateChanging => CachePolicy.UpdateChanging
case coursier.cache.CachePolicy.Update => CachePolicy.Update
case coursier.cache.CachePolicy.FetchMissing => CachePolicy.FetchMissing
case coursier.cache.CachePolicy.ForceDownload => CachePolicy.ForceDownload
}
}

View File

@ -0,0 +1,19 @@
package lmcoursier.definitions
sealed abstract class Reconciliation extends Serializable
object Reconciliation {
case object Default extends Reconciliation
case object Relaxed extends Reconciliation
case object Strict extends Reconciliation
case object SemVer extends Reconciliation
def apply(input: String): Option[Reconciliation] =
input match {
case "default" => Some(Default)
case "relaxed" => Some(Relaxed)
case "strict" => Some(Strict)
case "semver" => Some(SemVer)
case _ => None
}
}

View File

@ -0,0 +1,208 @@
package lmcoursier.definitions
import lmcoursier.credentials.{Credentials, DirectCredentials, FileCredentials}
import sbt.librarymanagement.InclExclRule
// TODO Make private[lmcoursier]
// private[coursier]
object ToCoursier {
def configuration(configuration: Configuration): coursier.core.Configuration =
coursier.core.Configuration(configuration.value)
private def attributes(attributes: Attributes): coursier.core.Attributes =
coursier.core.Attributes(
coursier.core.Type(attributes.`type`.value),
coursier.core.Classifier(attributes.classifier.value)
)
def publication(publication: Publication): coursier.core.Publication =
coursier.core.Publication(
publication.name,
coursier.core.Type(publication.`type`.value),
coursier.core.Extension(publication.ext.value),
coursier.core.Classifier(publication.classifier.value)
)
def authentication(authentication: Authentication): coursier.core.Authentication =
coursier.core.Authentication(authentication.user, authentication.password)
.withOptional(authentication.optional)
.withRealmOpt(authentication.realmOpt)
.withHttpHeaders(authentication.headers)
.withHttpsOnly(authentication.httpsOnly)
.withPassOnRedirect(authentication.passOnRedirect)
def module(mod: Module): coursier.core.Module =
module(mod.organization.value, mod.name.value, mod.attributes)
def module(organization: String, name: String, attributes: Map[String, String] = Map.empty): coursier.core.Module =
coursier.core.Module(
coursier.core.Organization(organization),
coursier.core.ModuleName(name),
attributes
)
def moduleMatchers(matcher: ModuleMatchers): coursier.util.ModuleMatchers =
coursier.util.ModuleMatchers(
exclude = matcher.exclude map { x =>
coursier.util.ModuleMatcher(module(x))
},
include = matcher.include map { x =>
coursier.util.ModuleMatcher(module(x))
},
includeByDefault = matcher.includeByDefault
)
def reconciliation(r: Reconciliation): coursier.core.Reconciliation =
r match {
case Reconciliation.Default => coursier.core.Reconciliation.Default
case Reconciliation.Relaxed => coursier.core.Reconciliation.Relaxed
case Reconciliation.Strict => coursier.core.Reconciliation.Strict
case Reconciliation.SemVer => coursier.core.Reconciliation.SemVer
}
def reconciliation(rs: Vector[(ModuleMatchers, Reconciliation)]):
Vector[(coursier.util.ModuleMatchers, coursier.core.Reconciliation)] =
rs map { case (m, r) => (moduleMatchers(m), reconciliation(r)) }
def sameVersions(sv: Seq[Set[InclExclRule]]):
Seq[(coursier.params.rule.SameVersion, coursier.params.rule.RuleResolution)] =
sv.map { libs =>
val matchers = libs.map(rule => coursier.util.ModuleMatcher(module(rule.organization, rule.name)))
coursier.params.rule.SameVersion(matchers) -> coursier.params.rule.RuleResolution.TryResolve
}
def dependency(dependency: Dependency): coursier.core.Dependency =
coursier.core.Dependency(
module(dependency.module),
dependency.version,
configuration(dependency.configuration),
dependency.exclusions.map {
case (org, name) =>
(coursier.core.Organization(org.value), coursier.core.ModuleName(name.value))
},
publication(dependency.publication),
dependency.optional,
dependency.transitive
)
def project(project: Project): coursier.core.Project =
coursier.core.Project(
module(project.module),
project.version,
project.dependencies.map {
case (conf, dep) =>
configuration(conf) -> dependency(dep)
},
project.configurations.map {
case (k, l) =>
configuration(k) -> l.map(configuration)
},
None,
Nil,
project.properties,
Nil,
None,
None,
project.packagingOpt.map(t => coursier.core.Type(t.value)),
relocated = false,
None,
project.publications.map {
case (conf, pub) =>
configuration(conf) -> publication(pub)
},
coursier.core.Info(
project.info.description,
project.info.homePage,
project.info.licenses,
project.info.developers.map { dev =>
coursier.core.Info.Developer(
dev.id,
dev.name,
dev.url
)
},
project.info.publication.map { dt =>
coursier.core.Versions.DateTime(
dt.year,
dt.month,
dt.day,
dt.hour,
dt.minute,
dt.second
)
},
None // TODO Add scm field in lmcoursier.definitions.Info?
)
)
def credentials(credentials: Credentials): coursier.credentials.Credentials =
credentials match {
case d: DirectCredentials =>
coursier.credentials.DirectCredentials()
.withHost(d.host)
.withUsername(d.username)
.withPassword(d.password)
.withRealm(d.realm)
.withOptional(d.optional)
.withMatchHost(d.matchHost)
.withHttpsOnly(d.httpsOnly)
case f: FileCredentials =>
coursier.credentials.FileCredentials(f.path)
.withOptional(f.optional)
}
def cacheLogger(logger: CacheLogger): coursier.cache.CacheLogger =
new coursier.cache.CacheLogger {
override def foundLocally(url: String): Unit =
logger.foundLocally(url)
override def downloadingArtifact(url: String): Unit =
logger.downloadingArtifact(url)
override def downloadProgress(url: String, downloaded: Long): Unit =
logger.downloadProgress(url, downloaded)
override def downloadedArtifact(url: String, success: Boolean): Unit =
logger.downloadedArtifact(url, success)
override def checkingUpdates(url: String, currentTimeOpt: Option[Long]): Unit =
logger.checkingUpdates(url, currentTimeOpt)
override def checkingUpdatesResult(url: String, currentTimeOpt: Option[Long], remoteTimeOpt: Option[Long]): Unit =
logger.checkingUpdatesResult(url, currentTimeOpt, remoteTimeOpt)
override def downloadLength(url: String, totalLength: Long, alreadyDownloaded: Long, watching: Boolean): Unit =
logger.downloadLength(url, totalLength, alreadyDownloaded, watching)
override def gettingLength(url: String): Unit =
logger.gettingLength(url)
override def gettingLengthResult(url: String, length: Option[Long]): Unit =
logger.gettingLengthResult(url, length)
override def removedCorruptFile(url: String, reason: Option[String]): Unit =
logger.removedCorruptFile(url, reason)
override def init(sizeHint: Option[Int] = None): Unit =
logger.init(sizeHint)
override def stop(): Unit =
logger.stop()
}
def strict(strict: Strict): coursier.params.rule.Strict =
coursier.params.rule.Strict()
.withInclude(strict.include.map {
case (o, n) =>
coursier.util.ModuleMatcher(coursier.Module(coursier.Organization(o), coursier.ModuleName(n)))
})
.withExclude(strict.exclude.map {
case (o, n) =>
coursier.util.ModuleMatcher(coursier.Module(coursier.Organization(o), coursier.ModuleName(n)))
})
.withIncludeByDefault(strict.includeByDefault)
.withIgnoreIfForcedVersion(strict.ignoreIfForcedVersion)
.withSemVer(strict.semVer)
def cachePolicy(r: CachePolicy): coursier.cache.CachePolicy =
r match {
case CachePolicy.LocalOnly => coursier.cache.CachePolicy.LocalOnly
case CachePolicy.LocalOnlyIfValid => coursier.cache.CachePolicy.LocalOnlyIfValid
case CachePolicy.LocalUpdateChanging => coursier.cache.CachePolicy.LocalUpdateChanging
case CachePolicy.LocalUpdate => coursier.cache.CachePolicy.LocalUpdate
case CachePolicy.UpdateChanging => coursier.cache.CachePolicy.UpdateChanging
case CachePolicy.Update => coursier.cache.CachePolicy.Update
case CachePolicy.FetchMissing => coursier.cache.CachePolicy.FetchMissing
case CachePolicy.ForceDownload => coursier.cache.CachePolicy.ForceDownload
}
}

View File

@ -0,0 +1,19 @@
package lmcoursier.internal
import coursier.cache.{CacheLogger, FileCache}
import coursier.core.{Classifier, Resolution}
import coursier.util.Task
// private[coursier]
final case class ArtifactsParams(
classifiers: Option[Seq[Classifier]],
resolutions: Seq[Resolution],
includeSignatures: Boolean,
loggerOpt: Option[CacheLogger],
projectName: String,
sbtClassifiers: Boolean,
cache: FileCache[Task],
parallel: Int,
classpathOrder: Boolean,
missingOk: Boolean
)

View File

@ -0,0 +1,75 @@
package lmcoursier.internal
import coursier.Artifacts
import coursier.cache.CacheLogger
import coursier.cache.loggers.{FallbackRefreshDisplay, ProgressBarRefreshDisplay, RefreshLogger}
import coursier.core.Type
import sbt.util.Logger
// private[lmcoursier]
object ArtifactsRun {
def apply(
params: ArtifactsParams,
verbosityLevel: Int,
log: Logger
): Either[coursier.error.FetchError, Artifacts.Result] = {
val printOptionalMessage = verbosityLevel >= 0 && verbosityLevel <= 1
val artifactInitialMessage =
if (verbosityLevel >= 0)
s"Fetching artifacts of ${params.projectName}" +
(if (params.sbtClassifiers) " (sbt classifiers)" else "")
else
""
val coursierLogger = params.loggerOpt.getOrElse {
RefreshLogger.create(
if (RefreshLogger.defaultFallbackMode)
new FallbackRefreshDisplay()
else
ProgressBarRefreshDisplay.create(
if (printOptionalMessage) log.info(artifactInitialMessage),
if (printOptionalMessage || verbosityLevel >= 2)
log.info(
s"Fetched artifacts of ${params.projectName}" +
(if (params.sbtClassifiers) " (sbt classifiers)" else "")
)
)
)
}
Lock.maybeSynchronized(needsLock = params.loggerOpt.nonEmpty || !RefreshLogger.defaultFallbackMode){
result(params, coursierLogger)
}
}
private def result(
params: ArtifactsParams,
coursierLogger: CacheLogger
): Either[coursier.error.FetchError, Artifacts.Result] =
coursier.Artifacts()
.withResolutions(params.resolutions)
.withArtifactTypes(Set(Type.all))
.withClassifiers(params.classifiers.getOrElse(Nil).toSet)
.withClasspathOrder(params.classpathOrder)
.addExtraArtifacts { l =>
if (params.includeSignatures)
l.flatMap(_._3.extra.get("sig").toSeq)
else
Nil
}
.addTransformArtifacts { artifacts =>
if (params.missingOk)
artifacts.map {
case (dependency, publication, artifact) =>
(dependency, publication, artifact.withOptional(true))
}
else
artifacts
}
.withCache(params.cache.withLogger(coursierLogger))
.eitherResult()
}

View File

@ -0,0 +1,22 @@
package lmcoursier.internal
import lmcoursier.CoursierConfiguration
import sbt.librarymanagement._
private[lmcoursier] final case class CoursierModuleDescriptor(
descriptor: ModuleDescriptorConfiguration,
conf: CoursierConfiguration
) extends ModuleDescriptor {
def directDependencies: Vector[ModuleID] =
descriptor.dependencies
def scalaModuleInfo: Option[ScalaModuleInfo] =
descriptor.scalaModuleInfo
def moduleSettings: CoursierModuleSettings =
CoursierModuleSettings()
lazy val extraInputHash: Long =
conf.##
}

View File

@ -0,0 +1,5 @@
package lmcoursier.internal
import sbt.librarymanagement.ModuleSettings
private[lmcoursier] case class CoursierModuleSettings() extends ModuleSettings

View File

@ -0,0 +1,35 @@
package lmcoursier.internal
import coursier.core._
import coursier.util.{EitherT, Monad}
// private[coursier]
final case class InterProjectRepository(projects: Seq[Project]) extends Repository {
private val map = projects
.map(proj => proj.moduleVersion -> proj)
.toMap
def find[F[_]](
module: Module,
version: String,
fetch: Repository.Fetch[F]
)(implicit
F: Monad[F]
): EitherT[F, String, (ArtifactSource, Project)] = {
val res = map
.get((module, version))
.map((this, _))
.toRight("Not found")
EitherT(F.point(res))
}
override def artifacts(
dependency: Dependency,
project: Project,
overrideClassifiers: Option[Seq[Classifier]]
) =
Nil
}

View File

@ -0,0 +1,10 @@
package lmcoursier.internal
private[lmcoursier] object Lock {
private val lock = new Object
/* Progress bars require us to only work on one module at the time. Without those we can go faster */
def maybeSynchronized[T](needsLock: Boolean)(f: => T): T =
if (needsLock) lock.synchronized(f)
else f
}

View File

@ -0,0 +1,117 @@
package lmcoursier.internal
import java.io.File
import coursier.cache.{CacheLogger, FileCache}
import coursier.ProjectCache
import coursier.core._
import coursier.params.rule.Strict
import lmcoursier.FallbackDependency
import lmcoursier.definitions.ToCoursier
import coursier.util.Task
import scala.collection.mutable
import scala.concurrent.duration.{DurationInt, FiniteDuration}
// private[coursier]
final case class ResolutionParams(
dependencies: Seq[(Configuration, Dependency)],
fallbackDependencies: Seq[FallbackDependency],
orderedConfigs: Seq[(Configuration, Seq[Configuration])],
autoScalaLibOpt: Option[(Organization, String)],
mainRepositories: Seq[Repository],
parentProjectCache: ProjectCache,
interProjectDependencies: Seq[Project],
internalRepositories: Seq[Repository],
sbtClassifiers: Boolean,
projectName: String,
loggerOpt: Option[CacheLogger],
cache: coursier.cache.FileCache[Task],
parallel: Int,
params: coursier.params.ResolutionParams,
strictOpt: Option[Strict],
missingOk: Boolean,
retry: (FiniteDuration, Int)
) {
lazy val allConfigExtends: Map[Configuration, Set[Configuration]] = {
val map = new mutable.HashMap[Configuration, Set[Configuration]]
for ((config, extends0) <- orderedConfigs) {
val allExtends = extends0
.iterator
// the else of the getOrElse shouldn't be hit (because of the ordering of the configurations)
.foldLeft(Set(config))((acc, ext) => acc ++ map.getOrElse(ext, Set(ext)))
map += config -> allExtends
}
map.toMap
}
val fallbackDependenciesRepositories =
if (fallbackDependencies.isEmpty)
Nil
else {
val map = fallbackDependencies
.map { dep =>
(ToCoursier.module(dep.module), dep.version) -> ((dep.url, dep.changing))
}
.toMap
Seq(
TemporaryInMemoryRepository(map, cache)
)
}
lazy val resolutionKey = {
val cleanCache = cache
.withPool(null)
.withLogger(null)
.withSync(null)
SbtCoursierCache.ResolutionKey(
dependencies,
internalRepositories,
mainRepositories,
fallbackDependenciesRepositories,
copy(
parentProjectCache = Map.empty,
loggerOpt = None,
parallel = 0,
cache = cleanCache
),
cleanCache,
missingOk
)
}
override lazy val hashCode =
this match {
case ResolutionParams(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) =>
(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) .##
}
// ResolutionParams.unapply(this).get.##
}
// private[coursier]
object ResolutionParams {
def defaultIvyProperties(ivyHomeOpt: Option[File]): Map[String, String] = {
val ivyHome = sys.props
.get("ivy.home")
.orElse(ivyHomeOpt.map(_.getAbsoluteFile.toURI.getPath))
.getOrElse(new File(sys.props("user.home")).toURI.getPath + ".ivy2")
val sbtIvyHome = sys.props.getOrElse(
"sbt.ivy.home",
ivyHome
)
Map(
"ivy.home" -> ivyHome,
"sbt.ivy.home" -> sbtIvyHome
) ++ sys.props
}
val defaultRetry: (FiniteDuration, Int) = (1.seconds, 3)
}

View File

@ -0,0 +1,211 @@
package lmcoursier.internal
import coursier.{Resolution, Resolve}
import coursier.cache.internal.ThreadUtil
import coursier.cache.loggers.{FallbackRefreshDisplay, ProgressBarRefreshDisplay, RefreshLogger}
import coursier.core._
import coursier.error.ResolutionError
import coursier.error.ResolutionError.CantDownloadModule
import coursier.ivy.IvyRepository
import coursier.maven.MavenRepositoryLike
import coursier.params.rule.RuleResolution
import coursier.util.Task
import sbt.util.Logger
import scala.concurrent.duration.FiniteDuration
import scala.collection.mutable
// private[coursier]
object ResolutionRun {
private def resolution(
params: ResolutionParams,
verbosityLevel: Int,
log: Logger,
configs: Set[Configuration],
startingResolutionOpt: Option[Resolution]
): Either[coursier.error.ResolutionError, Resolution] = {
val isScalaToolConfig = configs(Configuration("scala-tool"))
// Ref coursier/coursier#1340 coursier/coursier#1442
// This treats ScalaTool as a sandbox configuration isolated from other subprojects.
// Likely this behavior is needed only for ScalaTool configuration where the scala-xml
// build's ScalaTool configuration transitively loops back to scala-xml's Compile artifacts.
// In most other cases, it's desirable to allow "x->compile" relationship.
def isSandboxConfig: Boolean = isScalaToolConfig
val repositories =
params.internalRepositories.drop(if (isSandboxConfig) 1 else 0) ++
params.mainRepositories ++
params.fallbackDependenciesRepositories
val rules = params.params.rules ++ params.strictOpt.map(s => Seq((s, RuleResolution.Fail))).getOrElse(Nil)
val printOptionalMessage = verbosityLevel >= 0 && verbosityLevel <= 1
def depsRepr(deps: Seq[(Configuration, Dependency)]) =
deps.map { case (config, dep) =>
s"${dep.module}:${dep.version}:${config.value}->${dep.configuration.value}"
}.sorted.distinct
val initialMessage =
Seq(
if (verbosityLevel >= 0)
Seq(s"Updating ${params.projectName}" + (if (params.sbtClassifiers) " (sbt classifiers)" else ""))
else
Nil,
if (verbosityLevel >= 2)
depsRepr(params.dependencies).map(depRepr =>
s" $depRepr"
)
else
Nil
).flatten.mkString("\n")
if (verbosityLevel >= 2) {
val repoReprs = repositories.map {
case r: IvyRepository =>
s"ivy:${r.pattern}"
case _: InterProjectRepository =>
"inter-project"
case r: MavenRepositoryLike =>
r.root
case r =>
// should not happen
r.toString
}
log.info(
"Repositories:\n" +
repoReprs.map(" " + _).mkString("\n")
)
}
if (verbosityLevel >= 2)
log.info(initialMessage)
val resolveTask: Resolve[Task] = {
Resolve()
// re-using various caches from a resolution of a configuration we extend
.withInitialResolution(startingResolutionOpt)
.withDependencies(
params.dependencies.collect {
case (config, dep) if configs(config) =>
dep
}
)
.withRepositories(repositories)
.withResolutionParams(
params
.params
.addForceVersion((if (isSandboxConfig) Nil else params.interProjectDependencies.map(_.moduleVersion)): _*)
.withForceScalaVersion(params.autoScalaLibOpt.nonEmpty)
.withScalaVersionOpt(params.autoScalaLibOpt.map(_._2))
.withTypelevel(params.params.typelevel)
.withRules(rules)
)
.withCache(
params
.cache
.withLogger(
params.loggerOpt.getOrElse {
RefreshLogger.create(
if (RefreshLogger.defaultFallbackMode)
new FallbackRefreshDisplay()
else
ProgressBarRefreshDisplay.create(
if (printOptionalMessage) log.info(initialMessage),
if (printOptionalMessage || verbosityLevel >= 2)
log.info(s"Resolved ${params.projectName} dependencies")
)
)
}
)
)
}
val (period, maxAttempts) = params.retry
val finalResult: Either[ResolutionError, Resolution] = {
def retry(attempt: Int, waitOnError: FiniteDuration): Task[Either[ResolutionError, Resolution]] =
resolveTask
.io
.attempt
.flatMap {
case Left(e: ResolutionError) =>
val hasConnectionTimeouts = e.errors.exists {
case err: CantDownloadModule => err.perRepositoryErrors.exists(_.contains("Connection timed out"))
case _ => false
}
if (hasConnectionTimeouts)
if (attempt + 1 >= maxAttempts) {
log.error(s"Failed, maximum iterations ($maxAttempts) reached")
Task.point(Left(e))
}
else {
log.warn(s"Attempt ${attempt + 1} failed: $e")
Task.completeAfter(retryScheduler, waitOnError).flatMap { _ =>
retry(attempt + 1, waitOnError * 2)
}
}
else
Task.point(Left(e))
case Left(ex) =>
Task.fail(ex)
case Right(value) =>
Task.point(Right(value))
}
retry(0, period).unsafeRun()(resolveTask.cache.ec)
}
finalResult match {
case Left(err) if params.missingOk => Right(err.resolution)
case others => others
}
}
def resolutions(
params: ResolutionParams,
verbosityLevel: Int,
log: Logger
): Either[coursier.error.ResolutionError, Map[Configuration, Resolution]] = {
// TODO Warn about possible duplicated modules from source repositories?
if (verbosityLevel >= 2) {
log.info("InterProjectRepository")
for (p <- params.interProjectDependencies)
log.info(s" ${p.module}:${p.version}")
}
SbtCoursierCache.default.resolutionOpt(params.resolutionKey).map(Right(_)).getOrElse {
val resOrError =
Lock.maybeSynchronized(needsLock = params.loggerOpt.nonEmpty || !RefreshLogger.defaultFallbackMode) {
val map = new mutable.HashMap[Configuration, Resolution]
val either = params.orderedConfigs.foldLeft[Either[coursier.error.ResolutionError, Unit]](Right(())) {
case (acc, (config, extends0)) =>
for {
_ <- acc
initRes = {
val it = extends0.iterator.flatMap(map.get(_).iterator)
if (it.hasNext) Some(it.next())
else None
}
allExtends = params.allConfigExtends.getOrElse(config, Set.empty)
res <- resolution(params, verbosityLevel, log, allExtends, initRes)
} yield {
map += config -> res
()
}
}
either.map(_ => map.toMap)
}
for (res <- resOrError)
SbtCoursierCache.default.putResolution(params.resolutionKey, res)
resOrError
}
}
private lazy val retryScheduler = ThreadUtil.fixedScheduledThreadPool(1)
}

View File

@ -0,0 +1,198 @@
package lmcoursier.internal
import java.net.MalformedURLException
import java.nio.file.Paths
import coursier.cache.CacheUrl
import coursier.core.{Authentication, Repository}
import coursier.ivy.IvyRepository
import coursier.maven.SbtMavenRepository
import org.apache.ivy.plugins.resolver.IBiblioResolver
import sbt.librarymanagement.{Configuration => _, MavenRepository => _, _}
import sbt.util.Logger
import scala.collection.JavaConverters._
object Resolvers {
private def mavenCompatibleBaseOpt(patterns: Patterns): Option[String] =
if (patterns.isMavenCompatible) {
//input : /Users/user/custom/repo/[organisation]/[module](_[scalaVersion])(_[sbtVersion])/[revision]/[artifact]-[revision](-[classifier]).[ext]
//output : /Users/user/custom/repo/
def basePattern(pattern: String): String = pattern.takeWhile(c => c != '[' && c != '(')
val baseIvyPattern = basePattern(patterns.ivyPatterns.head)
val baseArtifactPattern = basePattern(patterns.artifactPatterns.head)
if (baseIvyPattern == baseArtifactPattern)
Some(baseIvyPattern)
else
None
} else
None
private def mavenRepositoryOpt(
root: String,
log: Logger,
authentication: Option[Authentication],
classLoaders: Seq[ClassLoader]
): Option[SbtMavenRepository] =
try {
CacheUrl.url(root, classLoaders) // ensure root is a URL whose protocol can be handled here
val root0 = if (root.endsWith("/")) root else root + "/"
Some(
SbtMavenRepository(
root0,
authentication = authentication
)
)
} catch {
case e: MalformedURLException =>
log.warn(
"Error parsing Maven repository base " +
root +
Option(e.getMessage).fold("")(" (" + _ + ")") +
", ignoring it"
)
None
}
// this handles whitespace in path
private def pathToUriString(path: String): String = {
val stopAtIdx = path.indexWhere(c => c == '[' || c == '$' || c == '(')
if (stopAtIdx > 0) {
val (pathPart, patternPart) = path.splitAt(stopAtIdx)
Paths.get(pathPart).toUri.toASCIIString + patternPart
} else if (stopAtIdx == 0)
"file://" + path
else
Paths.get(path).toUri.toASCIIString
}
def repository(
resolver: Resolver,
ivyProperties: Map[String, String],
log: Logger,
authentication: Option[Authentication],
classLoaders: Seq[ClassLoader]
): Option[Repository] =
resolver match {
case r: sbt.librarymanagement.MavenRepository =>
mavenRepositoryOpt(r.root, log, authentication, classLoaders)
case r: FileRepository
if r.patterns.ivyPatterns.lengthCompare(1) == 0 &&
r.patterns.artifactPatterns.lengthCompare(1) == 0 =>
val mavenCompatibleBaseOpt0 = mavenCompatibleBaseOpt(r.patterns)
mavenCompatibleBaseOpt0 match {
case None =>
val repo = IvyRepository.parse(
pathToUriString(r.patterns.artifactPatterns.head),
metadataPatternOpt = Some(pathToUriString(r.patterns.ivyPatterns.head)),
changing = Some(true),
properties = ivyProperties,
dropInfoAttributes = true,
authentication = authentication
) match {
case Left(err) =>
sys.error(
s"Cannot parse Ivy patterns ${r.patterns.artifactPatterns.head} and ${r.patterns.ivyPatterns.head}: $err"
)
case Right(repo) =>
repo
}
Some(repo)
case Some(mavenCompatibleBase) =>
mavenRepositoryOpt(pathToUriString(mavenCompatibleBase), log, authentication, classLoaders)
}
case r: URLRepository if patternMatchGuard(r.patterns) =>
parseMavenCompatResolver(log, ivyProperties, authentication, r.patterns, classLoaders)
case raw: RawRepository if raw.name == "inter-project" => // sbt.RawRepository.equals just compares names anyway
None
// Pattern Match resolver-type-specific RawRepositories
case IBiblioRepository(p) =>
parseMavenCompatResolver(log, ivyProperties, authentication, p, classLoaders)
case other =>
log.warn(s"Unrecognized repository ${other.name}, ignoring it")
None
}
private object IBiblioRepository {
private def stringVector(v: java.util.List[_]): Vector[String] =
Option(v).map(_.asScala.toVector).getOrElse(Vector.empty).collect {
case s: String => s
}
private def patterns(resolver: IBiblioResolver): Patterns = Patterns(
ivyPatterns = stringVector(resolver.getIvyPatterns),
artifactPatterns = stringVector(resolver.getArtifactPatterns),
isMavenCompatible = resolver.isM2compatible,
descriptorOptional = !resolver.isUseMavenMetadata,
skipConsistencyCheck = !resolver.isCheckconsistency
)
def unapply(r: Resolver): Option[Patterns] =
r match {
case raw: RawRepository =>
raw.resolver match {
case b: IBiblioResolver =>
Some(patterns(b))
.filter(patternMatchGuard)
case _ =>
None
}
case _ =>
None
}
}
private def patternMatchGuard(patterns: Patterns): Boolean =
patterns.ivyPatterns.lengthCompare(1) == 0 &&
patterns.artifactPatterns.lengthCompare(1) == 0
private def parseMavenCompatResolver(
log: Logger,
ivyProperties: Map[String, String],
authentication: Option[Authentication],
patterns: Patterns,
classLoaders: Seq[ClassLoader],
): Option[Repository] = {
val mavenCompatibleBaseOpt0 = mavenCompatibleBaseOpt(patterns)
mavenCompatibleBaseOpt0 match {
case None =>
val repo = IvyRepository.parse(
patterns.artifactPatterns.head,
metadataPatternOpt = Some(patterns.ivyPatterns.head),
changing = None,
properties = ivyProperties,
dropInfoAttributes = true,
authentication = authentication
) match {
case Left(err) =>
sys.error(
s"Cannot parse Ivy patterns ${patterns.artifactPatterns.head} and ${patterns.ivyPatterns.head}: $err"
)
case Right(repo) =>
repo
}
Some(repo)
case Some(mavenCompatibleBase) =>
mavenRepositoryOpt(mavenCompatibleBase, log, authentication, classLoaders)
}
}
}

View File

@ -0,0 +1,23 @@
package lmcoursier.internal
import java.io.File
import coursier.core.{Module, ModuleName, Organization}
// private[coursier]
object SbtBootJars {
def apply(
scalaOrg: Organization,
scalaVersion: String,
jars: Seq[File]
): Map[(Module, String), File] =
jars
.collect {
case jar if jar.getName.endsWith(".jar") =>
val name = ModuleName(jar.getName.stripSuffix(".jar"))
val mod = Module(scalaOrg, name, Map.empty)
(mod, scalaVersion) -> jar
}
.toMap
}

View File

@ -0,0 +1,66 @@
package lmcoursier.internal
import java.util.concurrent.ConcurrentHashMap
import coursier.core._
import sbt.librarymanagement.UpdateReport
import coursier.cache.FileCache
import coursier.util.Task
// private[coursier]
class SbtCoursierCache {
import SbtCoursierCache._
private val resolutionsCache = new ConcurrentHashMap[ResolutionKey, Map[Configuration, Resolution]]
// these may actually not need to be cached any more, now that the resolutions
// are cached
private val reportsCache = new ConcurrentHashMap[ReportKey, UpdateReport]
def resolutionOpt(key: ResolutionKey): Option[Map[Configuration, Resolution]] =
Option(resolutionsCache.get(key))
def putResolution(key: ResolutionKey, res: Map[Configuration, Resolution]): Unit =
resolutionsCache.put(key, res)
def reportOpt(key: ReportKey): Option[UpdateReport] =
Option(reportsCache.get(key))
def putReport(key: ReportKey, report: UpdateReport): Unit =
reportsCache.put(key, report)
def clear(): Unit = {
resolutionsCache.clear()
reportsCache.clear()
}
def isEmpty: Boolean =
resolutionsCache.isEmpty && reportsCache.isEmpty
}
// private[coursier]
object SbtCoursierCache {
final case class ResolutionKey(
dependencies: Seq[(Configuration, Dependency)],
internalRepositories: Seq[Repository],
mainRepositories: Seq[Repository],
fallbackRepositories: Seq[Repository],
params: ResolutionParams,
cache: FileCache[Task],
sbtClassifiers: Boolean
)
final case class ReportKey(
dependencies: Seq[(Configuration, Dependency)],
resolution: Map[Configuration, Resolution],
withClassifiers: Boolean,
sbtClassifiers: Boolean,
includeSignatures: Boolean
)
// private[coursier]
val default = new SbtCoursierCache
}

View File

@ -0,0 +1,415 @@
package lmcoursier.internal
import java.io.File
import java.net.URL
import java.util.GregorianCalendar
import java.util.concurrent.ConcurrentHashMap
import coursier.cache.CacheUrl
import coursier.{Attributes, Dependency, Module, Project, Resolution}
import coursier.core.{Classifier, Configuration, Extension, Info, Publication, Type}
import coursier.maven.MavenAttributes
import coursier.util.Artifact
import sbt.librarymanagement.{Artifact => _, Configuration => _, _}
import sbt.util.Logger
import scala.annotation.tailrec
private[internal] object SbtUpdateReport {
private def caching[K, V](f: K => V): K => V = {
val cache = new ConcurrentHashMap[K, V]
key =>
val previousValueOpt = Option(cache.get(key))
previousValueOpt.getOrElse {
val value = f(key)
val concurrentValueOpt = Option(cache.putIfAbsent(key, value))
concurrentValueOpt.getOrElse(value)
}
}
private def infoProperties(project: Project): Seq[(String, String)] =
project.properties.filter(_._1.startsWith("info."))
private val moduleId = caching[(Dependency, String, Map[String, String]), ModuleID] {
case (dependency, version, extraProperties) =>
val mod = sbt.librarymanagement.ModuleID(
dependency.module.organization.value,
dependency.module.name.value,
version
)
mod
.withConfigurations(
Some(dependency.configuration.value)
.filter(_.nonEmpty) // ???
)
.withExtraAttributes(dependency.module.attributes ++ extraProperties)
.withExclusions(
dependency
.minimizedExclusions
.toVector
.map {
case (org, name) =>
sbt.librarymanagement.InclExclRule()
.withOrganization(org.value)
.withName(name.value)
}
)
.withIsTransitive(dependency.transitive)
}
private val artifact = caching[(Module, Map[String, String], Publication, Artifact, Seq[ClassLoader]), sbt.librarymanagement.Artifact] {
case (module, extraProperties, pub, artifact, classLoaders) =>
sbt.librarymanagement.Artifact(pub.name)
.withType(pub.`type`.value)
.withExtension(pub.ext.value)
.withClassifier(
Some(pub.classifier)
.filter(_.nonEmpty)
.orElse(MavenAttributes.typeDefaultClassifierOpt(pub.`type`))
.map(_.value)
)
.withUrl(Some(CacheUrl.url(artifact.url, classLoaders).toURI))
.withExtraAttributes(module.attributes ++ extraProperties)
}
private val moduleReport = caching[(Dependency, Seq[(Dependency, ProjectInfo)], Project, Seq[(Publication, Artifact, Option[File])], Seq[ClassLoader]), ModuleReport] {
case (dependency, dependees, project, artifacts, classLoaders) =>
val sbtArtifacts = artifacts.collect {
case (pub, artifact0, Some(file)) =>
(artifact((dependency.module, infoProperties(project).toMap, pub, artifact0, classLoaders)), file)
}
val sbtMissingArtifacts = artifacts.collect {
case (pub, artifact0, None) =>
artifact((dependency.module, infoProperties(project).toMap, pub, artifact0, classLoaders))
}
val publicationDate = project.info.publication.map { dt =>
new GregorianCalendar(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
}
val callers = dependees.distinct.map {
case (dependee, dependeeProj) =>
Caller(
moduleId((dependee, dependeeProj.version, Map.empty)),
// FIXME Shouldn't we only keep the configurations pulling dependency?
dependeeProj.configs,
dependee.module.attributes ++ dependeeProj.properties,
// FIXME Set better values here
isForceDependency = false,
isChangingDependency = false,
isTransitiveDependency = dependency.transitive,
isDirectlyForceDependency = false
)
}
val rep = ModuleReport(
moduleId((dependency, project.version, infoProperties(project).toMap)),
sbtArtifacts.toVector,
sbtMissingArtifacts.toVector
)
rep
// .withStatus(None)
.withPublicationDate(publicationDate)
// .withResolver(None)
// .withArtifactResolver(None)
// .withEvicted(false)
// .withEvictedData(None)
// .withEvictedReason(None)
// .withProblem(None)
.withHomepage(Some(project.info.homePage).filter(_.nonEmpty))
.withLicenses(project.info.licenses.toVector)
.withExtraAttributes(dependency.module.attributes ++ infoProperties(project))
// .withIsDefault(None)
// .withBranch(None)
.withConfigurations(project.configurations.keys.toVector.map(c => ConfigRef(c.value)))
.withLicenses(project.info.licenses.toVector)
.withCallers(callers.toVector)
}
private def moduleReports(
thisModule: (Module, String),
res: Resolution,
interProjectDependencies: Seq[Project],
classifiersOpt: Option[Seq[Classifier]],
artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File],
fullArtifactsOpt: Option[Map[(Dependency, Publication, Artifact), Option[File]]],
log: Logger,
includeSignatures: Boolean,
classpathOrder: Boolean,
missingOk: Boolean,
classLoaders: Seq[ClassLoader]
): Vector[ModuleReport] = {
val deps = classifiersOpt match {
case Some(classifiers) =>
res.dependencyArtifacts(Some(classifiers.toSeq), classpathOrder)
case None =>
res.dependencyArtifacts(None, classpathOrder)
}
val depArtifacts1 = fullArtifactsOpt match {
case Some(map) =>
deps.map {
case (d, p, a) =>
val d0 = d.withAttributes(d.attributes.withClassifier(p.classifier))
val a0 = if (missingOk) a.withOptional(true) else a
val f = map.get((d0, p, a0)).flatten
(d, p, a0, f) // not d0
}
case None =>
deps.map {
case (d, p, a) =>
(d, p, a, None)
}
}
val depArtifacts0 = depArtifacts1.filter {
case (_, pub, _, _) =>
pub.attributes != Attributes(Type.pom, Classifier.empty)
}
val depArtifacts =
if (includeSignatures) {
val notFound = depArtifacts0.filter(!_._3.extra.contains("sig"))
if (notFound.isEmpty)
depArtifacts0.flatMap {
case (dep, pub, a, f) =>
val sigPub = pub
// not too sure about those
.withExt(Extension(pub.ext.value))
.withType(Type(pub.`type`.value))
Seq((dep, pub, a, f)) ++
a.extra.get("sig").toSeq.map((dep, sigPub, _, None))
}
else {
for ((_, _, a, _) <- notFound)
log.error(s"No signature found for ${a.url}")
sys.error(s"${notFound.length} signature(s) not found")
}
} else
depArtifacts0
val groupedDepArtifacts = {
val m = depArtifacts.groupBy(_._1)
val fromLib = depArtifacts.map(_._1).distinct.map { dep =>
dep -> m.getOrElse(dep, Nil).map { case (_, pub, a, f) => (pub, a, f) }
}
val fromInterProj = interProjectDependencies
.filter(p => p.module != thisModule._1)
.map(p => Dependency(p.module, p.version) -> Nil)
fromLib ++ fromInterProj
}
val versions = (Vector(Dependency(thisModule._1, thisModule._2)) ++ res.dependencies.toVector ++ res.rootDependencies.toVector)
.map { dep =>
dep.module -> dep.version
}.toMap
def clean(dep: Dependency): Dependency =
dep
.withConfiguration(Configuration.empty)
.withExclusions(Set.empty)
.withOptional(false)
def lookupProject(mv: coursier.core.Resolution.ModuleVersion): Option[Project] =
res.projectCache.get(mv) match {
case Some((_, p)) => Some(p)
case _ =>
interProjectDependencies.find( p =>
mv == (p.module, p.version)
)
}
/**
* Assemble the project info, resolving inherited fields. Only implements resolving
* the fields that are relevant for moduleReport
*
* @see https://maven.apache.org/pom.html#Inheritance
* @see https://maven.apache.org/ref/3-LATEST/maven-model-builder/index.html#Inheritance_Assembly
*/
def assemble(project: Project): Project = {
@tailrec
def licenseInfo(project: Project): Seq[Info.License] = {
if (project.info.licenseInfo.nonEmpty || project.parent.isEmpty)
project.info.licenseInfo
else
licenseInfo(lookupProject(project.parent.get).get)
}
project.withInfo(
project.info.withLicenseInfo(licenseInfo(project))
)
}
val m = Dependency(thisModule._1, "")
val directReverseDependencies = res.rootDependencies.toSet.map(clean).map(_.withVersion(""))
.map(
dep => dep -> Vector(m)
)
.toMap
val reverseDependencies = {
val transitiveReverseDependencies = res.reverseDependencies
.toVector
.map { case (k, v) =>
clean(k) -> v.map(clean)
}
.groupBy(_._1)
.mapValues(_.flatMap(_._2))
(transitiveReverseDependencies.toVector ++ directReverseDependencies.toVector)
.groupBy(_._1)
.mapValues(_.flatMap(_._2).toVector)
.toVector
.toMap
}
groupedDepArtifacts.toVector.map {
case (dep, artifacts) =>
val proj = lookupProject(dep.moduleVersion).get
val assembledProject = assemble(proj)
// FIXME Likely flaky...
val dependees = reverseDependencies
.getOrElse(clean(dep.withVersion("")), Vector.empty)
.flatMap { dependee0 =>
val version = versions(dependee0.module)
val dependee = dependee0.withVersion(version)
lookupProject(dependee.moduleVersion) match {
case Some(dependeeProj) =>
Vector((dependee, ProjectInfo(
dependeeProj.version,
dependeeProj.configurations.keys.toVector.map(c => ConfigRef(c.value)),
dependeeProj.properties)))
case _ =>
Vector.empty
}
}
val filesOpt = artifacts.map {
case (pub, a, fileOpt) =>
val fileOpt0 = fileOpt.orElse {
if (fullArtifactsOpt.isEmpty) artifactFileOpt(proj.module, proj.version, pub.attributes, a)
else None
}
(pub, a, fileOpt0)
}
moduleReport((
dep,
dependees,
assembledProject,
filesOpt,
classLoaders,
))
}
}
def apply(
thisModule: (Module, String),
configDependencies: Map[Configuration, Seq[Dependency]],
resolutions: Seq[(Configuration, Resolution)],
interProjectDependencies: Vector[Project],
classifiersOpt: Option[Seq[Classifier]],
artifactFileOpt: (Module, String, Attributes, Artifact) => Option[File],
fullArtifactsOpt: Option[Map[(Dependency, Publication, Artifact), Option[File]]],
log: Logger,
includeSignatures: Boolean,
classpathOrder: Boolean,
missingOk: Boolean,
forceVersions: Map[Module, String],
classLoaders: Seq[ClassLoader],
): UpdateReport = {
val configReports = resolutions.map {
case (config, subRes) =>
val reports = moduleReports(
thisModule,
subRes,
interProjectDependencies,
classifiersOpt,
artifactFileOpt,
fullArtifactsOpt,
log,
includeSignatures = includeSignatures,
classpathOrder = classpathOrder,
missingOk = missingOk,
classLoaders = classLoaders,
)
val reports0 = subRes.rootDependencies match {
case Seq(dep) if subRes.projectCache.contains(dep.moduleVersion) =>
// quick hack ensuring the module for the only root dependency
// appears first in the update report, see https://github.com/coursier/coursier/issues/650
val (_, proj) = subRes.projectCache(dep.moduleVersion)
val mod = moduleId((dep, proj.version, infoProperties(proj).toMap))
val (main, other) = reports.partition { r =>
r.module.organization == mod.organization &&
r.module.name == mod.name &&
r.module.crossVersion == mod.crossVersion
}
main ++ other
case _ => reports
}
val mainReportDetails = reports0.map { rep =>
OrganizationArtifactReport(rep.module.organization, rep.module.name, Vector(rep))
}
val evicted = for {
c <- coursier.graph.Conflict(subRes)
// ideally, forceVersions should be taken into account by coursier.core.Resolution itself, when
// it computes transitive dependencies. It only handles forced versions at a global level for now,
// rather than handing them for each dependency (where each dependency could have its own forced
// versions, and apply and pass them to its transitive dependencies, just like for exclusions today).
if !forceVersions.contains(c.module)
projOpt = subRes.projectCache.get((c.module, c.wantedVersion))
.orElse(subRes.projectCache.get((c.module, c.version)))
(_, proj) <- projOpt.toSeq
} yield {
val dep = Dependency(c.module, c.wantedVersion)
val dependee = Dependency(c.dependeeModule, c.dependeeVersion)
val dependeeProj = subRes.projectCache.get((c.dependeeModule, c.dependeeVersion)) match {
case Some((_, p)) =>
ProjectInfo(p.version, p.configurations.keys.toVector.map(c => ConfigRef(c.value)), p.properties)
case None =>
// should not happen
ProjectInfo(c.dependeeVersion, Vector.empty, Vector.empty)
}
val rep = moduleReport((dep, Seq((dependee, dependeeProj)), proj.withVersion(c.wantedVersion), Nil, classLoaders))
.withEvicted(true)
.withEvictedData(Some("version selection")) // ??? put latest-revision like sbt/ivy here?
OrganizationArtifactReport(c.module.organization.value, c.module.name.value, Vector(rep))
}
val details = (mainReportDetails ++ evicted)
.groupBy(r => (r.organization, r.name))
.toVector // order?
.map {
case ((org, name), l) =>
val modules = l.flatMap(_.modules)
OrganizationArtifactReport(org, name, modules)
}
ConfigurationReport(
ConfigRef(config.value),
reports0,
details
)
}
UpdateReport(
new File("."), // dummy value
configReports.toVector,
UpdateStats(-1L, -1L, -1L, cached = false),
Map.empty
)
}
private case class ProjectInfo(version: String, configs: Vector[ConfigRef], properties: Seq[(String, String)])
}

View File

@ -0,0 +1,209 @@
package lmcoursier.internal
import java.io.{File, FileNotFoundException, IOException}
import java.net.{HttpURLConnection, URL, URLConnection}
import coursier.cache.{ConnectionBuilder, FileCache}
import coursier.core._
import coursier.util.{Artifact, EitherT, Monad}
import scala.util.Try
object TemporaryInMemoryRepository {
def closeConn(conn: URLConnection): Unit = {
Try(conn.getInputStream).toOption.filter(_ != null).foreach(_.close())
conn match {
case conn0: HttpURLConnection =>
Try(conn0.getErrorStream).toOption.filter(_ != null).foreach(_.close())
conn0.disconnect()
case _ =>
}
}
def exists(
url: URL,
localArtifactsShouldBeCached: Boolean
): Boolean =
exists(url, localArtifactsShouldBeCached, None)
def exists(
url: URL,
localArtifactsShouldBeCached: Boolean,
cacheOpt: Option[FileCache[Nothing]]
): Boolean = {
// Sometimes HEAD attempts fail even though standard GETs are fine.
// E.g. https://github.com/NetLogo/NetLogo/releases/download/5.3.1/NetLogo.jar
// returning 403s. Hence the second attempt below.
val protocolSpecificAttemptOpt = {
def ifFile: Option[Boolean] = {
if (localArtifactsShouldBeCached && !new File(url.toURI).exists()) {
val cachePath = coursier.cache.CacheDefaults.location
// 'file' here stands for the protocol (e.g. it's https instead for https:// URLs)
Some(new File(cachePath, s"file/${url.getPath}").exists())
} else {
Some(new File(url.toURI).exists()) // FIXME Escaping / de-escaping needed here?
}
}
def ifHttp: Option[Boolean] = {
// HEAD request attempt, adapted from http://stackoverflow.com/questions/22541629/android-how-can-i-make-an-http-head-request/22545275#22545275
var conn: URLConnection = null
try {
conn = ConnectionBuilder(url.toURI.toASCIIString)
.withFollowHttpToHttpsRedirections(cacheOpt.fold(false)(_.followHttpToHttpsRedirections))
.withFollowHttpsToHttpRedirections(cacheOpt.fold(false)(_.followHttpsToHttpRedirections))
.withSslSocketFactoryOpt(cacheOpt.flatMap(_.sslSocketFactoryOpt))
.withHostnameVerifierOpt(cacheOpt.flatMap(_.hostnameVerifierOpt))
.withMethod("HEAD")
.withMaxRedirectionsOpt(cacheOpt.flatMap(_.maxRedirections))
.connection()
// Even though the finally clause handles this too, this has to be run here, so that we return Some(true)
// iff this doesn't throw.
conn.getInputStream.close()
Some(true)
}
catch {
case _: FileNotFoundException => Some(false)
case _: IOException => None // error other than not found
}
finally {
if (conn != null)
closeConn(conn)
}
}
url.getProtocol match {
case "file" => ifFile
case "http" | "https" => ifHttp
case _ => None
}
}
def genericAttempt: Boolean = {
var conn: URLConnection = null
try {
conn = url.openConnection()
// NOT setting request type to HEAD here.
conn.getInputStream.close()
true
}
catch {
case _: IOException => false
}
finally {
if (conn != null)
closeConn(conn)
}
}
protocolSpecificAttemptOpt
.getOrElse(genericAttempt)
}
def apply(
fallbacks: Map[(Module, String), (URL, Boolean)]
): TemporaryInMemoryRepository =
new TemporaryInMemoryRepository(fallbacks, localArtifactsShouldBeCached = false, None)
def apply(
fallbacks: Map[(Module, String), (URL, Boolean)],
localArtifactsShouldBeCached: Boolean
): TemporaryInMemoryRepository =
new TemporaryInMemoryRepository(fallbacks, localArtifactsShouldBeCached, None)
def apply[F[_]](
fallbacks: Map[(Module, String), (URL, Boolean)],
cache: FileCache[F]
): TemporaryInMemoryRepository =
new TemporaryInMemoryRepository(
fallbacks,
localArtifactsShouldBeCached = cache.localArtifactsShouldBeCached,
Some(cache.asInstanceOf[FileCache[Nothing]])
)
}
final class TemporaryInMemoryRepository private(
val fallbacks: Map[(Module, String), (URL, Boolean)],
val localArtifactsShouldBeCached: Boolean,
val cacheOpt: Option[FileCache[Nothing]]
) extends Repository {
def find[F[_]](
module: Module,
version: String,
fetch: Repository.Fetch[F]
)(implicit
F: Monad[F]
): EitherT[F, String, (ArtifactSource, Project)] = {
def res = fallbacks
.get((module, version))
.fold[Either[String, (ArtifactSource, Project)]](Left("No fallback URL found")) {
case (url, _) =>
val urlStr = url.toExternalForm
val idx = urlStr.lastIndexOf('/')
if (idx < 0 || urlStr.endsWith("/"))
Left(s"$url doesn't point to a file")
else {
val (dirUrlStr, fileName) = urlStr.splitAt(idx + 1)
if (TemporaryInMemoryRepository.exists(url, localArtifactsShouldBeCached, cacheOpt)) {
val proj = Project(
module,
version,
Nil,
Map.empty,
None,
Nil,
Nil,
Nil,
None,
None,
None,
relocated = false,
None,
Nil,
Info.empty
)
Right((this, proj))
} else
Left(s"$fileName not found under $dirUrlStr")
}
}
// EitherT(F.bind(F.point(()))(_ => F.point(res)))
EitherT(F.map(F.point(()))(_ => res))
}
def artifacts(
dependency: Dependency,
project: Project,
overrideClassifiers: Option[Seq[Classifier]]
): Seq[(Publication, Artifact)] = {
fallbacks
.get(dependency.moduleVersion)
.toSeq
.map {
case (url, changing) =>
val url0 = url.toString
val ext = url0.substring(url0.lastIndexOf('.') + 1)
val pub = Publication(
dependency.module.name.value, // ???
Type(ext),
Extension(ext),
Classifier.empty
)
(pub, Artifact(url0, Map.empty, Map.empty, changing, optional = false, None))
}
}
}

View File

@ -0,0 +1,50 @@
package lmcoursier.internal
import java.io.File
import coursier.core._
import coursier.util.Artifact
// private[coursier]
final case class UpdateParams(
thisModule: (Module, String),
artifacts: Map[Artifact, File],
fullArtifacts: Option[Map[(Dependency, Publication, Artifact), Option[File]]],
classifiers: Option[Seq[Classifier]],
configs: Map[Configuration, Set[Configuration]],
dependencies: Seq[(Configuration, Dependency)],
forceVersions: Map[Module, String],
interProjectDependencies: Seq[Project],
res: Map[Configuration, Resolution],
includeSignatures: Boolean,
sbtBootJarOverrides: Map[(Module, String), File],
classpathOrder: Boolean,
missingOk: Boolean,
classLoaders: Seq[ClassLoader]
) {
def artifactFileOpt(
module: Module,
version: String,
attributes: Attributes,
artifact: Artifact
): Option[File] = {
// Under some conditions, SBT puts the scala JARs of its own classpath
// in the application classpath. Ensuring we return SBT's jars rather than
// JARs from the coursier cache, so that a same JAR doesn't land twice in the
// application classpath (once via SBT jars, once via coursier cache).
val fromBootJars =
if (attributes.classifier.isEmpty && attributes.`type` == Type.jar)
sbtBootJarOverrides.get((module, version))
else
None
val artifact0 =
if (missingOk) artifact.withOptional(true)
else artifact
fromBootJars.orElse(artifacts.get(artifact0))
}
}

View File

@ -0,0 +1,98 @@
package lmcoursier.internal
import coursier.cache.loggers.RefreshLogger
import coursier.core.Resolution.ModuleVersion
import coursier.core._
import coursier.util.Print
import sbt.librarymanagement.UpdateReport
import sbt.util.Logger
// private[coursier]
object UpdateRun {
// Move back to coursier.util (in core module) after 1.0?
private def allDependenciesByConfig(
res: Map[Configuration, Resolution],
depsByConfig: Map[Configuration, Seq[Dependency]],
configs: Map[Configuration, Set[Configuration]]
): Map[Configuration, Set[Dependency]] = {
val allDepsByConfig = depsByConfig.map {
case (config, deps) =>
config -> res(config).subset(deps).minDependencies
}
val filteredAllDepsByConfig = allDepsByConfig.map {
case (config, allDeps) =>
val allExtendedConfigs = configs.getOrElse(config, Set.empty) - config
val inherited = allExtendedConfigs
.flatMap(allDepsByConfig.getOrElse(_, Set.empty))
config -> (allDeps -- inherited)
}
filteredAllDepsByConfig
}
// Move back to coursier.util (in core module) after 1.0?
private def dependenciesWithConfig(
res: Map[Configuration, Resolution],
depsByConfig: Map[Configuration, Seq[Dependency]],
configs: Map[Configuration, Set[Configuration]]
): Set[Dependency] =
allDependenciesByConfig(res, depsByConfig, configs)
.flatMap {
case (config, deps) =>
deps.map(dep => dep.withConfiguration(config --> dep.configuration))
}
.groupBy(_.withConfiguration(Configuration.empty))
.map {
case (dep, l) =>
dep.withConfiguration(Configuration.join(l.map(_.configuration).toSeq: _*))
}
.toSet
def update(
params: UpdateParams,
verbosityLevel: Int,
log: Logger
): UpdateReport = Lock.maybeSynchronized(needsLock = !RefreshLogger.defaultFallbackMode) {
val depsByConfig = grouped(params.dependencies)
if (verbosityLevel >= 2) {
val finalDeps = dependenciesWithConfig(
params.res,
depsByConfig,
params.configs
)
val projCache = params.res.values.foldLeft(Map.empty[ModuleVersion, Project])(_ ++ _.projectCache.mapValues(_._2))
val repr = Print.dependenciesUnknownConfigs(finalDeps.toVector, projCache)
log.info(repr.split('\n').map(" " + _).mkString("\n"))
}
SbtUpdateReport(
params.thisModule,
depsByConfig,
params.res.toVector.sortBy(_._1.value), // FIXME Order by config topologically?
params.interProjectDependencies.toVector,
params.classifiers,
params.artifactFileOpt,
params.fullArtifacts,
log,
includeSignatures = params.includeSignatures,
classpathOrder = params.classpathOrder,
missingOk = params.missingOk,
params.forceVersions,
params.classLoaders,
)
}
private def grouped[K, V](map: Seq[(K, V)]): Map[K, Seq[V]] =
map
.groupBy(_._1)
.mapValues(_.map(_._2))
.iterator
.toMap
}

View File

@ -0,0 +1,209 @@
package lmcoursier
import coursier.cache.CacheDefaults
import lmcoursier.credentials._
import lmcoursier.definitions._
import sbt.librarymanagement.{Resolver, UpdateConfiguration, ModuleID, CrossVersion, ModuleInfo, ModuleDescriptorConfiguration}
import xsbti.Logger
import scala.concurrent.duration.{Duration, FiniteDuration}
import java.io.File
import java.net.URL
import java.net.URLClassLoader
package object syntax {
implicit class CoursierConfigurationModule(value: CoursierConfiguration.type) {
@deprecated("Legacy cache location support was dropped, this method does nothing.", "2.0.0-RC6-10")
def checkLegacyCache(): Unit = ()
def apply(
log: Logger,
resolvers: Vector[Resolver],
parallelDownloads: Int,
maxIterations: Int,
sbtScalaOrganization: String,
sbtScalaVersion: String,
sbtScalaJars: Vector[File],
interProjectDependencies: Vector[Project],
excludeDependencies: Vector[(String, String)],
fallbackDependencies: Vector[FallbackDependency],
autoScalaLibrary: Boolean,
hasClassifiers: Boolean,
classifiers: Vector[String],
mavenProfiles: Vector[String],
scalaOrganization: String,
scalaVersion: String,
authenticationByRepositoryId: Vector[(String, Authentication)],
credentials: Seq[Credentials],
logger: CacheLogger,
cache: File
): CoursierConfiguration =
CoursierConfiguration(
Option(log),
resolvers,
parallelDownloads,
maxIterations,
Option(sbtScalaOrganization),
Option(sbtScalaVersion),
sbtScalaJars,
interProjectDependencies,
excludeDependencies,
fallbackDependencies,
autoScalaLibrary,
hasClassifiers,
classifiers,
mavenProfiles,
Option(scalaOrganization),
Option(scalaVersion),
authenticationByRepositoryId,
credentials,
Option(logger),
Option(cache),
ivyHome = None,
followHttpToHttpsRedirections = None,
strict = None,
extraProjects = Vector.empty,
forceVersions = Vector.empty,
reconciliation = Vector.empty,
classpathOrder = true,
verbosityLevel = 0,
ttl = CacheDefaults.ttl,
checksums = CacheDefaults.checksums.toVector,
cachePolicies = CacheDefaults.cachePolicies.toVector.map(FromCoursier.cachePolicy),
missingOk = false,
sbtClassifiers = false,
providedInCompile = false,
protocolHandlerDependencies = Vector.empty,
retry = None,
sameVersions = Nil,
)
}
implicit class CoursierConfigurationOp(value: CoursierConfiguration) {
def withLog(log: Logger): CoursierConfiguration =
value.withLog(Option(log))
def withSbtScalaOrganization(sbtScalaOrganization: String): CoursierConfiguration =
value.withSbtScalaOrganization(Option(sbtScalaOrganization))
def withSbtScalaVersion(sbtScalaVersion: String): CoursierConfiguration =
value.withSbtScalaVersion(Option(sbtScalaVersion))
def withScalaOrganization(scalaOrganization: String): CoursierConfiguration =
value.withScalaOrganization(Option(scalaOrganization))
def withScalaVersion(scalaVersion: String): CoursierConfiguration =
value.withScalaVersion(Option(scalaVersion))
def withLogger(logger: CacheLogger): CoursierConfiguration =
value.withLogger(Option(logger))
def withCache(cache: File): CoursierConfiguration =
value.withCache(Option(cache))
def withIvyHome(ivyHome: File): CoursierConfiguration =
value.withIvyHome(Option(ivyHome))
def withFollowHttpToHttpsRedirections(followHttpToHttpsRedirections: Boolean): CoursierConfiguration =
value.withFollowHttpToHttpsRedirections(Some(followHttpToHttpsRedirections))
def withFollowHttpToHttpsRedirections(): CoursierConfiguration =
value.withFollowHttpToHttpsRedirections(Some(true))
def withStrict(strict: Strict): CoursierConfiguration =
value.withStrict(Some(strict))
def withTtl(ttl: Duration): CoursierConfiguration =
value.withTtl(Some(ttl))
def addRepositoryAuthentication(repositoryId: String, authentication: Authentication): CoursierConfiguration =
value.withAuthenticationByRepositoryId(value.authenticationByRepositoryId :+ (repositoryId, authentication))
def withUpdateConfiguration(conf: UpdateConfiguration): CoursierConfiguration =
value.withMissingOk(conf.missingOk)
def withRetry(retry: (FiniteDuration, Int)): CoursierConfiguration =
value.withRetry(Some((retry._1, retry._2)))
}
implicit class PublicationOp(value: Publication) {
def attributes: Attributes =
Attributes(value.`type`, value.classifier)
def withAttributes(attributes: Attributes): Publication =
value.withType(attributes.`type`)
.withClassifier(attributes.classifier)
}
implicit class DependencyModule(value: Dependency.type) {
def apply(
module: Module,
version: String,
configuration: Configuration,
exclusions: Set[(Organization, ModuleName)],
attributes: Attributes,
optional: Boolean,
transitive: Boolean
): Dependency =
Dependency(
module,
version,
configuration,
exclusions,
Publication("", attributes.`type`, Extension(""), attributes.classifier),
optional,
transitive
)
}
implicit class DependencyOp(value: Dependency) {
def attributes: Attributes = value.publication.attributes
def withAttributes(attributes: Attributes): Dependency =
value.withPublication(
value.publication
.withType(attributes.`type`)
.withClassifier(attributes.classifier)
)
}
implicit class ModuleMatchersModule(value: ModuleMatchers.type) {
def all: ModuleMatchers =
ModuleMatchers(Set.empty, Set.empty)
def only(organization: String, moduleName: String): ModuleMatchers =
ModuleMatchers(Set.empty, Set(Module(Organization(organization), ModuleName(moduleName), Map())), includeByDefault = false)
def only(mod: Module): ModuleMatchers =
ModuleMatchers(Set.empty, Set(mod), includeByDefault = false)
}
implicit class StrictOp(value: Strict) {
def addInclude(include: (String, String)*): Strict =
value.withInclude(value.include ++ include)
def addExclude(exclude: (String, String)*): Strict =
value.withExclude(value.exclude ++ exclude)
}
implicit class AuthenticationModule(value: Authentication.type) {
def apply(headers: Seq[(String, String)]): Authentication =
Authentication("", "").withHeaders(headers)
}
implicit class DirectCredentialsModule(value: DirectCredentials.type) {
def apply(host: String, username: String, password: String, realm: String): DirectCredentials =
DirectCredentials(host, username, password, Option(realm))
def apply(host: String, username: String, password: String, realm: String, optional: Boolean): DirectCredentials =
DirectCredentials(host, username, password, Option(realm))
}
implicit class DirectCredentialsOp(value: DirectCredentials) {
def withRealm(realm: String): DirectCredentials =
value.withRealm(Option(realm))
}
implicit class CredentialsModule(value: Credentials.type) {
def apply(): DirectCredentials = DirectCredentials()
def apply(host: String, username: String, password: String): DirectCredentials =
DirectCredentials(host, username, password)
def apply(host: String, username: String, password: String, realm: Option[String]): DirectCredentials =
DirectCredentials(host, username, password, realm)
def apply(host: String, username: String, password: String, realm: String): DirectCredentials =
DirectCredentials(host, username, password, Option(realm))
def apply(host: String, username: String, password: String, realm: Option[String], optional: Boolean): DirectCredentials =
DirectCredentials(host, username, password, realm, optional, matchHost = false, httpsOnly = true)
def apply(host: String, username: String, password: String, realm: String, optional: Boolean): DirectCredentials =
DirectCredentials(host, username, password, Option(realm), optional, matchHost = false, httpsOnly = true)
def apply(f: File): FileCredentials =
FileCredentials(f.getAbsolutePath)
def apply(f: File, optional: Boolean): FileCredentials =
FileCredentials(f.getAbsolutePath, optional)
}
}

View File

@ -0,0 +1,43 @@
package lmcoursier
import org.scalatest.matchers.should.Matchers
import org.scalatest.propspec.AnyPropSpec
import sbt.librarymanagement.ModuleID
import sbt.librarymanagement.UpdateConfiguration
import sbt.librarymanagement.UnresolvedWarningConfiguration
import sbt.util.Logger
import sbt.librarymanagement.ModuleInfo
import sbt.librarymanagement.ModuleDescriptorConfiguration
import sbt.librarymanagement.Configuration
class CoursierDependencyResolutionTests extends AnyPropSpec with Matchers {
property("missingOk from passed UpdateConfiguration") {
val depRes = CoursierDependencyResolution(CoursierConfiguration().withAutoScalaLibrary(false))
val desc = ModuleDescriptorConfiguration(ModuleID("test", "foo", "1.0"), ModuleInfo("foo"))
.withDependencies(Vector(
ModuleID("io.get-coursier", "coursier_2.13", "0.1.53").withConfigurations(Some("compile")),
ModuleID("org.scala-lang", "scala-library", "2.12.11").withConfigurations(Some("compile"))
))
.withConfigurations(Vector(Configuration.of("Compile", "compile")))
val module = depRes.moduleDescriptor(desc)
val logger: Logger = new Logger {
def log(level: sbt.util.Level.Value, message: => String): Unit =
System.err.println(s"${level.id} $message")
def success(message: => String): Unit =
System.err.println(message)
def trace(t: => Throwable): Unit =
System.err.println(s"trace $t")
}
depRes.update(module, UpdateConfiguration(), UnresolvedWarningConfiguration(), logger)
.fold(w => (), rep => sys.error(s"Expected resolution to fail, got report $rep"))
val report = depRes.update(module, UpdateConfiguration().withMissingOk(true), UnresolvedWarningConfiguration(), logger)
.fold(w => throw w.resolveException, identity)
}
}

View File

@ -0,0 +1,28 @@
package lmcoursier
import lmcoursier.definitions.{Configuration, Info, Module, ModuleName, Organization, Project}
import org.scalatest.matchers.should.Matchers
import org.scalatest.propspec.AnyPropSpec
class IvyXmlTests extends AnyPropSpec with Matchers {
property("no truncation") {
val project = Project(
Module(Organization("org"), ModuleName("name"), Map()),
"ver",
Nil,
Map(
Configuration("foo") -> (1 to 80).map(n => Configuration("bar" + n)) // long list of configurations -> no truncation any way
),
Nil,
None,
Nil,
Info("", "", Nil, Nil, None)
)
val content = IvyXml(project, Nil, Nil)
assert(!content.contains("</conf>"))
}
}

View File

@ -0,0 +1,251 @@
package lmcoursier
import org.scalatest.matchers.should.Matchers
import org.scalatest.propspec.AnyPropSpec
import sbt.internal.librarymanagement.cross.CrossVersionUtil
import sbt.internal.util.ConsoleLogger
import sbt.librarymanagement._
import sbt.librarymanagement.Configurations.Component
import sbt.librarymanagement.Resolver.{DefaultMavenRepository, JCenterRepository, JavaNet2Repository}
import sbt.librarymanagement.{Resolver, UnresolvedWarningConfiguration, UpdateConfiguration}
import sbt.librarymanagement.syntax._
final class ResolutionSpec extends AnyPropSpec with Matchers {
lazy val log = ConsoleLogger()
def configurations = Vector(Compile, Test, Runtime, Provided, Optional, Component)
def module(
lmEngine: DependencyResolution,
moduleId: ModuleID,
deps: Vector[ModuleID],
scalaFullVersion: Option[String],
overrideScalaVersion: Boolean = true
): ModuleDescriptor = {
val scalaModuleInfo = scalaFullVersion map { fv =>
ScalaModuleInfo(
scalaFullVersion = fv,
scalaBinaryVersion = CrossVersionUtil.binaryScalaVersion(fv),
configurations = configurations,
checkExplicit = true,
filterImplicit = false,
overrideScalaVersion = overrideScalaVersion
)
}
val moduleSetting = ModuleDescriptorConfiguration(moduleId, ModuleInfo("foo"))
.withDependencies(deps)
.withConfigurations(configurations)
.withScalaModuleInfo(scalaModuleInfo)
lmEngine.moduleDescriptor(moduleSetting)
}
def resolvers = Vector(
DefaultMavenRepository,
JavaNet2Repository,
JCenterRepository,
Resolver.sbtPluginRepo("releases")
)
val lmEngine = CoursierDependencyResolution(CoursierConfiguration().withResolvers(resolvers))
private final val stubModule = "com.example" % "foo" % "0.1.0" % "compile"
property("very simple module") {
val dependencies = Vector(
"com.typesafe.scala-logging" % "scala-logging_2.12" % "3.7.2" % "compile",
"org.scalatest" % "scalatest_2.12" % "3.0.4" % "test"
).map(_.withIsTransitive(false))
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.4"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
val r = resolution.toOption.get
r.configurations.map(_.configuration) should have size configurations.length
val compileConfig = r.configurations.find(_.configuration == Compile.toConfigRef).get
compileConfig.modules should have size 1
val runtimeConfig = r.configurations.find(_.configuration == Runtime.toConfigRef).get
runtimeConfig.modules should have size 1
val testConfig = r.configurations.find(_.configuration == Test.toConfigRef).get
testConfig.modules should have size 2
}
property("resolve compiler bridge") {
val dependencies =
Vector(("org.scala-sbt" % "compiler-interface" % "1.0.4" % "component").sources())
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.4"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
val r = resolution.right.get
val componentConfig = r.configurations.find(_.configuration == Component.toConfigRef).get
componentConfig.modules should have size 2
componentConfig.modules.head.artifacts should have size 1
componentConfig.modules.head.artifacts.head._1.classifier should contain("sources")
}
property("resolve sbt jars") {
val dependencies =
Vector("org.scala-sbt" % "sbt" % "1.1.0" % "provided")
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.4"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
val r = resolution.right.get
val modules = r.configurations.flatMap(_.modules)
modules.map(_.module.name) should contain("main_2.12")
}
property("resolve with default resolvers") {
val dependencies =
Vector(("org.scala-sbt" % "compiler-interface" % "1.0.4" % "component").sources())
val lmEngine =
CoursierDependencyResolution(
CoursierConfiguration()
.withResolvers(Resolver.combineDefaultResolvers(Vector.empty))
)
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.4"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
assert(resolution.isRight)
}
/*
property("resolve with resolvers using a custom protocols") {
val sbtModule = "org.scala-sbt" % "sbt" % "1.1.0"
val dependencies = Vector(sbtModule)
val protocolHandlerDependencies = Vector(
"org.example" %% "customprotocol-handler" % "0.1.0"
)
val resolvers = Vector(
"custom" at "customprotocol://host"
)
val configuration =
CoursierConfiguration()
.withResolvers(resolvers)
val protocolHandlerConfiguration =
Some(
CoursierConfiguration()
.withProtocolHandlerDependencies(protocolHandlerDependencies)
.withResolvers(Resolver.combineDefaultResolvers(Vector.empty))
)
val lmEngine =
CoursierDependencyResolution(
configuration = configuration,
protocolHandlerConfiguration = protocolHandlerConfiguration
)
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.13"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
val report = resolution.right.get
val modules = report.configurations.flatMap(_.modules)
modules.map(_.module).map(module => (module.organization, module.name, module.revision)) should contain(
(sbtModule.organization, sbtModule.name, sbtModule.revision)
)
}
property("resolve with resolvers using a custom protocols written in java") {
val sbtModule = "org.scala-sbt" % "sbt" % "1.1.0"
val dependencies = Vector(sbtModule)
val protocolHandlerDependencies = Vector(
"org.example" % "customprotocoljava-handler" % "0.1.0"
)
val resolvers = Vector(
"custom" at "customprotocoljava://host"
)
val configuration =
CoursierConfiguration()
.withResolvers(resolvers)
val protocolHandlerConfiguration =
Some(
CoursierConfiguration()
.withProtocolHandlerDependencies(protocolHandlerDependencies)
.withResolvers(Resolver.combineDefaultResolvers(Vector.empty))
)
val lmEngine =
CoursierDependencyResolution(
configuration = configuration,
protocolHandlerConfiguration = protocolHandlerConfiguration
)
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.13"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
val report = resolution.right.get
val modules = report.configurations.flatMap(_.modules)
modules.map(_.module).map(module => (module.organization, module.name, module.revision)) should contain(
(sbtModule.organization, sbtModule.name, sbtModule.revision)
)
}
*/
property("resolve plugin") {
val pluginAttributes = Map("scalaVersion" -> "2.12", "sbtVersion" -> "1.0")
val dependencies =
Vector(("org.xerial.sbt" % "sbt-sonatype" % "2.0").withExtraAttributes(pluginAttributes))
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.4"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
val r = resolution.right.get
val componentConfig = r.configurations.find(_.configuration == Compile.toConfigRef).get
componentConfig.modules.map(_.module.name) should have size 5
}
property("strip e: prefix from plugin attributes") {
val pluginAttributes = Map("e:scalaVersion" -> "2.12", "e:sbtVersion" -> "1.0")
val dependencies =
Vector(("org.xerial.sbt" % "sbt-sonatype" % "2.0").withExtraAttributes(pluginAttributes))
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.4"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
assert(resolution.isRight)
}
property("resolve plugins hosted on repo.typesafe.com") {
val pluginAttributes = Map("e:scalaVersion" -> "2.12", "e:sbtVersion" -> "1.0")
val dependencies =
Vector(("com.typesafe.sbt" % "sbt-git" % "0.9.3").withExtraAttributes(pluginAttributes))
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.4"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
assert(resolution.isRight)
}
property("resolve licenses from parent poms") {
val dependencies =
Vector(("org.apache.commons" % "commons-compress" % "1.26.2"))
val coursierModule = module(lmEngine, stubModule, dependencies, Some("2.12.4"))
val resolution =
lmEngine.update(coursierModule, UpdateConfiguration(), UnresolvedWarningConfiguration(), log)
assert(resolution.isRight)
val componentConfig = resolution.right.get.configurations.find(_.configuration == Compile.toConfigRef).get
val compress = componentConfig.modules.find(_.module.name == "commons-compress").get
compress.licenses should have size 1
}
}

View File

@ -0,0 +1,60 @@
package coursier
import java.io.{File, FileInputStream}
import java.util.Properties
import lmcoursier.definitions.Authentication
// actually deprecated (all public ways of creating that are)
sealed abstract class Credentials extends Product with Serializable {
def user: String
def password: String
def authentication: Authentication =
Authentication(user, password)
}
object Credentials {
private final case class Direct(user: String, password: String) extends Credentials {
override def toString = s"Direct($user, ******)"
}
private final 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)
}
private object FromFile {
// from sbt.Credentials
private val fileUserKeys = Seq("user", "user.name", "username")
private val filePasswordKeys = Seq("password", "pwd", "pass", "passwd")
}
@deprecated("Use coursierExtraCredentials rather than coursierCredentials", "1.1.0-M14")
def apply(user: String, password: String): Credentials =
Direct(user, password)
@deprecated("Use coursierExtraCredentials rather than coursierCredentials", "1.1.0-M14")
def apply(file: File): Credentials =
FromFile(file)
}

View File

@ -0,0 +1,138 @@
package coursier.sbtcoursiershared
import lmcoursier.definitions.{Classifier, Configuration, Extension, Publication, Type}
import coursier.sbtcoursiershared.Structure._
import sbt.librarymanagement.{Artifact => _, Configuration => _, _}
import sbt.Def
import sbt.Keys._
private[sbtcoursiershared] object ArtifactsTasks {
def coursierPublicationsTask(
configsMap: (sbt.librarymanagement.Configuration, Configuration)*
): Def.Initialize[sbt.Task[Seq[(Configuration, Publication)]]] =
Def.task {
val state = sbt.Keys.state.value
val projectRef = sbt.Keys.thisProjectRef.value
val projId = sbt.Keys.projectID.value
val sv = sbt.Keys.scalaVersion.value
val sbv = sbt.Keys.scalaBinaryVersion.value
val ivyConfs = sbt.Keys.ivyConfigurations.value
val sourcesConfigOpt =
if (ivyConfigurations.value.exists(_.name == "sources"))
Some(Configuration("sources"))
else
None
val docsConfigOpt =
if (ivyConfigurations.value.exists(_.name == "docs"))
Some(Configuration("docs"))
else
None
val sbtBinArtifacts =
for ((config, targetConfig) <- configsMap) yield {
val publish = publishArtifact
.in(projectRef)
.in(packageBin)
.in(config)
.getOrElse(state, false)
if (publish)
artifact
.in(projectRef)
.in(packageBin)
.in(config)
.find(state)
.map(targetConfig -> _)
else
None
}
val sbtSourceArtifacts =
for ((config, targetConfig) <- configsMap) yield {
val publish = publishArtifact
.in(projectRef)
.in(packageSrc)
.in(config)
.getOrElse(state, false)
if (publish)
artifact
.in(projectRef)
.in(packageSrc)
.in(config)
.find(state)
.map(sourcesConfigOpt.getOrElse(targetConfig) -> _)
else
None
}
val sbtDocArtifacts =
for ((config, targetConfig) <- configsMap) yield {
val publish = publishArtifact
.in(projectRef)
.in(packageDoc)
.in(config)
.getOrElse(state, false)
if (publish)
artifact
.in(projectRef)
.in(packageDoc)
.in(config)
.find(state)
.map(docsConfigOpt.getOrElse(targetConfig) -> _)
else
None
}
val sbtArtifacts = sbtBinArtifacts ++ sbtSourceArtifacts ++ sbtDocArtifacts
def artifactPublication(artifact: sbt.Artifact) = {
val name = CrossVersion(projId.crossVersion, sv, sbv)
.fold(artifact.name)(_(artifact.name))
Publication(
name,
Type(artifact.`type`),
Extension(artifact.extension),
artifact.classifier.fold(Classifier(""))(Classifier(_))
)
}
val sbtArtifactsPublication = sbtArtifacts.collect {
case Some((config, artifact)) =>
config -> artifactPublication(artifact)
}
val stdArtifactsSet = sbtArtifacts.flatMap(_.map { case (_, a) => a }.toSeq).toSet
// Second-way of getting artifacts from SBT
// No obvious way of getting the corresponding publishArtifact value for the ones
// only here, it seems.
val extraSbtArtifacts = sbt.Keys.artifacts.in(projectRef).getOrElse(state, Nil)
.filterNot(stdArtifactsSet)
// Seems that SBT does that - if an artifact has no configs,
// it puts it in all of them. See for example what happens to
// the standalone JAR artifact of the coursier cli module.
def allConfigsIfEmpty(configs: Iterable[ConfigRef]): Iterable[ConfigRef] =
if (configs.isEmpty) ivyConfs.filter(_.isPublic).map(c => ConfigRef(c.name)) else configs
val extraSbtArtifactsPublication = for {
artifact <- extraSbtArtifacts
config <- allConfigsIfEmpty(artifact.configurations.map(x => ConfigRef(x.name)))
// FIXME If some configurations from artifact.configurations are not public, they may leak here :\
} yield Configuration(config.name) -> artifactPublication(artifact)
sbtArtifactsPublication ++ extraSbtArtifactsPublication
}
}

View File

@ -0,0 +1,270 @@
package coursier.sbtcoursiershared
import lmcoursier.definitions.{Attributes, Classifier, Configuration, Dependency, Extension, Info, Module, ModuleName, Organization, Project, Publication, Strict, Type}
import lmcoursier.{FallbackDependency, FromSbt, Inputs}
import coursier.sbtcoursiershared.SbtCoursierShared.autoImport._
import coursier.sbtcoursiershared.Structure._
import lmcoursier.credentials.DirectCredentials
import sbt.{Def, SettingKey}
import sbt.Keys._
import sbt.librarymanagement.{ConflictManager, InclExclRule, ModuleID}
import sbt.util.Logger
import scala.collection.JavaConverters._
import scala.language.reflectiveCalls
object InputsTasks {
lazy val actualExcludeDependencies =
try {
sbt.Keys
.asInstanceOf[{ def allExcludeDependencies: SettingKey[scala.Seq[InclExclRule]] }]
.allExcludeDependencies
} catch {
case _: NoSuchMethodException =>
excludeDependencies
}
private def coursierProject0(
projId: ModuleID,
dependencies: Seq[ModuleID],
configurations: Seq[sbt.librarymanagement.Configuration],
sv: String,
sbv: String,
log: Logger
): Project = {
val configMap = Inputs.configExtendsSeq(configurations).toMap
FromSbt.project(
projId,
dependencies,
configMap,
sv,
sbv
)
}
private[sbtcoursiershared] def coursierProjectTask: Def.Initialize[sbt.Task[Project]] =
Def.taskDyn {
val state = sbt.Keys.state.value
val projectRef = sbt.Keys.thisProjectRef.value
val allDependenciesTask = allDependencies.in(projectRef).get(state)
Def.task {
coursierProject0(
projectID.in(projectRef).get(state),
allDependenciesTask.value,
// should projectID.configurations be used instead?
ivyConfigurations.in(projectRef).get(state),
scalaVersion.in(projectRef).get(state),
scalaBinaryVersion.in(projectRef).get(state),
state.log
)
}
}
private def moduleFromIvy(id: org.apache.ivy.core.module.id.ModuleRevisionId): Module =
Module(
Organization(id.getOrganisation),
ModuleName(id.getName),
id.getExtraAttributes
.asScala
.map {
case (k0, v0) => k0.asInstanceOf[String] -> v0.asInstanceOf[String]
}
.toMap
)
private def dependencyFromIvy(desc: org.apache.ivy.core.module.descriptor.DependencyDescriptor): Seq[(Configuration, Dependency)] = {
val id = desc.getDependencyRevisionId
val module = moduleFromIvy(id)
val exclusions = desc
.getAllExcludeRules
.map { rule =>
// we're ignoring rule.getConfigurations and rule.getMatcher here
val modId = rule.getId.getModuleId
// we're ignoring modId.getAttributes here
(Organization(modId.getOrganisation), ModuleName(modId.getName))
}
.toSet
val configurations = desc
.getModuleConfigurations
.toVector
.flatMap(Inputs.ivyXmlMappings)
def dependency(conf: Configuration, pub: Publication) = Dependency(
module,
id.getRevision,
conf,
exclusions,
pub,
optional = false,
desc.isTransitive
)
val publications: Configuration => Publication = {
val artifacts = desc.getAllDependencyArtifacts
val m = artifacts.toVector.flatMap { art =>
val pub = Publication(art.getName, Type(art.getType), Extension(art.getExt), Classifier(""))
art.getConfigurations.map(Configuration(_)).toVector.map { conf =>
conf -> pub
}
}.toMap
c => m.getOrElse(c, Publication("", Type(""), Extension(""), Classifier("")))
}
configurations.map {
case (from, to) =>
from -> dependency(to, publications(to))
}
}
private[sbtcoursiershared] def coursierInterProjectDependenciesTask: Def.Initialize[sbt.Task[Seq[Project]]] =
Def.taskDyn {
val state = sbt.Keys.state.value
val projectRef = sbt.Keys.thisProjectRef.value
val projectRefs = Structure.allRecursiveInterDependencies(state, projectRef)
val t = coursierProject.forAllProjectsOpt(state, projectRefs :+ projectRef)
Def.task {
t.value.toVector.flatMap {
case (ref, None) =>
if (ref.build != projectRef.build)
state.log.warn(s"Cannot get coursier info for project under ${ref.build}, is sbt-coursier also added to it?")
Nil
case (_, Some(p)) =>
Seq(p)
}
}
}
private[sbtcoursiershared] def coursierExtraProjectsTask: Def.Initialize[sbt.Task[Seq[Project]]] =
Def.task {
val projects = coursierInterProjectDependencies.value
val projectModules = projects.map(_.module).toSet
// this includes org.scala-sbt:global-plugins referenced from meta-builds in particular
sbt.Keys.projectDescriptors.value
.map {
case (k, v) =>
moduleFromIvy(k) -> v
}
.filter {
case (module, _) =>
!projectModules(module)
}
.toVector
.map {
case (module, v) =>
val configurations = v
.getConfigurations
.map { c =>
Configuration(c.getName) -> c.getExtends.map(Configuration(_)).toSeq
}
.toMap
val deps = v.getDependencies.flatMap(dependencyFromIvy)
Project(
module,
v.getModuleRevisionId.getRevision,
deps,
configurations,
Nil,
None,
Nil,
Info("", "", Nil, Nil, None)
)
}
}
private[sbtcoursiershared] def coursierFallbackDependenciesTask: Def.Initialize[sbt.Task[Seq[FallbackDependency]]] =
Def.taskDyn {
val state = sbt.Keys.state.value
val projectRef = sbt.Keys.thisProjectRef.value
val projects = allRecursiveInterDependencies(state, projectRef)
val allDependenciesTask = allDependencies
.forAllProjects(state, projectRef +: projects)
.map(_.values.toVector.flatten)
Def.task {
val allDependencies = allDependenciesTask.value
FromSbt.fallbackDependencies(
allDependencies,
scalaVersion.in(projectRef).get(state),
scalaBinaryVersion.in(projectRef).get(state)
)
}
}
val credentialsTask = Def.taskDyn {
val useSbtCredentials = coursierUseSbtCredentials.value
val fromSbt =
if (useSbtCredentials)
Def.task {
val log = streams.value.log
sbt.Keys.credentials.value
.flatMap {
case dc: sbt.DirectCredentials => List(dc)
case fc: sbt.FileCredentials =>
sbt.Credentials.loadCredentials(fc.path) match {
case Left(err) =>
log.warn(s"$err, ignoring it")
Nil
case Right(dc) => List(dc)
}
}
.map { c =>
DirectCredentials()
.withHost(c.host)
.withUsername(c.userName)
.withPassword(c.passwd)
.withRealm(Some(c.realm).filter(_.nonEmpty))
.withHttpsOnly(false)
.withMatchHost(true)
}
}
else
Def.task(Seq.empty[DirectCredentials])
Def.task {
fromSbt.value ++ coursierExtraCredentials.value
}
}
def strictTask = Def.task {
val cm = conflictManager.value
val log = streams.value.log
cm.name match {
case ConflictManager.latestRevision.name =>
None
case ConflictManager.strict.name =>
val strict = Strict()
.withInclude(Set((cm.organization, cm.module)))
Some(strict)
case other =>
log.warn(s"Unsupported conflict manager $other")
None
}
}
}

View File

@ -0,0 +1,109 @@
package coursier.sbtcoursiershared
import java.io.File
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.Files
import lmcoursier.{Inputs, IvyXml}
import lmcoursier.definitions.{Configuration, Project}
import org.apache.ivy.core.module.id.ModuleRevisionId
import sbt.{Def, Setting, Task, TaskKey}
import sbt.internal.librarymanagement.IvySbt
import sbt.librarymanagement.{CrossVersion, PublishConfiguration}
import scala.collection.JavaConverters._
object IvyXmlGeneration {
// These are required for publish to be fine, later on.
private def writeFiles(
currentProject: Project,
exclusions: Seq[(String, String)],
overrides: Seq[(String, String, String)],
ivySbt: IvySbt,
log: sbt.util.Logger
): File = {
val ivyCacheManager = ivySbt.withIvy(log)(ivy =>
ivy.getResolutionCacheManager
)
val ivyModule = ModuleRevisionId.newInstance(
currentProject.module.organization.value,
currentProject.module.name.value,
currentProject.version,
currentProject.module.attributes.asJava
)
val cacheIvyFile = ivyCacheManager.getResolvedIvyFileInCache(ivyModule)
val cacheIvyPropertiesFile = ivyCacheManager.getResolvedIvyPropertiesInCache(ivyModule)
val content0 = IvyXml(currentProject, exclusions, overrides)
cacheIvyFile.getParentFile.mkdirs()
log.info(s"Writing Ivy file $cacheIvyFile")
Files.write(cacheIvyFile.toPath, content0.getBytes(UTF_8))
// Just writing an empty file here... Are these only used?
cacheIvyPropertiesFile.getParentFile.mkdirs()
Files.write(cacheIvyPropertiesFile.toPath, Array.emptyByteArray)
cacheIvyFile
}
def writeIvyXml: Def.Initialize[Task[File]] =
Def.task {
import SbtCoursierShared.autoImport._
val sv = sbt.Keys.scalaVersion.value
val sbv = sbt.Keys.scalaBinaryVersion.value
val log = sbt.Keys.streams.value.log
val currentProject = {
val proj = coursierProject.value
val publications = coursierPublications.value
proj.withPublications(publications)
}
val overrides = Inputs.forceVersions(sbt.Keys.dependencyOverrides.value, sv, sbv).map {
case (mod, ver) =>
(mod.organization.value, mod.name.value, ver)
}
val excludeDeps = Inputs.exclusionsSeq(InputsTasks.actualExcludeDependencies.value, sv, sbv, log)
.map {
case (org, name) =>
(org.value, name.value)
}
writeFiles(currentProject, excludeDeps, overrides, sbt.Keys.ivySbt.value, log)
}
private def makeIvyXmlBefore[T](task: TaskKey[T]): Setting[Task[T]] =
task := task.dependsOn {
Def.taskDyn {
import SbtCoursierShared.autoImport._
val doGen = coursierGenerateIvyXml.value
if (doGen)
Def.task {
coursierWriteIvyXml.value
()
}
else
Def.task(())
}
}.value
private lazy val needsIvyXmlLocal = Seq(sbt.Keys.publishLocalConfiguration) ++ getPubConf("makeIvyXmlLocalConfiguration")
private lazy val needsIvyXml = Seq(sbt.Keys.publishConfiguration) ++ getPubConf("makeIvyXmlConfiguration")
private[this] def getPubConf(method: String): List[TaskKey[PublishConfiguration]] =
try {
val cls = sbt.Keys.getClass
val m = cls.getMethod(method)
val task = m.invoke(sbt.Keys).asInstanceOf[TaskKey[PublishConfiguration]]
List(task)
} catch {
case _: Throwable => // FIXME Too wide
Nil
}
def generateIvyXmlSettings: Seq[Setting[_]] =
(needsIvyXml ++ needsIvyXmlLocal).map(makeIvyXmlBefore)
}

View File

@ -0,0 +1,25 @@
package coursier.sbtcoursiershared
import java.util.{Properties => JProperties}
object Properties {
private lazy val props = {
val p = new JProperties
try {
p.load(
getClass
.getClassLoader
.getResourceAsStream("coursier/sbtcoursier.properties")
)
}
catch {
case _: NullPointerException =>
}
p
}
lazy val version = props.getProperty("version")
lazy val commitHash = props.getProperty("commit-hash")
}

View File

@ -0,0 +1,111 @@
package coursier.sbtcoursiershared
import coursier.sbtcoursiershared.SbtCoursierShared.autoImport._
import coursier.sbtcoursiershared.Structure._
import sbt.{Classpaths, Def}
import sbt.Keys._
import sbt.librarymanagement.{Resolver, URLRepository}
private[sbtcoursiershared] object RepositoriesTasks {
private object Resolvers {
private val slowReposBase = Seq(
"https://repo.typesafe.com/",
"https://repo.scala-sbt.org/",
"http://repo.typesafe.com/",
"http://repo.scala-sbt.org/"
)
private val fastReposBase = Seq(
"http://repo1.maven.org/",
"https://repo1.maven.org/"
)
private def url(res: Resolver): Option[String] =
res match {
case m: sbt.librarymanagement.MavenRepository =>
Some(m.root)
case u: URLRepository =>
u.patterns.artifactPatterns.headOption
.orElse(u.patterns.ivyPatterns.headOption)
case _ =>
None
}
private def fastRepo(res: Resolver): Boolean =
url(res).exists(u => fastReposBase.exists(u.startsWith))
private def slowRepo(res: Resolver): Boolean =
url(res).exists(u => slowReposBase.exists(u.startsWith))
def reorderResolvers(resolvers: Seq[Resolver]): Seq[Resolver] =
if (resolvers.exists(fastRepo) && resolvers.exists(slowRepo)) {
val (slow, other) = resolvers.partition(slowRepo)
other ++ slow
} else
resolvers
}
private def resultTask(bootResOpt: Option[Seq[Resolver]], overrideFlag: Boolean): Def.Initialize[sbt.Task[Seq[Resolver]]] =
bootResOpt.filter(_ => overrideFlag) match {
case Some(r) => Def.task(r)
case None =>
Def.taskDyn {
val extRes = externalResolvers.value
val isSbtPlugin = sbtPlugin.value
if (isSbtPlugin)
Def.task {
Seq(
sbtResolver.value,
Classpaths.sbtPluginReleases
) ++ extRes
}
else
Def.task(extRes)
}
}
def coursierResolversTask: Def.Initialize[sbt.Task[Seq[Resolver]]] =
Def.taskDyn {
val bootResOpt = bootResolvers.value
val overrideFlag = overrideBuildResolvers.value
Def.task {
val result = resultTask(bootResOpt, overrideFlag).value
val reorderResolvers = coursierReorderResolvers.value
val keepPreloaded = coursierKeepPreloaded.value
val result0 =
if (reorderResolvers)
Resolvers.reorderResolvers(result)
else
result
if (keepPreloaded)
result0
else
result0.filter { r =>
!r.name.startsWith("local-preloaded")
}
}
}
def coursierRecursiveResolversTask: Def.Initialize[sbt.Task[Seq[Resolver]]] =
Def.taskDyn {
val state = sbt.Keys.state.value
val projectRef = sbt.Keys.thisProjectRef.value
val projects = allRecursiveInterDependencies(state, projectRef)
val t = coursierResolvers
.forAllProjects(state, projectRef +: projects)
.map(_.values.toVector.flatten)
Def.task(t.value)
}
}

View File

@ -0,0 +1,194 @@
package coursier.sbtcoursiershared
import java.io.File
import coursier.{Credentials => LegacyCredentials}
import lmcoursier.credentials.Credentials
import lmcoursier.{CoursierDependencyResolution, FallbackDependency}
import lmcoursier.definitions.{CacheLogger, Configuration, Project, Publication}
import lmcoursier.internal.SbtCoursierCache
import lmcoursier.syntax._
import sbt.{AutoPlugin, Classpaths, Compile, Setting, TaskKey, Test, settingKey, taskKey}
import sbt.Keys._
import sbt.librarymanagement.DependencyBuilders.OrganizationArtifactName
import sbt.librarymanagement.{ModuleID, Resolver, URLRepository}
import scala.concurrent.duration.FiniteDuration
object SbtCoursierShared extends AutoPlugin {
override def trigger = allRequirements
override def requires = sbt.plugins.JvmPlugin
object autoImport {
val coursierGenerateIvyXml = settingKey[Boolean]("")
val coursierWriteIvyXml = taskKey[File]("")
val coursierProject = TaskKey[Project]("coursier-project")
val coursierInterProjectDependencies = TaskKey[Seq[Project]]("coursier-inter-project-dependencies", "Projects the current project depends on, possibly transitively")
val coursierExtraProjects = TaskKey[Seq[Project]]("coursier-extra-projects", "")
val coursierPublications = TaskKey[Seq[(Configuration, Publication)]]("coursier-publications")
val coursierKeepPreloaded = settingKey[Boolean]("Whether to take into account sbt preloaded repositories or not")
val coursierReorderResolvers = settingKey[Boolean](
"Whether resolvers should be re-ordered so that typically slow ones are given a lower priority"
)
val coursierResolvers = taskKey[Seq[Resolver]]("")
val coursierRecursiveResolvers = taskKey[Seq[Resolver]]("Resolvers of the current project, plus those of all from its inter-dependency projects")
val coursierSbtResolvers = taskKey[Seq[Resolver]]("")
val coursierFallbackDependencies = taskKey[Seq[FallbackDependency]]("")
val mavenProfiles = settingKey[Set[String]]("")
val versionReconciliation = taskKey[Seq[ModuleID]]("")
private[coursier] val actualCoursierCredentials = TaskKey[Map[String, LegacyCredentials]]("coursierCredentials", "")
val coursierUseSbtCredentials = settingKey[Boolean]("")
@deprecated("Use coursierExtraCredentials rather than coursierCredentials", "1.1.0-M14")
val coursierCredentials = actualCoursierCredentials
val coursierExtraCredentials = taskKey[Seq[Credentials]]("")
val coursierLogger = taskKey[Option[CacheLogger]]("")
val coursierCache = settingKey[File]("")
val sbtCoursierVersion = Properties.version
val coursierRetry = taskKey[Option[(FiniteDuration, Int)]]("Retry for downloading dependencies")
}
import autoImport._
def publicationsSetting(packageConfigs: Seq[(sbt.Configuration, Configuration)]): Setting[_] =
coursierPublications := ArtifactsTasks.coursierPublicationsTask(packageConfigs: _*).value
override def globalSettings: Seq[Setting[_]] =
Seq(
coursierUseSbtCredentials := true,
actualCoursierCredentials := Map.empty,
coursierExtraCredentials := Nil
)
override def buildSettings: Seq[Setting[_]] =
Seq(
coursierReorderResolvers := true,
coursierKeepPreloaded := false,
coursierLogger := None,
coursierCache := CoursierDependencyResolution.defaultCacheLocation,
coursierRetry := None
)
private val pluginIvySnapshotsBase = Resolver.SbtRepositoryRoot.stripSuffix("/") + "/ivy-snapshots"
override def projectSettings = settings(pubSettings = true)
def settings(pubSettings: Boolean) =
Seq[Setting[_]](
clean := {
val noWarningPlz = clean.value
SbtCoursierCache.default.clear()
},
onUnload := {
SbtCoursierCache.default.clear()
onUnload.value
},
coursierGenerateIvyXml := true,
coursierWriteIvyXml := IvyXmlGeneration.writeIvyXml.value,
coursierProject := InputsTasks.coursierProjectTask.value,
coursierInterProjectDependencies := InputsTasks.coursierInterProjectDependenciesTask.value,
coursierExtraProjects := InputsTasks.coursierExtraProjectsTask.value
) ++ {
if (pubSettings)
Seq(
publicationsSetting(Seq(Compile, Test).map(c => c -> Configuration(c.name)))
)
else
Nil
} ++ Seq(
// Tests artifacts from Maven repositories are given this type.
// Adding it here so that these work straightaway.
classpathTypes += "test-jar", // FIXME Should this go in buildSettings?
coursierResolvers := RepositoriesTasks.coursierResolversTask.value,
coursierRecursiveResolvers := RepositoriesTasks.coursierRecursiveResolversTask.value,
coursierSbtResolvers := {
// TODO Add docker-based integration test for that, see https://github.com/coursier/coursier/issues/632
val resolvers =
sbt.Classpaths.bootRepositories(appConfiguration.value).toSeq.flatten ++ // required because of the hack above it seems
externalResolvers.in(updateSbtClassifiers).value
val pluginIvySnapshotsFound = resolvers.exists {
case repo: URLRepository =>
repo
.patterns
.artifactPatterns
.headOption
.exists(_.startsWith(pluginIvySnapshotsBase))
case _ => false
}
val resolvers0 =
if (pluginIvySnapshotsFound && !resolvers.contains(Classpaths.sbtPluginReleases))
resolvers :+ Classpaths.sbtPluginReleases
else
resolvers
if (SbtCoursierShared.autoImport.coursierKeepPreloaded.value)
resolvers0
else
resolvers0.filter { r =>
!r.name.startsWith("local-preloaded")
}
},
coursierFallbackDependencies := InputsTasks.coursierFallbackDependenciesTask.value,
ivyConfigurations := {
val confs = ivyConfigurations.value
val names = confs.map(_.name).toSet
// Yes, adding those back in sbt 1.0. Can't distinguish between config test (whose jars with classifier tests ought to
// be added), and sources / docs else (if their JARs are in compile, they would get added too then).
val extraSources =
if (names("sources"))
None
else
Some(
sbt.Configuration.of(
id = "Sources",
name = "sources",
description = "",
isPublic = true,
extendsConfigs = Vector.empty,
transitive = false
)
)
val extraDocs =
if (names("docs"))
None
else
Some(
sbt.Configuration.of(
id = "Docs",
name = "docs",
description = "",
isPublic = true,
extendsConfigs = Vector.empty,
transitive = false
)
)
confs ++ extraSources.toSeq ++ extraDocs.toSeq
},
mavenProfiles := Set.empty,
versionReconciliation := Seq.empty,
coursierRetry := None
) ++ {
if (pubSettings)
IvyXmlGeneration.generateIvyXmlSettings
else
Nil
}
}

View File

@ -0,0 +1,74 @@
package coursier.sbtcoursiershared
import sbt._
object Structure {
def allRecursiveInterDependencies(state: sbt.State, projectRef: sbt.ProjectRef) = {
def dependencies(map: Map[String, Seq[String]], id: String): Set[String] = {
def helper(map: Map[String, Seq[String]], acc: Set[String]): Set[String] =
if (acc.exists(map.contains)) {
val (kept, rem) = map.partition { case (k, _) => acc(k) }
helper(rem, acc ++ kept.valuesIterator.flatten)
} else
acc
helper(map - id, map.getOrElse(id, Nil).toSet)
}
val allProjectsDeps =
for (p <- structure(state).allProjects)
yield p.id -> p.dependencies.map(_.project.project)
val deps = dependencies(allProjectsDeps.toMap, projectRef.project)
structure(state).allProjectRefs.filter(p => deps(p.project))
}
// vv things from sbt-structure vv
def structure(state: State) =
sbt.Project.structure(state)
implicit class `enrich SettingKey`[T](key: SettingKey[T]) {
def find(state: State): Option[T] =
key.get(structure(state).data)
def get(state: State): T =
find(state).get
def getOrElse(state: State, default: => T): T =
find(state).getOrElse(default)
}
implicit class `enrich TaskKey`[T](key: TaskKey[T]) {
def find(state: State): Option[sbt.Task[T]] =
key.get(structure(state).data)
def get(state: State): sbt.Task[T] =
find(state).get
def forAllProjects(state: State, projects: Seq[ProjectRef]): sbt.Task[Map[ProjectRef, T]] = {
val tasks = projects.flatMap(p => key.in(p).get(structure(state).data).map(_.map(it => (p, it))))
std.TaskExtra.joinTasks(tasks).join.map(_.toMap)
}
// ^^ things from sbt-structure ^^
def forAllProjectsOpt(state: State, projects: Seq[ProjectRef]): sbt.Task[Map[ProjectRef, Option[T]]] = {
val settings = structure(state).data
val tasks = projects.map { p =>
val taskOpt = key.in(p).get(settings)
taskOpt match {
case None =>
Def.task(p -> Option.empty[T]).evaluate(settings)
case Some(t) =>
t.map(p -> Option(_))
}
}
std.TaskExtra.joinTasks(tasks).join.map(_.toMap)
}
}
}

View File

@ -0,0 +1,93 @@
package coursier.sbtcoursier
import java.io.File
import coursier.cache.FileCache
import coursier.core._
import coursier.util.Artifact
import lmcoursier.internal.{ArtifactsParams, ArtifactsRun}
import coursier.sbtcoursier.Keys._
import coursier.sbtcoursiershared.InputsTasks.credentialsTask
import coursier.sbtcoursiershared.SbtCoursierShared.autoImport.{coursierCache, coursierLogger}
import lmcoursier.definitions.ToCoursier
import sbt.Def
import sbt.Keys._
object ArtifactsTasks {
def artifactsTask(
withClassifiers: Boolean,
sbtClassifiers: Boolean = false,
ignoreArtifactErrors: Boolean = false,
includeSignatures: Boolean = false
): Def.Initialize[sbt.Task[Map[Artifact, File]]] = {
val resTask: sbt.Def.Initialize[sbt.Task[Seq[Resolution]]] =
if (withClassifiers && sbtClassifiers)
Def.task(coursierSbtClassifiersResolutions.value.values.toVector)
else
Def.task(coursierResolutions.value.values.toVector)
val classifiersTask: sbt.Def.Initialize[sbt.Task[Option[Seq[Classifier]]]] =
if (withClassifiers) {
if (sbtClassifiers)
Def.task(Some(coursierSbtClassifiersModule.value.classifiers.map(Classifier(_))))
else
Def.task(Some(transitiveClassifiers.value.map(Classifier(_))))
} else
Def.task(None)
Def.task {
val projectName = thisProjectRef.value.project
val parallelDownloads = coursierParallelDownloads.value
val artifactsChecksums = coursierArtifactsChecksums.value
val cachePolicies = coursierCachePolicies.value
val ttl = coursierTtl.value
val cache = coursierCache.value
val createLogger = coursierLogger.value.map(ToCoursier.cacheLogger)
val credentials = credentialsTask.value.map(ToCoursier.credentials)
val log = streams.value.log
val verbosityLevel = coursierVerbosity.value
val classifiers = classifiersTask.value
val res = resTask.value
val params = ArtifactsParams(
classifiers = classifiers,
resolutions = res,
includeSignatures = includeSignatures,
loggerOpt = createLogger,
projectName = projectName,
sbtClassifiers = sbtClassifiers,
cache = FileCache()
.withLocation(cache)
.withChecksums(artifactsChecksums)
.withTtl(ttl)
.withCachePolicies(cachePolicies)
.withCredentials(credentials)
.withFollowHttpToHttpsRedirections(true),
parallel = parallelDownloads,
classpathOrder = true,
missingOk = sbtClassifiers
)
val resOrError = ArtifactsRun(
params,
verbosityLevel,
log
)
resOrError match {
case Left(err) =>
throw err
case Right(res0) =>
res0.artifacts.toMap
}
}
}
}

View File

@ -0,0 +1,195 @@
package coursier.sbtcoursier
import coursier.cache.CacheDefaults
import coursier.core.{Configuration, ResolutionProcess}
import coursier.sbtcoursiershared.SbtCoursierShared
import sbt.{Cache => _, Configuration => _, _}
import sbt.Keys._
object CoursierPlugin extends AutoPlugin {
override def trigger = allRequirements
override def requires = SbtCoursierShared
import SbtCoursierShared.autoImport._
object autoImport {
val coursierParallelDownloads = Keys.coursierParallelDownloads
val coursierMaxIterations = Keys.coursierMaxIterations
val coursierChecksums = Keys.coursierChecksums
val coursierArtifactsChecksums = Keys.coursierArtifactsChecksums
val coursierCachePolicies = Keys.coursierCachePolicies
val coursierTtl = Keys.coursierTtl
val coursierVerbosity = Keys.coursierVerbosity
val coursierConfigGraphs = Keys.coursierConfigGraphs
val coursierSbtClassifiersModule = Keys.coursierSbtClassifiersModule
val coursierConfigurations = Keys.coursierConfigurations
val coursierParentProjectCache = Keys.coursierParentProjectCache
val coursierResolutions = Keys.coursierResolutions
val coursierSbtClassifiersResolutions = Keys.coursierSbtClassifiersResolutions
val coursierDependencyTree = Keys.coursierDependencyTree
val coursierDependencyInverseTree = Keys.coursierDependencyInverseTree
val coursierWhatDependsOn = Keys.coursierWhatDependsOn
val coursierArtifacts = Keys.coursierArtifacts
val coursierSignedArtifacts = Keys.coursierSignedArtifacts
val coursierClassifiersArtifacts = Keys.coursierClassifiersArtifacts
val coursierSbtClassifiersArtifacts = Keys.coursierSbtClassifiersArtifacts
@deprecated("Use sbtCoursierVersion instead", "1.1.0-M9")
val coursierVersion = sbtCoursierVersion
val addSbtCoursier: Seq[Def.Setting[_]] = {
import sbt._
Seq(
addSbtPlugin("io.get-coursier" % "sbt-coursier" % sbtCoursierVersion),
// seems needed for some sbt plugins (https://github.com/coursier/coursier/issues/450)
classpathTypes += "maven-plugin"
)
}
}
import autoImport._
lazy val treeSettings = Seq(
coursierDependencyTree := DisplayTasks.coursierDependencyTreeTask(
inverse = false
).value,
coursierDependencyInverseTree := DisplayTasks.coursierDependencyTreeTask(
inverse = true
).value,
coursierWhatDependsOn := Def.inputTaskDyn {
import sbt.complete.DefaultParsers._
val input = token(SpaceClass ~ NotQuoted, "<arg>").parsed._2
DisplayTasks.coursierWhatDependsOnTask(input)
}.evaluated
)
// allows to get the actual repo list when sbt starts up
private val hackHack = Seq(
// TODO Add docker-based non reg test for that, with sbt-assembly 0.14.5 in ~/.sbt/1.0/plugins/plugins.sbt
// along with the required extra repo https://repository.jboss.org/nexus/content/repositories/public
// (required for coursier, because of bad checksums on central)
appConfiguration.in(updateSbtClassifiers) := {
val app = appConfiguration.in(updateSbtClassifiers).value
// hack to trigger https://github.com/sbt/sbt/blob/v1.0.1/main/src/main/scala/sbt/Defaults.scala#L2856,
// to have the third case be used instead of the second one, at https://github.com/sbt/sbt/blob/v1.0.1/main/src/main/scala/sbt/Defaults.scala#L2069
new xsbti.AppConfiguration {
def provider() = {
import scala.language.reflectiveCalls
val prov = app.provider()
val noWarningForDeprecatedStuffProv = prov.asInstanceOf[{
def mainClass(): Class[_ <: xsbti.AppMain]
}]
new xsbti.AppProvider {
def newMain() = prov.newMain()
def components() = prov.components()
def mainClass() = noWarningForDeprecatedStuffProv.mainClass()
def mainClasspath() = prov.mainClasspath()
def loader() = prov.loader()
def scalaProvider() = {
val scalaProv = prov.scalaProvider()
val noWarningForDeprecatedStuffScalaProv = scalaProv.asInstanceOf[{
def libraryJar(): File
def compilerJar(): File
}]
new xsbti.ScalaProvider {
def app(id: xsbti.ApplicationID) = scalaProv.app(id)
def loader() = scalaProv.loader()
def jars() = scalaProv.jars()
def libraryJar() = noWarningForDeprecatedStuffScalaProv.libraryJar()
def version() = scalaProv.version()
def compilerJar() = noWarningForDeprecatedStuffScalaProv.compilerJar()
def launcher() = {
val launch = scalaProv.launcher()
new xsbti.Launcher {
def app(id: xsbti.ApplicationID, version: String) = launch.app(id, version)
def checksums() = launch.checksums()
def globalLock() = launch.globalLock()
def bootDirectory() = launch.bootDirectory()
def appRepositories() = launch.appRepositories()
def topLoader() = launch.topLoader()
def getScala(version: String) = launch.getScala(version)
def getScala(version: String, reason: String) = launch.getScala(version, reason)
def getScala(version: String, reason: String, scalaOrg: String) = launch.getScala(version, reason, scalaOrg)
def isOverrideRepositories = launch.isOverrideRepositories
def ivyRepositories() =
throw new NoSuchMethodError("nope")
def ivyHome() = launch.ivyHome()
}
}
}
}
def entryPoint() = prov.entryPoint()
def id() = prov.id()
}
}
def arguments() = app.arguments()
def baseDirectory() = app.baseDirectory()
}
}
)
def coursierSettings: Seq[Setting[_]] = hackHack ++ Seq(
coursierArtifacts := ArtifactsTasks.artifactsTask(withClassifiers = false).value,
coursierSignedArtifacts := ArtifactsTasks.artifactsTask(withClassifiers = false, includeSignatures = true).value,
coursierClassifiersArtifacts := ArtifactsTasks.artifactsTask(
withClassifiers = true
).value,
coursierSbtClassifiersArtifacts := ArtifactsTasks.artifactsTask(
withClassifiers = true,
sbtClassifiers = true
).value,
update := UpdateTasks.updateTask(withClassifiers = false).value,
updateClassifiers := UpdateTasks.updateTask(withClassifiers = true).value,
updateSbtClassifiers.in(Defaults.TaskGlobal) := UpdateTasks.updateTask(withClassifiers = true, sbtClassifiers = true).value,
coursierConfigGraphs := InputsTasks.ivyGraphsTask.value,
coursierSbtClassifiersModule := classifiersModule.in(updateSbtClassifiers).value,
coursierConfigurations := InputsTasks.coursierConfigurationsTask.value,
coursierParentProjectCache := InputsTasks.parentProjectCacheTask.value,
coursierResolutions := (Def.taskDyn {
val missingOk = updateConfiguration.value.missingOk
ResolutionTasks.resolutionsTask(missingOk = missingOk)
}).value,
Keys.actualCoursierResolution := {
val config = Configuration(Compile.name)
coursierResolutions
.value
.getOrElse(
config,
sys.error(s"Resolution for configuration $config not found")
)
},
coursierSbtClassifiersResolutions := (Def.taskDyn {
val missingOk = (updateConfiguration in updateSbtClassifiers).value.missingOk
ResolutionTasks.resolutionsTask(
sbtClassifiers = true,
missingOk = missingOk,
)
}).value
)
override lazy val buildSettings = super.buildSettings ++ Seq(
coursierParallelDownloads := 6,
coursierMaxIterations := ResolutionProcess.defaultMaxIterations,
coursierChecksums := Seq(Some("SHA-1"), None),
coursierArtifactsChecksums := Seq(None),
coursierCachePolicies := CacheDefaults.cachePolicies,
coursierTtl := CacheDefaults.ttl,
coursierVerbosity := Settings.defaultVerbosityLevel(sLog.value)
)
override lazy val projectSettings = coursierSettings ++
inConfig(Compile)(treeSettings) ++
inConfig(Test)(treeSettings)
}

View File

@ -0,0 +1,135 @@
package coursier.sbtcoursier
import coursier.core._
import lmcoursier.definitions.ToCoursier
import coursier.parse.ModuleParser
import coursier.sbtcoursier.Keys._
import coursier.sbtcoursiershared.SbtCoursierShared.autoImport._
import coursier.util.Print
import sbt.Def
import sbt.Keys._
import scala.collection.mutable
object DisplayTasks {
private case class ResolutionResult(config: Configuration, resolution: Resolution, dependencies: Seq[Dependency])
private def coursierResolutionTask(
sbtClassifiers: Boolean = false,
ignoreArtifactErrors: Boolean = false
): Def.Initialize[sbt.Task[Seq[ResolutionResult]]] = {
val currentProjectTask =
if (sbtClassifiers)
Def.task {
val sv = scalaVersion.value
val sbv = scalaBinaryVersion.value
val cm = coursierSbtClassifiersModule.value
SbtCoursierFromSbt.sbtClassifiersProject(cm, sv, sbv)
}
else
Def.task {
val proj = coursierProject.value
val publications = coursierPublications.value
proj.withPublications(publications)
}
val resolutionsTask =
if (sbtClassifiers)
coursierSbtClassifiersResolutions
else
coursierResolutions
Def.task {
val currentProject = ToCoursier.project(currentProjectTask.value)
val config = Configuration(configuration.value.name)
val configs = coursierConfigurations.value
val includedConfigs = configs.getOrElse(config, Set.empty) + config
val resolutions = resolutionsTask.value
for {
(subGraphConfig, res) <- resolutions.toSeq
if includedConfigs(subGraphConfig)
} yield {
val dependencies0 = currentProject
.dependencies
.collect {
case (`subGraphConfig`, dep) =>
dep
}
.sortBy { dep =>
(dep.module.organization, dep.module.name, dep.version)
}
val subRes = res.subset(dependencies0)
ResolutionResult(subGraphConfig, subRes, dependencies0)
}
}
}
def coursierDependencyTreeTask(
inverse: Boolean,
sbtClassifiers: Boolean = false,
ignoreArtifactErrors: Boolean = false
) = Def.task {
val projectName = thisProjectRef.value.project
val resolutions = coursierResolutionTask(sbtClassifiers, ignoreArtifactErrors).value
for (ResolutionResult(subGraphConfig, resolution, dependencies) <- resolutions) {
streams.value.log.info(
s"$projectName (configuration ${subGraphConfig.value})" + "\n" +
Print.dependencyTree(
resolution,
dependencies,
printExclusions = true,
inverse,
colors = !sys.props.get("sbt.log.noformat").toSeq.contains("true")
)
)
}
}
def coursierWhatDependsOnTask(
moduleName: String,
sbtClassifiers: Boolean = false,
ignoreArtifactErrors: Boolean = false
) = Def.task {
val module = ModuleParser.module(moduleName, scalaVersion.value)
.right
.getOrElse(throw new RuntimeException(s"Could not parse module `$moduleName`"))
val projectName = thisProjectRef.value.project
val resolutions = coursierResolutionTask(sbtClassifiers, ignoreArtifactErrors).value
val result = new mutable.StringBuilder
for (ResolutionResult(subGraphConfig, resolution, _) <- resolutions) {
val roots = resolution
.minDependencies
.filter(f => f.module == module)
.toVector
.sortBy(_.toString) // elements already have the same module, there's not much left for sorting
val strToPrint = s"$projectName (configurations ${subGraphConfig.value})" + "\n" +
Print.dependencyTree(
resolution,
roots,
printExclusions = true,
reverse = true,
colors = !sys.props.get("sbt.log.noformat").toSeq.contains("true")
)
streams.value.log.info(strToPrint)
result.append(strToPrint)
result.append("\n")
}
result.toString
}
}

View File

@ -0,0 +1,69 @@
package coursier.sbtcoursier
import coursier.ProjectCache
import coursier.core._
import lmcoursier._
import lmcoursier.definitions.ToCoursier
import coursier.sbtcoursier.Keys._
import coursier.sbtcoursiershared.SbtCoursierShared.autoImport._
import coursier.sbtcoursiershared.Structure._
import sbt.librarymanagement.{Configuration => _, _}
import sbt.Def
import sbt.Keys._
object InputsTasks {
def coursierConfigurationsTask: Def.Initialize[sbt.Task[Map[Configuration, Set[Configuration]]]] =
Def.task {
Inputs.coursierConfigurationsMap(ivyConfigurations.value).map {
case (k, v) =>
ToCoursier.configuration(k) -> v.map(ToCoursier.configuration)
}
}
def ivyGraphsTask: Def.Initialize[sbt.Task[Seq[(Configuration, Seq[Configuration])]]] =
Def.task {
val p = coursierProject.value
Inputs.orderedConfigurations(p.configurations.toSeq).map {
case (config, extends0) =>
(ToCoursier.configuration(config), extends0.map(ToCoursier.configuration))
}
}
def parentProjectCacheTask: Def.Initialize[sbt.Task[Map[Seq[sbt.librarymanagement.Resolver], Seq[coursier.ProjectCache]]]] =
Def.taskDyn {
val state = sbt.Keys.state.value
val projectRef = sbt.Keys.thisProjectRef.value
val projectDeps = structure(state).allProjects
.find(_.id == projectRef.project)
.map(_.dependencies.map(_.project.project).toSet)
.getOrElse(Set.empty)
val projects = structure(state).allProjectRefs.filter(p => projectDeps(p.project))
val t =
for {
m <- coursierRecursiveResolvers.forAllProjects(state, projects)
n <- coursierResolutions.forAllProjects(state, m.keys.toSeq)
} yield
n.foldLeft(Map.empty[Seq[Resolver], Seq[ProjectCache]]) {
case (caches, (ref, resolutions)) =>
val mainResOpt = resolutions.collectFirst {
case (Configuration.compile, v) => v
}
val r = for {
resolvers <- m.get(ref)
resolution <- mainResOpt
} yield
caches.updated(resolvers, resolution.projectCache +: caches.getOrElse(resolvers, Seq.empty))
r.getOrElse(caches)
}
Def.task(t.value)
}
}

View File

@ -0,0 +1,55 @@
package coursier.sbtcoursier
import java.io.File
import coursier.cache.CachePolicy
import coursier.ProjectCache
import coursier.core._
import coursier.util.Artifact
import sbt.librarymanagement.{GetClassifiersModule, Resolver}
import sbt.{InputKey, SettingKey, TaskKey}
import scala.concurrent.duration.{Duration, FiniteDuration}
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 coursierTtl = SettingKey[Option[Duration]]("coursier-ttl")
val coursierVerbosity = SettingKey[Int]("coursier-verbosity")
val coursierConfigGraphs = TaskKey[Seq[(Configuration, Seq[Configuration])]]("coursier-config-graphs")
val coursierSbtClassifiersModule = TaskKey[GetClassifiersModule]("coursier-sbt-classifiers-module")
val coursierConfigurations = TaskKey[Map[Configuration, Set[Configuration]]]("coursier-configurations")
val coursierParentProjectCache = TaskKey[Map[Seq[Resolver], Seq[ProjectCache]]]("coursier-parent-project-cache")
val coursierResolutions = TaskKey[Map[Configuration, Resolution]]("coursier-resolutions")
private[coursier] val actualCoursierResolution = TaskKey[Resolution]("coursier-resolution")
val coursierSbtClassifiersResolutions = TaskKey[Map[Configuration, 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 coursierWhatDependsOn = InputKey[String](
"coursier-what-depends-on",
"Prints dependencies and transitive dependencies as an inverted tree for a specific module (dependees as children)"
)
val coursierArtifacts = TaskKey[Map[Artifact, File]]("coursier-artifacts")
val coursierSignedArtifacts = TaskKey[Map[Artifact, File]]("coursier-signed-artifacts")
val coursierClassifiersArtifacts = TaskKey[Map[Artifact, File]]("coursier-classifiers-artifacts")
val coursierSbtClassifiersArtifacts = TaskKey[Map[Artifact, File]]("coursier-sbt-classifiers-artifacts")
}

View File

@ -0,0 +1,189 @@
package coursier.sbtcoursier
import coursier.ProjectCache
import coursier.cache.FileCache
import coursier.core._
import coursier.internal.Typelevel
import lmcoursier.definitions.ToCoursier
import lmcoursier.{FallbackDependency, FromSbt, Inputs}
import lmcoursier.internal.{InterProjectRepository, ResolutionParams, ResolutionRun, Resolvers}
import coursier.sbtcoursier.Keys._
import coursier.sbtcoursiershared.InputsTasks.{credentialsTask, strictTask}
import coursier.sbtcoursiershared.SbtCoursierShared.autoImport._
import coursier.util.{ModuleMatcher, ModuleMatchers}
import sbt.Def
import sbt.Keys._
object ResolutionTasks {
def resolutionsTask(
sbtClassifiers: Boolean = false,
missingOk: Boolean = false,
): Def.Initialize[sbt.Task[Map[Configuration, coursier.Resolution]]] = {
val currentProjectTask: sbt.Def.Initialize[sbt.Task[(Project, Seq[FallbackDependency], Seq[(Configuration, Seq[Configuration])])]] =
if (sbtClassifiers)
Def.task {
val sv = scalaVersion.value
val sbv = scalaBinaryVersion.value
val cm = coursierSbtClassifiersModule.value
val proj = ToCoursier.project(SbtCoursierFromSbt.sbtClassifiersProject(cm, sv, sbv))
val fallbackDeps = FromSbt.fallbackDependencies(
cm.dependencies,
sv,
sbv
)
(proj, fallbackDeps, cm.configurations.map(c => Configuration(c.name) -> Nil))
}
else
Def.task {
val baseConfigGraphs = coursierConfigGraphs.value
val publications = coursierPublications.value
(ToCoursier.project(coursierProject.value.withPublications(publications)), coursierFallbackDependencies.value, baseConfigGraphs)
}
val resolversTask =
if (sbtClassifiers)
Def.task(coursierSbtResolvers.value)
else
Def.task(coursierRecursiveResolvers.value.distinct)
val retrySettings = Def.task(coursierRetry.value)
Def.task {
val projectName = thisProjectRef.value.project
val sv = scalaVersion.value
val sbv = scalaBinaryVersion.value
val interProjectDependencies = coursierInterProjectDependencies.value.map(ToCoursier.project)
val extraProjects = coursierExtraProjects.value.map(ToCoursier.project)
val parallelDownloads = coursierParallelDownloads.value
val checksums = coursierChecksums.value
val maxIterations = coursierMaxIterations.value
val cachePolicies = coursierCachePolicies.value
val ttl = coursierTtl.value
val cache = coursierCache.value
val createLogger = coursierLogger.value.map(ToCoursier.cacheLogger)
val log = streams.value.log
// are these always defined? (e.g. for Java only projects?)
val so = Organization(scalaOrganization.value)
val userForceVersions = Inputs.forceVersions(dependencyOverrides.value, sv, sbv)
val verbosityLevel = coursierVerbosity.value
val userEnabledProfiles = mavenProfiles.value
val versionReconciliations0 = versionReconciliation.value.map { mod =>
Reconciliation(mod.revision) match {
case Some(rec) =>
val (mod0, _) = FromSbt.moduleVersion(mod, sv, sbv)
val matcher = ModuleMatchers.only(Organization(mod0.organization.value), ModuleName(mod0.name.value))
matcher -> rec
case None =>
throw new Exception(s"Unrecognized reconciliation: '${mod.revision}'")
}
}
val typelevel = Organization(scalaOrganization.value) == Typelevel.typelevelOrg
val interProjectRepo = InterProjectRepository(interProjectDependencies)
val extraProjectsRepo = InterProjectRepository(extraProjects)
val ivyProperties = ResolutionParams.defaultIvyProperties(ivyPaths.value.ivyHome)
val authenticationByRepositoryId = actualCoursierCredentials.value.mapValues(_.authentication)
val (currentProject, fallbackDependencies, orderedConfigs) = currentProjectTask.value
val autoScalaLib = autoScalaLibrary.value && scalaModuleInfo.value.forall(_.overrideScalaVersion)
val resolvers = resolversTask.value
// TODO Warn about possible duplicated modules from source repositories?
val credentials = credentialsTask.value.map(ToCoursier.credentials)
val strictOpt = strictTask.value.map(ToCoursier.strict)
val parentProjectCache: ProjectCache = coursierParentProjectCache.value
.get(resolvers)
.map(_.foldLeft[ProjectCache](Map.empty)(_ ++ _))
.getOrElse(Map.empty)
val excludeDeps = Inputs.exclusions(
coursier.sbtcoursiershared.InputsTasks.actualExcludeDependencies.value,
sv,
sbv,
log
).map {
case (org, name) =>
(Organization(org.value), ModuleName(name.value))
}
val mainRepositories = resolvers
.flatMap { resolver =>
Resolvers.repository(
resolver,
ivyProperties,
log,
authenticationByRepositoryId.get(resolver.name).map { a =>
Authentication(a.user, a.password)
.withOptional(a.optional)
.withRealmOpt(a.realmOpt)
},
Seq(),
)
}
val resOrError = ResolutionRun.resolutions(
ResolutionParams(
dependencies = currentProject.dependencies,
fallbackDependencies = fallbackDependencies,
orderedConfigs = orderedConfigs,
autoScalaLibOpt = if (autoScalaLib) Some((so, sv)) else None,
mainRepositories = mainRepositories,
parentProjectCache = parentProjectCache,
interProjectDependencies = interProjectDependencies,
internalRepositories = Seq(interProjectRepo, extraProjectsRepo),
sbtClassifiers = sbtClassifiers,
projectName = projectName,
loggerOpt = createLogger,
cache = FileCache()
.withLocation(cache)
.withCachePolicies(cachePolicies)
.withTtl(ttl)
.withChecksums(checksums)
.withCredentials(credentials)
.withFollowHttpToHttpsRedirections(true),
parallel = parallelDownloads,
params = coursier.params.ResolutionParams()
.withMaxIterations(maxIterations)
.withProfiles(userEnabledProfiles)
.withForceVersion(userForceVersions.map { case (k, v) => (ToCoursier.module(k), v) }.toMap)
.withTypelevel(typelevel)
.addReconciliation(versionReconciliations0: _*)
.withExclusions(excludeDeps),
strictOpt = strictOpt,
missingOk = missingOk,
retry = retrySettings.value.getOrElse(ResolutionParams.defaultRetry)
),
verbosityLevel,
log
)
resOrError match {
case Left(err) =>
throw err
case Right(res) =>
res
}
}
}
}

View File

@ -0,0 +1,37 @@
package coursier.sbtcoursier
import lmcoursier.FromSbt
import lmcoursier.definitions.Configuration
import sbt.librarymanagement.GetClassifiersModule
object SbtCoursierFromSbt {
def sbtClassifiersProject(
cm: GetClassifiersModule,
scalaVersion: String,
scalaBinaryVersion: String
) = {
val p = FromSbt.project(
cm.id,
cm.dependencies,
cm.configurations.map(cfg => Configuration(cfg.name) -> cfg.extendsConfigs.map(c => Configuration(c.name))).toMap,
scalaVersion,
scalaBinaryVersion
)
// for w/e reasons, the dependencies sometimes don't land in the right config above
// this is a loose attempt at fixing that
cm.configurations match {
case Seq(cfg) =>
p.withDependencies(
p.dependencies.map {
case (_, d) => (Configuration(cfg.name), d)
}
)
case _ =>
p
}
}
}

View File

@ -0,0 +1,46 @@
package coursier.sbtcoursier
import sbt.Logger
import scala.util.{Failure, Success, Try}
object Settings {
private val baseDefaultVerbosityLevel =
if (System.console() == null) // non interactive mode
0
else
1
def defaultVerbosityLevel(logger: Logger): Int = {
def fromOption(value: Option[String], description: String): Option[Int] =
value
.filter(_.nonEmpty)
.flatMap { str =>
Try(str.toInt) match {
case Success(level) => Some(level)
case Failure(_) =>
logger.warn(
s"unrecognized $description value (should be an integer), ignoring it."
)
None
}
}
val fromEnv = fromOption(
sys.env.get("COURSIER_VERBOSITY"),
"COURSIER_VERBOSITY environment variable"
)
def fromProps = fromOption(
sys.props.get("coursier.verbosity"),
"Java property coursier.verbosity"
)
fromEnv
.orElse(fromProps)
.getOrElse(baseDefaultVerbosityLevel)
}
}

View File

@ -0,0 +1,148 @@
package coursier.sbtcoursier
import coursier.core._
import coursier.sbtcoursier.Keys._
import coursier.sbtcoursiershared.SbtCoursierShared.autoImport._
import lmcoursier.definitions.ToCoursier
import lmcoursier.Inputs
import lmcoursier.internal.{SbtBootJars, SbtCoursierCache, UpdateParams, UpdateRun}
import sbt.Def
import sbt.Keys._
import sbt.librarymanagement.UpdateReport
object UpdateTasks {
def updateTask(
withClassifiers: Boolean,
sbtClassifiers: Boolean = false,
includeSignatures: Boolean = false
): Def.Initialize[sbt.Task[UpdateReport]] = {
val currentProjectTask =
if (sbtClassifiers)
Def.task {
val sv = scalaVersion.value
val sbv = scalaBinaryVersion.value
SbtCoursierFromSbt.sbtClassifiersProject(coursierSbtClassifiersModule.value, sv, sbv)
}
else
Def.task {
val proj = coursierProject.value
val publications = coursierPublications.value
proj.withPublications(publications)
}
val resTask =
if (withClassifiers && sbtClassifiers)
coursierSbtClassifiersResolutions
else
coursierResolutions
// we should be able to call .value on that one here, its conditions don't originate from other tasks
val artifactFilesOrErrors0Task =
if (withClassifiers) {
if (sbtClassifiers)
Keys.coursierSbtClassifiersArtifacts
else
Keys.coursierClassifiersArtifacts
} else if (includeSignatures)
Keys.coursierSignedArtifacts
else
Keys.coursierArtifacts
val configsTask: sbt.Def.Initialize[sbt.Task[Map[Configuration, Set[Configuration]]]] =
if (withClassifiers && sbtClassifiers)
Def.task {
val cm = coursierSbtClassifiersModule.value
cm.configurations.map(c => Configuration(c.name) -> Set(Configuration(c.name))).toMap
}
else
coursierConfigurations
val classifiersTask: sbt.Def.Initialize[sbt.Task[Option[Seq[Classifier]]]] =
if (withClassifiers) {
if (sbtClassifiers)
Def.task {
val cm = coursierSbtClassifiersModule.value
Some(cm.classifiers.map(Classifier(_)))
}
else
Def.task(Some(transitiveClassifiers.value.map(Classifier(_))))
} else
Def.task(None)
Def.taskDyn {
val so = Organization(scalaOrganization.value)
val internalSbtScalaProvider = appConfiguration.value.provider.scalaProvider
val sbtBootJarOverrides = SbtBootJars(
so, // this seems plain wrong - this assumes that the scala org of the project is the same
// as the one that started SBT. This will scrap the scala org specific JARs by the ones
// that booted SBT, even if the latter come from the standard org.scala-lang org.
// But SBT itself does it this way, and not doing so may make two different versions
// of the scala JARs land in the classpath...
internalSbtScalaProvider.version(),
internalSbtScalaProvider.jars()
)
val log = streams.value.log
val verbosityLevel = coursierVerbosity.value
val p = ToCoursier.project(currentProjectTask.value)
val dependencies = p.dependencies
val res = resTask.value
val key = SbtCoursierCache.ReportKey(
dependencies,
res,
withClassifiers,
sbtClassifiers,
includeSignatures
)
val interProjectDependencies = coursierInterProjectDependencies.value.map(ToCoursier.project)
SbtCoursierCache.default.reportOpt(key) match {
case Some(report) =>
Def.task(report)
case None =>
Def.task {
val artifactFilesOrErrors0 = artifactFilesOrErrors0Task.value
val classifiers = classifiersTask.value
val configs = configsTask.value
val sv = scalaVersion.value
val sbv = scalaBinaryVersion.value
val forceVersions = Inputs.forceVersions(dependencyOverrides.value, sv, sbv)
.map {
case (m, v) =>
(ToCoursier.module(m), v)
}
.toMap
val params = UpdateParams(
(p.module, p.version),
artifactFilesOrErrors0,
None,
classifiers,
configs,
dependencies,
forceVersions,
interProjectDependencies,
res,
includeSignatures,
sbtBootJarOverrides,
classpathOrder = true,
missingOk = sbtClassifiers,
classLoaders = Nil,
)
val rep = UpdateRun.update(params, verbosityLevel, log)
SbtCoursierCache.default.putReport(key, rep)
rep
}
}
}
}
}

View File

@ -0,0 +1,18 @@
scalaVersion := "2.12.8"
libraryDependencies += {
sys.props("sbt.log.noformat") = "true" // disables colors in coursierWhatDependsOn output
"org.apache.zookeeper" % "zookeeper" % "3.5.0-alpha"
}
lazy val whatDependsOnCheck = TaskKey[Unit]("whatDependsOnCheck")
import java.nio.file.{Files, Paths}
import CoursierPlugin.autoImport._
whatDependsOnCheck := {
val result = (coursierWhatDependsOn in Compile).toTask(" log4j:log4j").value
.replace(System.lineSeparator(), "\n")
val expected = new String(Files.readAllBytes(Paths.get("whatDependsOnResult.log")), "UTF-8")
assert(expected == result, s"Expected '$expected', got '$result'")
}

View File

@ -0,0 +1,13 @@
addSbtPlugin {
val name = sys.props.getOrElse(
"plugin.name",
sys.error("plugin.name Java property not set")
)
val version = sys.props.getOrElse(
"plugin.version",
sys.error("plugin.version Java property not set")
)
"io.get-coursier" % name % version
}

View File

@ -0,0 +1,2 @@
> coursierDependencyTree
> whatDependsOnCheck

View File

@ -0,0 +1,5 @@
dependency-graph (configurations compile)
└─ log4j:log4j:1.2.17
├─ org.apache.zookeeper:zookeeper:3.5.0-alpha log4j:log4j:1.2.16 -> 1.2.17
└─ org.slf4j:slf4j-log4j12:1.7.5
└─ org.apache.zookeeper:zookeeper:3.5.0-alpha

View File

@ -0,0 +1,10 @@
scalaVersion := "2.12.8"
coursierArtifacts := {
val f = file("coursier-artifacts")
if (f.exists())
sys.error(s"$f file found")
java.nio.file.Files.write(f.toPath, Array.empty[Byte])
coursierArtifacts.value
}

View File

@ -0,0 +1,13 @@
addSbtPlugin {
val name = sys.props.getOrElse(
"plugin.name",
sys.error("plugin.name Java property not set")
)
val version = sys.props.getOrElse(
"plugin.version",
sys.error("plugin.version Java property not set")
)
"io.get-coursier" % name % version
}

View File

@ -0,0 +1,4 @@
$ delete coursier-artifacts
> update
$ exists coursier-artifacts
> update

View File

@ -0,0 +1,56 @@
scalaVersion := "2.12.8"
resolvers += "Private S3 Snapshots" atS3
"s3://s3-us-west-2.amazonaws.com/bucket-name/snapshots"
resolvers += "Private S3 Releases" atS3
"s3://s3-us-west-2.amazonaws.com/bucket-name/releases"
// TODO: Would need a public s3 bucket to download an artifact
lazy val check = taskKey[Unit]("")
// Checks FromSbt.repository parses the "s3://" urls correctly.
check := {
val s: TaskStreams = streams.value
val sbtResolvers: Seq[sbt.librarymanagement.Resolver] = coursierResolvers.value
// Sanity check to ensure SBT is loading the resolvers properly
assert(sbtResolvers.exists(_.name == "Private S3 Snapshots"))
assert(sbtResolvers.exists(_.name == "Private S3 Releases"))
// Have Coursier SBT Plugin Parse the SBT Resolvers
val parsedCoursierResolvers: Seq[coursier.core.Repository] =
sbtResolvers.flatMap{ sbtResolver: sbt.librarymanagement.Resolver =>
lmcoursier.internal.Resolvers.repository(
resolver = sbtResolver,
ivyProperties = lmcoursier.internal.ResolutionParams.defaultIvyProperties(
ivyPaths.value.ivyHome
),
log = s.log,
authentication = None,
classLoaders = Seq()
)
}
// Verify the input resolvers == output resolvers
assert(
sbtResolvers.size == parsedCoursierResolvers.size,
s"SBT resolvers size (${sbtResolvers.size}) did not match " +
s"Coursier resolvers size (${parsedCoursierResolvers.size})"
)
def containsRepo(repo: String): Boolean = {
val accepted = Set(repo, repo.stripSuffix("/"))
parsedCoursierResolvers.exists {
case m: coursier.maven.MavenRepositoryLike => accepted(m.root)
case _ => false
}
}
assert(containsRepo("s3://s3-us-west-2.amazonaws.com/bucket-name/snapshots/"),
"Didn't have snapshots s3 repo")
assert(containsRepo("s3://s3-us-west-2.amazonaws.com/bucket-name/releases/"),
"Didn't have releases s3 repo")
}

View File

@ -0,0 +1 @@
sbt.version=1.2.8

View File

@ -0,0 +1 @@
addSbtPlugin("com.frugalmechanic" % "fm-sbt-s3-resolver" % "0.18.0")

View File

@ -0,0 +1 @@
addSbtCoursier

View File

@ -0,0 +1,13 @@
addSbtPlugin {
val name = sys.props.getOrElse(
"plugin.name",
sys.error("plugin.name Java property not set")
)
val version = sys.props.getOrElse(
"plugin.version",
sys.error("plugin.version Java property not set")
)
"io.get-coursier" % name % version
}

View File

@ -0,0 +1 @@
object Foo

View File

@ -0,0 +1 @@
> check

View File

@ -0,0 +1,4 @@
scalaVersion := "2.13.1"
libraryDependencies += "com.typesafe.play" %% "play-test" % "2.8.0-RC1" % Test // worked around in 2.8.0
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % Test

View File

@ -0,0 +1,13 @@
addSbtPlugin {
val name = sys.props.getOrElse(
"plugin.name",
sys.error("plugin.name Java property not set")
)
val version = sys.props.getOrElse(
"plugin.version",
sys.error("plugin.version Java property not set")
)
"io.get-coursier" % name % version
}

View File

@ -0,0 +1,20 @@
package t
import com.typesafe.config.ConfigFactory
import org.scalatest.{ MustMatchers, WordSpec }
class UnitSpec extends WordSpec with MustMatchers {
def conf = ConfigFactory.defaultReference()
"Config" should {
"return Akka HTTP server provider" in {
val serverProvider = conf.getString("play.server.provider")
serverProvider mustBe "play.core.server.AkkaHttpServerProvider"
}
"be able to load Netty settings" in {
val nettyTransport = conf.getString("play.server.netty.transport")
nettyTransport mustBe "jdk"
}
}
}

View File

@ -0,0 +1,6 @@
# https://github.com/coursier/coursier/issues/1466
> test
# ideally we can flip & assert the test fails
# but need to run this scripted test on sbt 1.3 in order to have a csrConfiguration
# > set csrConfiguration ~= (_.withClasspathOrder(false))
# -> test

View File

@ -0,0 +1,58 @@
// examples adapted from https://github.com/coursier/sbt-coursier/pull/75#issuecomment-497128870
lazy val a = project
.settings(
scalaVersion := "2.12.8",
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect" % "1.3.1",
"org.typelevel" %% "cats-core" % "1.5.0"
)
)
lazy val b = project
.settings(
scalaVersion := "2.12.8",
libraryDependencies ++= Seq(
"org.slf4s" %% "slf4s-api" % "1.7.25", // depends on org.slf4j:slf4j-api:1.7.25
"ch.qos.logback" % "logback-classic" % "1.1.2" // depends on org.slf4j:slf4j-api:1.7.6
)
)
lazy val c = project
.settings(
scalaVersion := "2.12.8",
libraryDependencies ++= Seq(
"org.slf4s" %% "slf4s-api" % "1.7.25", // depends on org.slf4j:slf4j-api:1.7.25
"ch.qos.logback" % "logback-classic" % "1.1.2" // depends on org.slf4j:slf4j-api:1.7.6
),
dependencyOverrides += "org.slf4j" % "slf4j-api" % "1.7.30"
)
lazy val check = taskKey[Unit]("")
check := {
val aReport = update.in(a).value
val bReport = update.in(b).value
val cReport = update.in(c).value
def doCheck(report: UpdateReport, evictionsExpected: Boolean = true): Unit = {
val compileReport = report
.configurations
.find(_.configuration.name == "compile")
.getOrElse {
sys.error("compile report not found")
}
val foundEvictions = compileReport.details.exists(_.modules.exists(_.evicted))
if (foundEvictions != evictionsExpected)
compileReport.details.foreach(println)
assert(foundEvictions == evictionsExpected)
}
doCheck(aReport)
doCheck(bReport)
doCheck(cReport, evictionsExpected = false)
}

View File

@ -0,0 +1,13 @@
addSbtPlugin {
val name = sys.props.getOrElse(
"plugin.name",
sys.error("plugin.name Java property not set")
)
val version = sys.props.getOrElse(
"plugin.version",
sys.error("plugin.version Java property not set")
)
"io.get-coursier" % name % version
}

View File

@ -0,0 +1,2 @@
> evicted
> check

View File

@ -0,0 +1,2 @@
scalaVersion := "2.12.2"
enablePlugins(ScalafmtPlugin)

View File

@ -0,0 +1 @@
addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15")

View File

@ -0,0 +1,13 @@
addSbtPlugin {
val name = sys.props.getOrElse(
"plugin.name",
sys.error("plugin.name Java property not set")
)
val version = sys.props.getOrElse(
"plugin.version",
sys.error("plugin.version Java property not set")
)
"io.get-coursier" % name % version
}

View File

@ -0,0 +1,6 @@
import java.io.File
import java.nio.file.Files
object Main extends App {
Files.write(new File("output").toPath, "OK".getBytes("UTF-8"))
}

Some files were not shown because too many files have changed in this diff Show More