Adding a build recipe¶
A recipe teaches crab setup how to obtain and build a benchmark on the current machine. It's
optional — you only need one if you want CRAB to build the benchmark for you rather than pointing
it at a binary you built yourself. Running a recipe produces a receipt.
The contract¶
Create a module in src/crab/setup/recipes/ with a class subclassing BenchmarkRecipe. The setup
wizard auto-discovers any such subclass in that package — just dropping the file in is enough,
no registration.
from .base import BenchmarkRecipe, BuildResult
class MyBenchRecipe(BenchmarkRecipe):
@property
def name(self) -> str:
return "My Benchmark"
@property
def benchmark_id(self) -> str:
return "mybench" # MUST match the wrapper's benchmark_id
benchmark_id is the link
The recipe's benchmark_id is what ties the generated receipt to the
wrapper. They must be identical (here, "mybench").
Required methods¶
| Method | Returns | Purpose |
|---|---|---|
name (property) |
str |
Display name in the wizard. |
benchmark_id (property) |
str |
Unique id; names the receipt and links the wrapper. |
check_dependencies(env) |
(bool, str) |
Pre-flight check (compilers, tools present?) against the build environment. |
download_and_build(target_dir, params, env, log_callback) |
(bool, BuildResult \| None, str) |
Clone and compile; return the build result or an error message. |
verify_existing(path) |
bool |
Does this path already contain a valid build? |
Optional overrides¶
| Member | Default | Purpose |
|---|---|---|
suite (property) |
name |
Groups multiple recipes under one wizard entry (e.g. QE v6 & v7). |
launcher_override (property) |
"" |
Force a launcher (e.g. "mpirun") regardless of the cluster default. |
pre_run_hooks (property) |
[] |
Commands recorded into the receipt to run before each launch. |
build_manifest (property) |
BuildManifest() |
Declares build inputs — whether modules are needed and any BuildParameters (e.g. a cpu/gpu choice). |
fast_search(dir) |
checks <dir>/<id> and PATH |
Tier-1 auto-detect of an existing install. |
The base class also gives you run_command_streamed(cmd, cwd, step_name, env, log_callback) — run
a build command with its output streamed live into the wizard UI. Use it for every clone/compile
step.
Worked example (Graph500)¶
import os, shutil
from typing import Tuple, Optional, Callable, Dict
from .base import BenchmarkRecipe, BuildResult
class G500Recipe(BenchmarkRecipe):
@property
def name(self) -> str: return "Graph500"
@property
def benchmark_id(self) -> str: return "g500"
def check_dependencies(self, env: Dict[str, str]) -> Tuple[bool, str]:
if not shutil.which("mpicc", path=env.get("PATH")):
return False, "MPI compiler (mpicc) not found."
if not shutil.which("make", path=env.get("PATH")):
return False, "Make is missing."
return True, "Dependencies found."
def download_and_build(self, target_dir, params, env, log_callback=None):
if not self.run_command_streamed(
["git", "clone", "https://github.com/graph500/graph500.git", target_dir],
".", "Cloning Repository...", env, log_callback):
return False, None, "Git clone failed."
src_dir = os.path.join(target_dir, "src")
if not self.run_command_streamed(
["make", "MPICC=mpicc -fcommon", "-j"],
src_dir, "Compiling Binaries...", env, log_callback):
return False, None, "Make compilation failed."
if os.path.exists(os.path.join(src_dir, "graph500_reference_bfs")):
return True, BuildResult(binary_path=src_dir), "Built successfully."
return False, None, "Binaries missing after build."
def verify_existing(self, path: str) -> bool:
binary = os.path.join(path, "graph500_reference_bfs")
return os.path.isfile(binary) and os.access(binary, os.X_OK)
def fast_search(self, crab_benchmarks_dir: str) -> Optional[str]:
target = os.path.join(crab_benchmarks_dir, "g500", "src")
return target if self.verify_existing(target) else None
Build parameters and metadata¶
To ask the user for build-time choices, declare a build_manifest with BuildParameters and read
them from the params dict in download_and_build. Anything you put in
BuildResult(binary_path=..., metadata={...}) is merged into the receipt — for example QE records
{"target_arch": "gpu"}, which CRAB later uses for an architecture guardrail.
from .base import BuildManifest, BuildParameter
@property
def build_manifest(self) -> BuildManifest:
return BuildManifest(
requires_modules=True,
parameters=[BuildParameter(name="arch", description="Target architecture",
choices=["cpu", "gpu"], default="cpu")],
)
What the binary_path should point to¶
Return whatever directory or file your wrapper expects. The convention used by the
shipped recipes is to return the directory containing the binaries (e.g. bin/, src/) and let
the wrapper append the specific executable name via get_path. Be consistent between the two.
Once the recipe exists, crab setup lists it, builds it, and writes the receipt — see
From recipe to receipt. For the exact base-class signatures, see the
Wrapper & Recipe API.