mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
#
|
|
# This file is distributed under the MIT License. See LICENSE.md for details.
|
|
#
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import defaultdict
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import Iterable, Mapping
|
|
|
|
from revng.pypeline.container import ContainerID
|
|
from revng.pypeline.model import ModelPath, ModelPathSet
|
|
from revng.pypeline.object import ObjectID
|
|
from revng.pypeline.task.task import ObjectDependencies
|
|
|
|
from .storage_provider import ConfigurationId, ContainerLocation, ProjectMetadata, SavepointID
|
|
from .storage_provider import SavePointsRange, StorageProvider
|
|
from .util import _REVNG_VERSION_PLACEHOLDER
|
|
|
|
|
|
@dataclass
|
|
class DependencyEntry:
|
|
savepoint_start: SavepointID
|
|
savepoint_end: SavepointID
|
|
container_id: ContainerID
|
|
configuration_id: ConfigurationId
|
|
object_id: ObjectID
|
|
|
|
|
|
class InMemoryStorageProvider(StorageProvider):
|
|
"""A simple in-memory storage provider for testing purposes.
|
|
This is not thread-safe and should not be used in production.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.model = b""
|
|
self.storage: dict[ContainerLocation, dict[ObjectID, bytes]] = {}
|
|
self.dependencies: dict[str, list[DependencyEntry]] = defaultdict(list)
|
|
self.last_change = datetime.now()
|
|
|
|
def has(
|
|
self,
|
|
location: ContainerLocation,
|
|
keys: Iterable[ObjectID],
|
|
) -> Iterable[ObjectID]:
|
|
if location not in self.storage:
|
|
return []
|
|
storage = self.storage[location]
|
|
return [key for key in keys if key in storage]
|
|
|
|
def get(
|
|
self,
|
|
location: ContainerLocation,
|
|
keys: Iterable[ObjectID],
|
|
) -> Mapping[ObjectID, bytes]:
|
|
if location not in self.storage:
|
|
raise KeyError(f"Savepoint {location} not found in storage.")
|
|
storage = self.storage[location]
|
|
return {k: storage[k] for k in keys if k in storage}
|
|
|
|
def add_dependencies(
|
|
self,
|
|
savepoint_range: SavePointsRange,
|
|
configuration_id: ConfigurationId,
|
|
deps: ObjectDependencies,
|
|
) -> None:
|
|
for container_name, obj, path in deps:
|
|
self.dependencies[path].append(
|
|
DependencyEntry(
|
|
savepoint_range.start,
|
|
savepoint_range.end,
|
|
container_name,
|
|
configuration_id,
|
|
obj,
|
|
)
|
|
)
|
|
self.last_change = datetime.now()
|
|
|
|
def put(
|
|
self,
|
|
location: ContainerLocation,
|
|
values: Mapping[ObjectID, bytes],
|
|
) -> None:
|
|
self.storage.setdefault(location, {})
|
|
for key, value in values.items():
|
|
self.storage[location][key] = value
|
|
self.last_change = datetime.now()
|
|
|
|
def _invalidate(self, path: ModelPath) -> None:
|
|
for key, entries in self.dependencies.items():
|
|
if key != path:
|
|
continue
|
|
|
|
# TODO: this double loop is very inefficient
|
|
for entry in entries:
|
|
for container_loc, objects in self.storage.items():
|
|
if (
|
|
container_loc.savepoint_id < entry.savepoint_start
|
|
or container_loc.savepoint_id > entry.savepoint_end
|
|
or container_loc.container_id != entry.container_id
|
|
or container_loc.configuration_id != entry.configuration_id
|
|
):
|
|
continue
|
|
if entry.object_id in objects:
|
|
del objects[entry.object_id]
|
|
|
|
def invalidate(self, invalidation_list: ModelPathSet) -> None:
|
|
for path in invalidation_list:
|
|
self._invalidate(path)
|
|
self.last_change = datetime.now()
|
|
|
|
def get_model(self) -> bytes:
|
|
return self.model
|
|
|
|
def set_model(self, new_model: bytes):
|
|
self.model = new_model
|
|
self.last_change = datetime.now()
|
|
|
|
def metadata(self) -> ProjectMetadata:
|
|
"""
|
|
Fetch metadata about the current project
|
|
"""
|
|
return ProjectMetadata(
|
|
last_change=self.last_change,
|
|
revng_version=_REVNG_VERSION_PLACEHOLDER,
|
|
)
|
|
|
|
def prune_objects(self):
|
|
"""
|
|
Prunes all the objects (except metadata) from storage
|
|
"""
|
|
self.storage.clear()
|
|
self.dependencies.clear()
|