Skip to content

Commit

Permalink
Include certification in packaged artifacts (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
raboof authored Dec 18, 2018
1 parent f434f7b commit b5f9381
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 36 deletions.
45 changes: 43 additions & 2 deletions src/main/scala/Certification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.math.BigInteger
import java.nio.file.Files
import java.security.MessageDigest

import sbt.{Artifact, File}
import sbt.io.syntax.File

import scala.collection.mutable
Expand All @@ -19,6 +20,7 @@ object Checksum {
}

case class Certification(
name: String,
groupId: String,
artifactId: String,
version: String,
Expand All @@ -27,11 +29,13 @@ case class Certification(
scalaVersion: String,
scalaBinaryVersion: String,
sbtVersion: String,
checksums: List[Checksum]
checksums: List[Checksum],
date: Long,
) {
def asPropertyString: String = {
val packageName = groupId + ":" + artifactId
val content = mutable.LinkedHashMap(
"name" -> name,
"group_id" -> groupId,
"artifact_id" -> artifactId,
"version" -> version,
Expand All @@ -44,6 +48,7 @@ case class Certification(
"sbt.version" -> sbtVersion,
"scala.version" -> scalaVersion,
"scala.binary-version" -> scalaBinaryVersion,
"date" -> date,
) ++ checksums.zipWithIndex.flatMap {
case (Checksum(filename, length, checksum), idx) =>
Seq(
Expand All @@ -58,6 +63,40 @@ case class Certification(

}
object Certification {
def apply(
organization: String,
packageName: String,
packageVersion: String,
packagedArtifacts: Map[Artifact, File],
scalaVersion: String,
scalaBinaryVersion: String,
sbtVersion: String,
): Certification = {

val artifacts = packagedArtifacts
.filter { case (artifact, _) => artifact.`type` == "pom" || artifact.`type` == "jar" }

val classifier = packagedArtifacts.collectFirst { case (artifact, _) if artifact.`type` == "jar" => artifact.classifier }.flatten

val checksums: List[Checksum] = artifacts
.map { case (_, packagedFile) => Checksum(packagedFile) }
.toList

Certification(
packageName,
organization,
packageName + "_" + scalaBinaryVersion,
packageVersion,
classifier,
"all",
scalaVersion,
scalaBinaryVersion,
sbtVersion,
checksums,
artifacts.values.map(_.lastModified()).max
)
}

def apply(propertyString: String): Certification = {
val properties = new java.util.Properties()
properties.load(new StringReader(propertyString))
Expand All @@ -80,6 +119,7 @@ object Certification {
}

new Certification(
properties.getProperty("name"),
properties.getProperty("group_id"),
properties.getProperty("artifact_id"),
properties.getProperty("version"),
Expand All @@ -88,7 +128,8 @@ object Certification {
properties.getProperty("scala.version"),
properties.getProperty("scala.binary-version"),
properties.getProperty("sbt.version"),
checksums
checksums,
properties.getProperty("date").toLong
)
}
}
69 changes: 41 additions & 28 deletions src/main/scala/ReproducibleBuildsPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.typesafe.sbt.pgp.PgpKeys._
import com.typesafe.sbt.pgp.PgpSettings.{pgpPassphrase => _, pgpSecretRing => _, pgpSigningKey => _, useGpgAgent => _, _}
import io.github.zlika.reproducible._
import sbt.io.syntax.{URI, uri}
import sbt.librarymanagement.{Artifact, Configuration}
import sbt.librarymanagement.Http.http

import scala.util.{Success, Try}
Expand All @@ -30,54 +31,66 @@ object ReproducibleBuildsPlugin extends AutoPlugin {

val reproducibleBuildsPackageName = taskKey[String]("Package name of this build, including version but excluding disambiguation string")
val reproducibleBuildsCertification = taskKey[File]("Create a Reproducible Builds certification")
val publishCertification = taskKey[Boolean]("Include the certification when publishing")
val reproducibleBuildsUploadPrefix = taskKey[URI]("Base URL to send uploads to")
val signedReproducibleBuildsCertification = taskKey[File]("Create a signed Reproducible Builds certification")
val reproducibleBuildsUploadCertification = taskKey[Unit]("Upload the Reproducible Builds certification")
val reproducibleBuildsCheckCertification = taskKey[Unit]("Download and compare Reproducible Builds certifications")

val disambiguation = taskKey[Iterable[File] => Option[String]]("Generator for optional discriminator string")
val disambiguation = taskKey[Certification => Option[String]]("Generator for optional discriminator string")

override lazy val projectSettings = Seq(
publishCertification := true,
reproducibleBuildsUploadPrefix := uri("http://localhost:8000/"),
disambiguation in Compile := ((packagedFiles: Iterable[File]) =>
Some(sys.env.get("USER").orElse(sys.env.get("USERNAME")).map(_ + "-").getOrElse("") + packagedFiles.map(_.lastModified()).max)
disambiguation in Compile := ((c: Certification) =>
Some(sys.env.get("USER").orElse(sys.env.get("USERNAME")).map(_ + "-").getOrElse("") + c.date)
),
packageBin in Compile := postProcessJar((packageBin in Compile).value),
artifactPath in reproducibleBuildsCertification := artifactPathSetting(artifact in reproducibleBuildsCertification).value,
reproducibleBuildsPackageName := moduleName.value + "_" + scalaBinaryVersion.value,
reproducibleBuildsPackageName := moduleName.value,
reproducibleBuildsCertification := {
val packageName = reproducibleBuildsPackageName.value

val targetDirPath = crossTarget.value
val packageVersion = version.value
val architecture = "all"

val artifacts = (packagedArtifacts in Compile).value
.filter { case (artifact, _) => artifact.`type` == "pom" || artifact.`type` == "jar" }
val classifier = (packagedArtifacts in Compile).value.flatMap { case (artifact, _) => artifact.classifier }.headOption

val targetFilePath = targetDirPath.toPath.resolve(targetFilename(packageName, packageVersion, architecture, (disambiguation in Compile).value(artifacts.map(_._2))))

val checksums: List[Checksum] = artifacts
.map { case (_, packagedFile) => Checksum(packagedFile) }
.toList

val certification = Certification(
organization.value,
packageName,
packageVersion,
classifier,
architecture,
reproducibleBuildsPackageName.value,
version.value,
(packagedArtifacts in Compile).value,
(scalaVersion in artifactName).value,
(scalaBinaryVersion in artifactName).value,
sbtVersion.value,
checksums,
sbtVersion.value
)

val targetDirPath = crossTarget.value
val targetFilePath = targetDirPath.toPath.resolve(targetFilename(certification.artifactId, certification.version, certification.classifier, (disambiguation in Compile).value(certification)))

Files.write(targetFilePath, certification.asPropertyString.getBytes(Charset.forName("UTF-8")))

targetFilePath.toFile
},
packagedArtifacts ++= {
val artifact = Map(
Artifact(reproducibleBuildsPackageName.value, "buildinfo", "buildinfo") ->
{
val certification = Certification(
organization.value,
reproducibleBuildsPackageName.value,
version.value,
(packagedArtifacts in Compile).value,
(scalaVersion in artifactName).value,
(scalaBinaryVersion in artifactName).value,
sbtVersion.value
)

val targetDirPath = crossTarget.value
val targetFilePath = targetDirPath.toPath.resolve(targetFilename(certification.artifactId, certification.version, certification.classifier, (disambiguation in Compile).value(certification)))

Files.write(targetFilePath, certification.asPropertyString.getBytes(Charset.forName("UTF-8")))

targetFilePath.toFile
}
)

if (publishCertification.value) artifact else Map.empty[Artifact, File]
},
signedReproducibleBuildsCertification := {
val file = reproducibleBuildsCertification.value
val signer = new CleartextCommandLineGpgSigner(gpgCommand.value, useGpgAgent.value, pgpSigningKey.value, pgpPassphrase.value)
Expand Down Expand Up @@ -192,6 +205,6 @@ object ReproducibleBuildsPlugin extends AutoPlugin {
* See https://wiki.debian.org/ReproducibleBuilds/BuildinfoFiles#File_name_and_encoding
* @return
*/
def targetFilename(source: String, version: String, architecture: String, suffix: Option[String]) =
source + "_" + version + "_" + architecture + suffix.map("_" + _).getOrElse("") + ".buildinfo"
def targetFilename(source: String, version: String, architecture: Option[String], suffix: Option[String]) =
source + "-" + version + architecture.map("_" + _).getOrElse("") + suffix.map("_" + _).getOrElse("") + ".buildinfo"
}
2 changes: 1 addition & 1 deletion src/sbt-test/sbt-reproducible-builds/native-packager/test
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
$ exists target/scala-2.12/stripped/native-packager_2.12-0.1.0-SNAPSHOT.jar
$ must-mirror target/scala-2.12/stripped/native-packager_2.12-0.1.0-SNAPSHOT.jar expected/native-packager_2.12-0.1.0-SNAPSHOT.jar
> reproducibleBuildsCertification
$ exists target/scala-2.12/native-packager_2.12_0.1.0-SNAPSHOT_all_STATIC.buildinfo
$ exists target/scala-2.12/native-packager_2.12-0.1.0-SNAPSHOT_STATIC.buildinfo
# Not on travis:
#> signedReproducibleBuildsCertification
#> reproducibleBuildsUploadCertification
Expand Down
2 changes: 1 addition & 1 deletion src/sbt-test/sbt-reproducible-builds/osgi/test
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
$ exists target/scala-2.12/stripped/osgi_2.12-0.1.0-SNAPSHOT.jar
$ must-mirror target/scala-2.12/stripped/osgi_2.12-0.1.0-SNAPSHOT.jar expected/osgi_2.12-0.1.0-SNAPSHOT.jar
> reproducibleBuildsCertification
$ exists target/scala-2.12/osgi_2.12_0.1.0-SNAPSHOT_all_STATIC.buildinfo
$ exists target/scala-2.12/osgi_2.12-0.1.0-SNAPSHOT_STATIC.buildinfo
# Not on travis:
#> signedReproducibleBuildsCertification
#> reproducibleBuildsUploadCertification
Expand Down
2 changes: 1 addition & 1 deletion src/sbt-test/sbt-reproducible-builds/simple/test
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
$ exists target/scala-2.12/stripped/simple_2.12-0.1.0-SNAPSHOT.jar
$ must-mirror target/scala-2.12/stripped/simple_2.12-0.1.0-SNAPSHOT.jar expected/simple_2.12-0.1.0-SNAPSHOT.jar
> reproducibleBuildsCertification
$ exists target/scala-2.12/simple_2.12_0.1.0-SNAPSHOT_all_STATIC.buildinfo
$ exists target/scala-2.12/simple_2.12-0.1.0-SNAPSHOT_STATIC.buildinfo
# Not on travis:
#> signedReproducibleBuildsCertification
#> reproducibleBuildsUploadCertification
Expand Down
8 changes: 5 additions & 3 deletions src/test/scala/CertificationSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class CertificationSpec extends WordSpec with Matchers {
"The certification model" should {
"Roundtrip through Properties format" in {
val cert = Certification(
groupId="net.bzzt",
artifactId="simple_2.12",
name = "simple",
groupId = "net.bzzt",
artifactId = "simple_2.12",
version = "0.12.7",
classifier = None,
architecture = "all",
Expand All @@ -21,7 +22,8 @@ class CertificationSpec extends WordSpec with Matchers {
checksums = List(
Checksum("foo.jar", 42, Array[Byte](0x31, 0x23).toList),
Checksum("bar-with-weird-characters.xml" , 42, Array[Byte](0x31, 0x33).toList)
)
),
42
)
val asString = cert.asPropertyString
val roundtripped = Certification(asString)
Expand Down

0 comments on commit b5f9381

Please sign in to comment.