Skip to content

workspace: compare changes from an explicit baseline - #13855

Merged
grouville merged 5 commits into
dagger:mainfrom
grouville:polyfill-changeset-rooting
Aug 14, 2026
Merged

grouville merged 5 commits into
dagger:mainfrom
grouville:polyfill-changeset-rooting

Conversation

@grouville

@grouville grouville commented Aug 7, 2026 •

Copy link
Copy Markdown
Member

This completes the engine API needed to remove dagger/polyfill.

The caller now keeps the workspace it received, generates files on a new immutable workspace value, then compares the two:

before = disk + existing edits
after  = moduleSource.generate(before)  # before + generated files

after.changes(from: before)             # generated files only

ModuleSource.generate(workspace) returns a Workspace, so SDKs can compose generation with normal workspace edits. Workspace.changes(from:) performs the comparison only when a Changeset is required and returns paths relative to the workspace cwd.

This avoids a separate WorkspaceDiff type and avoids a fork() lifecycle that callers could forget. The baseline is an ordinary immutable workspace value already available to the caller. It also handles module initialization correctly: the SDK sees the staged module scaffold, but returns only the files it generated.

ModuleSource.generateLocalDependencies remains temporarily because released SDKs still call it. New SDK code uses generate; the compatibility field can be removed after those migrations ship.

This PR builds on #13854.

Test

go test ./core ./core/schema
dagger call engine-dev test --run 'TestWorkspaceChangesFrom|TestWorkspaceChangesetRooting|TestModuleInitGeneratesForNewModuleOnly|TestGenerateLocalDependenciesTerminatesOnRootDep|TestGeneratorsInstalledInWorkspace'

@grouville
grouville requested review from a team as code owners August 7, 2026 08:22
@grouville
grouville marked this pull request as draft August 7, 2026 09:47
@grouville
grouville force-pushed the polyfill-changeset-rooting branch from 1afe7c0 to 36c178e Compare August 11, 2026 23:00
@grouville
grouville force-pushed the polyfill-changeset-rooting branch from 36c178e to 45d1352 Compare August 11, 2026 23:58
@grouville
grouville changed the base branch from main to polyfill-workspace-apis August 12, 2026 00:17
@grouville
grouville changed the base branch from polyfill-workspace-apis to main August 12, 2026 00:17
@grouville
grouville marked this pull request as draft August 13, 2026 07:32
@grouville grouville changed the title workspace: isolate and reroot generator changes workspace: make changes cwd-relative and isolate SDK generation Aug 13, 2026
@grouville grouville changed the title workspace: make changes cwd-relative and isolate SDK generation workspace: compare changes from an explicit baseline Aug 13, 2026
@grouville
grouville force-pushed the polyfill-changeset-rooting branch 9 times, most recently from eadeb2c to b920a58 Compare August 14, 2026 05:58
@grouville
grouville marked this pull request as ready for review August 14, 2026 07:48

@vito vito left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a couple questions, approving pending their resolution (maybe no-ops)

Comment thread internal/cmd/dagger/setup.go Outdated
applied, err = handleWorkspaceResponse(ctx, dag, ws.WithChanges(changes), autoApply)
// Migration can hoist configuration from a nested module to the workspace
// root, so its preview and apply are deliberately root-scoped.
applied, err = handleWorkspaceResponse(ctx, dag, ws.WithWorkdir("."), ws.WithChanges(changes).WithWorkdir("."), autoApply)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What does withWorkdir(".") do? Seems like it would be a no-op - should it be /?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good question, I had to double-check too. Here withWorkdir(".") is not a no-op: Workspace paths are relative to the root, so . selects the root and / is invalid.

We need it on the updated workspace because migration can write dagger.toml above the current directory, while changes() returns paths from that directory. But you were right that it was redundant on from, so I removed it and added a comment in 3e35ffada.

Comment thread internal/cmd/dagger/workspace.go Outdated
isEmpty, err := updated.Changes().IsEmpty(ctx)
// Installing a module edits the workspace config, which may live above the
// caller's cwd. Measure from the workspace root for this root-scoped check.
isEmpty, err := updated.WithWorkdir(".").Changes(dagger.WorkspaceChangesOpts{From: target.WithWorkdir(".")}).IsEmpty(ctx)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same here. Installing a module can update the workspace config above the current directory, so we need withWorkdir(".") on updated. We do not need it on target, so I removed that extra call in 3e35ffada.

Comment thread core/integration/module_config_test.go Outdated

// TestEngineVersionLatestStaysFloating covers the floating "latest"
// engineVersion surviving config edits. A dagger.json can declare
// "engineVersion": "latest" — module init writes exactly that — and the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I thought specifying "engineVersion": "latest" was just AI slop, and that all things writing it should just switch to using the current engine version?

But I may have been corrected on that and just forgotten. 😅 I just distinctly remember pointing it out in a call in the early days of one of the SDKs because the model that authored it didn't know there was an API to get the current engine version. Is it a valid use case after all?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yep, you were right. This was our mistake. latest is accepted as input, but when writing the config we should resolve it and pin the concrete engine version.

I removed the floating behavior and fixed module initialization in c773db368. Tests now cover both initialization and later config edits.

@grouville
grouville force-pushed the polyfill-changeset-rooting branch from e0713bc to 3e35ffa Compare August 14, 2026 21:38
Workspace.changes previously returned every pending edit, including changes
that were already present when a generator received the workspace. Applying
that result could duplicate those earlier changes.

New callers can pass the workspace they started from. The engine compares the
two immutable workspace states and returns only the later edits, with paths
relative to the current workspace cwd for modules using the new behavior.

The argument remains optional so beta.9 CLIs and SDKs keep their cumulative
behavior during the migration. Internal callers use the explicit baseline, and
tests cover both forms.

Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Add ModuleSource.generate(workspace), which returns the supplied Workspace with
the module's generated files applied. The input workspace stays immutable, so
an SDK can compare the result with the workspace it received and return only
its own edits.

This also composes across managed modules: each resulting workspace can be
passed to the next module, and the SDK calls changes(from:) once at the end.
Local dependencies are generated only as input to their parent, so their files
are not accidentally returned twice.

Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Regenerate the checked-in clients and API reference for the optional
Workspace.changes(from:) baseline and ModuleSource.generate returning a
Workspace. The existing asSDK.modules object shape remains unchanged.

Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
Go bindings represent optional GraphQL arguments with an options struct. Update the generator fixtures to pass the baseline workspace through WorkspaceChangesOpts so the generated fixture modules compile.

Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
These commands can update the workspace config above the caller's current directory. Keep the resulting workspace rooted so changes can include that file, but leave the comparison workspace unchanged because only its contents are used as the baseline.

Signed-off-by: Guillaume de Rouville <guillaume@dagger.io>
@grouville
grouville force-pushed the polyfill-changeset-rooting branch from 3e35ffa to feca094 Compare August 14, 2026 22:27
@grouville
grouville merged commit 501b57e into dagger:main Aug 14, 2026
3 checks passed
eunomie added a commit to dagger/java-sdk that referenced this pull request Aug 20, 2026
The engine applies a generator's changeset relative to the client's cwd, so a
workspace-rooted changeset lands every path under the cwd a second time and
leaves the module ungenerated. This SDK re-rooted its own result to compensate,
and raised for a module found above the cwd, because the changesets came back
workspace-rooted.

dagger/dagger#13855 moved that into the engine: from v1.0.0-beta.10 a
Workspace.changes result is measured from the workspace's own cwd, the caller
supplies the baseline to compare against, and a change that falls outside the
cwd fails loudly instead of being dropped. Declare the version and hand the work
back — stage onto the workspace value and diff it against the baseline already
in hand.

The baseline is the dependency-staged workspace, not the one handed in: the
dependencies' generated code belongs to their own SDKs and must stay out of this
changeset, which is what the filtered before-directory used to express.

Signed-off-by: Yves Brissaud <yves@dagger.io>
TomChv added a commit to dagger/typescript-sdk that referenced this pull request Aug 21, 2026
* Drop the polyfill for native workspace APIs

The TypeScript SDK depended on `dagger/polyfill` for three things: selecting
the modules it manages, forking the workspace to stage edits, and re-rooting
the resulting changeset into the client's cwd. All three are now native in the
engine (dagger/dagger#13854, dagger/dagger#13855), so the dependency goes.

Signed-off-by: Tom Chauveau <tom@dagger.io>
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