You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(ir): replace task helpers with typed builder structs
Replace the free-function ADO task helpers in src/compile/ir/tasks.rs
(positional required args + untyped .with_input("camelKey","str") for
optionals) with a tasks/ directory module of typed builder structs, one
file per task. Each builder exposes new(<required>) + typed chained
setters + into_step() -> TaskStep; only set fields emit inputs, with
enums for constrained values and Option<bool> for bool-string inputs.
Command/mode-dispatch tasks (Docker@2, DotNetCoreCLI@2, NuGetCommand@2,
PowerShell@2) use a command enum with per-variant data so invalid
input/command combinations are unrepresentable; tasks/docker.rs is the
canonical template. Migrate the docker_installer call sites, update the
ado-task-ir-contributor workflow + docs to the new convention.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
description: Crawls Azure DevOps built-in task docs, contributes typed IR helper functions for uncovered tasks, converts compiler-generated Step::RawYaml usages to typed steps, and opens a focused PR per run.
5
+
description: Crawls Azure DevOps built-in task docs, contributes typed IR builder structs for uncovered tasks, converts compiler-generated Step::RawYaml usages to typed steps, and opens a focused PR per run.
6
6
permissions:
7
7
contents: read
8
8
issues: read
@@ -42,9 +42,9 @@ The `ado-aw` compiler transforms agent markdown into Azure DevOps YAML through a
-`Step::RawYaml` — **escape hatch** for user-authored YAML that the IR cannot model
44
44
45
-
The `TaskStep` struct is already fully generic — it accepts any `task: String` and an `inputs: IndexMap`. The gap is:
46
-
1.**No typed factory functions** for most ADO built-in tasks — every place that uses a known ADO task must hand-craft `TaskStep::new("Foo@1", "display").with_input(...)`.
47
-
2.**Compiler-generated `Step::RawYaml`** — occasionally, extension/runtime code emits `Step::RawYaml` for steps that could be expressed as `Step::Task` with a typed helper; these should be migrated.
45
+
The `TaskStep` struct is fully generic — it accepts any `task: String` and an `inputs: IndexMap`. Typed coverage of specific ADO tasks lives in `src/compile/ir/tasks/` as **builder structs** (one submodule per task). Each builder exposes `new(<required>)`, one typed chained setter per optional input, and `into_step() -> TaskStep`; only inputs that were set are emitted. Constrained input values are typed enums (each with `as_ado_str()`), bool-string inputs take a Rust `bool`, and command/mode-dispatch tasks (`Docker@2`, `DotNetCoreCLI@2`, `NuGetCommand@2`, `PowerShell@2`) use a command enum with per-variant data so invalid input/command combinations are unrepresentable. `src/compile/ir/tasks/docker.rs` is the canonical template. The gaps are:
46
+
1.**No typed builder** for most ADO built-in tasks — code that uses an uncovered task must hand-craft `TaskStep::new("Foo@1", "display").with_input(...)` with raw string keys.
47
+
2.**Compiler-generated `Step::RawYaml`** — occasionally, extension/runtime code emits `Step::RawYaml` for steps that could be expressed as `Step::Task` with a typed builder; these should be migrated.
48
48
49
49
## Step 1 — Load Previous State
50
50
@@ -67,10 +67,13 @@ If the prior PR is still open, emit `noop` with "Waiting on open PR #N before ad
67
67
68
68
## Step 2 — Audit Existing Typed Coverage
69
69
70
-
Examine what typed factory functions already exist:
70
+
Examine which tasks already have a typed builder under `src/compile/ir/tasks/`:
71
71
72
72
```bash
73
-
# Existing TaskStep factory functions in runtimes and extensions
73
+
# One submodule per covered task; the struct + into_step are the builder.
Ignore `push_raw_yaml_if_nonempty`, `step_to_raw_yaml_string`, and any function that explicitly handles user-authored YAML (these are legitimately `Step::RawYaml`).
89
92
90
-
Build a set of **already-typed tasks**: task identifiers that already have typed factory functions (e.g. `UseNode@1`, `UseDotNet@2`, `UsePythonVersion@0`, `NpmAuthenticate@0`, `PipAuthenticate@1`, `NuGetAuthenticate@1`).
93
+
Build the set of **already-typed tasks**: the union of the task identifiers covered by `src/compile/ir/tasks/` builder structs and the runtime/extension factories (e.g. `UseNode@1`, `UseDotNet@2`, `UsePythonVersion@0`, `NpmAuthenticate@0`, `PipAuthenticate@1`, `NuGetAuthenticate@1`).
1. Is **not** already in `completed_tasks` from state
124
-
2. Is **not** already typed (no existing factory function)
127
+
2. Is **not** already typed (no existing builder struct under `src/compile/ir/tasks/` and no runtime/extension factory)
125
128
3. Has a stable, widely-used task identifier (prefer tasks in the `Utility`, `Build`, or `Test` categories)
126
129
4. Has clear documentation on `learn.microsoft.com`
127
130
131
+
The chosen task will become a new submodule `src/compile/ir/tasks/<task_snake>.rs`.
132
+
128
133
Priority order (highest first):
129
134
1. A `Step::RawYaml` in compiler-generated code that maps to a known ADO task — converting it to `Step::Task` removes tech debt directly.
130
135
2. A task used frequently in the `tests/` fixtures as raw YAML strings.
@@ -149,67 +154,130 @@ Document the task:
149
154
- Optional inputs with defaults
150
155
- Relevant use cases in ado-aw
151
156
152
-
## Step 5 — Implement the Typed Helper
157
+
## Step 5 — Implement the Typed Builder
153
158
154
-
Decide where to place the factory function:
159
+
Decide where to place the builder:
155
160
156
-
-**If the task is tied to a specific runtime** (language install, package auth): add to `src/runtimes/<name>/mod.rs`.
157
-
-**If the task is a general-purpose ADO built-in**: add to `src/compile/ir/step.rs`as a standalone constructor function alongside the existing step types, or create a new `src/compile/ir/tasks.rs`module if multiple new tasks are being added.
161
+
-**If the task is tied to a specific runtime** (language install, package auth): add to `src/runtimes/<name>/mod.rs` (these remain free factory functions).
162
+
-**If the task is a general-purpose ADO built-in**: create a **new submodule**`src/compile/ir/tasks/<task_snake>.rs`and declare it in `src/compile/ir/tasks/mod.rs`with `pub mod <task_snake>;` (alphabetical order). One file per task.
158
163
159
-
For a new `tasks.rs` module, create it at `src/compile/ir/tasks.rs` and add `pub mod tasks;` to `src/compile/ir/mod.rs`.
164
+
### Builder struct shape
160
165
161
-
### Helper function shape
166
+
Model after `src/compile/ir/tasks/copy_files.rs` (a single-mode task) and, for command/mode-dispatch tasks, `src/compile/ir/tasks/docker.rs` (the canonical command-enum template). Shared helpers (`bool_input`, `push_opt`, `push_bool`) live in `src/compile/ir/tasks/common.rs`.
162
167
163
-
Model after existing patterns in `src/runtimes/`:
168
+
A builder is a struct with the required inputs as fields, each optional input as an `Option<…>` field, and a `display_name: Option<String>` override:
164
169
165
170
```rust
166
-
/// Returns a [`TaskStep`] for `CopyFiles@2`.
167
-
///
168
-
/// Copies files matching `contents` from `source_folder` to `target_folder`.
169
-
/// All parameters map directly to the ADO task inputs.
170
-
pubfncopy_files_step(
171
-
source_folder:implInto<String>,
172
-
contents:implInto<String>,
173
-
target_folder:implInto<String>,
174
-
) ->TaskStep {
175
-
TaskStep::new("CopyFiles@2", "Copy Files")
176
-
.with_input("SourceFolder", source_folder)
177
-
.with_input("Contents", contents)
178
-
.with_input("TargetFolder", target_folder)
171
+
usesuper::common::bool_input;
172
+
usecrate::compile::ir::step::TaskStep;
173
+
174
+
/// Builder for a [`TaskStep`] invoking `CopyFiles@2`.
175
+
#[derive(Debug, Clone)]
176
+
pubstructCopyFiles {
177
+
contents:String,
178
+
target_folder:String,
179
+
source_folder:Option<String>,
180
+
clean_target_folder:Option<bool>,
181
+
display_name:Option<String>,
182
+
}
183
+
184
+
implCopyFiles {
185
+
/// Required inputs are positional parameters of `new`.
- Function name: snake_case, derived from the task display name, without the version suffix (e.g. `copy_files_step`, `publish_test_results_step`).
184
-
- Required inputs: positional parameters.
185
-
- Optional inputs with common defaults: keyword-style builder on the returned `TaskStep` (use `.with_input` after the initial construction).
186
-
- Include a doc comment with the task identifier and a one-line description.
187
-
- Do NOT add a new `Step` enum variant for standard tasks — `Step::Task(TaskStep)` is the correct representation.
234
+
- Struct name: PascalCase of the task display name, without the version suffix (e.g. `CopyFiles`, `PublishTestResults`).
235
+
-**Required inputs** → positional parameters of `new`.
236
+
-**Optional inputs** → `Option<…>` fields with one typed chained setter each; only emit them in `into_step` when set.
237
+
-**Constrained values** (a fixed set of string tokens) → a typed enum with `as_ado_str(&self) -> &'static str` returning the exact ADO token. Colocate the enum with the task (reusable shared enums may live in `common.rs`).
238
+
-**Bool-string inputs** → `Option<bool>`; lower via `bool_input`.
239
+
-**Command/mode-dispatch tasks** (each command exposes a different optional-input set) → a command **enum with per-variant data** so invalid input/command combos are unrepresentable. Wrap it in a builder struct (`new(<Command>)` plus per-command constructors) and match the variant in `into_step`. Model on `src/compile/ir/tasks/docker.rs`.
240
+
- Include a doc comment with the task identifier and the ADO task reference URL.
241
+
- Do NOT add a new `Step` enum variant for standard tasks — `Step::Task(<builder>.into_step())` is the correct representation.
188
242
189
243
### Convert RawYaml if applicable
190
244
191
245
If Step 4 identified a `Step::RawYaml` in compiler code that this task covers, replace it now:
Add at least one unit test to the same file (or `tests/compiler_tests.rs` for integration tests):
258
+
Add at least one `#[cfg(test)] mod tests`unit test to the new task submodule (or `tests/compiler_tests.rs` for integration tests), building via the struct and asserting on `task` / `inputs`:
0 commit comments