Skip to content

Commit 69179dd

Browse files
committed
feat(analyzer): Add initial analyzer models
Signed-off-by: Helio Chissini de Castro <heliocastro@gmail.com>
1 parent 8a51173 commit 69179dd

23 files changed

Lines changed: 826 additions & 327 deletions

src/ort/models/analyzer_result.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro <heliocastro@gmail.com>
2+
# SPDX-License-Identifier: MIT
3+
4+
5+
from _pytest.python import Package
6+
from pydantic import BaseModel, ConfigDict, Field
7+
8+
from ort.models.dependency_graph import DependencyGraph
9+
from ort.models.identifier import Identifier
10+
from ort.models.issue import Issue
11+
from ort.models.project import Project
12+
13+
14+
class AnalyzerResult(BaseModel):
15+
"""
16+
A class that merges all information from individual [ProjectAnalyzerResult]s created for each found definition file.
17+
"""
18+
19+
model_config = ConfigDict(
20+
extra="forbid",
21+
)
22+
projects: set[Project] = Field(
23+
description="Sorted set of the projects, as they appear in the individual analyzer results.",
24+
)
25+
packages: set[Package] = Field(
26+
description="The set of identified packages for all projects.",
27+
)
28+
issues: dict[Identifier, list[Issue]] = Field(
29+
description="The lists of Issue objects that occurred within the analyzed projects themselves. Issues related"
30+
"to project dependencies are contained in the dependencies of the project's scopes. This property is not"
31+
"serialized if the map is empty to reduce the size of the result file.",
32+
)
33+
dependency_graphs: dict[str, DependencyGraph] = Field(
34+
description="A map with DependencyGraph objects keyed by the name of the package manager that created this"
35+
"graph. Package managers supporting this feature can construct a shared DependencyGraph over all projects and"
36+
"store it in this map.",
37+
)

src/ort/models/analyzer_run.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro <heliocastro@gmail.com>
2+
# SPDX-License-Identifier: MIT
3+
4+
from datetime import datetime
5+
6+
from pydantic import BaseModel, ConfigDict, Field
7+
8+
from ort.models.analyzer_result import AnalyzerResult
9+
from ort.models.config.analyzer_configuration import AnalyzerConfiguration
10+
from ort.utils.environment import Environment
11+
12+
13+
class AnalyzerRun(BaseModel):
14+
"""
15+
The summary of a single run of the analyzer.
16+
17+
"""
18+
19+
model_config = ConfigDict(
20+
extra="forbid",
21+
)
22+
start_time: datetime = Field(
23+
description="The time the analyzer was started.",
24+
)
25+
end_time: datetime = Field(
26+
description="The time the analyzer has finished.",
27+
)
28+
environment: Environment = Field(
29+
description="The [Environment] in which the analyzer was executed.",
30+
)
31+
config: AnalyzerConfiguration = Field(
32+
description="The [AnalyzerConfiguration] used for this run.",
33+
)
34+
result: AnalyzerResult | None = Field(
35+
default=None,
36+
description="The result of this run.",
37+
)

src/ort/models/config/analyzer_configuration.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,9 @@
3838

3939
class AnalyzerConfiguration(BaseModel):
4040
"""
41-
Enable the analysis of projects that use version ranges to declare their dependencies. If set to true,
42-
dependencies of exactly the same project might change with another scan done at a later time if any of the
43-
(transitive) dependencies are declared using version ranges and a new version of such a dependency was
44-
published in the meantime. If set to false, analysis of projects that use version ranges will fail. Defaults to
45-
false.
41+
The configuration model of the analyzer. This class is (de-)serialized in the following places:
42+
- Deserialized from "config.yml" as part of [OrtConfiguration] (via Hoplite).
43+
- (De-)Serialized as part of [org.ossreviewtoolkit.model.OrtResult] (via Jackson).
4644
"""
4745

4846
model_config = ConfigDict(

src/ort/models/dependency_graph.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro <heliocastro@gmail.com>
2+
# SPDX-License-Identifier: MIT
3+
4+
5+
from pydantic import BaseModel, ConfigDict, Field
6+
7+
from ort.models.dependency_graph_edge import DependencyGraphEdge
8+
from ort.models.dependency_graph_node import DependencyGraphNode
9+
from ort.models.dependency_reference import DependencyReference
10+
from ort.models.identifier import Identifier
11+
from ort.models.root_dependency_index import RootDependencyIndex
12+
13+
14+
class DependencyGraph(BaseModel):
15+
"""
16+
Represents the graph of dependencies of a project.
17+
18+
This class holds information about a project's scopes and their dependencies in a format that minimizes the
19+
consumption of memory. In projects with many scopes there is often a high degree of duplication in the dependencies
20+
of the scopes. To avoid this, this class aims to share as many parts of the dependency graph as possible between
21+
the different scopes. Ideally, there is only a single dependency graph containing the dependencies used by all
22+
scopes. This is not always possible due to inconsistencies in dependency relations, like a package using different
23+
dependencies in different scopes. Then the dependency graph is split into multiple fragments, and each fragment has
24+
a consistent view on the dependencies it contains.
25+
26+
When constructing a dependency graph the dependencies are organized as a connected structure of DependencyReference
27+
objects in memory. Originally, the serialization format of a graph was based on this structure, but that turned out
28+
to be not ideal: During serialization, sub graphs referenced from multiple nodes (e.g. libraries with transitive
29+
dependencies referenced from multiple projects) get duplicated, which can cause a significant amount of redundancy.
30+
Therefore, the data representation has been changed again to a form, which can be serialized without introducing
31+
redundancy. It consists of the following elements:
32+
33+
- packages: A list with the coordinates of all the packages (free of duplication) that are referenced by the graph.
34+
This allows extracting the packages directly, but also has the advantage that the package coordinates do not have
35+
to be repeated over and over: All the references to packages are expressed by indices into this list.
36+
- nodes: An ordered list with the nodes of the dependency graph. A single node represents a package, and therefore
37+
has a reference into the list with package coordinates. It can, however, happen that packages occur multiple
38+
times in the graph if they are in different subtrees with different sets of transitive dependencies. Then there
39+
are multiple nodes for the packages affected, and a fragment_index is used to identify them uniquely. Nodes also
40+
store information about issues of a package and their linkage.
41+
- edges: Here the structure of the graph comes in. Each edge connects two nodes and represents a directed
42+
depends-on relationship. The nodes are referenced by numeric indices into the list of nodes.
43+
- scopes: This is a map that associates the scopes used by projects with their direct dependencies. A single
44+
dependency graph contains the dependencies of all the projects processed by a specific package manager.
45+
Therefore, the keys of this map are scope names qualified by the coordinates of a project; which makes them
46+
unique. The values are references to the nodes in the graph that correspond to the packages the scopes depend on
47+
directly.
48+
49+
To navigate this structure, start with a scope and gather the references to its direct dependency nodes. Then, by
50+
following the edges starting from these nodes, the set of transitive dependencies can be determined. The numeric
51+
indices can be resolved via the packages list.
52+
"""
53+
54+
model_config = ConfigDict(
55+
extra="forbid",
56+
)
57+
58+
packages: list[Identifier] = Field(
59+
...,
60+
description="A list with the identifiers of the packages that appear in the dependency graph. This list is "
61+
"used to resolve the numeric indices contained in the dependency_graph_node objects.",
62+
)
63+
64+
scope_roots: set[DependencyReference] = Field(
65+
...,
66+
description="Stores the dependency graph as a list of root nodes for the direct dependencies referenced by "
67+
"scopes. Starting with these nodes, the whole graph can be traversed. The nodes are constructed "
68+
"from the direct dependencies declared by scopes that cannot be reached via other paths in the "
69+
"dependency graph. Note that this property exists for backwards compatibility only; it is replaced "
70+
"by the lists of nodes and edges.",
71+
)
72+
73+
scopes: dict[str, list[RootDependencyIndex]] = Field(
74+
...,
75+
description="A mapping from scope names to the direct dependencies of the scopes. Based on this information, "
76+
"the set of scopes of a project can be constructed from the serialized form.",
77+
)
78+
79+
nodes: list[DependencyGraphNode] = Field(
80+
...,
81+
description="A list with the nodes of this dependency graph. Nodes correspond to packages, but in contrast to "
82+
"the packages list, there can be multiple nodes for a single package. The order of nodes in this "
83+
"list is relevant; the edges of the graph reference their nodes by numeric indices.",
84+
)
85+
86+
edges: set[DependencyGraphEdge] = Field(
87+
...,
88+
description="A set with the edges of this dependency graph. By traversing the edges, the dependencies of "
89+
"packages can be determined.",
90+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro <heliocastro@gmail.com>
2+
# SPDX-License-Identifier: MIT
3+
4+
5+
from pydantic import BaseModel, ConfigDict, Field
6+
7+
8+
class DependencyGraphEdge(BaseModel):
9+
"""
10+
A data class representing an edge in the dependency graph.
11+
12+
An edge corresponds to a directed depends-on relationship between two packages. The packages are identified by the
13+
numeric indices into the list of nodes.
14+
"""
15+
16+
model_config = ConfigDict(
17+
extra="forbid",
18+
)
19+
20+
from_: int = Field(
21+
...,
22+
alias="from",
23+
description="The index of the source node of this edge.",
24+
)
25+
to_: int = Field(
26+
...,
27+
alias="to",
28+
description="The index of the destination node of this edge.",
29+
)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro <heliocastro@gmail.com>
2+
# SPDX-License-Identifier: MIT
3+
4+
5+
from pydantic import BaseModel, ConfigDict, Field
6+
7+
from ort.models.issue import Issue
8+
from ort.models.package_linkage import PackageLinkage
9+
10+
11+
class DependencyGraphNode(BaseModel):
12+
"""
13+
A data class representing a node in the dependency graph.
14+
15+
A node corresponds to a package, which is referenced by a numeric index. A package may, however, occur multiple
16+
times in the dependency graph with different transitive dependencies. In this case, different fragment indices are
17+
used to distinguish between these occurrences.
18+
"""
19+
20+
model_config = ConfigDict(
21+
extra="forbid",
22+
)
23+
24+
pkg: int = Field(
25+
...,
26+
description="Stores the numeric index of the package dependency referenced by this object. The package behind "
27+
"this index can be resolved by evaluating the list of identifiers stored in DependencyGraph at "
28+
"this index.",
29+
)
30+
fragment: int = Field(
31+
0,
32+
description="Stores the index of the fragment in the dependency graph where the referenced dependency is "
33+
"contained. This is needed to uniquely identify the target if the dependency occurs multiple times "
34+
"in the graph.",
35+
)
36+
linkage: PackageLinkage = Field(
37+
default=PackageLinkage.DYNAMIC,
38+
description="The type of linkage used for the referred package from its dependent package. As most of ORT's "
39+
"supported package managers / languages only support dynamic linking or at least default to it, "
40+
"also use that as the default value here to not blow up ORT result files.",
41+
)
42+
issues: list[Issue] = Field(
43+
default_factory=list, description="A list of Issue objects that occurred handling this dependency."
44+
)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro <heliocastro@gmail.com>
2+
# SPDX-License-Identifier: MIT
3+
4+
5+
from pydantic import BaseModel, ConfigDict, Field
6+
7+
from ort.models.issue import Issue
8+
from ort.models.package_linkage import PackageLinkage
9+
10+
11+
class DependencyReference(BaseModel):
12+
"""
13+
A class to model a tree-like structure to represent the dependencies of a project.
14+
15+
Instances of this class are used to store the relations between dependencies in fragments of dependency trees in an
16+
Analyzer result. The main purpose of this class is to define an efficient serialization format, which avoids
17+
redundancy as far as possible. Therefore, dependencies are represented by numeric indices into an external table.
18+
As a dependency can occur multiple times in the dependency graph with different transitive dependencies, the class
19+
defines another index to distinguish these cases.
20+
21+
Note: This is by intention no data class. Equality is tested via references and not via the values contained.
22+
"""
23+
24+
model_config = ConfigDict(
25+
extra="forbid",
26+
)
27+
28+
pkg: int = Field(
29+
...,
30+
description="Stores the numeric index of the package dependency referenced by this object. The package behind "
31+
"this index can be resolved by evaluating the list of identifiers stored in DependencyGraph at "
32+
"this index.",
33+
)
34+
fragment: int = Field(
35+
default=0,
36+
description="Stores the index of the fragment in the dependency graph where the referenced dependency is "
37+
"contained. This is needed to uniquely identify the target if the dependency occurs multiple times "
38+
"in the graph.",
39+
)
40+
dependencies: set["DependencyReference"] = Field(
41+
default_factory=set,
42+
description="A set with the references to the dependencies of this dependency. That way a tree-like structure "
43+
"is established.",
44+
)
45+
linkage: PackageLinkage = Field(
46+
default=PackageLinkage.DYNAMIC,
47+
description="The type of linkage used for the referred package from its dependent package. As most of ORT's "
48+
"supported package managers / languages only support dynamic linking or at least default to it, "
49+
"also use that as the default value here to not blow up ORT result files.",
50+
)
51+
issues: list[Issue] = Field(..., description="A list of Issue objects that occurred handling this dependency.")

src/ort/models/issue.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro <heliocastro@gmail.com>
2+
# SPDX-License-Identifier: MIT
3+
4+
5+
from datetime import datetime
6+
7+
from pydantic import BaseModel, ConfigDict, Field
8+
9+
from ort.severity import Severity
10+
11+
12+
class Issue(BaseModel):
13+
"""
14+
An issue that occurred while executing ORT.
15+
"""
16+
17+
model_config = ConfigDict(
18+
extra="forbid",
19+
)
20+
21+
timestamp: datetime = Field(
22+
description="The timestamp of the issue.",
23+
)
24+
source: str = Field(
25+
description="A description of the issue source, e.g. the tool that caused the issue.",
26+
)
27+
message: str = Field(
28+
description="The issue's message.",
29+
)
30+
severity: Severity = Field(
31+
description="The issue's severity.",
32+
)
33+
affected_path: str | None = Field(
34+
default=None,
35+
description="The affected file or directory the issue is limited to, if any.",
36+
)

0 commit comments

Comments
 (0)