Skip to content
Closed
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
10 changes: 5 additions & 5 deletions core/daglang/daglang-lower/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5678,13 +5678,13 @@ fn add_service_call_edges(
known_interface_types: &HashSet<String>,
data_values: &HashMap<String, serde_json::Value>,
) -> Result<(), LowerError> {
// Track transport endpoint usage across ALL modules so that any callable
// referencing a service operation already wired by a prior module gets a
// cloned triplet (_c1, _c2, …) instead of wiring duplicate scalar edges
// to the original.
let mut endpoint_use_count: HashMap<String, usize> = HashMap::new();
for module in &project.modules {
let module_name = module.module_path.as_dotted();
// Track transport endpoint usage across ALL callables in the module so
// that the second callable to reference the same service operation gets
// a cloned triplet (_c1, _c2, …) instead of wiring duplicate scalar
// edges to the original.
let mut endpoint_use_count: HashMap<String, usize> = HashMap::new();
for item in &module.ast.items {
let (item_name, params, stmts, uses_binding_types, body_lossy) = match &item.node {
Item::FnDef(def) => (
Expand Down
3 changes: 3 additions & 0 deletions core/daglang/daglang-resolve/tests/module_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ fn real_corpus_dependency_counts_match_expected_snapshot() {
("shared.gist_modes".into(), 5),
("std.access".into(), 0),
("std.box_draw".into(), 3),
("std.ci".into(), 0),
("std.ci_render".into(), 1),
("std.fermi".into(), 1),
("std.fidelity".into(), 2),
("std.filesystem".into(), 1),
Expand All @@ -243,6 +245,7 @@ fn real_corpus_dependency_counts_match_expected_snapshot() {
("std.width".into(), 2),
("tools.bootstrap".into(), 4),
("tools.build".into(), 3),
("tools.cigen".into(), 3),
("tools.clippy".into(), 4),
("tools.codegen".into(), 2),
("tools.deps".into(), 4),
Expand Down
3 changes: 3 additions & 0 deletions core/daglang/daglang-syntax/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ pub fn expected_dsl_files_sorted() -> Vec<&'static str> {
"shared/gist_modes.dag",
"std/access.dag",
"std/box_draw.dag",
"std/ci.dag",
"std/ci_render.dag",
"std/fermi.dag",
"std/fidelity.dag",
"std/filesystem.dag",
Expand All @@ -125,6 +127,7 @@ pub fn expected_dsl_files_sorted() -> Vec<&'static str> {
"std/width.dag",
"tools/bootstrap.dag",
"tools/build.dag",
"tools/cigen.dag",
"tools/clippy.dag",
"tools/codegen.dag",
"tools/deps.dag",
Expand Down
11 changes: 11 additions & 0 deletions core/infra/src/workspace_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,17 @@ pub fn baseline_commit_policies() -> Vec<CommitPolicy> {
reason: CommitReason::BootstrapSeed,
producer: Some("docgen"),
},
// CI workflow YAML — generated by cigen but committed for CI systems.
CommitPolicy {
pattern: ".github/workflows/ci.yml",
reason: CommitReason::BootstrapSeed,
producer: Some("cigen"),
},
CommitPolicy {
pattern: ".gitlab-ci.yml",
reason: CommitReason::BootstrapSeed,
producer: Some("cigen"),
},
]
}

Expand Down
127 changes: 127 additions & 0 deletions dsl/std/ci.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// std/ci.dag -- CI workflow model: tautological definitions.
//
// Layer 0 types for CI/CD pipeline configuration. Each type models
// a universal CI concept (providers, jobs, steps, triggers, caches).
// Data declarations capture canonical defaults (Rust cache paths,
// Cargo CI environment). The rendering layer (ci_render.dag) consumes
// these types to produce provider-specific YAML.

module std.ci

// ---------------------------------------------------------------------------
// Provider
// ---------------------------------------------------------------------------

type CiProvider = GitHub | GitLab

// ---------------------------------------------------------------------------
// Primitives (composition blocks)
// ---------------------------------------------------------------------------

type CiPermission {
scope: String,
level: String
}

type CiEnvVar {
key: String,
value: String
}

type CiCache {
key: String,
paths: List<String>,
restore_keys: List<String>
}

type CiCheckout {
fetch_depth: Int?,
submodules: String?
}

type CiRunner {
id: String,
name: String
}

type CiTrigger {
branches: List<String>,
on_push: Bool,
on_pull_request: Bool
}

type CiSecret {
name: String
}

// ---------------------------------------------------------------------------
// Step (sum type — provider-agnostic CI operations)
// ---------------------------------------------------------------------------

type CiStep
= Run { name: String, command: String }
| Uses { name: String, action: String, with_params: List<CiEnvVar> }
| DagRun { name: String, command: String, env: List<CiEnvVar> }

// ---------------------------------------------------------------------------
// Job and Workflow (compositional aggregation)
// ---------------------------------------------------------------------------

type CiJob {
name: String,
runner: CiRunner,
timeout_minutes: Int,
steps: List<CiStep>
}

type CiWorkflow {
name: String,
provider: CiProvider,
trigger: CiTrigger,
permissions: List<CiPermission>,
env: List<CiEnvVar>,
cache: CiCache?,
checkout: CiCheckout?,
jobs: List<CiJob>,
secrets_env: List<String>,
generator_name: String,
regenerate_command: String
}

// ---------------------------------------------------------------------------
// Tautological data: canonical CI defaults
// ---------------------------------------------------------------------------

data default_checkout: CiCheckout = {
fetch_depth: 1,
submodules: null
}

data ubuntu_latest: CiRunner = {
id: "ubuntu-latest",
name: "Ubuntu Latest"
}

data rust_cache: CiCache = {
key: "cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}",
paths: [
"~/.cargo/bin/",
"~/.cargo/registry/index/",
"~/.cargo/registry/cache/",
"~/.cargo/git/db/"
],
restore_keys: [
"cargo-${{ runner.os }}-"
]
}

data cargo_ci_env: List<CiEnvVar> = [
{ key: "CARGO_TERM_COLOR", value: "always" },
{ key: "RUSTFLAGS", value: "-D warnings" }
]

data default_trigger: CiTrigger = {
branches: ["main"],
on_push: true,
on_pull_request: true
}
198 changes: 198 additions & 0 deletions dsl/std/ci_render.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// std/ci_render.dag -- CI workflow rendering: provider-specific YAML output.
//
// Pure functions that transform CiWorkflow records into YAML strings.
// Matches the output previously generated by codegen_cli.rs. Each helper
// renders one YAML section; top-level functions compose them.

module std.ci_render

import std.ci {
CiProvider, CiWorkflow, CiJob, CiStep, CiEnvVar, CiPermission,
CiCache, CiCheckout, CiTrigger, CiRunner, CiSecret
}

// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------

fn render_ci_header(generator_name: String, regenerate_command: String) -> String {
"# Generated by {generator_name}\n# DO NOT EDIT - regenerate with: {regenerate_command}"
}

fn render_yaml_block(header: String, items: List<String>) -> String {
if items |> count() == 0 {
""
} else {
let lines = items |> join("\n")
"{header}\n{lines}\n"
}
}

// ---------------------------------------------------------------------------
// GitHub Actions rendering
// ---------------------------------------------------------------------------

fn render_github_trigger(trigger: CiTrigger) -> String {
let branch_lines = trigger.branches |> map(b => " - {b}") |> join("\n")
let push_block = if trigger.on_push {
"on:\n push:\n branches:\n{branch_lines}\n"
} else {
"on:\n"
}
let pr_block = if trigger.on_pull_request {
" pull_request:\n branches:\n{branch_lines}\n"
} else {
""
}
"{push_block}{pr_block}"
}

fn render_github_permissions(permissions: List<CiPermission>) -> String {
if permissions |> count() == 0 {
""
} else {
let lines = permissions |> map(p => " {p.scope}: {p.level}") |> join("\n")
"permissions:\n{lines}\n\n"
}
}

fn render_github_env(env: List<CiEnvVar>) -> String {
if env |> count() == 0 {
""
} else {
let lines = env |> map(e => " {e.key}: {e.value}") |> join("\n")
"env:\n{lines}\n\n"
}
}

fn render_github_cache(cache: CiCache?) -> String {
match cache {
null => "",
c => {
let path_lines = c.paths |> map(p => " {p}") |> join("\n")
let restore_lines = c.restore_keys |> map(k => " {k}") |> join("\n")
" - name: Cache Cargo\n uses: actions/cache@v4\n with:\n path: |\n{path_lines}\n key: {c.key}\n restore-keys: |\n{restore_lines}\n"
}
}
}

fn render_github_checkout(checkout: CiCheckout?) -> String {
match checkout {
null => "",
co => {
let base = " - name: Checkout\n uses: actions/checkout@v4\n"
match co.fetch_depth {
null => base + "\n",
depth => base + " with:\n fetch-depth: {depth}\n\n"
}
}
}
}

fn render_github_step_env(env: List<CiEnvVar>) -> String {
if env |> count() == 0 {
""
} else {
let lines = env |> map(e => " {e.key}: {e.value}") |> join("\n")
" env:\n{lines}\n"
}
}

fn render_github_secrets(secrets: List<String>) -> String {
secrets |> map(s => " {s}: ${{{{ secrets.{s} }}}}") |> join("\n")
}

fn render_github_job(job: CiJob, checkout: CiCheckout?, cache: CiCache?, secrets_env: List<String>) -> String {
let job_header = "jobs:\n {job.name}:\n runs-on: {job.runner.id}\n timeout-minutes: {job.timeout_minutes}\n steps:\n"
let checkout_section = render_github_checkout(checkout: checkout)
let rust_setup = " - name: Setup Rust\n uses: dtolnay/rust-toolchain@stable\n\n"
let cache_section = render_github_cache(cache: cache)
let steps_section = job.steps |> map(step => render_github_step(step: step, secrets_env: secrets_env)) |> join("")
"{job_header}{checkout_section}{rust_setup}{cache_section}{steps_section}"
}

fn render_github_step(step: CiStep, secrets_env: List<String>) -> String {
match step {
Run(s) => " - name: {s.name}\n run: {s.command}\n",
Uses(s) => {
let base = " - name: {s.name}\n uses: {s.action}\n"
if s.with_params |> count() > 0 {
let params = s.with_params |> map(p => " {p.key}: {p.value}") |> join("\n")
base + " with:\n{params}\n"
} else {
base
}
},
DagRun(s) => {
let base = " - name: {s.name}\n run: {s.command}\n"
let env_lines = s.env |> map(e => " {e.key}: {e.value}") |> join("\n")
let secret_lines = render_github_secrets(secrets: secrets_env)
let all_env = if env_lines + secret_lines != "" {
let combined = if env_lines != "" && secret_lines != "" {
"{env_lines}\n{secret_lines}"
} else {
"{env_lines}{secret_lines}"
}
" env:\n{combined}\n"
} else {
""
}
"{base}{all_env}"
}
}
}

fn render_github_workflow(w: CiWorkflow) -> String {
let header = render_ci_header(generator_name: w.generator_name, regenerate_command: w.regenerate_command)
let name_line = "\n\nname: {w.name}\n\n"
let trigger = render_github_trigger(trigger: w.trigger)
let permissions = render_github_permissions(permissions: w.permissions)
let env = render_github_env(env: w.env)
let job = w.jobs |> first()
match job {
null => "{header}{name_line}{trigger}{permissions}{env}",
j => {
let job_section = render_github_job(job: j, checkout: w.checkout, cache: w.cache, secrets_env: w.secrets_env)
"{header}{name_line}{trigger}{permissions}{env}{job_section}"
}
}
}

// ---------------------------------------------------------------------------
// GitLab CI rendering
// ---------------------------------------------------------------------------

fn render_gitlab_variables(env: List<CiEnvVar>) -> String {
if env |> count() == 0 {
""
} else {
let lines = env |> map(e => " {e.key}: \"{e.value}\"") |> join("\n")
"variables:\n{lines}\n\n"
}
}

fn render_gitlab_cache() -> String {
"cache:\n key: cargo-${{CI_COMMIT_REF_SLUG}}\n paths:\n - .cargo/\n - target/\n\n"
}

fn render_gitlab_workflow(w: CiWorkflow) -> String {
let header = render_ci_header(generator_name: w.generator_name, regenerate_command: w.regenerate_command)
let image = "\n\nimage: rust:latest\n\n"
let variables = render_gitlab_variables(env: w.env)
let stages = "stages:\n - ci\n\n"
let cache = render_gitlab_cache()
let job = w.jobs |> first()
match job {
null => "{header}{image}{variables}{stages}{cache}",
j => {
let step = j.steps |> first()
let command = match step {
null => "echo 'no steps'",
DagRun(s) => s.command,
Run(s) => s.command,
Uses(s) => s.action
}
"{header}{image}{variables}{stages}{cache}{w.name}:\n stage: ci\n script:\n - {command}\n"
}
}
}
Loading
Loading