From e660027b8d6cc2b264cd29fa228e0227ba64ea3e Mon Sep 17 00:00:00 2001 From: Roman Langolf Date: Mon, 13 Jul 2026 07:10:28 +0000 Subject: [PATCH 1/2] Port ArtifactRegistryIvyRepository/ArtifactRegistryIvyResolver to Scala 3 / sbt 2 The scala-2.12 (sbt 1.x) source set had ArtifactRegistryIvyRepository/ ArtifactRegistryIvyResolver, using a custom Ivy repository that PUTs via the Google HTTP client directly. This was never ported to the scala-3 (sbt 2.x) source set, so publishTo on sbt 2 falls back to Ivy's stock URLRepository, which only supports the http protocol for PUT and throws UnsupportedOperationException for artifactregistry://. --- .../gar/ArtifactRegistryIvyRepository.scala | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 modules/sbt/src/main/scala-3/dev/rolang/sbt/gar/ArtifactRegistryIvyRepository.scala diff --git a/modules/sbt/src/main/scala-3/dev/rolang/sbt/gar/ArtifactRegistryIvyRepository.scala b/modules/sbt/src/main/scala-3/dev/rolang/sbt/gar/ArtifactRegistryIvyRepository.scala new file mode 100644 index 0000000..620c139 --- /dev/null +++ b/modules/sbt/src/main/scala-3/dev/rolang/sbt/gar/ArtifactRegistryIvyRepository.scala @@ -0,0 +1,70 @@ +package dev.rolang.sbt.gar + +import com.google.api.client.http.{ByteArrayContent, GenericUrl, HttpRequestFactory} +import dev.rolang.gar.{ArtifactRegistryUrlHandlerFactory, Logger} +import org.apache.ivy.core.module.descriptor.Artifact +import org.apache.ivy.plugins.repository.{AbstractRepository, Resource} +import org.apache.ivy.plugins.repository.url.URLResource +import org.apache.ivy.plugins.resolver.IBiblioResolver +import org.apache.ivy.util.Message +import sbt.librarymanagement.{RawRepository, Resolver} + +import java.io.File +import java.net.URI +import java.nio.file.{Files, StandardCopyOption} + +class ArtifactRegistryIvyRepository(logger: Logger) extends AbstractRepository { + + private lazy val requestFactory: HttpRequestFactory = + ArtifactRegistryUrlHandlerFactory.createRequestFactory(logger) + + override def getName: String = "ArtifactRegistry" + + override def getResource(source: String): Resource = + new URLResource(URI.create(source).toURL) + + override def get(source: String, destination: File): Unit = { + val response = requestFactory.buildGetRequest(toHttpsUrl(source)).execute() + val is = response.getContent + try Files.copy(is, destination.toPath, StandardCopyOption.REPLACE_EXISTING) + finally is.close() + } + + override def put(artifact: Artifact, src: File, destination: String, overwrite: Boolean): Unit = { + logger.info(s"Uploading artifact to: $destination") + val bytes = Files.readAllBytes(src.toPath) + requestFactory + .buildPutRequest(toHttpsUrl(destination), new ByteArrayContent(null, bytes)) + .execute() + } + + override def list(parent: String): java.util.List[String] = + java.util.Collections.emptyList() + + private def toHttpsUrl(raw: String): GenericUrl = { + val url = URI.create(raw).toURL + val g = new GenericUrl() + g.setScheme("https") + g.setHost(url.getHost) + g.appendRawPath(url.getPath) + g + } +} + +object ArtifactRegistryIvyResolver { + + def create(name: String, root: String): Resolver = { + val logger = new Logger { + def info(msg: String): Unit = Message.info(msg) + def error(msg: String): Unit = Message.error(msg) + def debug(msg: String): Unit = Message.debug(msg) + } + val resolver = new IBiblioResolver + resolver.setName(name) + resolver.setRoot(root) + resolver.setM2compatible(true) + resolver.setUseMavenMetadata(true) + resolver.setRepository(new ArtifactRegistryIvyRepository(logger)) + new RawRepository(resolver, name) + } +} From 3c8cf39aaa62a3a12a77d6c95b53664e37b9b75d Mon Sep 17 00:00:00 2001 From: Roman Langolf Date: Mon, 13 Jul 2026 07:23:12 +0000 Subject: [PATCH 2/2] Wire ArtifactRegistryIvyResolver into scala-3 GarCompat publishTo mapping Restores parity with the scala-2.12 (sbt 1.x) source set: a plain artifactregistry:// publishTo is now automatically wrapped with ArtifactRegistryIvyResolver, so it goes through the Google HTTP client PUT instead of Ivy's stock URLRepository, which only supports http. --- modules/sbt/src/main/scala-3/dev/rolang/sbt/gar/GarCompat.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sbt/src/main/scala-3/dev/rolang/sbt/gar/GarCompat.scala b/modules/sbt/src/main/scala-3/dev/rolang/sbt/gar/GarCompat.scala index aa7581a..d83239f 100644 --- a/modules/sbt/src/main/scala-3/dev/rolang/sbt/gar/GarCompat.scala +++ b/modules/sbt/src/main/scala-3/dev/rolang/sbt/gar/GarCompat.scala @@ -29,7 +29,7 @@ object GarCompat: }, publishTo := publishTo.value.map { case m: sbt.librarymanagement.MavenRepository if m.root.startsWith("artifactregistry://") => - m + ArtifactRegistryIvyResolver.create(m.name, m.root) case other => other } )