Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6ee1f23
Add test example for issue 941
stefan6419846 Mar 5, 2026
ca1946f
Merge branch 'main' into issue941-test
jkowalleck Jun 4, 2026
0993a4c
Merge branch 'main' into issue941-test
jkowalleck Jun 8, 2026
a26899c
wip: deps flatten
jkowalleck Jun 8, 2026
e1b4ab9
fix: json flatten dep graph
jkowalleck Jun 8, 2026
3af721b
refactor
jkowalleck Jun 8, 2026
de53cf8
refactor
jkowalleck Jun 8, 2026
70327bc
fix: json flatten and merge dep graph
jkowalleck Jun 8, 2026
293bd75
tests
jkowalleck Jun 8, 2026
bda96aa
wip
jkowalleck Jun 9, 2026
ab738f1
wip
jkowalleck Jun 9, 2026
6b3894d
wip
jkowalleck Jun 10, 2026
24a06b7
wip
jkowalleck Jun 10, 2026
6223670
wip
jkowalleck Jun 10, 2026
6da7563
docs
jkowalleck Jun 10, 2026
265c4d8
tests
jkowalleck Jun 10, 2026
811127c
tests
jkowalleck Jun 10, 2026
c6ff130
tests
jkowalleck Jun 10, 2026
7180274
tests
jkowalleck Jun 10, 2026
649e265
wip
jkowalleck Jun 10, 2026
dadcd0b
wip
jkowalleck Jun 10, 2026
a208fec
wip
jkowalleck Jun 10, 2026
1afafac
tidy
jkowalleck Jun 10, 2026
fd89a41
Merge remote-tracking branch 'origin/main' into fix/dependencygraph-f…
jkowalleck Jun 11, 2026
b3e3e62
docs
jkowalleck Jun 11, 2026
3441d97
docs
jkowalleck Jun 11, 2026
896771e
wip
jkowalleck Jun 11, 2026
4663019
docs
jkowalleck Jun 11, 2026
5eb3397
Merge remote-tracking branch 'origin/main' into fix/dependencygraph-f…
jkowalleck Jun 11, 2026
1e9b399
Merge remote-tracking branch 'origin/main' into fix/dependencygraph-f…
jkowalleck Jun 11, 2026
76f1830
Merge remote-tracking branch 'origin/main' into fix/dependencygraph-f…
jkowalleck Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions cyclonedx/output/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
# Copyright (c) OWASP Foundation. All Rights Reserved.

from abc import abstractmethod
from itertools import chain
from json import dumps as json_dumps, loads as json_loads
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
from typing import TYPE_CHECKING, Any, Iterable, Literal, Optional, Union

from ..exception.output import FormatNotSupportedException
from ..model.dependency import Dependency
from ..schema import OutputFormat, SchemaVersion
from ..schema.schema import (
SCHEMA_VERSIONS,
Expand All @@ -39,6 +41,48 @@
from ..model.bom import Bom


class _BomDependencyGraphFlattener:
"""
!!! THIS CLASS IS INTERNAL.
Everything might change without any notice.
"""

def __init__(self, bom: 'Bom'):
self._bom = bom
# do NOT use the getter - see `reset()` for reasons
self._deps = self._bom._dependencies

def __enter__(self) -> None:
self.flatten()

def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
self.reset()

def reset(self) -> None:
# Do NOT use the setter - this would create overhead and most importantly,
# and this could cause deduplication of an existing malformed set.
# Just access the internal field directly!
self._bom._dependencies = self._deps

def flatten(self) -> None:
self._bom.dependencies = chain.from_iterable(
self.__flatten_dep(dep) for dep in self._deps
)

@staticmethod
def __flatten_dep(dep: Dependency) -> Iterable[Dependency]:
if not dep.dependencies:
return dep,
flat: list[Dependency] = []
todos: list[Dependency] = [dep]
while todos:
todo = todos.pop()
if todo.dependencies:
flat.append(Dependency(todo.ref, (Dependency(d.ref) for d in todo.dependencies)))
todos.extend(todo.dependencies)
return flat


class Json(BaseOutput, BaseSchemaVersion):

def __init__(self, bom: 'Bom') -> None:
Expand Down Expand Up @@ -70,10 +114,12 @@ def generate(self, force_regeneration: bool = False) -> None:
_view = SCHEMA_VERSIONS.get(self.schema_version_enum)
bom = self.get_bom()
bom.validate()
# utilize contrib.dependency.flatten() somewhere here
with BomRefDiscriminator.from_bom(bom):
bom_json: dict[str, Any] = json_loads(
bom.as_json( # type:ignore[attr-defined]
view_=_view))
with _BomDependencyGraphFlattener(bom):
bom_json: dict[str, Any] = json_loads(
bom.as_json( # type:ignore[attr-defined]
view_=_view))
Comment thread
jkowalleck marked this conversation as resolved.
bom_json.update(_json_core)
self._bom_json = bom_json
self.generated = True
Expand Down
39 changes: 39 additions & 0 deletions tests/_data/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,45 @@ def get_bom_for_issue540_duplicate_components() -> Bom:
bom.register_dependency(component1, [component3])
return bom


def get_bom_for_issue941_nested_dependencies_irreversible_migrate() -> Bom:
bom = _make_bom()
bom.metadata.component = root_component = Component(
name='myApp',
type=ComponentType.APPLICATION,
bom_ref='myApp'
)

component1 = Component(
type=ComponentType.LIBRARY,
name='some-library',
bom_ref='some-library1'
)
bom.components.add(component1)

component2 = Component(
type=ComponentType.LIBRARY,
name='some-library',
bom_ref='some-library2'
)
bom.components.add(component2)

bom.dependencies.add(
Dependency(
root_component.bom_ref,
dependencies=[
Dependency(
component1.bom_ref,
dependencies=[
Dependency(component2.bom_ref)
]
)
]
)
)

return bom

# ---


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" ?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.0" version="1">
<components>
<component type="library">
<name>some-library</name>
<version/>
<modified>false</modified>
</component>
<component type="library">
<name>some-library</name>
<version/>
<modified>false</modified>
</component>
</components>
</bom>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" ?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.1" serialNumber="urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac" version="1">
<components>
<component type="library" bom-ref="some-library1">
<name>some-library</name>
<version/>
</component>
<component type="library" bom-ref="some-library2">
<name>some-library</name>
<version/>
</component>
</components>
</bom>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"components": [
{
"bom-ref": "some-library1",
"name": "some-library",
"type": "library",
"version": ""
},
{
"bom-ref": "some-library2",
"name": "some-library",
"type": "library",
"version": ""
}
],
"dependencies": [
{
"dependsOn": [
"some-library1"
],
"ref": "myApp"
},
{
"dependsOn": [
"some-library2"
],
"ref": "some-library1"
},
{
"ref": "some-library1"
},
{
"ref": "some-library2"
}
],
"metadata": {
"component": {
"bom-ref": "myApp",
"name": "myApp",
"type": "application",
"version": ""
},
"timestamp": "2023-01-07T13:44:32.312678+00:00"
},
"serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac",
"version": 1,
"$schema": "http://cyclonedx.org/schema/bom-1.2b.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.2"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" ?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.2" serialNumber="urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac" version="1">
<metadata>
<timestamp>2023-01-07T13:44:32.312678+00:00</timestamp>
<component type="application" bom-ref="myApp">
<name>myApp</name>
<version/>
</component>
</metadata>
<components>
<component type="library" bom-ref="some-library1">
<name>some-library</name>
<version/>
</component>
<component type="library" bom-ref="some-library2">
<name>some-library</name>
<version/>
</component>
</components>
<dependencies>
<dependency ref="myApp">
<dependency ref="some-library1">
<dependency ref="some-library2"/>
</dependency>
</dependency>
<dependency ref="some-library1"/>
<dependency ref="some-library2"/>
</dependencies>
</bom>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"components": [
{
"bom-ref": "some-library1",
"name": "some-library",
"type": "library",
"version": ""
},
{
"bom-ref": "some-library2",
"name": "some-library",
"type": "library",
"version": ""
}
],
"dependencies": [
{
"dependsOn": [
"some-library1"
],
"ref": "myApp"
},
{
"dependsOn": [
"some-library2"
],
"ref": "some-library1"
},
{
"ref": "some-library1"
},
{
"ref": "some-library2"
}
],
"metadata": {
"component": {
"bom-ref": "myApp",
"name": "myApp",
"type": "application",
"version": ""
},
"timestamp": "2023-01-07T13:44:32.312678+00:00"
},
"serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac",
"version": 1,
"$schema": "http://cyclonedx.org/schema/bom-1.3a.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.3"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" ?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.3" serialNumber="urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac" version="1">
<metadata>
<timestamp>2023-01-07T13:44:32.312678+00:00</timestamp>
<component type="application" bom-ref="myApp">
<name>myApp</name>
<version/>
</component>
</metadata>
<components>
<component type="library" bom-ref="some-library1">
<name>some-library</name>
<version/>
</component>
<component type="library" bom-ref="some-library2">
<name>some-library</name>
<version/>
</component>
</components>
<dependencies>
<dependency ref="myApp">
<dependency ref="some-library1">
<dependency ref="some-library2"/>
</dependency>
</dependency>
<dependency ref="some-library1"/>
<dependency ref="some-library2"/>
</dependencies>
</bom>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"components": [
{
"bom-ref": "some-library1",
"name": "some-library",
"type": "library"
},
{
"bom-ref": "some-library2",
"name": "some-library",
"type": "library"
}
],
"dependencies": [
{
"dependsOn": [
"some-library1"
],
"ref": "myApp"
},
{
"dependsOn": [
"some-library2"
],
"ref": "some-library1"
},
{
"ref": "some-library1"
},
{
"ref": "some-library2"
}
],
"metadata": {
"component": {
"bom-ref": "myApp",
"name": "myApp",
"type": "application"
},
"timestamp": "2023-01-07T13:44:32.312678+00:00"
},
"serialNumber": "urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac",
"version": 1,
"$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.4"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" ?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:1441d33a-e0fc-45b5-af3b-61ee52a88bac" version="1">
<metadata>
<timestamp>2023-01-07T13:44:32.312678+00:00</timestamp>
<component type="application" bom-ref="myApp">
<name>myApp</name>
</component>
</metadata>
<components>
<component type="library" bom-ref="some-library1">
<name>some-library</name>
</component>
<component type="library" bom-ref="some-library2">
<name>some-library</name>
</component>
</components>
<dependencies>
<dependency ref="myApp">
<dependency ref="some-library1">
<dependency ref="some-library2"/>
</dependency>
</dependency>
<dependency ref="some-library1"/>
<dependency ref="some-library2"/>
</dependencies>
</bom>
Loading
Loading