11import java.security.MessageDigest
22import java.time.Instant
3+ import java.util.UUID
4+ import groovy.json.JsonOutput
35
46plugins {
57 java
@@ -267,6 +269,7 @@ val packageWindowsZip = tasks.register<Zip>("packageWindowsZip") {
267269}
268270
269271val provenanceFile = layout.buildDirectory.file(" release/provenance.json" )
272+ val sbomFile = layout.buildDirectory.file(" release/$releasePackageName -sbom.cdx.json" )
270273
271274tasks.register(" releaseProvenance" ) {
272275 group = " distribution"
@@ -306,15 +309,16 @@ tasks.register("releaseProvenance") {
306309tasks.register(" releaseChecksums" ) {
307310 group = " distribution"
308311 description = " Write SHA-256 checksums for release artifacts"
309- dependsOn(packageWindowsZip, tasks.named(" releaseProvenance" ))
312+ dependsOn(packageWindowsZip, tasks.named(" releaseProvenance" ), " releaseSbom " )
310313 val checksumsFile = layout.buildDirectory.file(" release/checksums.txt" )
311- inputs.files(packageWindowsZip.flatMap { it.archiveFile }, provenanceFile)
314+ inputs.files(packageWindowsZip.flatMap { it.archiveFile }, provenanceFile, sbomFile )
312315 outputs.file(checksumsFile)
313316 doLast {
314317 val artifacts =
315318 listOf (
316319 packageWindowsZip.get().archiveFile.get().asFile,
317320 provenanceFile.get().asFile,
321+ sbomFile.get().asFile,
318322 )
319323 checksumsFile.get().asFile.writeText(
320324 artifacts.joinToString(System .lineSeparator()) {
@@ -324,10 +328,52 @@ tasks.register("releaseChecksums") {
324328 }
325329}
326330
331+ tasks.register(" releaseSbom" ) {
332+ group = " distribution"
333+ description = " Write a CycloneDX SBOM for the release runtime classpath"
334+ outputs.file(sbomFile)
335+ doLast {
336+ val components =
337+ configurations.runtimeClasspath.get().resolvedConfiguration.resolvedArtifacts
338+ .sortedWith(compareBy({ it.moduleVersion.id.group }, { it.name }, { it.moduleVersion.id.version }))
339+ .map {
340+ val id = it.moduleVersion.id
341+ mapOf (
342+ " type" to " library" ,
343+ " bom-ref" to " pkg:maven/${id.group} /${id.name} @${id.version} " ,
344+ " group" to id.group,
345+ " name" to id.name,
346+ " version" to id.version,
347+ " purl" to " pkg:maven/${id.group} /${id.name} @${id.version} " ,
348+ )
349+ }
350+ val bom =
351+ mapOf (
352+ " bomFormat" to " CycloneDX" ,
353+ " specVersion" to " 1.6" ,
354+ " serialNumber" to " urn:uuid:${UUID .nameUUIDFromBytes(" ${project.name} :${project.version} " .toByteArray())} " ,
355+ " version" to 1 ,
356+ " metadata" to
357+ mapOf (
358+ " timestamp" to Instant .now().toString(),
359+ " component" to
360+ mapOf (
361+ " type" to " application" ,
362+ " name" to project.name,
363+ " version" to project.version.toString(),
364+ " purl" to " pkg:maven/${project.group} /${project.name} @${project.version} " ,
365+ ),
366+ ),
367+ " components" to components,
368+ )
369+ sbomFile.get().asFile.writeText(JsonOutput .prettyPrint(JsonOutput .toJson(bom)))
370+ }
371+ }
372+
327373tasks.register(" releaseBundle" ) {
328374 group = " distribution"
329- description = " Build release zip, checksums, and provenance metadata"
330- dependsOn(packageWindowsZip, tasks.named(" releaseProvenance" ), tasks.named(" releaseChecksums" ))
375+ description = " Build release zip, checksums, SBOM, and provenance metadata"
376+ dependsOn(packageWindowsZip, tasks.named(" releaseProvenance" ), tasks.named(" releaseSbom " ), tasks.named( " releaseChecksums" ))
331377}
332378
333379fun sha256 (file : File ): String {
0 commit comments