Skip to content

Commit

Permalink
json bomFormat (sbt#99)
Browse files Browse the repository at this point in the history
* json format

* cleanup and tests

* fix formatting

* update README.md

* reduce diff

* fixed tests
  • Loading branch information
lhns authored Dec 12, 2024
1 parent 062e821 commit 16d8b33
Show file tree
Hide file tree
Showing 24 changed files with 2,101 additions and 29 deletions.
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,16 @@ The `listBom` command can be used to generate the contents of the BOM without wr

### configuration

| Setting | Type | Default | Description |
|------------------------|---------|----------------------------------------------|-----------------------------------------------------------------|
| bomFileName | String | `"${artifactId}-${artifactVersion}.bom.xml"` | bom file name |
| bomSchemaVersion | String | `"1.6"` | bom schema version |
| includeBomSerialNumber | Boolean | `false` | include serial number in bom |
| includeBomTimestamp | Boolean | `false` | include timestamp in bom |
| includeBomToolVersion | Boolean | `true` | include tool version in bom |
| includeBomHashes | Boolean | `true` | include artifact hashes in bom |
| enableBomSha3Hashes | Boolean | `true` | enable the generation of sha3 hashes (not available on java 8) |
| Setting | Type | Default | Description |
|------------------------|---------|------------------------------------------------------------------------|----------------------------------------------------------------|
| bomFileName | String | `"${artifactId}-${artifactVersion}.bom.xml"` | bom file name |
| bomFormat | String | `json` or `xml`, defaults to the format of bomFileName or else `json` | bom format |
| bomSchemaVersion | String | `"1.6"` | bom schema version |
| includeBomSerialNumber | Boolean | `false` | include serial number in bom |
| includeBomTimestamp | Boolean | `false` | include timestamp in bom |
| includeBomToolVersion | Boolean | `true` | include tool version in bom |
| includeBomHashes | Boolean | `true` | include artifact hashes in bom |
| enableBomSha3Hashes | Boolean | `true` | enable the generation of sha3 hashes (not available on java 8) |

Sample configuration:

Expand All @@ -68,7 +69,7 @@ lazy val root = (project in file("."))

## CycloneDX support

Actually, only version 1.0 of the CycloneDX specification is supported. Support for later versions of the specification, such as for creating BOMs in json format, is expected later.
This plugin supports the CycloneDX XML and JSON BOM formats.

## Contributing

Expand Down
35 changes: 35 additions & 0 deletions src/main/scala/com/github/sbt/sbom/BomFormat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.github.sbt.sbom

import com.github.sbt.sbom.PluginConstants.supportedVersions
import org.cyclonedx.Version

sealed abstract class BomFormat(val string: String)

object BomFormat {
case object Json extends BomFormat("json")
case object Xml extends BomFormat("xml")

def fromSettings(bomFormat: Option[String], bomFileName: Option[String], schemaVersion: String): BomFormat = {
bomFormat
.collect {
case Json.string => Json
case Xml.string => Xml
case format =>
throw new BomError(s"Unsupported format ${format}")
}
.orElse {
bomFileName.map(_.toLowerCase).collect {
case ext if ext.endsWith(".json") => Json
case ext if ext.endsWith(".xml") => Xml
}
}
.orElse {
supportedVersions.find(_.getVersionString == schemaVersion).collect {
case foundVersion if foundVersion.getVersion > Version.VERSION_11.getVersion => Json
}
}
.getOrElse {
Xml
}
}
}
7 changes: 6 additions & 1 deletion src/main/scala/com/github/sbt/sbom/BomSbtPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ object BomSbtPlugin extends AutoPlugin {
lazy val bomSchemaVersion: SettingKey[String] = settingKey[String](
s"bom schema version; must be one of ${supportedVersionsDescr}; default is ${defaultSupportedVersionDescr}"
)
lazy val bomFormat: SettingKey[String] = settingKey[String](
"bom format; must be json or xml"
)
lazy val includeBomSerialNumber: SettingKey[Boolean] = settingKey[Boolean](
"should the resulting BOM contain a serial number? default is false, because the current mechanism for determining the serial number is not reproducible"
)
Expand Down Expand Up @@ -52,7 +55,9 @@ object BomSbtPlugin extends AutoPlugin {
val bomFileNameSetting = Def.setting {
val artifactId = artifact.value.name
val artifactVersion = version.value
s"${artifactId}-${artifactVersion}.bom.xml"
val schemaVersion = bomSchemaVersion.value
val format = BomFormat.fromSettings(bomFormat.?.value, None, schemaVersion)
s"${artifactId}-${artifactVersion}.bom.${format.string}"
}
Seq(
bomFileName := bomFileNameSetting.value,
Expand Down
12 changes: 12 additions & 0 deletions src/main/scala/com/github/sbt/sbom/BomSbtSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ import sbt._
object BomSbtSettings {
def makeBomTask(report: UpdateReport, currentConfiguration: Configuration): Def.Initialize[Task[sbt.File]] =
Def.task[File] {
val format = BomFormat.fromSettings(
(currentConfiguration / bomFormat).?.value,
(currentConfiguration / bomFileName).?.value,
bomSchemaVersion.value
)
new MakeBomTask(
BomTaskProperties(
report,
currentConfiguration,
sLog.value,
bomSchemaVersion.value,
format,
includeBomSerialNumber.value,
includeBomTimestamp.value,
includeBomToolVersion.value,
Expand All @@ -25,12 +31,18 @@ object BomSbtSettings {

def listBomTask(report: UpdateReport, currentConfiguration: Configuration): Def.Initialize[Task[String]] =
Def.task[String] {
val format = BomFormat.fromSettings(
(currentConfiguration / bomFormat).?.value,
(currentConfiguration / bomFileName).?.value,
bomSchemaVersion.value
)
new ListBomTask(
BomTaskProperties(
report,
currentConfiguration,
sLog.value,
bomSchemaVersion.value,
format,
includeBomSerialNumber.value,
includeBomTimestamp.value,
includeBomToolVersion.value,
Expand Down
24 changes: 13 additions & 11 deletions src/main/scala/com/github/sbt/sbom/BomTask.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import org.apache.commons.io.FileUtils
import org.cyclonedx.Version
import org.cyclonedx.generators.BomGeneratorFactory
import org.cyclonedx.model.Bom
import org.cyclonedx.parsers.XmlParser
import org.cyclonedx.parsers.{ JsonParser, XmlParser }
import sbt._

import java.nio.charset.Charset
Expand All @@ -16,6 +16,7 @@ final case class BomTaskProperties(
currentConfiguration: Configuration,
log: Logger,
schemaVersion: String,
bomFormat: BomFormat,
includeBomSerialNumber: Boolean,
includeBomTimestamp: Boolean,
includeBomToolVersion: Boolean,
Expand All @@ -30,7 +31,10 @@ abstract class BomTask[T](protected val properties: BomTaskProperties) {
protected def getBomText: String = {
val params: BomExtractorParams = extractorParams(currentConfiguration)
val bom: Bom = new BomExtractor(params, report, log).bom
val bomText: String = getXmlText(bom)
val bomText: String = bomFormat match {
case BomFormat.Json => BomGeneratorFactory.createJson(schemaVersion, bom).toJsonString
case BomFormat.Xml => BomGeneratorFactory.createXml(schemaVersion, bom).toXmlString
}
logBomInfo(params, bom)
bomText
}
Expand All @@ -40,11 +44,14 @@ abstract class BomTask[T](protected val properties: BomTaskProperties) {
}

protected def validateBomFile(bomFile: File): Unit = {
val parser = new XmlParser()
val parser = bomFormat match {
case BomFormat.Json => new JsonParser()
case BomFormat.Xml => new XmlParser()
}
val exceptions = parser.validate(bomFile, schemaVersion).asScala
if (exceptions.nonEmpty) {
val message =
s"The BOM file ${bomFile.getAbsolutePath} does not conform to the CycloneDX BOM standard as defined by the XSD"
s"The BOM file ${bomFile.getAbsolutePath} does not conform to the CycloneDX BOM standard as defined by the Schema"
log.error(s"$message:")
exceptions.foreach { exception =>
log.error(s"- ${exception.getMessage}")
Expand All @@ -70,13 +77,6 @@ abstract class BomTask[T](protected val properties: BomTaskProperties) {
enableBomSha3Hashes
)

private def getXmlText(bom: Bom): String = {
val bomGenerator = BomGeneratorFactory.createXml(schemaVersion, bom)
bomGenerator.generate
val bomText = bomGenerator.toXmlString
bomText
}

protected def logBomInfo(params: BomExtractorParams, bom: Bom): Unit = {
log.info(s"Schema version: ${schemaVersion.getVersionString}")
// log.info(s"Serial number : ${bom.getSerialNumber}")
Expand All @@ -98,6 +98,8 @@ abstract class BomTask[T](protected val properties: BomTaskProperties) {
throw new BomError(message)
}

protected lazy val bomFormat: BomFormat = properties.bomFormat

protected lazy val includeBomSerialNumber: Boolean = properties.includeBomSerialNumber

protected lazy val includeBomTimestamp: Boolean = properties.includeBomTimestamp
Expand Down
2 changes: 1 addition & 1 deletion src/sbt-test/bomfile/exists/test
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
> clean
> compile
> makeBom
$ exists target/exists-0.1.bom.xml
$ exists target/exists-0.1.bom.json
2 changes: 0 additions & 2 deletions src/sbt-test/dependencies/compile/build.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import scala.xml.XML

lazy val root = (project in file("."))
.settings(
name := "dependencies",
Expand Down
2 changes: 0 additions & 2 deletions src/sbt-test/dependencies/integrationTest/build.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import scala.xml.XML

lazy val root = (project in file("."))
.settings(
name := "dependencies",
Expand Down
2 changes: 0 additions & 2 deletions src/sbt-test/dependencies/test/build.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import scala.xml.XML

lazy val root = (project in file("."))
.settings(
name := "dependencies",
Expand Down
26 changes: 26 additions & 0 deletions src/sbt-test/dependenciesJson/compile/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
lazy val root = (project in file("."))
.settings(
name := "dependencies",
version := "0.1",
libraryDependencies ++= Dependencies.library,
bomFileName := "bom.json",
includeBomToolVersion := false,
enableBomSha3Hashes := false,
scalaVersion := "2.12.20",
check := Def
.sequential(
Compile / clean,
Compile / compile,
checkTask
)
.value
)

lazy val check = taskKey[Unit]("check")
lazy val checkTask = Def.task {
val s: TaskStreams = streams.value
s.log.info("Verifying bom content...")
makeBom.value
import scala.sys.process._
require(Seq("diff", "-w", "target/bom.json", s"${thisProject.value.base}/etc/bom.json").! == 0)
}
Loading

0 comments on commit 16d8b33

Please sign in to comment.