diff --git a/lm-ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala b/lm-ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala
index b0f6ebc2f..9581449ad 100644
--- a/lm-ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala
+++ b/lm-ivy/src/main/scala/sbt/internal/librarymanagement/IvyActions.scala
@@ -96,7 +96,70 @@ object IvyActions {
val options = DeliverOptions.newInstance(ivy.getSettings).setStatus(status)
options.setConfs(getConfigurations(md, configuration.configurations))
ivy.deliver(revID, revID.getRevision, deliverIvyPattern, options)
- deliveredFile(ivy, deliverIvyPattern, md)
+ 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 =
+ 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) =>
+ // Build new attributes by replacing 'rev' attribute value
+ 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
+ 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
}
}
diff --git a/lm-ivy/src/test/scala/sbt/internal/librarymanagement/IvyActionsOverrideSpec.scala b/lm-ivy/src/test/scala/sbt/internal/librarymanagement/IvyActionsOverrideSpec.scala
new file mode 100644
index 000000000..5dc5e7d57
--- /dev/null
+++ b/lm-ivy/src/test/scala/sbt/internal/librarymanagement/IvyActionsOverrideSpec.scala
@@ -0,0 +1,178 @@
+package sbt.internal.librarymanagement
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class IvyActionsOverrideSpec extends AnyFlatSpec with Matchers {
+
+ "applyOverridesToDeliveredIvy XML transformation" should "replace rev attribute for matching dependencies" in {
+ // Simulate the override map
+ val overrideMap = Map(("org.slf4j", "slf4j-api") -> "2.0.16")
+
+ // Sample Ivy XML with "managed" version (as reported in issue #7951)
+ val sampleXml =
+
+
+
+
+
+
+
+
+ // Apply the same transformation logic as in IvyActions
+ val updated =
+ 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) =>
+ // Build new attributes by replacing 'rev' attribute value
+ 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(sampleXml).head
+
+ // Verify the transformation
+ val dependencies = (updated \\ "dependency")
+
+ // Check slf4j-api has overridden version
+ val slf4jDep = dependencies.find(d => (d \ "@org").text == "org.slf4j")
+ slf4jDep shouldBe defined
+ (slf4jDep.get \ "@rev").text shouldBe "2.0.16"
+ (slf4jDep.get \ "@name").text shouldBe "slf4j-api"
+ (slf4jDep.get \ "@conf").text shouldBe "compile->default(compile)"
+
+ // Check other-lib is unchanged
+ val otherDep = dependencies.find(d => (d \ "@org").text == "other.org")
+ otherDep shouldBe defined
+ (otherDep.get \ "@rev").text shouldBe "1.0.0"
+ (otherDep.get \ "@name").text shouldBe "other-lib"
+ }
+
+ it should "preserve all attributes when replacing rev" in {
+ val overrideMap = Map(("org.example", "test-lib") -> "3.0.0")
+
+ val sampleXml =
+
+
+
+
+
+
+ val updated =
+ 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(sampleXml).head
+
+ val dep = (updated \\ "dependency").head
+ (dep \ "@org").text shouldBe "org.example"
+ (dep \ "@name").text shouldBe "test-lib"
+ (dep \ "@rev").text shouldBe "3.0.0"
+ (dep \ "@conf").text shouldBe "compile->default"
+ (dep \ "@transitive").text shouldBe "false"
+ (dep \ "@force").text shouldBe "true"
+ }
+
+ it should "not modify dependencies without overrides" in {
+ val overrideMap = Map(("org.other", "other-lib") -> "2.0.0")
+
+ val sampleXml =
+
+
+
+
+
+
+ val updated =
+ 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(sampleXml).head
+
+ val dep = (updated \\ "dependency").head
+ (dep \ "@rev").text shouldBe "1.0.0" // Should remain unchanged
+ }
+}
diff --git a/main/src/main/scala/sbt/internal/GCMonitor.scala b/main/src/main/scala/sbt/internal/GCMonitor.scala
index 79b49af02..f39b8a4ff 100644
--- a/main/src/main/scala/sbt/internal/GCMonitor.scala
+++ b/main/src/main/scala/sbt/internal/GCMonitor.scala
@@ -62,8 +62,10 @@ class GCMonitor(logger: Logger) extends GCMonitorBase with AutoCloseable {
override protected def emitWarning(total: Long, over: Option[Long]): Unit = {
val totalSeconds = total / 1000.0
- val amountMsg = over.fold(s"$totalSeconds seconds") { d =>
- "In the last " + (d / 1000.0).ceil.toInt + f" seconds, $totalSeconds (${total.toDouble / d * 100}%.1f%%)"
+ val amountMsg = over.fold(f"$totalSeconds%.3f seconds") { d =>
+ val dSeconds = (d / 1000.0).ceil.toInt
+ val percentage = total.toDouble / d * 100
+ f"In the last $dSeconds seconds, $totalSeconds%.3f seconds ($percentage%.1f%%) of GC pause"
}
val msg = s"$amountMsg were spent in GC. " +
s"[Heap: ${gbString(runtime.freeMemory())} free " +