diff --git a/.dagger/modules/e2e/fixtures/dagger.toml b/.dagger/modules/e2e/fixtures/dagger.toml index ae892f8..bc02768 100644 --- a/.dagger/modules/e2e/fixtures/dagger.toml +++ b/.dagger/modules/e2e/fixtures/dagger.toml @@ -6,16 +6,16 @@ check.skip = ["*"] name = "java" [[modules.java-sdk.as-sdk.modules]] -path = ".dagger/modules/e2e/fixtures/generate/app" +path = "generate/app" [[modules.java-sdk.as-sdk.modules]] -path = ".dagger/modules/e2e/fixtures/lookup/app" +path = "lookup/app" [[modules.java-sdk.as-sdk.modules]] -path = ".dagger/modules/e2e/fixtures/deps/app" +path = "deps/app" [[modules.java-sdk.as-sdk.modules]] -path = ".dagger/modules/e2e/fixtures/skip/app" +path = "skip/app" [[modules.java-sdk.as-sdk.modules]] -path = ".dagger/modules/e2e/fixtures/managed-toml/app" +path = "managed-toml/app" diff --git a/.dagger/modules/e2e/main.dang b/.dagger/modules/e2e/main.dang index 93b6528..1a6e517 100644 --- a/.dagger/modules/e2e/main.dang +++ b/.dagger/modules/e2e/main.dang @@ -151,11 +151,10 @@ type E2e { modules that live outside it. """ modulesCwdCheck(ws: Workspace!): Void @check { - # A stable snapshot of the workspace, re-anchorable at any cwd. Snapshot the - # whole workspace (not just config files) so the re-anchored workspace keeps - # its dagger.toml — modules() reads the managed-module list from it via - # ws.sdk — alongside every module config (dagger.json and dagger-module.toml, - # for discovery) and lookup/app/nested (a config-less subdirectory). + # A stable snapshot of the workspace, re-anchorable at any cwd. It has to + # carry the fixture dagger.toml, which is where currentModule.asSDK reads the + # managed-module list from, and lookup/app/nested, the config-less + # subdirectory the find-up case is anchored in. let root = testWS(ws).directory("/") # Walk-down: from fixtures/generate only generate/app is in the cone; the diff --git a/dagger-module.toml b/dagger-module.toml index 3d53cf8..2bf7646 100644 --- a/dagger-module.toml +++ b/dagger-module.toml @@ -1,10 +1,5 @@ name = "java-sdk" -engineVersion = "v1.0.0-0" +engineVersion = "v1.0.0-beta.10" [runtime] source = "dang" - -[[dependencies]] - name = "polyfill" - source = "github.com/dagger/polyfill@main" - pin = "e90bbfc4843258a877a3a95b8db1571e7981e65f" diff --git a/dagger.json b/dagger.json index e38c38e..95b85ed 100644 --- a/dagger.json +++ b/dagger.json @@ -1,14 +1,7 @@ { "name": "java-sdk", - "engineVersion": "latest", + "engineVersion": "v1.0.0-beta.10", "sdk": { "source": "dang" - }, - "dependencies": [ - { - "name": "polyfill", - "source": "github.com/dagger/polyfill@main", - "pin": "e90bbfc4843258a877a3a95b8db1571e7981e65f" - } - ] + } } diff --git a/dagger.lock b/dagger.lock index 8949b1e..e7ebe70 100644 --- a/dagger.lock +++ b/dagger.lock @@ -1,4 +1,3 @@ -[["version","1"]] -["","git.head",["https://github.com/dagger/dang-sdk"],"c724eec4270870aae489daa9f3cb8cbacfb44680","float"] -["","git.head",["https://github.com/dagger/sdk-sdk"],"e1747f4b6221fa24da080701e027243e0cc5fa33","float"] -["","git.ref",["https://github.com/dagger/polyfill","main"],"ec3ea84a2351b4beb06ecece951f2e5ef66509ff","float"] \ No newline at end of file +[["version","2"]] +["","git.ref",["https://github.com/dagger/dang-sdk","HEAD"],{"ref":"refs/heads/main","sha":"c724eec4270870aae489daa9f3cb8cbacfb44680"}] +["","git.ref",["https://github.com/dagger/sdk-sdk","HEAD"],{"ref":"refs/heads/main","sha":"00bb06748bcf22d724ed467f2298d31f1fb49be0"}] \ No newline at end of file diff --git a/main.dang b/main.dang index 658c372..0ecc12e 100644 --- a/main.dang +++ b/main.dang @@ -19,13 +19,6 @@ type JavaSdk { """ skipGenerateFilename: String! = ".dagger-java-sdk-skip-generate" - """ - Config filenames that mark a Dagger module root: the CLI 1.0 - `dagger-module.toml` (workspace-managed modules) and the legacy `dagger.json`. - A managed module is discovered by whichever it uses. - """ - let moduleConfigFilenames: [String!]! = ["dagger-module.toml", "dagger.json"] - """ Commit the compiled Dagger Java SDK as a jar into each generated module so its runtime build compiles only the module's own code against the jar instead of @@ -56,14 +49,12 @@ type JavaSdk { rawPath.trimSuffix("/") } - let before = if (modPath == ".") { - ws.directory("/", include: ["**"]) - } else { - ws.directory("/", include: [modPath + "/**"]) - } - let selectedTemplate = if (template == "") { "default" } else { template } - before.withDirectory(modPath, renderedTemplate(name, selectedTemplate)).changes(before) + # `path` is workspace-root-relative, so anchor it: relative workspace paths + # resolve from ws.cwd and would be prefixed again when initModule is called + # directly from a subdirectory instead of being driven by the engine. + let target = if (modPath == ".") { "/" } else { "/" + modPath } + ws.withNewDirectory(target, renderedTemplate(name, selectedTemplate)).changes(ws) } """ @@ -98,62 +89,26 @@ type JavaSdk { Return every Java SDK module this workspace manages that is visible from the client's current location. - Discovery is anchored at the client's cwd (never the workspace root): the - nearest enclosing module plus every module at or below the cwd, intersected - with the SDK's engine-owned list of managed modules - (currentModule.asSDK.modules). So running from a subdirectory acts on the - project you're in — and the projects beneath it — not the whole workspace. - - Discovery is the polyfill's cwd-aware findConfigDirs (dagger/dagger#13688); - this maps its cwd-relative results to workspace-root-relative paths and keeps - the ones this SDK manages, whether they use dagger-module.toml or dagger.json. + The engine owns both the list and the cwd policy: currentModule.asSDK returns + the modules registered to this SDK that sit at or below the client's cwd, plus + the nearest enclosing one when the cwd is not itself a module. So running from + a subdirectory acts on the project you're in — and the projects beneath it — + not the whole workspace. Paths come back relative to the workspace root, the + currency Mod.rootPath uses. """ modules(ws: Workspace!): [Mod!]! { - let managed = ws.sdk(name: currentModule.name).modules.{{source}} - let cwd = normalizePath(ws.cwd) - polyfill - .workspace(ws) - .findConfigDirs(moduleConfigFilenames, exclude: ["**/target/**"]) - .map { dir => moduleRelPath(cwd, dir) } - .uniq - .filter { path => managed.filter { m => normalizePath(m.source) == path }.length > 0 } - .map { path => Mod( - rootPath: path, + currentModule + .asSDK(workspace: ws) + .modules + .{{path}} + .map { module => Mod( + rootPath: module.path, ws: ws, skipGenerateFilename: skipGenerateFilename, vendorSdkJar: vendorSdkJar, ) } } - """ - Normalize a workspace path: strip a leading "./" or "/" and any trailing "/", - and map the empty/root path to ".". - """ - let normalizePath(path: String!): String! { - let normalized = path.trimPrefix("./").trimPrefix("/").trimSuffix("/") - if (normalized == "") { "." } else { normalized } - } - - """ - Resolve a findConfigDirs result — a cwd-relative path, at or below the cwd - ("." , "sub/dir") or a strict ancestor (".." , "../..") — against the cwd into a - workspace-root-relative path, the format both managed module sources and - Mod.rootPath use. - """ - let moduleRelPath(cwd: String!, dir: String!): String! { - let base = if (cwd == "" or cwd == ".") { [] } else { cwd.split("/") } - let segs = dir.split("/").reduce(base) { acc, seg => - if (seg == "..") { - acc.dropLast(1) - } else if (seg == "." or seg == "") { - acc - } else { - acc + [seg] - } - } - if (segs.length == 0) { "." } else { segs.join("/") } - } - """ Generate every managed Java SDK module visible from the client's current location (runs at `dagger generate`). Discovery goes through modules(ws), so diff --git a/main.dang.tmpl b/main.dang.tmpl index a3bb400..228a5e2 100644 --- a/main.dang.tmpl +++ b/main.dang.tmpl @@ -19,13 +19,6 @@ type JavaSdk { """ skipGenerateFilename: String! = ".dagger-java-sdk-skip-generate" - """ - Config filenames that mark a Dagger module root: the CLI 1.0 - `dagger-module.toml` (workspace-managed modules) and the legacy `dagger.json`. - A managed module is discovered by whichever it uses. - """ - let moduleConfigFilenames: [String!]! = ["dagger-module.toml", "dagger.json"] - """ Commit the compiled Dagger Java SDK as a jar into each generated module so its runtime build compiles only the module's own code against the jar instead of @@ -56,14 +49,12 @@ type JavaSdk { rawPath.trimSuffix("/") } - let before = if (modPath == ".") { - ws.directory("/", include: ["**"]) - } else { - ws.directory("/", include: [modPath + "/**"]) - } - let selectedTemplate = if (template == "") { "default" } else { template } - before.withDirectory(modPath, renderedTemplate(name, selectedTemplate)).changes(before) + # `path` is workspace-root-relative, so anchor it: relative workspace paths + # resolve from ws.cwd and would be prefixed again when initModule is called + # directly from a subdirectory instead of being driven by the engine. + let target = if (modPath == ".") { "/" } else { "/" + modPath } + ws.withNewDirectory(target, renderedTemplate(name, selectedTemplate)).changes(ws) } """ @@ -98,62 +89,26 @@ type JavaSdk { Return every Java SDK module this workspace manages that is visible from the client's current location. - Discovery is anchored at the client's cwd (never the workspace root): the - nearest enclosing module plus every module at or below the cwd, intersected - with the SDK's engine-owned list of managed modules - (currentModule.asSDK.modules). So running from a subdirectory acts on the - project you're in — and the projects beneath it — not the whole workspace. - - Discovery is the polyfill's cwd-aware findConfigDirs (dagger/dagger#13688); - this maps its cwd-relative results to workspace-root-relative paths and keeps - the ones this SDK manages, whether they use dagger-module.toml or dagger.json. + The engine owns both the list and the cwd policy: currentModule.asSDK returns + the modules registered to this SDK that sit at or below the client's cwd, plus + the nearest enclosing one when the cwd is not itself a module. So running from + a subdirectory acts on the project you're in — and the projects beneath it — + not the whole workspace. Paths come back relative to the workspace root, the + currency Mod.rootPath uses. """ modules(ws: Workspace!): [Mod!]! { - let managed = ws.sdk(name: currentModule.name).modules.{{source}} - let cwd = normalizePath(ws.cwd) - polyfill - .workspace(ws) - .findConfigDirs(moduleConfigFilenames, exclude: ["**/target/**"]) - .map { dir => moduleRelPath(cwd, dir) } - .uniq - .filter { path => managed.filter { m => normalizePath(m.source) == path }.length > 0 } - .map { path => Mod( - rootPath: path, + currentModule + .asSDK(workspace: ws) + .modules + .{{path}} + .map { module => Mod( + rootPath: module.path, ws: ws, skipGenerateFilename: skipGenerateFilename, vendorSdkJar: vendorSdkJar, ) } } - """ - Normalize a workspace path: strip a leading "./" or "/" and any trailing "/", - and map the empty/root path to ".". - """ - let normalizePath(path: String!): String! { - let normalized = path.trimPrefix("./").trimPrefix("/").trimSuffix("/") - if (normalized == "") { "." } else { normalized } - } - - """ - Resolve a findConfigDirs result — a cwd-relative path, at or below the cwd - ("." , "sub/dir") or a strict ancestor (".." , "../..") — against the cwd into a - workspace-root-relative path, the format both managed module sources and - Mod.rootPath use. - """ - let moduleRelPath(cwd: String!, dir: String!): String! { - let base = if (cwd == "" or cwd == ".") { [] } else { cwd.split("/") } - let segs = dir.split("/").reduce(base) { acc, seg => - if (seg == "..") { - acc.dropLast(1) - } else if (seg == "." or seg == "") { - acc - } else { - acc + [seg] - } - } - if (segs.length == 0) { "." } else { segs.join("/") } - } - """ Generate every managed Java SDK module visible from the client's current location (runs at `dagger generate`). Discovery goes through modules(ws), so diff --git a/mod.dang b/mod.dang index 04a1966..c682800 100644 --- a/mod.dang +++ b/mod.dang @@ -67,7 +67,7 @@ type Mod { if (skipGenerate(ws)) { changeset } else { - generateModule(ws, rootPath) + generateModule(ws) } } @@ -199,83 +199,50 @@ type Mod { Stage the vendored SDK + generated entrypoint for one module. The returned changeset is relative to the client's cwd, the form the engine - applies a generator's result in. - """ - let generateModule(ws: Workspace!, modPathArg: String!): Changeset! { - # Native module-source resolution (Directory.asModuleSource, Query.moduleSource) - # fails from module code: SDK and user-defaults loading need the requester's host - # session. The polyfill resolves the source from a nested client that has one. - # Anchor rootPath at "/" so a non-root workspace cwd is not prefixed again. - - # Create an overlay containing generated (local) dependencies so the generation - # can work. - # - # Only stage when there is a local dependency to stage: the call resolves - # module sources through the workspace's owning client, which a synthetic - # workspace (Directory.asWorkspace, as the e2e checks build) does not have, - # and an all-remote dependency list yields an empty changeset anyway. - # Remote dependencies are assumed committed, matching the engine. - let localDeps = ws - .moduleSource(workspaceRef(rootPath)) - .dependencies - .{{kind}} - .filter { dep => dep.kind != ModuleSourceKind.GIT_SOURCE } - let wsWithDeps = if (localDeps.length == 0) { - ws - } else { - ws.withChanges( - polyfill.workspace(ws).moduleSource(workspaceRef(rootPath)).core.generateLocalDependencies(ws), - ) - } - - let modSource = polyfill.workspace(wsWithDeps).moduleSource(workspaceRef(modPathArg)) - let name = modSource.core.moduleName - let introspectionJSON = modSource.core.introspectionSchemaJSON + applies a generator's result in: past v1.0.0-beta.10 every Workspace.changes + result is measured from the workspace cwd, so staging onto the workspace value + and diffing it against the baseline the caller already holds is all this needs. + """ + let generateModule(ws: Workspace!): Changeset! { + # A module that depends on another by local path cannot resolve its own + # schema until that dependency has been generated, so overlay the generated + # closure onto the workspace first. The engine skips remote dependencies — + # assumed committed, as it does itself — and returns an empty changeset when + # there is nothing to stage. + let wsWithDeps = ws.withChanges( + ws.moduleSource(workspaceRef(rootPath)).generateLocalDependencies(ws), + ) + + let modSource = wsWithDeps.moduleSource(workspaceRef(rootPath)) + let name = modSource.moduleName + let introspectionJSON = modSource.introspectionSchemaJSON let vendored = vendoredSdk(introspectionJSON, name) # the module as committed, with the whole committed sdk/ dropped (source and # any previously vendored jar) and the freshly vendored SDK sources overlaid, # plus any stale generated entrypoint dropped so the processor regenerates it - let baseDir = moduleDir(wsWithDeps, modPathArg) + let baseDir = moduleDir(wsWithDeps, rootPath) .withoutDirectory("sdk") .withDirectory("sdk", vendored) .withoutDirectory("src/generated") let entrypoint = generatedEntrypoint(baseDir, name) - let before = if (modPathArg == ".") { - wsWithDeps.directory("/", include: ["**"]) - } else { - wsWithDeps.directory("/", include: [modPathArg + "/**"]) - } - let staged = before - .withDirectory(joinPath(modPathArg, "sdk"), vendored) - .withDirectory(joinPath(modPathArg, "src/generated/java"), entrypoint) + let staged = wsWithDeps + .withNewDirectory(workspaceRef(joinPath(rootPath, "sdk")), vendored) + .withNewDirectory(workspaceRef(joinPath(rootPath, "src/generated/java")), entrypoint) let after = if (vendorSdkJar) { - staged.withDirectory(joinPath(modPathArg, "sdk/repo"), vendoredSdkJar( - introspectionJSON, - name, - )) + staged.withNewDirectory( + workspaceRef(joinPath(rootPath, "sdk/repo")), + vendoredSdkJar(introspectionJSON, name), + ) } else { staged } - # The staging above is workspace-rooted, but the engine applies a returned - # changeset relative to the caller's cwd — so without re-rooting here every - # path ends up nested under the cwd a second time and the module is never - # actually generated. Selecting a subdirectory is metadata only, so this - # costs nothing. - let cwd = ws.cwd.trimPrefix("/").trimSuffix("/") - if (cwd == "") { - after.changes(before) - } else if (modPathArg == cwd or modPathArg.trimPrefix(cwd + "/") != modPathArg) { - after.directory(cwd).changes(before.directory(cwd)) - } else { - # A cwd-relative changeset cannot express paths outside the cwd, so a - # module discovered above it (find-up) would be silently dropped and left - # ungenerated. Fail loudly until there is a policy for such modules. - raise "module " + modPathArg + " lies outside the current directory " + cwd + ": its generated changes cannot be expressed relative to the cwd" - } + # Baseline on the staged-dependency workspace, not the one handed in: the + # dependencies' generated code belongs to their own SDKs, not this changeset. + after.changes(wsWithDeps) } """ @@ -297,8 +264,8 @@ type Mod { } """ - A module path as a workspace-root-absolute ref, the form Workspace.moduleSource - resolves from the workspace root rather than from the client's cwd. + A module path as a workspace-root-absolute path, the form Workspace resolves + from the workspace root rather than from the client's cwd. """ let workspaceRef(modPathArg: String!): String! { if (modPathArg == ".") { "/" } else { "/" + modPathArg } diff --git a/prebuilt/m2/io/dagger/dagger-codegen-maven-plugin/0.21.4/dagger-codegen-maven-plugin-0.21.4.jar b/prebuilt/m2/io/dagger/dagger-codegen-maven-plugin/0.21.4/dagger-codegen-maven-plugin-0.21.4.jar index e4c09a0..bb7442b 100644 Binary files a/prebuilt/m2/io/dagger/dagger-codegen-maven-plugin/0.21.4/dagger-codegen-maven-plugin-0.21.4.jar and b/prebuilt/m2/io/dagger/dagger-codegen-maven-plugin/0.21.4/dagger-codegen-maven-plugin-0.21.4.jar differ diff --git a/sdk/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/Helpers.java b/sdk/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/Helpers.java index 91bd827..bab17a5 100644 --- a/sdk/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/Helpers.java +++ b/sdk/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/Helpers.java @@ -189,11 +189,17 @@ static MethodSpec withSetter(InputObject var, TypeName type, TypeName returnType return builder.build(); } - /** Fix using '$' char in javadoc */ + /** + * Escape characters that have a special meaning in javadoc. + * + *

'$' is escaped for JavaPoet's format strings and '&' as an HTML entity. The comment + * terminator is escaped so that a glob example such as {@code **/target/**} cannot end the + * generated javadoc early. + */ static String escapeJavadoc(String str) { if (str == null) { return ""; } - return str.replace("$", "$$").replace("&", "&"); + return str.replace("$", "$$").replace("&", "&").replace("*/", "*/"); } } diff --git a/sdk/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/InterfaceVisitor.java b/sdk/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/InterfaceVisitor.java index db1ef07..721c980 100644 --- a/sdk/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/InterfaceVisitor.java +++ b/sdk/dagger-codegen-maven-plugin/src/main/java/io/dagger/codegen/introspection/InterfaceVisitor.java @@ -36,6 +36,14 @@ TypeSpec generateType(Type type) { .addJavadoc(Helpers.escapeJavadoc(type.getDescription())) .addModifiers(Modifier.PUBLIC); + // An interface exposing an id field is IDAble like any object, so a value + // typed by it (a Node argument, say) serializes by ID through the same + // Arguments.Builder overloads. + if (type.providesId()) { + interfaceBuilder.addSuperinterface( + ParameterizedTypeName.get(ClassName.bestGuess("IDAble"), ClassName.bestGuess("ID"))); + } + if (type.getFields() != null) { for (Field field : type.getFields()) { MethodSpec.Builder methodBuilder =