55package com .github .sbt .sbom
66
77import com .github .packageurl .PackageURL
8+ import com .github .sbt .sbom .BomExtractor .purl
89import com .github .sbt .sbom .licenses .LicensesArchive
910import org .cyclonedx .Version
1011import org .cyclonedx .model .{
@@ -22,11 +23,10 @@ import org.cyclonedx.util.BomUtils
2223import sbt ._
2324import sbt .librarymanagement .ModuleReport
2425
25- import java .util
26- import java .util .UUID
26+ import java .util .{ TreeMap => TM , UUID }
2727import scala .collection .JavaConverters ._
2828
29- import SbtUpdateReport .ModuleGraph
29+ import SbtUpdateReport .{ ModuleGraph , getModuleQualifier }
3030
3131class BomExtractor (settings : BomExtractorParams , report : UpdateReport , rootModuleID : ModuleID , log : Logger ) {
3232 private val serialNumber : String = " urn:uuid:" + UUID .randomUUID.toString
@@ -52,9 +52,26 @@ class BomExtractor(settings: BomExtractorParams, report: UpdateReport, rootModul
5252 metadata.setTimestamp(null )
5353 }
5454 metadata.addTool(tool)
55+ metadata.setComponent(metadataComponent)
5556 metadata
5657 }
5758
59+ private lazy val metadataComponent : Component = {
60+ val metadataComponent = new Component ()
61+ val group : String = rootModuleID.organization
62+ val name : String = rootModuleID.name
63+ val version : String = rootModuleID.revision
64+
65+ metadataComponent.setGroup(group)
66+ metadataComponent.setName(name)
67+ metadataComponent.setBomRef(purl(group, name, version))
68+ metadataComponent.setVersion(version)
69+ metadataComponent.setType(toCycloneDxProjectType(settings.projectType))
70+ metadataComponent.setPurl(purl(group, name, version))
71+
72+ metadataComponent
73+ }
74+
5875 private lazy val tool : Tool = {
5976 val tool = new Tool ()
6077 // https://github.com/devops-kung-fu/bomber/blob/main/lib/loader.go#L112 searches for string CycloneDX to detect format
@@ -123,17 +140,20 @@ class BomExtractor(settings: BomExtractorParams, report: UpdateReport, rootModul
123140 - "info.apiURL"
124141 - "info.versionScheme"
125142 */
143+
126144 val component = new Component ()
127145 component.setGroup(group)
128146 component.setName(name)
129147 component.setVersion(version)
130148 component.setModified(false )
131149 component.setType(Component .Type .LIBRARY )
132- component.setPurl(purl(group, name, version))
150+
151+ component.setPurl(purl(group, name, version, getModuleQualifier(moduleReport, Some (log))))
133152 if (settings.schemaVersion.getVersion >= Version .VERSION_11 .getVersion) {
134153 // component bom-refs must be unique
135154 component.setBomRef(component.getPurl)
136155 }
156+
137157 component.setScope(Component .Scope .REQUIRED )
138158 if (settings.includeBomHashes) {
139159 component.setHashes(hashes(artifactPaths(moduleReport)).asJava)
@@ -214,9 +234,6 @@ class BomExtractor(settings: BomExtractorParams, report: UpdateReport, rootModul
214234 }
215235 }
216236
217- private def purl (group : String , name : String , version : String ): String =
218- new PackageURL (PackageURL .StandardTypes .MAVEN , group, name, version, new util.TreeMap (), null ).canonicalize()
219-
220237 private def dependencyTree : Seq [Dependency ] = {
221238 val dependencyTree = configurationsForComponents(settings.configuration).flatMap { configuration =>
222239 dependencyTreeForConfiguration(configuration)
@@ -235,25 +252,56 @@ class BomExtractor(settings: BomExtractorParams, report: UpdateReport, rootModul
235252 }
236253
237254 class DependencyTreeExtractor (configurationReport : ConfigurationReport ) {
238- def dependencyTree : Seq [Dependency ] =
255+ def dependencyTree : Seq [Dependency ] = {
239256 moduleGraph.nodes
257+ .filter(_.evictedByVersion.isEmpty)
240258 .sortBy(_.id.idString)
241259 .map { node =>
242- val bomRef = purl(node.id.organization, node.id.name, node.id.version)
260+ val bomRef = purl(node.id.organization, node.id.name, node.id.version, node.qualifier )
243261
244262 val dependency = new Dependency (bomRef)
245263
246264 val dependsOn = moduleGraph.dependencyMap.getOrElse(node.id, Nil ).sortBy(_.id.idString)
247265 dependsOn.foreach { module =>
248- val bomRef = purl(module.id.organization, module.id.name, module.id.version)
249- dependency.addDependency(new Dependency (bomRef))
266+ if (module.evictedByVersion.isEmpty) {
267+ val bomRef = purl(module.id.organization, module.id.name, module.id.version, module.qualifier)
268+
269+ dependency.addDependency(new Dependency (bomRef))
270+ }
250271 }
251272
252273 dependency
253274 }
275+ }
254276
255- private def moduleGraph : ModuleGraph = SbtUpdateReport .fromConfigurationReport(configurationReport, rootModuleID)
277+ private def moduleGraph : ModuleGraph =
278+ SbtUpdateReport .fromConfigurationReport(configurationReport, rootModuleID, log)
256279 }
280+
281+ private def toCycloneDxProjectType (e : ProjectType ): Component .Type = {
282+ e match {
283+ case APPLICATION => Component .Type .APPLICATION
284+ case FRAMEWORK => Component .Type .FRAMEWORK
285+ case LIBRARY => Component .Type .LIBRARY
286+ case CONTAINER => Component .Type .CONTAINER
287+ case PLATFORM => Component .Type .PLATFORM
288+ case OPERATING_SYSTEM => Component .Type .OPERATING_SYSTEM
289+ case DEVICE => Component .Type .DEVICE
290+ case DEVICE_DRIVER => Component .Type .DEVICE_DRIVER
291+ case FIRMWARE => Component .Type .FIRMWARE
292+ case FILE => Component .Type .FILE
293+ case MACHINE_LEARNING_MODEL => Component .Type .MACHINE_LEARNING_MODEL
294+ case DATA => Component .Type .DATA
295+ case CRYPTOGRAPHIC_ASSET =>
296+ if (settings.schemaVersion.getVersion < Version .VERSION_16 .getVersion)
297+ throw new UnsupportedOperationException (
298+ " Current cyclonedx version does not support CRYPTOGRAPHIC_ASSET. Use 1.6 or newer"
299+ )
300+ else Component .Type .CRYPTOGRAPHIC_ASSET
301+
302+ }
303+ }
304+
257305 def logComponent (component : Component ): Unit = {
258306 log.info(s """ "
259307 | ${component.getGroup}" % " ${component.getName}" % " ${component.getVersion}",
@@ -263,3 +311,16 @@ class BomExtractor(settings: BomExtractorParams, report: UpdateReport, rootModul
263311 }
264312
265313}
314+
315+ object BomExtractor {
316+ private [sbom] def purl (
317+ group : String ,
318+ name : String ,
319+ version : String ,
320+ qualifier : Map [String , String ] = Map [String , String ]()
321+ ): String = {
322+ val convertedMap = new TM [String , String ](qualifier.asJava)
323+
324+ new PackageURL (PackageURL .StandardTypes .MAVEN , group, name, version, convertedMap, null ).canonicalize()
325+ }
326+ }
0 commit comments