Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/ir.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Those wrappers are the only place per-target shape (top-level `PipelineShape`, t
- `tasks.rs` — typed factory helpers for built-in ADO tasks that return preconfigured `TaskStep` values with required inputs set. Prefer extending these helpers for compiler-generated tasks rather than open-coding `TaskStep::new(...)` with raw string keys at each call site.
- `job.rs` — `Job`, `Pool`, job variables, 1ES `templateContext` support, and target-job external `dependsOn` / `condition` wrapping.
- `stage.rs` — `Stage` plus target-stage external `dependsOn` / `condition` wrapping.
- `env.rs` — typed environment values (`EnvValue`) including ADO macros, pipeline variables, secrets, `OutputRef`s, `Coalesce`, and macro-form `Concat`.
- `env.rs` — typed environment values (`EnvValue`) including ADO macros, pipeline variables, secrets, `OutputRef`s, `Coalesce`, macro-form `Concat`, and `RuntimeExpression` (a `$[ ... ]` ADO runtime expression that the lowering pass auto-hoists to a job-level `variables:` entry — ADO does not evaluate `$[ ... ]` inside step `env:`). `RuntimeExpression` is only valid at the top level of a step `env:` value: nesting it inside `Concat` or `Coalesce` is rejected at lower time (the hoist pass walks only the top level, so a nested occurrence would emit a dangling `$(AwRtExpr_…)` macro). A `Literal` or `RawYamlScalar(String)` smuggling a raw `$[ ... ]` into a step `env:` value (whether at the top level or nested inside a `Concat`) is likewise rejected at lower time — ADO passes such scalars verbatim, so the typed `RuntimeExpression` variant must be used instead.
- `condition.rs` — the `Condition` / `Expr` AST and code generation to ADO condition syntax.
- `output.rs` — `OutputDecl`, `OutputRef`, and the output-reference lowering rules.
- `graph.rs` — graph construction, `dependsOn` derivation, output validation, `isOutput=true` promotion, and cycle detection.
Expand Down
54 changes: 54 additions & 0 deletions src/compile/ir/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
//! same-job consumers, where macro form is the only form that
//! resolves correctly (see `src/compile/filter_ir.rs` for the
//! underlying bug history).
//! - [`EnvValue::RuntimeExpression`] — a hand-written ADO `$[ ... ]`
//! runtime expression. ADO does not evaluate these inside step
//! `env:`, so the lowering pass hoists each one to a
//! compiler-generated job-level `variables:` entry and emits a
//! `$(name)` macro reference in the step `env:`.

use super::output::OutputRef;

Expand Down Expand Up @@ -86,6 +91,25 @@ pub enum EnvValue {
/// yields the live value — the `prGate` step's
/// `$(System.PullRequest.X)$(synthPr.X)` exclusive-OR.
Concat(Vec<EnvValue>),
/// A hand-written ADO **runtime expression** (`$[ ... ]`), carried
/// as a distinct type so it can never be confused with a
/// [`EnvValue::Literal`].
///
/// The stored string is the expression **body** only — the
/// `$[ ` / ` ]` wrapper is added during lowering. For example
/// `RuntimeExpression("coalesce(dependencies.Agent.result, '')")`
/// lowers to the variable value `$[ coalesce(dependencies.Agent.result, '') ]`.
///
/// ADO only evaluates `$[ ... ]` runtime expressions inside
/// job-level `variables:` mappings and `condition:` fields — **not**
/// inside step `env:` values, where the literal expression string is
/// passed verbatim (msazuresphere/4x4 build #612528,
/// githubnext/ado-aw#1076). To make that mistake structurally
/// impossible, the lowering pass automatically **hoists** every
/// `RuntimeExpression` in a step's `env:` to a compiler-generated
/// job-level `variables:` entry and rewrites the step `env:` value
/// to a `$(generated_name)` macro reference pointing at it.
RuntimeExpression(String),
/// Pre-built YAML scalar emitted verbatim into the value position.
///
/// Used by [`crate::compile::agentic_pipeline`] when a legacy YAML
Expand Down Expand Up @@ -210,6 +234,25 @@ impl EnvValue {
pub fn concat(values: Vec<EnvValue>) -> Self {
EnvValue::Concat(values)
}

/// Construct an [`EnvValue::RuntimeExpression`] from an ADO
/// runtime-expression **body** (without the `$[ ]` wrapper).
///
/// Use this instead of [`EnvValue::literal`] whenever a step
/// `env:` value needs an ADO `$[ ... ]` runtime expression: the
/// lowering pass hoists it to a job-level `variables:` entry and
/// emits a `$(name)` macro reference, because ADO does not
/// evaluate `$[ ... ]` inside step `env:`.
///
/// **Body is interpolated verbatim.** At hoist time the lowering
/// pass wraps `body` as `$[ {body} ]` without escaping, so this
/// constructor is intended for compiler-internal expressions only.
/// A `body` containing `]` would prematurely close the wrapper and
/// produce a malformed expression. If untrusted/user text is ever
/// routed here, validate it (reject `]`) before constructing.
pub fn runtime_expression(body: impl Into<String>) -> Self {
EnvValue::RuntimeExpression(body.into())
}
}

#[cfg(test)]
Expand Down Expand Up @@ -269,4 +312,15 @@ mod tests {
_ => panic!("expected Concat"),
}
}

#[test]
fn runtime_expression_stores_body_verbatim() {
let v = EnvValue::runtime_expression("coalesce(dependencies.Agent.result, '')");
match v {
EnvValue::RuntimeExpression(body) => {
assert_eq!(body, "coalesce(dependencies.Agent.result, '')");
}
_ => panic!("expected RuntimeExpression"),
}
}
}
1 change: 1 addition & 0 deletions src/compile/ir/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ fn collect_env_refs_into<'a>(v: &'a EnvValue, out: &mut Vec<&'a OutputRef>) {
| EnvValue::AdoMacro(_)
| EnvValue::PipelineVar(_)
| EnvValue::Secret(_)
| EnvValue::RuntimeExpression(_)
| EnvValue::RawYamlScalar(_) => {}
EnvValue::StepOutput(r) => out.push(r),
EnvValue::Coalesce(children) | EnvValue::Concat(children) => {
Expand Down
Loading
Loading