summaryrefslogtreecommitdiff
path: root/scalalib/src/publish/SonatypePublisher.scala
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-12-12 16:56:02 -0800
committerGitHub <noreply@github.com>2018-12-12 16:56:02 -0800
commit9ba4cb69331386dfde9bac69dc2d5b22401face3 (patch)
tree120349e8015ae5717d36bd44209cde6ff9543518 /scalalib/src/publish/SonatypePublisher.scala
parentea7fceb6e56f53bde3517586dfc57e10a605a524 (diff)
downloadmill-9ba4cb69331386dfde9bac69dc2d5b22401face3.tar.gz
mill-9ba4cb69331386dfde9bac69dc2d5b22401face3.tar.bz2
mill-9ba4cb69331386dfde9bac69dc2d5b22401face3.zip
collapse boilerplate folder structure within src/ folders (#505)
* collapse boilerplate folder structure within src/ folders * .
Diffstat (limited to 'scalalib/src/publish/SonatypePublisher.scala')
-rw-r--r--scalalib/src/publish/SonatypePublisher.scala164
1 files changed, 164 insertions, 0 deletions
diff --git a/scalalib/src/publish/SonatypePublisher.scala b/scalalib/src/publish/SonatypePublisher.scala
new file mode 100644
index 00000000..1843943b
--- /dev/null
+++ b/scalalib/src/publish/SonatypePublisher.scala
@@ -0,0 +1,164 @@
+package mill.scalalib.publish
+
+import java.math.BigInteger
+import java.security.MessageDigest
+
+import mill.api.Logger
+
+import scalaj.http.HttpResponse
+
+class SonatypePublisher(uri: String,
+ snapshotUri: String,
+ credentials: String,
+ gpgPassphrase: Option[String],
+ signed: Boolean,
+ log: Logger) {
+
+ private val api = new SonatypeHttpApi(uri, credentials)
+
+ def publish(fileMapping: Seq[(os.Path, String)], artifact: Artifact, release: Boolean): Unit = {
+ publishAll(release, fileMapping -> artifact)
+ }
+ def publishAll(release: Boolean, artifacts: (Seq[(os.Path, String)], Artifact)*): Unit = {
+
+ val mappings = for ((fileMapping0, artifact) <- artifacts) yield {
+ val publishPath = Seq(
+ artifact.group.replace(".", "/"),
+ artifact.id,
+ artifact.version
+ ).mkString("/")
+ val fileMapping = fileMapping0.map{ case (file, name) => (file, publishPath+"/"+name) }
+
+ val signedArtifacts = if (signed) fileMapping.map {
+ case (file, name) => poorMansSign(file, gpgPassphrase) -> s"$name.asc"
+ } else Seq()
+
+ artifact -> (fileMapping ++ signedArtifacts).flatMap {
+ case (file, name) =>
+ val content = os.read.bytes(file)
+
+ Seq(
+ name -> content,
+ (name + ".md5") -> md5hex(content),
+ (name + ".sha1") -> sha1hex(content)
+ )
+ }
+ }
+
+ val (snapshots, releases) = mappings.partition(_._1.isSnapshot)
+ if(snapshots.nonEmpty) {
+ publishSnapshot(snapshots.flatMap(_._2), snapshots.map(_._1))
+ }
+ val releaseGroups = releases.groupBy(_._1.group)
+ for((group, groupReleases) <- releaseGroups){
+ publishRelease(release, groupReleases.flatMap(_._2), group, releases.map(_._1))
+ }
+ }
+
+ private def publishSnapshot(payloads: Seq[(String, Array[Byte])],
+ artifacts: Seq[Artifact]): Unit = {
+
+ val publishResults = payloads.map {
+ case (fileName, data) =>
+ log.info(s"Uploading $fileName")
+ val resp = api.upload(s"$snapshotUri/$fileName", data)
+ resp
+ }
+ reportPublishResults(publishResults, artifacts)
+ }
+
+ private def publishRelease(release: Boolean,
+ payloads: Seq[(String, Array[Byte])],
+ stagingProfile: String,
+ artifacts: Seq[Artifact]): Unit = {
+ val profileUri = api.getStagingProfileUri(stagingProfile)
+ val stagingRepoId =
+ api.createStagingRepo(profileUri, stagingProfile)
+ val baseUri = s"$uri/staging/deployByRepositoryId/$stagingRepoId/"
+
+ val publishResults = payloads.map {
+ case (fileName, data) =>
+ log.info(s"Uploading ${fileName}")
+ api.upload(s"$baseUri/$fileName", data)
+ }
+ reportPublishResults(publishResults, artifacts)
+
+ if (release) {
+ log.info("Closing staging repository")
+ api.closeStagingRepo(profileUri, stagingRepoId)
+
+ log.info("Waiting for staging repository to close")
+ awaitRepoStatus("closed", stagingRepoId)
+
+ log.info("Promoting staging repository")
+ api.promoteStagingRepo(profileUri, stagingRepoId)
+
+ log.info("Waiting for staging repository to release")
+ awaitRepoStatus("released", stagingRepoId)
+
+ log.info("Dropping staging repository")
+ api.dropStagingRepo(profileUri, stagingRepoId)
+
+ log.info(s"Published ${artifacts.map(_.id).mkString(", ")} successfully")
+ }
+ }
+
+ private def reportPublishResults(publishResults: Seq[HttpResponse[String]],
+ artifacts: Seq[Artifact]) = {
+ if (publishResults.forall(_.is2xx)) {
+ log.info(s"Published ${artifacts.map(_.id).mkString(", ")} to Sonatype")
+ } else {
+ val errors = publishResults.filterNot(_.is2xx).map { response =>
+ s"Code: ${response.code}, message: ${response.body}"
+ }
+ throw new RuntimeException(
+ s"Failed to publish ${artifacts.map(_.id).mkString(", ")} to Sonatype. Errors: \n${errors.mkString("\n")}"
+ )
+ }
+ }
+
+ private def awaitRepoStatus(status: String,
+ stagingRepoId: String,
+ attempts: Int = 20): Unit = {
+ def isRightStatus =
+ api.getStagingRepoState(stagingRepoId).equalsIgnoreCase(status)
+ var attemptsLeft = attempts
+
+ while (attemptsLeft > 0 && !isRightStatus) {
+ Thread.sleep(3000)
+ attemptsLeft -= 1
+ if (attemptsLeft == 0) {
+ throw new RuntimeException(
+ s"Couldn't wait for staging repository to be ${status}. Failing")
+ }
+ }
+ }
+
+ // http://central.sonatype.org/pages/working-with-pgp-signatures.html#signing-a-file
+ private def poorMansSign(file: os.Path, maybePassphrase: Option[String]): os.Path = {
+ val fileName = file.toString
+ maybePassphrase match {
+ case Some(passphrase) =>
+ os.proc("gpg", "--passphrase", passphrase, "--batch", "--yes", "-a", "-b", fileName)
+ .call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit)
+ case None =>
+ os.proc("gpg", "--batch", "--yes", "-a", "-b", fileName)
+ .call(stdin = os.Inherit, stdout = os.Inherit, stderr = os.Inherit)
+ }
+ os.Path(fileName + ".asc")
+ }
+
+ private def md5hex(bytes: Array[Byte]): Array[Byte] =
+ hexArray(md5.digest(bytes)).getBytes
+
+ private def sha1hex(bytes: Array[Byte]): Array[Byte] =
+ hexArray(sha1.digest(bytes)).getBytes
+
+ private def md5 = MessageDigest.getInstance("md5")
+
+ private def sha1 = MessageDigest.getInstance("sha1")
+
+ private def hexArray(arr: Array[Byte]) =
+ String.format("%0" + (arr.length << 1) + "x", new BigInteger(1, arr))
+
+}