Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dependents support for register_builtin #779

Merged
merged 1 commit into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions lib/ramble/ramble/graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def __init__(self, exec_order, executables, builtin_objects, builtin_groups, obj
obj_inst (object): Object instance to extract attributes from (when necessary)
"""
super().__init__(obj_inst)
self._builtin_dependencies = {}
self._builtin_dependencies = defaultdict(list)
# Mapping from shorter_name -> fully qualified names
self._builtin_aliases = defaultdict(list)

Expand All @@ -282,6 +282,10 @@ def __init__(self, exec_order, executables, builtin_objects, builtin_groups, obj
)
self.node_definitions[builtin] = exec_node
self._build_builtin_aliases(builtin)
for builtin, blt_conf in builtins.items():
dependents = blt_conf["dependents"].copy()
for dependent in dependents:
self._builtin_dependencies[dependent].append(builtin)

dep_exec = None
for exec_name in exec_order:
Expand All @@ -308,14 +312,17 @@ def __init__(self, exec_order, executables, builtin_objects, builtin_groups, obj
blt_node = self.node_definitions[builtin]
super().update_graph(blt_node)

if blt_conf["injection_method"] == "prepend":
# TODO: This should include `depends_on` as well.
relative = bool(blt_conf["dependents"])

if not relative and blt_conf["injection_method"] == "prepend":
if head_node is not None:
super().update_graph(head_node, [blt_node])

if tail_prepend_builtin is not None:
super().update_graph(blt_node, [tail_prepend_builtin])
tail_prepend_builtin = blt_node
elif blt_conf["injection_method"] == "append":
elif not relative and blt_conf["injection_method"] == "append":
if tail_node is not None:
super().update_graph(blt_node, [tail_node])

Expand All @@ -333,6 +340,13 @@ def __init__(self, exec_order, executables, builtin_objects, builtin_groups, obj
exec_node = self.node_definitions[builtin]
super().update_graph(exec_node, deps)

if blt_conf["dependents"]:
exec_node = self.node_definitions[builtin]
super().update_graph(exec_node)
for dependent in blt_conf["dependents"]:
dependent_node = self._resolve_builtin_node(dependent)
super().update_graph(dependent_node, [exec_node])

def inject_executable(self, exec_name, injection_order, relative):
"""Inject an executable into the graph

Expand Down
9 changes: 7 additions & 2 deletions lib/ramble/ramble/language/shared_language.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,9 @@ def _execute_success_criteria(obj):


@shared_directive("builtins")
def register_builtin(name, required=True, injection_method="prepend", depends_on=[]):
def register_builtin(
name, required=True, injection_method="prepend", depends_on=[], dependents=[]
):
"""Register a builtin

Builtins are methods that return lists of strings. These methods represent
Expand Down Expand Up @@ -308,7 +310,7 @@ def example_builtin(self):
The 'required' attribute marks a builtin as required for all workloads. This
will ensure the builtin is added to the workload if it is not explicitly
added. If required builtins are not explicitly added to a workload, they
are injected into the list of executables, based on the injection_method
are injected into the list of executables, based on the injection_method
attribute.

The 'injection_method' attribute controls where the builtin will be
Expand All @@ -324,6 +326,8 @@ def example_builtin(self):
'prepend' or 'append'
depends_on (list(str)): The names of builtins this builtin depends on
(and must execute after).
dependents (list(str)): The names of builtins that should come
after the current one.
"""
supported_injection_methods = ["prepend", "append"]

Expand All @@ -342,6 +346,7 @@ def _store_builtin(obj):
"required": required,
"injection_method": injection_method,
"depends_on": depends_on.copy(),
"dependents": dependents.copy(),
}

return _store_builtin
Expand Down
9 changes: 9 additions & 0 deletions lib/ramble/ramble/test/application_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,15 @@ def test_register_builtin_app(mutable_mock_apps_repo):
for builtin in excluded_builtins:
assert exec_graph.get_node(builtin) is None

# Test for dependency injection
found_prerequisite = False
for node in exec_graph.walk():
if node.key == "builtin::test_builtin":
break
if node.key == "builtin::test_builtin_pre":
found_prerequisite = True
assert found_prerequisite


@pytest.mark.parametrize(
"app", ["basic", "basic-inherited", "input-test", "interleved-env-vars", "register-builtin"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class RegisterBuiltin(ExecutableApplication):
# Test disabling the typically included env_vars builtin.
register_builtin("env_vars", required=False)
register_builtin("test_builtin", required=True)
register_builtin("test_builtin_pre", dependents=["test_builtin"])

def test_builtin(self):
return ['echo "foo"']

def test_builtin_pre(self):
return ['echo "bar"']