Files
Giacomo Vercesi 4e0f4a6540 pypeline: stabilize pipeline description
Before this commit the pipeline description suffered from some
non-determinism that led to some values changing between runs of
`revng`. Add extra sorting so to eliminate the non-determinism.
2026-05-22 09:04:35 +02:00

94 lines
3.5 KiB
Python

#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
from functools import partial
from pathlib import Path
from typing import Any
import jsonschema
import yaml
import revng.pypeline
from revng.pypeline.analysis import Analysis
from revng.pypeline.container import Container
from revng.pypeline.model import Model
from revng.pypeline.object import Kind
from revng.pypeline.pipeline import Pipeline
from revng.pypeline.pipeline_node import PipelineConfiguration
from revng.pypeline.task.pipe import Pipe
from revng.pypeline.utils import PypelineException
from revng.pypeline.utils.registry import get_registry, get_singleton
def get_pipeline_description(pipeline: Pipeline) -> dict[str, Any]:
"""
Build and validate the web representation of the pipeline.
"""
model_type: type[Model] = get_singleton(Model) # type: ignore [type-abstract]
sort_by_name = partial(sorted, key=lambda x: x["name"])
# Build the pipeline description
pipeline_description = {
"version": revng.pypeline.__version__,
"model": {
"identifier": model_type.identifier,
"name": model_type.model_name(),
"mime_type": model_type.mime_type(),
"is_text": model_type.is_text(),
},
"kinds": sort_by_name(get_singleton(Kind).type_dict()), # type: ignore
"container_types": sorted(
[
container.type_dict()
for container in get_registry(Container).values() # type: ignore [type-abstract]
],
key=lambda x: x["class"],
),
"containers": {
declaration.name: declaration.container_type.name
for declaration in sorted(pipeline.declarations, key=lambda x: x.name)
},
"root_node_id": pipeline.root.id,
"nodes": [node.to_dict() for node in pipeline.walk_pipeline(stable=True)],
"artifacts": sort_by_name([artifact.to_dict() for artifact in pipeline.artifacts.values()]),
"analyses": sort_by_name([analysis.to_dict() for analysis in pipeline.analyses.values()]),
"analyses_lists": sort_by_name(
[analysis_list.to_dict() for analysis_list in pipeline.analysis_lists.values()]
),
}
# Ensure that it respects the agreed schema
root = Path(__file__).resolve().parent.parent
with open(root / "pipeline-description-schema.yml", "r", encoding="utf-8") as f:
schema = yaml.safe_load(f)
validator = jsonschema.Draft7Validator(schema)
validator.validate(pipeline_description)
return pipeline_description
def deserialize_configuration(pipeline: Pipeline, input_: dict[str, str]) -> PipelineConfiguration:
"""Given a `dict[str, str]`, map it to a PipelineConfiguration by walking
the pipeline tree and assigning keys based on the pipe/analysis name
"""
pipes = get_registry(Pipe) # type: ignore[type-abstract]
analyses = get_registry(Analysis) # type: ignore[type-abstract]
for key in input_:
if key not in pipes and key not in analyses:
raise PypelineException(f"Passed configuration key '{key}' does not exist")
configuration: dict[Pipe | Analysis, str] = {}
for node in pipeline.walk_pipeline():
if not isinstance(node.task, Pipe):
continue
if node.task.name in input_:
configuration[node.task] = input_[node.task.name]
for binding in pipeline.analyses.values():
if binding.analysis.name in input_:
configuration[binding.analysis] = input_[binding.analysis.name]
return configuration