Skip to content

Commit 0ddbbc2

Browse files
committed
Merge branch 'main' into 1727-sca-integration-scio-to-ort-workflow
Signed-off-by: tdruez <[email protected]> # Conflicts: # scanpipe/pipes/spdx.py
2 parents ee489bf + 30c23a3 commit 0ddbbc2

File tree

15 files changed

+1109
-75
lines changed

15 files changed

+1109
-75
lines changed

scanpipe/api/views.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,18 +153,19 @@ def results_download(self, request, *args, **kwargs):
153153
"""Return the results in the provided `output_format` as an attachment."""
154154
project = self.get_object()
155155
format = request.query_params.get("output_format", "json")
156+
156157
version = request.query_params.get("version")
157158
output_kwargs = {}
159+
if version:
160+
output_kwargs["version"] = version
158161

159162
if format == "json":
160163
return project_results_json_response(project, as_attachment=True)
161164
elif format == "xlsx":
162165
output_file = output.to_xlsx(project)
163166
elif format == "spdx":
164-
output_file = output.to_spdx(project)
167+
output_file = output.to_spdx(project, **output_kwargs)
165168
elif format == "cyclonedx":
166-
if version:
167-
output_kwargs["version"] = version
168169
output_file = output.to_cyclonedx(project, **output_kwargs)
169170
elif format == "attribution":
170171
output_file = output.to_attribution(project)

scanpipe/management/commands/output.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def handle_output(self, output_format):
7979
output_kwargs = {}
8080
if ":" in output_format:
8181
output_format, version = output_format.split(":", maxsplit=1)
82-
if output_format != "cyclonedx":
82+
if output_format not in ["cyclonedx", "spdx"]:
8383
raise CommandError(
8484
'The ":" version syntax is only supported for the cyclonedx format.'
8585
)

scanpipe/models.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1248,7 +1248,12 @@ def add_upload(self, uploaded_file, tag=""):
12481248
adds the `input_source`.
12491249
"""
12501250
self.write_input_file(uploaded_file)
1251-
self.add_input_source(filename=uploaded_file.name, is_uploaded=True, tag=tag)
1251+
input_source = self.add_input_source(
1252+
filename=uploaded_file.name,
1253+
is_uploaded=True,
1254+
tag=tag,
1255+
)
1256+
return input_source
12521257

12531258
def add_uploads(self, uploads):
12541259
"""

scanpipe/pipes/output.py

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io
2626
import json
2727
import re
28+
import uuid
2829
from operator import attrgetter
2930
from pathlib import Path
3031

@@ -694,27 +695,83 @@ def get_dependency_as_spdx_relationship(dependency, document_spdx_id, packages_a
694695
return spdx_relationship
695696

696697

697-
def to_spdx(project, include_files=False):
698+
def get_inputs_as_spdx_packages(project):
699+
"""Return the Project's inputs as SPDX package to be used as root elements."""
700+
inputs_as_spdx_packages = []
701+
702+
for input_source in project.get_inputs_with_source():
703+
input_uuid = input_source.get("uuid") or uuid.uuid4()
704+
705+
input_as_spdx_package = spdx.Package(
706+
spdx_id=f"SPDXRef-scancodeio-input-{input_uuid}",
707+
name=input_source.get("filename"),
708+
filename=input_source.get("filename"),
709+
download_location=input_source.get("download_url"),
710+
files_analyzed=True,
711+
)
712+
inputs_as_spdx_packages.append(input_as_spdx_package)
713+
714+
return inputs_as_spdx_packages
715+
716+
717+
def to_spdx(project, version=spdx.SPDX_SPEC_VERSION_2_3, include_files=False):
698718
"""
699719
Generate output for the provided ``project`` in SPDX document format.
700720
The output file is created in the ``project`` "output/" directory.
701721
Return the path of the generated output file.
702722
"""
723+
if version not in [spdx.SPDX_SPEC_VERSION_2_2, spdx.SPDX_SPEC_VERSION_2_3]:
724+
raise ValueError(f"SPDX {version} is not supported.")
725+
703726
output_file = project.get_output_file_path("results", "spdx.json")
727+
document_spdx_id = f"SPDXRef-DOCUMENT-{project.uuid}"
704728

705729
discoveredpackage_qs = get_queryset(project, "discoveredpackage")
706730
discovereddependency_qs = get_queryset(project, "discovereddependency")
707731

708-
document_spdx_id = f"SPDXRef-DOCUMENT-{project.uuid}"
709732
packages_as_spdx = []
710733
license_expressions = []
711734
relationships = []
712735

736+
project_inputs_as_spdx_packages = get_inputs_as_spdx_packages(project)
737+
738+
if project_inputs_as_spdx_packages:
739+
packages_as_spdx.extend(project_inputs_as_spdx_packages)
740+
741+
# Use the Project's input as the root element that the SPDX document describes.
742+
# This ensures "documentDescribes" points only to the main subject of the SBOM,
743+
# not to every dependency or file in the project.
744+
# See https://github.com/spdx/spdx-spec/issues/395 and
745+
# https://github.com/aboutcode-org/scancode.io/issues/564#issuecomment-3269296563
746+
# for detailed context.
747+
if len(project_inputs_as_spdx_packages) == 1:
748+
describe_spdx_id = project_inputs_as_spdx_packages[0].spdx_id
749+
750+
# Fallback to the Project as the SPDX root element for the "documentDescribes",
751+
# if more than one input, or if no inputs, are available.
752+
else:
753+
project_as_root_package = spdx.Package(
754+
spdx_id=f"SPDXRef-scancodeio-project-{project.uuid}",
755+
name=project.name,
756+
files_analyzed=True,
757+
)
758+
packages_as_spdx.append(project_as_root_package)
759+
describe_spdx_id = project_as_root_package.spdx_id
760+
713761
for package in discoveredpackage_qs:
714-
packages_as_spdx.append(package.as_spdx())
762+
spdx_package = package.as_spdx()
763+
packages_as_spdx.append(spdx_package)
764+
715765
if license_expression := package.declared_license_expression:
716766
license_expressions.append(license_expression)
717767

768+
spdx_relationship = spdx.Relationship(
769+
spdx_id=describe_spdx_id,
770+
related_spdx_id=spdx_package.spdx_id,
771+
relationship="DEPENDS_ON",
772+
)
773+
relationships.append(spdx_relationship)
774+
718775
for dependency in discovereddependency_qs:
719776
spdx_relationship = get_dependency_as_spdx_relationship(
720777
dependency,
@@ -731,9 +788,11 @@ def to_spdx(project, include_files=False):
731788
]
732789

733790
document = spdx.Document(
791+
version=version,
734792
spdx_id=document_spdx_id,
735793
name=f"scancodeio_{project.name}",
736794
namespace=f"https://scancode.io/spdxdocs/{project.uuid}",
795+
describes=[describe_spdx_id],
737796
creation_info=spdx.CreationInfo(tool=f"ScanCode.io-{scancodeio_version}"),
738797
packages=packages_as_spdx,
739798
files=files_as_spdx,

0 commit comments

Comments
 (0)