mirror of https://github.com/sbt/sbt.git
Merge remote-tracking branch 'coursier/sbt-2.x' into 2.x-lm-coursier
This commit is contained in:
commit
7a325bc414
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "metadata"]
|
||||
path = metadata
|
||||
url = https://github.com/coursier/handmade-metadata.git
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
updates.pin = [
|
||||
{ groupId = "org.slf4j", artifactId="slf4j-api", version = "1." }
|
||||
]
|
||||
231
build.sbt
231
build.sbt
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 95874ca5bd90277c302f5a4d5c9b8119d91730af
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../../../../../lm-coursier/src/main/scala/lmcoursier/credentials/Credentials.scala
|
||||
|
|
@ -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)"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package lmcoursier.credentials
|
||||
|
||||
import dataclass.data
|
||||
|
||||
@data class FileCredentials(
|
||||
path: String,
|
||||
optional: Boolean = true
|
||||
) extends Credentials
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package lmcoursier.definitions
|
||||
|
||||
import dataclass.data
|
||||
|
||||
@data class Attributes(
|
||||
`type`: Type,
|
||||
classifier: Classifier
|
||||
)
|
||||
|
|
@ -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)"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../../../../../lm-coursier/src/main/scala/lmcoursier/definitions/CacheLogger.scala
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../../../../../lm-coursier/src/main/scala/lmcoursier/definitions/CachePolicy.scala
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../../../../../lm-coursier/src/main/scala/lmcoursier/definitions/Definitions.scala
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package lmcoursier.definitions
|
||||
|
||||
import dataclass.data
|
||||
|
||||
@data class Developer(
|
||||
id: String,
|
||||
name: String,
|
||||
url: String
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../../../../../lm-coursier/src/main/scala/lmcoursier/definitions/FromCoursier.scala
|
||||
|
|
@ -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]
|
||||
)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package lmcoursier.definitions
|
||||
import dataclass.data
|
||||
|
||||
@data class Module(
|
||||
organization: Organization,
|
||||
name: ModuleName,
|
||||
attributes: Map[String, String]
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package lmcoursier.definitions
|
||||
|
||||
import dataclass.data
|
||||
|
||||
@data class Publication(
|
||||
name: String,
|
||||
`type`: Type,
|
||||
ext: Extension,
|
||||
classifier: Classifier
|
||||
)
|
||||
|
|
@ -0,0 +1 @@
|
|||
../../../../../../lm-coursier/src/main/scala/lmcoursier/definitions/Reconciliation.scala
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package lmcoursier.credentials
|
||||
|
||||
import java.io.File
|
||||
|
||||
abstract class Credentials extends Serializable
|
||||
|
||||
object Credentials
|
||||
|
|
@ -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 {}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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()
|
||||
|
||||
}
|
||||
|
|
@ -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.##
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package lmcoursier.internal
|
||||
|
||||
import sbt.librarymanagement.ModuleSettings
|
||||
|
||||
private[lmcoursier] case class CoursierModuleSettings() extends ModuleSettings
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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)])
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>"))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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'")
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
> coursierDependencyTree
|
||||
> whatDependsOnCheck
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
$ delete coursier-artifacts
|
||||
> update
|
||||
$ exists coursier-artifacts
|
||||
> update
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
sbt.version=1.2.8
|
||||
|
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("com.frugalmechanic" % "fm-sbt-s3-resolver" % "0.18.0")
|
||||
|
|
@ -0,0 +1 @@
|
|||
addSbtCoursier
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
object Foo
|
||||
|
|
@ -0,0 +1 @@
|
|||
> check
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
> evicted
|
||||
> check
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
scalaVersion := "2.12.2"
|
||||
enablePlugins(ScalafmtPlugin)
|
||||
|
|
@ -0,0 +1 @@
|
|||
addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15")
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue