Skip to content

Commit a61fe17

Browse files
authored
Merge pull request #3005 from nexB/2987-cyclonedx
Do not fail without packages in cyclonedx #2987
2 parents 7394e79 + c1599f2 commit a61fe17

File tree

3 files changed

+84
-9
lines changed

3 files changed

+84
-9
lines changed

src/formattedcode/output_cyclonedx.py

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
# See https://aboutcode.org for more information about nexB OSS projects.
99
#
1010

11+
import os
1112
import json
13+
import logging
1214
import uuid
1315
from collections import defaultdict
1416
from datetime import datetime
1517
from enum import Enum
1618
from typing import List
19+
import warnings
1720

1821
import attr
1922
from lxml import etree
@@ -26,6 +29,25 @@
2629
from 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+
2951
class 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
600637
class 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,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"bomFormat": "CycloneDX",
3+
"specVersion": "1.3",
4+
"version": 1,
5+
"components": [],
6+
"dependencies": []
7+
}

tests/formattedcode/test_output_cyclonedx.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,16 +197,17 @@ def test_CycloneDxMetadata_from_headers():
197197
'notice': 'some notice',
198198
'message': 'some message',
199199
'errors': ['some error'],
200-
'extra_data': {'WARNING': 'some warning', 'spdx_version': '3.1.2'}
200+
'warnings': ['some warning'],
201+
'extra_data': {'spdx_version': '3.1.2'}
201202
}]
202203
m = CycloneDxMetadata.from_headers(headers).to_dict()
203204
m.pop('timestamp')
204205
expected = {
205206
'properties': [
206207
{'name': 'notice', 'value': 'some notice'},
207208
{'name': 'errors', 'value': ['some error']},
209+
{'name': 'warnings', 'value': ['some warning']},
208210
{'name': 'message', 'value': 'some message'},
209-
{'name': 'WARNING', 'value': 'some warning'},
210211
{'name': 'spdx_version', 'value': '3.1.2'},
211212
],
212213
'tools': [
@@ -216,6 +217,14 @@ def test_CycloneDxMetadata_from_headers():
216217
assert m == expected
217218

218219

220+
def test_cyclonedx_plugin_does_not_fail_without_packages():
221+
test_dir = test_env.get_test_loc('cyclonedx/simple')
222+
result_file = test_env.get_temp_file('cyclonedx.json')
223+
run_scan_click([test_dir, '--cyclonedx', result_file])
224+
expected_file = test_env.get_test_loc('cyclonedx/expected-without-packages.json')
225+
check_cyclone_output(expected_file, result_file, regen=REGEN_TEST_FIXTURES)
226+
227+
219228
def test_cyclonedx_plugin_json():
220229
test_dir = test_env.get_test_loc('cyclonedx/simple')
221230
result_file = test_env.get_temp_file('cyclonedx.json')

0 commit comments

Comments
 (0)