Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding sonatype central support #3130

Merged
merged 10 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ object Deps {
val fansi = ivy"com.lihaoyi::fansi:0.5.0"
val jarjarabrams = ivy"com.eed3si9n.jarjarabrams::jarjar-abrams-core:1.14.0"
val requests = ivy"com.lihaoyi::requests:0.8.2"
val sonatypeCentralClient = ivy"com.lumidion::sonatype-central-client-requests:0.2.0"

/** Used to manage transitive versions. */
val transitiveDeps = Seq(
Expand Down Expand Up @@ -1008,6 +1009,12 @@ object contrib extends Module {
def ivyDeps = Agg(Deps.requests)
}


object sonatypecentral extends ContribModule {
def compileModuleDeps = Seq(scalalib)
def ivyDeps = Agg(Deps.sonatypeCentralClient)
}

object versionfile extends ContribModule {
def compileModuleDeps = Seq(scalalib)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package mill.contrib.artifactory
import mill._
import mill.api.Result
import scalalib._
import publish.Artifact
import mill.contrib.artifactory.ArtifactoryPublishModule.checkArtifactoryCreds
import mill.define.{ExternalModule, Task}

Expand Down Expand Up @@ -62,8 +61,8 @@ object ArtifactoryPublishModule extends ExternalModule {
connectTimeout: Int = 5000
) = T.command {

val x: Seq[(Seq[(os.Path, String)], Artifact)] = T.sequence(publishArtifacts.value)().map {
case PublishModule.PublishData(a, s) => (s.map { case (p, f) => (p.path, f) }, a)
val artifacts = T.sequence(publishArtifacts.value)().map {
case data @ PublishModule.PublishData(_, _) => data.withConcretePath
}
new ArtifactoryPublisher(
artifactoryUri,
Expand All @@ -73,7 +72,7 @@ object ArtifactoryPublishModule extends ExternalModule {
connectTimeout,
T.log
).publishAll(
x: _*
artifacts: _*
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mill.contrib.codeartifact

import mill._, scalalib._, define.ExternalModule, publish.Artifact
import mill._
import scalalib._
import define.ExternalModule

trait CodeartifactPublishModule extends PublishModule {
def codeartifactUri: String
Expand Down Expand Up @@ -40,12 +42,9 @@ object CodeartifactPublishModule extends ExternalModule {
connectTimeout: Int = 5000
) =
T.command {

val x: Seq[(Seq[(os.Path, String)], Artifact)] =
T.sequence(publishArtifacts.value)().map {
case PublishModule.PublishData(a, s) =>
(s.map { case (p, f) => (p.path, f) }, a)
}
val artifacts = T.sequence(publishArtifacts.value)().map {
case data @ PublishModule.PublishData(_, _) => data.withConcretePath
}
new CodeartifactPublisher(
codeartifactUri,
codeartifactSnapshotUri,
Expand All @@ -54,7 +53,7 @@ object CodeartifactPublishModule extends ExternalModule {
connectTimeout,
T.log
).publishAll(
x: _*
artifacts: _*
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import mill._
import mill.api.Result.{Failure, Success}
import mill.api.Result
import mill.define.{Command, ExternalModule, Task}
import mill.scalalib.publish.Artifact
import scalalib._

trait GitlabPublishModule extends PublishModule { outer =>
Expand Down Expand Up @@ -63,11 +62,9 @@ object GitlabPublishModule extends ExternalModule {
val repo = ProjectRepository(gitlabRoot, projectId)
val auth = GitlabAuthHeaders.privateToken(personalToken)

val artifacts: Seq[(Seq[(os.Path, String)], Artifact)] =
T.sequence(publishArtifacts.value)().map {
case PublishModule.PublishData(a, s) => (s.map { case (p, f) => (p.path, f) }, a)
}

val artifacts = T.sequence(publishArtifacts.value)().map {
case data @ PublishModule.PublishData(_, _) => data.withConcretePath
}
val uploader = new GitlabUploader(auth, readTimeout, connectTimeout)

new GitlabPublisher(
Expand Down
88 changes: 88 additions & 0 deletions contrib/sonatypecentral/readme.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
= Sonatype Central
:page-aliases: Plugin_Sonatype_Central.adoc

This plugin allows users to publish open-source packages to Maven Central via the Sonatype Central portal.

== Quickstart
Add the following to your `build.sc`:
[source,scala]
----
import $ivy.`com.lihaoyi::mill-contrib-sonatypecentral:`
import mill.contrib.sonatypecentral.SonatypeCentralPublishModule

object mymodule extends SonatypeCentralPublishModule {
...
}
----

Then run the following to publish the individual module:

----
$ mill mymodule.publishSonatypeCentral
----

To publish several modules at once, run the following, with arguments adjusted for your use case:

----
$ mill -i \
mill.contrib.sonatypecentral.SonatypeCentralPublishModule/publishAll \
--username myusername \
--password mypassword \
--gpgArgs --passphrase=$GPG_PASSPHRASE,--no-tty,--pinentry-mode,loopback,--batch,--yes,-a,-b \
--publishArtifacts __.publishArtifacts \
--readTimeout 36000 \
--awaitTimeout 36000 \
--connectTimeout 36000 \
--shouldRelease false \
--bundleName com.lihaoyi-requests:1.0.0
----


=== Module Settings
Below are the default publishing settings on the module level, which can be explicitly configured by users like so:

[source,scala]
----
object mymodule extends SonatypeCentralPublishModule {
override def gpgArgs: T[String] = "--batch, --yes, -a, -b"

override def connectTimeout: T[Int] = 5000

override def readTimeout: T[Int] = 60000

override def awaitTimeout: T[Int] = 120 * 1000

override def shouldRelease: T[Boolean] = true
...
}
----

=== Argument Reference

==== publishAll

The `mill.contrib.sonatypecentral.SonatypeCentralPublishModule/publishAll` method takes the following arguments:

`username`: The username for calling the Sonatype Central publishing api. Defaults to the `SONATYPE_USERNAME` environment variable if unset. If neither the parameter nor the environment variable are set, an error will be thrown. +

`password`: The password for calling the Sonatype Central publishing api. Defaults to the `SONATYPE_PASSWORD` environment variable if unset. If neither the parameter nor the environment variable are set, an error will be thrown. +

`gpgArgs`: Arguments to pass to the gpg package for signing artifacts. _Default: `--batch, --yes, -a, -b`._ +

`publishArtifacts`: The command for generating all publishable artifacts (ex. `__.publishArtifacts`). Required. +

`readTimeout`: The timeout for receiving a response from Sonatype Central after the initial connection has occurred. _Default: 60000._ +

`awaitTimeout`: The overall timeout for all retries (including exponential backoff) of the bundle upload. _Default: 120 * 1000._ +

`connectTimeout`: The timeout for the initial connection to Sonatype Central if there is no response. _Default: 5000._ +

`shouldRelease`: Whether the bundle should be automatically released when uploaded to Sonatype Central. If `false`, the bundle will still be uploaded, but users will need to manually log in to Sonatype Central and publish the bundle from the portal. _Default: true_ +

`bundleName`: If set, all packages will be uploaded in a single bundle with the given name. If unset, packages will be uploaded separately. Recommended bundle name syntax: groupName-artifactId-versionNumber. As an example, if publishing the `com.lihaoyi` `requests` package, without the bundle name, four different bundles will be uploaded, one for each scala version supported. With a bundle name of `com.lihaoyi-requests-<new_version>`, a single bundle will be uploaded that contains all packages across scala versions. It is recommended to set the bundle name, so that packages can be verified and deployed together. _Default: No bundle name is set and packages will be uploaded separately_

==== publishSonatypeCentral

The `__.publishSonatypeCentral` command takes the `username` and `password` arguments, documented above.

`
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package mill.contrib.sonatypecentral

import com.lumidion.sonatype.central.client.core.{PublishingType, SonatypeCredentials}
import mill._
import scalalib._
import define.{ExternalModule, Task}
import mill.api.Result
import mill.contrib.sonatypecentral.SonatypeCentralPublishModule.{
defaultAwaitTimeout,
defaultConnectTimeout,
defaultCredentials,
defaultReadTimeout,
getPublishingTypeFromReleaseFlag,
getSonatypeCredentials
}
import mill.scalalib.PublishModule.{defaultGpgArgs, getFinalGpgArgs}
import mill.scalalib.publish.Artifact
import mill.scalalib.publish.SonatypeHelpers.{
PASSWORD_ENV_VARIABLE_NAME,
USERNAME_ENV_VARIABLE_NAME
}

trait SonatypeCentralPublishModule extends PublishModule {
def sonatypeCentralGpgArgs: T[String] = T { defaultGpgArgs.mkString(",") }

def sonatypeCentralConnectTimeout: T[Int] = T { defaultConnectTimeout }

def sonatypeCentralReadTimeout: T[Int] = T { defaultReadTimeout }

def sonatypeCentralAwaitTimeout: T[Int] = T { defaultAwaitTimeout }

def sonatypeCentralShouldRelease: T[Boolean] = T { true }

def publishSonatypeCentral(
username: String = defaultCredentials,
password: String = defaultCredentials
): define.Command[Unit] =
T.command {
val publishData = publishArtifacts()
val fileMapping = publishData.withConcretePath._1
val artifact = publishData.meta
val finalCredentials = getSonatypeCredentials(username, password)()

val publisher = new SonatypeCentralPublisher(
credentials = finalCredentials,
gpgArgs = getFinalGpgArgs(sonatypeCentralGpgArgs()),
connectTimeout = sonatypeCentralConnectTimeout(),
readTimeout = sonatypeCentralReadTimeout(),
log = T.log,
workspace = T.workspace,
env = T.env,
awaitTimeout = sonatypeCentralAwaitTimeout()
)
publisher.publish(
fileMapping,
artifact,
getPublishingTypeFromReleaseFlag(sonatypeCentralShouldRelease())
)
}
}

object SonatypeCentralPublishModule extends ExternalModule {

val defaultCredentials: String = ""
val defaultReadTimeout: Int = 60000
val defaultConnectTimeout: Int = 5000
val defaultAwaitTimeout: Int = 120 * 1000
val defaultShouldRelease: Boolean = true

def publishAll(
publishArtifacts: mill.main.Tasks[PublishModule.PublishData],
username: String = defaultCredentials,
password: String = defaultCredentials,
shouldRelease: Boolean = defaultShouldRelease,
gpgArgs: String = defaultGpgArgs.mkString(","),
readTimeout: Int = defaultReadTimeout,
connectTimeout: Int = defaultConnectTimeout,
awaitTimeout: Int = defaultAwaitTimeout,
bundleName: String = ""
): Command[Unit] = T.command {

val artifacts: Seq[(Seq[(os.Path, String)], Artifact)] =
T.sequence(publishArtifacts.value)().map {
case data @ PublishModule.PublishData(_, _) => data.withConcretePath
}

val finalBundleName = if (bundleName.isEmpty) None else Some(bundleName)
val finalCredentials = getSonatypeCredentials(username, password)()

val publisher = new SonatypeCentralPublisher(
credentials = finalCredentials,
gpgArgs = getFinalGpgArgs(gpgArgs),
connectTimeout = connectTimeout,
readTimeout = readTimeout,
log = T.log,
workspace = T.workspace,
env = T.env,
awaitTimeout = awaitTimeout
)
publisher.publishAll(
getPublishingTypeFromReleaseFlag(shouldRelease),
finalBundleName,
artifacts: _*
)
}

private def getPublishingTypeFromReleaseFlag(shouldRelease: Boolean): PublishingType = {
if (shouldRelease) {
PublishingType.AUTOMATIC
} else {
PublishingType.USER_MANAGED
}
}

private def getSonatypeCredential(
credentialParameterValue: String,
credentialName: String,
envVariableName: String
): Task[String] = T.task {
if (credentialParameterValue.nonEmpty) {
Result.Success(credentialParameterValue)
} else {
(for {
credential <- T.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] = T.task {
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.define.Discover[this.type] = mill.define.Discover[this.type]
}
Loading