Skip to content

chore(core): unify Task into a single Rust struct#35540

Merged
AgentEnder merged 8 commits intomasterfrom
explore-rust-task
May 4, 2026
Merged

chore(core): unify Task into a single Rust struct#35540
AgentEnder merged 8 commits intomasterfrom
explore-rust-task

Conversation

@FrozenPandaz
Copy link
Copy Markdown
Contributor

Current Behavior

Task is defined twice and the two definitions have drifted:

  • TS canonical: packages/nx/src/config/task-graph.ts — 13 fields including overrides: any, hash, hashDetails, cache, parallelism.
  • Rust / NAPI: packages/nx/src/native/tasks/types.rs — 7 fields, missing all the dynamic / options-shaped ones.

Plus a swarm of variant types (TaskResult defined in both Rust and TS, TaskTarget, etc.). Anything that wants to consume a Task in Rust either gets the lossy 7-field copy or has to translate at the boundary. The historical blocker for unifying was Task.overrides — a freeform POJO of CLI args that had no obvious Rust representation.

Expected Behavior

One canonical Task struct, defined in Rust, exposed to TS via NAPI:

  • The Rust struct in packages/nx/src/native/tasks/types.rs is now the superset (all 13 fields).
  • overrides is modeled as serde_json::Value, surfaced in TS as Record<string, unknown>. The serde-json napi feature is enabled to make this round-trip work.
  • TaskResult.status gets the literal-union TS type via #[napi(ts_type)] so we can collapse the duplicate TaskResult in tasks-runner/life-cycle.ts into a re-export from the native bindings.
  • packages/nx/src/config/task-graph.ts now re-exports Task / TaskGraph / TaskTarget / TaskHashDetails from ../native. Every existing import keeps working.
  • A small ergonomics layer for Rust callers: Task::new(project, target) (id auto-derived as {project}:{target}, matching the formula create-task-graph.ts already uses) plus chained with_* builders for the optional fields. with_configuration recomputes the id with the configuration suffix.

Behavior is unchanged on every existing TS code path. The legacy overrides: Object parameter annotations in create-task-graph.ts and hash-plan-inspector.ts are tightened to Record<string, unknown>. Direct overrides reads at combineOptionsForExecutor call sites and __overrides_unparsed__ accesses get tight casts that preserve prior runtime semantics, including the throw-on-missing behavior.

This unblocks porting the task scheduler (and eventually the orchestrator) to Rust — without one shared struct, every port has to redo the boundary translation.

Verification

  • cargo test --lib — 352 passed, 0 failed.
  • pnpm nx build nx — clean.
  • pnpm nx run-many -t build — all packages build (the two examples-angular-rspack-mf-* failures predate this branch).
  • pnpm nx run nx:lint — clean (only a pre-existing skipped-test warning).
  • pnpm nx run nx:test --testPathPatterns='create-task-graph|tasks-runner|hasher' — all suites pass, including should interpolate overrides.

Out of scope

  • Porting orchestrator / scheduler to Rust (this is the foundation for that next).
  • Collapsing HashedTask (history DB row) and TaskRun (history record) — they are storage shapes, not Tasks.

Related Issue(s)

Foundation for the eventual Rust port of the task scheduler and orchestrator.

The TS Task interface and the Rust Task struct were two separate
definitions that drifted: the Rust copy was missing overrides, hash,
hashDetails, cache, and parallelism. Make the Rust struct in
packages/nx/src/native/tasks/types.rs the canonical superset and have
TS re-export it through ../native, so Task / TaskGraph / TaskTarget /
TaskResult / TaskHashDetails are all defined in one place. This
unblocks moving the task orchestrator and scheduler to Rust.

The historical blocker for unifying was Task.overrides (a freeform
POJO of CLI args). Enable napi's serde-json feature so the field can
be modeled as serde_json::Value, surfaced in TS as
Record<string, unknown>. TaskResult.status gets the literal-union TS
type via #[napi(ts_type)], letting us drop the duplicate TaskResult
in tasks-runner/life-cycle.ts.

Behavior is unchanged on every existing code path. The legacy
overrides: Object parameter annotations in create-task-graph.ts and
hash-plan-inspector.ts are upgraded to Record<string, unknown>;
direct overrides reads (combineOptionsForExecutor call sites,
__overrides_unparsed__) get tight casts that preserve the prior
runtime semantics including the throw-on-missing behavior.
The TUI / dep-outputs Rust tests were spelling out every Task field
with placeholder Nones. Add a Task::new(id, project, target)
constructor that fills in the canonical required fields and leaves
the rest at Default, so tests can use struct-update syntax to
override only what they care about. Net -423 lines from test
fixtures, and adding new fields to Task no longer requires touching
every test site.
The struct-update form was still requiring Task { ... } at every test
site. Add with_project_root / with_outputs / with_continuous /
with_configuration / with_hash / with_cache / with_parallelism /
with_overrides on Task, all consuming self by value so they chain.

Test fixtures now read as plain method chains:

  Task::new("task1", "app1", "test")
      .with_project_root("")
      .with_continuous(false)

instead of:

  Task {
      project_root: Some("".to_string()),
      continuous: Some(false),
      ..Task::new("task1", "app1", "test")
  }
Task::new now takes (project, target) and derives the id as
"{project}:{target}", matching the formula the TS task-graph builder
already uses (packages/nx/src/tasks-runner/create-task-graph.ts).
with_configuration recomputes the id with the configuration suffix.
A with_id escape hatch exists for the small handful of test fixtures
that depend on a specific non-canonical id (length-sensitive column
visibility tests, pinned-task width arithmetic).

TS construction is unaffected. createTaskGraph still builds the id
explicitly when assembling the task literal, just like today.

Test code that previously queried tasks by their non-canonical ids
("app1", "task1", etc.) now uses the canonical "{project}:{target}"
form. Snapshot expectations were regenerated for the new display
strings.
@netlify
Copy link
Copy Markdown

netlify Bot commented May 1, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 84fe001
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/69f56811e3bb420008edb550
😎 Deploy Preview https://deploy-preview-35540--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 1, 2026

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 84fe001
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/69f568112cd6b20008e1667e
😎 Deploy Preview https://deploy-preview-35540--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented May 1, 2026

View your CI Pipeline Execution ↗ for commit 84fe001

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 36m 30s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 4s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 17s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 23s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 7s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-02 03:37:36 UTC

Remove with_configuration / with_hash / with_cache / with_parallelism /
with_overrides — none had callers. Keeping with_project_root /
with_outputs / with_continuous (the three actually used in test
fixtures).

Also extract getUnparsedOverrideArgs(task) in tasks-runner/utils.ts
and use it at the three call sites that were repeating
(task.overrides as { __overrides_unparsed__: string[] }).
__overrides_unparsed__.

Trim a now-redundant inline example from the Task::new doc.
nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

FrozenPandaz and others added 2 commits May 1, 2026 22:39
Restores the ability for callers of initTasksRunner.invoke (and any other
JS-side Task constructor) to omit `parallelism`. napi-rs was rejecting
hand-built Task objects with `Missing field parallelism on TaskGraph.tasks`
because the unified Rust struct declared it as a required `bool`.

Mirrors the optionality of `cache` and `continuous`. Adds
`Task::is_parallelizable()` so future Rust readers default `None` to `true`,
matching `create-task-graph.ts` (`parallelism ?? true`).
Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nx Cloud has identified a flaky task in your failed CI:

🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.

Nx Cloud View detailed reasoning in Nx Cloud ↗


🎓 Learn more about Self-Healing CI on nx.dev

Comment on lines -8 to +10
"> ✔ task1 - ..."
" ✔ task2 - ..."
" ✔ task3 - ..."
"> ✔ app1:build - ..."
" ✔ app1:test - ..."
" ✔ app2:lint - ..."
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suspicious. Why did these change?

@FrozenPandaz FrozenPandaz marked this pull request as ready for review May 4, 2026 13:31
@FrozenPandaz FrozenPandaz requested a review from a team as a code owner May 4, 2026 13:31
@FrozenPandaz FrozenPandaz requested a review from MaxKless May 4, 2026 13:31
@AgentEnder AgentEnder merged commit e8aa612 into master May 4, 2026
24 checks passed
@AgentEnder AgentEnder deleted the explore-rust-task branch May 4, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants