Serpentine is a workflow runner with a huge focus on simplicity and developer ergonomics. It uses its own custom DSL called snek which lets you define a graph of steps to execute.
Serpentine does not have the concept of jobs, pipelines, etc. it has the concept of the node, and which nodes depend on which, the end. This makes it trivially easy to share setup logic, parallelize workflows, etc.
You can read the book/docs at https://serpentine.vivax.dev/ (or locally in the book folder).
base = Image("rust:latest") > With(FromHost("."), "/app") > WorkingDir("/app");
test = base > Exec("cargo test");
clippy = base > Exec("cargo clippy");
export DEFAULT = base > !(test, clippy) Exec("cargo build");
This represents the following graph:
---
config:
theme: redux
layout: elk
---
flowchart TD
A["Image(rust:latest)"] --> B["With(FromHost(.), /app)"]
B --> C["WorkingDir(/app)"]
C --> D["Exec(cargo test)"] & E["Exec(cargo clippy)"] & F["Exec(cargo build)"]
D -.-> F
E -.-> F
F --> G["Output"]
Serpentine also comes with a standard library that makes the above even more cache efficient
import "@/rust.snek" as rust;
source_code = FromHost(".");
test = rust::Nextest(source_code);
clippy = rust::Clippy(source_code);
export DEFAULT = rust::stable
> With(source_code, ".")
> !(test, clippy) rust::WithChef("cargo build");