88# See https://aboutcode.org for more information about nexB OSS projects.
99#
1010
11+ import os
1112import json
13+ import logging
1214import uuid
1315from collections import defaultdict
1416from datetime import datetime
1517from enum import Enum
1618from typing import List
19+ import warnings
1720
1821import attr
1922from lxml import etree
2629from plugincode .output import output_impl
2730
2831
32+ TRACE = os .environ .get ('SCANCODE_DEBUG_OUTPUTS' , False )
33+
34+
35+ def logger_debug (* args ):
36+ pass
37+
38+
39+ logger = logging .getLogger (__name__ )
40+
41+ if TRACE :
42+ import sys
43+
44+ logging .basicConfig (stream = sys .stdout )
45+ logger .setLevel (logging .DEBUG )
46+
47+ def logger_debug (* args ):
48+ return logger .debug (' ' .join (isinstance (a , str ) and a or repr (a ) for a in args ))
49+
50+
2951class ToDictMixin :
3052
3153 def to_dict (self ):
@@ -274,7 +296,7 @@ def from_package(cls, package):
274296
275297 purl = package .get ('purl' )
276298
277- return CycloneDxComponent (
299+ return cls (
278300 bom_ref = purl ,
279301 purl = purl ,
280302 name = name ,
@@ -301,7 +323,7 @@ def from_packages(cls, packages):
301323 """
302324 components_by_purl = defaultdict (list )
303325 for package in packages :
304- comp = CycloneDxComponent .from_package (package )
326+ comp = cls .from_package (package )
305327 if not comp :
306328 continue
307329 components_by_purl [comp .purl ].append (comp )
@@ -497,7 +519,7 @@ def from_package(cls, package, components_by_purl):
497519 warnings_by_dependent [purl ].append (msg )
498520
499521 for ref , dependsOn in dependencies_by_dependent .items ():
500- yield CycloneDxDependency (
522+ yield cls (
501523 ref = ref ,
502524 dependsOn = dependsOn ,
503525 warnings = warnings_by_dependent .get (purl , [])
@@ -556,6 +578,11 @@ def from_headers(cls, headers):
556578 headers = [h for h in headers if h .get ('tool_name' ) == 'scancode-toolkit' ]
557579 scancode_header = headers [0 ] if headers else {}
558580
581+ if TRACE :
582+ logger_debug ('CycloneDxMetadata: headers' )
583+ from pprint import pformat
584+ logger_debug (pformat (headers ))
585+
559586 try :
560587 tool_header = {
561588 'vendor' : 'AboutCode.org' ,
@@ -568,11 +595,17 @@ def from_headers(cls, headers):
568595 props = dict (
569596 notice = scancode_header .get ('notice' ),
570597 errors = scancode_header .get ('errors' , []),
598+ warnings = scancode_header .get ('warnings' , []),
571599 message = scancode_header .get ('message' ),
572600 )
573601 props .update (scancode_header .get ('extra_data' , {}))
574602 properties = [CycloneDxProperty (k , v ) for k , v in props .items ()]
575603
604+ if TRACE :
605+ logger_debug ('CycloneDxMetadata: properties' )
606+ from pprint import pformat
607+ logger_debug (pformat (properties ))
608+
576609 return CycloneDxMetadata (
577610 tools = [tool_header ],
578611 properties = properties ,
@@ -596,6 +629,10 @@ def to_xml_element(self):
596629 return xmetadata
597630
598631
632+ class CycloneDxPluginNoPackagesWarning (DeprecationWarning ):
633+ pass
634+
635+
599636@attr .s
600637class CycloneDxBom :
601638 """
@@ -629,10 +666,32 @@ def from_codebase(cls, codebase):
629666 """
630667 Return a CycloneDxBom built from a ScanCode ``codebase``.
631668 """
632- metadata = CycloneDxMetadata .from_headers (codebase .get_headers ())
633- packages = codebase .attributes .packages
634- components = list (CycloneDxComponent .from_packages (packages ))
635- dependencies = list (CycloneDxDependency .from_packages (packages , components ))
669+ components = []
670+ dependencies = []
671+
672+ packages_not_found_message = (
673+ "The --cyclonedx-xml option will not output any component/dependency data "
674+ "as there are no package data in the present scan. To get package data "
675+ "please rerun the scan with --package or --system-package CLI options enabled."
676+ )
677+ codebase .get_or_create_current_header ()
678+
679+ if hasattr (codebase .attributes , 'packages' ):
680+ packages = codebase .attributes .packages
681+ components = list (CycloneDxComponent .from_packages (packages ))
682+ dependencies = list (CycloneDxDependency .from_packages (packages , components ))
683+ else :
684+ warnings .simplefilter ('always' , CycloneDxPluginNoPackagesWarning )
685+ warnings .warn (
686+ packages_not_found_message ,
687+ CycloneDxPluginNoPackagesWarning ,
688+ stacklevel = 2 ,
689+ )
690+ headers = codebase .get_or_create_current_header ()
691+ headers .warnings .append (packages_not_found_message )
692+
693+ codebase_headers = codebase .get_headers ()
694+ metadata = CycloneDxMetadata .from_headers (codebase_headers )
636695
637696 return CycloneDxBom (
638697 metadata = metadata ,
0 commit comments