[2.x] Remove Ivy dependency (#9615)

**Problem**
sbt still has some code that's depending on Ivy.

**Solution**
This removes it.
This commit is contained in:
eugene yokota
2026-08-24 12:02:28 -04:00
committed by GitHub
parent bd9d9b3f5d
commit a91cf079db
125 changed files with 89 additions and 10570 deletions
-1
View File
@@ -152,7 +152,6 @@ jobs:
./sbt -v --client "scripted dependency-management/* project-load/* project-matrix/* java/* run/*"
./sbt -v --client "scripted plugins/*"
./sbt -v --client "scripted nio/*"
./sbt -v --client "scripted ivy/*"
- name: Build and test (4)
if: ${{ matrix.jobtype == 4 }}
shell: bash
+6 -41
View File
@@ -747,7 +747,6 @@ lazy val mainProj = (project in file("main"))
runProj,
commandProj,
collectionProj,
lmIvy,
zincLmIntegrationProj,
utilLogging,
)
@@ -818,21 +817,6 @@ lazy val mainProj = (project in file("main"))
.dependsOn(lmCore, lmCoursierShadedPublishing)
.configure(addSbtIO, addSbtCompilerInterface, addSbtZincCompileCore)
lazy val sbtIvyProj = (project in file("sbt-ivy"))
.dependsOn(sbtProj, lmIvy)
.settings(
testedBaseSettings,
name := "sbt-ivy",
sbtPlugin := true,
pluginCrossBuild / sbtVersion := version.value,
libraryDependencies += {
// https://github.com/scala/scala3/issues/18487
"net.hamnaberg" %% "dataclass-annotation" % dataclassScalafixVersion % Provided
},
mimaPreviousArtifacts := Set.empty, // new module, no previous artifacts
)
.configure(addSbtIO)
// Strictly for bringing implicits and aliases from subsystems into the top-level sbt namespace through a single package object
// technically, we need a dependency on all of mainProj's dependencies, but we don't do that since this is strictly an integration project
// with the sole purpose of providing certain identifiers without qualification (with a package object)
@@ -847,6 +831,12 @@ lazy val sbtProj = (project in file("sbt-app"))
javaOptions ++= Seq("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"),
mimaSettings,
mimaBinaryIssueFilters ++= sbtIgnoredProblems,
mimaBinaryIssueFilters ++= Vector(
// Dropped the top-level Ivy-specific UpdateOptions alias; use
// sbt.internal.librarymanagement.ivy.UpdateOptions directly if needed.
exclude[DirectMissingMethodProblem]("sbt.Import.UpdateOptions"),
exclude[DirectMissingMethodProblem]("sbt.package.UpdateOptions"),
),
)
.settings(
Test / run / connectInput := true,
@@ -1096,7 +1086,6 @@ def allProjects =
mainSettingsProj,
zincLmIntegrationProj,
mainProj,
sbtIvyProj,
sbtProj,
bundledLauncherProj,
sbtClientProj,
@@ -1108,7 +1097,6 @@ def allProjects =
coreMacrosProj,
remoteCacheProj,
lmCore,
lmIvy,
lmCoursierDefinitions,
lmCoursier,
lmCoursierShaded,
@@ -1271,29 +1259,6 @@ lazy val lmCore = (project in file("lm-core"))
.dependsOn(utilLogging, utilPosition, utilCache)
.configure(addSbtIO, addSbtCompilerInterface)
lazy val lmIvy = (project in file("lm-ivy"))
.enablePlugins(ContrabandPlugin, JsonCodecPlugin)
.dependsOn(lmCore)
.settings(
exportJars := false,
commonSettings,
lmTestSettings,
name := "librarymanagement-ivy",
contrabandSjsonNewVersion := sjsonNewVersion,
libraryDependencies ++= Seq(
ivy,
sjsonNewScalaJson.value,
sjsonNewCore.value,
scalacheck % Test,
scalaVerify % Test,
hedgehog % Test,
),
libraryDependencies ++= scalatest,
contrabandSettings,
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat,
mimaSettings,
)
lazy val lmCoursierSettings: Seq[Setting[?]] = Def.settings(
baseSettings,
headerLicense := Some(
@@ -0,0 +1,17 @@
package sbt.internal.librarymanagement
package ivy
import java.io.File
import sbt.librarymanagement.{ Credentials, CredentialUtils }
// Kept for backward compatibility with sbt2-compat
private[sbt] object IvyCredentials:
def forHost(sc: Seq[Credentials], host: String) =
CredentialUtils.forHost(sc, host)
def allDirect(sc: Seq[Credentials]): Seq[Credentials.DirectCredentials] =
CredentialUtils.allDirect(sc)
def toDirect(c: Credentials): Credentials.DirectCredentials =
CredentialUtils.toDirect(c)
def loadCredentials(path: File): Either[String, Credentials.DirectCredentials] =
CredentialUtils.loadCredentials(path)
end IvyCredentials
@@ -0,0 +1,14 @@
package sbt.internal.librarymanagement
package mavenint
/**
* Keys for the extra pom properties used to propagate the sbtVersion/scalaVersion
* attributes for sbt plugin dependencies, plus the Maven packagings that sbt treats as jars.
*/
object PomExtraAttributeKeys:
val SbtVersionKey = "sbtVersion"
val ScalaVersionKey = "scalaVersion"
// packagings that should be jars, but that Ivy doesn't handle as jars
val JarPackagings = Set("eclipse-plugin", "hk2-jar", "orbit", "scala-jar")
end PomExtraAttributeKeys
@@ -1,66 +0,0 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.librarymanagement.ivy
final class ExternalIvyConfiguration private (
lock: Option[xsbti.GlobalLock],
log: Option[xsbti.Logger],
updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions,
val baseDirectory: Option[java.io.File],
val uri: Option[java.net.URI],
val extraResolvers: Vector[sbt.librarymanagement.Resolver]) extends sbt.internal.librarymanagement.ivy.IvyConfiguration(lock, log, updateOptions) with Serializable {
private def this() = this(None, None, sbt.internal.librarymanagement.ivy.UpdateOptions(), None, None, Vector())
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: ExternalIvyConfiguration => (this.lock == x.lock) && (this.log == x.log) && (this.updateOptions == x.updateOptions) && (this.baseDirectory == x.baseDirectory) && (this.uri == x.uri) && (this.extraResolvers == x.extraResolvers)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.librarymanagement.ivy.ExternalIvyConfiguration".##) + lock.##) + log.##) + updateOptions.##) + baseDirectory.##) + uri.##) + extraResolvers.##)
}
override def toString: String = {
"ExternalIvyConfiguration(" + lock + ", " + log + ", " + updateOptions + ", " + baseDirectory + ", " + uri + ", " + extraResolvers + ")"
}
private def copy(lock: Option[xsbti.GlobalLock] = lock, log: Option[xsbti.Logger] = log, updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions = updateOptions, baseDirectory: Option[java.io.File] = baseDirectory, uri: Option[java.net.URI] = uri, extraResolvers: Vector[sbt.librarymanagement.Resolver] = extraResolvers): ExternalIvyConfiguration = {
new ExternalIvyConfiguration(lock, log, updateOptions, baseDirectory, uri, extraResolvers)
}
def withLock(lock: Option[xsbti.GlobalLock]): ExternalIvyConfiguration = {
copy(lock = lock)
}
def withLock(lock: xsbti.GlobalLock): ExternalIvyConfiguration = {
copy(lock = Option(lock))
}
def withLog(log: Option[xsbti.Logger]): ExternalIvyConfiguration = {
copy(log = log)
}
def withLog(log: xsbti.Logger): ExternalIvyConfiguration = {
copy(log = Option(log))
}
def withUpdateOptions(updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions): ExternalIvyConfiguration = {
copy(updateOptions = updateOptions)
}
def withBaseDirectory(baseDirectory: Option[java.io.File]): ExternalIvyConfiguration = {
copy(baseDirectory = baseDirectory)
}
def withBaseDirectory(baseDirectory: java.io.File): ExternalIvyConfiguration = {
copy(baseDirectory = Option(baseDirectory))
}
def withUri(uri: Option[java.net.URI]): ExternalIvyConfiguration = {
copy(uri = uri)
}
def withUri(uri: java.net.URI): ExternalIvyConfiguration = {
copy(uri = Option(uri))
}
def withExtraResolvers(extraResolvers: Vector[sbt.librarymanagement.Resolver]): ExternalIvyConfiguration = {
copy(extraResolvers = extraResolvers)
}
}
object ExternalIvyConfiguration {
def apply(): ExternalIvyConfiguration = new ExternalIvyConfiguration()
def apply(lock: Option[xsbti.GlobalLock], log: Option[xsbti.Logger], updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions, baseDirectory: Option[java.io.File], uri: Option[java.net.URI], extraResolvers: Vector[sbt.librarymanagement.Resolver]): ExternalIvyConfiguration = new ExternalIvyConfiguration(lock, log, updateOptions, baseDirectory, uri, extraResolvers)
def apply(lock: xsbti.GlobalLock, log: xsbti.Logger, updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions, baseDirectory: java.io.File, uri: java.net.URI, extraResolvers: Vector[sbt.librarymanagement.Resolver]): ExternalIvyConfiguration = new ExternalIvyConfiguration(Option(lock), Option(log), updateOptions, Option(baseDirectory), Option(uri), extraResolvers)
}
@@ -1,37 +0,0 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.librarymanagement.ivy
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait ExternalIvyConfigurationFormats { self: sbt.internal.librarymanagement.formats.GlobalLockFormat & sbt.internal.librarymanagement.formats.LoggerFormat & sbt.internal.librarymanagement.ivy.formats.UpdateOptionsFormat & sbt.librarymanagement.ResolverFormats & sjsonnew.BasicJsonProtocol =>
given ExternalIvyConfigurationFormat: JsonFormat[sbt.internal.librarymanagement.ivy.ExternalIvyConfiguration] = new JsonFormat[sbt.internal.librarymanagement.ivy.ExternalIvyConfiguration] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.librarymanagement.ivy.ExternalIvyConfiguration = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val lock = unbuilder.readField[Option[xsbti.GlobalLock]]("lock")
val log = unbuilder.readField[Option[xsbti.Logger]]("log")
val updateOptions = unbuilder.readField[sbt.internal.librarymanagement.ivy.UpdateOptions]("updateOptions")
val baseDirectory = unbuilder.readField[Option[java.io.File]]("baseDirectory")
val uri = unbuilder.readField[Option[java.net.URI]]("uri")
val extraResolvers = unbuilder.readField[Vector[sbt.librarymanagement.Resolver]]("extraResolvers")
unbuilder.endObject()
sbt.internal.librarymanagement.ivy.ExternalIvyConfiguration(lock, log, updateOptions, baseDirectory, uri, extraResolvers)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.librarymanagement.ivy.ExternalIvyConfiguration, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("lock", obj.lock)
builder.addField("log", obj.log)
builder.addField("updateOptions", obj.updateOptions)
builder.addField("baseDirectory", obj.baseDirectory)
builder.addField("uri", obj.uri)
builder.addField("extraResolvers", obj.extraResolvers)
builder.endObject()
}
}
}
@@ -1,82 +0,0 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.librarymanagement.ivy
final class InlineIvyConfiguration private (
lock: Option[xsbti.GlobalLock],
log: Option[xsbti.Logger],
updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions,
val paths: Option[sbt.librarymanagement.IvyPaths],
val resolvers: Vector[sbt.librarymanagement.Resolver],
val otherResolvers: Vector[sbt.librarymanagement.Resolver],
val moduleConfigurations: Vector[sbt.librarymanagement.ModuleConfiguration],
val checksums: Vector[String],
val managedChecksums: Boolean,
val resolutionCacheDir: Option[java.io.File]) extends sbt.internal.librarymanagement.ivy.IvyConfiguration(lock, log, updateOptions) with Serializable {
private def this() = this(None, None, sbt.internal.librarymanagement.ivy.UpdateOptions(), None, sbt.librarymanagement.Resolver.defaults, Vector.empty, Vector.empty, sbt.internal.librarymanagement.ivy.IvyDefaults.defaultChecksums, false, None)
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
case x: InlineIvyConfiguration => (this.lock == x.lock) && (this.log == x.log) && (this.updateOptions == x.updateOptions) && (this.paths == x.paths) && (this.resolvers == x.resolvers) && (this.otherResolvers == x.otherResolvers) && (this.moduleConfigurations == x.moduleConfigurations) && (this.checksums == x.checksums) && (this.managedChecksums == x.managedChecksums) && (this.resolutionCacheDir == x.resolutionCacheDir)
case _ => false
})
override def hashCode: Int = {
37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.librarymanagement.ivy.InlineIvyConfiguration".##) + lock.##) + log.##) + updateOptions.##) + paths.##) + resolvers.##) + otherResolvers.##) + moduleConfigurations.##) + checksums.##) + managedChecksums.##) + resolutionCacheDir.##)
}
override def toString: String = {
"InlineIvyConfiguration(" + lock + ", " + log + ", " + updateOptions + ", " + paths + ", " + resolvers + ", " + otherResolvers + ", " + moduleConfigurations + ", " + checksums + ", " + managedChecksums + ", " + resolutionCacheDir + ")"
}
private def copy(lock: Option[xsbti.GlobalLock] = lock, log: Option[xsbti.Logger] = log, updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions = updateOptions, paths: Option[sbt.librarymanagement.IvyPaths] = paths, resolvers: Vector[sbt.librarymanagement.Resolver] = resolvers, otherResolvers: Vector[sbt.librarymanagement.Resolver] = otherResolvers, moduleConfigurations: Vector[sbt.librarymanagement.ModuleConfiguration] = moduleConfigurations, checksums: Vector[String] = checksums, managedChecksums: Boolean = managedChecksums, resolutionCacheDir: Option[java.io.File] = resolutionCacheDir): InlineIvyConfiguration = {
new InlineIvyConfiguration(lock, log, updateOptions, paths, resolvers, otherResolvers, moduleConfigurations, checksums, managedChecksums, resolutionCacheDir)
}
def withLock(lock: Option[xsbti.GlobalLock]): InlineIvyConfiguration = {
copy(lock = lock)
}
def withLock(lock: xsbti.GlobalLock): InlineIvyConfiguration = {
copy(lock = Option(lock))
}
def withLog(log: Option[xsbti.Logger]): InlineIvyConfiguration = {
copy(log = log)
}
def withLog(log: xsbti.Logger): InlineIvyConfiguration = {
copy(log = Option(log))
}
def withUpdateOptions(updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions): InlineIvyConfiguration = {
copy(updateOptions = updateOptions)
}
def withPaths(paths: Option[sbt.librarymanagement.IvyPaths]): InlineIvyConfiguration = {
copy(paths = paths)
}
def withPaths(paths: sbt.librarymanagement.IvyPaths): InlineIvyConfiguration = {
copy(paths = Option(paths))
}
def withResolvers(resolvers: Vector[sbt.librarymanagement.Resolver]): InlineIvyConfiguration = {
copy(resolvers = resolvers)
}
def withOtherResolvers(otherResolvers: Vector[sbt.librarymanagement.Resolver]): InlineIvyConfiguration = {
copy(otherResolvers = otherResolvers)
}
def withModuleConfigurations(moduleConfigurations: Vector[sbt.librarymanagement.ModuleConfiguration]): InlineIvyConfiguration = {
copy(moduleConfigurations = moduleConfigurations)
}
def withChecksums(checksums: Vector[String]): InlineIvyConfiguration = {
copy(checksums = checksums)
}
def withManagedChecksums(managedChecksums: Boolean): InlineIvyConfiguration = {
copy(managedChecksums = managedChecksums)
}
def withResolutionCacheDir(resolutionCacheDir: Option[java.io.File]): InlineIvyConfiguration = {
copy(resolutionCacheDir = resolutionCacheDir)
}
def withResolutionCacheDir(resolutionCacheDir: java.io.File): InlineIvyConfiguration = {
copy(resolutionCacheDir = Option(resolutionCacheDir))
}
}
object InlineIvyConfiguration {
def apply(): InlineIvyConfiguration = new InlineIvyConfiguration()
def apply(lock: Option[xsbti.GlobalLock], log: Option[xsbti.Logger], updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions, paths: Option[sbt.librarymanagement.IvyPaths], resolvers: Vector[sbt.librarymanagement.Resolver], otherResolvers: Vector[sbt.librarymanagement.Resolver], moduleConfigurations: Vector[sbt.librarymanagement.ModuleConfiguration], checksums: Vector[String], managedChecksums: Boolean, resolutionCacheDir: Option[java.io.File]): InlineIvyConfiguration = new InlineIvyConfiguration(lock, log, updateOptions, paths, resolvers, otherResolvers, moduleConfigurations, checksums, managedChecksums, resolutionCacheDir)
def apply(lock: xsbti.GlobalLock, log: xsbti.Logger, updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions, paths: sbt.librarymanagement.IvyPaths, resolvers: Vector[sbt.librarymanagement.Resolver], otherResolvers: Vector[sbt.librarymanagement.Resolver], moduleConfigurations: Vector[sbt.librarymanagement.ModuleConfiguration], checksums: Vector[String], managedChecksums: Boolean, resolutionCacheDir: java.io.File): InlineIvyConfiguration = new InlineIvyConfiguration(Option(lock), Option(log), updateOptions, Option(paths), resolvers, otherResolvers, moduleConfigurations, checksums, managedChecksums, Option(resolutionCacheDir))
}
@@ -1,45 +0,0 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.librarymanagement.ivy
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait InlineIvyConfigurationFormats { self: sbt.internal.librarymanagement.formats.GlobalLockFormat & sbt.internal.librarymanagement.formats.LoggerFormat & sbt.internal.librarymanagement.ivy.formats.UpdateOptionsFormat & sbt.librarymanagement.IvyPathsFormats & sbt.librarymanagement.ResolverFormats & sbt.librarymanagement.ModuleConfigurationFormats & sjsonnew.BasicJsonProtocol =>
given InlineIvyConfigurationFormat: JsonFormat[sbt.internal.librarymanagement.ivy.InlineIvyConfiguration] = new JsonFormat[sbt.internal.librarymanagement.ivy.InlineIvyConfiguration] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.librarymanagement.ivy.InlineIvyConfiguration = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val lock = unbuilder.readField[Option[xsbti.GlobalLock]]("lock")
val log = unbuilder.readField[Option[xsbti.Logger]]("log")
val updateOptions = unbuilder.readField[sbt.internal.librarymanagement.ivy.UpdateOptions]("updateOptions")
val paths = unbuilder.readField[Option[sbt.librarymanagement.IvyPaths]]("paths")
val resolvers = unbuilder.readField[Vector[sbt.librarymanagement.Resolver]]("resolvers")
val otherResolvers = unbuilder.readField[Vector[sbt.librarymanagement.Resolver]]("otherResolvers")
val moduleConfigurations = unbuilder.readField[Vector[sbt.librarymanagement.ModuleConfiguration]]("moduleConfigurations")
val checksums = unbuilder.readField[Vector[String]]("checksums")
val managedChecksums = unbuilder.readField[Boolean]("managedChecksums")
val resolutionCacheDir = unbuilder.readField[Option[java.io.File]]("resolutionCacheDir")
unbuilder.endObject()
sbt.internal.librarymanagement.ivy.InlineIvyConfiguration(lock, log, updateOptions, paths, resolvers, otherResolvers, moduleConfigurations, checksums, managedChecksums, resolutionCacheDir)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.librarymanagement.ivy.InlineIvyConfiguration, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("lock", obj.lock)
builder.addField("log", obj.log)
builder.addField("updateOptions", obj.updateOptions)
builder.addField("paths", obj.paths)
builder.addField("resolvers", obj.resolvers)
builder.addField("otherResolvers", obj.otherResolvers)
builder.addField("moduleConfigurations", obj.moduleConfigurations)
builder.addField("checksums", obj.checksums)
builder.addField("managedChecksums", obj.managedChecksums)
builder.addField("resolutionCacheDir", obj.resolutionCacheDir)
builder.endObject()
}
}
}
@@ -1,21 +0,0 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.librarymanagement.ivy
abstract class IvyConfiguration(
val lock: Option[xsbti.GlobalLock],
val log: Option[xsbti.Logger],
val updateOptions: sbt.internal.librarymanagement.ivy.UpdateOptions) extends Serializable {
def this() = this(None, None, sbt.internal.librarymanagement.ivy.UpdateOptions())
override def toString: String = {
"IvyConfiguration(" + lock + ", " + log + ", " + updateOptions + ")"
}
}
object IvyConfiguration {
}
@@ -1,11 +0,0 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.librarymanagement.ivy
import _root_.sjsonnew.JsonFormat
trait IvyConfigurationFormats { self: sbt.internal.librarymanagement.formats.GlobalLockFormat & sbt.internal.librarymanagement.formats.LoggerFormat & sbt.internal.librarymanagement.ivy.formats.UpdateOptionsFormat & sbt.librarymanagement.IvyPathsFormats & sbt.librarymanagement.ResolverFormats & sbt.librarymanagement.ModuleConfigurationFormats & sjsonnew.BasicJsonProtocol & sbt.internal.librarymanagement.ivy.InlineIvyConfigurationFormats & sbt.internal.librarymanagement.ivy.ExternalIvyConfigurationFormats =>
given IvyConfigurationFormat: JsonFormat[sbt.internal.librarymanagement.ivy.IvyConfiguration] = flatUnionFormat2[sbt.internal.librarymanagement.ivy.IvyConfiguration, sbt.internal.librarymanagement.ivy.InlineIvyConfiguration, sbt.internal.librarymanagement.ivy.ExternalIvyConfiguration]("type")
}
-109
View File
@@ -1,109 +0,0 @@
{
"codecNamespace": "sbt.internal.librarymanagement.ivy",
"types": [
{
"name": "IvyConfiguration",
"namespace": "sbt.internal.librarymanagement.ivy",
"target": "Scala",
"type": "interface",
"fields": [
{
"name": "lock",
"type": "xsbti.GlobalLock?",
"default": "None",
"since": "0.0.1"
},
{
"name": "log",
"type": "xsbti.Logger?",
"default": "None",
"since": "0.0.1"
},
{
"name": "updateOptions",
"type": "sbt.internal.librarymanagement.ivy.UpdateOptions",
"default": "sbt.internal.librarymanagement.ivy.UpdateOptions()",
"since": "0.0.1"
}
],
"types": [
{
"name": "InlineIvyConfiguration",
"namespace": "sbt.internal.librarymanagement.ivy",
"target": "Scala",
"type": "record",
"fields": [
{
"name": "paths",
"type": "sbt.librarymanagement.IvyPaths?",
"default": "None",
"since": "0.0.1"
},
{
"name": "resolvers",
"type": "sbt.librarymanagement.Resolver*",
"default": "sbt.librarymanagement.Resolver.defaults",
"since": "0.0.1"
},
{
"name": "otherResolvers",
"type": "sbt.librarymanagement.Resolver*",
"default": "Vector.empty",
"since": "0.0.1"
},
{
"name": "moduleConfigurations",
"type": "sbt.librarymanagement.ModuleConfiguration*",
"default": "Vector.empty",
"since": "0.0.1"
},
{
"name": "checksums",
"type": "String*",
"default": "sbt.internal.librarymanagement.ivy.IvyDefaults.defaultChecksums",
"since": "0.0.1"
},
{
"name": "managedChecksums",
"type": "Boolean",
"default": "false",
"since": "0.0.1"
},
{
"name": "resolutionCacheDir",
"type": "java.io.File?",
"default": "None",
"since": "0.0.1"
}
]
},
{
"name": "ExternalIvyConfiguration",
"namespace": "sbt.internal.librarymanagement.ivy",
"target": "Scala",
"type": "record",
"fields": [
{
"name": "baseDirectory",
"type": "java.io.File?",
"default": "None",
"since": "0.0.1"
},
{
"name": "uri",
"type": "java.net.URI?",
"default": "None",
"since": "0.0.1"
},
{
"name": "extraResolvers",
"type": "sbt.librarymanagement.Resolver*",
"default": "Vector()",
"since": "0.0.1"
}
]
}
]
}
]
}
@@ -1,16 +0,0 @@
package sbt.internal.librarymanagement;
import java.util.Map;
import org.apache.ivy.plugins.resolver.DependencyResolver;
// implements the methods with raw types
@SuppressWarnings("rawtypes")
public abstract class ResolverAdapter implements DependencyResolver {
public String[] listTokenValues(String token, Map otherTokenValues) {
return new String[0];
}
public Map[] listTokenValues(String[] tokens, Map criteria) {
return new Map[0];
}
}
@@ -1,135 +0,0 @@
package org.apache.ivy.plugins.parser.m2
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
/**
* It turns out there was a very subtle, and evil, issue sitting the Ivy/maven configuration, and it
* related to dependency mapping. A mapping of `foo->bar(*)` means that the local configuration
* `foo` depends on the remote configuration `bar`, if it exists, or *ALL CONFIGURATIONS* if `bar`
* does not exist. Since the default Ivy configuration mapping was using the random `master`
* configuration, which AFAICT is NEVER specified, just an assumed default, this would cause leaks
* between maven + ivy projects.
*
* i.e. if a maven POM depends on a module denoted by an ivy.xml file, then you'd wind up accidentally
* bleeding ALL the ivy module's configurations into the maven module's configurations.
*
* This fix works around the issue, by assuming that if there is no `master` configuration, than the
* maven default of `compile` is intended. As sbt forces generated `ivy.xml` files to abide by
* maven conventions, this works in all of our test cases. The only scenario where it wouldn't work
* is those who have custom ivy.xml files *and* have pom.xml files which rely on those custom ivy.xml files,
* a very unlikely situation where the workaround is: "define a master configuration".
*
* Also see: http://ant.apache.org/ivy/history/2.3.0/ivyfile/dependency.html
* and: http://svn.apache.org/repos/asf/ant/ivy/core/tags/2.3.0/src/java/org/apache/ivy/plugins/parser/m2/PomModuleDescriptorBuilder.java
*/
object ReplaceMavenConfigurationMappings {
def addMappings(dd: DefaultDependencyDescriptor, scope: String, isOptional: Boolean) = {
val mapping = ReplaceMavenConfigurationMappings.REPLACEMENT_MAVEN_MAPPINGS.get(scope)
mapping.addMappingConfs(dd, isOptional)
}
val REPLACEMENT_MAVEN_MAPPINGS = {
// Here we copy paste from Ivy
val REPLACEMENT_MAPPINGS = new java.util.HashMap[String, PomModuleDescriptorBuilder.ConfMapper]
// NOTE - This code is copied from org.apache.ivy.plugins.parser.m2.PomModuleDescriptorBuilder
// except with altered default configurations...
REPLACEMENT_MAPPINGS.put(
"compile",
new PomModuleDescriptorBuilder.ConfMapper {
def addMappingConfs(dd: DefaultDependencyDescriptor, isOptional: Boolean): Unit = {
if (isOptional) {
dd.addDependencyConfiguration("optional", "compile(*)")
// FIX - Here we take a more conservative approach of depending on the compile configuration if master isn't there.
dd.addDependencyConfiguration("optional", "master(compile)")
} else {
dd.addDependencyConfiguration("compile", "compile(*)")
// FIX - Here we take a more conservative approach of depending on the compile configuration if master isn't there.
dd.addDependencyConfiguration("compile", "master(compile)")
dd.addDependencyConfiguration("runtime", "runtime(*)")
}
}
}
)
REPLACEMENT_MAPPINGS.put(
"provided",
new PomModuleDescriptorBuilder.ConfMapper {
def addMappingConfs(dd: DefaultDependencyDescriptor, isOptional: Boolean): Unit = {
if (isOptional) {
dd.addDependencyConfiguration("optional", "compile(*)")
dd.addDependencyConfiguration("optional", "provided(*)")
dd.addDependencyConfiguration("optional", "runtime(*)")
// FIX - Here we take a more conservative approach of depending on the compile configuration if master isn't there.
dd.addDependencyConfiguration("optional", "master(compile)")
} else {
dd.addDependencyConfiguration("provided", "compile(*)")
dd.addDependencyConfiguration("provided", "provided(*)")
dd.addDependencyConfiguration("provided", "runtime(*)")
// FIX - Here we take a more conservative approach of depending on the compile configuration if master isn't there.
dd.addDependencyConfiguration("provided", "master(compile)")
}
}
}
)
REPLACEMENT_MAPPINGS.put(
"runtime",
new PomModuleDescriptorBuilder.ConfMapper {
def addMappingConfs(dd: DefaultDependencyDescriptor, isOptional: Boolean): Unit = {
if (isOptional) {
dd.addDependencyConfiguration("optional", "compile(*)")
dd.addDependencyConfiguration("optional", "provided(*)")
// FIX - Here we take a more conservative approach of depending on the compile configuration if master isn't there.
dd.addDependencyConfiguration("optional", "master(compile)")
} else {
dd.addDependencyConfiguration("runtime", "compile(*)")
dd.addDependencyConfiguration("runtime", "runtime(*)")
// FIX - Here we take a more conservative approach of depending on the compile configuration if master isn't there.
dd.addDependencyConfiguration("runtime", "master(compile)")
}
}
}
)
REPLACEMENT_MAPPINGS.put(
"test",
new PomModuleDescriptorBuilder.ConfMapper {
def addMappingConfs(dd: DefaultDependencyDescriptor, isOptional: Boolean): Unit = {
dd.addDependencyConfiguration("test", "runtime(*)")
// FIX - Here we take a more conservative approach of depending on the compile configuration if master isn't there.
dd.addDependencyConfiguration("test", "master(compile)")
}
}
)
REPLACEMENT_MAPPINGS.put(
"system",
new PomModuleDescriptorBuilder.ConfMapper {
def addMappingConfs(dd: DefaultDependencyDescriptor, isOptional: Boolean): Unit = {
// FIX - Here we take a more conservative approach of depending on the compile configuration if master isn't there.
dd.addDependencyConfiguration("system", "master(compile)")
}
}
)
REPLACEMENT_MAPPINGS
}
def init(): Unit = {
// Here we mutate a static final field, because we have to AND because it's evil.
try {
val map = PomModuleDescriptorBuilder.MAVEN2_CONF_MAPPING
.asInstanceOf[java.util.Map[String, PomModuleDescriptorBuilder.ConfMapper]]
map.clear()
map.putAll(REPLACEMENT_MAVEN_MAPPINGS)
} catch {
case e: Exception =>
// TODO - Log that Ivy may not be configured correctly and you could have maven/ivy issues.
throw new RuntimeException(
"FAILURE to install Ivy maven hooks. Your ivy-maven interaction may suffer resolution errors",
e
)
}
}
}
@@ -1,111 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt.internal.librarymanagement
import java.io.File
import java.util.concurrent.Callable
import sbt.util.Logger
import sbt.librarymanagement.*
import scala.util.Using
/**
* A component manager provides access to the pieces of xsbt that are distributed as components.
* There are two types of components. The first type is compiled subproject jars with their dependencies.
* The second type is a subproject distributed as a source jar so that it can be compiled against a specific
* version of Scala.
*
* The component manager provides services to install and retrieve components to the local repository.
* This is used for compiled source jars so that the compilation need not be repeated for other projects on the same
* machine.
*/
class ComponentManager(
globalLock: xsbti.GlobalLock,
provider: xsbti.ComponentProvider,
ivyHome: Option[File],
val log: Logger
) {
private val ivyCache = new IvyCache(ivyHome)
/** Get all of the files for component 'id', throwing an exception if no files exist for the component. */
def files(id: String)(ifMissing: IfMissing): Iterable[File] = {
def fromGlobal =
lockGlobalCache {
try {
update(id); getOrElse(createAndCache)
} catch {
case _: NotInCache => createAndCache
}
}
def getOrElse(orElse: => Iterable[File]): Iterable[File] = {
val existing = provider.component(id)
if (existing.isEmpty) orElse else existing
}
def notFound = invalid("Could not find required component '" + id + "'")
def createAndCache =
ifMissing match {
case IfMissing.Fail => notFound
case d: IfMissing.Define =>
d()
if (d.cache) cache(id)
getOrElse(notFound)
}
lockLocalCache { getOrElse(fromGlobal) }
}
/** This is used to lock the local cache in project/boot/. By checking the local cache first, we can avoid grabbing a global lock. */
private def lockLocalCache[T](action: => T): T = lock(provider.lockFile)(action)
/** This is used to ensure atomic access to components in the global Ivy cache. */
private def lockGlobalCache[T](action: => T): T = lock(ivyCache.lockFile)(action)
private def lock[T](file: File)(action: => T): T =
globalLock(file, new Callable[T] { def call = action })
/** Get the file for component 'id', throwing an exception if no files or multiple files exist for the component. */
def file(id: String)(ifMissing: IfMissing): File =
files(id)(ifMissing).toList match {
case x :: Nil => x
case xs =>
invalid("Expected single file for component '" + id + "', found: " + xs.mkString(", "))
}
private def invalid(msg: String) = throw new InvalidComponent(msg)
def define(id: String, files: Iterable[File]) = lockLocalCache {
provider.defineComponent(id, files.toSeq.toArray)
}
/** Retrieve the file for component 'id' from the local repository. */
private def update(id: String): Unit =
ivyCache.withCachedJar(sbtModuleID(id), Some(globalLock), log)(jar => define(id, Seq(jar)))
private def sbtModuleID(id: String) =
ModuleID(SbtArtifacts.Organization, id, ComponentManager.stampedVersion)
/** Install the files for component 'id' to the local repository. This is usually used after writing files to the directory returned by 'location'. */
def cache(id: String): Unit =
ivyCache.cacheJar(sbtModuleID(id), file(id)(IfMissing.Fail), Some(globalLock), log)
def clearCache(id: String): Unit = lockGlobalCache {
ivyCache.clearCachedJar(sbtModuleID(id), Some(globalLock), log)
}
}
class InvalidComponent(msg: String, cause: Throwable) extends RuntimeException(msg, cause) {
def this(msg: String) = this(msg, null)
}
sealed trait IfMissing
object IfMissing {
object Fail extends IfMissing
final class Define(val cache: Boolean, define: => Unit) extends IfMissing {
def apply() = define
}
}
object ComponentManager {
lazy val (version, timestamp) = {
val properties = new java.util.Properties
Using.resource(getClass.getResourceAsStream("/xsbt.version.properties")) { propertiesStream =>
properties.load(propertiesStream)
}
(properties.getProperty("version"), properties.getProperty("timestamp"))
}
lazy val stampedVersion = version + "_" + timestamp
}
@@ -1,467 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt.internal.librarymanagement
import java.net.URI
import java.util.Collections
import org.apache.ivy.core.module.descriptor.DependencyDescriptor
import org.apache.ivy.core.resolve.{ DownloadOptions, ResolveData }
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.plugins.repository.{ RepositoryCopyProgressListener, Resource, TransferEvent }
import org.apache.ivy.plugins.resolver.{
BasicResolver,
DependencyResolver,
IBiblioResolver,
RepositoryResolver
}
import org.apache.ivy.plugins.resolver.{
AbstractPatternsBasedResolver,
AbstractSshBasedResolver,
FileSystemResolver,
SFTPResolver,
SshResolver,
URLResolver
}
import org.apache.ivy.plugins.repository.url.URLRepository as URLRepo
import org.apache.ivy.plugins.repository.file.{ FileResource, FileRepository as FileRepo }
import java.io.{ File, IOException }
import java.util.Date
import org.apache.ivy.core.module.descriptor.Artifact as IArtifact
import org.apache.ivy.core.module.id.ModuleRevisionId
import org.apache.ivy.core.module.descriptor.DefaultArtifact
import org.apache.ivy.core.report.DownloadReport
import org.apache.ivy.plugins.resolver.util.{ ResolvedResource, ResourceMDParser }
import org.apache.ivy.util.{ ChecksumHelper, FileUtil, Message }
import scala.jdk.CollectionConverters.*
import sbt.internal.librarymanagement.ivy.UpdateOptions
import sbt.internal.librarymanagement.mavenint.PomExtraDependencyAttributes
import sbt.io.IO
import sbt.util.Logger
import sbt.librarymanagement.*
private[sbt] object ConvertResolver {
import UpdateOptions.ResolverConverter
/**
* This class contains all the reflective lookups used in the
* checksum-friendly URL publishing shim.
*/
private object ChecksumFriendlyURLResolver {
import java.lang.reflect.AccessibleObject
private def reflectiveLookup[A <: AccessibleObject](f: Class[?] => A): Option[A] =
try {
val cls = classOf[RepositoryResolver]
val thing = f(cls)
thing.setAccessible(true)
Some(thing)
} catch {
case (_: java.lang.NoSuchFieldException) | (_: java.lang.SecurityException) |
(_: java.lang.NoSuchMethodException) =>
None
}
private val signerNameField: Option[java.lang.reflect.Field] =
reflectiveLookup(_.getDeclaredField("signerName"))
private val putChecksumMethod: Option[java.lang.reflect.Method] =
reflectiveLookup(
_.getDeclaredMethod(
"putChecksum",
classOf[IArtifact],
classOf[File],
classOf[String],
classOf[Boolean],
classOf[String]
)
)
private val putSignatureMethod: Option[java.lang.reflect.Method] =
reflectiveLookup(
_.getDeclaredMethod(
"putSignature",
classOf[IArtifact],
classOf[File],
classOf[String],
classOf[Boolean]
)
)
}
/**
* The default behavior of ivy's overwrite flags ignores the fact that a lot of repositories
* will autogenerate checksums *for* an artifact if it doesn't already exist. Therefore
* if we succeed in publishing an artifact, we need to just blast the checksums in place.
* This acts as a "shim" on RepositoryResolvers so that we can hook our methods into
* both the IBiblioResolver + URLResolver without having to duplicate the code in two
* places. However, this does mean our use of reflection is awesome.
*
* TODO - See about contributing back to ivy.
*/
private trait ChecksumFriendlyURLResolver extends RepositoryResolver {
import ChecksumFriendlyURLResolver.*
private def signerName: String = signerNameField match {
case Some(field) => field.get(this).asInstanceOf[String]
case None => null
}
override protected def put(
artifact: IArtifact,
src: File,
dest: String,
overwrite: Boolean
): Unit = {
// verify the checksum algorithms before uploading artifacts!
val checksums = getChecksumAlgorithms()
val repository = getRepository()
for {
checksum <- checksums
if !ChecksumHelper.isKnownAlgorithm(checksum)
} throw new IllegalArgumentException("Unknown checksum algorithm: " + checksum)
repository.put(artifact, src, dest, overwrite)
if !dest.endsWith(".asc") then
for checksum <- checksums do
putChecksumMethod match
case Some(method) =>
method.invoke(this, artifact, src, dest, true: java.lang.Boolean, checksum)
case None => // TODO - issue warning?
if (signerName != null) {
putSignatureMethod match {
case None => ()
case Some(method) => method.invoke(artifact, src, dest, true: java.lang.Boolean); ()
}
}
}
}
private[librarymanagement] val ManagedChecksums = "sbt.managedChecksums"
/** Converts the given sbt resolver into an Ivy resolver. */
def apply(
r: Resolver,
settings: IvySettings,
updateOptions: UpdateOptions,
log: Logger
): DependencyResolver =
(updateOptions.resolverConverter orElse defaultConvert)((r, settings, log))
/** The default implementation of converter. */
lazy val defaultConvert: ResolverConverter = { (r, settings, log) =>
val managedChecksums = Option(settings.getVariable(ManagedChecksums)) match {
case Some(x) => x.toBoolean
case _ => false
}
r match {
case repo: MavenRepository => {
val pattern = Collections.singletonList(
Resolver.resolvePattern(repo.root, Resolver.mavenStyleBasePattern)
)
final class PluginCapableResolver
extends IBiblioResolver
with ChecksumFriendlyURLResolver
with DescriptorRequired {
override val managedChecksumsEnabled: Boolean = managedChecksums
override def getResource(resource: Resource, dest: File): Long = get(resource, dest)
def setPatterns(): Unit = {
// done this way for access to protected methods.
setArtifactPatterns(pattern)
setIvyPatterns(pattern)
}
override protected def findResourceUsingPattern(
mrid: ModuleRevisionId,
pattern: String,
artifact: IArtifact,
rmdparser: ResourceMDParser,
date: Date
): ResolvedResource = {
val extraAttributes =
mrid.getExtraAttributes.asScala.toMap.asInstanceOf[Map[String, String]]
getSbtPluginCrossVersion(extraAttributes) match {
case Some(sbtCrossVersion) =>
// if the module is an sbt plugin
// we first try to resolve the artifact with the sbt cross version suffix
// and we fallback to the one without the suffix
val newArtifact = DefaultArtifact.cloneWithAnotherName(
artifact,
artifact.getName + sbtCrossVersion
)
val resolved =
super.findResourceUsingPattern(mrid, pattern, newArtifact, rmdparser, date)
if (resolved != null) resolved
else super.findResourceUsingPattern(mrid, pattern, artifact, rmdparser, date)
case None =>
super.findResourceUsingPattern(mrid, pattern, artifact, rmdparser, date)
}
}
}
val resolver = new PluginCapableResolver
if (repo.localIfFile) resolver.setRepository(new LocalIfFileRepo)
initializeMavenStyle(resolver, repo.name, repo.root)
resolver
.setPatterns() // has to be done after initializeMavenStyle, which calls methods that overwrite the patterns
resolver
}
case repo: SshRepository => {
val resolver = new SshResolver with DescriptorRequired with ThreadSafeSshBasedResolver {
override val managedChecksumsEnabled: Boolean = managedChecksums
override def getResource(resource: Resource, dest: File): Long = get(resource, dest)
}
initializeSSHResolver(resolver, repo, settings)
repo.publishPermissions.foreach(perm => resolver.setPublishPermissions(perm))
resolver
}
case repo: SftpRepository => {
val resolver = new SFTPResolver with ThreadSafeSshBasedResolver
initializeSSHResolver(resolver, repo, settings)
resolver
}
case repo: FileRepository => {
val resolver =
new FileSystemResolver with ChecksumFriendlyURLResolver with DescriptorRequired {
// Workaround for #1156
// Temporarily in sbt 0.13.x we deprecate overwriting
// in local files for non-changing revisions.
// This will be fully enforced in sbt 1.0.
setRepository(new WarnOnOverwriteFileRepo())
override val managedChecksumsEnabled: Boolean = managedChecksums
override def getResource(resource: Resource, dest: File): Long = get(resource, dest)
}
resolver.setName(repo.name)
initializePatterns(resolver, repo.patterns, settings)
import repo.configuration.{ isLocal, isTransactional }
resolver.setLocal(isLocal)
isTransactional.foreach(value => resolver.setTransactional(value.toString))
resolver
}
case repo: URLRepository => {
val resolver = new URLResolver with ChecksumFriendlyURLResolver with DescriptorRequired {
override val managedChecksumsEnabled: Boolean = managedChecksums
override def getResource(resource: Resource, dest: File): Long = get(resource, dest)
}
resolver.setName(repo.name)
initializePatterns(resolver, repo.patterns, settings)
resolver
}
case repo: ChainedResolver =>
IvySbt.resolverChain(repo.name, repo.resolvers, settings, log)
case repo: RawRepository =>
repo.resolver match {
case r: DependencyResolver => r
}
}
}
private def getSbtPluginCrossVersion(extraAttributes: Map[String, String]): Option[String] = {
for {
sbtVersion <- extraAttributes.get(PomExtraDependencyAttributes.SbtVersionKey)
scalaVersion <- extraAttributes.get(PomExtraDependencyAttributes.ScalaVersionKey)
} yield s"_${scalaVersion}_$sbtVersion"
}
private sealed trait DescriptorRequired extends BasicResolver {
// Works around implementation restriction to access protected method `get`
def getResource(resource: Resource, dest: File): Long
/**
* Defines an option to tell ivy to disable checksums when downloading and
* let the user handle verifying these checksums.
*
* This means that the checksums are stored in the ivy cache directory. This
* is good for reproducibility from outside ivy. Sbt can check that jars are
* not corrupted, ever, independently of trusting whatever it's there in the
* local directory.
*/
def managedChecksumsEnabled: Boolean
private def downloadChecksum(
resource: Resource,
targetChecksumFile: File,
algorithm: String
): Boolean = {
if (!ChecksumHelper.isKnownAlgorithm(algorithm))
throw new IllegalArgumentException(s"Unknown checksum algorithm: $algorithm")
val checksumResource = resource.clone(s"${resource.getName}.$algorithm")
if (!checksumResource.exists) false
else {
Message.debug(s"$algorithm file found for $resource: downloading...")
// Resource must be cleaned up outside of this function if it's invalid
getResource(checksumResource, targetChecksumFile)
true
}
}
private final val PartEnd = ".part"
private final val JarEnd = ".jar"
private final val TemporaryJar = JarEnd + PartEnd
override def getAndCheck(resource: Resource, target: File): Long = {
val targetPath = target.getAbsolutePath
if (!managedChecksumsEnabled || !targetPath.endsWith(TemporaryJar)) {
super.getAndCheck(resource, target)
} else {
// +ivy deviation
val size = getResource(resource, target)
val checksumAlgorithms = getChecksumAlgorithms
checksumAlgorithms.foldLeft(false) { (checked, algorithm) =>
// Continue checking until we hit a failure
val checksumFile = new File(targetPath.stripSuffix(PartEnd) + s".$algorithm")
if (checked) checked
else downloadChecksum(resource, checksumFile, algorithm)
}
// -ivy deviation
size
}
}
override def getDependency(dd: DependencyDescriptor, data: ResolveData) = {
val prev = descriptorString(isAllownomd)
setDescriptor(descriptorString(hasExplicitURL(dd)))
val t =
try super.getDependency(dd, data)
finally setDescriptor(prev)
t
}
def descriptorString(optional: Boolean) =
if (optional) BasicResolver.DESCRIPTOR_OPTIONAL else BasicResolver.DESCRIPTOR_REQUIRED
def hasExplicitURL(dd: DependencyDescriptor): Boolean =
dd.getAllDependencyArtifacts.exists(_.getUrl != null)
}
private def initializeMavenStyle(resolver: IBiblioResolver, name: String, root: String): Unit = {
resolver.setName(name)
resolver.setM2compatible(true)
resolver.setRoot(root)
}
private def initializeSSHResolver(
resolver: AbstractSshBasedResolver,
repo: SshBasedRepository,
settings: IvySettings
): Unit = {
resolver.setName(repo.name)
resolver.setPassfile(null)
initializePatterns(resolver, repo.patterns, settings)
initializeConnection(resolver, repo.connection)
}
private def initializeConnection(
resolver: AbstractSshBasedResolver,
connection: SshConnection
): Unit = {
import resolver.*
import connection.*
hostname.foreach(setHost)
port.foreach(setPort)
authentication foreach {
case pa: PasswordAuthentication =>
setUser(pa.user)
pa.password.foreach(setUserPassword)
case kfa: KeyFileAuthentication =>
setKeyFile(kfa.keyfile)
kfa.password.foreach(setKeyFilePassword)
setUser(kfa.user)
}
}
private def initializePatterns(
resolver: AbstractPatternsBasedResolver,
patterns: Patterns,
settings: IvySettings
): Unit = {
resolver.setM2compatible(patterns.isMavenCompatible)
resolver.setDescriptor(
if (patterns.descriptorOptional) BasicResolver.DESCRIPTOR_OPTIONAL
else BasicResolver.DESCRIPTOR_REQUIRED
)
resolver.setCheckconsistency(!patterns.skipConsistencyCheck)
patterns.ivyPatterns.foreach(p => resolver.addIvyPattern(settings.substitute(p)))
patterns.artifactPatterns.foreach(p => resolver.addArtifactPattern(settings.substitute(p)))
}
/**
* A custom Ivy URLRepository that returns FileResources for file URLs.
* This allows using the artifacts from the Maven local repository instead of copying them to the Ivy cache.
*/
private final class LocalIfFileRepo extends URLRepo {
private val repo = new WarnOnOverwriteFileRepo()
private val progress = new RepositoryCopyProgressListener(this);
override def getResource(source: String) = {
val uri = new URI(source)
if (uri.getScheme == IO.FileScheme)
new FileResource(repo, IO.toFile(uri))
else
super.getResource(source)
}
override def put(source: File, destination: String, overwrite: Boolean): Unit = {
val uri = new URI(destination)
try {
if (uri.getScheme != IO.FileScheme) super.put(source, destination, overwrite)
else {
// Here we duplicate the put method for files so we don't just bail on trying ot use Http handler
val resource = getResource(destination)
if (!overwrite && resource.exists()) {
throw new IOException(s"destination file exists and overwrite == false");
}
fireTransferInitiated(resource, TransferEvent.REQUEST_PUT);
try {
val totalLength = source.length
if (totalLength > 0) {
progress.setTotalLength(totalLength);
}
FileUtil.copy(source, new java.io.File(uri), progress, overwrite)
()
} catch {
case ex: IOException =>
fireTransferError(ex)
throw ex
case ex: RuntimeException =>
fireTransferError(ex)
throw ex
} finally {
progress.setTotalLength(null);
}
}
} catch {
// This error could be thrown either by super.put or the above
case ex: IOException if ex.getMessage.contains("destination file exists") =>
throw new IOException(
s"""PUT operation failed because the destination file exists and overwriting is disabled:
| source : $source
| destination: $destination
|If you have a staging repository that has failed, drop it and start over.
|Otherwise fix the double publishing, or relax the setting as follows:
| publishConfiguration := publishConfiguration.value.withOverwrite(true)
| publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true)
|
|If you have a remote cache repository, you can enable overwriting as follows:
| pushRemoteCacheConfiguration := pushRemoteCacheConfiguration.value.withOverwrite(true)
|""".stripMargin,
ex
)
}
}
}
private final class WarnOnOverwriteFileRepo extends FileRepo() {
override def put(source: java.io.File, destination: String, overwrite: Boolean): Unit = {
try super.put(source, destination, overwrite)
catch {
case e: java.io.IOException if e.getMessage.contains("destination already exists") =>
val overwriteWarning =
if destination.contains("-SNAPSHOT") then s"Attempting to overwrite $destination"
else
s"Attempting to overwrite $destination (non-SNAPSHOT)\n\tYou need to remove it from the cache manually to take effect."
import org.apache.ivy.util.Message
Message.warn(overwriteWarning)
super.put(source, destination, true)
}
}
}
private sealed trait ThreadSafeSshBasedResolver
extends org.apache.ivy.plugins.resolver.AbstractSshBasedResolver {
//uncomment to test non-threadsafe behavior
// private def lock = new Object
private val lock = org.apache.ivy.plugins.repository.ssh.SshCache.getInstance
override def download(artifacts: Array[IArtifact], options: DownloadOptions): DownloadReport =
lock.synchronized {
super.download(artifacts, options)
}
}
}
@@ -1,344 +0,0 @@
package sbt.internal.librarymanagement
import org.apache.ivy.core.module.id.ModuleRevisionId
import org.apache.ivy.core.module.descriptor.{
DefaultArtifact,
DefaultExtendsDescriptor,
DefaultModuleDescriptor,
ModuleDescriptor
}
import org.apache.ivy.core.module.descriptor.{ DefaultDependencyDescriptor, DependencyDescriptor }
import org.apache.ivy.plugins.parser.{
ModuleDescriptorParser,
ModuleDescriptorParserRegistry,
ParserSettings
}
import org.apache.ivy.plugins.parser.m2.{
ReplaceMavenConfigurationMappings,
PomModuleDescriptorBuilder,
PomModuleDescriptorParser
}
import org.apache.ivy.plugins.repository.Resource
import org.apache.ivy.plugins.namespace.NamespaceTransformer
import org.apache.ivy.util.extendable.ExtendableItem
import java.io.{ File, InputStream }
import java.net.URL
import sbt.internal.librarymanagement.mavenint.{
PomExtraDependencyAttributes,
SbtPomExtraProperties
}
import sbt.io.Hash
import scala.collection.immutable.ArraySeq
// @deprecated("We now use an Aether-based pom parser.", "0.13.8")
final class CustomPomParser(
delegate: ModuleDescriptorParser,
transform: (ModuleDescriptorParser, ModuleDescriptor) => ModuleDescriptor
) extends ModuleDescriptorParser {
override def parseDescriptor(
ivySettings: ParserSettings,
descriptorURL: URL,
validate: Boolean
) =
transform(this, delegate.parseDescriptor(ivySettings, descriptorURL, validate))
override def parseDescriptor(
ivySettings: ParserSettings,
descriptorURL: URL,
res: Resource,
validate: Boolean
) =
transform(this, delegate.parseDescriptor(ivySettings, descriptorURL, res, validate))
override def toIvyFile(is: InputStream, res: Resource, destFile: File, md: ModuleDescriptor) =
delegate.toIvyFile(is, res, destFile, md)
override def accept(res: Resource) = delegate.accept(res)
override def getType() = delegate.getType()
override def getMetadataArtifact(mrid: ModuleRevisionId, res: Resource) =
delegate.getMetadataArtifact(mrid, res)
}
// @deprecated("We now use an Aether-based pom parser.", "0.13.8")
object CustomPomParser {
// Evil hackery to override the default maven pom mappings.
ReplaceMavenConfigurationMappings.init()
/** The key prefix that indicates that this is used only to store extra information and is not intended for dependency resolution. */
val InfoKeyPrefix = SbtPomExtraProperties.POM_INFO_KEY_PREFIX
val ApiURLKey = SbtPomExtraProperties.POM_API_KEY
val VersionSchemeKey = SbtPomExtraProperties.VERSION_SCHEME_KEY
val SbtVersionKey = PomExtraDependencyAttributes.SbtVersionKey
val ScalaVersionKey = PomExtraDependencyAttributes.ScalaVersionKey
val ExtraAttributesKey = PomExtraDependencyAttributes.ExtraAttributesKey
private val unqualifiedKeys =
Set(SbtVersionKey, ScalaVersionKey, ExtraAttributesKey, ApiURLKey, VersionSchemeKey)
/**
* In the new POM format of sbt plugins, the dependency to an sbt plugin
* contains the sbt cross-version _2.12_1.0. The reason is we want Maven to be able
* to resolve the dependency using the pattern:
* <org>/<artifact-name>_2.12_1.0/<version>/<artifact-name>_2.12_1.0-<version>.pom
* In sbt 1.x we use extra-attributes to resolve sbt plugins, so here we must remove
* the sbt cross-version and keep the extra-attributes.
* Parsing a dependency found in the new POM format produces the same module as
* if it is found in the old POM format. It used not to contain the sbt cross-version
* suffix, but that was invalid.
* Hence we can resolve conflicts between new and old POM formats.
*
* To compare the two formats you can look at the POMs in:
* https://repo1.maven.org/maven2/ch/epfl/scala/sbt-plugin-example-diamond_2.12_1.0/0.5.0/
*/
private def removeSbtCrossVersion(
properties: Map[String, String],
moduleName: String
): String = {
val sbtCrossVersion = for {
sbtVersion <- properties.get(s"e:$SbtVersionKey")
scalaVersion <- properties.get(s"e:$ScalaVersionKey")
} yield s"_${scalaVersion}_$sbtVersion"
sbtCrossVersion.map(moduleName.stripSuffix).getOrElse(moduleName)
}
// packagings that should be jars, but that Ivy doesn't handle as jars
// TODO - move this elsewhere.
val JarPackagings = Set("eclipse-plugin", "hk2-jar", "orbit", "scala-jar")
val default = new CustomPomParser(PomModuleDescriptorParser.getInstance, defaultTransform)
private val TransformedHashKey = "e:sbtTransformHash"
// A hash of the parameters transformation is based on.
// If a descriptor has a different hash, we need to retransform it.
private def makeCoords(mrid: ModuleRevisionId): String =
s"${mrid.getOrganisation}:${mrid.getName}:${mrid.getRevision}"
// We now include the ModuleID in a hash, to ensure that parent-pom transformations don't corrupt child poms.
private def MakeTransformHash(md: ModuleDescriptor): String = {
val coords: String = makeCoords(md.getModuleRevisionId)
hash((unqualifiedKeys ++ JarPackagings ++ Set(coords)).toSeq.sorted)
}
private def hash(ss: Seq[String]): String =
Hash.toHex(Hash(ss.flatMap(_.getBytes("UTF-8")).toArray))
// Unfortunately, ModuleDescriptorParserRegistry is add-only and is a singleton instance.
lazy val registerDefault: Unit = ModuleDescriptorParserRegistry.getInstance.addParser(default)
def defaultTransform(parser: ModuleDescriptorParser, md: ModuleDescriptor): ModuleDescriptor =
if (transformedByThisVersion(md)) md
else defaultTransformImpl(parser, md)
private def transformedByThisVersion(md: ModuleDescriptor): Boolean = {
val oldTransformedHashKey = "sbtTransformHash"
val extraInfo = md.getExtraInfo
val MyHash = MakeTransformHash(md)
// sbt 0.13.1 used "sbtTransformHash" instead of "e:sbtTransformHash" until #1192 so read both
Option(extraInfo).isDefined &&
(Option(extraInfo.get(TransformedHashKey))
.orElse(Option(extraInfo.get(oldTransformedHashKey))) match
case Some(MyHash) => true
case _ => false)
}
private def defaultTransformImpl(
parser: ModuleDescriptorParser,
md: ModuleDescriptor
): ModuleDescriptor = {
val properties = getPomProperties(md)
// Extracts extra attributes (currently, sbt and Scala versions) stored in the <properties> element of the pom.
// These are attached to the module itself.
val filtered = shouldBeUnqualified(properties)
// Extracts extra attributes for the dependencies.
// Because the <dependency> tag in pom.xml cannot include additional metadata,
// sbt includes extra attributes in a 'extraDependencyAttributes' property.
// This is read/written from/to a pure string (no element structure) because Ivy only
// parses the immediate text nodes of the property.
val extraDepAttributes = getDependencyExtra(filtered)
val unqualify = toUnqualify(filtered)
// Here we always add extra attributes. There's a scenario where parent-pom information corrupts child-poms with "e:" namespaced xml elements
// and we have to force the every generated xml file to have the appropriate xml namespace
addExtra(unqualify, extraDepAttributes, parser, md)
}
// The <properties> element of the pom is used to store additional metadata, such as for sbt plugins or for the base URL for API docs.
// This is done because the pom XSD does not appear to allow extra metadata anywhere else.
// The extra sbt plugin metadata in pom.xml does not need to be readable by maven, but the other information may be.
// However, the pom.xml needs to be valid in all cases because other tools like repository managers may read the pom.xml.
private[sbt] def getPomProperties(md: ModuleDescriptor): Map[String, String] = {
import scala.jdk.CollectionConverters.*
PomModuleDescriptorBuilder
.extractPomProperties(md.getExtraInfo)
.asInstanceOf[java.util.Map[String, String]]
.asScala
.toMap
}
private[sbt] def toUnqualify(propertyAttributes: Map[String, String]): Map[String, String] =
(propertyAttributes - ExtraAttributesKey) map { (k, v) => ("e:" + k, v) }
private def shouldBeUnqualified(m: Map[String, String]): Map[String, String] =
m.view.filterKeys(unqualifiedKeys).toMap
private def addExtra(
properties: Map[String, String],
id: ModuleRevisionId
): ModuleRevisionId = {
import scala.jdk.CollectionConverters.*
val oldExtra = qualifiedExtra(id)
val newExtra = (oldExtra ++ properties).asJava
// remove the sbt plugin cross version from the resolved ModuleRevisionId
// sbt-plugin-example_2.12_1.0 => sbt-plugin-example
val nameWithoutCrossVersion = removeSbtCrossVersion(properties, id.getName)
ModuleRevisionId.newInstance(
id.getOrganisation,
nameWithoutCrossVersion,
id.getBranch,
id.getRevision,
newExtra
)
}
private def getDependencyExtra(
m: Map[String, String]
): Map[ModuleRevisionId, Map[String, String]] =
PomExtraDependencyAttributes.getDependencyExtra(m)
def qualifiedExtra(item: ExtendableItem): Map[String, String] =
PomExtraDependencyAttributes.qualifiedExtra(item)
def filterCustomExtra(item: ExtendableItem, include: Boolean): Map[String, String] =
qualifiedExtra(item).view.filterKeys { k =>
qualifiedIsExtra(k) == include
}.toMap
def writeDependencyExtra(s: Seq[DependencyDescriptor]): Seq[String] =
PomExtraDependencyAttributes.writeDependencyExtra(s)
// parses the sequence of dependencies with extra attribute information, with one dependency per line
def readDependencyExtra(s: String): Seq[ModuleRevisionId] =
PomExtraDependencyAttributes.readDependencyExtra(s)
def qualifiedIsExtra(k: String): Boolean = PomExtraDependencyAttributes.qualifiedIsExtra(k)
// Reduces the id to exclude custom extra attributes
// This makes the id suitable as a key to associate a dependency parsed from a <dependency> element
// with the extra attributes from the <properties> section
def simplify(id: ModuleRevisionId): ModuleRevisionId = PomExtraDependencyAttributes.simplify(id)
private def addExtra(
dep: DependencyDescriptor,
extra: Map[ModuleRevisionId, Map[String, String]]
): DependencyDescriptor = {
val extras = if (extra.isEmpty) None else extra get simplify(dep.getDependencyRevisionId)
extras match {
case None => dep
case Some(extraAttrs) => transform(dep, revId => addExtra(extraAttrs, revId))
}
}
private def transform(
dep: DependencyDescriptor,
f: ModuleRevisionId => ModuleRevisionId
): DependencyDescriptor =
DefaultDependencyDescriptor.transformInstance(
dep,
namespaceTransformer(dep.getDependencyRevisionId, f),
false
)
private def namespaceTransformer(
txId: ModuleRevisionId,
f: ModuleRevisionId => ModuleRevisionId
): NamespaceTransformer =
new NamespaceTransformer {
def transform(revId: ModuleRevisionId): ModuleRevisionId =
if (revId == txId) f(revId) else revId
def isIdentity = false
}
// TODO: It would be better if we can make dd.isForce to `false` when VersionRange.isVersionRange is `true`.
private def stripVersionRange(dd: DependencyDescriptor): DependencyDescriptor =
VersionRange.stripMavenVersionRange(dd.getDependencyRevisionId.getRevision) match {
case Some(newVersion) =>
val id = dd.getDependencyRevisionId
val newId = ModuleRevisionId.newInstance(
id.getOrganisation,
id.getName,
id.getBranch,
newVersion,
id.getExtraAttributes
)
transform(dd, _ => newId)
case None => dd
}
import scala.jdk.CollectionConverters.*
def addExtra(
properties: Map[String, String],
dependencyExtra: Map[ModuleRevisionId, Map[String, String]],
parser: ModuleDescriptorParser,
md: ModuleDescriptor
): ModuleDescriptor = {
val dmd = new DefaultModuleDescriptor(parser, md.getResource)
val mrid = addExtra(properties, md.getModuleRevisionId)
val resolvedMrid = addExtra(properties, md.getResolvedModuleRevisionId)
dmd.setModuleRevisionId(mrid)
dmd.setResolvedModuleRevisionId(resolvedMrid)
dmd.setDefault(md.isDefault)
dmd.setHomePage(md.getHomePage)
dmd.setDescription(md.getDescription)
dmd.setLastModified(md.getLastModified)
dmd.setStatus(md.getStatus())
dmd.setPublicationDate(md.getPublicationDate())
dmd.setResolvedPublicationDate(md.getResolvedPublicationDate())
for (l <- md.getLicenses) dmd.addLicense(l)
for ((key, value) <- md.getExtraInfo.asInstanceOf[java.util.Map[String, String]].asScala)
dmd.addExtraInfo(key, value)
dmd.addExtraInfo(
TransformedHashKey,
MakeTransformHash(md)
) // mark as transformed by this version, so we don't need to do it again
for (
(key, value) <- md.getExtraAttributesNamespaces
.asInstanceOf[java.util.Map[String, String]]
.asScala
) dmd.addExtraAttributeNamespace(key, value)
IvySbt.addExtraNamespace(dmd)
val withExtra = ArraySeq.unsafeWrapArray(md.getDependencies) map { dd =>
addExtra(dd, dependencyExtra)
}
val withVersionRangeMod: Seq[DependencyDescriptor] =
if (LMSysProp.modifyVersionRange) withExtra map { stripVersionRange } else withExtra
val unique = IvySbt.mergeDuplicateDefinitions(withVersionRangeMod)
unique foreach dmd.addDependency
for (ed <- md.getInheritedDescriptors)
dmd.addInheritedDescriptor(
new DefaultExtendsDescriptor(md, ed.getLocation, ed.getExtendsTypes)
)
for (conf <- md.getConfigurations) {
dmd.addConfiguration(conf)
for (art <- md.getArtifacts(conf.getName)) {
val ext = art.getExt
val newExt = if (JarPackagings(ext)) "jar" else ext
val nart = new DefaultArtifact(
mrid,
art.getPublicationDate,
art.getName,
art.getType,
newExt,
art.getUrl,
art.getQualifiedExtraAttributes
)
dmd.addArtifact(conf.getName, nart)
}
}
dmd
}
}
@@ -1,39 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt.internal.librarymanagement
import java.io.ByteArrayInputStream
import java.net.URL
import org.apache.ivy.core.module.descriptor.{
DefaultDependencyDescriptor,
DefaultModuleDescriptor
}
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser
import org.apache.ivy.plugins.repository.Resource
import org.apache.ivy.plugins.repository.url.URLResource
/** Subclasses the default Ivy file parser in order to provide access to protected methods. */
private[sbt] object CustomXmlParser extends XmlModuleDescriptorParser {
import XmlModuleDescriptorParser.Parser
class CustomParser(settings: IvySettings, defaultConfig: Option[String])
extends Parser(CustomXmlParser, settings) {
def setSource(url: URL) = {
super.setResource(new URLResource(url))
super.setInput(url)
}
def setInput(bytes: Array[Byte]): Unit = setInput(new ByteArrayInputStream(bytes))
/** Overridden because the super implementation overwrites the module descriptor. */
override def setResource(res: Resource): Unit = ()
override def setMd(md: DefaultModuleDescriptor) = {
super.setMd(md)
if (defaultConfig.isDefined) setDefaultConfMapping("*->default(compile)")
}
override def parseDepsConfs(confs: String, dd: DefaultDependencyDescriptor) =
super.parseDepsConfs(confs, dd)
override def getDefaultConf = defaultConfig.getOrElse(super.getDefaultConf)
}
}
@@ -1,75 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt.internal.librarymanagement
import java.io.{ File, FileInputStream, IOException }
import java.net.{ HttpURLConnection, URL }
import org.apache.ivy.util.url.{ BasicURLHandler, IvyAuthenticator }
import org.apache.ivy.util.{ CopyProgressListener, FileUtil, Message }
import org.apache.ivy.Ivy
import scala.io.Source
import scala.util.Using
private[librarymanagement] class ErrorLoggingURLHandler extends BasicURLHandler {
private val ErrorBodyTruncateLen = 1024
override def upload(
source: File,
dest: URL,
l: CopyProgressListener
): Unit = {
if (dest.getProtocol != "http" && dest.getProtocol != "https") {
throw new UnsupportedOperationException(
"URL repository only support HTTP PUT at the moment"
)
}
IvyAuthenticator.install()
var conn: HttpURLConnection = null
try {
val normalizedDest = normalizeToURL(dest)
conn = normalizedDest.openConnection().asInstanceOf[HttpURLConnection]
conn.setDoOutput(true)
conn.setRequestMethod("PUT")
conn.setRequestProperty("User-Agent", "Apache Ivy/" + Ivy.getIvyVersion)
conn.setRequestProperty(
"Accept",
"application/octet-stream, application/json, application/xml, */*"
)
conn.setRequestProperty("Content-type", "application/octet-stream")
conn.setRequestProperty("Content-length", source.length().toString)
conn.setInstanceFollowRedirects(true)
val in = new FileInputStream(source)
try {
val os = conn.getOutputStream
FileUtil.copy(in, os, l)
} finally {
try in.close()
catch { case _: IOException => }
}
val responseCode = conn.getResponseCode
val responseMessage = conn.getResponseMessage
val errorBody = Option(conn.getErrorStream).map { stream =>
Using.resource(stream) { s =>
val body = Source.fromInputStream(s, "UTF-8").mkString
if (body.length > ErrorBodyTruncateLen)
body.take(ErrorBodyTruncateLen) + "..."
else body
}
}
errorBody.filter(_.nonEmpty).foreach { body =>
Message.error(s"Server response body: $body")
}
validatePutStatusCode(dest, responseCode, responseMessage)
} finally {
if (conn != null) conn.disconnect()
}
}
}
@@ -1,217 +0,0 @@
package sbt
import java.io.File
import java.net.URI
import org.apache.ivy.core.cache.ArtifactOrigin
import org.apache.ivy.core.cache.{ DefaultRepositoryCacheManager, RepositoryCacheManager }
import org.apache.ivy.core.module.descriptor.{
Artifact as IvyArtifact,
DefaultArtifact,
DefaultDependencyArtifactDescriptor,
DefaultModuleDescriptor,
DependencyArtifactDescriptor,
DependencyDescriptor
}
import org.apache.ivy.core.module.id.ModuleRevisionId
import org.apache.ivy.core.report.ArtifactDownloadReport
import org.apache.ivy.core.report.{ DownloadReport, DownloadStatus }
import org.apache.ivy.core.report.MetadataArtifactDownloadReport
import org.apache.ivy.core.resolve.{ DownloadOptions, ResolveData, ResolvedModuleRevision }
import org.apache.ivy.core.search.{ ModuleEntry, OrganisationEntry, RevisionEntry }
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.plugins.namespace.Namespace
import org.apache.ivy.plugins.resolver.{ DependencyResolver, ResolverSettings }
import org.apache.ivy.plugins.resolver.util.ResolvedResource
import FakeResolver.*
/**
* A fake `DependencyResolver` that statically serves predefined artifacts.
*/
private[sbt] class FakeResolver(private var name: String, cacheDir: File, modules: ModulesMap)
extends DependencyResolver {
private object Artifact {
def unapply(art: IvyArtifact): Some[(String, String, String)] = {
val revisionID = art.getModuleRevisionId()
val organisation = revisionID.getOrganisation
val name = revisionID.getName
val revision = revisionID.getRevision
Some((organisation, name, revision))
}
def unapply(dd: DependencyDescriptor): Some[(String, String, String)] = {
val module = dd.getDependencyId()
val organisation = module.getOrganisation
val name = module.getName
val mrid = dd.getDependencyRevisionId()
val revision = mrid.getRevision()
Some((organisation, name, revision))
}
}
override def publish(artifact: IvyArtifact, src: File, overwrite: Boolean): Unit =
throw new UnsupportedOperationException("This resolver doesn't support publishing.")
override def abortPublishTransaction(): Unit =
throw new UnsupportedOperationException("This resolver doesn't support publishing.")
override def beginPublishTransaction(module: ModuleRevisionId, overwrite: Boolean): Unit =
throw new UnsupportedOperationException("This resolver doesn't support publishing.")
override def commitPublishTransaction(): Unit =
throw new UnsupportedOperationException("This resolver doesn't support publishing.")
override def download(
artifact: ArtifactOrigin,
options: DownloadOptions
): ArtifactDownloadReport = {
val report = new ArtifactDownloadReport(artifact.getArtifact)
val path = new URI(artifact.getLocation).getPath
val localFile = new File(path)
if (path.nonEmpty && localFile.exists) {
report.setLocalFile(localFile)
report.setDownloadStatus(DownloadStatus.SUCCESSFUL)
report.setSize(localFile.length)
} else {
report.setDownloadStatus(DownloadStatus.FAILED)
}
report
}
override def download(artifacts: Array[IvyArtifact], options: DownloadOptions): DownloadReport = {
val report = new DownloadReport
artifacts foreach { art =>
Option(locate(art)) foreach (o => report.addArtifactReport(download(o, options)))
}
report
}
override def dumpSettings(): Unit = ()
override def exists(artifact: IvyArtifact): Boolean = {
val Artifact(organisation, name, revision) = artifact
modules.get((organisation, name, revision)).isDefined
}
// This is a fake resolver and we don't have Ivy files. Ivy's spec says we can return `null` if
// we can't find the module descriptor.
override def findIvyFileRef(dd: DependencyDescriptor, data: ResolveData): ResolvedResource = null
override def getDependency(
dd: DependencyDescriptor,
data: ResolveData
): ResolvedModuleRevision = {
val Artifact(organisation, name, revision) = dd
val mrid = dd.getDependencyRevisionId()
val artifact = modules get ((organisation, name, revision)) map { arts =>
val artifacts: Array[DependencyArtifactDescriptor] = arts.toArray.map(_.artifactOf(dd))
val moduleDescriptor = DefaultModuleDescriptor.newDefaultInstance(mrid, artifacts)
val defaultArtifact = arts.headOption match {
case Some(FakeArtifact(name, tpe, ext, _)) =>
new DefaultArtifact(mrid, new java.util.Date, name, tpe, ext)
case None => null
}
val metadataReport = new MetadataArtifactDownloadReport(defaultArtifact)
metadataReport.setDownloadStatus(DownloadStatus.SUCCESSFUL)
new ResolvedModuleRevision(this, this, moduleDescriptor, metadataReport)
}
artifact.orNull
}
override def getName(): String = name
override val getNamespace: Namespace = {
val ns = new Namespace()
ns.setName(name)
ns
}
override val getRepositoryCacheManager: RepositoryCacheManager = {
val cacheName = name + "-cache"
val ivySettings = new IvySettings()
val baseDir = cacheDir
new DefaultRepositoryCacheManager(cacheName, ivySettings, baseDir)
}
override def listModules(organisation: OrganisationEntry): Array[ModuleEntry] =
modules.keys.collect {
case (o, m, _) if o == organisation.getOrganisation =>
val organisationEntry = new OrganisationEntry(this, o)
new ModuleEntry(organisationEntry, m)
}.toArray
override def listOrganisations(): Array[OrganisationEntry] =
modules.keys.map { case (o, _, _) => new OrganisationEntry(this, o) }.toArray
override def listRevisions(module: ModuleEntry): Array[RevisionEntry] =
modules.keys.collect {
case (o, m, v) if o == module.getOrganisation && m == module.getModule =>
new RevisionEntry(module, v)
}.toArray
override def listTokenValues(
tokens: Array[String],
criteria: java.util.Map[?, ?]
): Array[java.util.Map[?, ?]] =
Array.empty
override def listTokenValues(
token: String,
otherTokenValues: java.util.Map[?, ?]
): Array[String] =
Array.empty
override def locate(art: IvyArtifact): ArtifactOrigin = {
val Artifact(moduleOrganisation, moduleName, moduleRevision) = art
val artifact =
for {
artifacts <- modules get ((moduleOrganisation, moduleName, moduleRevision))
artifact <- artifacts find (a =>
a.name == art.getName && a.tpe == art.getType && a.ext == art.getExt
)
} yield new ArtifactOrigin(art, /* isLocal = */ true, artifact.file.toURI.toURL.toString)
artifact.orNull
}
override def reportFailure(art: IvyArtifact): Unit = ()
override def reportFailure(): Unit = ()
override def setName(name: String): Unit = {
this.name = name
getNamespace.setName(name)
}
override def setSettings(settings: ResolverSettings): Unit = ()
}
private[sbt] object FakeResolver {
type ModulesMap = Map[(String, String, String), Seq[FakeArtifact]]
final case class FakeArtifact(name: String, tpe: String, ext: String, file: File) {
def artifactOf(dd: DependencyDescriptor): DependencyArtifactDescriptor =
new DefaultDependencyArtifactDescriptor(
dd,
name,
tpe,
ext,
file.toURI.toURL,
new java.util.HashMap
)
}
}
File diff suppressed because it is too large Load Diff
@@ -1,599 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt.internal.librarymanagement
import java.io.File
import ivyint.CachedResolutionResolveEngine
import org.apache.ivy.Ivy
import org.apache.ivy.core.{ IvyPatternHelper, LogOptions }
import org.apache.ivy.core.deliver.DeliverOptions
import org.apache.ivy.core.install.InstallOptions
import org.apache.ivy.core.module.descriptor.{
DefaultModuleDescriptor,
MDArtifact,
ModuleDescriptor,
Artifact as IArtifact
}
import org.apache.ivy.core.resolve.ResolveOptions
import org.apache.ivy.plugins.resolver.{ BasicResolver, DependencyResolver }
import org.apache.ivy.util.filter.Filter as IvyFilter
import sbt.io.{ IO, PathFinder }
import sbt.util.Logger
import sbt.librarymanagement.{ ModuleDescriptorConfiguration as InlineConfiguration, * }
import syntax.*
import InternalDefaults.*
import UpdateClassifiersUtil.*
import sbt.internal.librarymanagement.IvyUtil.TransientNetworkException
object IvyActions {
/** Installs the dependencies of the given 'module' from the resolver named 'from' to the resolver named 'to'. */
def install(module: IvySbt#Module, from: String, to: String, log: Logger): Unit = {
module.withModule(log) { (ivy, md, _) =>
for (dependency <- md.getDependencies) {
log.info("Installing " + dependency)
val options = new InstallOptions
options.setValidate(module.moduleSettings.validate)
options.setTransitive(dependency.isTransitive)
ivy.install(dependency.getDependencyRevisionId, from, to, options)
}
}
}
/** Clears the Ivy cache, as configured by 'config'. */
def cleanCache(ivy: IvySbt, log: Logger) = ivy.withIvy(log) { iv =>
iv.getSettings.getResolutionCacheManager.clean()
iv.getSettings.getRepositoryCacheManagers.foreach(_.clean())
}
/**
* Cleans the cached resolution cache, if any.
* This is called by clean.
*/
private[sbt] def cleanCachedResolutionCache(module: IvySbt#Module, log: Logger): Unit =
module.withModule(log) { (_, _, _) =>
module.owner.cleanCachedResolutionCache()
}
/** Creates a Maven pom from the given Ivy configuration */
def makePomFile(module: IvySbt#Module, configuration: MakePomConfiguration, log: Logger): File = {
import configuration.{
allRepositories,
configurations,
filterRepositories,
process,
includeTypes
}
val file = configuration.file.getOrElse(sys.error("file must be specified."))
val moduleInfo = configuration.moduleInfo.getOrElse(sys.error("moduleInfo must be specified."))
val extra = configuration.extra.getOrElse(scala.xml.NodeSeq.Empty)
module.withModule(log) { (ivy, md, _) =>
(new MakePom(log)).write(
ivy,
md,
moduleInfo,
configurations,
includeTypes,
extra,
process,
filterRepositories,
allRepositories,
file
)
log.info("Wrote " + file.getAbsolutePath)
file
}
}
def deliver(module: IvySbt#Module, configuration: PublishConfiguration, log: Logger): File = {
val deliverIvyPattern = configuration.deliverIvyPattern
.getOrElse(sys.error("deliverIvyPattern must be specified."))
val status = getDeliverStatus(configuration.status)
module.withModule(log) { case (ivy, md, _) =>
val revID = md.getModuleRevisionId
val options = DeliverOptions.newInstance(ivy.getSettings).setStatus(status)
options.setConfs(getConfigurations(md, configuration.configurations))
ivy.deliver(revID, revID.getRevision, deliverIvyPattern, options)
val file = deliveredFile(ivy, deliverIvyPattern, md)
// Apply dependency overrides to the delivered Ivy XML
applyOverridesToDeliveredIvy(file, module.moduleSettings, log)
file
}
}
/**
* Post-processes the delivered Ivy XML file to apply dependency overrides.
* This is necessary because Ivy's deliver() method doesn't automatically apply
* DependencyDescriptorMediators when writing the XML.
*/
private def applyOverridesToDeliveredIvy(
ivyFile: File,
moduleSettings: ModuleSettings,
log: Logger
): Unit = {
moduleSettings match {
case ic: InlineConfiguration if ic.overrides.nonEmpty =>
val overrideMap = ic.overrides.map(m => (m.organization, m.name) -> m.revision).toMap
if (ivyFile.exists()) {
val xml = scala.xml.XML.loadFile(ivyFile)
val updated = applyDependencyOverrides(xml, overrideMap)
scala.xml.XML.save(ivyFile.getAbsolutePath, updated, "UTF-8", xmlDecl = true, null)
log.debug(s"Applied ${overrideMap.size} dependency override(s) to ${ivyFile.getName}")
}
case _ => // No overrides to apply
}
}
/**
* Applies dependency overrides to an Ivy XML node by updating the rev attribute
* of dependency elements that match entries in the override map.
*
* @param xml The Ivy XML root node to transform
* @param overrideMap Map from (organization, name) to the overridden revision
* @return The transformed XML node with updated dependency revisions
*/
def applyDependencyOverrides(
xml: scala.xml.Node,
overrideMap: Map[(String, String), String]
): scala.xml.Node = {
new scala.xml.transform.RuleTransformer(new scala.xml.transform.RewriteRule {
override def transform(n: scala.xml.Node): Seq[scala.xml.Node] = n match {
case e @ scala.xml.Elem(prefix, "dependency", attrs, scope, children*) =>
val org = attrs.get("org").map(_.text).getOrElse("")
val name = attrs.get("name").map(_.text).getOrElse("")
overrideMap.get((org, name)) match {
case Some(overrideRev) =>
def updateAttrs(metadata: scala.xml.MetaData): scala.xml.MetaData = {
metadata match {
case scala.xml.Null => scala.xml.Null
case attr if attr.key == "rev" =>
new scala.xml.UnprefixedAttribute(
"rev",
overrideRev,
updateAttrs(attr.next)
)
case attr =>
attr.copy(next = updateAttrs(attr.next))
}
}
scala.xml.Elem(
prefix,
"dependency",
updateAttrs(attrs),
scope,
minimizeEmpty = true,
children*
)
case None => e
}
case other => other
}
}).transform(xml).head
}
def getConfigurations(
module: ModuleDescriptor,
configurations: Option[Vector[ConfigRef]]
): Array[String] =
configurations match {
case Some(confs) => (confs map { _.name }).toArray
case None => module.getPublicConfigurationsNames
}
def deliveredFile(ivy: Ivy, pattern: String, md: ModuleDescriptor): File =
ivy.getSettings.resolveFile(
IvyPatternHelper.substitute(pattern, md.getResolvedModuleRevisionId)
)
def publish(module: IvySbt#Module, configuration: PublishConfiguration, log: Logger): Unit = {
val resolverName = configuration.resolverName match {
case Some(x) => x
case _ => sys.error("Resolver name is not specified")
}
// Todo. Fix publish ordering https://github.com/sbt/sbt/issues/2088#issuecomment-246208872
val ivyFile: Option[File] =
if (configuration.publishMavenStyle) None
else {
Option(deliver(module, configuration, log))
}
val artifacts = Map(configuration.artifacts*)
val checksums = configuration.checksums
module.withModule(log) { case (ivy, md, _) =>
val resolver = ivy.getSettings.getResolver(resolverName)
if (resolver eq null) sys.error("Undefined resolver '" + resolverName + "'")
val ivyArtifact = ivyFile map { file =>
(MDArtifact.newIvyArtifact(md), file)
}
val cross = crossVersionMap(module.moduleSettings)
val as = mapArtifacts(md, cross, artifacts) ++ ivyArtifact.toList
withChecksums(resolver, checksums) {
publish(md, as, resolver, overwrite = configuration.overwrite)
}
}
}
private def withChecksums[T](resolver: DependencyResolver, checksums: Vector[String])(
act: => T
): T =
resolver match { case br: BasicResolver => withChecksums(br, checksums)(act); case _ => act }
private def withChecksums[T](resolver: BasicResolver, checksums: Vector[String])(
act: => T
): T = {
val previous = resolver.getChecksumAlgorithms
resolver.setChecksums(checksums mkString ",")
try {
act
} finally {
resolver.setChecksums(previous mkString ",")
}
}
private def crossVersionMap(moduleSettings: ModuleSettings): Option[String => String] =
moduleSettings match {
case i: InlineConfiguration => CrossVersion(i.module, i.scalaModuleInfo)
case _ => None
}
def mapArtifacts(
module: ModuleDescriptor,
cross: Option[String => String],
artifacts: Map[Artifact, File]
): Vector[(IArtifact, File)] = {
val rawa = artifacts.keys.toVector
val seqa = CrossVersion.substituteCross(rawa, cross)
val zipped = rawa zip IvySbt.mapArtifacts(module, seqa)
zipped map { (a, ivyA) => (ivyA, artifacts(a)) }
}
/**
* Updates one module's dependencies performing a dependency resolution and retrieval.
*
* The following mechanism uses ivy under the hood.
*
* @param module The module to be resolved.
* @param configuration The update configuration.
* @param uwconfig The configuration to handle unresolved warnings.
* @param log The logger.
* @return The result, either an unresolved warning or an update report. Note that this
* update report will or will not be successful depending on the `missingOk` option.
*/
private[sbt] def updateEither(
module: IvySbt#Module,
configuration: UpdateConfiguration,
uwconfig: UnresolvedWarningConfiguration,
log: Logger
): Either[UnresolvedWarning, UpdateReport] = {
module.withModule(log) { case (ivy, moduleDescriptor, _) =>
// Warn about duplicated and inconsistent dependencies
val iw = IvySbt.inconsistentDuplicateWarning(moduleDescriptor)
iw.foreach(log.warn(_))
val metadataDirectory = configuration.metadataDirectory
// Create inputs, resolve and retrieve the module descriptor
val inputs = ResolutionInputs(ivy, moduleDescriptor, configuration, log)
val resolutionResult: Either[ResolveException, UpdateReport] = {
if (
module.owner.configuration.updateOptions.cachedResolution && metadataDirectory.isDefined
) {
val cache =
metadataDirectory.getOrElse(sys.error("Missing directory for cached resolution."))
cachedResolveAndRetrieve(inputs, cache)
} else resolveAndRetrieve(inputs)
}
// Convert to unresolved warning or retrieve update report
resolutionResult.fold(
exception => Left(UnresolvedWarning(exception, uwconfig)),
ur0 => {
val ur = configuration.retrieveManaged match {
case Some(retrieveConf) => retrieve(log, ivy, ur0, retrieveConf)
case _ => ur0
}
Right(ur)
}
)
}
}
def groupedConflicts[T](moduleFilter: ModuleFilter, grouping: ModuleID => T)(
report: UpdateReport
): Map[T, Set[String]] =
report.configurations.flatMap { confReport =>
val evicted = confReport.evicted.filter(moduleFilter)
val evictedSet = evicted.map(m => (m.organization, m.name)).toSet
val conflicted =
confReport.allModules.filter(mod => evictedSet((mod.organization, mod.name)))
grouped(grouping)(conflicted ++ evicted)
}.toMap
def grouped[T](grouping: ModuleID => T)(mods: Seq[ModuleID]): Map[T, Set[String]] =
mods.groupBy(grouping).view.mapValues(_.map(_.revision).toSet).toMap
def addExcluded(
report: UpdateReport,
classifiers: Vector[String],
exclude: Map[ModuleID, Set[String]]
): UpdateReport =
report.addMissing { id =>
classifiedArtifacts(id.name, classifiers filter getExcluded(id, exclude))
}
private def getExcluded(id: ModuleID, exclude: Map[ModuleID, Set[String]]): Set[String] =
exclude.getOrElse(restrictedCopy(id, false), Set.empty[String])
def extractExcludes(report: UpdateReport): Map[ModuleID, Set[String]] =
report.allMissing flatMap { case (_, mod, art) =>
art.classifier.map { c =>
(restrictedCopy(mod, false), c)
}
} groupBy (_._1) map { (mod, pairs) => (mod, pairs.map(_._2).toSet) }
/**
* Represents the inputs to pass in to [[resolveAndRetrieve]] and [[cachedResolveAndRetrieve]].
*
* @param ivy The ivy instance to resolve and retrieve dependencies.
* @param module The module descriptor to be resolved.
* @param updateConfiguration The update configuration for [[ResolveOptions]].
* @param log The logger.
*/
private case class ResolutionInputs(
ivy: Ivy,
module: DefaultModuleDescriptor,
updateConfiguration: UpdateConfiguration,
log: Logger
)
implicit def toIvyFilter(f: ArtifactTypeFilter): IvyFilter = new IvyFilter {
override def accept(o: Object): Boolean = Option(o) exists { case a: IArtifact =>
applyFilter(a)
}
def applyFilter(a: IArtifact): Boolean =
(f.types contains a.getType) ^ f.inverted
}
/**
* Defines the internal entrypoint of module resolution and retrieval.
*
* This method is the responsible of populating [[ResolveOptions]] and pass
* it in to the ivy instance to perform the module resolution.
*
* It returns an already resolved [[UpdateReport]] instead of a [[ResolveReport]]
* like its counterpart [[CachedResolutionResolveEngine.customResolve]].
*
* @param inputs The resolution inputs.
* @return The result of the resolution.
*/
private def resolveAndRetrieve(
inputs: ResolutionInputs
): Either[ResolveException, UpdateReport] = {
// Populate resolve options from the passed arguments
val ivyInstance = inputs.ivy
val moduleDescriptor = inputs.module
val updateConfiguration = inputs.updateConfiguration
val resolveOptions = new ResolveOptions
val resolveId = ResolveOptions.getDefaultResolveId(moduleDescriptor)
val artifactFilter = getArtifactTypeFilter(updateConfiguration.artifactFilter)
import updateConfiguration.*
resolveOptions.setResolveId(resolveId)
resolveOptions.setArtifactFilter(artifactFilter)
resolveOptions.setUseCacheOnly(offline)
resolveOptions.setLog(ivyLogLevel(logging))
if (frozen) {
resolveOptions.setTransitive(false)
resolveOptions.setCheckIfChanged(false)
}
ResolutionCache.cleanModule(
moduleDescriptor.getModuleRevisionId,
resolveId,
ivyInstance.getSettings.getResolutionCacheManager
)
val resolveReport = ivyInstance.resolve(moduleDescriptor, resolveOptions)
if (resolveReport.hasError && !missingOk) {
import scala.jdk.CollectionConverters.*
// If strict error, collect report information and generated UnresolvedWarning
val messages = resolveReport.getAllProblemMessages.asScala.toSeq.map(_.toString).distinct
val failedPaths = resolveReport.getUnresolvedDependencies.map { node =>
val moduleID = IvyRetrieve.toModuleID(node.getId)
val path = IvyRetrieve
.findPath(node, moduleDescriptor.getModuleRevisionId)
.map(x => IvyRetrieve.toModuleID(x.getId))
moduleID -> path
}.toMap
val failedModules = failedPaths.keys.toSeq
Left(new ResolveException(messages, failedModules, failedPaths))
} else {
// If no strict error, we convert the resolve report into an update report
val cachedDescriptor = ivyInstance.getSettings.getResolutionCacheManager
.getResolvedIvyFileInCache(moduleDescriptor.getModuleRevisionId)
Right(IvyRetrieve.updateReport(resolveReport, cachedDescriptor))
}
}
/**
* Resolves and retrieves a module with a cache mechanism defined in
* <a href="https://www.scala-sbt.org/1.x/docs/Cached-Resolution.html">sbt Cached Resolution</a>.
*
* It's the cached version of [[resolveAndRetrieve]].
*
* @param inputs The resolution inputs.
* @param cache The optional cache dependency.
* @return The result of the cached resolution.
*/
private def cachedResolveAndRetrieve(
inputs: ResolutionInputs,
cache: File
): Either[ResolveException, UpdateReport] = {
val log = inputs.log
val descriptor = inputs.module
val updateConfiguration = inputs.updateConfiguration
val resolver = inputs.ivy.getResolveEngine.asInstanceOf[CachedResolutionResolveEngine]
val resolveOptions = new ResolveOptions
val resolveId = ResolveOptions.getDefaultResolveId(descriptor)
val artifactFilter = getArtifactTypeFilter(updateConfiguration.artifactFilter)
import updateConfiguration.*
resolveOptions.setResolveId(resolveId)
resolveOptions.setArtifactFilter(artifactFilter)
resolveOptions.setUseCacheOnly(offline)
resolveOptions.setLog(ivyLogLevel(logging))
if (frozen) {
resolveOptions.setTransitive(false)
resolveOptions.setCheckIfChanged(false)
}
resolver.customResolve(
descriptor,
missingOk,
updateConfiguration.logicalClock,
resolveOptions,
cache,
log
)
}
private def retrieve(
log: Logger,
ivy: Ivy,
report: UpdateReport,
config: RetrieveConfiguration
): UpdateReport = {
val copyChecksums =
Option(ivy.getVariable(ConvertResolver.ManagedChecksums)) match {
case Some(x) => x.toBoolean
case _ => false
}
val toRetrieve: Option[Vector[ConfigRef]] = config.configurationsToRetrieve
val base = getRetrieveDirectory(config.retrieveDirectory)
val pattern = getRetrievePattern(config.outputPattern)
val existingFiles = PathFinder(base).allPaths.get() filterNot { _.isDirectory }
val toCopy = new collection.mutable.HashSet[(File, File)]
val retReport = report retrieve { (conf: ConfigRef, mid, art, cached) =>
toRetrieve match {
case None => performRetrieve(conf, mid, art, base, pattern, cached, copyChecksums, toCopy)
case Some(refs) if refs.contains[ConfigRef](conf) =>
performRetrieve(conf, mid, art, base, pattern, cached, copyChecksums, toCopy)
case _ => cached
}
}
IO.copy(toCopy)
val resolvedFiles = toCopy.map(_._2)
if (config.sync) {
val filesToDelete = existingFiles.filterNot(resolvedFiles.contains)
filesToDelete foreach { f =>
log.info(s"Deleting old dependency: ${f.getAbsolutePath}")
f.delete()
}
}
retReport
}
private def performRetrieve(
conf: ConfigRef,
mid: ModuleID,
art: Artifact,
base: File,
pattern: String,
cached: File,
copyChecksums: Boolean,
toCopy: collection.mutable.HashSet[(File, File)]
): File = {
val to = retrieveTarget(conf, mid, art, base, pattern)
toCopy += ((cached, to))
if (copyChecksums) {
// Copy over to the lib managed directory any checksum for a jar if it exists
// TODO(jvican): Support user-provided checksums
val cachePath = cached.getAbsolutePath
IvySbt.DefaultChecksums.foreach { checksum =>
if (cachePath.endsWith(".jar")) {
val cacheChecksum = new File(s"$cachePath.$checksum")
if (cacheChecksum.exists()) {
val toChecksum = new File(s"${to.getAbsolutePath}.$checksum")
toCopy += ((cacheChecksum, toChecksum))
}
}
}
}
to
}
private def retrieveTarget(
conf: ConfigRef,
mid: ModuleID,
art: Artifact,
base: File,
pattern: String
): File =
new File(base, substitute(conf, mid, art, pattern))
private def substitute(conf: ConfigRef, mid: ModuleID, art: Artifact, pattern: String): String = {
val mextra = IvySbt.javaMap(mid.extraAttributes, true)
val aextra = IvySbt.extra(art, true)
IvyPatternHelper.substitute(
pattern,
mid.organization,
mid.name,
mid.branchName.orNull,
mid.revision,
art.name,
art.`type`,
art.extension,
conf.name,
null,
mextra,
aextra
)
}
import UpdateLogging.{ Quiet, Full, DownloadOnly, Default }
import LogOptions.{ LOG_QUIET, LOG_DEFAULT, LOG_DOWNLOAD_ONLY }
private def ivyLogLevel(level: UpdateLogging) =
level match {
case Quiet => LOG_QUIET
case DownloadOnly => LOG_DOWNLOAD_ONLY
case Full => LOG_DEFAULT
case Default => LOG_DOWNLOAD_ONLY
}
def publish(
module: ModuleDescriptor,
artifacts: Seq[(IArtifact, File)],
resolver: DependencyResolver,
overwrite: Boolean
): Unit = {
if (artifacts.nonEmpty) {
checkFilesPresent(artifacts)
try {
resolver.beginPublishTransaction(module.getModuleRevisionId(), overwrite);
artifacts.foreach { (artifact, file) =>
IvyUtil.retryWithBackoff(
resolver.publish(artifact, file, overwrite),
TransientNetworkException.apply,
maxAttempts = LMSysProp.maxPublishAttempts
)
}
resolver.commitPublishTransaction()
} catch {
case e: Throwable =>
try {
resolver.abortPublishTransaction()
} finally {
throw e
}
}
}
}
private def checkFilesPresent(artifacts: Seq[(IArtifact, File)]): Unit = {
val missing = artifacts filter { case (_, file) => !file.exists }
if (missing.nonEmpty)
sys.error(
"Missing files for publishing:\n\t" + missing.map(_._2.getAbsolutePath).mkString("\n\t")
)
}
}
@@ -1,132 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt.internal.librarymanagement
import java.io.File
import org.apache.ivy.core.cache.{
ArtifactOrigin,
CacheDownloadOptions,
DefaultRepositoryCacheManager
}
import org.apache.ivy.core.module.descriptor.{ Artifact as IvyArtifact, DefaultArtifact }
import org.apache.ivy.plugins.repository.file.{ FileRepository as IvyFileRepository, FileResource }
import org.apache.ivy.plugins.repository.{ ArtifactResourceResolver, Resource, ResourceDownloader }
import org.apache.ivy.plugins.resolver.util.ResolvedResource
import org.apache.ivy.util.FileUtil
import sbt.io.Path
import sbt.internal.librarymanagement.ivy.InlineIvyConfiguration
import sbt.librarymanagement.*
import sbt.util.Logger
class NotInCache(val id: ModuleID, cause: Throwable)
extends RuntimeException(NotInCache(id, cause), cause) {
def this(id: ModuleID) = this(id, null)
}
private object NotInCache {
def apply(id: ModuleID, cause: Throwable) = {
val postfix = if (cause == null) "" else (": " + cause.toString)
"File for " + id + " not in cache" + postfix
}
}
/** Provides methods for working at the level of a single jar file with the default Ivy cache. */
class IvyCache(val ivyHome: Option[File]) {
def lockFile = new File(ivyHome getOrElse Path.userHome, ".sbt.cache.lock")
/** Caches the given 'file' with the given ID. It may be retrieved or cleared using this ID. */
def cacheJar(
moduleID: ModuleID,
file: File,
lock: Option[xsbti.GlobalLock],
log: Logger
): Unit = {
val artifact = defaultArtifact(moduleID)
val resolved =
new ResolvedResource(new FileResource(new IvyFileRepository, file), moduleID.revision)
withDefaultCache(lock, log) { cache =>
val resolver = new ArtifactResourceResolver { def resolve(artifact: IvyArtifact) = resolved }
cache.download(artifact, resolver, new FileDownloader, new CacheDownloadOptions)
()
}
}
/** Clears the cache of the jar for the given ID. */
def clearCachedJar(id: ModuleID, lock: Option[xsbti.GlobalLock], log: Logger): Unit = {
try {
withCachedJar(id, lock, log)(_.delete); ()
} catch {
case e: Exception => log.debug("Error cleaning cached jar: " + e.toString)
}
}
/** Copies the cached jar for the given ID to the directory 'toDirectory'. If the jar is not in the cache, NotInCache is thrown. */
def retrieveCachedJar(
id: ModuleID,
toDirectory: File,
lock: Option[xsbti.GlobalLock],
log: Logger
) =
withCachedJar(id, lock, log) { cachedFile =>
val copyTo = new File(toDirectory, cachedFile.getName)
FileUtil.copy(cachedFile, copyTo, null)
copyTo
}
/** Get the location of the cached jar for the given ID in the Ivy cache. If the jar is not in the cache, NotInCache is thrown . */
def withCachedJar[T](id: ModuleID, lock: Option[xsbti.GlobalLock], log: Logger)(
f: File => T
): T = {
val cachedFile =
try {
withDefaultCache(lock, log) { cache =>
val artifact = defaultArtifact(id)
cache.getArchiveFileInCache(artifact, unknownOrigin(artifact))
}
} catch { case e: Exception => throw new NotInCache(id, e) }
if (cachedFile.exists) f(cachedFile) else throw new NotInCache(id)
}
/** Calls the given function with the default Ivy cache. */
def withDefaultCache[T](lock: Option[xsbti.GlobalLock], log: Logger)(
f: DefaultRepositoryCacheManager => T
): T = {
val (ivy, _) = basicLocalIvy(lock, log)
ivy.withIvy(log) { ivy =>
val cache = ivy.getSettings.getDefaultRepositoryCacheManager
.asInstanceOf[DefaultRepositoryCacheManager]
cache.setUseOrigin(false)
f(cache)
}
}
private def unknownOrigin(artifact: IvyArtifact) = ArtifactOrigin.unkwnown(artifact)
/** A minimal Ivy setup with only a local resolver and the current directory as the base directory. */
private def basicLocalIvy(lock: Option[xsbti.GlobalLock], log: Logger) = {
val local = Resolver.defaultLocal
val paths = IvyPaths(".", ivyHome.map(_.toString))
val conf = InlineIvyConfiguration()
.withPaths(paths)
.withResolvers(Vector(local))
.withLock(lock)
.withLog(log)
(new IvySbt(conf), local)
}
/** Creates a default jar artifact based on the given ID. */
private def defaultArtifact(moduleID: ModuleID): IvyArtifact =
new DefaultArtifact(IvySbt.toID(moduleID), null, moduleID.name, "jar", "jar")
}
/** Required by Ivy for copying to the cache. */
private class FileDownloader extends ResourceDownloader {
def download(artifact: IvyArtifact, resource: Resource, dest: File): Unit = {
if (dest.exists()) dest.delete()
val part = new File(dest.getAbsolutePath + ".part")
FileUtil.copy(resource.openStream, part, null)
if (!part.renameTo(dest))
sys.error("Could not move temporary file " + part + " to final location " + dest)
}
}
@@ -1,28 +0,0 @@
package sbt
package internal.librarymanagement
import java.io.File
import sbt.librarymanagement.IvyPaths
import sbt.io.syntax.*
import xsbti.Logger as XLogger
import sbt.util.Logger
/**
* This is a list of functions with default values.
*/
object IvyInternalDefaults {
def defaultBaseDirectory: File =
(new File(".")).getAbsoluteFile / "lib_managed"
def getBaseDirectory(opt: Option[File]): File =
opt.getOrElse(defaultBaseDirectory)
def getLog(opt: Option[XLogger]): XLogger =
opt.getOrElse(Logger.Null)
def defaultIvyPaths: IvyPaths =
IvyPaths(defaultBaseDirectory.toString, None)
def getIvyPaths(opt: Option[IvyPaths]): IvyPaths =
opt.getOrElse(defaultIvyPaths)
}
@@ -1,58 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt.internal.librarymanagement
import org.apache.ivy.util.{ Message, MessageLogger, MessageLoggerEngine }
import sbt.util.Logger
/** Interface to Ivy logging. */
private[sbt] final class IvyLoggerInterface(logger: Logger) extends MessageLogger {
def rawlog(msg: String, level: Int): Unit = log(msg, level)
def log(msg: String, level: Int): Unit = {
import Message.{ MSG_DEBUG, MSG_VERBOSE, MSG_INFO, MSG_WARN, MSG_ERR }
level match {
case MSG_DEBUG => debug(msg)
case MSG_VERBOSE => verbose(msg)
case MSG_INFO => info(msg)
case MSG_WARN => warn(msg)
case MSG_ERR => error(msg)
}
}
// DEBUG level messages are very verbose and rarely useful to users.
// TODO: provide access to this information some other way
def debug(msg: String): Unit = ()
def verbose(msg: String): Unit = logger.verbose(msg)
def deprecated(msg: String): Unit = warn(msg)
def info(msg: String): Unit = if (SbtIvyLogger.acceptInfo(msg)) logger.info(msg)
def rawinfo(msg: String): Unit = info(msg)
def warn(msg: String): Unit = logger.warn(msg)
def error(msg: String): Unit = if (SbtIvyLogger.acceptError(msg)) logger.error(msg)
private def emptyList = java.util.Collections.emptyList[String]
def getProblems = emptyList
def getWarns = emptyList
def getErrors = emptyList
def clearProblems(): Unit = ()
def sumupProblems(): Unit = clearProblems()
def progress(): Unit = ()
def endProgress(): Unit = ()
def endProgress(msg: String): Unit = info(msg)
def isShowProgress = false
def setShowProgress(progress: Boolean): Unit = ()
}
private[sbt] final class SbtMessageLoggerEngine extends MessageLoggerEngine {
/** This is a hack to filter error messages about 'unknown resolver ...'. */
override def error(msg: String): Unit = if (SbtIvyLogger.acceptError(msg)) super.error(msg)
override def sumupProblems(): Unit = clearProblems()
}
private[sbt] object SbtIvyLogger {
final val unknownResolver = "unknown resolver"
def acceptError(msg: String) = (msg ne null) && !msg.startsWith(unknownResolver)
final val loadingSettings = ":: loading settings"
def acceptInfo(msg: String) = (msg ne null) && !msg.startsWith(loadingSettings)
}
@@ -1,276 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.internal.librarymanagement
import java.io.File
import java.util as ju
import collection.mutable
import collection.immutable.ArraySeq
import org.apache.ivy.core.{ module, report, resolve }
import module.descriptor.{ Artifact as IvyArtifact, License as IvyLicense }
import module.id.{ ModuleRevisionId, ModuleId as IvyModuleId }
import report.{ ArtifactDownloadReport, ConfigurationResolveReport, ResolveReport }
import resolve.{ IvyNode, IvyNodeCallers }
import IvyNodeCallers.Caller as IvyCaller
import ivyint.SbtDefaultDependencyDescriptor
import sbt.librarymanagement.*, syntax.*
object IvyRetrieve {
def reports(report: ResolveReport): Vector[ConfigurationResolveReport] =
report.getConfigurations.toVector map report.getConfigurationReport
def moduleReports(confReport: ConfigurationResolveReport): Vector[ModuleReport] =
for {
revId <- confReport.getModuleRevisionIds.toArray.toVector collect {
case revId: ModuleRevisionId => revId
}
} yield moduleRevisionDetail(confReport, confReport.getDependency(revId))
private[sbt] def artifacts(
artReport: Seq[ArtifactDownloadReport]
): (Vector[(Artifact, File)], Vector[Artifact]) = {
val missing = new mutable.ListBuffer[Artifact]
val resolved = new mutable.ListBuffer[(Artifact, File)]
for (r <- artReport) {
val fileOpt = Option(r.getLocalFile)
val art = toArtifact(r.getArtifact)
fileOpt match {
case Some(file) => resolved += ((art, file))
case None => missing += art
}
}
(resolved.toVector, missing.toVector)
}
// We need this because current module report used as part of UpdateReport/ConfigurationReport contains
// only the revolved modules.
// Sometimes the entire module can be excluded via rules etc.
private[sbt] def organizationArtifactReports(
confReport: ConfigurationResolveReport
): Vector[OrganizationArtifactReport] = {
val moduleIds = confReport.getModuleIds.toArray.toVector collect { case mId: IvyModuleId =>
mId
}
def organizationArtifact(mid: IvyModuleId): OrganizationArtifactReport = {
val deps = confReport.getNodes(mid).toArray.toVector collect { case node: IvyNode => node }
OrganizationArtifactReport(
mid.getOrganisation,
mid.getName,
deps map {
moduleRevisionDetail(confReport, _)
}
)
}
moduleIds map { organizationArtifact }
}
private[sbt] def nonEmptyString(s: String): Option[String] =
s match {
case null => None
case x if x.trim == "" => None
case x => Some(x.trim)
}
private[sbt] def moduleRevisionDetail(
confReport: ConfigurationResolveReport,
dep: IvyNode
): ModuleReport = {
def toExtraAttributes(ea: ju.Map[?, ?]): Map[String, String] =
Map(ea.entrySet.toArray collect {
case entry: ju.Map.Entry[?, ?]
if nonEmptyString(entry.getKey.toString).isDefined && nonEmptyString(
entry.getValue.toString
).isDefined =>
(entry.getKey.toString, entry.getValue.toString)
}*)
def toCaller(caller: IvyCaller): Caller = {
val m = toModuleID(caller.getModuleRevisionId)
val callerConfigurations = caller.getCallerConfigurations.toVector collect {
case x if nonEmptyString(x).isDefined => ConfigRef(x)
}
val ddOpt = Option(caller.getDependencyDescriptor)
val (extraAttributes, isForce, isChanging, isTransitive, isDirectlyForce) = ddOpt match {
case Some(dd: SbtDefaultDependencyDescriptor) =>
val mod = dd.dependencyModuleId
(
toExtraAttributes(dd.getExtraAttributes),
mod.isForce,
mod.isChanging,
mod.isTransitive,
mod.isForce
)
case Some(dd) =>
(
toExtraAttributes(dd.getExtraAttributes),
dd.isForce,
dd.isChanging,
dd.isTransitive,
false
)
case None => (Map.empty[String, String], false, false, true, false)
}
Caller(
m,
callerConfigurations,
extraAttributes,
isForce,
isChanging,
isTransitive,
isDirectlyForce
)
}
val revId = dep.getResolvedId
val moduleId = toModuleID(revId)
val branch = nonEmptyString(revId.getBranch)
val (status, publicationDate, resolver, artifactResolver) = dep.isLoaded match {
case true =>
val c = new ju.GregorianCalendar()
c.setTimeInMillis(dep.getPublication)
(
nonEmptyString(dep.getDescriptor.getStatus),
Some(c),
nonEmptyString(dep.getModuleRevision.getResolver.getName),
nonEmptyString(dep.getModuleRevision.getArtifactResolver.getName)
)
case _ => (None, None, None, None)
}
val (evicted, evictedData, evictedReason) = dep.isEvicted(confReport.getConfiguration) match {
case true =>
val edOpt = Option(dep.getEvictedData(confReport.getConfiguration))
edOpt match {
case Some(ed) =>
(
true,
nonEmptyString(Option(ed.getConflictManager) map { _.toString } getOrElse {
"transitive"
}),
nonEmptyString(ed.getDetail)
)
case None => (true, None, None)
}
case _ => (false, None, None)
}
val problem = dep.hasProblem match {
case true => nonEmptyString(dep.getProblem.getMessage)
case _ => None
}
val mdOpt = for {
mr <- Option(dep.getModuleRevision)
md <- Option(mr.getDescriptor)
} yield md
val homepage = mdOpt match {
case Some(md) =>
nonEmptyString(md.getHomePage)
case _ => None
}
val extraAttributes: Map[String, String] = toExtraAttributes(mdOpt match {
case Some(md) => md.getExtraAttributes
case _ => dep.getResolvedId.getExtraAttributes
})
val isDefault = Option(dep.getDescriptor) map { _.isDefault }
val configurations = dep.getConfigurations(confReport.getConfiguration).toVector map {
ConfigRef(_)
}
val licenses: Vector[(String, Option[String])] = mdOpt match {
case Some(md) =>
md.getLicenses.toVector collect {
case lic: IvyLicense if Option(lic.getName).isDefined =>
val temporaryURL = "http://localhost"
(lic.getName, nonEmptyString(lic.getUrl) orElse { Some(temporaryURL) })
}
case _ => Vector.empty
}
val callers = dep.getCallers(confReport.getConfiguration).toVector map { toCaller }
val (resolved, missing) = artifacts(
ArraySeq.unsafeWrapArray(confReport.getDownloadReports(revId))
)
ModuleReport(
moduleId,
resolved,
missing,
status,
publicationDate,
resolver,
artifactResolver,
evicted,
evictedData,
evictedReason,
problem,
homepage,
extraAttributes,
isDefault,
branch,
configurations,
licenses,
callers
)
}
def evicted(confReport: ConfigurationResolveReport): Seq[ModuleID] =
ArraySeq.unsafeWrapArray(confReport.getEvictedNodes).map(node => toModuleID(node.getId))
def toModuleID(revID: ModuleRevisionId): ModuleID =
ModuleID(revID.getOrganisation, revID.getName, revID.getRevision)
.withExtraAttributes(IvySbt.getExtraAttributes(revID))
.branch(nonEmptyString(revID.getBranch))
def toArtifact(art: IvyArtifact): Artifact = {
import art.*
Artifact(
getName,
getType,
getExt,
Option(getExtraAttribute("classifier")),
getConfigurations.toVector map { (c: String) =>
ConfigRef(c)
},
Option(getUrl).map(_.toURI)
)
}
def updateReport(report: ResolveReport, cachedDescriptor: File): UpdateReport =
UpdateReport(
cachedDescriptor,
reports(report) map configurationReport,
updateStats(report),
Map.empty
).recomputeStamps()
def updateStats(report: ResolveReport): UpdateStats =
UpdateStats(
report.getResolveTime,
report.getDownloadTime,
report.getDownloadSize,
false,
Some(System.currentTimeMillis().toString)
)
def configurationReport(confReport: ConfigurationResolveReport): ConfigurationReport =
ConfigurationReport(
ConfigRef(confReport.getConfiguration),
moduleReports(confReport),
organizationArtifactReports(confReport)
)
/**
* Tries to find Ivy graph path the from node to target.
*/
def findPath(target: IvyNode, from: ModuleRevisionId): List[IvyNode] = {
def doFindPath(current: IvyNode, path: List[IvyNode]): List[IvyNode] = {
// Ivy actually returns mix of direct and non-direct callers here.
// that's why we have to calculate all possible paths below and pick the longest path.
val callers = current.getAllRealCallers.toList
val callersRevId = (callers map { _.getModuleRevisionId }).distinct
val paths: List[List[IvyNode]] = ((callersRevId map { revId =>
val node = current.findNode(revId)
if (revId == from) node :: path
else if (node == node.getRoot) Nil
else if (path.contains[IvyNode](node)) path
else doFindPath(node, node :: path)
}) sortBy { _.size }).reverse
paths.headOption getOrElse Nil
}
if (target.getId == from) List(target)
else doFindPath(target, List(target))
}
}
@@ -1,232 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt.internal.librarymanagement
import java.util.Collections.emptyMap
import org.apache.ivy.core.module.descriptor.*
import org.apache.ivy.core.module.id.{ ArtifactId, ModuleId, ModuleRevisionId }
import org.apache.ivy.plugins.matcher.ExactPatternMatcher
import org.apache.ivy.plugins.namespace.NamespaceTransformer
import sbt.util.Logger
import sbt.librarymanagement.ScalaArtifacts.*
import sbt.librarymanagement.{ Configuration, CrossVersion, ScalaModuleInfo }
object IvyScalaUtil {
/** Performs checks/adds filters on Scala dependencies (if enabled in ScalaModuleInfo). */
def checkModule(
module: DefaultModuleDescriptor,
scalaVersionConfigs: Vector[String],
log: Logger
)(check: ScalaModuleInfo): Unit = {
if (check.checkExplicit)
checkDependencies(
module,
check.scalaOrganization,
check.scalaArtifacts,
check.scalaBinaryVersion,
scalaVersionConfigs,
log
)
if (check.filterImplicit)
excludeScalaJars(module, check.configurations)
if (check.overrideScalaVersion)
overrideScalaVersion(
module,
check.scalaOrganization,
check.scalaFullVersion,
scalaVersionConfigs
)
}
class OverrideScalaMediator(
scalaOrganization: String,
scalaVersion: String,
scalaVersionConfigs0: Vector[String]
) extends DependencyDescriptorMediator {
private val scalaVersionConfigs = scalaVersionConfigs0.toSet
private val binaryVersion = CrossVersion.binaryScalaVersion(scalaVersion)
def mediate(dd: DependencyDescriptor): DependencyDescriptor = {
// Mediate only for the dependencies in scalaVersion configurations. https://github.com/sbt/sbt/issues/2786
def configQualifies: Boolean =
dd.getModuleConfigurations exists { scalaVersionConfigs }
// Do not rewrite the dependencies of Scala dependencies themselves, this prevents bootstrapping
// a Scala compiler using another Scala compiler.
def dependeeQualifies: Boolean =
dd.getParentRevisionId == null ||
!isScala2Artifact(dd.getParentRevisionId.getName) ||
!isScala3Artifact(dd.getParentRevisionId.getName)
def matchBinaryVersion(version: String): Boolean =
CrossVersion.binaryScalaVersion(version) == binaryVersion
val transformer =
new NamespaceTransformer {
def transform(mrid: ModuleRevisionId): ModuleRevisionId = {
if (mrid == null) mrid
else if (
(isScala2Artifact(mrid.getName) || isScala3Artifact(mrid.getName)) &&
configQualifies &&
dependeeQualifies
) {
// do not override the binary incompatible Scala version because:
// - the artifacts compiled with Scala 3 depends on the Scala 2.13 scala-library
// - the Scala 2 TASTy reader can consume the Scala 3 artifacts
val newScalaVersion =
if (matchBinaryVersion(mrid.getRevision)) scalaVersion
else mrid.getRevision
ModuleRevisionId.newInstance(
scalaOrganization,
mrid.getName,
mrid.getBranch,
newScalaVersion,
mrid.getQualifiedExtraAttributes
)
} else mrid
}
def isIdentity: Boolean = false
}
DefaultDependencyDescriptor.transformInstance(dd, transformer, false)
}
}
def overrideScalaVersion(
module: DefaultModuleDescriptor,
organization: String,
version: String,
scalaVersionConfigs: Vector[String]
): Unit = {
val mediator = new OverrideScalaMediator(organization, version, scalaVersionConfigs)
module.addDependencyDescriptorMediator(
new ModuleId(Organization, "*"),
ExactPatternMatcher.INSTANCE,
mediator
)
if (organization != Organization)
module.addDependencyDescriptorMediator(
new ModuleId(organization, "*"),
ExactPatternMatcher.INSTANCE,
mediator
)
}
def overrideVersion(
module: DefaultModuleDescriptor,
org: String,
name: String,
version: String
): Unit = {
val id = new ModuleId(org, name)
val over = new OverrideDependencyDescriptorMediator(null, version)
module.addDependencyDescriptorMediator(id, ExactPatternMatcher.INSTANCE, over)
}
/**
* Checks the immediate dependencies of module for dependencies on scala jars and verifies that the version on the
* dependencies matches scalaVersion.
*/
private def checkDependencies(
module: ModuleDescriptor,
scalaOrganization: String,
scalaArtifacts: Vector[String],
scalaBinaryVersion: String,
scalaVersionConfigs0: Vector[String],
log: Logger
): Unit = {
val scalaVersionConfigs: String => Boolean =
if (scalaVersionConfigs0.isEmpty) (_: String) => false else scalaVersionConfigs0.toSet
def binaryScalaWarning(dep: DependencyDescriptor): Option[String] = {
val id = dep.getDependencyRevisionId
val depBinaryVersion = CrossVersion.binaryScalaVersion(id.getRevision)
def isScalaLangOrg = id.getOrganisation == scalaOrganization
def isScalaArtifact = scalaArtifacts.contains[String](id.getName)
def hasBinVerMismatch =
depBinaryVersion != scalaBinaryVersion &&
// scala 2.13 is compatible with scala 3.x
!Seq(depBinaryVersion, scalaBinaryVersion)
.forall(bv => bv.startsWith("3") || bv.startsWith("2.13"))
def matchesOneOfTheConfigs = dep.getModuleConfigurations exists { scalaVersionConfigs }
val mismatched =
isScalaLangOrg && isScalaArtifact && hasBinVerMismatch && matchesOneOfTheConfigs
if (mismatched)
Some(
"Binary version (" + depBinaryVersion + ") for dependency " + id +
"\n\tin " + module.getModuleRevisionId +
" differs from Scala binary version in project (" + scalaBinaryVersion + ")."
)
else
None
}
module.getDependencies.toList.flatMap(binaryScalaWarning).toSet foreach { (s: String) =>
log.warn(s)
}
}
private def configurationSet(configurations: Iterable[Configuration]) =
configurations.map(_.toString).toSet
/**
* Adds exclusions for the scala library and compiler jars so that they are not downloaded. This is
* done because these jars are provided by the ScalaInstance of the project. The version of Scala to use
* is done by setting scalaVersion in the project definition.
*/
private def excludeScalaJars(
module: DefaultModuleDescriptor,
configurations: Iterable[Configuration]
): Unit = {
val configurationNames = {
val names = module.getConfigurationsNames
if (configurations.isEmpty) names
else {
val configSet = configurationSet(configurations)
configSet.toArray
}
}
def excludeScalaJar(name: String): Unit =
module.addExcludeRule(excludeRule(Organization, name, configurationNames, "jar"))
excludeScalaJar(LibraryID)
excludeScalaJar(CompilerID)
}
/**
* Creates an ExcludeRule that excludes artifacts with the given module organization and name for
* the given configurations.
*/
private[sbt] def excludeRule(
organization: String,
name: String,
configurationNames: Iterable[String],
excludeTypePattern: String
): ExcludeRule = {
val artifact =
new ArtifactId(ModuleId.newInstance(organization, name), "*", excludeTypePattern, "*")
val rule =
new DefaultExcludeRule(artifact, ExactPatternMatcher.INSTANCE, emptyMap[AnyRef, AnyRef])
configurationNames.foreach(rule.addConfiguration)
rule
}
/**
* Creates an IncludeRule that includes artifacts with the given module organization and name for
* the given configurations.
*/
private[sbt] def includeRule(
organization: String,
name: String,
configurationNames: Iterable[String],
includeTypePattern: String
): IncludeRule = {
val artifact =
new ArtifactId(ModuleId.newInstance(organization, name), "*", includeTypePattern, "*")
val rule =
new DefaultIncludeRule(artifact, ExactPatternMatcher.INSTANCE, emptyMap[AnyRef, AnyRef])
configurationNames.foreach(rule.addConfiguration)
rule
}
}
@@ -1,58 +0,0 @@
package sbt.internal.librarymanagement
import java.io.IOException
import java.net.{ SocketException, SocketTimeoutException }
import scala.annotation.tailrec
import scala.util.{ Failure, Success, Try }
private[sbt] object IvyUtil {
def separate[A, B](l: Seq[Either[A, B]]): (Seq[A], Seq[B]) =
(l.flatMap(_.left.toOption), l.flatMap(_.toOption))
@tailrec
final def retryWithBackoff[T](
f: => T,
predicate: Throwable => Boolean,
maxAttempts: Int,
retry: Int = 0
): T = {
// Using Try helps in catching NonFatal exceptions only
Try {
f
} match {
case Success(value) => value
case Failure(e) if predicate(e) && retry < (maxAttempts - 1) =>
// max 8s backoff
val backoff = math.min(math.pow(2d, retry.toDouble).toLong * 1000L, 8000L)
Thread.sleep(backoff)
retryWithBackoff(f, predicate, maxAttempts, retry + 1)
case Failure(e) => throw e
}
}
/**
* Currently transient network errors are defined as:
* - a network timeout
* - all server errors (response code 5xx)
* - rate limiting (response code 429)
*/
object TransientNetworkException {
private val _r = """.*HTTP response code: (5\d{2}|408|429).*""".r
@inline private def check(s: String): Boolean = {
if (s == null) {
false
} else {
_r.pattern.matcher(s).matches()
}
}
def apply(t: Throwable): Boolean = t match {
case _: SocketException | _: SocketTimeoutException => true
case e: IOException if check(e.getMessage) => true
case _ => false
}
}
}
@@ -1,485 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah
*/
// based on Ivy's PomModuleDescriptorWriter, which is Apache Licensed, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package sbt.internal.librarymanagement
import java.io.File
import sbt.util.Logger
import sbt.librarymanagement.*
import Resolver.*
import mavenint.PomExtraDependencyAttributes
import scala.collection.immutable.ArraySeq
// Node needs to be renamed to XNode because the task subproject contains a Node type that will shadow
// scala.xml.Node when generating aggregated API documentation
import scala.xml.{ Elem, Node as XNode, NodeSeq, PrettyPrinter, PrefixedAttribute }
import Configurations.Optional
import org.apache.ivy.Ivy
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.core.module.descriptor.{
DependencyArtifactDescriptor,
DependencyDescriptor,
License,
ModuleDescriptor,
ExcludeRule
}
import org.apache.ivy.plugins.resolver.{ ChainResolver, DependencyResolver, IBiblioResolver }
import ivyint.CustomRemoteMavenResolver
import sbt.io.IO
object MakePom {
/** True if the revision is an ivy-range, not a complete revision. */
def isDependencyVersionRange(revision: String): Boolean = VersionRange.isVersionRange(revision)
/** Converts Ivy revision ranges to that of Maven POM */
def makeDependencyVersion(revision: String): String =
VersionRange.fromIvyToMavenVersion(revision)
}
class MakePom(val log: Logger) {
import MakePom.*
def write(
ivy: Ivy,
module: ModuleDescriptor,
moduleInfo: ModuleInfo,
configurations: Option[Iterable[Configuration]],
includeTypes: Set[String],
extra: NodeSeq,
process: XNode => XNode,
filterRepositories: MavenRepository => Boolean,
allRepositories: Boolean,
output: File
): Unit =
write(
process(
toPom(
ivy,
module,
moduleInfo,
configurations,
includeTypes,
extra,
filterRepositories,
allRepositories
)
),
output
)
// use \n as newline because toString uses PrettyPrinter, which hard codes line endings to be \n
def write(node: XNode, output: File): Unit = write(toString(node), output, "\n")
def write(xmlString: String, output: File, newline: String): Unit =
IO.write(output, "<?xml version='1.0' encoding='" + IO.utf8.name + "'?>" + newline + xmlString)
def toString(node: XNode): String = new PrettyPrinter(1000, 4).format(node)
def toPom(
ivy: Ivy,
module: ModuleDescriptor,
moduleInfo: ModuleInfo,
configurations: Option[Iterable[Configuration]],
includeTypes: Set[String],
extra: NodeSeq,
filterRepositories: MavenRepository => Boolean,
allRepositories: Boolean
): XNode =
(<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
{makeModuleID(module)}
<name>{moduleInfo.nameFormal}</name>
{makeStartYear(moduleInfo)}
{makeOrganization(moduleInfo)}
{makeScmInfo(moduleInfo)}
{makeDeveloperInfo(moduleInfo)}
{extra}
{
val deps = depsInConfs(module, configurations)
val (bomDeps, regularDeps) =
deps.partition(d =>
d.getAllDependencyArtifacts.nonEmpty &&
d.getAllDependencyArtifacts.forall(_.getType == Artifact.PomType)
)
makeProperties(module, deps) ++
makeDependencyManagement(bomDeps) ++
makeDependencies(
regularDeps,
includeTypes,
ArraySeq.unsafeWrapArray(module.getAllExcludeRules)
)
}
{makeRepositories(ivy.getSettings, allRepositories, filterRepositories)}
</project>)
def makeModuleID(module: ModuleDescriptor): NodeSeq = {
val mrid = moduleDescriptor(module)
val a: NodeSeq =
(<groupId>{mrid.getOrganisation}</groupId>
<artifactId>{mrid.getName}</artifactId>
<packaging>{packaging(module)}</packaging>)
val b: NodeSeq =
((description(module.getDescription) ++
homePage(module.getHomePage) ++
revision(mrid.getRevision) ++
licenses(module.getLicenses)): NodeSeq)
a ++ b
}
def makeStartYear(moduleInfo: ModuleInfo): NodeSeq =
moduleInfo.startYear match {
case Some(y) => <inceptionYear>{y}</inceptionYear>
case _ => NodeSeq.Empty
}
def makeOrganization(moduleInfo: ModuleInfo): NodeSeq = {
<organization>
<name>{moduleInfo.organizationName}</name>
{
moduleInfo.organizationHomepage match {
case Some(h) => <url>{h}</url>
case _ => NodeSeq.Empty
}
}
</organization>
}
def makeScmInfo(moduleInfo: ModuleInfo): NodeSeq = {
moduleInfo.scmInfo match {
case Some(s) =>
<scm>
<url>{s.browseUrl}</url>
<connection>{s.connection}</connection>
{
s.devConnection match {
case Some(d) => <developerConnection>{d}</developerConnection>
case _ => NodeSeq.Empty
}
}
</scm>
case _ => NodeSeq.Empty
}
}
def makeDeveloperInfo(moduleInfo: ModuleInfo): NodeSeq = {
if (moduleInfo.developers.nonEmpty) {
<developers>
{
moduleInfo.developers.map { (developer: Developer) =>
<developer>
<id>{developer.id}</id>
<name>{developer.name}</name>
<url>{developer.url}</url>
{
developer.email match {
case "" | null => NodeSeq.Empty
case e => <email>{e}</email>
}
}
</developer>
}
}
</developers>
} else NodeSeq.Empty
}
def makeProperties(module: ModuleDescriptor, dependencies: Seq[DependencyDescriptor]): NodeSeq = {
val extra = IvySbt.getExtraAttributes(module)
val depExtra = PomExtraDependencyAttributes.writeDependencyExtra(dependencies).mkString("\n")
val allExtra =
if (depExtra.isEmpty) extra
else extra.updated(PomExtraDependencyAttributes.ExtraAttributesKey, depExtra)
if (allExtra.isEmpty) NodeSeq.Empty else makeProperties(allExtra)
}
def makeProperties(extra: Map[String, String]): NodeSeq = {
def _extraAttributes(k: String) =
if (k == PomExtraDependencyAttributes.ExtraAttributesKey) xmlSpacePreserve
else scala.xml.Null
<properties> {
for ((key, value) <- extra)
yield (<x>{value}</x>).copy(label = key, attributes = _extraAttributes(key))
} </properties>
}
/**
* Attribute tag that PrettyPrinter won't ignore, saying "don't mess with my spaces"
* Without this, PrettyPrinter will flatten multiple entries for ExtraDependencyAttributes and make them
* unparseable. (e.g. a plugin that depends on multiple plugins will fail)
*/
def xmlSpacePreserve = new PrefixedAttribute("xml", "space", "preserve", scala.xml.Null)
def description(d: String) =
if ((d eq null) || d.isEmpty) NodeSeq.Empty
else
<description>{
d
}</description>
def licenses(ls: Array[License]) =
if (ls == null || ls.isEmpty) NodeSeq.Empty
else
<licenses>{
ls.map(license)
}</licenses>
def license(l: License) =
<license>
<name>{l.getName}</name>
<url>{l.getUrl}</url>
<distribution>repo</distribution>
</license>
def homePage(homePage: String) =
if (homePage eq null) NodeSeq.Empty
else
<url>{
homePage
}</url>
def revision(version: String) =
if (version ne null) <version>{
version
}</version>
else NodeSeq.Empty
def packaging(module: ModuleDescriptor) =
module.getAllArtifacts match {
case Array() => "pom"
case Array(x) => x.getType
case xs =>
val types = xs.map(_.getType).toList.filterNot(IgnoreTypes)
types match {
case Nil => Artifact.PomType
case xs if xs.contains(Artifact.DefaultType) => Artifact.DefaultType
case x :: (xs @ _) => x
}
}
val IgnoreTypes: Set[String] = Set(Artifact.SourceType, Artifact.DocType, Artifact.PomType)
/** BOM (Bill of Materials) deps: output under &lt;dependencyManagement&gt; with type pom, scope import (sbt#4531). */
def makeDependencyManagement(dependencies: Seq[DependencyDescriptor]): NodeSeq =
if (dependencies.isEmpty)
NodeSeq.Empty
else
<dependencyManagement>
<dependencies>
{dependencies.map(makeBomDependencyElem)}
</dependencies>
</dependencyManagement>
def makeBomDependencyElem(dependency: DependencyDescriptor): Elem = {
val mrid = dependency.getDependencyRevisionId
<dependency>
<groupId>{mrid.getOrganisation}</groupId>
<artifactId>{mrid.getName}</artifactId>
<version>{makeDependencyVersion(mrid.getRevision)}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
}
def makeDependencies(
dependencies: Seq[DependencyDescriptor],
includeTypes: Set[String],
excludes: Seq[ExcludeRule]
): NodeSeq =
if (dependencies.isEmpty)
NodeSeq.Empty
else
<dependencies>
{
dependencies.map(makeDependency(_, includeTypes, excludes))
}
</dependencies>
def makeDependency(
dependency: DependencyDescriptor,
includeTypes: Set[String],
excludes: Seq[ExcludeRule]
): NodeSeq = {
val artifacts = dependency.getAllDependencyArtifacts
val includeArtifacts = artifacts.filter(d => includeTypes(d.getType))
if (artifacts.isEmpty) {
val configs = dependency.getModuleConfigurations
if (configs.filterNot(Set("sources", "docs")).nonEmpty) {
val (scope, optional) = getScopeAndOptional(dependency.getModuleConfigurations)
makeDependencyElem(dependency, scope, optional, None, None, excludes)
} else NodeSeq.Empty
} else if (includeArtifacts.isEmpty)
NodeSeq.Empty
else
NodeSeq.fromSeq(artifacts.flatMap(a => makeDependencyElem(dependency, a, excludes)))
}
def makeDependencyElem(
dependency: DependencyDescriptor,
artifact: DependencyArtifactDescriptor,
excludes: Seq[ExcludeRule]
): Option[Elem] = {
val configs = artifact.getConfigurations.toList match {
case Nil | "*" :: Nil => dependency.getModuleConfigurations
case x => x.toArray
}
if (!configs.forall(Set("sources", "docs"))) {
val (scope, optional) = getScopeAndOptional(configs)
val classifier = artifactClassifier(artifact)
val baseType = artifactType(artifact)
val tpe = (classifier, baseType) match {
case (Some(c), Some(tpe)) if Artifact.classifierType(c) == tpe => None
case _ => baseType
}
Some(makeDependencyElem(dependency, scope, optional, classifier, tpe, excludes))
} else None
}
def makeDependencyElem(
dependency: DependencyDescriptor,
scope: Option[String],
optional: Boolean,
classifier: Option[String],
tpe: Option[String],
excludes: Seq[ExcludeRule]
): Elem = {
val mrid = dependency.getDependencyRevisionId
val rev = mrid.getRevision
val versionNode: NodeSeq =
if (rev == null || rev == "*" || rev.isEmpty) NodeSeq.Empty
else <version>{makeDependencyVersion(rev)}</version>
val result: Elem =
<dependency>
<groupId>{mrid.getOrganisation}</groupId>
<artifactId>{mrid.getName}</artifactId>
{versionNode}
{scopeElem(scope)}
{optionalElem(optional)}
{classifierElem(classifier)}
{typeElem(tpe)}
{exclusions(dependency, excludes)}
</dependency>
result
}
def artifactType(artifact: DependencyArtifactDescriptor): Option[String] =
Option(artifact.getType).flatMap { tpe =>
if (tpe == "jar") None else Some(tpe)
}
def typeElem(tpe: Option[String]): NodeSeq =
tpe match {
case Some(t) => <type>{t}</type>
case None => NodeSeq.Empty
}
def artifactClassifier(artifact: DependencyArtifactDescriptor): Option[String] =
Option(artifact.getExtraAttribute("classifier"))
def classifierElem(classifier: Option[String]): NodeSeq =
classifier match {
case Some(c) => <classifier>{c}</classifier>
case None => NodeSeq.Empty
}
def scopeElem(scope: Option[String]): NodeSeq = scope match {
case None | Some(Configurations.Compile.name) => NodeSeq.Empty
case Some(s) => <scope>{s}</scope>
}
def optionalElem(opt: Boolean) = if (opt) <optional>true</optional> else NodeSeq.Empty
def moduleDescriptor(module: ModuleDescriptor) = module.getModuleRevisionId
def getScopeAndOptional(confs: Array[String]): (Option[String], Boolean) = {
val (opt, notOptional) = confs.partition(_ == Optional.name)
val defaultNotOptional =
Configurations.defaultMavenConfigurations.find({ (c: Configuration) =>
notOptional contains c.name
})
val scope = defaultNotOptional.map(_.name)
(scope, opt.nonEmpty)
}
def exclusions(dependency: DependencyDescriptor, excludes: Seq[ExcludeRule]): NodeSeq = {
val excl = ArraySeq.unsafeWrapArray(
dependency.getExcludeRules(dependency.getModuleConfigurations)
) ++ excludes
val (warns, excls) = IvyUtil.separate(excl.map(makeExclusion))
if (warns.nonEmpty) log.warn(warns.mkString(IO.Newline))
if (excls.nonEmpty) <exclusions>{
excls
}</exclusions>
else NodeSeq.Empty
}
def makeExclusion(exclRule: ExcludeRule): Either[String, NodeSeq] = {
val m = exclRule.getId.getModuleId
val (g, a) = (m.getOrganisation, m.getName)
if (g == null || g.isEmpty || a == null || a.isEmpty)
Left(
s"Skipped generating '<exclusion/>' for ${m}. Dependency exclusion should have both 'org' and 'module' to comply with Maven POM's schema."
)
else
Right(
<exclusion>
<groupId>{g}</groupId>
<artifactId>{a}</artifactId>
</exclusion>
)
}
def makeRepositories(
settings: IvySettings,
includeAll: Boolean,
filterRepositories: MavenRepository => Boolean
) = {
val repositories =
if (includeAll) allResolvers(settings) else resolvers(settings.getDefaultResolver)
val mavenRepositories =
repositories.flatMap {
case m: CustomRemoteMavenResolver if m.repo.root != DefaultMavenRepository.root =>
MavenRepository(m.repo.name, m.repo.root) :: Nil
case m: IBiblioResolver if m.isM2compatible && m.getRoot != DefaultMavenRepository.root =>
MavenRepository(m.getName, m.getRoot) :: Nil
case _ => Nil
}
val repositoryElements = mavenRepositories.withFilter(filterRepositories).map(mavenRepository)
if (repositoryElements.isEmpty) repositoryElements
else
<repositories>{
repositoryElements
}</repositories>
}
def allResolvers(settings: IvySettings): Seq[DependencyResolver] =
flatten(castResolvers(settings.getResolvers)).distinct
def flatten(rs: Seq[DependencyResolver]): Seq[DependencyResolver] =
if (rs eq null) Nil else rs.flatMap(resolvers)
def resolvers(r: DependencyResolver): Seq[DependencyResolver] =
r match { case c: ChainResolver => flatten(castResolvers(c.getResolvers)); case _ => r :: Nil }
// cast the contents of a pre-generics collection
private def castResolvers(s: java.util.Collection[?]): Seq[DependencyResolver] = {
import scala.jdk.CollectionConverters.*
s.asScala.toSeq.map(_.asInstanceOf[DependencyResolver])
}
def toID(name: String) = checkID(name.filter(isValidIDCharacter).mkString, name)
def isValidIDCharacter(c: Char) = !"""\/:"<>|?*""".contains(c)
private def checkID(id: String, name: String) =
if (id.isEmpty) sys.error("Could not convert '" + name + "' to an ID") else id
def mavenRepository(repo: MavenRepository): XNode =
mavenRepository(toID(repo.name), repo.name, repo.root)
def mavenRepository(id: String, name: String, root: String): XNode =
<repository>
<id>{id}</id>
<name>{name}</name>
<url>{root}</url>
<layout>{"default"}</layout>
</repository>
/**
* Retain dependencies only with the configurations given, or all public configurations of `module` if `configurations` is None.
* This currently only preserves the information required by makePom
*/
private def depsInConfs(
module: ModuleDescriptor,
configurations: Option[Iterable[Configuration]]
): Seq[DependencyDescriptor] = {
val keepConfigurations = IvySbt.getConfigurations(module, configurations)
val keepSet: Set[String] = keepConfigurations.toSet
def translate(dependency: DependencyDescriptor) = {
val keep = dependency.getModuleConfigurations
.filter((conf: String) => keepSet.contains(conf))
if (keep.isEmpty)
None
else // TODO: translate the dependency to contain only configurations to keep
Some(dependency)
}
ArraySeq.unsafeWrapArray(module.getDependencies) flatMap translate
}
}
@@ -1,108 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2011 Mark Harrah
*/
package sbt.internal.librarymanagement
import java.io.File
import java.util.Date
import org.apache.ivy.core.{ cache, module, report, resolve, search }
import cache.ArtifactOrigin
import search.{ ModuleEntry, OrganisationEntry, RevisionEntry }
import module.id.ModuleRevisionId
import module.descriptor.{
Artifact as IArtifact,
DefaultArtifact,
DependencyDescriptor,
ModuleDescriptor
}
import org.apache.ivy.plugins.namespace.Namespace
import org.apache.ivy.plugins.resolver.ResolverSettings
import report.{
ArtifactDownloadReport,
DownloadReport,
DownloadStatus,
MetadataArtifactDownloadReport
}
import resolve.{ DownloadOptions, ResolveData, ResolvedModuleRevision }
/**
* A Resolver that uses a predefined mapping from module ids to in-memory descriptors.
* It does not handle artifacts.
*/
class ProjectResolver(name: String, map: Map[ModuleRevisionId, ModuleDescriptor])
extends ResolverAdapter {
def getName = name
def setName(name: String) = sys.error("Setting name not supported by ProjectResolver")
override def toString = "ProjectResolver(" + name + ", mapped: " + map.keys.mkString(", ") + ")"
def getDependency(dd: DependencyDescriptor, data: ResolveData): ResolvedModuleRevision =
getDependency(dd.getDependencyRevisionId).orNull
private def getDependency(revisionId: ModuleRevisionId): Option[ResolvedModuleRevision] = {
def constructResult(descriptor: ModuleDescriptor) =
new ResolvedModuleRevision(this, this, descriptor, report(revisionId), true)
map get revisionId map constructResult
}
private[sbt] def getModuleDescriptor(revisionId: ModuleRevisionId): Option[ModuleDescriptor] =
map.get(revisionId)
def report(revisionId: ModuleRevisionId): MetadataArtifactDownloadReport = {
val artifact = DefaultArtifact.newIvyArtifact(revisionId, new Date)
val r = new MetadataArtifactDownloadReport(artifact)
r.setSearched(false)
r.setDownloadStatus(DownloadStatus.FAILED)
r
}
// this resolver never locates artifacts, only resolves dependencies
def exists(artifact: IArtifact) = false
def locate(artifact: IArtifact) = null
def download(artifacts: Array[IArtifact], options: DownloadOptions): DownloadReport = {
val r = new DownloadReport
for (artifact <- artifacts)
if (getDependency(artifact.getModuleRevisionId).isEmpty)
r.addArtifactReport(notDownloaded(artifact))
r
}
def download(artifact: ArtifactOrigin, options: DownloadOptions): ArtifactDownloadReport =
notDownloaded(artifact.getArtifact)
def findIvyFileRef(dd: DependencyDescriptor, data: ResolveData) = null
def notDownloaded(artifact: IArtifact): ArtifactDownloadReport = {
val r = new ArtifactDownloadReport(artifact)
r.setDownloadStatus(DownloadStatus.FAILED)
r
}
// doesn't support publishing
def publish(artifact: IArtifact, src: File, overwrite: Boolean) =
sys.error("Publish not supported by ProjectResolver")
def beginPublishTransaction(module: ModuleRevisionId, overwrite: Boolean): Unit = ()
def abortPublishTransaction(): Unit = ()
def commitPublishTransaction(): Unit = ()
def reportFailure(): Unit = ()
def reportFailure(art: IArtifact): Unit = ()
def listOrganisations() = new Array[OrganisationEntry](0)
def listModules(org: OrganisationEntry) = new Array[ModuleEntry](0)
def listRevisions(module: ModuleEntry) = new Array[RevisionEntry](0)
def getNamespace = Namespace.SYSTEM_NAMESPACE
private var settings: Option[ResolverSettings] = None
def dumpSettings(): Unit = ()
def setSettings(settings: ResolverSettings): Unit = { this.settings = Some(settings) }
def getRepositoryCacheManager = settings match {
case Some(s) => s.getDefaultRepositoryCacheManager;
case None => sys.error("No settings defined for ProjectResolver")
}
}
object ProjectResolver {
private[sbt] val InterProject = "inter-project"
}
@@ -1,107 +0,0 @@
package sbt.internal.librarymanagement
import java.io.File
import org.apache.ivy.core
import org.apache.ivy.plugins.parser
import core.IvyPatternHelper
import core.settings.IvySettings
import core.cache.ResolutionCacheManager
import core.module.id.ModuleRevisionId
import core.module.descriptor.ModuleDescriptor
import ResolutionCache.{ Name, ReportDirectory, ResolvedName, ResolvedPattern }
import parser.xml.XmlModuleDescriptorParser
import sbt.io.IO
import sbt.librarymanagement.*
/**
* Replaces the standard Ivy resolution cache in order to:
* 1. Separate cached resolved Ivy files from resolution reports, making the resolution reports easier to find.
* 2. Have them per-project for easier cleaning (possible with standard cache, but central to this custom one).
* 3. Cache location includes extra attributes so that cross builds of a plugin do not overwrite each other.
*/
private[sbt] final class ResolutionCache(base: File, settings: IvySettings)
extends ResolutionCacheManager {
private def resolvedFileInCache(m: ModuleRevisionId, name: String, ext: String): File = {
val p = ResolvedPattern
val f = IvyPatternHelper.substitute(
p,
m.getOrganisation,
m.getName,
m.getBranch,
m.getRevision,
name,
name,
ext,
null,
null,
m.getAttributes,
null
)
new File(base, f)
}
private val reportBase: File = new File(base, ReportDirectory)
def getResolutionCacheRoot: File = base
def clean(): Unit = IO.delete(base)
override def toString = Name
def getResolvedIvyFileInCache(mrid: ModuleRevisionId): File =
resolvedFileInCache(mrid, ResolvedName, "xml")
def getResolvedIvyPropertiesInCache(mrid: ModuleRevisionId): File =
resolvedFileInCache(mrid, ResolvedName, "properties")
// name needs to be the same as Ivy's default because the ivy-report.xsl stylesheet assumes this
// when making links to reports for other configurations
def getConfigurationResolveReportInCache(resolveId: String, conf: String): File =
new File(reportBase, resolveId + "-" + conf + ".xml")
def getConfigurationResolveReportsInCache(resolveId: String): Array[File] =
IO.listFiles(reportBase).filter(_.getName.startsWith(resolveId + "-"))
// XXX: this method is required by ResolutionCacheManager in Ivy 2.3.0 final,
// but it is apparently unused by Ivy as sbt uses Ivy. Therefore, it is
// unexercised in tests. Note that the implementation of this method in Ivy 2.3.0's
// DefaultResolutionCache also resolves parent properties for a given mrid
def getResolvedModuleDescriptor(mrid: ModuleRevisionId): ModuleDescriptor = {
val ivyFile = getResolvedIvyFileInCache(mrid)
if (!ivyFile.exists()) {
throw new IllegalStateException("Ivy file not found in cache for " + mrid + "!")
}
XmlModuleDescriptorParser.getInstance().parseDescriptor(settings, ivyFile.toURI.toURL, false)
}
def saveResolvedModuleDescriptor(md: ModuleDescriptor): Unit = {
val mrid = md.getResolvedModuleRevisionId
val cachedIvyFile = getResolvedIvyFileInCache(mrid)
md.toIvyFile(cachedIvyFile)
}
}
private[sbt] object ResolutionCache {
/**
* Removes cached files from the resolution cache for the module with ID `mrid`
* and the resolveId (as set on `ResolveOptions`).
*/
private[sbt] def cleanModule(
mrid: ModuleRevisionId,
resolveId: String,
manager: ResolutionCacheManager
): Unit = {
val files =
Option(manager.getResolvedIvyFileInCache(mrid)).toList :::
Option(manager.getResolvedIvyPropertiesInCache(mrid)).toList :::
Option(manager.getConfigurationResolveReportsInCache(resolveId)).toList.flatten
IO.delete(files)
}
private val ReportDirectory = "reports"
// base name (name except for extension) of resolution report file
private val ResolvedName = "resolved.xml"
// Cache name
private val Name = "sbt-resolution-cache"
// use sbt-specific extra attributes so that resolved xml files do not get overwritten when using different Scala/sbt versions
private val ResolvedPattern =
"[organisation]/[module]/" + Resolver.PluginPattern + "([branch]/)[revision]/[artifact].[ext]"
}
@@ -1,33 +0,0 @@
package sbt.internal.librarymanagement
package ivy
import org.apache.ivy.plugins.circular.{
CircularDependencyStrategy,
WarnCircularDependencyStrategy,
IgnoreCircularDependencyStrategy,
ErrorCircularDependencyStrategy
}
/**
* Wrapper around circular dependency strategy.
*/
sealed trait CircularDependencyLevel {
private[sbt] def ivyStrategy: CircularDependencyStrategy
private[sbt] def name: String
override def toString: String = name
}
object CircularDependencyLevel {
val Warn: CircularDependencyLevel = new CircularDependencyLevel {
def ivyStrategy: CircularDependencyStrategy = WarnCircularDependencyStrategy.getInstance
def name: String = "warn"
}
val Ignore: CircularDependencyLevel = new CircularDependencyLevel {
def ivyStrategy: CircularDependencyStrategy = IgnoreCircularDependencyStrategy.getInstance
def name: String = "ignore"
}
val Error: CircularDependencyLevel = new CircularDependencyLevel {
def ivyStrategy: CircularDependencyStrategy = ErrorCircularDependencyStrategy.getInstance
def name: String = "error"
}
}
@@ -1,74 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt.internal.librarymanagement
package ivy
import java.io.File
import org.apache.ivy.util.url.CredentialsStore
import sbt.internal.librarymanagement.IvyUtil
import sbt.io.IO
import sbt.librarymanagement.Credentials
import sbt.util.Logger
object IvyCredentials {
/** Add the provided credentials to Ivy's credentials cache. */
def add(realm: String, host: String, userName: String, passwd: String): Unit =
CredentialsStore.INSTANCE.addCredentials(realm, host, userName, passwd)
/** Load credentials from the given file into Ivy's credentials cache. */
def add(path: File, log: Logger): Unit =
loadCredentials(path) match {
case Left(err) => log.warn(err)
case Right(dc) => add(dc.realm, dc.host, dc.userName, dc.passwd)
}
def forHost(sc: Seq[Credentials], host: String) = allDirect(sc) find { _.host == host }
def allDirect(sc: Seq[Credentials]): Seq[Credentials.DirectCredentials] = sc map toDirect
def toDirect(c: Credentials): Credentials.DirectCredentials = c match {
case dc: Credentials.DirectCredentials => dc
case fc: Credentials.FileCredentials =>
loadCredentials(fc.path) match {
case Left(err) => sys.error(err)
case Right(dc) => dc
}
}
def loadCredentials(path: File): Either[String, Credentials.DirectCredentials] =
if (path.exists) {
val properties = read(path)
def get(keys: List[String]): Either[String, String] =
keys
.flatMap(properties.get)
.headOption
.toRight(keys.head + " not specified in credentials file: " + path)
IvyUtil.separate(List(HostKeys, UserKeys, PasswordKeys).map(get)) match
case (Nil, List(host: String, user: String, pass: String)) =>
IvyUtil.separate(List(RealmKeys).map(get)) match
case (_, List(realm: String)) =>
Right(new Credentials.DirectCredentials(realm, host, user, pass))
case _ => Right(new Credentials.DirectCredentials(null, host, user, pass))
case (errors, _) => Left(errors.mkString("\n"))
} else Left("Credentials file " + path + " does not exist")
def register(cs: Seq[Credentials], log: Logger): Unit =
cs foreach {
case f: Credentials.FileCredentials => add(f.path, log)
case d: Credentials.DirectCredentials => add(d.realm, d.host, d.userName, d.passwd)
}
private val RealmKeys = List("realm")
private val HostKeys = List("host", "hostname")
private val UserKeys = List("user", "user.name", "username")
private val PasswordKeys = List("password", "pwd", "pass", "passwd")
import scala.jdk.CollectionConverters.*
private def read(from: File): Map[String, String] = {
val properties = new java.util.Properties
IO.load(properties, from)
properties.asScala.map { (k, v) => (k, v.trim) }.toMap
}
}
@@ -1,10 +0,0 @@
package sbt
package internal
package librarymanagement.ivy
/**
* This is a list of functions with default values.
*/
object IvyDefaults {
val defaultChecksums: Vector[String] = Vector("sha1", "md5")
}
@@ -1,32 +0,0 @@
package sbt
package internal
package librarymanagement
package ivy
import sbt.librarymanagement.*
import sbt.util.Logger
class IvyDependencyResolution private[sbt] (val ivySbt: IvySbt)
extends DependencyResolutionInterface {
type Module = ivySbt.Module
override def moduleDescriptor(moduleSetting: ModuleDescriptorConfiguration): ModuleDescriptor = {
new Module(moduleSetting)
}
override def update(
module: ModuleDescriptor,
configuration: UpdateConfiguration,
uwconfig: UnresolvedWarningConfiguration,
log: Logger
): Either[UnresolvedWarning, UpdateReport] =
IvyActions.updateEither(toModule(module), configuration, uwconfig, log)
private[sbt] def toModule(module: ModuleDescriptor): Module =
module.asInstanceOf[Module]
}
object IvyDependencyResolution {
def apply(ivyConfiguration: IvyConfiguration): DependencyResolution =
DependencyResolution(new IvyDependencyResolution(new IvySbt(ivyConfiguration)))
}
@@ -1,17 +0,0 @@
package sbt.internal.librarymanagement
package ivy
trait IvyLibraryManagementCodec
extends sjsonnew.BasicJsonProtocol
with sbt.librarymanagement.LibraryManagementCodec
with sbt.internal.librarymanagement.formats.GlobalLockFormat
with sbt.internal.librarymanagement.formats.LoggerFormat
with sbt.internal.librarymanagement.ivy.formats.UpdateOptionsFormat
with sbt.librarymanagement.IvyPathsFormats
with sbt.librarymanagement.ResolverFormats
with sbt.librarymanagement.ModuleConfigurationFormats
with InlineIvyConfigurationFormats
with ExternalIvyConfigurationFormats
with IvyConfigurationFormats
object IvyLibraryManagementCodec extends IvyLibraryManagementCodec
@@ -1,38 +0,0 @@
package sbt
package internal
package librarymanagement
package ivy
import sbt.librarymanagement.*
import sbt.util.Logger
import java.io.File
class IvyPublisher private[sbt] (val ivySbt: IvySbt) extends PublisherInterface {
type Module = ivySbt.Module
override def moduleDescriptor(moduleSetting: ModuleDescriptorConfiguration): ModuleDescriptor = {
new Module(moduleSetting)
}
override def makePomFile(
module: ModuleDescriptor,
configuration: MakePomConfiguration,
log: Logger
): File =
IvyActions.makePomFile(toModule(module), configuration, log)
override def publish(
module: ModuleDescriptor,
configuration: PublishConfiguration,
log: Logger
): Unit =
IvyActions.publish(toModule(module), configuration, log)
private[sbt] def toModule(module: ModuleDescriptor): Module =
module.asInstanceOf[Module]
}
object IvyPublisher {
def apply(ivyConfiguration: IvyConfiguration): Publisher =
Publisher(new IvyPublisher(new IvySbt(ivyConfiguration)))
}
@@ -1,117 +0,0 @@
package sbt.internal.librarymanagement
package ivy
import org.apache.ivy.plugins.resolver.DependencyResolver
import org.apache.ivy.core.settings.IvySettings
import sbt.util.Logger
import sbt.librarymanagement.*
/**
* Represents configurable options for update task.
* While UpdateConfiguration is passed into update at runtime,
* UpdateOption is intended to be used while setting up the Ivy object.
*
* See also UpdateConfiguration in IvyActions.scala.
*/
final class UpdateOptions private[sbt] (
// If set to CircularDependencyLevel.Error, halt the dependency resolution.
val circularDependencyLevel: CircularDependencyLevel,
// If set to true, prioritize inter-project resolver
val interProjectFirst: Boolean,
// If set to true, check all resolvers for snapshots.
val latestSnapshots: Boolean,
// If set to true, use cached resolution.
val cachedResolution: Boolean,
// If set to true, use Gigahorse
val gigahorse: Boolean,
// Extension point for an alternative resolver converter.
val resolverConverter: UpdateOptions.ResolverConverter,
// Map the unique resolver to be checked for the module ID
val moduleResolvers: Map[ModuleID, Resolver]
) {
def withCircularDependencyLevel(
circularDependencyLevel: CircularDependencyLevel
): UpdateOptions =
copy(circularDependencyLevel = circularDependencyLevel)
def withInterProjectFirst(interProjectFirst: Boolean): UpdateOptions =
copy(interProjectFirst = interProjectFirst)
def withLatestSnapshots(latestSnapshots: Boolean): UpdateOptions =
copy(latestSnapshots = latestSnapshots)
def withCachedResolution(cachedResolution: Boolean): UpdateOptions =
copy(cachedResolution = cachedResolution)
def withGigahorse(gigahorse: Boolean): UpdateOptions =
copy(gigahorse = gigahorse)
/** Extention point for an alternative resolver converter. */
def withResolverConverter(resolverConverter: UpdateOptions.ResolverConverter): UpdateOptions =
copy(resolverConverter = resolverConverter)
def withModuleResolvers(moduleResolvers: Map[ModuleID, Resolver]): UpdateOptions =
copy(moduleResolvers = moduleResolvers)
private[sbt] def copy(
circularDependencyLevel: CircularDependencyLevel = this.circularDependencyLevel,
interProjectFirst: Boolean = this.interProjectFirst,
latestSnapshots: Boolean = this.latestSnapshots,
cachedResolution: Boolean = this.cachedResolution,
gigahorse: Boolean = this.gigahorse,
resolverConverter: UpdateOptions.ResolverConverter = this.resolverConverter,
moduleResolvers: Map[ModuleID, Resolver] = this.moduleResolvers
): UpdateOptions =
new UpdateOptions(
circularDependencyLevel,
interProjectFirst,
latestSnapshots,
cachedResolution,
gigahorse,
resolverConverter,
moduleResolvers
)
override def toString(): String =
s"""UpdateOptions(
| circularDependencyLevel = $circularDependencyLevel,
| latestSnapshots = $latestSnapshots,
| cachedResolution = $cachedResolution
|)""".stripMargin
override def equals(o: Any): Boolean = o match {
case o: UpdateOptions =>
this.circularDependencyLevel == o.circularDependencyLevel &&
this.interProjectFirst == o.interProjectFirst &&
this.latestSnapshots == o.latestSnapshots &&
this.cachedResolution == o.cachedResolution &&
this.gigahorse == o.gigahorse &&
this.resolverConverter == o.resolverConverter &&
this.moduleResolvers == o.moduleResolvers
case _ => false
}
override def hashCode: Int = {
var hash = 1
hash = hash * 31 + this.circularDependencyLevel.##
hash = hash * 31 + this.interProjectFirst.##
hash = hash * 31 + this.latestSnapshots.##
hash = hash * 31 + this.cachedResolution.##
hash = hash * 31 + this.gigahorse.##
hash = hash * 31 + this.resolverConverter.##
hash = hash * 31 + this.moduleResolvers.##
hash
}
}
object UpdateOptions {
type ResolverConverter = PartialFunction[(Resolver, IvySettings, Logger), DependencyResolver]
def apply(): UpdateOptions =
new UpdateOptions(
circularDependencyLevel = CircularDependencyLevel.Warn,
interProjectFirst = true,
latestSnapshots = true,
cachedResolution = false,
gigahorse = LMSysProp.useGigahorse,
resolverConverter = PartialFunction.empty,
moduleResolvers = Map.empty
)
}
@@ -1,63 +0,0 @@
package sbt.internal.librarymanagement.ivy
package formats
import sjsonnew.*
import sbt.librarymanagement.*
trait UpdateOptionsFormat {
self: BasicJsonProtocol & ModuleIDFormats & ResolverFormats &
sbt.librarymanagement.ArtifactFormats & sbt.librarymanagement.ConfigRefFormats &
sbt.librarymanagement.ChecksumFormats & sbt.librarymanagement.InclExclRuleFormats &
sbt.librarymanagement.CrossVersionFormats & sbt.librarymanagement.DisabledFormats &
sbt.librarymanagement.BinaryFormats & sbt.librarymanagement.ConstantFormats &
sbt.librarymanagement.PatchFormats & sbt.librarymanagement.FullFormats &
sbt.librarymanagement.For3Use2_13Formats & sbt.librarymanagement.For2_13Use3Formats &
sbt.librarymanagement.ChainedResolverFormats & sbt.librarymanagement.MavenRepoFormats &
sbt.librarymanagement.MavenCacheFormats & sbt.librarymanagement.PatternsFormats &
sbt.librarymanagement.FileConfigurationFormats & sbt.librarymanagement.FileRepositoryFormats &
sbt.librarymanagement.URLRepositoryFormats & sbt.librarymanagement.SshConnectionFormats &
sbt.librarymanagement.SshAuthenticationFormats & sbt.librarymanagement.SshRepositoryFormats &
sbt.librarymanagement.SftpRepositoryFormats &
sbt.librarymanagement.PasswordAuthenticationFormats &
sbt.librarymanagement.KeyFileAuthenticationFormats =>
/* This is necessary to serialize/deserialize `directResolvers`. */
private given moduleIdJsonKeyFormat: sjsonnew.JsonKeyFormat[ModuleID] = {
new sjsonnew.JsonKeyFormat[ModuleID] {
import sjsonnew.support.scalajson.unsafe.*
val moduleIdFormat: JsonFormat[ModuleID] = implicitly[JsonFormat[ModuleID]]
def write(key: ModuleID): String =
CompactPrinter(Converter.toJsonUnsafe(key)(using moduleIdFormat))
def read(key: String): ModuleID =
Converter.fromJsonUnsafe[ModuleID](Parser.parseUnsafe(key))(using moduleIdFormat)
}
}
given UpdateOptionsFormat: JsonFormat[UpdateOptions] =
projectFormat(
(uo: UpdateOptions) =>
(
uo.circularDependencyLevel.name,
uo.interProjectFirst,
uo.latestSnapshots,
uo.cachedResolution,
uo.gigahorse,
uo.moduleResolvers
),
(xs: (String, Boolean, Boolean, Boolean, Boolean, Map[ModuleID, Resolver])) =>
new UpdateOptions(
levels(xs._1),
xs._2,
xs._3,
xs._4,
xs._5,
PartialFunction.empty,
xs._6
)
)
private val levels: Map[String, CircularDependencyLevel] = Map(
"warn" -> CircularDependencyLevel.Warn,
"ignore" -> CircularDependencyLevel.Ignore,
"error" -> CircularDependencyLevel.Error
)
}
@@ -1,11 +0,0 @@
package sbt.internal.librarymanagement
package ivyint
import org.apache.ivy.plugins.resolver.DependencyResolver
import sbt.librarymanagement.*
// These are placeholder traits for sbt-aether-resolver
trait CustomMavenResolver extends DependencyResolver {}
trait CustomRemoteMavenResolver extends CustomMavenResolver {
def repo: MavenRepository
}
@@ -1,130 +0,0 @@
package sbt.internal.librarymanagement
package ivyint
import java.net.{ Authenticator, PasswordAuthentication }
import org.apache.ivy.util.Message
import org.apache.ivy.util.url.IvyAuthenticator
/**
* Helper to install an Authenticator that works with the IvyAuthenticator to provide better error messages when
* credentials don't line up.
*/
object ErrorMessageAuthenticator {
private var securityWarningLogged = false
private def originalAuthenticator: Option[Authenticator] =
Option(Authenticator.getDefault())
private lazy val ivyOriginalField = {
val field = classOf[IvyAuthenticator].getDeclaredField("original")
field.setAccessible(true)
field
}
// Attempts to get the original authenticator form the ivy class or returns null.
private def installIntoIvy(ivy: IvyAuthenticator): Option[Authenticator] = {
// Here we install ourselves as the IvyAuthenticator's default so we get called AFTER Ivy has a chance to run.
def installIntoIvyImpl(original: Option[Authenticator]): Unit = {
val newOriginal = new ErrorMessageAuthenticator(original)
ivyOriginalField.set(ivy, newOriginal)
}
try
Option(ivyOriginalField.get(ivy).asInstanceOf[Authenticator]) match {
case Some(
_: ErrorMessageAuthenticator
) => // We're already installed, no need to do the work again.
case originalOpt => installIntoIvyImpl(originalOpt)
}
catch {
case t: Throwable =>
Message.debug(
"Error occurred while trying to install debug messages into Ivy Authentication" + t.getMessage
)
}
Some(ivy)
}
/** Installs the error message authenticator so we have nicer error messages when using java's URL for downloading. */
def install(): Unit = {
// Actually installs the error message authenticator.
def doInstall(original: Option[Authenticator]): Unit =
try Authenticator.setDefault(new ErrorMessageAuthenticator(original))
catch {
case _: SecurityException if !securityWarningLogged =>
securityWarningLogged = true
Message.warn(
"Not enough permissions to set the ErrorMessageAuthenticator. "
+ "Helpful debug messages disabled!"
);
}
// We will try to use the original authenticator as backup authenticator.
// Since there is no getter available, so try to use some reflection to
// obtain it. If that doesn't work, assume there is no original authenticator
def doInstallIfIvy(original: Option[Authenticator]): Unit =
original match {
case Some(_: ErrorMessageAuthenticator) => // Ignore, we're already installed
case Some(ivy: IvyAuthenticator) =>
installIntoIvy(ivy); ()
case original => doInstall(original)
}
doInstallIfIvy(originalAuthenticator)
}
}
/**
* An authenticator which just delegates to a previous authenticator and issues *nice*
* error messages on failure to find credentials.
*
* Since ivy installs its own credentials handler EVERY TIME it resolves or publishes, we want to
* install this one at some point and eventually ivy will capture it and use it.
*/
private[sbt] final class ErrorMessageAuthenticator(original: Option[Authenticator])
extends Authenticator {
protected override def getPasswordAuthentication(): PasswordAuthentication = {
// We're guaranteed to only get here if Ivy's authentication fails
if (!isProxyAuthentication) {
val host = getRequestingHost
// TODO - levenshtein distance "did you mean" message.
Message.error(s"Unable to find credentials for [${getRequestingPrompt} @ ${host}].")
val configuredRealms = IvyCredentialsLookup.realmsForHost.getOrElse(host, Set.empty)
if (configuredRealms.nonEmpty) {
Message.error(s" Is one of these realms misspelled for host [${host}]:")
configuredRealms foreach { realm =>
Message.error(s" * ${realm}")
}
}
}
// TODO - Maybe we should work on a helpful proxy message...
// TODO - To be more maven friendly, we may want to also try to grab the "first" authentication that shows up for a server and try it.
// or maybe allow that behavior to be configured, since maven users aren't used to realms (which they should be).
// Grabs the authentication that would have been provided had we not been installed...
def originalAuthentication: Option[PasswordAuthentication] = {
Authenticator.setDefault(original.orNull)
try
Option(
Authenticator.requestPasswordAuthentication(
getRequestingHost,
getRequestingSite,
getRequestingPort,
getRequestingProtocol,
getRequestingPrompt,
getRequestingScheme
)
)
finally Authenticator.setDefault(this)
}
originalAuthentication.orNull
}
/**
* Returns true if this authentication if for a proxy and not for an HTTP server.
* We want to display different error messages, depending.
*/
private def isProxyAuthentication: Boolean =
getRequestorType == Authenticator.RequestorType.PROXY
}
@@ -1,71 +0,0 @@
package sbt.internal.librarymanagement
package ivyint
import org.apache.ivy.util.url.CredentialsStore
import scala.jdk.CollectionConverters.*
/** A key used to store credentials in the ivy credentials store. */
private[sbt] sealed trait CredentialKey
/** Represents a key in the ivy credentials store that is only specific to a host. */
private[sbt] case class Host(name: String) extends CredentialKey
/** Represents a key in the ivy credentials store that is keyed to both a host and a "realm". */
private[sbt] case class Realm(host: String, realm: String) extends CredentialKey
/**
* Helper mechanism to improve credential related error messages.
*
* This evil class exposes to us the necessary information to warn on credential failure and offer
* spelling/typo suggestions.
*/
private[sbt] object IvyCredentialsLookup {
/** Helper extractor for Ivy's key-value store of credentials. */
private object KeySplit {
def unapply(key: String): Option[(String, String)] = {
key.indexOf('@') match {
case -1 => None
case n => Some(key.take(n) -> key.drop(n + 1))
}
}
}
/**
* Here we cheat runtime private so we can look in the credentials store.
*
* TODO - Don't bomb at class load time...
*/
private val credKeyringField = {
val tmp = classOf[CredentialsStore].getDeclaredField("KEYRING")
tmp.setAccessible(true)
tmp
}
/** All the keys for credentials in the ivy configuration store. */
def keyringKeys: Set[CredentialKey] = {
val map = credKeyringField.get(null).asInstanceOf[java.util.HashMap[String, Any]]
// make a clone of the set...
(map.keySet.asScala.map {
case KeySplit(realm, host) => (Realm(host, realm): CredentialKey)
case host => (Host(host): CredentialKey)
}).toSet
}
/**
* A mapping of host -> realms in the ivy credentials store.
*/
def realmsForHost: Map[String, Set[String]] =
keyringKeys
.collect { case x: Realm =>
x
}
.groupBy { realm =>
realm.host
}
.view
.mapValues { realms =>
realms map (_.realm)
}
.toMap
}
@@ -1,194 +0,0 @@
package sbt.internal.librarymanagement
package ivyint
import scala.collection.immutable.ArraySeq
import org.apache.ivy.core
import core.module.descriptor.{ DependencyArtifactDescriptor, DefaultDependencyArtifactDescriptor }
import core.module.descriptor.DependencyDescriptor
import core.module.id.{ ArtifactId, ModuleRevisionId }
private[sbt] object MergeDescriptors {
def mergeable(a: DependencyDescriptor, b: DependencyDescriptor): Boolean =
a.isForce == b.isForce &&
a.isChanging == b.isChanging &&
a.isTransitive == b.isTransitive &&
a.getParentRevisionId == b.getParentRevisionId &&
a.getNamespace == b.getNamespace && {
val amrid = a.getDependencyRevisionId
val bmrid = b.getDependencyRevisionId
amrid == bmrid
} && {
val adyn = a.getDynamicConstraintDependencyRevisionId
val bdyn = b.getDynamicConstraintDependencyRevisionId
adyn == bdyn
}
def apply(a: DependencyDescriptor, b: DependencyDescriptor): DependencyDescriptor = {
assert(mergeable(a, b))
new MergedDescriptors(a, b)
}
}
// combines the artifacts, configurations, includes, and excludes for DependencyDescriptors `a` and `b`
// that otherwise have equal IDs
private[sbt] final case class MergedDescriptors(a: DependencyDescriptor, b: DependencyDescriptor)
extends DependencyDescriptor {
def getDependencyId = a.getDependencyId
def isForce = a.isForce
def isChanging = a.isChanging
def isTransitive = a.isTransitive
def getNamespace = a.getNamespace
def getParentRevisionId = a.getParentRevisionId
def getDependencyRevisionId = a.getDependencyRevisionId
def getDynamicConstraintDependencyRevisionId = a.getDynamicConstraintDependencyRevisionId
def getModuleConfigurations = concat(a.getModuleConfigurations, b.getModuleConfigurations)
def getDependencyConfigurations(moduleConfiguration: String, requestedConfiguration: String) =
concat(
a.getDependencyConfigurations(moduleConfiguration, requestedConfiguration),
b.getDependencyConfigurations(moduleConfiguration)
)
def getDependencyConfigurations(moduleConfiguration: String) =
concat(
a.getDependencyConfigurations(moduleConfiguration),
b.getDependencyConfigurations(moduleConfiguration)
)
def getDependencyConfigurations(moduleConfigurations: Array[String]) =
concat(
a.getDependencyConfigurations(moduleConfigurations),
b.getDependencyConfigurations(moduleConfigurations)
)
def getAllDependencyArtifacts =
concatArtifacts(a, a.getAllDependencyArtifacts, b, b.getAllDependencyArtifacts)
def getDependencyArtifacts(moduleConfigurations: String) =
concatArtifacts(
a,
a.getDependencyArtifacts(moduleConfigurations),
b,
b.getDependencyArtifacts(moduleConfigurations)
)
def getDependencyArtifacts(moduleConfigurations: Array[String]) =
concatArtifacts(
a,
a.getDependencyArtifacts(moduleConfigurations),
b,
b.getDependencyArtifacts(moduleConfigurations)
)
def getAllIncludeRules = concat(a.getAllIncludeRules, b.getAllIncludeRules)
def getIncludeRules(moduleConfigurations: String) =
concat(a.getIncludeRules(moduleConfigurations), b.getIncludeRules(moduleConfigurations))
def getIncludeRules(moduleConfigurations: Array[String]) =
concat(a.getIncludeRules(moduleConfigurations), b.getIncludeRules(moduleConfigurations))
private def concatArtifacts(
a: DependencyDescriptor,
as: Array[DependencyArtifactDescriptor],
b: DependencyDescriptor,
bs: Array[DependencyArtifactDescriptor]
) = {
if (as.isEmpty)
if (bs.isEmpty) as
else defaultArtifact(a) ++ explicitConfigurations(b, bs)
else if (bs.isEmpty) explicitConfigurations(a, as) ++ defaultArtifact(b)
else concat(explicitConfigurations(a, as), explicitConfigurations(b, bs))
}
private def explicitConfigurations(
base: DependencyDescriptor,
arts: Array[DependencyArtifactDescriptor]
): Array[DependencyArtifactDescriptor] =
arts map { art =>
explicitConfigurations(base, art)
}
private def explicitConfigurations(
base: DependencyDescriptor,
art: DependencyArtifactDescriptor
): DependencyArtifactDescriptor = {
val aConfs = Option(art.getConfigurations) map { _.toList }
// In case configuration list is "*", we should still specify the module configuration of the DependencyDescriptor
// otherwise the explicit specified artifacts from one dd can leak over to the other.
// See gh-1500, gh-2002
aConfs match {
case None | Some(Nil) | Some(List("*")) =>
copyWithConfigurations(art, ArraySeq.unsafeWrapArray(base.getModuleConfigurations))
case _ => art
}
}
private def defaultArtifact(
a: DependencyDescriptor
): Array[DependencyArtifactDescriptor] = {
val dd = new DefaultDependencyArtifactDescriptor(
a,
a.getDependencyRevisionId.getName,
"jar",
"jar",
null,
null
)
addConfigurations(dd, ArraySeq.unsafeWrapArray(a.getModuleConfigurations))
// If the dependency descriptor is empty, then it means that it has been created from a POM file. In this case,
// it is correct to create a seemingly nonexistent dependency artifact.
if (a.getAllDependencyArtifacts.isEmpty) Array(dd)
else a.getAllDependencyArtifacts filter (_ == dd)
}
private def copyWithConfigurations(
dd: DependencyArtifactDescriptor,
confs: Seq[String]
): DependencyArtifactDescriptor = {
val dextra = dd.getQualifiedExtraAttributes
val newd = new DefaultDependencyArtifactDescriptor(
dd.getDependencyDescriptor,
dd.getName,
dd.getType,
dd.getExt,
dd.getUrl,
dextra
)
addConfigurations(newd, confs)
newd
}
private def addConfigurations(
dd: DefaultDependencyArtifactDescriptor,
confs: Seq[String]
): Unit =
confs foreach dd.addConfiguration
private def concat[T: reflect.ClassTag](a: Array[T], b: Array[T]): Array[T] =
(a ++ b).distinct
def getAllExcludeRules = concat(a.getAllExcludeRules, b.getAllExcludeRules)
def getExcludeRules(moduleConfigurations: String) =
concat(a.getExcludeRules(moduleConfigurations), b.getExcludeRules(moduleConfigurations))
def getExcludeRules(moduleConfigurations: Array[String]) =
concat(a.getExcludeRules(moduleConfigurations), b.getExcludeRules(moduleConfigurations))
def doesExclude(moduleConfigurations: Array[String], artifactId: ArtifactId) =
a.doesExclude(moduleConfigurations, artifactId) || b.doesExclude(
moduleConfigurations,
artifactId
)
def canExclude = a.canExclude || b.canExclude
def asSystem = this
def clone(revision: ModuleRevisionId) =
new MergedDescriptors(a.clone(revision), b.clone(revision))
def getAttribute(name: String): String = a.getAttribute(name)
def getAttributes = a.getAttributes
def getExtraAttribute(name: String) = a.getExtraAttribute(name)
def getExtraAttributes = a.getExtraAttributes
def getQualifiedExtraAttributes = a.getQualifiedExtraAttributes
def getSourceModule = a.getSourceModule
}
@@ -1,114 +0,0 @@
package sbt.internal.librarymanagement.ivyint
import java.util.concurrent.Executors
import org.apache.ivy.core.event.EventManager
import org.apache.ivy.core.event.download.PrepareDownloadEvent
import org.apache.ivy.core.module.descriptor.Artifact
import org.apache.ivy.core.report.*
import org.apache.ivy.core.resolve.*
import org.apache.ivy.core.sort.SortEngine
import org.apache.ivy.util.filter.Filter
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, ExecutionContext, Future }
private[ivyint] case class DownloadResult(
dep: IvyNode,
report: DownloadReport,
totalSizeDownloaded: Long
)
object ParallelResolveEngine {
private lazy val resolveExecutionContext: ExecutionContext = {
// This throttles the connection number, especially when Gigahorse is not used.
val maxConnectionCount = 6
val executor = Executors.newFixedThreadPool(maxConnectionCount)
ExecutionContext.fromExecutor(executor)
}
}
/** Define an ivy [[ResolveEngine]] that resolves dependencies in parallel. */
private[sbt] class ParallelResolveEngine(
settings: ResolveEngineSettings,
eventManager: EventManager,
sortEngine: SortEngine
) extends ResolveEngine(settings, eventManager, sortEngine) {
override def downloadArtifacts(
report: ResolveReport,
artifactFilter: Filter,
options: DownloadOptions
): Unit = {
import scala.jdk.CollectionConverters.*
val start = System.currentTimeMillis
report.getArtifacts match {
case typed: java.util.List[Artifact @unchecked] =>
new PrepareDownloadEvent(typed.asScala.toArray)
}
// Farm out the dependencies for parallel download
given ExecutionContext = ParallelResolveEngine.resolveExecutionContext
val allDownloadsFuture = Future.traverse(report.getDependencies.asScala) { case dep: IvyNode =>
Future {
if (
!(dep.isCompletelyEvicted || dep.hasProblem) &&
dep.getModuleRevision != null
) {
Some(downloadNodeArtifacts(dep, artifactFilter, options))
} else None
}
}
val allDownloads = Await.result(allDownloadsFuture, Duration.Inf)
// compute total downloaded size
val totalSize = allDownloads.foldLeft(0L) {
case (size, Some(download)) =>
val dependency = download.dep
val moduleConfigurations = dependency.getRootModuleConfigurations
moduleConfigurations.foreach { configuration =>
val configurationReport = report.getConfigurationReport(configuration)
// Take into account artifacts required by the given configuration
if (
dependency.isEvicted(configuration) ||
dependency.isBlacklisted(configuration)
) {
configurationReport.addDependency(dependency)
} else configurationReport.addDependency(dependency, download.report)
}
size + download.totalSizeDownloaded
case (size, None) => size
}
report.setDownloadTime(System.currentTimeMillis() - start)
report.setDownloadSize(totalSize)
}
/**
* Download all the artifacts associated with an ivy node.
*
* Return the report and the total downloaded size.
*/
private def downloadNodeArtifacts(
dependency: IvyNode,
artifactFilter: Filter,
options: DownloadOptions
): DownloadResult = {
val resolver = dependency.getModuleRevision.getArtifactResolver
val selectedArtifacts = dependency.getSelectedArtifacts(artifactFilter)
val downloadReport = resolver.download(selectedArtifacts, options)
val artifactReports = downloadReport.getArtifactsReports
val totalSize = artifactReports.foldLeft(0L) { (size, artifactReport) =>
// Check download status and report resolution failures
artifactReport.getDownloadStatus match {
case DownloadStatus.SUCCESSFUL =>
size + artifactReport.getSize
case _ => size
}
}
DownloadResult(dependency, downloadReport, totalSize)
}
}
@@ -1,509 +0,0 @@
package sbt.internal.librarymanagement
package ivyint
import java.io.{ ByteArrayOutputStream, File, PrintWriter }
import java.text.ParseException
import java.util.Date
import org.apache.ivy.core.cache.ArtifactOrigin
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.core.{ IvyContext, LogOptions }
import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
import org.apache.ivy.core.module.descriptor.DependencyDescriptor
import org.apache.ivy.core.module.descriptor.ModuleDescriptor
import org.apache.ivy.core.module.descriptor.Artifact as IArtifact
import org.apache.ivy.core.resolve.{ ResolveData, ResolvedModuleRevision }
import org.apache.ivy.plugins.latest.LatestStrategy
import org.apache.ivy.plugins.repository.file.{ FileResource, FileRepository as IFileRepository }
import org.apache.ivy.plugins.repository.url.URLResource
import org.apache.ivy.plugins.resolver.*
import org.apache.ivy.plugins.resolver.util.{ HasLatestStrategy, ResolvedResource }
import org.apache.ivy.util.{ Message, StringUtils as IvyStringUtils }
import sbt.util.Logger
import sbt.librarymanagement.*
import sbt.internal.librarymanagement.ivy.UpdateOptions
import scala.util.control.NonFatal
private[sbt] case class SbtChainResolver(
name: String,
resolvers: Seq[DependencyResolver],
settings: IvySettings,
updateOptions: UpdateOptions,
log: Logger
) extends ChainResolver {
override def setCheckmodified(check: Boolean): Unit = super.setCheckmodified(check)
override def equals(o: Any): Boolean = o match {
case o: SbtChainResolver =>
this.name == o.name &&
this.resolvers == o.resolvers &&
this.settings == o.settings &&
this.updateOptions == o.updateOptions
case _ => false
}
override def hashCode: Int = {
var hash = 1
hash = hash * 31 + this.name.##
hash = hash * 31 + this.resolvers.##
hash = hash * 31 + this.settings.##
hash = hash * 31 + this.updateOptions.##
hash
}
// TODO - We need to special case the project resolver so it always "wins" when resolving with inter-project dependencies.
def initializeChainResolver(): Unit = {
// Initialize ourselves.
setName(name)
setReturnFirst(true)
setCheckmodified(false)
/* Append all the resolvers to the extended chain resolvers since we get its value later on */
resolvers.foreach(add)
}
initializeChainResolver()
// Technically, this should be applied to module configurations.
// That would require custom subclasses of all resolver types in ConvertResolver (a delegation approach does not work).
// It would be better to get proper support into Ivy.
// A workaround is to configure the ModuleConfiguration resolver to be a ChainResolver.
//
// This method is only used by the pom parsing code in Ivy to find artifacts it doesn't know about.
// In particular, a) it looks up source and javadoc classifiers b) it looks up a main artifact for packaging="pom"
// sbt now provides the update-classifiers or requires explicitly specifying classifiers explicitly
// Providing a main artifact for packaging="pom" does not seem to be correct and the lookup can be expensive.
//
// Ideally this could just skip the lookup, but unfortunately several artifacts in practice do not follow the
// correct behavior for packaging="pom" and so it is only skipped for source/javadoc classifiers.
override def locate(artifact: IArtifact): ArtifactOrigin =
if (IvySbt.hasImplicitClassifier(artifact)) null else super.locate(artifact)
override def getDependency(
dd: DependencyDescriptor,
data: ResolveData
): ResolvedModuleRevision = {
if (data.getOptions.getLog == LogOptions.LOG_DEFAULT)
Message.info("Resolving " + dd.getDependencyRevisionId + " ...")
val gd = CustomSbtResolution.getDependency(dd, data)
val mod = IvySbt.resetArtifactResolver(gd)
mod
}
/** Implements the custom sbt chain resolution with support for snapshots and caching. */
private object CustomSbtResolution {
def getCached(
dd: DependencyDescriptor,
data: ResolveData,
resolved0: Option[ResolvedModuleRevision]
): Option[ResolvedModuleRevision] = {
resolved0.orElse {
val resolverName = getName
Message.verbose(s"$resolverName: Checking cache for: $dd")
Option(findModuleInCache(dd, data, true)).map { moduleRev =>
Message.verbose(s"$resolverName: module revision found in cache: ${moduleRev.getId}")
forcedRevision(moduleRev)
}
}
}
/* Copy pasted from `IvyStringUtils` to handle `Throwable` */
private def getStackTrace(e: Throwable): String = {
if (e == null) {
""
} else {
val baos = new ByteArrayOutputStream()
val printWriter = new PrintWriter(baos)
e.printStackTrace(printWriter)
printWriter.flush()
val stackTrace = new String(baos.toByteArray)
printWriter.close()
stackTrace
}
}
/** If None, module was not found. Otherwise, hit. */
type TriedResolution = Option[(ResolvedModuleRevision, DependencyResolver)]
/**
* Attempts to resolve the artifact from each of the resolvers in the chain.
*
* Contract:
* 1. It doesn't resolve anything when there is a resolved module, `isReturnFirst` is
* enabled and `useLatest` is false (meaning that resolution is pure, no SNAPSHOT).
* 2. Otherwise, we try to resolve it.
*
* @param resolved0 The perhaps already resolved module.
* @param useLatest Whether snapshot resolution should be enabled.
* @param data The resolve data to use.
* @param descriptor The dependency descriptor of the in-resolution module.
*/
def getResults(
resolved0: Option[ResolvedModuleRevision],
useLatest: Boolean,
data: ResolveData,
descriptor: DependencyDescriptor,
resolvers: Seq[DependencyResolver]
): Seq[Either[Throwable, TriedResolution]] = {
var currentlyResolved = resolved0
def performResolution(
resolver: DependencyResolver
): Option[(ResolvedModuleRevision, DependencyResolver)] = {
// Resolve all resolvers when the module is changing
val previouslyResolved = currentlyResolved
if (useLatest) data.setCurrentResolvedModuleRevision(null)
else data.setCurrentResolvedModuleRevision(currentlyResolved.orNull)
currentlyResolved = Option(resolver.getDependency(descriptor, data))
if (currentlyResolved eq previouslyResolved) None
else if (useLatest) {
currentlyResolved.map(x =>
(reparseModuleDescriptor(descriptor, data, resolver, x), resolver)
)
} else currentlyResolved.map(x => (forcedRevision(x), resolver))
}
def reportError(throwable: Throwable, resolver: DependencyResolver): Unit = {
val trace = getStackTrace(throwable)
Message.verbose(s"problem occurred while resolving $descriptor with $resolver: $trace")
}
resolvers.map { (resolver: DependencyResolver) =>
// Return none when revision is cached and `isReturnFirst` is set
if (isReturnFirst && currentlyResolved.isDefined && !useLatest) Right(None)
else {
// We actually do resolution.
val oldLatest: Option[LatestStrategy] =
setLatestIfRequired(resolver, Option(getLatestStrategy))
try Right(performResolution(resolver))
catch { case NonFatal(t) => reportError(t, resolver); Left(t) }
finally {
oldLatest.foreach(_ => doSetLatestStrategy(resolver, oldLatest))
checkInterrupted()
}
}
}
}
private final val prefix = "Undefined resolution order"
def resolveLatest(
foundRevisions: Seq[(ResolvedModuleRevision, DependencyResolver)],
descriptor: DependencyDescriptor,
data: ResolveData
): Option[ResolvedModuleRevision] = {
val sortedRevisions = foundRevisions.sortBy { (rmr, resolver) =>
val publicationDate = rmr.getPublicationDate
val descriptorDate = rmr.getDescriptor.getPublicationDate
Message.warn(s"Sorting results from $rmr, using $publicationDate and $descriptorDate.")
// Just issue warning about issues with publication date, and fake one on it for now
val chosenPublicationDate = Option(publicationDate).orElse(Option(descriptorDate))
chosenPublicationDate match {
case Some(date) => date.getTime
case None =>
val id = rmr.getId
val resolvedResource = (resolver.findIvyFileRef(descriptor, data), rmr.getDescriptor)
resolvedResource match {
case (res: ResolvedResource, dmd: DefaultModuleDescriptor) =>
val resolvedPublicationDate = new java.util.Date(res.getLastModified)
Message.debug(s"No publication date from resolver $resolver for $id.")
Message.debug(s"Setting publication date to: $resolvedPublicationDate.")
dmd.setPublicationDate(resolvedPublicationDate)
res.getLastModified
case (ivf, dmd) =>
// The dependency is specified by a direct URL or some sort of non-ivy file
if (ivf == null && descriptor.isChanging)
Message.warn(s"$prefix: changing dependency $id with no ivy/pom file!")
if (dmd == null)
Message.warn(s"$prefix: no publication date from resolver $resolver for $id")
0L
}
}
}
val firstHit = sortedRevisions.reverse.headOption
firstHit.map { hit =>
val (resolvedModule, resolver) = hit
if (resolvedModule.getId.getRevision.contains("SNAPSHOT")) {
Message.warn(
"Resolving a snapshot version. It's going to be slow unless you use `updateOptions := updateOptions.value.withLatestSnapshots(false)` options."
)
val resolvers = sortedRevisions.map(_._2.getName)
sortedRevisions.foreach(h => {
val (module, resolver) = h
Message.info(
s"Out of ${sortedRevisions.size} candidates we found for ${module.getId} in ${resolvers
.mkString(" and ")}, we are choosing ${resolver}."
)
})
} else {
Message.warn(s"Choosing $resolver for ${resolvedModule.getId}")
}
// Now that we know the real latest revision, let's force Ivy to use it
val resolvedDescriptor = resolvedModule.getDescriptor
val artifactOpt = findFirstArtifactRef(resolvedDescriptor, data, resolver)
// If `None` do nothing -- modules without artifacts. Otherwise cache.
artifactOpt.foreach { artifactRef =>
val dep = toSystem(descriptor)
val first = toSystem(resolvedDescriptor).getAllArtifacts.head
val options = getCacheOptions(data)
val cacheManager = getRepositoryCacheManager
cacheManager.cacheModuleDescriptor(resolver, artifactRef, dep, first, null, options)
}
resolvedModule
}
}
def resolveByAllMeans(
cachedModule: Option[ResolvedModuleRevision],
useLatest: Boolean,
interResolver: Option[DependencyResolver],
resolveModules: () => Seq[Either[Throwable, TriedResolution]],
dd: DependencyDescriptor,
data: ResolveData
): Option[ResolvedModuleRevision] = {
val internallyResolved: Option[ResolvedModuleRevision] = {
if (!updateOptions.interProjectFirst) None
else interResolver.flatMap(resolver => Option(resolver.getDependency(dd, data)))
}
val internalOrExternal = internallyResolved.orElse {
val foundRevisions: Seq[(ResolvedModuleRevision, DependencyResolver)] =
resolveModules().collect { case Right(Some(x)) => x }
if (useLatest) resolveLatest(foundRevisions, dd, data)
else foundRevisions.reverse.headOption.map(_._1) // Resolvers are hit in reverse order
}
internalOrExternal.orElse(cachedModule)
}
/** Cleans unnecessary module id information not provided by [[IvyRetrieve.toModuleID()]]. */
private final val moduleResolvers = updateOptions.moduleResolvers.map { (key, value) =>
val cleanKey = ModuleID(key.organization, key.name, key.revision)
.withExtraAttributes(key.extraAttributes)
.withBranchName(key.branchName)
cleanKey -> value
}
/**
* Gets the list of resolvers to use for resolving a given descriptor.
*
* NOTE: The ivy implementation guarantees that all resolvers implement dependency resolver.
* @param descriptor The descriptor to be resolved.
*/
def getDependencyResolvers(descriptor: DependencyDescriptor): Vector[DependencyResolver] = {
val moduleRevisionId = descriptor.getDependencyRevisionId
val moduleID = IvyRetrieve.toModuleID(moduleRevisionId)
val resolverForModule = moduleResolvers.get(moduleID)
val ivyResolvers = getResolvers.toArray // Get resolvers from chain resolver directly
val allResolvers = ivyResolvers.collect { case r: DependencyResolver => r }.toVector
// Double check that dependency resolver will always be the super trait of a resolver
assert(ivyResolvers.size == allResolvers.size, "ALERT: Some ivy resolvers were filtered.")
val mappedResolver = resolverForModule.flatMap(r => allResolvers.find(_.getName == r.name))
mappedResolver match {
case Some(uniqueResolver) => Vector(uniqueResolver)
case None => allResolvers
}
}
def findInterProjectResolver(resolvers: Seq[DependencyResolver]): Option[DependencyResolver] =
resolvers.find(_.getName == ProjectResolver.InterProject)
/**
* Gets the dependency for a given descriptor with the pertinent resolve data.
*
* This is a custom sbt chain operation that produces better error output and deals with
* cases that the conventional ivy resolver does not. It accumulates the resolution of
* several resolvers and returns the module which fits the provided resolution strategy.
*
* These are the differences with regard to the default ivy [[ChainResolver]]:
* 1. It skips resolution if "return first" is set to true.
* 2. It skips resolution if a previously resolved or cached resolution is found.
* 3. It always checks all the resolvers and compares timestamps for changing dependencies
* if and only if `latestSnapshots` is enabled in the update options, regardless of what
* the latest strategies are (http://ant.apache.org/ivy/history/2.3.0/settings/latest-strategies.html).
* See https://github.com/sbt/sbt/pull/1520 for more information on this topic.
*
* Note the tradeoff here in SNAPSHOTs: correctness vs slowness.
*/
def getDependency(dd: DependencyDescriptor, data0: ResolveData): ResolvedModuleRevision = {
val isDynamic = dd.isChanging || IvySbt.isChanging(dd.getDependencyRevisionId)
val useLatest = isDynamic && updateOptions.latestSnapshots
if (useLatest) Message.verbose(s"$getName is changing. Checking all resolvers on the chain.")
/* Get the resolved module descriptor from:
* 1. An already resolved branch of the resolution tree.
* 2. The value from the cache. */
val data = new ResolveData(data0, doValidate(data0))
val resolved0 = Option(data.getCurrentResolvedModuleRevision)
val resolvedOrCached = getCached(dd, data0, resolved0)
val cached: Option[ResolvedModuleRevision] = if (useLatest) None else resolvedOrCached
val resolvers = getDependencyResolvers(dd)
val interResolver = findInterProjectResolver(resolvers)
// TODO: Please, change `Option` return types so that this goes away
lazy val results = getResults(cached, useLatest, data, dd, resolvers)
lazy val errors = results.collect { case Left(t) => t }
val runResolution = () => results
val resolved = resolveByAllMeans(cached, useLatest, interResolver, runResolution, dd, data)
resolved match {
case None if errors.size == 1 =>
errors.head match {
case e: RuntimeException => throw e
case e: ParseException => throw e
case e: Throwable => throw new RuntimeException(e.toString, e)
}
case None if errors.size > 1 =>
val traces = errors.toList.map(e => IvyStringUtils.getErrorMessage(e))
val msg = s"Resolution failed several times for $dd:"
throw new RuntimeException(s"$msg: ${traces.mkString("\n\t", "\n\t", "\n")}")
case _ =>
// Can be either `None` with empty error or `Some`
if (resolved0 == resolved) resolved0.orNull
else resolved.map(resolvedRevision).orNull
}
}
}
/* Ivy keeps module descriptors in memory, so we need to make sure that the
* resolved module revision is in fact the one found in the latest resolver. */
private def reparseModuleDescriptor(
dd: DependencyDescriptor,
data: ResolveData,
resolver: DependencyResolver,
previouslyResolved: ResolvedModuleRevision
): ResolvedModuleRevision = {
// TODO: Figure out better alternative or directly attack the
// resolvers ivy uses to get correct behaviour for SNAPSHOT
Option(resolver.findIvyFileRef(dd, data)) flatMap { ivyFile =>
ivyFile.getResource match {
case r: FileResource =>
val urlDescriptor = r.getFile.toURI.toURL
try {
val parser = previouslyResolved.getDescriptor.getParser
val md = parser.parseDescriptor(settings, urlDescriptor, r, false)
val report = previouslyResolved.getReport
// Note that we always set force for SNAPSHOT resolution...
Some(new ResolvedModuleRevision(resolver, resolver, md, report, true))
} catch {
case _: ParseException =>
Message.warn(s"The descriptor in $urlDescriptor from $resolver could not be parsed.")
Some(previouslyResolved)
}
case unhandledResource =>
val unhandledClassName = unhandledResource.getClass.getName
val tip = s"Returning previously resolved $previouslyResolved."
Message.debug(s"Latest snapshots option does not handle `$unhandledClassName`. $tip")
Some(previouslyResolved)
}
} getOrElse {
val previousRevision = dd.getDependencyRevisionId
val date = previouslyResolved.getPublicationDate
// Change from warn to debug -- see https://github.com/sbt/sbt/issues/2650.
Message.debug(s"Unable to find new descriptor for $previousRevision at $date in $resolver.")
previouslyResolved
}
}
/** Ported from BasicResolver#findFirstArtifactRef. */
private def findFirstArtifactRef(
md: ModuleDescriptor,
data: ResolveData,
resolver: DependencyResolver
): Option[ResolvedResource] = {
def artifactRef(artifact: IArtifact, date: Date): Option[ResolvedResource] =
resolver match {
case resolver: BasicResolver =>
IvyContext.getContext.set(resolver.getName + ".artifact", artifact)
try {
Option(resolver.doFindArtifactRef(artifact, date)) orElse {
Option(artifact.getUrl) map { url =>
Message.verbose("\tusing url for " + artifact + ": " + url)
val resource =
if ("file" == url.getProtocol)
new FileResource(new IFileRepository(), new File(url.getPath))
else new URLResource(url)
new ResolvedResource(resource, artifact.getModuleRevisionId.getRevision)
}
}
} finally {
IvyContext.getContext.set(resolver.getName + ".artifact", null)
}
case _ =>
None
}
val artifactRefs = md.getConfigurations.iterator flatMap { conf =>
md.getArtifacts(conf.getName).iterator flatMap { af =>
artifactRef(af, data.getDate).iterator
}
}
if (artifactRefs.hasNext) Some(artifactRefs.next())
else None
}
/** Ported from ChainResolver#forcedRevision. */
private def forcedRevision(rmr: ResolvedModuleRevision): ResolvedModuleRevision =
new ResolvedModuleRevision(
rmr.getResolver,
rmr.getArtifactResolver,
rmr.getDescriptor,
rmr.getReport,
true
)
/** Ported from ChainResolver#resolvedRevision. */
private def resolvedRevision(rmr: ResolvedModuleRevision): ResolvedModuleRevision =
if (isDual)
new ResolvedModuleRevision(
rmr.getResolver,
this,
rmr.getDescriptor,
rmr.getReport,
rmr.isForce
)
else rmr
/** Ported from ChainResolver#setLatestIfRequired. */
private def setLatestIfRequired(
resolver: DependencyResolver,
latest: Option[LatestStrategy]
): Option[LatestStrategy] =
latestStrategyName(resolver) match {
case Some(latestName) if latestName != "default" =>
val oldLatest = latestStrategy(resolver)
doSetLatestStrategy(resolver, latest)
oldLatest
case _ => None
}
/** Ported from ChainResolver#getLatestStrategyName. */
private def latestStrategyName(resolver: DependencyResolver): Option[String] =
resolver match {
case r: HasLatestStrategy => Some(r.getLatest)
case _ => None
}
/** Ported from ChainResolver#getLatest. */
private def latestStrategy(resolver: DependencyResolver): Option[LatestStrategy] =
resolver match {
case r: HasLatestStrategy => Some(r.getLatestStrategy)
case _ => None
}
/** Ported from ChainResolver#setLatest. */
private def doSetLatestStrategy(
resolver: DependencyResolver,
latest: Option[LatestStrategy]
): Option[LatestStrategy] =
resolver match {
case r: HasLatestStrategy =>
val oldLatest = latestStrategy(resolver)
r.setLatestStrategy(latest.orNull)
oldLatest
case _ => None
}
}
@@ -1,10 +0,0 @@
package sbt.internal.librarymanagement
package ivyint
import org.apache.ivy.core
import core.module.descriptor.DefaultDependencyDescriptor
import sbt.librarymanagement.*
trait SbtDefaultDependencyDescriptor { self: DefaultDependencyDescriptor =>
def dependencyModuleId: ModuleID
}
@@ -1,133 +0,0 @@
package sbt.internal.librarymanagement
package mavenint
import scala.collection.immutable.ArraySeq
import java.util.Properties
import java.util.regex.Pattern
import org.apache.ivy.core.module.descriptor.DependencyDescriptor
import org.apache.ivy.core.module.id.ModuleRevisionId
import org.apache.ivy.util.extendable.ExtendableItem
/**
* This class contains all the logic for dealing with the extra attributes in pom files relating to extra attributes
* on dependency declarations.
*
* Specifically, if we have a dependency on an sbt plugin, there are two properties that need to propagate:
* - `sbtVersion`
* - `scalaVersion`
*
* These need to exist on the *dependency declaration*. Maven/Aether has no way to inject these into
* the <dependency> section of pom files, so we use Ivy's Extra attribute hackery to inject a lookup table
* of extra attributes by dependency id into POM files and later we read these back.
*/
object PomExtraDependencyAttributes {
val ExtraAttributesKey = "extraDependencyAttributes"
val SbtVersionKey = "sbtVersion"
val ScalaVersionKey = "scalaVersion"
/**
* Reads the extra dependency attributes out of a maven property.
* @param props The properties from an Aether resolution.
* @return
* A map of module id to extra dependency attributes associated with dependencies on that module.
*/
def readFromAether(
props: java.util.Map[String, AnyRef]
): Map[ModuleRevisionId, Map[String, String]] = {
import scala.jdk.CollectionConverters.*
(props.asScala get ExtraAttributesKey) match {
case None => Map.empty
case Some(str) =>
def processDep(m: ModuleRevisionId) = (simplify(m), filterCustomExtra(m, include = true))
(for {
(id, props) <- readDependencyExtra(str.toString).map(processDep)
} yield id -> props).toMap
}
}
/**
* Mutates the to collection with the extra dependency attributes from the incoming pom properties list.
*
* @param from The properties directly off a maven POM file
* @param to The aether properties where we can write whatever we want.
*
* TODO - maybe we can just parse this directly here. Note the `readFromAether` method uses
* whatever we set here.
*/
def transferDependencyExtraAttributes(
from: Properties,
to: java.util.Map[String, AnyRef]
): Unit =
Option(from.getProperty(ExtraAttributesKey, null)) foreach (to.put(ExtraAttributesKey, _))
/**
* Reads the extra dependency information out of Ivy's notion of POM properties and returns
* the map of ID -> Extra Properties.
*/
def getDependencyExtra(m: Map[String, String]): Map[ModuleRevisionId, Map[String, String]] =
(m get ExtraAttributesKey) match {
case None => Map.empty
case Some(str) =>
def processDep(m: ModuleRevisionId) = (simplify(m), filterCustomExtra(m, include = true))
readDependencyExtra(str).map(processDep).toMap
}
def qualifiedExtra(item: ExtendableItem): Map[String, String] = {
import scala.jdk.CollectionConverters.*
item.getQualifiedExtraAttributes.asInstanceOf[java.util.Map[String, String]].asScala.toMap
}
def filterCustomExtra(item: ExtendableItem, include: Boolean): Map[String, String] =
qualifiedExtra(item).view.filterKeys { k => qualifiedIsExtra(k) == include }.toMap
def qualifiedIsExtra(k: String): Boolean =
k.endsWith(ScalaVersionKey) || k.endsWith(SbtVersionKey)
// Reduces the id to exclude custom extra attributes
// This makes the id suitable as a key to associate a dependency parsed from a <dependency> element
// with the extra attributes from the <properties> section
def simplify(id: ModuleRevisionId): ModuleRevisionId = {
import scala.jdk.CollectionConverters.*
ModuleRevisionId.newInstance(
id.getOrganisation,
id.getName,
id.getBranch,
id.getRevision,
filterCustomExtra(id, include = false).asJava
)
}
/** parses the sequence of dependencies with extra attribute information, with one dependency per line */
def readDependencyExtra(s: String): Seq[ModuleRevisionId] = ArraySeq.unsafeWrapArray(
LinesP.split(s).map(_.trim).withFilter(!_.isEmpty).map(ModuleRevisionId.decode)
)
private val LinesP = Pattern.compile("(?m)^")
/**
* Creates the "extra" property values for DependencyDescriptors that can be written into a maven pom
* so we don't lose the information.
* @param s
* @return
*/
def writeDependencyExtra(s: Seq[DependencyDescriptor]): Seq[String] =
s.flatMap { dd =>
val revId = dd.getDependencyRevisionId
val filteredExtra = filterCustomExtra(revId, include = true)
if (filteredExtra.isEmpty)
Nil
else {
import scala.jdk.CollectionConverters.*
val revId0 = ModuleRevisionId.newInstance(
revId.getOrganisation,
revId.getName,
revId.getBranch,
revId.getRevision,
filteredExtra.asJava
)
revId0.encodeToString :: Nil
}
}
}
Binary file not shown.
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ivy-module version="2.0" xmlns:e="http://ant.apache.org/ivy/extra">
<info organisation="com.test" module="module-with-srcs" revision="0.1.00" status="release" publication="20160107130136">
<description>
Just a test module that publishes both a binary jar and a src jar in the 'compile' configuration.
</description>
</info>
<configurations>
<conf name="compile" visibility="public" description=""/>
<conf name="runtime" visibility="public" description="" extends="compile"/>
<conf name="test" visibility="public" description="" extends="runtime"/>
<conf name="provided" visibility="public" description=""/>
<conf name="optional" visibility="public" description=""/>
<conf name="default" visibility="public" description="" extends="runtime"/>
<conf name="pom" visibility="public" description=""/>
</configurations>
<publications>
<artifact name="libmodule" type="jar" ext="jar" conf="compile"/>
<artifact name="libmodule-source" type="src" ext="jar" conf="compile"/>
</publications>
<dependencies>
</dependencies>
</ivy-module>
@@ -1,15 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>test-artifact</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>scala-jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
@@ -1,24 +0,0 @@
package sbt.internal.librarymanagement
import sbt.librarymanagement.*
import verify.BasicTestSuite
abstract class AbstractEngineSpec extends BasicTestSuite {
def cleanCache(): Unit
def module(
moduleId: ModuleID,
deps: Vector[ModuleID],
scalaFullVersion: Option[String]
): ModuleDescriptor
def updateEither(module: ModuleDescriptor): Either[UnresolvedWarning, UpdateReport]
def update(module: ModuleDescriptor) =
updateEither(module) match {
case Right(r) => r
case Left(w) => throw w.resolveException
}
def cleanCachedResolutionCache(@deprecated("unused", "") module: ModuleDescriptor): Unit = ()
}
@@ -1,16 +0,0 @@
package sbt.internal.librarymanagement
import sbt.librarymanagement.*
import sbt.internal.librarymanagement.ivy.*
trait BaseCachedResolutionSpec extends BaseIvySpecification {
override def module(
moduleId: ModuleID,
deps: Vector[ModuleID],
scalaFullVersion: Option[String]
): ModuleDescriptor = {
val uo: UpdateOptions = UpdateOptions()
.withCachedResolution(true)
module(moduleId, deps, scalaFullVersion, uo, true)
}
}
@@ -1,136 +0,0 @@
package sbt.internal.librarymanagement
import sbt.io.IO
import sbt.io.syntax.*
import java.io.File
import sbt.internal.librarymanagement.ivy.*
import sbt.internal.util.ConsoleLogger
import sbt.librarymanagement.*
import cross.CrossVersionUtil
import Configurations.*
trait BaseIvySpecification extends AbstractEngineSpec {
def currentBase: File = new File(".")
def label: String = this.getClass.getSimpleName
def currentTarget: File = currentBase / "target" / label / "ivyhome"
def currentManaged: File = currentBase / "target" / label / "lib_managed"
def currentDependency: File = currentBase / "target" / label / "dependency"
def defaultModuleId: ModuleID =
ModuleID("com.example", "foo", "0.1.0").withConfigurations(Some("compile"))
def scala2_13 = "2.13.10"
lazy val log = ConsoleLogger()
def lmEngine(uo: UpdateOptions = UpdateOptions()): DependencyResolution =
IvyDependencyResolution(mkIvyConfiguration(uo))
def configurations = Vector(Compile, Test, Runtime)
def module(
moduleId: ModuleID,
deps: Vector[ModuleID],
scalaFullVersion: Option[String]
): ModuleDescriptor = {
module(moduleId, deps, scalaFullVersion, UpdateOptions(), true)
}
def module(
moduleId: ModuleID,
deps: Vector[ModuleID],
scalaFullVersion: Option[String],
uo: UpdateOptions = UpdateOptions(),
overrideScalaVersion: Boolean = true,
appendSbtCrossVersion: Boolean = false,
platform: Option[String] = None,
): IvySbt#Module = {
val scalaModuleInfo = scalaFullVersion map { fv =>
ScalaModuleInfo(
scalaFullVersion = fv,
scalaBinaryVersion = CrossVersionUtil.binaryScalaVersion(fv),
configurations = Vector.empty,
checkExplicit = true,
filterImplicit = false,
overrideScalaVersion = overrideScalaVersion
)
.withPlatform(platform)
}
val moduleSetting: ModuleSettings = ModuleDescriptorConfiguration(moduleId, ModuleInfo("foo"))
.withDependencies(deps)
.withConfigurations(configurations)
.withScalaModuleInfo(scalaModuleInfo)
val ivySbt = new IvySbt(mkIvyConfiguration(uo))
new ivySbt.Module(moduleSetting, appendSbtCrossVersion)
}
def resolvers: Vector[Resolver] = Vector(Resolver.mavenCentral)
def chainResolver = ChainedResolver("sbt-chain", resolvers)
def mkIvyConfiguration(uo: UpdateOptions): IvyConfiguration = {
val moduleConfs = Vector(ModuleConfiguration("*", chainResolver))
val resCacheDir = currentTarget / "resolution-cache"
InlineIvyConfiguration()
.withPaths(IvyPaths(currentBase.toString, Some(currentTarget.toString)))
.withResolvers(resolvers)
.withModuleConfigurations(moduleConfs)
.withChecksums(Vector.empty)
.withResolutionCacheDir(resCacheDir)
.withLog(log)
.withUpdateOptions(uo)
}
def makeUpdateConfiguration(
offline: Boolean,
metadataDirectory: Option[File]
): UpdateConfiguration = {
val retrieveConfig = RetrieveConfiguration()
.withRetrieveDirectory(currentManaged)
.withOutputPattern(Resolver.defaultRetrievePattern)
.withSync(false)
UpdateConfiguration()
.withRetrieveManaged(retrieveConfig)
.withLogging(UpdateLogging.Full)
.withOffline(offline)
.withMetadataDirectory(metadataDirectory)
}
def updateEither(module: ModuleDescriptor): Either[UnresolvedWarning, UpdateReport] =
ivyUpdateEither(module)
def ivyUpdateEither(module: ModuleDescriptor): Either[UnresolvedWarning, UpdateReport] = {
module match {
case m: IvySbt#Module =>
val config = makeUpdateConfiguration(false, Some(currentDependency))
IvyActions.updateEither(m, config, UnresolvedWarningConfiguration(), log)
}
}
def cleanCache(): Unit = cleanIvyCache()
def cleanIvyCache(): Unit = IO.delete(currentTarget / "cache")
override def cleanCachedResolutionCache(module: ModuleDescriptor): Unit = {
module match {
case m: IvySbt#Module => IvyActions.cleanCachedResolutionCache(m, log)
}
}
def ivyUpdate(module: ModuleDescriptor): UpdateReport =
update(module)
def mkPublishConfiguration(
resolver: Resolver,
artifacts: Map[Artifact, File]
): PublishConfiguration = {
PublishConfiguration()
.withResolverName(resolver.name)
.withArtifacts(artifacts.toVector)
.withChecksums(Vector.empty)
.withOverwrite(true)
}
def ivyPublish(module: IvySbt#Module, config: PublishConfiguration) = {
IvyActions.publish(module, config, log)
}
}
@@ -1,10 +0,0 @@
package sbt.internal.librarymanagement
import sbt.librarymanagement.*
class CachedResolutionSpec extends ResolutionSpec with BaseCachedResolutionSpec {
override val resolvers = Vector(
Resolver.mavenCentral,
Resolver.sbtPluginRepo("releases")
)
}
@@ -1,165 +0,0 @@
package sbt.internal.librarymanagement
import verify.BasicTestSuite
// TODO - We need to re-enable this test. Right now, we dont' have a "stub" launcher for this.
// This is testing something which uses a launcher interface, but was grabbing the underlying class directly
// when it really should, instead, be stubbing out the underlying class.
object ComponentManagerTest extends BasicTestSuite {
val TestID = "manager-test"
/*
test(
"Component manager should throw an exception if 'file' is called for a non-existing component"
) {
withManager { manager =>
intercept[InvalidComponent] {
manager.file(TestID)(Fail)
()
}
}
}
test("it should throw an exception if 'file' is called for an empty component") {
withManager { manager =>
manager.define(TestID, Nil)
intercept[InvalidComponent] {
manager.file(TestID)(Fail)
()
}
}
}
test("it should return the file for a single-file component") {
withManager { manager =>
val hash = defineFile(manager, TestID, "a")
assert(checksum(manager.file(TestID)(Fail)) == hash)
}
}
test("it should throw an exception if 'file' is called for multi-file component") {
withManager { manager =>
defineFiles(manager, TestID, "a", "b")
intercept[InvalidComponent] {
manager.file(TestID)(Fail)
()
}
}
}
test("it should return the files for a multi-file component") {
withManager { manager =>
val hashes = defineFiles(manager, TestID, "a", "b")
assert(checksum(manager.files(TestID)(Fail)).toSet == hashes.toSet)
}
}
test("it should return the files for a single-file component") {
withManager { manager =>
val hashes = defineFiles(manager, TestID, "a")
assert(checksum(manager.files(TestID)(Fail)).toSet == hashes.toSet)
}
}
test("it should throw an exception if 'files' is called for a non-existing component") {
withManager { manager =>
intercept[InvalidComponent] {
manager.files(TestID)(Fail)
()
}
}
}
test("it should properly cache a file and then retrieve it to an unresolved component") {
withTemporaryDirectory { ivyHome =>
withManagerHome(ivyHome) { definingManager =>
val hash = defineFile(definingManager, TestID, "a")
try {
definingManager.cache(TestID)
withManagerHome(ivyHome) { usingManager =>
assert(checksum(usingManager.file(TestID)(Fail)) == hash)
}
} finally {
definingManager.clearCache(TestID)
}
}
}
}
private def checksum(files: Iterable[File]): Seq[String] = files.map(checksum).toSeq
private def checksum(file: File): String =
if (file.exists) ChecksumHelper.computeAsString(file, "sha1") else ""
private def defineFile(manager: ComponentManager, id: String, name: String): String =
createFile(manager, id, name)(checksum)
private def defineFiles(manager: ComponentManager, id: String, names: String*): Seq[String] =
createFiles(manager, id, names: _*)(checksum)
private def createFile[T](manager: ComponentManager, id: String, name: String)(f: File => T): T =
createFiles(manager, id, name)(files => f(files.toList.head))
private def createFiles[T](manager: ComponentManager, id: String, names: String*)(
f: Seq[File] => T
): T =
withTemporaryDirectory { dir =>
val files = names.map(name => new File(dir, name))
files.foreach(writeRandomContent)
manager.define(id, files)
f(files)
}
private def writeRandomContent(file: File) = IO.write(file, randomString)
private def randomString = "asdf"
private def withManager[T](f: ComponentManager => T): T =
withTemporaryDirectory { ivyHome =>
withManagerHome(ivyHome)(f)
}
private def withManagerHome[T](ivyHome: File)(f: ComponentManager => T): T =
TestLogger { logger =>
withTemporaryDirectory { temp =>
// The actual classes we'll use at runtime.
// val mgr = new ComponentManager(xsbt.boot.Locks, new xsbt.boot.ComponentProvider(temp, true), Some(ivyHome), logger)
// A stub component manager
object provider extends ComponentProvider {
override def componentLocation(id: String): File = new File(temp, id)
override def lockFile(): File = {
IO.createDirectory(temp)
new java.io.File(temp, "sbt.components.lock")
}
override def defineComponent(id: String, files: Array[File]): Unit = {
val location = componentLocation(id)
if (location.exists)
throw new RuntimeException(
s"Cannot redefine component. ID: $id, files: ${files.mkString(",")}"
)
else {
IO.copy(files.map { f =>
f -> new java.io.File(location, f.getName)
})
()
}
}
override def addToComponent(id: String, files: Array[File]): Boolean = {
val location = componentLocation(id)
IO.copy(files.map { f =>
f -> new java.io.File(location, f.getName)
})
true
}
override def component(id: String): Array[File] =
Option(componentLocation(id).listFiles())
.map(_.filter(_.isFile))
.getOrElse(Array.empty)
}
// A stubbed locking API.
object locks extends xsbti.GlobalLock {
override def apply[U](lockFile: File, run: Callable[U]): U = {
// TODO - do we need to lock?
run.call()
}
}
val mgr = new ComponentManager(locks, provider, Some(ivyHome), logger)
f(mgr)
}
}
*/
}
@@ -1,40 +0,0 @@
package sbt.internal.librarymanagement
import sbt.librarymanagement.*
import sbt.librarymanagement.syntax.*
object ConflictWarningSpec extends BaseIvySpecification {
test("it should print out message about the cross-Scala conflict") {
var found = false
val deps = Vector(
`scala2.13.6`,
`cats-effect3.1.1`,
`cats-core2.6.1`.cross(CrossVersion.for3Use2_13),
)
val m = module(defaultModuleId, deps, Some("3.0.1-RC2"))
val report = ivyUpdate(m)
val w = ConflictWarning.default("foo")
try {
ConflictWarning(w, report, log)
} catch {
case e: Throwable =>
found = true
assert(
e.getMessage.linesIterator.toList.head
.startsWith("Conflicting cross-version suffixes in")
)
}
if (!found) {
sys.error("conflict warning was expected, but didn't happen sbt/sbt#6578")
}
}
lazy val `scala2.13.6` =
ModuleID("org.scala-lang", "scala-library", "2.13.6").withConfigurations(Some("compile"))
lazy val `cats-effect3.1.1` =
("org.typelevel" %% "cats-effect" % "3.1.1").withConfigurations(Some("compile"))
lazy val `cats-core2.6.1` =
("org.typelevel" %% "cats-core" % "2.6.1").withConfigurations(Some("compile"))
}
@@ -1,60 +0,0 @@
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.librarymanagement
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.plugins.resolver.AbstractPatternsBasedResolver
import sbt.internal.librarymanagement.ivy.UpdateOptions
import sbt.internal.util.ConsoleLogger
import sbt.librarymanagement.{ Patterns, URLRepository }
import verify.BasicTestSuite
import scala.jdk.CollectionConverters.*
// Regression coverage for sbt/sbt#535: the organization token in an Ivy SFTP/SSH/URL resolver pattern
// should be rendered literally unless the resolver is explicitly Maven-compatible. ConvertResolver is the
// boundary sbt owns: it maps Patterns.isMavenCompatible onto Apache Ivy's setM2compatible, which is what
// makes Ivy keep [organisation] literal (false) or rewrite it to slash form (true).
object ConvertResolverSpec extends BasicTestSuite {
private val log = ConsoleLogger()
// The configuration from issue #535: a custom Ivy pattern using the [organisation] token.
private val orgPatterns =
Patterns()
.withIvyPatterns(
Vector("https://example.org/repo/[organisation]/[module]/ivys/ivy-[revision].xml")
)
.withArtifactPatterns(
Vector(
"https://example.org/repo/[organisation]/[module]/[type]s/[artifact]-[revision].[ext]"
)
)
private def convert(patterns: Patterns): AbstractPatternsBasedResolver =
ConvertResolver(URLRepository("test-repo", patterns), new IvySettings, UpdateOptions(), log)
.asInstanceOf[AbstractPatternsBasedResolver]
test("the default Patterns keeps the Ivy resolver non-m2compatible (issue #535)") {
assert(!orgPatterns.isMavenCompatible)
assert(!convert(orgPatterns).isM2compatible)
}
test("isMavenCompatible = false renders the [organisation] token literally") {
val resolver = convert(orgPatterns)
assert(!resolver.isM2compatible)
// sbt forwards the pattern verbatim; with m2compatible off Ivy does not rewrite the organization.
assert(resolver.getArtifactPatterns.asScala.exists(_.toString.contains("[organisation]")))
}
test(
"isMavenCompatible = true makes the Ivy resolver m2compatible (organization rewritten to slash form)"
) {
assert(convert(orgPatterns.withIsMavenCompatible(true)).isM2compatible)
}
}
@@ -1,51 +0,0 @@
package sbt.internal.librarymanagement
import sbt.internal.librarymanagement.ivy.IvyCredentials
import sbt.librarymanagement.Credentials
import java.io.File
import java.nio.file.Files
import org.scalatest.funsuite.AnyFunSuite
class CredentialsSpec extends AnyFunSuite {
test("load credential file without authentication") {
val credentialsFile = File.createTempFile("credentials", "tmp")
val content =
"""|host=example.org
|user=username
|password=password""".stripMargin
Files.write(credentialsFile.toPath(), content.getBytes())
val Right(credentials) = IvyCredentials.loadCredentials(credentialsFile): @unchecked
assert(credentials.realm == null)
credentialsFile.delete()
}
test("DirectCredentials.toString") {
assert(
Credentials(
realm = null,
host = "example.org",
userName = "username",
passwd = "password"
).toString ==
"""DirectCredentials(null, "example.org", "username", ****)"""
)
assert(
Credentials(
realm = "realm",
host = "example.org",
userName = "username",
passwd = "password"
).toString ==
"""DirectCredentials("realm", "example.org", "username", ****)"""
)
}
}
@@ -1,42 +0,0 @@
package sbt.internal.librarymanagement
import java.io.File
import org.apache.ivy.core.module.descriptor.Artifact as IvyArtifact
import org.apache.ivy.core.module.id.ModuleRevisionId
import org.apache.ivy.core.resolve.ResolveOptions
import sbt.internal.librarymanagement.ivy.InlineIvyConfiguration
import sbt.librarymanagement.*
import sbt.io.IO.withTemporaryDirectory
import sbt.internal.util.ConsoleLogger
import verify.BasicTestSuite
object CustomPomParserTest extends BasicTestSuite {
test(
"CustomPomParser should resolve an artifact with packaging 'scala-jar' as a regular jar file."
) {
val log = ConsoleLogger()
withTemporaryDirectory { cacheDir =>
val repoUrl = getClass.getResource("/test-maven-repo")
val local = MavenRepository("Test Repo", repoUrl.toExternalForm)
val paths = IvyPaths(new File(".").toString, Some(cacheDir.toString))
val conf = InlineIvyConfiguration()
.withPaths(paths)
.withResolvers(Vector(local))
.withLog(log)
val ivySbt = new IvySbt(conf)
val resolveOpts = new ResolveOptions().setConfs(Array("default"))
val mrid = ModuleRevisionId.newInstance("com.test", "test-artifact", "1.0.0-SNAPSHOT")
val resolveReport = ivySbt.withIvy(log) { ivy =>
ivy.resolve(mrid, resolveOpts, true)
}
assert(!resolveReport.hasError)
assert(resolveReport.getArtifacts.size() == 1)
val artifact: IvyArtifact =
resolveReport.getArtifacts.asInstanceOf[java.util.List[IvyArtifact]].get(0)
assert(artifact.getModuleRevisionId == mrid)
assert(artifact.getExt == "jar")
}
}
}
@@ -1,110 +0,0 @@
package sbt.internal.librarymanagement
import java.net.URI
import java.io.File
import sbt.librarymanagement.*
import sjsonnew.shaded.scalajson.ast.unsafe.*
import sjsonnew.*, support.scalajson.unsafe.*
import LibraryManagementCodec.given
import verify.BasicTestSuite
object DMSerializationSpec extends BasicTestSuite {
test("CrossVersion.full should roundtrip") {
roundtripStr(CrossVersion.full: CrossVersion)
}
test("CrossVersion.binary should roundtrip") {
roundtripStr(CrossVersion.binary: CrossVersion)
}
test("CrossVersion.for3Use2_13 should roundtrip") {
roundtripStr(CrossVersion.for3Use2_13: CrossVersion)
}
test("CrossVersion.for2_13Use3 with prefix should roundtrip") {
roundtripStr(CrossVersion.for2_13Use3With("_sjs1", ""): CrossVersion)
}
test("CrossVersion.Disabled should roundtrip") {
roundtrip(Disabled(): CrossVersion)
}
test("""Artifact("foo") should roundtrip""") {
roundtrip(Artifact("foo"))
}
test("""Artifact("foo", "sources") should roundtrip""") {
roundtrip(Artifact("foo", "sources"))
}
test("""Artifact.pom("foo") should roundtrip""") {
roundtrip(Artifact.pom("foo"))
}
test("""Artifact("foo", url("http://example.com/")) should roundtrip""") {
roundtrip(Artifact("foo", new URI("http://example.com/")))
}
test("""Artifact("foo").extra(("key", "value")) should roundtrip""") {
roundtrip(Artifact("foo").extra(("key", "value")))
}
test("""ModuleID("org", "name", "1.0") should roundtrip""") {
roundtrip(ModuleID("org", "name", "1.0"))
}
test("""ModuleReport(ModuleID("org", "name", "1.0"), Nil, Nil) should roundtrip""") {
roundtripStr(ModuleReport(ModuleID("org", "name", "1.0"), Vector.empty, Vector.empty))
}
test("Organization artifact report should roundtrip") {
roundtripStr(organizationArtifactReportExample)
}
test("Configuration report should roundtrip") {
roundtripStr(configurationReportExample)
}
test("Update report should roundtrip") {
roundtripStr(updateReportExample)
}
lazy val updateReportExample =
UpdateReport(
new File("./foo"),
Vector(configurationReportExample),
UpdateStats(0, 0, 0, false),
Map("./foo" -> 0)
)
lazy val configurationReportExample =
ConfigurationReport(
ConfigRef("compile"),
Vector(moduleReportExample),
Vector(organizationArtifactReportExample)
)
lazy val organizationArtifactReportExample =
OrganizationArtifactReport("org", "name", Vector(moduleReportExample))
lazy val moduleReportExample =
ModuleReport(ModuleID("org", "name", "1.0"), Vector.empty, Vector.empty)
def roundtrip[A: JsonReader: JsonWriter](a: A): Unit =
roundtripBuilder(a) { (x1, x2) =>
assert(x1 == x2)
}
def roundtripStr[A: JsonReader: JsonWriter](a: A): Unit =
roundtripBuilder(a) { (x1, x2) =>
assert(x1.toString == x2.toString)
}
def roundtripBuilder[A: JsonReader: JsonWriter](a: A)(f: (A, A) => Unit): Unit = {
val json = isoString.to(Converter.toJsonUnsafe(a))
println(json)
val obj = Converter.fromJsonUnsafe[A](isoString.from(json))
f(a, obj)
}
given isoString: IsoString[JValue] =
IsoString.iso(CompactPrinter.apply, Parser.parseUnsafe)
}
@@ -1,89 +0,0 @@
package sbt
package internal
package librarymanagement
import java.io.File
import sbt.librarymanagement.{ ModuleID, RawRepository, Resolver, UpdateReport, ResolveException }
object FakeResolverSpecification extends BaseIvySpecification {
import FakeResolver.*
val myModule =
ModuleID("org.example", "my-module", "0.0.1-SNAPSHOT").withConfigurations(Some("compile"))
val example = ModuleID("com.example", "example", "1.0.0").withConfigurations(Some("compile"))
val anotherExample =
ModuleID("com.example", "another-example", "1.0.0").withConfigurations(Some("compile"))
val nonExisting =
ModuleID("com.example", "does-not-exist", "1.2.3").withConfigurations(Some("compile"))
test("The FakeResolver should find modules with only one artifact") {
val m = getModule(myModule)
val report = ivyUpdate(m)
val allFiles = getAllFiles(report)
assert(report.allModules.length == 1)
assert(report.allModuleReports.length == 1)
assert(report.configurations.length == 3)
assert(allFiles.toSet.size == 1)
assert(allFiles(1).getName == "artifact1-0.0.1-SNAPSHOT.jar")
}
test("it should find modules with more than one artifact") {
val m = getModule(example)
val report = ivyUpdate(m)
val allFiles = getAllFiles(report).toSet
assert(report.allModules.length == 1)
assert(report.allModuleReports.length == 1)
assert(report.configurations.length == 3)
assert(allFiles.toSet.size == 2)
assert(allFiles.map(_.getName) == Set("artifact1-1.0.0.jar", "artifact2-1.0.0.txt"))
}
test("it should fail gracefully when asked for unknown modules") {
val m = getModule(nonExisting)
intercept[ResolveException] {
ivyUpdate(m)
()
}
}
test("it should fail gracefully when some artifacts cannot be found") {
val m = getModule(anotherExample)
intercept[ResolveException] {
ivyUpdate(m)
()
}
}
private def artifact1 = new File(getClass.getResource("/artifact1.jar").toURI.getPath)
private def artifact2 = new File(getClass.getResource("/artifact2.txt").toURI.getPath)
private def modules = Map(
("org.example", "my-module", "0.0.1-SNAPSHOT") -> List(
FakeArtifact("artifact1", "jar", "jar", artifact1)
),
("com.example", "example", "1.0.0") -> List(
FakeArtifact("artifact1", "jar", "jar", artifact1),
FakeArtifact("artifact2", "txt", "txt", artifact2)
),
("com.example", "another-example", "1.0.0") -> List(
FakeArtifact("artifact1", "jar", "jar", artifact1),
FakeArtifact("non-existing", "txt", "txt", new File("non-existing-file"))
)
)
private def fakeResolver = new FakeResolver("FakeResolver", new File("tmp"), modules)
override def resolvers: Vector[Resolver] =
Vector(new RawRepository(fakeResolver, fakeResolver.getName))
private def getModule(myModule: ModuleID): IvySbt#Module =
module(defaultModuleId, Vector(myModule), None)
private def getAllFiles(report: UpdateReport) =
for {
conf <- report.configurations
m <- conf.modules
(_, f) <- m.artifacts
} yield f
}
@@ -1,77 +0,0 @@
package sbt.internal.librarymanagement
import sbt.librarymanagement.*
import sbt.internal.librarymanagement.ivy.UpdateOptions
import sbt.librarymanagement.syntax.*
object FrozenModeSpec extends BaseIvySpecification {
private final val targetDir = Some(currentDependency)
private final val onlineConf = makeUpdateConfiguration(false, targetDir)
private final val frozenConf = makeUpdateConfiguration(false, targetDir).withFrozen(true)
private final val warningConf = UnresolvedWarningConfiguration()
private final val normalOptions = UpdateOptions()
final val stoml = Vector("me.vican.jorge" % "stoml_2.12" % "0.4" % "compile")
/* https://repo1.maven.org/maven2/me/vican/jorge/stoml_2.12/0.4/stoml_2.12-0.4.jar
* https://repo1.maven.org/maven2/org/scala-lang/scala-library/2.12.0/scala-library-2.12.0.jar
* https://repo1.maven.org/maven2/com/lihaoyi/fastparse_2.12/0.4.2/fastparse_2.12-0.4.2.jar
* https://repo1.maven.org/maven2/com/lihaoyi/fastparse-utils_2.12/0.4.2/fastparse-utils_2.12-0.4.2.jar
* https://repo1.maven.org/maven2/com/lihaoyi/sourcecode_2.12/0.1.3/sourcecode_2.12-0.1.3.jar */
final val explicitStoml = Vector(
"me.vican.jorge" % "stoml_2.12" % "0.4" % "compile",
"org.scala-lang" % "scala-library" % "2.12.0" % "compile",
"com.lihaoyi" % "fastparse_2.12" % "0.4.2" % "compile",
"com.lihaoyi" % "fastparse-utils_2.12" % "0.4.2" % "compile",
"com.lihaoyi" % "sourcecode_2.12" % "0.1.3" % "compile"
)
test("fail when artifacts are missing in the cache") {
cleanIvyCache()
def update(module: IvySbt#Module, conf: UpdateConfiguration) =
IvyActions.updateEither(module, conf, warningConf, log)
val toResolve = module(defaultModuleId, stoml, None, normalOptions)
val onlineResolution = update(toResolve, onlineConf)
assert(onlineResolution.isRight)
val numberResolved =
onlineResolution.fold(e => throw e.resolveException, identity).allModules.size
val numberReportsResolved =
onlineResolution.fold(e => throw e.resolveException, identity).allModuleReports.size
cleanIvyCache()
val singleFrozenResolution = update(toResolve, frozenConf)
assert(singleFrozenResolution.isRight)
assert(
singleFrozenResolution.fold(e => throw e.resolveException, identity).allModules.size == 1,
s"The number of explicit modules in frozen mode should 1"
)
assert(
singleFrozenResolution
.fold(e => throw e.resolveException, identity)
.allModuleReports
.size == 1,
s"The number of explicit module reports in frozen mode should 1"
)
cleanIvyCache()
// This relies on the fact that stoml has 5 transitive dependencies
val toExplicitResolve = module(defaultModuleId, explicitStoml, None, normalOptions)
val frozenResolution = update(toExplicitResolve, frozenConf)
assert(frozenResolution.isRight)
assert(
frozenResolution
.fold(e => throw e.resolveException, identity)
.allModules
.size == numberResolved,
s"The number of explicit modules in frozen mode should be equal than $numberResolved"
)
assert(
frozenResolution
.fold(e => throw e.resolveException, identity)
.allModuleReports
.size == numberReportsResolved,
s"The number of explicit module reports in frozen mode should be equal than $numberReportsResolved"
)
}
}
@@ -1,44 +0,0 @@
package sbt.internal.librarymanagement
import sbt.librarymanagement.*
import verify.BasicTestSuite
// This is a specification to check the inconsistent duplicate warnings
object InconsistentDuplicateSpec extends BasicTestSuite {
test("Duplicate with different version should be warned") {
assert(
IvySbt.inconsistentDuplicateWarning(Seq(akkaActor214, akkaActor230)) ==
List(
"Multiple dependencies with the same organization/name but different versions. To avoid conflict, pick one version:",
" * com.typesafe.akka:akka-actor:(2.1.4, 2.3.0)"
)
)
}
test("it should not be warned if in different configurations") {
assert(IvySbt.inconsistentDuplicateWarning(Seq(akkaActor214, akkaActor230Test)) == Nil)
}
test("Duplicate with same version should not be warned") {
assert(IvySbt.inconsistentDuplicateWarning(Seq(akkaActor230Test, akkaActor230)) == Nil)
}
def akkaActor214 =
ModuleID("com.typesafe.akka", "akka-actor", "2.1.4")
.withConfigurations(
Some("compile")
)
.cross(CrossVersion.binary)
def akkaActor230 =
ModuleID("com.typesafe.akka", "akka-actor", "2.3.0")
.withConfigurations(
Some("compile")
)
.cross(CrossVersion.binary)
def akkaActor230Test =
ModuleID("com.typesafe.akka", "akka-actor", "2.3.0")
.withConfigurations(
Some("test")
)
.cross(CrossVersion.binary)
}
@@ -1,74 +0,0 @@
package sbt.internal.librarymanagement
import verify.BasicTestSuite
object IvyActionsOverrideSpec extends BasicTestSuite:
test(
"IvyActions.applyDependencyOverrides should replace rev attribute for matching dependencies"
):
val overrideMap = Map(("org.slf4j", "slf4j-api") -> "2.0.16")
val sampleXml =
<ivy-module version="2.0">
<info organisation="test" module="test" revision="1.0"/>
<dependencies>
<dependency org="org.slf4j" name="slf4j-api" rev="managed" conf="compile->default(compile)"/>
<dependency org="other.org" name="other-lib" rev="1.0.0" conf="compile->default(compile)"/>
</dependencies>
</ivy-module>
val updated = IvyActions.applyDependencyOverrides(sampleXml, overrideMap)
val dependencies = (updated \\ "dependency")
// Check slf4j-api has overridden version
val slf4jDep = dependencies.find(d => (d \ "@org").text == "org.slf4j")
assert(slf4jDep.isDefined)
assert((slf4jDep.get \ "@rev").text == "2.0.16")
assert((slf4jDep.get \ "@name").text == "slf4j-api")
assert((slf4jDep.get \ "@conf").text == "compile->default(compile)")
// Check other-lib is unchanged
val otherDep = dependencies.find(d => (d \ "@org").text == "other.org")
assert(otherDep.isDefined)
assert((otherDep.get \ "@rev").text == "1.0.0")
assert((otherDep.get \ "@name").text == "other-lib")
test(
"IvyActions.applyDependencyOverrides should preserve all attributes when replacing rev"
):
val overrideMap = Map(("org.example", "test-lib") -> "3.0.0")
val sampleXml =
<ivy-module version="2.0">
<dependencies>
<dependency org="org.example" name="test-lib" rev="1.0.0" conf="compile->default" transitive="false" force="true"/>
</dependencies>
</ivy-module>
val updated = IvyActions.applyDependencyOverrides(sampleXml, overrideMap)
val dep = (updated \\ "dependency").head
assert((dep \ "@org").text == "org.example")
assert((dep \ "@name").text == "test-lib")
assert((dep \ "@rev").text == "3.0.0")
assert((dep \ "@conf").text == "compile->default")
assert((dep \ "@transitive").text == "false")
assert((dep \ "@force").text == "true")
test("IvyActions.applyDependencyOverrides should not modify dependencies without overrides"):
val overrideMap = Map(("org.other", "other-lib") -> "2.0.0")
val sampleXml =
<ivy-module version="2.0">
<dependencies>
<dependency org="org.example" name="test-lib" rev="1.0.0" conf="compile->default"/>
</dependencies>
</ivy-module>
val updated = IvyActions.applyDependencyOverrides(sampleXml, overrideMap)
val dep = (updated \\ "dependency").head
assert((dep \ "@rev").text == "1.0.0") // Should remain unchanged
end IvyActionsOverrideSpec
@@ -1,38 +0,0 @@
package sbt.internal.librarymanagement
import sbt.internal.librarymanagement.mavenint.PomExtraDependencyAttributes.{
SbtVersionKey,
ScalaVersionKey
}
import sbt.librarymanagement.{ CrossVersion, ModuleDescriptorConfiguration }
object IvyModuleSpec extends BaseIvySpecification {
test("The Scala binary version of a Scala module should be appended to its name") {
val m = module(
defaultModuleId.withCrossVersion(CrossVersion.Binary()),
Vector.empty,
Some("2.13.10")
)
m.moduleSettings match {
case configuration: ModuleDescriptorConfiguration =>
assert(configuration.module.name == "foo_2.13")
case _ => fail()
}
}
test("The sbt cross-version should be appended to the name of an sbt plugin") {
val m = module(
defaultModuleId.extra(SbtVersionKey -> "1.0", ScalaVersionKey -> "2.12"),
Vector.empty,
Some("2.12.17"),
appendSbtCrossVersion = true
)
m.moduleSettings match {
case configuration: ModuleDescriptorConfiguration =>
assert(configuration.module.name == "foo_2.12_1.0")
case _ => fail()
}
}
}
@@ -1,105 +0,0 @@
package sbt.internal.librarymanagement
import org.scalatest.Inside
import sbt.librarymanagement.*
import sbt.librarymanagement.syntax.*
import InternalDefaults.*
object IvyRepoSpec extends BaseIvySpecification {
val ourModuleID = ModuleID("com.example", "foo", "0.1.0").withConfigurations(Some("compile"))
def makeModuleForDepWithSources = {
// By default a module seems to only have [compile, test, runtime], yet deps automatically map to
// default->compile(default) ... so I guess we have to explicitly use e.g. "compile"
val dep = "com.test" % "module-with-srcs" % "0.1.00" % "compile"
module(
ourModuleID,
Vector(dep),
None // , UpdateOptions().withCachedResolution(true)
)
}
test(
"ivyUpdate from ivy repository should resolve only binary artifact from module which also contains a sources artifact under the same configuration."
) {
cleanIvyCache()
val m = makeModuleForDepWithSources
val report = ivyUpdate(m)
import Inside.*
inside(report.configuration(ConfigRef("compile")).map(_.modules)) { case Some(Seq(mr)) =>
inside(mr.artifacts) { case Seq((ar, _)) =>
assert(ar.`type` == "jar")
assert(ar.extension == "jar")
}
}
}
test(
"it should resolve only sources artifact of an acceptable artifact type, \"src\", when calling updateClassifiers."
) {
cleanIvyCache()
val m = makeModuleForDepWithSources
// the "default" configuration used in `update`.
val c = makeUpdateConfiguration(false, None)
val scalaModuleInfo = m.moduleSettings.scalaModuleInfo
val srcTypes = Vector("src")
val docTypes = Vector("javadoc")
// These will be the default classifiers that SBT should try, in case a dependency is Maven.
// In this case though, they will be tried and should fail gracefully - only the
val attemptedClassifiers = Vector("sources", "javadoc")
// The dep that we want to get the "classifiers" (i.e. sources / docs) for.
// We know it has only one source artifact in the "compile" configuration.
val dep = "com.test" % "module-with-srcs" % "0.1.00" % "compile"
val clMod = {
val externalModules = Vector(dep)
// Note: need to extract ourModuleID so we can plug it in here, can't fish it back out of the IvySbt#Module (`m`)
GetClassifiersModule(
ourModuleID,
scalaModuleInfo,
externalModules,
Vector(Configurations.Compile),
attemptedClassifiers
)
}
val artifactFilter = getArtifactTypeFilter(c.artifactFilter)
val gcm = GetClassifiersConfiguration(
clMod,
Vector.empty,
c.withArtifactFilter(artifactFilter.invert),
srcTypes,
docTypes
)
val report2 =
lmEngine()
.updateClassifiers(gcm, UnresolvedWarningConfiguration(), Vector(), log)
.fold(e => throw e.resolveException, identity)
import Inside.*
inside(report2.configuration(ConfigRef("compile")).map(_.modules)) { case Some(Seq(mr)) =>
inside(mr.artifacts) { case Seq((ar, _)) =>
assert(ar.name == "libmodule-source")
assert(ar.`type` == "src")
assert(ar.extension == "jar")
}
}
}
override lazy val resolvers: Vector[Resolver] = Vector(testIvy)
lazy val testIvy = {
val repoUrl = getClass.getResource("/test-ivy-repo").toURI()
Resolver.uri("Test Repo", repoUrl)(using Resolver.ivyStylePatterns)
}
}
@@ -1,10 +0,0 @@
package sbt.internal.librarymanagement
import sbt.librarymanagement.*
class IvyResolutionSpec extends ResolutionSpec with BaseIvySpecification {
override val resolvers = Vector(
Resolver.mavenCentral,
Resolver.sbtPluginRepo("releases")
)
}
@@ -1,66 +0,0 @@
package sbt.internal.librarymanagement
import java.io.IOException
import org.scalatest.funsuite.AnyFunSuite
import sbt.internal.librarymanagement.IvyUtil.*
class IvyUtilSpec extends AnyFunSuite {
test("503 should be a TransientNetworkException") {
val statusCode503Exception =
new IOException("Server returned HTTP response code: 503 for URL:")
assert(TransientNetworkException(statusCode503Exception))
}
test("500 should be a TransientNetworkException") {
val statusCode500Exception =
new IOException("Server returned HTTP response code: 500 for URL:")
assert(TransientNetworkException(statusCode500Exception))
}
test("408 should be a TransientNetworkException") {
val statusCode408Exception =
new IOException("Server returned HTTP response code: 408 for URL:")
assert(TransientNetworkException(statusCode408Exception))
}
test("429 should be a TransientNetworkException") {
val statusCode429Exception =
new IOException(" Server returned HTTP response code: 429 for URL:")
assert(TransientNetworkException(statusCode429Exception))
}
test("404 should not be a TransientNetworkException") {
val statusCode404Exception =
new IOException("Server returned HTTP response code: 404 for URL:")
assert(!TransientNetworkException(statusCode404Exception))
}
test("IllegalArgumentException should not be a TransientNetworkException") {
val illegalArgumentException = new IllegalArgumentException()
assert(!TransientNetworkException(illegalArgumentException))
}
test("it should retry for 3 attempts") {
var i = 0
def f: Int = {
i += 1
if (i < 3) throw new RuntimeException() else i
}
// exception predicate retries on all exceptions for this test
val result = retryWithBackoff(f, _ => true, maxAttempts = 3)
assert(result == 3)
}
test("it should fail after maxAttempts") {
var i = 0
def f: Int = {
i += 1
throw new RuntimeException()
}
intercept[RuntimeException] {
retryWithBackoff(f, _ => true, maxAttempts = 3)
}
assert(i == 3)
}
}
@@ -1,100 +0,0 @@
package sbt.internal.librarymanagement
import sbt.internal.util.ConsoleLogger
import sbt.librarymanagement.MavenRepository
import verify.BasicTestSuite
// http://ant.apache.org/ivy/history/2.3.0/ivyfile/dependency.html
// http://maven.apache.org/enforcer/enforcer-rules/versionRanges.html
object MakePomSpec extends BasicTestSuite {
// This is a specification to check the Ivy revision number conversion to pom.
test("1.0 should convert to 1.0") {
convertTo("1.0", "1.0")
}
test("[1.0,2.0] should convert to [1.0,2.0]") {
convertTo("[1.0,2.0]", "[1.0,2.0]")
}
test("[1.0,2.0[ should convert to [1.0,2.0)") {
convertTo("[1.0,2.0[", "[1.0,2.0)")
}
test("]1.0,2.0] should convert to (1.0,2.0]") {
convertTo("]1.0,2.0]", "(1.0,2.0]")
}
test("]1.0,2.0[ should convert to (1.0,2.0)") {
convertTo("]1.0,2.0[", "(1.0,2.0)")
}
test("[1.0,) should convert to [1.0,)") {
convertTo("[1.0,)", "[1.0,)")
}
test("]1.0,) should convert to (1.0,)") {
convertTo("]1.0,)", "(1.0,)")
}
test("(,2.0] should convert to (,2.0]") {
convertTo("(,2.0]", "(,2.0]")
}
test("(,2.0[ should convert to (,2.0)") {
convertTo("(,2.0[", "(,2.0)")
}
test("1.+ should convert to [1,2)") {
convertTo("1.+", "[1,2)")
}
test("1.2.3.4.+ should convert to [1.2.3.4,1.2.3.5)") {
convertTo("1.2.3.4.+", "[1.2.3.4,1.2.3.5)")
}
test("12.31.42.+ should convert to [12.31.42,12.31.43)") {
convertTo("12.31.42.+", "[12.31.42,12.31.43)")
}
test(
"1.1+ should convert to [1.1,1.2),[1.10,1.20),[1.100,1.200),[1.1000,1.2000),[1.10000,1.20000)"
) {
convertTo("1.1+", "[1.1,1.2),[1.10,1.20),[1.100,1.200),[1.1000,1.2000),[1.10000,1.20000)")
}
test("1+ should convert to [1,2),[10,20),[100,200),[1000,2000),[10000,20000)") {
convertTo("1+", "[1,2),[10,20),[100,200),[1000,2000),[10000,20000)")
}
test("+ should convert to [0,)") {
convertTo("+", "[0,)")
}
test("foo+ should convert to foo+") {
beParsedAsError("foo+")
}
test("repository id should not contain maven illegal repo id characters") {
val repository = mp.mavenRepository(
MavenRepository(
"""repository-id-\with-/illegal:"<-chars>|?*-others~!@#$%^&`';{}[]=+_,.""",
"uri"
)
)
assert(
(repository \ "id").text == "repository-id-with-illegal-chars-others~!@#$%^&`';{}[]=+_,."
)
}
val mp = new MakePom(ConsoleLogger())
def convertTo(s: String, expected: String): Unit = {
assert(MakePom.makeDependencyVersion(s) == expected)
}
def beParsedAsError(s: String): Unit = {
intercept[Throwable] {
MakePom.makeDependencyVersion(s)
()
}
}
}
@@ -1,59 +0,0 @@
package sbt.internal.librarymanagement
import org.apache.ivy.util.Message
import sbt.internal.librarymanagement.ivy.*
import sbt.librarymanagement.*
import sbt.io.IO
object ManagedChecksumsSpec extends BaseIvySpecification {
private final def targetDir = Some(currentDependency)
private final def onlineConf = makeUpdateConfiguration(false, targetDir)
private final def warningConf = UnresolvedWarningConfiguration()
private final val Checksum = "sha1"
def avro177 = ModuleID("org.apache.avro", "avro", "1.7.7")
def dataAvro1940 = ModuleID("com.linkedin.pegasus", "data-avro", "1.9.40")
def netty320 = ModuleID("org.jboss.netty", "netty", "3.2.0.Final")
final def dependencies: Vector[ModuleID] =
Vector(avro177, dataAvro1940, netty320).map(_.withConfigurations(Some("compile")))
import sbt.io.syntax.*
override def mkIvyConfiguration(uo: UpdateOptions): IvyConfiguration = {
val moduleConfs = Vector(ModuleConfiguration("*", chainResolver))
val resCacheDir = currentTarget / "resolution-cache"
InlineIvyConfiguration()
.withPaths(IvyPaths(currentBase.toString, Some(currentTarget.toString)))
.withResolvers(resolvers)
.withModuleConfigurations(moduleConfs)
.withChecksums(Vector(Checksum))
.withResolutionCacheDir(resCacheDir)
.withLog(log)
.withUpdateOptions(uo)
.withManagedChecksums(true)
}
def cleanAll(): Unit = {
cleanIvyCache()
IO.delete(currentTarget)
IO.delete(currentManaged)
IO.delete(currentDependency)
}
def assertChecksumExists(file: File) = {
val shaFile = new File(file.getAbsolutePath + s".$Checksum")
Message.info(s"Checking $shaFile exists...")
assert(shaFile.exists(), s"The checksum $Checksum for $file does not exist")
}
test("Managed checksums should download the checksum files") {
cleanAll()
val updateOptions = UpdateOptions()
val toResolve = module(defaultModuleId, dependencies, None, updateOptions)
val res = IvyActions.updateEither(toResolve, onlineConf, warningConf, log)
assert(res.isRight, s"Resolution with managed checksums failed! $res")
val updateReport = res.fold(e => throw e.resolveException, identity)
val allModuleReports = updateReport.configurations.flatMap(_.modules)
val allArtifacts: Seq[File] = allModuleReports.flatMap(_.artifacts.map(_._2))
allArtifacts.foreach(assertChecksumExists)
}
}
@@ -1,40 +0,0 @@
package sbt.internal.librarymanagement
import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor
import sbt.librarymanagement.*
import sbt.internal.librarymanagement.ivy.UpdateOptions
import sbt.internal.librarymanagement.ivyint.*
object MergeDescriptorSpec extends BaseIvySpecification {
test("Merging duplicate dependencies should work") {
cleanIvyCache()
val m = module(
ModuleID("com.example", "foo", "0.1.0").withConfigurations(Some("compile")),
Vector(guavaTest, guavaTestTests),
None,
UpdateOptions()
)
m.withModule(log) { case (_, md, _) =>
val deps = md.getDependencies
assert(deps.size == 1)
deps.headOption.getOrElse(sys.error("Dependencies not found")) match {
case dd @ MergedDescriptors(_, _) =>
val arts = dd.getAllDependencyArtifacts
val a0: DependencyArtifactDescriptor = arts.toList(0)
val a1: DependencyArtifactDescriptor = arts.toList(1)
val configs0 = a0.getConfigurations.toList
val configs1 = a1.getConfigurations.toList
assert(configs0 == List("compile"))
assert(configs1 == List("test"))
}
}
}
def guavaTest =
ModuleID("com.google.guava", "guava-tests", "18.0").withConfigurations(Option("compile"))
def guavaTestTests =
ModuleID("com.google.guava", "guava-tests", "18.0")
.withConfigurations(Option("test"))
.classifier("tests")
def defaultOptions = EvictionWarningOptions.default
}
@@ -1,74 +0,0 @@
package sbt.internal.librarymanagement
import sbt.librarymanagement.*
import sbt.internal.librarymanagement.ivy.UpdateOptions
import sbt.io.IO
object OfflineModeSpec extends BaseIvySpecification {
private final def targetDir = Some(currentDependency)
private final def onlineConf = makeUpdateConfiguration(false, targetDir)
private final def offlineConf = makeUpdateConfiguration(true, targetDir)
private final def warningConf = UnresolvedWarningConfiguration()
private final def normalOptions = UpdateOptions()
private final def cachedOptions = UpdateOptions().withCachedResolution(true)
def avro177 = ModuleID("org.apache.avro", "avro", "1.7.7")
def dataAvro1940 = ModuleID("com.linkedin.pegasus", "data-avro", "1.9.40")
def netty320 = ModuleID("org.jboss.netty", "netty", "3.2.0.Final")
final def dependencies: Vector[ModuleID] =
Vector(avro177, dataAvro1940, netty320).map(_.withConfigurations(Some("compile")))
def cleanAll(): Unit = {
cleanIvyCache()
IO.delete(currentTarget)
IO.delete(currentManaged)
IO.delete(currentDependency)
}
def checkOnlineAndOfflineResolution(updateOptions: UpdateOptions): Unit = {
cleanAll()
val toResolve = module(defaultModuleId, dependencies, None, updateOptions)
if (updateOptions.cachedResolution)
cleanCachedResolutionCache(toResolve)
val onlineResolution =
IvyActions.updateEither(toResolve, onlineConf, warningConf, log)
assert(onlineResolution.isRight)
assert(onlineResolution.toOption.exists(report => report.stats.resolveTime > 0))
val originalResolveTime =
onlineResolution.fold(e => throw e.resolveException, identity).stats.resolveTime
val offlineResolution =
IvyActions.updateEither(toResolve, offlineConf, warningConf, log)
assert(offlineResolution.isRight)
val resolveTime =
offlineResolution.fold(e => throw e.resolveException, identity).stats.resolveTime
assert(originalResolveTime > resolveTime)
}
test("Offline update configuration should reuse the caches when offline is enabled") {
checkOnlineAndOfflineResolution(normalOptions)
}
test("it should reuse the caches when offline and cached resolution are enabled") {
checkOnlineAndOfflineResolution(cachedOptions)
}
def checkFailingResolution(updateOptions: UpdateOptions): Unit = {
cleanAll()
val toResolve = module(defaultModuleId, dependencies, None, updateOptions)
if (updateOptions.cachedResolution) cleanCachedResolutionCache(toResolve)
val failedOfflineResolution =
IvyActions.updateEither(toResolve, offlineConf, warningConf, log)
assert(failedOfflineResolution.isLeft)
}
test("it should fail when artifacts are missing in the cache") {
checkFailingResolution(normalOptions)
}
test("it should fail when artifacts are missing in the cache for cached resolution") {
checkFailingResolution(cachedOptions)
}
}
@@ -1,92 +0,0 @@
package sbt.internal.librarymanagement
import sbt.librarymanagement.*
import sbt.librarymanagement.syntax.*
import sbt.librarymanagement.Platform.*
object PlatformResolutionSpec extends BaseIvySpecification {
test("None platform resolves %% as JVM") {
cleanCache()
val m = exampleAutoModule(platform = None)
assert(
update(m).configurations.head.modules
.map(_.toString)
.mkString
.contains("com.github.scopt:scopt_2.13:4.1.0")
)
}
test("sjs1 platform resolves %% as sjs1") {
cleanCache()
val m = exampleAutoModule(platform = Some("sjs1"))
assert(
update(m).configurations.head.modules
.map(_.toString)
.mkString
.contains("com.github.scopt:scopt_sjs1_2.13")
)
}
test("sjs1 platform resolves % as JVM") {
cleanCache()
val m = module(
exampleModuleId("0.6.0"),
deps = Vector(junit),
Some(scala2_13),
platform = Some(sjs1),
)
assert(
update(m).configurations.head.modules
.map(_.toString)
.mkString
.contains("junit:junit:4.13.1")
)
}
test("None platform can specify .platform(sjs1) dependency") {
cleanCache()
val m = module(
exampleModuleId("0.6.0"),
deps = Vector(scopt.platform(sjs1)),
Some(scala2_13),
platform = None,
)
assert(
update(m).configurations.head.modules
.map(_.toString)
.mkString
.contains("com.github.scopt:scopt_sjs1_2.13")
)
}
test("sjs1 platform can specify .platform(jvm) dependency") {
cleanCache()
val m = module(
exampleModuleId("0.6.0"),
deps = Vector(scopt.platform(jvm)),
Some(scala2_13),
platform = None,
)
assert(
update(m).configurations.head.modules
.map(_.toString)
.mkString
.contains("com.github.scopt:scopt_2.13:4.1.0")
)
}
def exampleAutoModule(platform: Option[String]): ModuleDescriptor = module(
exampleModuleId("0.6.0"),
deps = Vector(scopt),
Some(scala2_13),
platform = platform,
)
def exampleModuleId(v: String): ModuleID = ("com.example" % "foo" % v % Compile)
def scopt = ("com.github.scopt" %% "scopt" % "4.1.0" % Compile)
def junit = ("junit" % "junit" % "4.13.1" % Compile)
override val resolvers = Vector(
Resolver.mavenCentral,
)
}
@@ -1,117 +0,0 @@
package sbt.internal.librarymanagement
import sbt.util.ShowLines
import sbt.librarymanagement.*
import sbt.librarymanagement.syntax.*
abstract class ResolutionSpec extends AbstractEngineSpec {
import TestShowLines.*
test("Resolving the same module twice should work") {
cleanCache()
val m = module(
exampleModuleId("0.1.0"),
Vector(commonsIo13),
Some("2.10.2")
)
val report = update(m)
cleanCachedResolutionCache(m)
val _ = update(m)
// first resolution creates the minigraph
println(report)
// second resolution reads from the minigraph
println(report.configurations.head.modules.head.artifacts)
assert(report.configurations.size == 3)
}
test("Resolving the unsolvable module should not work") {
// log.setLevel(Level.Debug)
val m = module(
exampleModuleId("0.2.0"),
Vector(mavenCayennePlugin302),
Some("2.10.2")
)
updateEither(m) match {
case Right(_) => sys.error("this should've failed")
case Left(uw) =>
println(uw.lines.mkString("\n"))
}
updateEither(m) match {
case Right(_) => sys.error("this should've failed 2")
case Left(uw) =>
List(
"\n\tNote: Unresolved dependencies path:",
"\t\tfoundrylogic.vpp:vpp:2.2.1",
"\t\t +- org.apache.cayenne:cayenne-tools:3.0.2",
"\t\t +- org.apache.cayenne.plugins:maven-cayenne-plugin:3.0.2",
"\t\t +- com.example:foo:0.2.0"
) foreach { line =>
assert(uw.lines.contains[String](line))
}
}
}
// https://github.com/sbt/sbt/issues/2046
// data-avro:1.9.40 depends on avro:1.4.0, which depends on netty:3.2.1.Final.
// avro:1.4.0 will be evicted by avro:1.7.7.
// #2046 says that netty:3.2.0.Final is incorrectly evicted by netty:3.2.1.Final
test("Resolving a module with a pseudo-conflict should work") {
// log.setLevel(Level.Debug)
cleanCache()
val m = module(
exampleModuleId("0.3.0"),
Vector(avro177, dataAvro1940, netty320),
Some("2.10.2")
)
// first resolution creates the minigraph
val _ = update(m)
cleanCachedResolutionCache(m)
// second resolution reads from the minigraph
val report = update(m)
val modules: Seq[String] = report.configurations.head.modules map { _.toString }
assert(modules exists { (x: String) =>
x.contains("""org.jboss.netty:netty:3.2.0.Final""")
})
assert(!(modules exists { (x: String) =>
x.contains("""org.jboss.netty:netty:3.2.1.Final""")
}))
}
test("Resolving a module with sbt cross build should work") {
cleanCache()
val attributes013 = Map("e:sbtVersion" -> "0.13", "e:scalaVersion" -> "2.10")
val attributes10 = Map("e:sbtVersion" -> "1.0", "e:scalaVersion" -> "2.12")
val module013 = module(
exampleModuleId("0.4.0"),
Vector(sbtRelease.withExtraAttributes(attributes013)),
Some("2.10.6")
)
val module10 = module(
exampleModuleId("0.4.1"),
Vector(sbtRelease.withExtraAttributes(attributes10)),
Some("2.12.3")
)
assert(
update(module013).configurations.head.modules.map(_.toString)
contains "com.github.gseitz:sbt-release:1.0.6 (scalaVersion=2.10, sbtVersion=0.13)"
)
assert(
update(module10).configurations.head.modules.map(_.toString)
contains "com.github.gseitz:sbt-release:1.0.6 (scalaVersion=2.12, sbtVersion=1.0)"
)
}
def exampleModuleId(v: String): ModuleID = ("com.example" % "foo" % v % Compile)
def commonsIo13 = ("commons-io" % "commons-io" % "1.3" % Compile)
def mavenCayennePlugin302 =
("org.apache.cayenne.plugins" % "maven-cayenne-plugin" % "3.0.2" % Compile)
def avro177 = ("org.apache.avro" % "avro" % "1.7.7" % Compile)
def dataAvro1940 =
("com.linkedin.pegasus" % "data-avro" % "1.9.40" % Compile)
def netty320 = ("org.jboss.netty" % "netty" % "3.2.0.Final" % Compile)
def sbtRelease = ("com.github.gseitz" % "sbt-release" % "1.0.6" % Compile)
def defaultOptions = EvictionWarningOptions.default
}
@@ -1,18 +0,0 @@
package sbttest
import java.net.URI
import sbt.librarymanagement.*
import sbt.librarymanagement.syntax.*
import verify.BasicTestSuite
class ResolverSpec extends BasicTestSuite {
test("Resolver.uri") {
Resolver.uri("Test Repo", new URI("http://example.com/"))(using Resolver.ivyStylePatterns)
()
}
test("at") {
"something" at "http://example.com"
()
}
}
@@ -1,276 +0,0 @@
package sbt.internal.librarymanagement
// import org.apache.ivy.core.module.id.ModuleRevisionId
// import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
// import sbt.internal.librarymanagement.IvyScalaUtil.OverrideScalaMediator
// import sbt.librarymanagement._
// import sbt.librarymanagement.ScalaArtifacts._
import verify.BasicTestSuite
object ScalaOverrideTest extends BasicTestSuite {
/*
val OtherOrgID = "other.org"
private val scalaConfigs =
Configurations.default.filter(Configurations.underScalaVersion).map(_.name)
def checkOrgAndVersion(
org0: String,
version0: String
)(org1: String, name1: String, version1: String): Unit = {
val osm = new OverrideScalaMediator(org0, version0, scalaConfigs)
val mrid = ModuleRevisionId.newInstance(org1, name1, version1)
val dd = new DefaultDependencyDescriptor(mrid, false)
dd.addDependencyConfiguration("compile", "compile")
val res = osm.mediate(dd)
assert(res.getDependencyRevisionId == ModuleRevisionId.newInstance(org0, name1, version0))
}
def checkOnlyOrg(
org0: String,
version0: String
)(org1: String, name1: String, version1: String): Unit = {
val osm = new OverrideScalaMediator(org0, version0, scalaConfigs)
val mrid = ModuleRevisionId.newInstance(org1, name1, version1)
val dd = new DefaultDependencyDescriptor(mrid, false)
dd.addDependencyConfiguration("compile", "compile")
val res = osm.mediate(dd)
assert(res.getDependencyRevisionId == ModuleRevisionId.newInstance(org0, name1, version1))
}
def checkNoOverride(
org0: String,
version0: String
)(org1: String, name1: String, version1: String): Unit = {
val osm = new OverrideScalaMediator(org0, version0, scalaConfigs)
val mrid = ModuleRevisionId.newInstance(org1, name1, version1)
val dd = new DefaultDependencyDescriptor(mrid, false)
dd.addDependencyConfiguration("compile", "compile")
val res = osm.mediate(dd)
assert(res.getDependencyRevisionId == mrid)
}
test("OverrideScalaMediator should override compiler version") {
checkOrgAndVersion(Organization, "2.11.8")(
Organization,
CompilerID,
"2.11.9"
)
}
test("it should override library version") {
checkOrgAndVersion(Organization, "2.11.8")(
Organization,
LibraryID,
"2.11.8"
)
}
test("it should override reflect version") {
checkOrgAndVersion(Organization, "2.11.8")(
Organization,
ReflectID,
"2.11.7"
)
}
test("it should override actors version") {
checkOrgAndVersion(Organization, "2.11.8")(
Organization,
ActorsID,
"2.11.6"
)
}
test("it should override scalap version") {
checkOrgAndVersion(Organization, "2.11.8")(
Organization,
ScalapID,
"2.11.5"
)
}
test("it should override default compiler organization") {
checkOrgAndVersion(OtherOrgID, "2.11.8")(
Organization,
CompilerID,
"2.11.9"
)
}
test("it should override default library organization") {
checkOrgAndVersion(OtherOrgID, "2.11.8")(
Organization,
LibraryID,
"2.11.8"
)
}
test("it should override default reflect organization") {
checkOrgAndVersion(OtherOrgID, "2.11.8")(
Organization,
ReflectID,
"2.11.7"
)
}
test("it should override default actors organization") {
checkOrgAndVersion(OtherOrgID, "2.11.8")(
Organization,
ActorsID,
"2.11.6"
)
}
test("it should override default scalap organization") {
checkOrgAndVersion(OtherOrgID, "2.11.8")(
Organization,
ScalapID,
"2.11.5"
)
}
test("it should override custom compiler organization") {
checkOrgAndVersion(Organization, "2.11.8")(
OtherOrgID,
CompilerID,
"2.11.9"
)
}
test("it should override custom library organization") {
checkOrgAndVersion(Organization, "2.11.8")(
OtherOrgID,
LibraryID,
"2.11.8"
)
}
test("it should override custom reflect organization") {
checkOrgAndVersion(Organization, "2.11.8")(
OtherOrgID,
ReflectID,
"2.11.7"
)
}
test("it should override custom actors organization") {
checkOrgAndVersion(Organization, "2.11.8")(
OtherOrgID,
ActorsID,
"2.11.6"
)
}
test("it should override custom scalap organization") {
checkOrgAndVersion(Organization, "2.11.8")(
OtherOrgID,
ScalapID,
"2.11.5"
)
}
test("it should override Scala 3 compiler version") {
checkOrgAndVersion(Organization, "3.1.0")(
Organization,
Scala3CompilerPrefix + "3",
"3.0.0"
)
}
test("it should override Scala 3 library version") {
checkOrgAndVersion(Organization, "3.1.0")(
Organization,
Scala3LibraryPrefix + "3",
"3.0.0"
)
}
test("it should override Scala 3 interfaces version") {
checkOrgAndVersion(Organization, "3.1.0")(
Organization,
Scala3InterfacesID,
"3.0.0"
)
}
test("it should override TASTy core version") {
checkOrgAndVersion(Organization, "3.1.0")(
Organization,
TastyCorePrefix + "3",
"3.0.0"
)
}
test("it should not override Scala 2 library version when using Scala 3") {
checkNoOverride(Organization, "3.1.0")(
Organization,
LibraryID,
"2.13.4"
)
}
test("it should not override TASTy core version when using Scala 2") {
checkNoOverride(Organization, "2.13.4")(
Organization,
TastyCorePrefix + "3",
"3.0.0"
)
}
test("it should override default Scala 3 compiler organization") {
checkOrgAndVersion(OtherOrgID, "3.1.0")(
Organization,
Scala3CompilerPrefix + "3",
"3.0.0"
)
}
test("it should override default Scala 3 library organization") {
checkOrgAndVersion(OtherOrgID, "3.1.0")(
Organization,
Scala3LibraryPrefix + "3",
"3.0.0"
)
}
test("it should override default Scala 3 interfaces organization") {
checkOrgAndVersion(OtherOrgID, "3.1.0")(
Organization,
Scala3InterfacesID,
"3.0.0"
)
}
test("it should override default Scala 3 TASTy core organization") {
checkOrgAndVersion(OtherOrgID, "3.1.0")(
Organization,
TastyCorePrefix + "3",
"3.0.0"
)
}
test("it should override default Scala 2 library organization when in Scala 3") {
checkOnlyOrg(OtherOrgID, "3.1.0")(
Organization,
LibraryID,
"2.13.4"
)
}
test("it should override default TASTy core organization when in Scala 2") {
checkOnlyOrg(OtherOrgID, "2.13.4")(
Organization,
TastyCorePrefix + "3",
"3.0.0"
)
}
*/
}
@@ -1,42 +0,0 @@
package sbt.internal.librarymanagement
import sbt.io.*
import sbt.io.syntax.*
import sbt.util.Level
import sbt.librarymanagement.*
import sbt.librarymanagement.syntax.*
import java.nio.file.Paths
//by default this test is ignored
//to run this you need to change "repo" to point to some sftp repository which contains a dependency referring a dependency in same repo
//it will then attempt to authenticate via key file and fetch the dependency specified via "org" and "module"
object SftpRepoSpec extends BaseIvySpecification {
val repo: Option[String] = None
// val repo: Option[String] = Some("some repo")
// a dependency which depends on another in the repo
def org(repo: String) = s"com.${repo}"
def module(org: String) = org % "some-lib" % "version"
override def resolvers = {
given Patterns = Resolver.defaultIvyPatterns
repo.map { repo =>
val privateKeyFile = Paths.get(sys.env("HOME"), ".ssh", s"id_${repo}").toFile
Resolver.sftp(repo, s"repo.${repo}.com", 2222).as(repo, privateKeyFile)
}.toVector ++ super.resolvers
}
test("resolving multiple deps from sftp repo should not hang or fail") {
repo match {
case Some(repo) =>
IO.delete(currentTarget / "cache" / org(repo))
// log.setLevel(Level.Debug)
lmEngine().retrieve(module(org(repo)), scalaModuleInfo = None, currentTarget, log) match {
case Right(v) => log.debug(v.toString())
case Left(e) =>
log.log(Level.Error, e.failedPaths.toString())
throw e.resolveException
}
case None => log.info(s"skipped ${getClass}")
}
}
}
@@ -1,14 +0,0 @@
package sbt
package internal
package librarymanagement
import sbt.util.*
import sbt.internal.util.*
object TestLogger {
def apply[T](f: Logger => T): T = {
val log = new BufferedLogger(ConsoleLogger())
log.setLevel(Level.Debug)
log.bufferQuietly(f(log))
}
}
@@ -1,9 +0,0 @@
package sbt.internal.librarymanagement
import sbt.util.ShowLines
object TestShowLines:
extension [A: ShowLines](a: A)
inline def lines: Seq[String] =
implicitly[ShowLines[A]].showLines(a)
end TestShowLines
@@ -1,26 +0,0 @@
package sbt.internal.librarymanagement
import sbt.internal.librarymanagement.ivy.*
import verify.BasicTestSuite
class UpdateOptionsSpec extends BasicTestSuite {
test("UpdateOptions should have proper toString defined") {
assert(UpdateOptions().toString() == """|UpdateOptions(
| circularDependencyLevel = warn,
| latestSnapshots = true,
| cachedResolution = false
|)""".stripMargin)
assert(
UpdateOptions()
.withCircularDependencyLevel(CircularDependencyLevel.Error)
.withCachedResolution(true)
.withLatestSnapshots(false)
.toString() == """|UpdateOptions(
| circularDependencyLevel = error,
| latestSnapshots = false,
| cachedResolution = true
|)""".stripMargin
)
}
}
+5 -8
View File
@@ -30,10 +30,7 @@ import sbt.internal.*
import sbt.internal.classpath.AlternativeZincUtil
import sbt.internal.inc.classpath.ClasspathFilter
import sbt.internal.inc.{ CompileOutput, MappedFileConverter, Stamps, ZincLmUtil, ZincUtil }
import sbt.internal.librarymanagement.mavenint.{
PomExtraDependencyAttributes,
SbtPomExtraProperties
}
import sbt.internal.librarymanagement.mavenint.{ PomExtraAttributeKeys, SbtPomExtraProperties }
import sbt.internal.librarymanagement.*
import sbt.internal.nio.{ CheckBuildSources, Globs }
import sbt.internal.server.{
@@ -265,7 +262,7 @@ object Defaults extends BuildCommon with DefExtra {
"bundle",
"maven-plugin",
"test-jar"
) ++ CustomPomParser.JarPackagings,
) ++ PomExtraAttributeKeys.JarPackagings,
artifactClassifier :== None,
checksums := Classpaths.bootChecksums(appConfiguration.value),
conflictManager := ConflictManager.default,
@@ -2622,8 +2619,8 @@ object Defaults extends BuildCommon with DefExtra {
partialVersion(sbtV) match
case Some((0, _)) | Some((1, _)) =>
m.extra(
PomExtraDependencyAttributes.SbtVersionKey -> sbtV,
PomExtraDependencyAttributes.ScalaVersionKey -> scalaV
PomExtraAttributeKeys.SbtVersionKey -> sbtV,
PomExtraAttributeKeys.ScalaVersionKey -> scalaV
).withCrossVersion(Disabled())
case Some(_) =>
// this produces a normal suffix like _sjs1_2.13
@@ -4031,7 +4028,7 @@ object Classpaths {
def deliverTask(config: TaskKey[PublishConfiguration]): Initialize[Task[File]] =
Def.task {
sys.error(
"deliver/makeIvyXml requires the sbt-ivy plugin. Add IvyDependencyPlugin to your project."
"deliver/makeIvyXml requires an Ivy-based publishing plugin, which is not part of this sbt distribution."
)
}
@@ -14,7 +14,6 @@ import scala.xml.{ Elem, Node, NodeSeq }
/**
* Generates Maven POM XML from sbt's own types, without requiring Ivy.
* This is used by the default publisher when the sbt-ivy plugin is not loaded.
*/
private[sbt] object PomGenerator:
@@ -16,7 +16,7 @@ import java.util.regex.Matcher
import gigahorse.AuthScheme
import gigahorse.support.apachehttp.Gigahorse
import sbt.internal.librarymanagement.mavenint.PomExtraDependencyAttributes
import sbt.internal.librarymanagement.mavenint.PomExtraAttributeKeys
import sbt.librarymanagement.*
import sbt.util.Logger
import sbt.io.IO
@@ -170,8 +170,8 @@ class GenericPublisher private[sbt] (
private def pluginCrossPath: Seq[String] =
val attrs = project.module.attributes
attrs.get(PomExtraDependencyAttributes.ScalaVersionKey).map("scala_" + _).toSeq ++
attrs.get(PomExtraDependencyAttributes.SbtVersionKey).map("sbt_" + _).toSeq
attrs.get(PomExtraAttributeKeys.ScalaVersionKey).map("scala_" + _).toSeq ++
attrs.get(PomExtraAttributeKeys.SbtVersionKey).map("sbt_" + _).toSeq
private def typeToFolder(tpe: String): String = tpe match
case "jar" => "jars"
@@ -266,8 +266,8 @@ class GenericPublisher private[sbt] (
else s = s.replace("(-[classifier])", "")
// Substitute or drop optional Ivy pattern parts (scala/sbt version), remove branch for ivyless layout
val attrs = project.module.attributes
val scalaV = attrs.get(PomExtraDependencyAttributes.ScalaVersionKey)
val sbtV = attrs.get(PomExtraDependencyAttributes.SbtVersionKey)
val scalaV = attrs.get(PomExtraAttributeKeys.ScalaVersionKey)
val sbtV = attrs.get(PomExtraAttributeKeys.SbtVersionKey)
s = s.replaceAll(
"\\(scala_[^)]+/\\)",
scalaV.map(v => Matcher.quoteReplacement(s"scala_$v/")).getOrElse("")
-8
View File
@@ -23,10 +23,6 @@ object DatatypeConfig {
"sbt.internal.librarymanagement.formats.NodeSeqFormat" :: Nil
}
case "org.apache.ivy.plugins.resolver.DependencyResolver" => { _ =>
"sbt.internal.librarymanagement.formats.DependencyResolverFormat" :: Nil
}
case "xsbti.GlobalLock" => { _ =>
"sbt.internal.librarymanagement.formats.GlobalLockFormat" :: Nil
}
@@ -38,10 +34,6 @@ object DatatypeConfig {
"sbt.librarymanagement.IvyPathsFormats" :: Nil
}
case "sbt.internal.librarymanagement.ivy.UpdateOptions" => { _ =>
"sbt.internal.librarymanagement.ivy.formats.UpdateOptionsFormat" :: Nil
}
case "sbt.librarymanagement.LogicalClock" => { _ =>
"sbt.internal.librarymanagement.formats.LogicalClockFormats" :: Nil
}
-1
View File
@@ -113,7 +113,6 @@ object Dependencies {
val hedgehog = "qa.hedgehog" %% "hedgehog-sbt" % "0.13.0"
val disruptor = "com.lmax" % "disruptor" % "3.4.2"
val ivy = "org.scala-sbt.ivy" % "ivy" % "2.3.0-sbt-f686954b0021a5c3245766ced0cdaeca8ba2fd7a"
// lm dependencies
val jsch = ("com.github.mwiede" % "jsch" % "0.2.23").intransitive()
-2
View File
@@ -357,8 +357,6 @@ trait Import {
val URLRepository = sbt.librarymanagement.URLRepository
type URLRepository = sbt.librarymanagement.URLRepository
val UpdateLogging = sbt.librarymanagement.UpdateLogging
val UpdateOptions = sbt.internal.librarymanagement.ivy.UpdateOptions
type UpdateOptions = sbt.internal.librarymanagement.ivy.UpdateOptions
val UpdateReport = sbt.librarymanagement.UpdateReport
type UpdateReport = sbt.librarymanagement.UpdateReport
val UpdateStats = sbt.librarymanagement.UpdateStats
@@ -1,5 +1,5 @@
// sbt/sbt#9117: published artifact names must carry the platform suffix, matching the
// coordinate, on both publish backends. `platform` is set directly, as sbt-scala-native does.
// coordinate. `platform` is set directly, as sbt-scala-native does.
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0"
@@ -11,42 +11,35 @@ ThisBuild / mavenRepo := (ThisBuild / baseDirectory).value / "maven-repo"
def expected(name: String) = s"${name}_native0.5_3"
def producer(useIvyFlag: Boolean): Seq[Setting[?]] = Seq(
platform := "native0.5",
crossVersion := CrossVersion.binary,
useIvy := useIvyFlag,
publishMavenStyle := true,
ivyPaths := IvyPaths(baseDirectory.value.toString, Some((target.value / "ivy2").toString)),
publishTo := Some(MavenCache("platform-publish-local", (ThisBuild / mavenRepo).value))
)
// checkPomArtifactId only for ivyless: there the artifactId comes from PomGenerator (and
// resolution does not validate POM content); the Ivy backend's is correct regardless.
def ivyLayoutCheck(base: String, checkPomArtifactId: Boolean): Setting[?] =
TaskKey[Unit]("check") := {
val nm = expected(base)
val dir = target.value / "ivy2" / "local" / organization.value / nm / version.value
def req(f: File): Unit = assert(f.exists, s"expected $f to exist")
req(dir / "jars" / s"$nm.jar")
req(dir / "srcs" / s"$nm-sources.jar")
val pom = dir / "poms" / s"$nm.pom"
req(pom)
if (checkPomArtifactId)
assert(IO.read(pom).contains(s"<artifactId>$nm</artifactId>"), s"POM artifactId must be $nm: ${IO.read(pom)}")
}
lazy val ivyless = (project in file("ivyless"))
.settings(name := "libivyless", producer(false), ivyLayoutCheck("libivyless", checkPomArtifactId = true))
.settings(
name := "libivyless",
platform := "native0.5",
crossVersion := CrossVersion.binary,
useIvy := false,
publishMavenStyle := true,
ivyPaths := IvyPaths(baseDirectory.value.toString, Some((target.value / "ivy2").toString)),
publishTo := Some(MavenCache("platform-publish-local", (ThisBuild / mavenRepo).value)),
TaskKey[Unit]("check") := {
val nm = expected("libivyless")
val dir = target.value / "ivy2" / "local" / organization.value / nm / version.value
def req(f: File): Unit = assert(f.exists, s"expected $f to exist")
req(dir / "jars" / s"$nm.jar")
req(dir / "srcs" / s"$nm-sources.jar")
val pom = dir / "poms" / s"$nm.pom"
req(pom)
assert(
IO.read(pom).contains(s"<artifactId>$nm</artifactId>"),
s"POM artifactId must be $nm: ${IO.read(pom)}"
)
}
)
lazy val ivyfull = (project in file("ivyfull"))
.settings(name := "libivyfull", producer(true), ivyLayoutCheck("libivyfull", checkPomArtifactId = false))
// Must not dependsOn the producers, so the coordinates resolve from the Maven repo rather
// Must not dependsOn the producer, so the coordinate resolves from the Maven repo rather
// than inter-project - otherwise a suffix-dropped published name would not be caught.
lazy val consumer = (project in file("consumer"))
.settings(
publish / skip := true,
resolvers += MavenCache("platform-publish-local", (ThisBuild / mavenRepo).value),
libraryDependencies += organization.value % expected("libivyless") % version.value,
libraryDependencies += organization.value % expected("libivyfull") % version.value
libraryDependencies += organization.value % expected("libivyless") % version.value
)
@@ -1,4 +0,0 @@
package lib
object Lib:
def greeting: String = "hi"
@@ -1,6 +0,0 @@
// sbt-ivy provides the Ivy publish backend exercised by the `ivyfull` project.
libraryDependencies += Defaults.sbtPluginExtra(
"org.scala-sbt" % "sbt-ivy" % sbtVersion.value,
sbtVersion.value,
scalaVersion.value,
)
@@ -1,14 +1,11 @@
# sbt/sbt#9117: published artifact names must carry the platform suffix (_native0.5),
# matching the coordinate, on both the ivyless and Ivy backends.
# matching the coordinate.
# publishLocal (ivy layout): filenames + POM artifactId
> ivyless/publishLocal
> ivyless/check
> ivyfull/publishLocal
> ivyfull/check
# publish to a local Maven repo, then resolve the artifacts back under their platform
# publish to a local Maven repo, then resolve the artifact back under its platform
# coordinate
> ivyless/publish
> ivyfull/publish
> consumer/update
@@ -63,14 +63,17 @@ lazy val root = (project in file("."))
"org.scala-lang:scala3-library_3",
"org.scala-lang:tasty-core_3",
"org.scala-sbt.ipcsocket:ipcsocket",
"org.scala-sbt.ivy:ivy",
"org.scala-sbt.jline:jline",
"org.scala-sbt.gson:shaded-gson",
"org.slf4j:slf4j-api",
)
def assertCollectionsEqual(message: String, expected: Seq[String], actual: Seq[String]): Unit =
// using the new line for a more readable comparison failure output
assert(expected.mkString("\n") == actual.mkString("\n"), message + ": " + actual)
val diff = ((expected.toVector diff actual.toVector)
.map("-" + _) ++
(actual.toVector diff expected.toVector).map("+" + _))
.mkString("\n")
assert(expected.mkString("\n") == actual.mkString("\n"), message + ": " + diff)
assertCollectionsEqual(
"Unexpected module ids in updateSbtClassifiers",
@@ -1,3 +0,0 @@
object A {
val x = B.x
}
@@ -1,3 +0,0 @@
object B {
val x = 3
}
@@ -1,4 +0,0 @@
ThisBuild / useIvy := true
lazy val root = (project in file("."))
lazy val a = project
lazy val b = project
@@ -1,5 +0,0 @@
Global / buildDependencies :=
(Global / buildDependencies).value.addClasspath(
(LocalProject("a") / thisProjectRef).value,
ResolvedClasspathDependency(thisProjectRef.value, None)
)
@@ -1,5 +0,0 @@
libraryDependencies += Defaults.sbtPluginExtra(
"org.scala-sbt" % "sbt-ivy" % sbtVersion.value,
sbtVersion.value,
scalaVersion.value,
)

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