Skip to content

Commit 0f0d814

Browse files
authored
Put back sonatype contrib module in 0.12.x (#5119)
This was deleted as part of #5107, but although we don't promise compatibility we should try to preserve it on a best effort basis
1 parent f9f243b commit 0f0d814

File tree

4 files changed

+292
-1
lines changed

4 files changed

+292
-1
lines changed

contrib/sonatypecentral/readme.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
= Sonatype Central (Plugin Moved to Scalalib)
1+
= Sonatype Central
22
:page-aliases: Plugin_Sonatype_Central.adoc
33

44
This plugin allows users to publish open-source packages to Maven Central via the Sonatype Central portal.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package mill.contrib.sonatypecentral
2+
3+
import com.lumidion.sonatype.central.client.core.{PublishingType, SonatypeCredentials}
4+
import mill._
5+
import scalalib._
6+
import define.{ExternalModule, Task}
7+
import mill.api.Result
8+
import mill.contrib.sonatypecentral.SonatypeCentralPublishModule.{
9+
defaultAwaitTimeout,
10+
defaultConnectTimeout,
11+
defaultCredentials,
12+
defaultReadTimeout,
13+
getPublishingTypeFromReleaseFlag,
14+
getSonatypeCredentials
15+
}
16+
import mill.scalalib.publish.Artifact
17+
import mill.scalalib.publish.SonatypeHelpers.{
18+
PASSWORD_ENV_VARIABLE_NAME,
19+
USERNAME_ENV_VARIABLE_NAME
20+
}
21+
22+
trait SonatypeCentralPublishModule extends PublishModule {
23+
def sonatypeCentralGpgArgs: T[String] = Task {
24+
PublishModule.defaultGpgArgsForPassphrase(Task.env.get("MILL_PGP_PASSPHRASE")).mkString(",")
25+
}
26+
27+
def sonatypeCentralConnectTimeout: T[Int] = Task { defaultConnectTimeout }
28+
29+
def sonatypeCentralReadTimeout: T[Int] = Task { defaultReadTimeout }
30+
31+
def sonatypeCentralAwaitTimeout: T[Int] = Task { defaultAwaitTimeout }
32+
33+
def sonatypeCentralShouldRelease: T[Boolean] = Task { true }
34+
35+
def publishSonatypeCentral(
36+
username: String = defaultCredentials,
37+
password: String = defaultCredentials
38+
): define.Command[Unit] =
39+
Task.Command {
40+
val publishData = publishArtifacts()
41+
val fileMapping = publishData.withConcretePath._1
42+
val artifact = publishData.meta
43+
val finalCredentials = getSonatypeCredentials(username, password)()
44+
PublishModule.pgpImportSecretIfProvided(Task.env)
45+
val publisher = new SonatypeCentralPublisher(
46+
credentials = finalCredentials,
47+
gpgArgs = sonatypeCentralGpgArgs().split(",").toIndexedSeq,
48+
connectTimeout = sonatypeCentralConnectTimeout(),
49+
readTimeout = sonatypeCentralReadTimeout(),
50+
log = Task.log,
51+
workspace = Task.workspace,
52+
env = Task.env,
53+
awaitTimeout = sonatypeCentralAwaitTimeout()
54+
)
55+
publisher.publish(
56+
fileMapping,
57+
artifact,
58+
getPublishingTypeFromReleaseFlag(sonatypeCentralShouldRelease())
59+
)
60+
}
61+
}
62+
63+
object SonatypeCentralPublishModule extends ExternalModule {
64+
65+
val defaultCredentials: String = ""
66+
val defaultReadTimeout: Int = 60000
67+
val defaultConnectTimeout: Int = 5000
68+
val defaultAwaitTimeout: Int = 120 * 1000
69+
val defaultShouldRelease: Boolean = true
70+
71+
def publishAll(
72+
publishArtifacts: mill.main.Tasks[PublishModule.PublishData],
73+
username: String = defaultCredentials,
74+
password: String = defaultCredentials,
75+
shouldRelease: Boolean = defaultShouldRelease,
76+
gpgArgs: String = "",
77+
readTimeout: Int = defaultReadTimeout,
78+
connectTimeout: Int = defaultConnectTimeout,
79+
awaitTimeout: Int = defaultAwaitTimeout,
80+
bundleName: String = ""
81+
): Command[Unit] = Task.Command {
82+
83+
val artifacts: Seq[(Seq[(os.Path, String)], Artifact)] =
84+
Task.sequence(publishArtifacts.value)().map {
85+
case data @ PublishModule.PublishData(_, _) => data.withConcretePath
86+
}
87+
88+
val finalBundleName = if (bundleName.isEmpty) None else Some(bundleName)
89+
val finalCredentials = getSonatypeCredentials(username, password)()
90+
PublishModule.pgpImportSecretIfProvided(Task.env)
91+
val publisher = new SonatypeCentralPublisher(
92+
credentials = finalCredentials,
93+
gpgArgs = gpgArgs match {
94+
case "" => PublishModule.defaultGpgArgsForPassphrase(Task.env.get("MILL_PGP_PASSPHRASE"))
95+
case gpgArgs => gpgArgs.split(",").toIndexedSeq
96+
},
97+
connectTimeout = connectTimeout,
98+
readTimeout = readTimeout,
99+
log = Task.log,
100+
workspace = Task.workspace,
101+
env = Task.env,
102+
awaitTimeout = awaitTimeout
103+
)
104+
publisher.publishAll(
105+
getPublishingTypeFromReleaseFlag(shouldRelease),
106+
finalBundleName,
107+
artifacts: _*
108+
)
109+
}
110+
111+
private def getPublishingTypeFromReleaseFlag(shouldRelease: Boolean): PublishingType = {
112+
if (shouldRelease) {
113+
PublishingType.AUTOMATIC
114+
} else {
115+
PublishingType.USER_MANAGED
116+
}
117+
}
118+
119+
private def getSonatypeCredential(
120+
credentialParameterValue: String,
121+
credentialName: String,
122+
envVariableName: String
123+
): Task[String] = Task.Anon {
124+
if (credentialParameterValue.nonEmpty) {
125+
Result.Success(credentialParameterValue)
126+
} else {
127+
(for {
128+
credential <- Task.env.get(envVariableName)
129+
} yield {
130+
Result.Success(credential)
131+
}).getOrElse(
132+
Result.Failure(
133+
s"No $credentialName set. Consider using the $envVariableName environment variable or passing `$credentialName` argument"
134+
)
135+
)
136+
}
137+
}
138+
139+
private def getSonatypeCredentials(
140+
usernameParameterValue: String,
141+
passwordParameterValue: String
142+
): Task[SonatypeCredentials] = Task.Anon {
143+
val username =
144+
getSonatypeCredential(usernameParameterValue, "username", USERNAME_ENV_VARIABLE_NAME)()
145+
val password =
146+
getSonatypeCredential(passwordParameterValue, "password", PASSWORD_ENV_VARIABLE_NAME)()
147+
Result.Success(SonatypeCredentials(username, password))
148+
}
149+
150+
lazy val millDiscover: mill.define.Discover = mill.define.Discover[this.type]
151+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package mill.contrib.sonatypecentral
2+
3+
import com.lumidion.sonatype.central.client.core.{
4+
DeploymentName,
5+
PublishingType,
6+
SonatypeCredentials
7+
}
8+
import com.lumidion.sonatype.central.client.requests.SyncSonatypeClient
9+
import mill.api.Logger
10+
import mill.scalalib.publish.Artifact
11+
import mill.scalalib.publish.SonatypeHelpers.getArtifactMappings
12+
13+
import java.nio.file.Files
14+
import java.util.jar.JarOutputStream
15+
import java.util.zip.ZipEntry
16+
17+
class SonatypeCentralPublisher(
18+
credentials: SonatypeCredentials,
19+
gpgArgs: Seq[String],
20+
readTimeout: Int,
21+
connectTimeout: Int,
22+
log: Logger,
23+
workspace: os.Path,
24+
env: Map[String, String],
25+
awaitTimeout: Int
26+
) {
27+
private val sonatypeCentralClient =
28+
new SyncSonatypeClient(credentials, readTimeout = readTimeout, connectTimeout = connectTimeout)
29+
30+
def publish(
31+
fileMapping: Seq[(os.Path, String)],
32+
artifact: Artifact,
33+
publishingType: PublishingType
34+
): Unit = {
35+
publishAll(publishingType, None, fileMapping -> artifact)
36+
}
37+
38+
def publishAll(
39+
publishingType: PublishingType,
40+
singleBundleName: Option[String],
41+
artifacts: (Seq[(os.Path, String)], Artifact)*
42+
): Unit = {
43+
val mappings = getArtifactMappings(isSigned = true, gpgArgs, workspace, env, artifacts)
44+
45+
val (_, releases) = mappings.partition(_._1.isSnapshot)
46+
47+
val releaseGroups = releases.groupBy(_._1.group)
48+
val wd = os.pwd / "out/publish-central"
49+
os.makeDir.all(wd)
50+
51+
singleBundleName.fold {
52+
for ((_, groupReleases) <- releaseGroups) {
53+
groupReleases.foreach { case (artifact, data) =>
54+
val fileNameWithoutExtension = s"${artifact.group}-${artifact.id}-${artifact.version}"
55+
val zipFile = streamToFile(fileNameWithoutExtension, wd) { outputStream =>
56+
zipFilesToJar(data, outputStream)
57+
}
58+
59+
val deploymentName = DeploymentName.fromArtifact(
60+
artifact.group,
61+
artifact.id,
62+
artifact.version
63+
)
64+
65+
publishFile(zipFile, deploymentName, publishingType)
66+
}
67+
}
68+
69+
} { singleBundleName =>
70+
val zipFile = streamToFile(singleBundleName, wd) { outputStream =>
71+
for ((_, groupReleases) <- releaseGroups) {
72+
groupReleases.foreach { case (_, data) =>
73+
zipFilesToJar(data, outputStream)
74+
}
75+
}
76+
}
77+
78+
val deploymentName = DeploymentName(singleBundleName)
79+
80+
publishFile(zipFile, deploymentName, publishingType)
81+
}
82+
}
83+
84+
private def publishFile(
85+
zipFile: java.io.File,
86+
deploymentName: DeploymentName,
87+
publishingType: PublishingType
88+
): Unit = {
89+
try {
90+
sonatypeCentralClient.uploadBundleFromFile(
91+
zipFile,
92+
deploymentName,
93+
Some(publishingType),
94+
timeout = awaitTimeout
95+
)
96+
} catch {
97+
case ex: Throwable => {
98+
throw new RuntimeException(
99+
s"Failed to publish ${deploymentName.unapply} to Sonatype Central. Error: \n${ex.getMessage}"
100+
)
101+
}
102+
103+
}
104+
105+
log.info(s"Successfully published ${deploymentName.unapply} to Sonatype Central")
106+
}
107+
108+
private def streamToFile(
109+
fileNameWithoutExtension: String,
110+
wd: os.Path
111+
)(func: JarOutputStream => Unit): java.io.File = {
112+
val zipFile =
113+
(wd / s"$fileNameWithoutExtension.zip")
114+
val fileOutputStream = Files.newOutputStream(zipFile.toNIO)
115+
val jarOutputStream = new JarOutputStream(fileOutputStream)
116+
try {
117+
func(jarOutputStream)
118+
} finally {
119+
jarOutputStream.close()
120+
}
121+
zipFile.toIO
122+
}
123+
124+
private def zipFilesToJar(
125+
files: Seq[(String, Array[Byte])],
126+
jarOutputStream: JarOutputStream
127+
): Unit = {
128+
files.foreach { case (filename, fileAsBytes) =>
129+
val zipEntry = new ZipEntry(filename)
130+
jarOutputStream.putNextEntry(zipEntry)
131+
jarOutputStream.write(fileAsBytes)
132+
jarOutputStream.closeEntry()
133+
}
134+
}
135+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package mill.contrib.sonatypecentral
2+
3+
import mill.define.ExternalModule
4+
5+
object `package` extends ExternalModule.Alias(SonatypeCentralPublishModule)

0 commit comments

Comments
 (0)