From 46074e6a2bf7fcc50c82e3dadbaea0a7b35f9208 Mon Sep 17 00:00:00 2001 From: Max Streese Date: Fri, 24 Oct 2025 14:32:20 +0200 Subject: [PATCH 1/4] Add SonatypeCredentialsModule, MavenPublsh and MavenPublishModule --- .../src/mill/javalib/MavenPublish.scala | 91 +++++++++++++++ .../src/mill/javalib/MavenPublishModule.scala | 73 ++++++++++++ .../SonatypeCentralPublishModule.scala | 107 +++++------------- .../javalib/SonatypeCredentialsModule.scala | 39 +++++++ 4 files changed, 231 insertions(+), 79 deletions(-) create mode 100644 libs/javalib/src/mill/javalib/MavenPublish.scala create mode 100644 libs/javalib/src/mill/javalib/MavenPublishModule.scala create mode 100644 libs/javalib/src/mill/javalib/SonatypeCredentialsModule.scala diff --git a/libs/javalib/src/mill/javalib/MavenPublish.scala b/libs/javalib/src/mill/javalib/MavenPublish.scala new file mode 100644 index 000000000000..c0b89291b4b3 --- /dev/null +++ b/libs/javalib/src/mill/javalib/MavenPublish.scala @@ -0,0 +1,91 @@ +package mill.javalib + +import com.lumidion.sonatype.central.client.core.SonatypeCredentials +import mill.api.daemon.Logger +import mill.javalib.PublishModule.PublishData +import mill.javalib.internal.MavenWorkerSupport as InternalMavenWorkerSupport + +private[mill] trait MavenPublish { + + def mavenPublishDatas( + publishDatas: Seq[PublishData], + bundleName: Option[String], + credentials: SonatypeCredentials, + releaseUri: String, + snapshotUri: String, + taskDest: os.Path, + log: Logger, + env: Map[String, String], + worker: InternalMavenWorkerSupport.Api, + ): Unit = { + val dryRun = env.get("MILL_TESTS_PUBLISH_DRY_RUN").contains("1") + + val (snapshots, releases) = publishDatas.partition(_.meta.isSnapshot) + + bundleName.filter(_ => snapshots.nonEmpty).foreach { bundleName => + throw new IllegalArgumentException( + s"Publishing SNAPSHOT versions when bundle name ($bundleName) is specified is not supported.\n\n" + + s"SNAPSHOT versions: ${pprint.apply(snapshots)}" + ) + } + + releases.map(_ -> false).appendedAll(snapshots.map(_ -> true)).foreach { (data, isSnapshot) => + mavenPublishData( + dryRun = dryRun, + publishData = data, + isSnapshot = isSnapshot, + credentials = credentials, + releaseUri = releaseUri, + snapshotUri = snapshotUri, + taskDest = taskDest, + log = log, + worker = worker + ) + } + } + + def mavenPublishData( + dryRun: Boolean, + publishData: PublishData, + isSnapshot: Boolean, + credentials: SonatypeCredentials, + releaseUri: String, + snapshotUri: String, + taskDest: os.Path, + log: Logger, + worker: InternalMavenWorkerSupport.Api + ): Unit = { + val uri = if (isSnapshot) releaseUri else snapshotUri + val artifacts = MavenWorkerSupport.RemoteM2Publisher.asM2ArtifactsFromPublishDatas( + publishData.meta, + publishData.payloadAsMap + ) + + if (isSnapshot) { + log.info(s"Detected a 'SNAPSHOT' version for ${publishData.meta}, publishing to Maven Repository at '$uri'") + } + + /** Maven uses this as a workspace for file manipulation. */ + val mavenWorkspace = taskDest / "maven" + + if (dryRun) { + val publishTo = taskDest / "repository" + val result = worker.publishToLocal( + publishTo = publishTo, + workspace = mavenWorkspace, + artifacts + ) + log.info(s"Dry-run publishing to '$publishTo' finished with result: $result") + } else { + val result = worker.publishToRemote( + uri = uri, + workspace = mavenWorkspace, + username = credentials.username, + password = credentials.password, + artifacts + ) + log.info(s"Publishing to '$uri' finished with result: $result") + } + } + +} \ No newline at end of file diff --git a/libs/javalib/src/mill/javalib/MavenPublishModule.scala b/libs/javalib/src/mill/javalib/MavenPublishModule.scala new file mode 100644 index 000000000000..588d35a5c6c3 --- /dev/null +++ b/libs/javalib/src/mill/javalib/MavenPublishModule.scala @@ -0,0 +1,73 @@ +package mill.javalib + +import com.lihaoyi.unroll +import mill.* +import mill.api.* +import mill.javalib.PublishModule.PublishData +import mill.util.Tasks + +trait MavenPublishModule extends PublishModule, MavenWorkerSupport, SonatypeCredentialsModule, MavenPublish { + + def mavenReleaseUri: T[String] + + def mavenSnapshotUri: T[String] + + def publishMaven( + username: String = "", + password: String = "", + @unroll sources: Boolean = true, + @unroll docs: Boolean = true + ): Task.Command[Unit] = Task.Command { + val artifact = artifactMetadata() + val credentials = getSonatypeCredentials(username, password)() + val publishData = publishArtifactsPayload(sources = sources, docs = docs)() + + mavenPublishDatas( + Seq(PublishData(artifact, publishData)), + bundleName = None, + credentials, + releaseUri = mavenReleaseUri(), + snapshotUri = mavenSnapshotUri(), + taskDest = Task.dest, + log = Task.log, + env = Task.env, + worker = mavenWorker() + ) + } + +} + +object MavenPublishModule extends ExternalModule, DefaultTaskModule, MavenWorkerSupport, SonatypeCredentialsModule, MavenPublish { + + def defaultTask(): String = "publishAll" + + def publishAll( + publishArtifacts: mill.util.Tasks[PublishModule.PublishData] = + Tasks.resolveMainDefault("__:PublishModule.publishArtifacts"), + username: String = "", + password: String = "", + bundleName: String = "", + releaseUri: String, + snapshotUri: String + ): Command[Unit] = Task.Command { + val artifacts = Task.sequence(publishArtifacts.value)() + + val finalBundleName = if (bundleName.isEmpty) None else Some(bundleName) + val credentials = getSonatypeCredentials(username, password)() + + mavenPublishDatas( + artifacts, + finalBundleName, + credentials, + releaseUri = releaseUri, + snapshotUri = snapshotUri, + taskDest = Task.dest, + log = Task.log, + env = Task.env, + worker = mavenWorker() + ) + } + + lazy val millDiscover: Discover = Discover[this.type] + +} \ No newline at end of file diff --git a/libs/javalib/src/mill/javalib/SonatypeCentralPublishModule.scala b/libs/javalib/src/mill/javalib/SonatypeCentralPublishModule.scala index 5101ffa72f7b..fdb1d1db4fc2 100644 --- a/libs/javalib/src/mill/javalib/SonatypeCentralPublishModule.scala +++ b/libs/javalib/src/mill/javalib/SonatypeCentralPublishModule.scala @@ -1,29 +1,29 @@ package mill.javalib import com.lihaoyi.unroll -import com.lumidion.sonatype.central.client.core.{PublishingType, SonatypeCredentials} +import com.lumidion.sonatype.central.client.core.PublishingType +import com.lumidion.sonatype.central.client.core.SonatypeCredentials import mill.* -import javalib.* -import mill.api.{ExternalModule, Task} -import mill.util.Tasks +import mill.api.BuildCtx import mill.api.DefaultTaskModule +import mill.api.ExternalModule import mill.api.Result -import mill.javalib.SonatypeCentralPublishModule.{ - defaultAwaitTimeout, - defaultConnectTimeout, - defaultCredentials, - defaultReadTimeout, - getPublishingTypeFromReleaseFlag, - getSonatypeCredentials -} -import mill.javalib.publish.Artifact -import mill.javalib.publish.SonatypeHelpers.{PASSWORD_ENV_VARIABLE_NAME, USERNAME_ENV_VARIABLE_NAME} -import mill.api.BuildCtx +import mill.api.Task import mill.api.daemon.Logger import mill.javalib.PublishModule.PublishData +import mill.javalib.SonatypeCentralPublishModule.defaultAwaitTimeout +import mill.javalib.SonatypeCentralPublishModule.defaultConnectTimeout +import mill.javalib.SonatypeCentralPublishModule.defaultCredentials +import mill.javalib.SonatypeCentralPublishModule.defaultReadTimeout +import mill.javalib.SonatypeCentralPublishModule.getPublishingTypeFromReleaseFlag import mill.javalib.internal.PublishModule.GpgArgs +import mill.javalib.publish.Artifact +import mill.util.Tasks -trait SonatypeCentralPublishModule extends PublishModule, MavenWorkerSupport { +import javalib.* + +trait SonatypeCentralPublishModule extends PublishModule, MavenWorkerSupport, + SonatypeCredentialsModule { @deprecated("Use `sonatypeCentralGpgArgsForKey` instead.", "Mill 1.0.1") def sonatypeCentralGpgArgs: T[String] = @@ -97,8 +97,8 @@ trait SonatypeCentralPublishModule extends PublishModule, MavenWorkerSupport { /** * External module to publish artifacts to `central.sonatype.org` */ -object SonatypeCentralPublishModule extends ExternalModule with DefaultTaskModule - with MavenWorkerSupport { +object SonatypeCentralPublishModule extends ExternalModule, DefaultTaskModule, MavenWorkerSupport, + SonatypeCredentialsModule, MavenPublish { private final val sonatypeCentralGpgArgsSentinelValue = "" def self = this @@ -169,37 +169,17 @@ object SonatypeCentralPublishModule extends ExternalModule with DefaultTaskModul val dryRun = env.get("MILL_TESTS_PUBLISH_DRY_RUN").contains("1") def publishSnapshot(publishData: PublishData): Unit = { - val uri = sonatypeCentralSnapshotUri - val artifacts = MavenWorkerSupport.RemoteM2Publisher.asM2ArtifactsFromPublishDatas( - publishData.meta, - publishData.payloadAsMap - ) - - log.info( - s"Detected a 'SNAPSHOT' version for ${publishData.meta}, publishing to Sonatype Central Snapshots at '$uri'" + mavenPublishData( + dryRun = dryRun, + publishData = publishData, + isSnapshot = true, + credentials = credentials, + releaseUri = sonatypeCentralSnapshotUri, + snapshotUri = sonatypeCentralSnapshotUri, + taskDest = taskDest, + log = log, + worker = worker ) - - /** Maven uses this as a workspace for file manipulation. */ - val mavenWorkspace = taskDest / "maven" - - if (dryRun) { - val publishTo = taskDest / "repository" - val result = worker.publishToLocal( - publishTo = publishTo, - workspace = mavenWorkspace, - artifacts - ) - log.info(s"Dry-run publishing to '$publishTo' finished with result: $result") - } else { - val result = worker.publishToRemote( - uri = uri, - workspace = mavenWorkspace, - username = credentials.username, - password = credentials.password, - artifacts - ) - log.info(s"Publishing to '$uri' finished with result: $result") - } } def publishReleases(artifacts: Seq[PublishData], gpgArgs: GpgArgs): Unit = { @@ -258,36 +238,5 @@ object SonatypeCentralPublishModule extends ExternalModule with DefaultTaskModul } } - private def getSonatypeCredential( - credentialParameterValue: String, - credentialName: String, - envVariableName: String - ): Task[String] = Task.Anon { - if (credentialParameterValue.nonEmpty) { - Result.Success(credentialParameterValue) - } else { - (for { - credential <- Task.env.get(envVariableName) - } yield { - Result.Success(credential) - }).getOrElse( - Result.Failure( - s"No $credentialName set. Consider using the $envVariableName environment variable or passing `$credentialName` argument" - ) - ) - } - } - - private def getSonatypeCredentials( - usernameParameterValue: String, - passwordParameterValue: String - ): Task[SonatypeCredentials] = Task.Anon { - val username = - getSonatypeCredential(usernameParameterValue, "username", USERNAME_ENV_VARIABLE_NAME)() - val password = - getSonatypeCredential(passwordParameterValue, "password", PASSWORD_ENV_VARIABLE_NAME)() - Result.Success(SonatypeCredentials(username, password)) - } - lazy val millDiscover: mill.api.Discover = mill.api.Discover[this.type] } diff --git a/libs/javalib/src/mill/javalib/SonatypeCredentialsModule.scala b/libs/javalib/src/mill/javalib/SonatypeCredentialsModule.scala new file mode 100644 index 000000000000..ca5f52509d97 --- /dev/null +++ b/libs/javalib/src/mill/javalib/SonatypeCredentialsModule.scala @@ -0,0 +1,39 @@ +package mill.javalib + +import com.lumidion.sonatype.central.client.core.SonatypeCredentials +import mill.api.* +import mill.javalib.publish.SonatypeHelpers.PASSWORD_ENV_VARIABLE_NAME +import mill.javalib.publish.SonatypeHelpers.USERNAME_ENV_VARIABLE_NAME + +private[mill] trait SonatypeCredentialsModule extends Module { + def getSonatypeCredential( + credentialParameterValue: String, + credentialName: String, + envVariableName: String + ): Task[String] = Task.Anon { + if (credentialParameterValue.nonEmpty) { + Result.Success(credentialParameterValue) + } else { + (for { + credential <- Task.env.get(envVariableName) + } yield { + Result.Success(credential) + }).getOrElse( + Result.Failure( + s"No $credentialName set. Consider using the $envVariableName environment variable or passing `$credentialName` argument" + ) + ) + } + } + + def getSonatypeCredentials( + usernameParameterValue: String, + passwordParameterValue: String + ): Task[SonatypeCredentials] = Task.Anon { + val username = + getSonatypeCredential(usernameParameterValue, "username", USERNAME_ENV_VARIABLE_NAME)() + val password = + getSonatypeCredential(passwordParameterValue, "password", PASSWORD_ENV_VARIABLE_NAME)() + Result.Success(SonatypeCredentials(username, password)) + } +} \ No newline at end of file From 9d9ef3ef18705ff089cf1b376c94583fab381830 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 12:58:22 +0000 Subject: [PATCH 2/4] [autofix.ci] apply automated fixes --- libs/javalib/src/mill/javalib/MavenPublish.scala | 8 +++++--- libs/javalib/src/mill/javalib/MavenPublishModule.scala | 8 +++++--- .../src/mill/javalib/SonatypeCredentialsModule.scala | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/libs/javalib/src/mill/javalib/MavenPublish.scala b/libs/javalib/src/mill/javalib/MavenPublish.scala index c0b89291b4b3..6823acabb0f4 100644 --- a/libs/javalib/src/mill/javalib/MavenPublish.scala +++ b/libs/javalib/src/mill/javalib/MavenPublish.scala @@ -16,7 +16,7 @@ private[mill] trait MavenPublish { taskDest: os.Path, log: Logger, env: Map[String, String], - worker: InternalMavenWorkerSupport.Api, + worker: InternalMavenWorkerSupport.Api ): Unit = { val dryRun = env.get("MILL_TESTS_PUBLISH_DRY_RUN").contains("1") @@ -62,7 +62,9 @@ private[mill] trait MavenPublish { ) if (isSnapshot) { - log.info(s"Detected a 'SNAPSHOT' version for ${publishData.meta}, publishing to Maven Repository at '$uri'") + log.info( + s"Detected a 'SNAPSHOT' version for ${publishData.meta}, publishing to Maven Repository at '$uri'" + ) } /** Maven uses this as a workspace for file manipulation. */ @@ -88,4 +90,4 @@ private[mill] trait MavenPublish { } } -} \ No newline at end of file +} diff --git a/libs/javalib/src/mill/javalib/MavenPublishModule.scala b/libs/javalib/src/mill/javalib/MavenPublishModule.scala index 588d35a5c6c3..1bcb67fc60c0 100644 --- a/libs/javalib/src/mill/javalib/MavenPublishModule.scala +++ b/libs/javalib/src/mill/javalib/MavenPublishModule.scala @@ -6,7 +6,8 @@ import mill.api.* import mill.javalib.PublishModule.PublishData import mill.util.Tasks -trait MavenPublishModule extends PublishModule, MavenWorkerSupport, SonatypeCredentialsModule, MavenPublish { +trait MavenPublishModule extends PublishModule, MavenWorkerSupport, SonatypeCredentialsModule, + MavenPublish { def mavenReleaseUri: T[String] @@ -37,7 +38,8 @@ trait MavenPublishModule extends PublishModule, MavenWorkerSupport, SonatypeCred } -object MavenPublishModule extends ExternalModule, DefaultTaskModule, MavenWorkerSupport, SonatypeCredentialsModule, MavenPublish { +object MavenPublishModule extends ExternalModule, DefaultTaskModule, MavenWorkerSupport, + SonatypeCredentialsModule, MavenPublish { def defaultTask(): String = "publishAll" @@ -70,4 +72,4 @@ object MavenPublishModule extends ExternalModule, DefaultTaskModule, MavenWorker lazy val millDiscover: Discover = Discover[this.type] -} \ No newline at end of file +} diff --git a/libs/javalib/src/mill/javalib/SonatypeCredentialsModule.scala b/libs/javalib/src/mill/javalib/SonatypeCredentialsModule.scala index ca5f52509d97..a149deef7eb0 100644 --- a/libs/javalib/src/mill/javalib/SonatypeCredentialsModule.scala +++ b/libs/javalib/src/mill/javalib/SonatypeCredentialsModule.scala @@ -36,4 +36,4 @@ private[mill] trait SonatypeCredentialsModule extends Module { getSonatypeCredential(passwordParameterValue, "password", PASSWORD_ENV_VARIABLE_NAME)() Result.Success(SonatypeCredentials(username, password)) } -} \ No newline at end of file +} From f08e1b8812561536478b64b11879c9f577507c2b Mon Sep 17 00:00:00 2001 From: Max Streese Date: Fri, 24 Oct 2025 15:40:49 +0200 Subject: [PATCH 3/4] Add scalalib alias and export for MavenPublishModule --- libs/scalalib/src/mill/scalalib/aliases.scala | 2 ++ libs/scalalib/src/mill/scalalib/exports.scala | 2 ++ 2 files changed, 4 insertions(+) diff --git a/libs/scalalib/src/mill/scalalib/aliases.scala b/libs/scalalib/src/mill/scalalib/aliases.scala index cabb2e381c5e..5fc2458b4fcd 100644 --- a/libs/scalalib/src/mill/scalalib/aliases.scala +++ b/libs/scalalib/src/mill/scalalib/aliases.scala @@ -1,5 +1,7 @@ package mill.scalalib object Dependency extends mill.api.ExternalModule.Alias(mill.javalib.Dependency) +object MavenPublishModule + extends mill.api.ExternalModule.Alias(mill.javalib.MavenPublishModule) object SonatypeCentralPublishModule extends mill.api.ExternalModule.Alias(mill.javalib.SonatypeCentralPublishModule) diff --git a/libs/scalalib/src/mill/scalalib/exports.scala b/libs/scalalib/src/mill/scalalib/exports.scala index 8a255b58f1c7..0ea573fb9706 100644 --- a/libs/scalalib/src/mill/scalalib/exports.scala +++ b/libs/scalalib/src/mill/scalalib/exports.scala @@ -44,6 +44,8 @@ export mill.javalib.RunModule export mill.javalib.SonatypeCentralPublisher +type MavenPublishModule = mill.javalib.MavenPublishModule + type SonatypeCentralPublishModule = mill.javalib.SonatypeCentralPublishModule export mill.javalib.TestModule From 2f01ebd56bebd5713a35f1bcd1e069d3cef60b1e Mon Sep 17 00:00:00 2001 From: Max Streese Date: Sun, 26 Oct 2025 15:38:53 +0100 Subject: [PATCH 4/4] Fix a bug in choosing between snapshot and release uri --- libs/javalib/src/mill/javalib/MavenPublish.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/javalib/src/mill/javalib/MavenPublish.scala b/libs/javalib/src/mill/javalib/MavenPublish.scala index 6823acabb0f4..f13b4f9e73f1 100644 --- a/libs/javalib/src/mill/javalib/MavenPublish.scala +++ b/libs/javalib/src/mill/javalib/MavenPublish.scala @@ -55,7 +55,7 @@ private[mill] trait MavenPublish { log: Logger, worker: InternalMavenWorkerSupport.Api ): Unit = { - val uri = if (isSnapshot) releaseUri else snapshotUri + val uri = if (isSnapshot) snapshotUri else releaseUri val artifacts = MavenWorkerSupport.RemoteM2Publisher.asM2ArtifactsFromPublishDatas( publishData.meta, publishData.payloadAsMap