Implement listen/watch edges

This commit is contained in:
Olivier 'reivilibre' 2020-11-25 22:04:30 +00:00
parent 194250d072
commit 9da8b4234e
4 changed files with 97 additions and 6 deletions

View File

@ -137,6 +137,9 @@ class RecipeDag:
self.resource_time: Dict[Resource, int] = dict()
# true for 'only when changed', false for '@when changed'
self.watching: Dict[Recipe, Dict[Vertex, bool]] = defaultdict(dict)
def add(self, vertex: Vertex):
self.vertices.add(vertex)
if isinstance(vertex, Recipe):
@ -211,3 +214,10 @@ class RecipeDag:
after_meta.incoming_uncompleted += 1
# TODO if after_meta.state ==
# TODO else ...
def watches(self, recipe: "Recipe", watching: Vertex, only_when: bool) -> None:
if watching not in self.edges or recipe not in self.edges[watching]:
raise ValueError(
f"{recipe} needs to be after {watching} before it can watch it."
)
self.watching[recipe][watching] = only_when

View File

@ -80,8 +80,8 @@ ListenEdgeDirectiveKind:
ListenEdgeDirective[ws=' \t']:
kind=ListenEdgeDirectiveKind
(recipe_id=IdString | resource=Resource)
'changes'
((':' recipe_id=IdString) | resource=Resource)
'changes' /\n/+
;

View File

@ -208,9 +208,32 @@ class Kitchen:
) -> Tuple[Optional[DependencyBook], bool]:
"""
:param recipe: recipe to inquire about
:return: dep book, or None if there wasn't one
:return: dep book, or None if there wasn't one needed to compute this
and true if the recipe should be skipped, false otherwise.
"""
only_when_flag_set = False
if recipe in self.head.dag.watching:
for watching, only_when in self.head.dag.watching[recipe].items():
if isinstance(watching, Resource):
# only recipe watches are accepted here.
# resource watches are handled by adding them to the watchlist
# in the dependency book
continue
assert isinstance(watching, Recipe)
only_when_flag_set |= only_when
watch_rmeta = self.head.dag.recipe_meta[watching]
if watch_rmeta.state == RecipeState.COOKED:
# underlying recipe changed. Ideally want a new changed state.
# TODO(design): have a 'changed' state for recipes?
return None, False
if only_when_flag_set:
# TODO(design) is it sensible to skip this here? What if we need to
# provide something? I suppose it's not guaranteed to be provided.
return None, True
inquiry = await self._dependency_store.inquire(recipe)
if inquiry is None:
return None, False
@ -288,6 +311,16 @@ class Kitchen:
meta.state = RecipeState.FAILED
raise RuntimeError(f"Recipe {next_job} failed!") from e
eprint(f"cooked {next_job}")
if next_job in self.head.dag.watching:
for watching, only_when in self.head.dag.watching[
next_job
].items():
if isinstance(watching, Resource):
# recipe watches are handled when loading the
# dependency book.
tracker.watch(watching)
await self._store_dependency(next_job)
meta.state = RecipeState.COOKED
elif isinstance(next_job, Resource):

View File

@ -74,6 +74,14 @@ class ResourceEdgeDirective:
resource: Resource
@attr.s(auto_attribs=True)
class ListenEdgeDirective:
# "when" or "only when"
kind: str
recipe_or_resource: Union[str, Resource]
@attr.s(auto_attribs=True, eq=False)
class MenuBlock:
id: Optional[None]
@ -109,6 +117,7 @@ class MenuRecipe:
for_directives: List[ForDirective] = attr.ib(factory=list)
recipe_edges: List[RecipeEdgeDirective] = attr.ib(factory=list)
resource_edges: List[ResourceEdgeDirective] = attr.ib(factory=list)
listen_edges: List[ListenEdgeDirective] = attr.ib(factory=list)
def convert_textx_value(txvalue) -> Any:
@ -165,6 +174,14 @@ def convert_textx_recipe(txrecipe_or_subblock, parent: Optional[MenuBlock]):
recipe.recipe_edges.append(
RecipeEdgeDirective(directive.kind[1:], directive.id)
)
elif isinstance(directive, scoml_classes["ListenEdgeDirective"]):
recipe.listen_edges.append(
ListenEdgeDirective(
directive.kind[1:],
directive.recipe_id
or convert_textx_resource(directive.resource),
)
)
else:
raise ValueError(f"Unknown directive {directive}")
@ -185,7 +202,9 @@ def convert_textx_resource(txresource) -> Resource:
else:
sous = txresource.sous
return Resource(txresource.type, txresource.primary, sous, extra_params)
return Resource(
txresource.type, convert_textx_value(txresource.primary), sous, extra_params
)
def convert_textx_block(txblock, parent: Optional[MenuBlock]) -> MenuBlock:
@ -450,7 +469,7 @@ class MenuLoader:
fors: Tuple[ForDirective, ...],
applicable_souss: Iterable[str],
sous_mask: Optional[Set[str]],
):
) -> None:
# TODO(feature): add edges
# add fors
@ -497,7 +516,36 @@ class MenuLoader:
elif resource_edge.kind == "provides":
self._dag.provides(instance, resource)
# XXX apply specific edges here including those from parent
for listen_edge in recipe.listen_edges:
if isinstance(listen_edge.recipe_or_resource, Resource):
# TODO(design): is it right for this to NEED it rather
# than WANT it?
resource = listen_edge.recipe_or_resource
if resource.sous == "(self)":
resource = attr.evolve(resource, sous=sous)
self._dag.needs(instance, resource)
self._dag.watches(
instance, resource, listen_edge.kind == "only when",
)
elif isinstance(listen_edge.recipe_or_resource, str):
target = self.resolve_ref(
recipe, listen_edge.recipe_or_resource
)
if isinstance(target, MenuRecipe):
for target_instance in self.get_related_instances(
sous, for_indices, recipe, target
):
self._dag.add_ordering(target_instance, instance)
self._dag.watches(
instance,
target_instance,
listen_edge.kind == "only when",
)
else:
raise RuntimeError(f"not supported on target: {target!r}")
# XXX apply edges from parent
def postdagify_block(
self,