Skip to content

Commit

Permalink
Merge pull request #804 from linsword13/template
Browse files Browse the repository at this point in the history
Add a `register_template` directive to objects
  • Loading branch information
linsword13 authored Jan 6, 2025
2 parents d7ba95a + 5e22329 commit b19c2e0
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 1 deletion.
47 changes: 46 additions & 1 deletion lib/ramble/ramble/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@

_NULL_CONTEXT = "null"

_DEFAULT_CONTENT_PERM = stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH


def _get_context_display_name(context):
return (
Expand Down Expand Up @@ -1173,6 +1175,7 @@ def add_expand_vars(self, workspace):
self._set_input_path()

self._derive_variables_for_template_path(workspace)
self._define_object_template_vars()
self._vars_are_expanded = True

def _inputs_and_fetchers(self, workload=None):
Expand Down Expand Up @@ -1376,7 +1379,9 @@ def _make_experiments(self, workspace, app_inst=None):
f.write(
self.expander.expand_var(template_conf["contents"], extra_vars=exec_vars)
)
os.chmod(expand_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH)
os.chmod(expand_path, _DEFAULT_CONTENT_PERM)

self._render_object_templates(exec_vars)

experiment_script = workspace.experiments_script
experiment_script.write(self.expander.expand_var("{batch_submit}\n"))
Expand Down Expand Up @@ -2267,6 +2272,46 @@ def evaluate_success(self):

return True

def _object_templates(self):
"""Return templates defined from different objects associated with the app_inst"""

def _get_template_config(obj, tpl_config):
src_path = os.path.join(os.path.dirname(obj._file_path), tpl_config["src_name"])
if not os.path.isfile(src_path):
raise ApplicationError(f"Object {obj.name} is missing template file at {src_path}")
return {**tpl_config, "src_path": src_path}

for tpl_config in self.templates.values():
yield _get_template_config(self, tpl_config)
for mod in self._modifier_instances:
for tpl_config in mod.templates.values():
yield _get_template_config(mod, tpl_config)
if self.package_manager is not None:
for tpl_config in self.package_manager.templates.values():
yield _get_template_config(self.package_manager, tpl_config)

def _render_object_templates(self, extra_vars):
run_dir = self.expander.experiment_run_dir
for tpl_config in self._object_templates():
src_path = tpl_config["src_path"]
with open(src_path) as f_in:
content = f_in.read()
rendered = self.expander.expand_var(content, extra_vars=extra_vars)
out_path = os.path.join(run_dir, tpl_config["dest_name"])
perm = tpl_config.get("content_perm", _DEFAULT_CONTENT_PERM)
with open(out_path, "w+") as f_out:
f_out.write(rendered)
f_out.write("\n")
os.chmod(out_path, perm)

def _define_object_template_vars(self):
run_dir = self.expander.experiment_run_dir
for tpl_config in self._object_templates():
var_name = tpl_config["var_name"]
if var_name is not None:
path = os.path.join(run_dir, tpl_config["dest_name"])
self.variables[var_name] = path


class ApplicationError(RambleError):
"""
Expand Down
1 change: 1 addition & 0 deletions lib/ramble/ramble/cmd/common/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"archive_patterns": None,
"success_criteria": None,
"target_shells": "shell_support_pattern",
"templates": None,
# Application specific:
"workloads": None,
"workload_groups": None,
Expand Down
35 changes: 35 additions & 0 deletions lib/ramble/ramble/language/shared_language.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,38 @@ def _execute_target_shells(obj):
obj.shell_support_pattern = shell_support_pattern

return _execute_target_shells


@shared_directive("templates")
def register_template(
name: str, src_name: str, dest_name: str, define_var: bool = True, output_perm=None
):
"""Directive to define an object-specific template to be rendered into experiment run_dir.
For instance, `register_template(name="foo", src_name="foo.tpl", dest_name="foo.sh")`
expects a "foo.tpl" template defined alongside the object source, and uses that to
render a file under "{experiment_run_dir}/foo.sh". The rendered path can also be
referenced with the `foo` variable name.
Args:
name: The name of the template. It is also used as the variable name
that an experiment can use to reference the rendered path, if
`define_var` is true.
src_name: The leaf name of the template. This is used to locate the
the template under the containing directory of the object.
dest_name: The leaf name of the rendered output under the experiment
run directory.
define_var: Controls if a variable named `name` should be defined.
output_perm: The chmod mask for the rendered output file.
"""

def _define_template(obj):
var_name = name if define_var else None
obj.templates[name] = {
"src_name": src_name,
"dest_name": dest_name,
"var_name": var_name,
"output_perm": output_perm,
}

return _define_template
57 changes: 57 additions & 0 deletions lib/ramble/ramble/test/end_to_end/test_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright 2022-2025 The Ramble Authors
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.

import os
import pytest

import ramble.workspace
from ramble.main import RambleCommand

pytestmark = pytest.mark.usefixtures(
"mutable_config", "mutable_mock_workspace_path", "mutable_mock_apps_repo"
)

workspace = RambleCommand("workspace")


def test_template():
test_config = """
ramble:
variables:
mpi_command: mpirun -n {n_ranks}
batch_submit: 'batch_submit {execute_experiment}'
processes_per_node: 1
applications:
template:
workloads:
test_template:
experiments:
test:
variables:
n_nodes: 1
hello_name: santa
"""
workspace_name = "test_template"
ws = ramble.workspace.create(workspace_name)
ws.write()
config_path = os.path.join(ws.config_dir, ramble.workspace.config_file_name)
with open(config_path, "w+") as f:
f.write(test_config)
ws._re_read()

workspace("setup", "--dry-run", global_args=["-w", workspace_name])
run_dir = os.path.join(ws.experiment_dir, "template/test_template/test/")
script_path = os.path.join(run_dir, "bar.sh")
assert os.path.isfile(script_path)
with open(script_path) as f:
content = f.read()
assert "echo hello santa" in content
execute_path = os.path.join(run_dir, "execute_experiment")
with open(execute_path) as f:
content = f.read()
assert script_path in content
39 changes: 39 additions & 0 deletions var/ramble/repos/builtin.mock/applications/template/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2022-2025 The Ramble Authors
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.

from ramble.appkit import *


class Template(ExecutableApplication):
"""An app for testing object templates."""

name = "template"

executable("foo", template=["bash {bar}"])

workload("test_template", executable="foo")

workload_variable(
"hello_name",
default="world",
description="hello name",
workload="test_template",
)

register_phase(
"ingest_dynamic_variables",
pipeline="setup",
run_before=["make_experiments"],
)

def _ingest_dynamic_variables(self, workspace, app_inst):
expander = self.expander
val = expander.expand_var('"hello {hello_name}"')
self.define_variable("dynamic_hello_world", val)

register_template("bar", src_name="bar.tpl", dest_name="bar.sh")
2 changes: 2 additions & 0 deletions var/ramble/repos/builtin.mock/applications/template/bar.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
echo {dynamic_hello_world}

0 comments on commit b19c2e0

Please sign in to comment.