mirror of
https://github.com/sbt/sbt.git
synced 2026-09-04 08:45:56 +02:00
[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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
+14
@@ -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
|
||||
Generated
-66
@@ -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)
|
||||
}
|
||||
-37
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
-82
@@ -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))
|
||||
}
|
||||
-45
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
-21
@@ -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 {
|
||||
|
||||
}
|
||||
Generated
-11
@@ -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")
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
-135
@@ -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 <dependencyManagement> 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]"
|
||||
}
|
||||
-33
@@ -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")
|
||||
}
|
||||
-32
@@ -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)))
|
||||
}
|
||||
-17
@@ -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
|
||||
)
|
||||
}
|
||||
-63
@@ -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
|
||||
)
|
||||
}
|
||||
-1029
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
-130
@@ -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
|
||||
|
||||
}
|
||||
-71
@@ -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
|
||||
}
|
||||
-114
@@ -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
|
||||
}
|
||||
}
|
||||
-10
@@ -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
|
||||
}
|
||||
-133
@@ -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>
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
-15
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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("")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
-4
@@ -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
Reference in New Issue
Block a user