Skip to content

Commit 7c548d3

Browse files
committed
WIP: Add CycloneDX SBOM
* Java: Uses cyclonedx-gradle-plugin * Python: Uses cyclonedx-bom * Not as rich as the SBOM for Java :(
1 parent 5c689f1 commit 7c548d3

File tree

11 files changed

+557
-7
lines changed

11 files changed

+557
-7
lines changed

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ client-license-check: client-setup-env ## Run license compliance check
168168
@$(ACTIVATE_AND_CD) && pip-licenses
169169
@echo "--- License compliance check complete ---"
170170

171+
.PHONY: client-license-check
172+
client-sbom: client-setup-env ## Generate SBOM
173+
@echo "--- Starting SBOM gernation ---"
174+
@$(ACTIVATE_AND_CD) && mkdir -p dist; \
175+
cyclonedx-py poetry --only main --output-reproducible --validate --output-format JSON --output-file dist/bom.json --verbose; \
176+
cyclonedx-py poetry --only main --output-reproducible --validate --output-format XML --output-file dist/bom.xml --verbose
177+
@echo "--- SBOM gernation complete ---"
178+
171179
.PHONY: client-build
172180
client-build: client-setup-env ## Build client distribution. Pass FORMAT=sdist or FORMAT=wheel to build a specific format.
173181
@echo "--- Building client distribution ---"

build-logic/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ plugins { `kotlin-dsl` }
2121

2222
dependencies {
2323
implementation(gradleKotlinDsl())
24+
implementation(baselibs.cyclonedx)
2425
implementation(baselibs.errorprone)
2526
implementation(baselibs.idea.ext)
2627
implementation(baselibs.jandex)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import java.util.Base64
21+
import org.cyclonedx.gradle.CyclonedxDirectTask
22+
import org.cyclonedx.model.AttachmentText
23+
import org.cyclonedx.model.Component.Type.APPLICATION
24+
import org.cyclonedx.model.License
25+
import org.cyclonedx.model.LicenseChoice
26+
import org.cyclonedx.model.Property
27+
import sbom.CyclonedxBundleTask
28+
import sbom.createCyclonedxConfigurations
29+
30+
val cyclonedxApplicationBomTask =
31+
tasks.register<CyclonedxBundleTask>("cyclonedxApplicationBom") {
32+
group = "publishing"
33+
description = "Generate CycloneDX SBOMs for this as an application"
34+
35+
val cyclonedxDirectBom = tasks.named<CyclonedxDirectTask>("cyclonedxDirectBom")
36+
inputBoms.from(cyclonedxDirectBom.map { it.jsonOutput })
37+
38+
// want to include the original license text in the generated SBOM as it may include variations
39+
// from the "standard" license text
40+
includeLicenseText = true
41+
42+
projectType = APPLICATION
43+
44+
// Needed for projects that use subdirectories in their build/ directory, like the Spark plugin
45+
jsonOutput.set(project.layout.buildDirectory.file("reports/cyclonedx-app/bom.json"))
46+
xmlOutput.set(project.layout.buildDirectory.file("reports/cyclonedx-app/bom.xml"))
47+
48+
val relativeProjectDir = project.projectDir.relativeTo(project.rootProject.projectDir)
49+
val gitInfo = GitInfo.memoized(project)
50+
51+
licenseChoice.set(
52+
LicenseChoice().apply {
53+
addLicense(
54+
License().apply {
55+
val gitCommit = GitInfo.memoized(project).gitHead
56+
id = "Apache-2.0"
57+
// TODO URL or text ??
58+
url = gitInfo.rawGithubLink("$relativeProjectDir/distribution/LICENSE")
59+
setLicenseText(
60+
AttachmentText().apply() {
61+
contentType = "plain/text"
62+
encoding = "base64"
63+
text =
64+
Base64.getEncoder()
65+
.encodeToString(project.file("distribution/LICENSE").readBytes())
66+
}
67+
)
68+
69+
// TODO Is there a better way to include NOTICE + DISCLAIMER in a CycloneDX SBOM?
70+
val props = mutableListOf<Property>()
71+
props.add(
72+
Property().apply {
73+
name = "NOTICE"
74+
value =
75+
project.file("distribution/NOTICE").readText(Charsets.UTF_8).replace("\n", "\\n")
76+
}
77+
)
78+
val disclaimerFile = project.file("distribution/DISCLAIMER")
79+
if (disclaimerFile.isFile) {
80+
props.add(
81+
Property().apply {
82+
name = "DISCLAIMER"
83+
value = disclaimerFile.readText(Charsets.UTF_8).replace("\n", "\\n")
84+
}
85+
)
86+
}
87+
properties = props
88+
}
89+
)
90+
}
91+
)
92+
}
93+
94+
createCyclonedxConfigurations(project, cyclonedxApplicationBomTask)
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import java.util.Base64
21+
import org.cyclonedx.model.AttachmentText
22+
import org.cyclonedx.model.License
23+
import org.cyclonedx.model.LicenseChoice
24+
import org.cyclonedx.model.Property
25+
import publishing.digestTaskOutputs
26+
import publishing.signTaskOutputs
27+
import sbom.CyclonedxBundleTask
28+
import sbom.createCyclonedxConfigurations
29+
30+
plugins { id("org.cyclonedx.bom") }
31+
32+
val bundleSboms by
33+
configurations.creating {
34+
isCanBeConsumed = false
35+
isCanBeResolved = true
36+
}
37+
38+
val cyclonedxBundleBom = tasks.register<CyclonedxBundleTask>("cyclonedxBundleBom")
39+
40+
cyclonedxBundleBom.configure {
41+
inputBoms = bundleSboms
42+
// The distribution itself has no dependencies, just components
43+
includeDependencies = false
44+
45+
val relativeProjectDir = project.projectDir.relativeTo(project.rootProject.projectDir)
46+
val gitInfo = GitInfo.memoized(project)
47+
48+
licenseChoice.set(
49+
LicenseChoice().apply {
50+
addLicense(
51+
License().apply {
52+
id = "Apache-2.0"
53+
// TODO URL or text ??
54+
url = gitInfo.rawGithubLink("$relativeProjectDir/LICENSE")
55+
setLicenseText(
56+
AttachmentText().apply() {
57+
contentType = "plain/text"
58+
encoding = "base64"
59+
text = Base64.getEncoder().encodeToString(project.file("LICENSE").readBytes())
60+
}
61+
)
62+
63+
// TODO Is there a better way to include NOTICE + DISCLAIMER in a CycloneDX SBOM?
64+
val props = mutableListOf<org.cyclonedx.model.Property>()
65+
props.add(
66+
Property().apply {
67+
name = "NOTICE"
68+
value = project.file("NOTICE").readText(Charsets.UTF_8).replace("\n", "\\n")
69+
}
70+
)
71+
val disclaimerFile = project.file("DISCLAIMER")
72+
if (disclaimerFile.isFile) {
73+
props.add(
74+
Property().apply {
75+
name = "DISCLAIMER"
76+
value = disclaimerFile.readText(Charsets.UTF_8).replace("\n", "\\n")
77+
}
78+
)
79+
}
80+
properties = props
81+
}
82+
)
83+
}
84+
)
85+
}
86+
87+
createCyclonedxConfigurations(project, cyclonedxBundleBom)
88+
89+
tasks.named("assemble") { dependsOn(cyclonedxBundleBom) }
90+
91+
digestTaskOutputs(cyclonedxBundleBom)
92+
93+
signTaskOutputs(cyclonedxBundleBom)

build-logic/src/main/kotlin/publishing/PublishingHelperPlugin.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import org.gradle.kotlin.dsl.registering
3838
import org.gradle.kotlin.dsl.withType
3939
import org.gradle.plugins.signing.SigningExtension
4040
import org.gradle.plugins.signing.SigningPlugin
41+
import sbom.configureCycloneDx
4142

4243
/**
4344
* Release-publishing helper plugin to generate publications that pass Sonatype validations,
@@ -94,6 +95,7 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl
9495

9596
apply(plugin = "maven-publish")
9697
apply(plugin = "signing")
98+
apply(plugin = "org.cyclonedx.bom")
9799

98100
// Generate a source tarball for a release to be uploaded to
99101
// https://dist.apache.org/repos/dist/dev/<name>/apache-<name>-<version-with-rc>/
@@ -175,5 +177,7 @@ constructor(private val softwareComponentFactory: SoftwareComponentFactory) : Pl
175177
}
176178
}
177179
}
180+
181+
configureCycloneDx()
178182
}
179183
}

0 commit comments

Comments
 (0)