@@ -7,7 +7,17 @@ package com.github.sbt.sbom
77import com .github .packageurl .PackageURL
88import com .github .sbt .sbom .licenses .LicensesArchive
99import org .cyclonedx .Version
10- import org .cyclonedx .model .{ Bom , Component , ExternalReference , Hash , License , LicenseChoice , Metadata , Tool }
10+ import org .cyclonedx .model .{
11+ Bom ,
12+ Component ,
13+ Dependency ,
14+ ExternalReference ,
15+ Hash ,
16+ License ,
17+ LicenseChoice ,
18+ Metadata ,
19+ Tool
20+ }
1121import org .cyclonedx .util .BomUtils
1222import sbt ._
1323import sbt .librarymanagement .ModuleReport
@@ -16,7 +26,9 @@ import java.util
1626import java .util .UUID
1727import scala .collection .JavaConverters ._
1828
19- class BomExtractor (settings : BomExtractorParams , report : UpdateReport , log : Logger ) {
29+ import SbtUpdateReport .ModuleGraph
30+
31+ class BomExtractor (settings : BomExtractorParams , report : UpdateReport , rootModuleID : ModuleID , log : Logger ) {
2032 private val serialNumber : String = " urn:uuid:" + UUID .randomUUID.toString
2133
2234 def bom : Bom = {
@@ -28,6 +40,9 @@ class BomExtractor(settings: BomExtractorParams, report: UpdateReport, log: Logg
2840 bom.setMetadata(metadata)
2941 }
3042 bom.setComponents(components.asJava)
43+ if (settings.includeBomDependencyTree && settings.schemaVersion.getVersion >= Version .VERSION_11 .getVersion) {
44+ bom.setDependencies(dependencyTree.asJava)
45+ }
3146 bom
3247 }
3348
@@ -114,9 +129,7 @@ class BomExtractor(settings: BomExtractorParams, report: UpdateReport, log: Logg
114129 component.setVersion(version)
115130 component.setModified(false )
116131 component.setType(Component .Type .LIBRARY )
117- component.setPurl(
118- new PackageURL (PackageURL .StandardTypes .MAVEN , group, name, version, new util.TreeMap (), null ).canonicalize()
119- )
132+ component.setPurl(purl(group, name, version))
120133 if (settings.schemaVersion.getVersion >= Version .VERSION_11 .getVersion) {
121134 // component bom-refs must be unique
122135 component.setBomRef(component.getPurl)
@@ -201,6 +214,46 @@ class BomExtractor(settings: BomExtractorParams, report: UpdateReport, log: Logg
201214 }
202215 }
203216
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+
220+ private def dependencyTree : Seq [Dependency ] = {
221+ val dependencyTree = configurationsForComponents(settings.configuration).flatMap { configuration =>
222+ dependencyTreeForConfiguration(configuration)
223+ }.distinct // deduplicate dependencies reported by multiple configurations
224+
225+ dependencyTree
226+ }
227+
228+ private def dependencyTreeForConfiguration (configuration : Configuration ): Seq [Dependency ] = {
229+ report
230+ .configuration(configuration)
231+ .toSeq
232+ .flatMap { configurationReport =>
233+ new DependencyTreeExtractor (configurationReport).dependencyTree
234+ }
235+ }
236+
237+ class DependencyTreeExtractor (configurationReport : ConfigurationReport ) {
238+ def dependencyTree : Seq [Dependency ] =
239+ moduleGraph.nodes
240+ .sortBy(_.id.idString)
241+ .map { node =>
242+ val bomRef = purl(node.id.organization, node.id.name, node.id.version)
243+
244+ val dependency = new Dependency (bomRef)
245+
246+ val dependsOn = moduleGraph.dependencyMap.getOrElse(node.id, Nil ).sortBy(_.id.idString)
247+ dependsOn.foreach { module =>
248+ val bomRef = purl(module.id.organization, module.id.name, module.id.version)
249+ dependency.addDependency(new Dependency (bomRef))
250+ }
251+
252+ dependency
253+ }
254+
255+ private def moduleGraph : ModuleGraph = SbtUpdateReport .fromConfigurationReport(configurationReport, rootModuleID)
256+ }
204257 def logComponent (component : Component ): Unit = {
205258 log.info(s """ "
206259 | ${component.getGroup}" % " ${component.getName}" % " ${component.getVersion}",
0 commit comments