4 releases (2 breaking)
| 0.3.0 | Jan 9, 2026 |
|---|---|
| 0.2.7 | Dec 29, 2025 |
| 0.1.3 | Dec 23, 2025 |
| 0.1.0 | Dec 23, 2025 |
#93 in Template engine
90 downloads per month
160KB
3K
SLoC
Sykli Rust SDK
CI pipelines defined in Rust instead of YAML.
use sykli::Pipeline;
fn main() {
let mut p = Pipeline::new();
p.task("test").run("cargo test");
p.task("build").run("cargo build --release").after(&["test"]);
p.emit();
}
Installation
Add to your Cargo.toml:
[dependencies]
sykli = "0.2"
[[bin]]
name = "sykli"
path = "src/bin/sykli.rs"
For optional dependency (recommended for libraries):
[features]
sykli = ["dep:sykli"]
[dependencies]
sykli = { version = "0.2", optional = true }
[[bin]]
name = "sykli"
path = "src/bin/sykli.rs"
required-features = ["sykli"]
Quick Start
Create src/bin/sykli.rs:
use sykli::Pipeline;
fn main() {
let mut p = Pipeline::new();
p.task("lint").run("cargo clippy -- -D warnings");
p.task("test").run("cargo test");
p.task("build")
.run("cargo build --release")
.after(&["lint", "test"]);
p.emit();
}
Create an empty sykli.rs in your project root:
touch sykli.rs
Run:
sykli run
Core Concepts
Tasks
Tasks are the basic unit of work:
p.task("test").run("cargo test");
Dependencies
Define execution order with .after():
p.task("deploy")
.run("./deploy.sh")
.after(&["build", "test"]); // Runs after both complete
Independent tasks run in parallel automatically.
Input-Based Caching
Skip unchanged tasks with .inputs():
p.task("test")
.run("cargo test")
.inputs(&["**/*.rs", "Cargo.toml", "Cargo.lock"]);
Outputs
Declare task outputs for artifact passing:
p.task("build")
.run("cargo build --release -o ./app")
.output("binary", "./app");
Conditional Execution
Run tasks based on branch, tag, or event:
// String-based conditions
p.task("deploy")
.run("./deploy.sh")
.when("branch == 'main'");
// Type-safe conditions (compile-time checked)
use sykli::{Branch, Tag};
p.task("release")
.run("./release.sh")
.when_cond(Branch::new("main").or(Tag::pattern("v*")));
Templates
Templates eliminate repetition:
let src = p.dir(".");
let cache = p.cache("cargo-registry");
// Define template
let rust = p.template("rust")
.container("rust:1.75")
.mount(&src, "/src")
.mount_cache(&cache, "/usr/local/cargo/registry")
.workdir("/src");
// Tasks inherit from template
p.task("lint").from(&rust).run("cargo clippy");
p.task("test").from(&rust).run("cargo test");
p.task("build").from(&rust).run("cargo build --release");
Containers
Run tasks in isolated containers:
let src = p.dir(".");
let registry_cache = p.cache("cargo-registry");
let target_cache = p.cache("cargo-target");
p.task("test")
.container("rust:1.75")
.mount(&src, "/src")
.mount_cache(®istry_cache, "/usr/local/cargo/registry")
.mount_cache(&target_cache, "/src/target")
.workdir("/src")
.run("cargo test");
Convenience Methods
// Mount current dir to /work
p.task("test")
.container("rust:1.75")
.mount_cwd()
.run("cargo test");
// Mount to custom path
p.task("build")
.container("rust:1.75")
.mount_cwd_at("/app")
.run("cargo build");
Composition
Parallel Groups
let checks = p.parallel("checks", vec![
p.task("lint").run("cargo clippy"),
p.task("fmt").run("cargo fmt --check"),
p.task("test").run("cargo test"),
]);
// Build depends on all checks
p.task("build")
.run("cargo build --release")
.after_group(&checks);
Chains
let test = p.task("test").run("cargo test");
let build = p.task("build").run("cargo build --release");
let deploy = p.task("deploy").run("./deploy.sh");
// test -> build -> deploy
p.chain(&[&test, &build, &deploy]);
Artifact Passing
p.task("build")
.run("cargo build --release -o /out/app")
.output("binary", "/out/app");
// Automatically depends on "build"
p.task("package")
.run("docker build -t myapp .")
.input_from("build", "binary", "./app");
Matrix Builds
// Test across Rust versions
p.matrix("rust-test", &["1.70", "1.75", "1.80"], |version| {
p.task(&format!("test-rust-{}", version))
.container(&format!("rust:{}", version))
.mount_cwd()
.run("cargo test")
});
// Deploy to environments
p.matrix_map("deploy", &[
("staging", "staging.example.com"),
("prod", "prod.example.com"),
], |(env, host)| {
p.task(&format!("deploy-{}", env))
.run(&format!("deploy --host {}", host))
.when("branch == 'main'")
});
Service Containers
p.task("integration")
.container("rust:1.75")
.mount_cwd()
.service("postgres:15", "db")
.service("redis:7", "cache")
.env("DATABASE_URL", "postgres://postgres:postgres@db:5432/test")
.env("REDIS_URL", "redis://cache:6379")
.run("cargo test --features integration")
.timeout(300);
Secrets
// Simple secret
p.task("deploy")
.secret("DEPLOY_TOKEN")
.run("./deploy.sh");
// Typed secrets with explicit source
use sykli::{FromEnv, FromVault, FromFile};
p.task("deploy")
.secret_from("GITHUB_TOKEN", FromEnv::new("GH_TOKEN"))
.secret_from("DB_PASSWORD", FromVault::new("secret/db#password"))
.secret_from("API_KEY", FromFile::new("/run/secrets/api-key"))
.run("./deploy.sh");
Retry & Timeout
p.task("flaky-test")
.run("./integration-test.sh")
.retry(3) // Retry up to 3 times
.timeout(300); // 5 minute timeout
Kubernetes Execution
use sykli::{K8sOptions, K8sResources};
// Pipeline-level defaults
let mut p = Pipeline::new_with_k8s_defaults(K8sOptions {
namespace: Some("ci-jobs".into()),
resources: K8sResources {
memory: Some("2Gi".into()),
..Default::default()
},
..Default::default()
});
// Task-specific K8s settings
p.task("train-model")
.container("pytorch/pytorch:2.0")
.run("python train.py")
.k8s(K8sOptions {
gpu: Some(1),
resources: K8sResources {
memory: Some("32Gi".into()),
..Default::default()
},
node_selector: [("gpu".into(), "nvidia-a100".into())].into(),
..Default::default()
});
// Hybrid: local + k8s
p.task("test").run("cargo test").target("local");
p.task("train").run("python train.py").target("k8s");
Language Presets
let mut p = Pipeline::new();
// Rust preset
p.rust().test(); // cargo test
p.rust().lint(); // cargo clippy
p.rust().build("target/release/app").after(&["test", "lint"]);
p.emit();
Dry Run / Explain
use sykli::ExplainContext;
p.explain(&ExplainContext {
branch: "feature/foo".into(),
tag: None,
ci: true,
});
// Output shows execution order and skipped tasks
Examples
See the examples directory for complete working examples:
01-basic/- Tasks, dependencies, parallel execution02-caching/- Input-based caching and conditions03-containers/- Container execution with mounts04-templates/- DRY configuration with templates05-composition/- Parallel groups, chains, artifacts06-matrix/- Matrix builds, services, secrets
API Reference
See REFERENCE.md for the complete API documentation.
License
MIT
Dependencies
~7–12MB
~132K SLoC