mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
b38832e251
Move the epoch inside the storage_provider, this allows us to make the storage_provider_factory to return an async context manager. This way we can simplify the locking for the daemon, but forces the CLI project commands to deal with async code.
193 lines
6.9 KiB
Python
193 lines
6.9 KiB
Python
#
|
|
# This file is distributed under the MIT License. See LICENSE.md for details.
|
|
#
|
|
|
|
import asyncio
|
|
import sys
|
|
from typing import AsyncContextManager
|
|
|
|
import click
|
|
|
|
from revng.pypeline.cli.utils import PypeGroup, build_arg_objects, build_help_text
|
|
from revng.pypeline.cli.utils import compute_objects, list_objects_for_container
|
|
from revng.pypeline.cli.utils import list_objects_option, normalize_whitespace, project_id_option
|
|
from revng.pypeline.cli.utils import token_option
|
|
from revng.pypeline.model import Model, ReadOnlyModel
|
|
from revng.pypeline.pipeline import AnalysisBinding, Pipeline
|
|
from revng.pypeline.storage.storage_provider import StorageProvider
|
|
from revng.pypeline.storage.storage_provider import storage_provider_factory_factory
|
|
from revng.pypeline.task.requests import Requests
|
|
from revng.pypeline.utils.logger import pypeline_logger
|
|
from revng.pypeline.utils.registry import get_singleton
|
|
|
|
|
|
class AnalyzeGroup(PypeGroup):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def list_commands(self, ctx):
|
|
base = super().list_commands(ctx)
|
|
pipeline = ctx.obj.get("pipeline")
|
|
if pipeline is None:
|
|
return base
|
|
return base + sorted(pipeline.analyses.keys())
|
|
|
|
def get_command(self, ctx, cmd_name):
|
|
pipeline = ctx.obj.get("pipeline")
|
|
if pipeline is None:
|
|
return super().get_command(ctx, cmd_name)
|
|
if cmd_name not in pipeline.analyses:
|
|
return super().get_command(ctx, cmd_name)
|
|
return self._build_analysis_command(
|
|
analysis_name=cmd_name,
|
|
pipeline=pipeline,
|
|
)
|
|
|
|
def _build_analysis_command(self, analysis_name: str, pipeline: Pipeline):
|
|
"""Dynamically create a command for running an analysis."""
|
|
analysis_binding: AnalysisBinding = pipeline.analyses[analysis_name]
|
|
|
|
if analysis_binding.analysis.__doc__:
|
|
help_text = click.wrap_text(
|
|
f"\n{normalize_whitespace(analysis_binding.analysis.__doc__)}"
|
|
)
|
|
else:
|
|
help_text = f"Run the analysis: {analysis_name}"
|
|
|
|
help_text = build_help_text(prologue=help_text, args=[], model_help=False)
|
|
|
|
# Build the actual function that will be the command
|
|
run_analysis_command = build_analysis_command(
|
|
analysis_binding=analysis_binding,
|
|
help_text=help_text,
|
|
model_type=get_singleton(Model), # type: ignore[type-abstract]
|
|
pipeline=pipeline,
|
|
)
|
|
|
|
config = getattr(
|
|
analysis_binding.analysis,
|
|
"configuration_help",
|
|
f'Configuration for the analysis "{analysis_name}".',
|
|
)
|
|
if config is not None:
|
|
run_analysis_command = click.option(
|
|
"-c",
|
|
"--configuration",
|
|
type=str,
|
|
default="",
|
|
help=normalize_whitespace(config),
|
|
)(run_analysis_command)
|
|
|
|
# For each argument, call the `click.argument` decorator to dynamically add
|
|
# them to the command
|
|
for container_decl in analysis_binding.bindings:
|
|
run_analysis_command = build_arg_objects(container_decl)(run_analysis_command)
|
|
|
|
return run_analysis_command
|
|
|
|
|
|
def build_analysis_command(
|
|
analysis_binding: AnalysisBinding,
|
|
help_text: str,
|
|
model_type: type[Model],
|
|
pipeline: Pipeline,
|
|
):
|
|
analysis_name: str = analysis_binding.analysis.name
|
|
|
|
async def async_part_of_command(
|
|
storage_provider_context: AsyncContextManager[StorageProvider],
|
|
configuration: str,
|
|
kwargs,
|
|
):
|
|
"""Since the storage provider factory returns an async context manager,
|
|
we need the code that uses the storage_provider to be an async function.
|
|
"""
|
|
async with storage_provider_context as storage_provider:
|
|
loaded_model = model_type.deserialize(storage_provider.get_model()[0])
|
|
|
|
pypeline_logger.debug_log(f'Model loaded: "{loaded_model}"')
|
|
|
|
if kwargs["list"]:
|
|
# If the user requested to list the available objects, we print them
|
|
# and exit
|
|
for container_decl in analysis_binding.bindings:
|
|
list_objects_for_container(
|
|
model=ReadOnlyModel(loaded_model),
|
|
arg_name=container_decl.name,
|
|
kind=container_decl.container_type.kind,
|
|
)
|
|
# Space between containers
|
|
print()
|
|
return
|
|
|
|
# Compute the requests for the incoming containers of the
|
|
# analysis
|
|
incoming = Requests()
|
|
for container_decl in analysis_binding.bindings:
|
|
incoming[container_decl] = compute_objects(
|
|
model=ReadOnlyModel(loaded_model),
|
|
arg_name=container_decl.name,
|
|
kind=container_decl.container_type.kind,
|
|
kwargs=kwargs,
|
|
)
|
|
|
|
# Finally, run the analysis
|
|
new_model, invalidated = pipeline.run_analysis(
|
|
model=ReadOnlyModel(loaded_model),
|
|
analysis_name=analysis_name,
|
|
requests=incoming,
|
|
analysis_configuration=configuration,
|
|
pipeline_configuration={},
|
|
storage_provider=storage_provider,
|
|
)
|
|
pypeline_logger.debug_log("Analysis run completed")
|
|
# Print on stdout the raw bytes of the modified model
|
|
sys.stdout.buffer.write(new_model.serialize())
|
|
|
|
for container_location, object_ids in invalidated.items():
|
|
serialized_ids = (object_id.serialize() for object_id in object_ids)
|
|
pypeline_logger.log(
|
|
f"Invalidated {container_location}: [{', '.join(serialized_ids)}]"
|
|
)
|
|
|
|
@click.command(name=analysis_name, help=help_text)
|
|
@list_objects_option
|
|
@project_id_option
|
|
@token_option
|
|
@click.pass_context
|
|
def run_analysis_command(
|
|
ctx: click.Context,
|
|
configuration: str,
|
|
project_id: str,
|
|
token: str,
|
|
**kwargs,
|
|
) -> None:
|
|
pypeline_logger.debug_log(f'Running analysis: "{analysis_name}"')
|
|
pypeline_logger.debug_log(f'configuration: "{configuration}"')
|
|
pypeline_logger.debug_log(f'and kwargs: "{kwargs}"')
|
|
|
|
# Load the model
|
|
storage_provider_factory = storage_provider_factory_factory(ctx.obj["storage_provider"])
|
|
storage_provider_context = storage_provider_factory.get(
|
|
project_id=project_id,
|
|
token=token,
|
|
cache_dir=ctx.obj["cache_dir"],
|
|
)
|
|
asyncio.run(
|
|
async_part_of_command(
|
|
storage_provider_context=storage_provider_context,
|
|
configuration=configuration,
|
|
kwargs=kwargs,
|
|
)
|
|
)
|
|
|
|
return run_analysis_command
|
|
|
|
|
|
@click.group(
|
|
cls=AnalyzeGroup,
|
|
help="Run an analysis",
|
|
)
|
|
def analyze() -> None:
|
|
pass
|