diff --git a/.dagger/modules/e2e/fixtures/generate/app/.gitattributes b/.dagger/modules/e2e/fixtures/generate/app/.gitattributes new file mode 100644 index 0000000..8274184 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/generate/app/.gitattributes @@ -0,0 +1 @@ +/sdk/** linguist-generated diff --git a/.dagger/modules/e2e/main.dang b/.dagger/modules/e2e/main.dang index 214c0cc..95069ff 100644 --- a/.dagger/modules/e2e/main.dang +++ b/.dagger/modules/e2e/main.dang @@ -46,6 +46,17 @@ type E2e { assert(contains(changes.addedPaths, path), "expected added path: " + path) } + """ + Assert that a changeset reported none of `paths` as added. + """ + let assertNoneAdded(changes: Changeset!, paths: [String!]!, what: String!): Void { + let reAdded = paths.filter { path => contains(changes.addedPaths, path) } + assert( + reAdded.length == 0, + what + " re-added files that already exist: " + reAdded.join(", "), + ) + } + """ Assert that a string contains a substring. """ @@ -249,6 +260,27 @@ type E2e { null } + """ + Generation should report only what generation produced. + + The module's own config is engine-owned, and the fixture commits the + `.gitattributes` that generation emits: both are already on disk, so a + changeset claiming to add either describes the workspace base rather than the + generated context. Every other generated file is genuinely new on this + fixture, which is why only an already-present one can catch that. + """ + pub generateSkipsExistingFilesCheck(ws: Workspace!): Void @check { + let existing = [ + generateModulePath + "/dagger.json", + generateModulePath + "/.gitattributes", + ] + + assertNoneAdded(pythonSdk.mod(ws, path: generateModulePath).generate, existing, "generate") + assertNoneAdded(pythonSdk.generateAll(ws), existing, "generateAll") + + null + } + """ Generating an existing module should produce generated files rooted at that module without touching unrelated paths. diff --git a/dagger.lock b/dagger.lock index 677b7c3..13afe35 100644 --- a/dagger.lock +++ b/dagger.lock @@ -1,4 +1,5 @@ [["version","2"]] ["","container.from",["docker.io/library/alpine:3.22","linux/amd64"],"sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce"] ["","container.from",["docker.io/library/golang:1.25-alpine","linux/amd64"],"sha256:1ae0735f00daffa3aaf1363a5184c0d2dc55c78e3db4ec70241cdac97bf84b59"] -["","git.ref",["https://github.com/dagger/sdk-sdk","HEAD"],{"ref":"refs/heads/main","sha":"00bb06748bcf22d724ed467f2298d31f1fb49be0"}] \ No newline at end of file +["","container.from",["docker.io/library/golang:1.26-alpine","linux/amd64"],"sha256:28d89ee9cc0ff9fec75c82ca201e6bf7fdf9a679d4b7b24dfa04f2bb766bb468"] +["","git.ref",["https://github.com/dagger/sdk-sdk","HEAD"],{"ref":"refs/heads/main","sha":"334448911a8292fba0d677e5f31926c79ad80ad3"}] \ No newline at end of file diff --git a/mod.dang b/mod.dang index 68d0508..f32bc28 100644 --- a/mod.dang +++ b/mod.dang @@ -58,7 +58,26 @@ type Mod { if (skipGenerate) { ws.changes(ws) } else { - ws.moduleSource("/" + rootPath).generate(ws).changes(ws) + # Stage the local dependency closure so this module's codegen sees + # up-to-date dependency bindings. It is only an input to resolving the + # module source, never the diff baseline, so the dependencies' codegen + # does not ride along. + let stagedWs = ws.withChanges( + ws.moduleSource("/" + rootPath).generateLocalDependencies(ws), + ) + let generated = stagedWs + .moduleSource("/" + rootPath) + .generatedContextDirectory + .directory(rootPath) + + # The generated context holds only generated files, and withNewDirectory + # replaces rather than layers, so merge it onto the module first. + # Workspace.changes then reports just the difference, rooted at the + # caller's cwd. + ws.withNewDirectory( + "/" + rootPath, + ws.directory("/" + rootPath).withDirectory(".", generated), + ).changes(ws) } } } diff --git a/python-sdk.dang b/python-sdk.dang index 5278837..02d96f2 100644 --- a/python-sdk.dang +++ b/python-sdk.dang @@ -216,11 +216,10 @@ type PythonSdk { Modules with the generate skip marker are skipped. """ pub generateAll(ws: Workspace!): Changeset! @generate { - modules(ws) - .filter { mod => mod.skipGenerate == false } - .reduce(ws) { stagedWs, mod => - stagedWs.moduleSource("/" + mod.rootPath).generate(stagedWs) - } - .changes(ws) + changeset.withChangesets( + modules(ws) + .filter { mod => mod.skipGenerate == false } + .map { mod => mod.generate }, + ) } }