Skip to content

Commit fe85a08

Browse files
authored
Add authors field to the CycloneDX output (#1990)
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent 4bae30c commit fe85a08

File tree

5 files changed

+137
-7
lines changed

5 files changed

+137
-7
lines changed

scanpipe/models.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
from commoncode.fileutils import parent_directory
7474
from cyclonedx import model as cyclonedx_model
7575
from cyclonedx.model import component as cyclonedx_component
76+
from cyclonedx.model import contact as cyclonedx_contact
7677
from cyclonedx.model import license as cyclonedx_license
7778
from extractcode import EXTRACT_SUFFIX
7879
from licensedcode.cache import build_spdx_license_expression
@@ -3666,6 +3667,42 @@ class AbstractPackage(models.Model):
36663667
class Meta:
36673668
abstract = True
36683669

3670+
def extract_from_parties(self, roles):
3671+
"""
3672+
Extract parties matching the given roles, deduplicated by name.
3673+
3674+
Args:
3675+
roles: Tuple of role strings to filter by.
3676+
3677+
Returns:
3678+
List of party dicts matching the specified roles, unique by name.
3679+
3680+
"""
3681+
seen_names = set()
3682+
results = []
3683+
for party in self.parties or []:
3684+
if party.get("role") in roles:
3685+
name = party.get("name")
3686+
if name and name not in seen_names:
3687+
seen_names.add(name)
3688+
results.append(party)
3689+
return results
3690+
3691+
def get_author_names(self, roles=("author", "maintainer")):
3692+
"""
3693+
Return a sorted list of party names matching the specified roles.
3694+
3695+
Args:
3696+
roles: Tuple of role strings to filter by.
3697+
Defaults to ("author", "maintainer").
3698+
3699+
Returns:
3700+
Sorted list of party names.
3701+
3702+
"""
3703+
parties = self.extract_from_parties(roles=roles)
3704+
return sorted(party["name"] for party in parties)
3705+
36693706

36703707
class DiscoveredPackage(
36713708
ProjectRelatedModel,
@@ -3952,6 +3989,14 @@ def as_cyclonedx(self):
39523989
if (hash_value := getattr(self, field_name))
39533990
]
39543991

3992+
authors = [
3993+
cyclonedx_contact.OrganizationalContact(
3994+
name=party.get("name", ""),
3995+
email=party.get("email", ""),
3996+
)
3997+
for party in self.extract_from_parties(roles=("author", "maintainer"))
3998+
]
3999+
39554000
# Those fields are not supported natively by CycloneDX but are required to
39564001
# load the BOM without major data loss.
39574002
# See https://github.com/nexB/aboutcode-cyclonedx-taxonomy
@@ -4012,6 +4057,7 @@ def as_cyclonedx(self):
40124057
properties=properties,
40134058
external_references=external_references,
40144059
evidence=evidence,
4060+
authors=authors,
40154061
)
40164062

40174063

scanpipe/pipes/ort.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,6 @@ def to_ort_package_list_yml(project):
136136

137137
dependencies = []
138138
for package in project.discoveredpackages.all():
139-
authors = {
140-
party.get("name").strip()
141-
for party in package.parties
142-
if party.get("role") in ("author", "maintainer") and party.get("name")
143-
}
144-
145139
dependency = Dependency(
146140
id=f"{project_type or package.type}::{package.name}:{package.version}",
147141
purl=package.purl,
@@ -150,7 +144,7 @@ def to_ort_package_list_yml(project):
150144
vcs=Vcs(url=package.vcs_url),
151145
description=package.description,
152146
homepageUrl=package.homepage_url,
153-
authors=sorted(authors),
147+
authors=package.get_author_names(),
154148
)
155149
dependencies.append(dependency)
156150

scanpipe/tests/__init__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,42 @@ def make_mock_response(url, content=b"\x00", status_code=200, headers=None):
196196
"extra_data": {},
197197
}
198198

199+
parties_data1 = [
200+
{
201+
"name": "AboutCode and others",
202+
"role": "author",
203+
"type": "person",
204+
"email": "info@aboutcode.org",
205+
"url": None,
206+
},
207+
# Duplicate on purpose
208+
{
209+
"name": "AboutCode and others",
210+
"role": "author",
211+
"type": "person",
212+
"email": "info@aboutcode.org",
213+
"url": None,
214+
},
215+
{
216+
"name": "Debian X Strike Force",
217+
"role": "maintainer",
218+
"email": "debian-x@lists.debian.org",
219+
},
220+
{
221+
"name": "JBoss.org Community",
222+
"role": "developer",
223+
"type": "person",
224+
"email": None,
225+
},
226+
{
227+
"url": "http://www.apache.org/",
228+
"name": "The Apache Software Foundation",
229+
"role": "owner",
230+
"type": "organization",
231+
"email": None,
232+
},
233+
]
234+
199235
package_data1 = {
200236
"type": "deb",
201237
"namespace": "debian",

0 commit comments

Comments
 (0)