Skip to content

Commit

Permalink
Add ability to sign using sbt-pgp (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdedetrich committed Oct 16, 2023
1 parent d2b361b commit 54ce83b
Show file tree
Hide file tree
Showing 15 changed files with 200 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Expand Up @@ -51,6 +51,9 @@ jobs:
if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false'
run: sbt '++ ${{ matrix.scala }}' reload +update

- name: Setup key
run: gpg --import test-key.gpg

- name: Check that workflows are up to date
run: sbt githubWorkflowCheck

Expand Down
15 changes: 15 additions & 0 deletions README.md
Expand Up @@ -34,3 +34,18 @@ You can then generate the source distribution using
```
sbt sourceDistGenerate
```

sbt-source-dist also supports signing the final generated source distribution
using [sbt-pgp](https://github.com/sbt/sbt-pgp). Signing the source
distribution in this manner has the added advantage of ensuring that the same
signing key that is used to sign binary maven artifacts is also used for the
source dist.

You can both generate the source distribution and then sign it using

```
sbt signedSourceDistGenerate
```

`signedSourceDistGenerate` will re-use the same settings provided by sbt-pgp
so if you need to configure it follow the instructions at https://github.com/sbt/sbt-pgp#usage.
9 changes: 9 additions & 0 deletions build.sbt
Expand Up @@ -20,6 +20,8 @@ libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.17" % Test
)

addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")

homepage := Some(url("https://github.com/pjfanning/sbt-source-dist"))

licenses := Seq("APL2" -> url("https://www.apache.org/licenses/LICENSE-2.0.txt"))
Expand All @@ -44,6 +46,13 @@ ThisBuild / githubWorkflowBuild := Seq(
WorkflowStep.Sbt(name = Some("Build project"), commands = List("test", "scripted"))
)

ThisBuild / githubWorkflowBuildPreamble := Seq(
WorkflowStep.Run(
commands = List("gpg --import test-key.gpg"),
name = Some("Setup key")
)
)

ThisBuild / githubWorkflowPublish := Seq(
WorkflowStep.Sbt(
List("ci-release"),
Expand Down
@@ -0,0 +1,8 @@
package com.github.pjfanning.sourcedist

import sbt.File

final case class GeneratedDist(dist: File, checksum: File) {
def toSignedGeneratedDist(detachedSignature: File): SignedGeneratedDist =
SignedGeneratedDist(dist, checksum, detachedSignature)
}
13 changes: 9 additions & 4 deletions src/main/scala/com/github/pjfanning/sourcedist/ShaUtils.scala
Expand Up @@ -3,22 +3,27 @@ package com.github.pjfanning.sourcedist
import java.io.{File, FileInputStream, FileOutputStream, InputStream, OutputStreamWriter}
import java.nio.charset.StandardCharsets
import java.security.{DigestInputStream, MessageDigest}
import scala.util.Using
import scala.util.{Failure, Success, Using}

object ShaUtils {
def writeShaDigest(file: File, keySize: Int): Unit = {
def writeShaDigest(file: File, keySize: Int): File = {
val digester = MessageDigest.getInstance(s"SHA-$keySize")
Using(new FileInputStream(file)) { fileStream =>
Using(new DigestInputStream(fileStream, digester)) { digestStream =>
digestStream.on(true)
readStream(digestStream)
val hexDigest = convertBytesToHexadecimal(digester.digest())
Using(new FileOutputStream(s"${file.getAbsolutePath}.sha$keySize")) { fos =>
val hexDigest = convertBytesToHexadecimal(digester.digest())
val outputFileName = new File(s"${file.getAbsolutePath}.sha$keySize")
Using(new FileOutputStream(outputFileName)) { fos =>
Using(new OutputStreamWriter(fos, StandardCharsets.UTF_8)) { writer =>
writer.append(s"$hexDigest ${file.getName}")
}
}
outputFileName
}
}.flatten match {
case Failure(exception) => throw exception
case Success(value) => value
}
}

Expand Down
@@ -0,0 +1,5 @@
package com.github.pjfanning.sourcedist

import sbt.File

final case class SignedGeneratedDist(dist: File, checksum: File, detachedSignature: File)
Expand Up @@ -14,7 +14,7 @@ private[sourcedist] object SourceDistGenerate {
suffix: String,
incubating: Boolean,
logger: ManagedLogger
): Unit = {
): GeneratedDist = {
val baseDir = new File(homeDir)
val gitState = new GitState(baseDir)
val files = if (gitState.isUnderGitControl) {
Expand Down Expand Up @@ -42,8 +42,7 @@ private[sourcedist] object SourceDistGenerate {
} else
logger.info(s"Creating tar archive at ${toTgzFile.getPath}")
TarUtils.tgzFiles(toTgzFile, files, homeDir)

ShaUtils.writeShaDigest(toTgzFile, 512)
GeneratedDist(toTgzFile, ShaUtils.writeShaDigest(toTgzFile, 512))
}

private def listLocalFiles(baseDir: File): Seq[File] = {
Expand Down
@@ -1,6 +1,6 @@
package com.github.pjfanning.sourcedist

import sbt.{File, SettingKey, TaskKey, settingKey, taskKey}
import sbt.{Artifact, File, SettingKey, TaskKey, settingKey, taskKey}

trait SourceDistKeys {
lazy val sourceDistHomeDir: SettingKey[File] =
Expand All @@ -19,5 +19,9 @@ trait SourceDistKeys {
lazy val sourceDistSuffix: SettingKey[String] = settingKey[String](
"The suffix to be used in the output archive file names, defaults to today's date in YYMMDD format"
)
lazy val sourceDistGenerate: TaskKey[Unit] = taskKey[Unit]("Generate the source distribution packages")
lazy val signedSourceDistGenerate: TaskKey[Option[SignedGeneratedDist]] = taskKey[Option[SignedGeneratedDist]](
"Signs the source distribution and provides a mapping of files their respective detached signatures"
)
lazy val sourceDistGenerate: TaskKey[GeneratedDist] =
taskKey[GeneratedDist]("Generate the source distribution packages")
}
@@ -1,8 +1,9 @@
package com.github.pjfanning.sourcedist

import sbt._
import Keys._
import sbt.{AutoPlugin, Setting}
import sbt.Keys._
import com.jsuereth.sbtpgp.PgpKeys.pgpSigner
import com.jsuereth.sbtpgp.{SbtPgp, gpgExtension}

import java.time.LocalDate
import java.time.format.DateTimeFormatter
Expand All @@ -19,6 +20,20 @@ object SourceDistPlugin extends AutoPlugin {
LocalRootProject / sourceDistName := (LocalRootProject / name).value,
LocalRootProject / sourceDistSuffix := LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE),
LocalRootProject / sourceDistIncubating := false,
LocalRootProject / signedSourceDistGenerate := {
val sourceDistGenerated = (LocalRootProject / sourceDistGenerate).value
val r = pgpSigner.value
val skipZ = (pgpSigner / skip).value
val s = streams.value
if (!skipZ) {
Some(
sourceDistGenerated.toSignedGeneratedDist(
r.sign(sourceDistGenerated.dist, new File(sourceDistGenerated.dist + gpgExtension), s)
)
)
} else
None
},
LocalRootProject / sourceDistGenerate := SourceDistGenerate.generateSourceDists(
homeDir = (LocalRootProject / sourceDistHomeDir).value.getAbsolutePath,
prefix = (LocalRootProject / sourceDistName).value,
Expand All @@ -30,6 +45,8 @@ object SourceDistPlugin extends AutoPlugin {
)
)

override def requires = SbtPgp

override lazy val buildSettings: Seq[Setting[_]] = sourceDistSettings

override def trigger = allRequirements
Expand Down
5 changes: 5 additions & 0 deletions src/sbt-test/sbt-source-dist/signing/build.sbt
@@ -0,0 +1,5 @@
scalaVersion := "2.13.10"
version := "0.1.9"
name := "apache-pekko"
sourceDistSuffix := "20230331"
pgpKeyRing := Some(baseDirectory.value / "pubring.pgp")
5 changes: 5 additions & 0 deletions src/sbt-test/sbt-source-dist/signing/project/plugins.sbt
@@ -0,0 +1,5 @@
sys.props.get("plugin.version") match {
case Some(x) => addSbtPlugin("com.github.pjfanning" % "sbt-source-dist" % x)
case _ => sys.error("""|The system property 'plugin.version' is not defined.
|Specify this property using the scriptedLaunchOpts -D.""".stripMargin)
}
Binary file added src/sbt-test/sbt-source-dist/signing/pubring.pgp
Binary file not shown.
@@ -0,0 +1,3 @@
object Main extends App {
println("hello")
}
6 changes: 6 additions & 0 deletions src/sbt-test/sbt-source-dist/signing/test
@@ -0,0 +1,6 @@
> show pgpPassphrase
> show pgpSigner
> signedSourceDistGenerate
$ exists target/dist/apache-pekko-0.1.9-src-20230331.tgz
$ exists target/dist/apache-pekko-0.1.9-src-20230331.tgz.sha512
$ exists target/dist/apache-pekko-0.1.9-src-20230331.tgz.asc
105 changes: 105 additions & 0 deletions test-key.gpg
@@ -0,0 +1,105 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----

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
=N0Eh
-----END PGP PRIVATE KEY BLOCK-----

0 comments on commit 54ce83b

Please sign in to comment.