From 4f06b90534c35b662ff84ce0dbf8ec20c56a8ca2 Mon Sep 17 00:00:00 2001 From: BrianHotopp Date: Tue, 31 Mar 2026 01:58:57 -0400 Subject: [PATCH] [2.x] fix: Write maven-metadata-local.xml during publishM2 for SNAPSHOTs (#8996) publishM2 never wrote maven-metadata-local.xml, which Maven uses to distinguish local installs from remote artifacts. Without it, Maven re-downloads remote SNAPSHOTs even when a local copy exists, making publishM2 effectively broken for SNAPSHOT workflows. Fixes #2053 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../sbt/internal/LibraryManagement.scala | 29 +++++++++++++++++++ .../publishm2-metadata/build.sbt | 21 ++++++++++++++ .../publishm2-metadata/test | 2 ++ 3 files changed, 52 insertions(+) create mode 100644 sbt-app/src/sbt-test/dependency-management/publishm2-metadata/build.sbt create mode 100644 sbt-app/src/sbt-test/dependency-management/publishm2-metadata/test diff --git a/main/src/main/scala/sbt/internal/LibraryManagement.scala b/main/src/main/scala/sbt/internal/LibraryManagement.scala index 57e542aaf..d911e09eb 100644 --- a/main/src/main/scala/sbt/internal/LibraryManagement.scala +++ b/main/src/main/scala/sbt/internal/LibraryManagement.scala @@ -872,6 +872,35 @@ private[sbt] object LibraryManagement { writeChecksumsForFile(targetFile, checksumAlgorithms, log) else log.warn(s"$targetFile already exists, skipping (overwrite=$overwrite)") + if version.endsWith("-SNAPSHOT") then + writeMavenMetadataLocal(versionDir, groupId, artifactId, version, log) + + private def writeMavenMetadataLocal( + versionDir: File, + groupId: String, + artifactId: String, + version: String, + log: Logger + ): Unit = + val timestamp = new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date()) + val metadata = + s"""| + | + | $groupId + | $artifactId + | $version + | + | + | true + | + | $timestamp + | + | + |""".stripMargin + val metadataFile = new File(versionDir, "maven-metadata-local.xml") + IO.write(metadataFile, metadata) + log.info(s"Published $metadataFile") + /** * Publishes artifacts to a remote Maven repo (HTTP) without using Apache Ivy. * Same layout as ivylessPublishMavenToFile; uses HTTP PUT with optional Basic auth. diff --git a/sbt-app/src/sbt-test/dependency-management/publishm2-metadata/build.sbt b/sbt-app/src/sbt-test/dependency-management/publishm2-metadata/build.sbt new file mode 100644 index 000000000..221eed115 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/publishm2-metadata/build.sbt @@ -0,0 +1,21 @@ +@transient +lazy val checkMetadata = taskKey[Unit]("check maven-metadata-local.xml is written for SNAPSHOT") + +lazy val root = (project in file(".")) + .settings( + scalaVersion := "2.13.16", + organization := "com.example.test.scripted", + version := "0.1.0-SNAPSHOT", + name := "publishm2-metadata-test", + checkMetadata := { + val m2Dir = new File( + System.getProperty("user.home"), + ".m2/repository/com/example/test/scripted/publishm2-metadata-test_2.13/0.1.0-SNAPSHOT" + ) + val metadataFile = new File(m2Dir, "maven-metadata-local.xml") + assert(metadataFile.exists, s"maven-metadata-local.xml not found at $metadataFile") + val content = IO.read(metadataFile) + assert(content.contains("true"), s"Missing localCopy element in:\n$content") + assert(content.contains("com.example.test.scripted"), s"Wrong groupId in:\n$content") + }, + ) diff --git a/sbt-app/src/sbt-test/dependency-management/publishm2-metadata/test b/sbt-app/src/sbt-test/dependency-management/publishm2-metadata/test new file mode 100644 index 000000000..edf905720 --- /dev/null +++ b/sbt-app/src/sbt-test/dependency-management/publishm2-metadata/test @@ -0,0 +1,2 @@ +> publishM2 +> checkMetadata