@@ -5,6 +5,10 @@ use crate::domain::{Candidate, Toolchain};
55use crate :: version:: matches_req;
66use cyclonedx_bom:: models:: component:: { Classification , Component , Components } ;
77use cyclonedx_bom:: models:: dependency:: { Dependencies , Dependency } ;
8+ use cyclonedx_bom:: models:: external_reference:: {
9+ ExternalReference , ExternalReferenceType , ExternalReferences ,
10+ } ;
11+ use cyclonedx_bom:: models:: hash:: { Hash , HashAlgorithm , HashValue , Hashes } ;
812use cyclonedx_bom:: models:: lifecycle:: { Lifecycle , Lifecycles , Phase } ;
913use cyclonedx_bom:: models:: metadata:: Metadata ;
1014use cyclonedx_bom:: models:: property:: { Properties , Property } ;
@@ -608,6 +612,57 @@ pub fn package_plan_to_bom(plan: &PackagePlan) -> Result<Bom, PackageError> {
608612 . description
609613 . as_deref ( )
610614 . map ( NormalizedString :: new) ;
615+ root. hashes = plan
616+ . sources
617+ . first ( )
618+ . and_then ( |source| source. sha256 . as_deref ( ) )
619+ . map ( |checksum| Hashes ( vec ! [ sha256_hash( checksum) ] ) ) ;
620+ let mut source_references = Vec :: new ( ) ;
621+ let mut seen_references = BTreeSet :: new ( ) ;
622+ for source in & plan. sources {
623+ if let Some ( git) = source. git . as_deref ( ) {
624+ let key = ( ExternalReferenceType :: Vcs . to_string ( ) , git. to_string ( ) ) ;
625+ if seen_references. insert ( key) {
626+ let mut reference =
627+ ExternalReference :: new ( ExternalReferenceType :: Vcs , Uri :: new ( git) ) ;
628+ let identity = [
629+ source. tag . as_deref ( ) . map ( |tag| format ! ( "tag={tag}" ) ) ,
630+ source
631+ . commit
632+ . as_deref ( )
633+ . map ( |commit| format ! ( "commit={commit}" ) ) ,
634+ ]
635+ . into_iter ( )
636+ . flatten ( )
637+ . collect :: < Vec < _ > > ( )
638+ . join ( " " ) ;
639+ if !identity. is_empty ( ) {
640+ reference. comment = Some ( identity) ;
641+ }
642+ source_references. push ( reference) ;
643+ }
644+ }
645+ if let Some ( url) = source_archive_url ( source) {
646+ let key = ( ExternalReferenceType :: Distribution . to_string ( ) , url. clone ( ) ) ;
647+ if seen_references. insert ( key) {
648+ let mut reference = ExternalReference :: new (
649+ ExternalReferenceType :: Distribution ,
650+ Uri :: new ( & url) ,
651+ ) ;
652+ reference. hashes = source
653+ . sha256
654+ . as_deref ( )
655+ . map ( |checksum| Hashes ( vec ! [ sha256_hash( checksum) ] ) ) ;
656+ reference. comment = source. target_directory . as_deref ( ) . map ( |directory| {
657+ format ! ( "staged in package source directory {directory}" )
658+ } ) ;
659+ source_references. push ( reference) ;
660+ }
661+ }
662+ }
663+ if !source_references. is_empty ( ) {
664+ root. external_references = Some ( ExternalReferences ( source_references) ) ;
665+ }
611666 root. properties = Some ( Properties ( vec ! [
612667 Property :: new( "eb-stack:origin" , origin_name( & plan. origin) ) ,
613668 Property :: new( "eb-stack:lifecycle" , "pre-build-plan" ) ,
@@ -690,6 +745,27 @@ pub fn package_plan_to_bom(plan: &PackagePlan) -> Result<Bom, PackageError> {
690745 } )
691746}
692747
748+ fn sha256_hash ( checksum : & str ) -> Hash {
749+ Hash {
750+ alg : HashAlgorithm :: SHA_256 ,
751+ content : HashValue ( checksum. to_string ( ) ) ,
752+ }
753+ }
754+
755+ fn source_archive_url ( source : & SourceArtifact ) -> Option < String > {
756+ source. url . clone ( ) . or_else ( || {
757+ let git = source. git . as_deref ( ) ?;
758+ let base = git. trim_end_matches ( ".git" ) ;
759+ if let Some ( tag) = source. tag . as_deref ( ) {
760+ Some ( format ! ( "{base}/archive/refs/tags/{tag}.tar.gz" ) )
761+ } else if let Some ( commit) = source. commit . as_deref ( ) {
762+ Some ( format ! ( "{base}/archive/{commit}.tar.gz" ) )
763+ } else {
764+ None
765+ }
766+ } )
767+ }
768+
693769pub fn package_plan_to_cyclonedx ( plan : & PackagePlan ) -> Result < Value , PackageError > {
694770 let bom = package_plan_to_bom ( plan) ?;
695771 let mut output = Vec :: new ( ) ;
0 commit comments