Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,25 @@ A second workspace exercises local path dependencies — `chain-a` depends on
depend on is generated, so an SDK that widens generation to the whole
workspace fails here.

A third workspace exercises the monorepo layout — `dagger.toml` in a `common/`
subdirectory of the git root, with everything driven from that subdirectory
([dagger#13889](https://github.com/dagger/dagger/issues/13889)):

- `dagger sdk install` registers the SDK in `common/dagger.toml`.
- `dagger module init` scaffolds a module under `common/.dagger/modules/`,
writes nothing outside it, and the module loads afterwards. The workspace
root is the git root, so the default module path the engine hands the SDK is
not under the caller's cwd: an SDK that reads it cwd-relative refuses it, and
one that reads it root-relative writes the module a directory above where
`common/dagger.toml` registers it.
- `dagger module init --path` keeps the whole module at that path. This is the
quiet half: an explicit path is workspace-root-relative, so a path naming the
config directory is also under the caller's cwd read the other way, and
nothing refuses it — the engine's module config and the SDK's files just land
in two different directories. An explicit path deliberately skips the
`[modules.<name>]` entry that the default layout writes, here as at the
workspace root, so there is nothing to load by name.

Function-level contract checks additionally call the SDK's `initModule`
directly (always with an explicit `--path`, as the engine does) and inspect
the returned changesets: `initModule` must seed at least one file, must not
Expand All @@ -66,6 +85,7 @@ behavior they cover and reported as `<group>:<check>`:
| `generation` | `checks-generate.dang` | `dagger generate` and the SDK's `@generate` hook |
| `module` | `checks-module.dang` | the scaffolded module loading and serving its API |
| `chain` | `checks-chain.dang` | local path dependencies and cwd-anchored generation |
| `monorepo` | `checks-monorepo.dang` | a workspace config in a subdirectory of the git root |
| `contract` | `checks-contract.dang` | function-level `initModule` changeset behavior |
| `template` | `template.dang` | sdk-sdk's own scaffolding template |

Expand Down
104 changes: 104 additions & 0 deletions checks-monorepo.dang
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""
Checks for workspaces whose dagger.toml lives in a subdirectory of the git
root, the layout of a monorepo where several project workspaces share one
repository.

The workspace root is the git root, so the caller's cwd sits below the root
instead of at it. Module paths cross the engine <-> SDK boundary
workspace-root-relative, so an SDK that reads them relative to the caller's cwd
either refuses the path outright or lands its files in a different directory
than the module config the engine writes.

See https://github.com/dagger/dagger/issues/13889.
"""
type MonorepoChecks {
let harness: SdkHarness!

"""
`dagger sdk install` should register the SDK in a subdirectory config.
"""
pub installsFromSubdirConfig(ws: Workspace!): Void @check {
let testTarget = harness.sdkTarget(ws)
let run = testTarget.installInSubdir
run.assertSuccess

if (run.workspaceFile(testTarget.subdirWorkspaceConfigPath).contains(testTarget.sdkInstallName) == false) {
raise "sdk install should register the SDK in " + testTarget.subdirWorkspaceConfigPath
}
}

"""
`dagger module init` should scaffold a module from a subdirectory config.

This is the reported failure: the engine hands the SDK the default module
path workspace-root-relative, and a cwd-scoped SDK rejects it as being
outside its changeset root.
"""
pub scaffoldsModuleFromSubdirConfig(ws: Workspace!): Void @check {
let testTarget = harness.sdkTarget(ws)
let run = testTarget.initSubdirModule
run.assertSuccess

if (run.workspaceHasFile(testTarget.subdirModuleConfigPath) == false) {
raise "module init from " + testTarget.subdirWorkspaceDir + " should write " + testTarget.subdirModuleConfigPath
}
}

"""
`dagger module init` should keep the whole module under the config directory.

The engine's config changeset is applied workspace-root-relative while the
SDK's file changeset is applied relative to the caller's cwd, so the two
halves of a new module can land in different directories.
"""
pub keepsModuleUnderSubdirConfig(ws: Workspace!): Void @check {
let testTarget = harness.sdkTarget(ws)
let run = testTarget.initSubdirModule
run.assertSuccess

let leaked = run.workspaceChangesOutsideModule(testTarget.subdirModulePath, testTarget.subdirWorkspaceDir)
if (leaked != "") {
raise "module init from " + testTarget.subdirWorkspaceDir + " should write nothing outside " + testTarget.subdirModulePath + "\nchanged:\n" + leaked
}
}

"""
A module scaffolded from a subdirectory config should load and serve its API.

A module split across two directories still registers in dagger.toml, so
loading it is what proves both halves ended up together.
"""
pub subdirModuleLoads(ws: Workspace!): Void @check {
let testTarget = harness.sdkTarget(ws)
testTarget.generateSubdirWorkspace.assertSuccess
testTarget.runInSubdir(["api", "functions", testTarget.subdirModuleName]).assertSuccess
}

"""
`dagger module init --path` from a subdirectory config should keep the whole
module at that path.

This is the half of the layout that fails quietly. An explicit path is
workspace-root-relative, so a path naming the config directory is also under
the caller's cwd read the other way: nothing refuses it, and the engine's
module config and the SDK's files land in two different directories.

An explicit path deliberately skips the `[modules.<name>]` entry that the
default layout writes, here as at the workspace root, so the module is
authored rather than installed and there is nothing to load by name.
"""
pub explicitPathKeepsModuleWhole(ws: Workspace!): Void @check {
let testTarget = harness.sdkTarget(ws)
let run = testTarget.initSubdirModuleAtPath
run.assertSuccess

if (run.workspaceHasFile(testTarget.subdirPathModuleConfigPath) == false) {
raise "module init --path " + testTarget.subdirPathArg + " should write " + testTarget.subdirPathModuleConfigPath
}

let leaked = run.workspaceChangesOutsideModule(testTarget.subdirPathArg, testTarget.subdirWorkspaceDir)
if (leaked != "") {
raise "module init --path " + testTarget.subdirPathArg + " should write nothing outside it\nchanged:\n" + leaked
}
}
}
16 changes: 16 additions & 0 deletions sdk-run.dang
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ type SdkRun {
.stdout
}

"""
Return the git status of everything a `module init` run wrote outside the new
module's own directory, ignoring the workspace config and lock file it is
meant to update. Empty when the module's files landed with its config.
"""
pub workspaceChangesOutsideModule(modulePath: String!, configDir: String!): String! {
container
.withExec([
"git", "-C", "/work", "status", "--porcelain", "--untracked-files=all", "--", ".",
":(exclude)" + modulePath,
":(exclude,glob)" + configDir + "/*.toml",
":(exclude,glob)" + configDir + "/*.lock",
])
.stdout
}

"""
The first failed pipeline command, as recorded by the run wrapper.
"""
Expand Down
9 changes: 8 additions & 1 deletion sdk-sdk.dang
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ and `@generate` hooks directly and inspect the returned changesets.

Checks are grouped by the behavior they cover and reported as
`<group>:<check>`: `install`, `init`, `generation`, `module`, `chain`,
`contract`, and `template`.
`monorepo`, `contract`, and `template`.

Under CLI 1.0 the engine owns module bookkeeping (dagger-module.toml, workspace
config, dependency and engine-version edits). An SDK module implements only
Expand Down Expand Up @@ -112,6 +112,13 @@ type SdkSdk {
ChainChecks(harness: harness)
}

"""
Checks for workspaces whose config lives in a subdirectory of the git root.
"""
pub monorepo: MonorepoChecks! {
MonorepoChecks(harness: harness)
}

"""
Function-level checks of the SDK's `initModule` contract.
"""
Expand Down
166 changes: 166 additions & 0 deletions sdk-target.dang
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ type SdkTarget {
"""
pub chainLeafName: String! = "chain-c"

"""
Directory holding the workspace config in the monorepo layout, a
subdirectory of the git root rather than the root itself.
"""
pub subdirWorkspaceDir: String! = "common"

"""
Name of the module scaffolded from the subdirectory workspace config.
"""
pub subdirModuleName: String! = "subdir-mod"

"""
Name of the module scaffolded from the subdirectory workspace config with an
explicit `--path`.
"""
pub subdirPathModuleName: String! = "subdir-path-mod"

"""
Return a copy of this target with a different command timeout.
"""
Expand Down Expand Up @@ -95,6 +112,45 @@ type SdkTarget {
".dagger/modules/" + chainLeafName
}

"""
Workspace-root-relative path of the subdirectory workspace config.
"""
pub subdirWorkspaceConfigPath: String! {
subdirWorkspaceDir + "/dagger.toml"
}

"""
Workspace-root-relative path of the module scaffolded from the subdirectory
workspace config.
"""
pub subdirModulePath: String! {
subdirWorkspaceDir + "/.dagger/modules/" + subdirModuleName
}

"""
Workspace-root-relative path of the subdirectory module's config.
"""
pub subdirModuleConfigPath: String! {
subdirModulePath + "/dagger-module.toml"
}

"""
Path passed to `dagger module init --path` from the subdirectory workspace,
the one from the issue report: a path that is under the caller's cwd only
when read workspace-root-relative.
"""
pub subdirPathArg: String! {
subdirWorkspaceDir + "/" + subdirPathModuleName
}

"""
Workspace-root-relative path of the config for the module scaffolded with an
explicit `--path`.
"""
pub subdirPathModuleConfigPath: String! {
subdirPathArg + "/dagger-module.toml"
}

"""
Run `dagger sdk install` for the SDK under test and capture the result.
"""
Expand Down Expand Up @@ -141,6 +197,38 @@ type SdkTarget {
SdkRun(container: chainGeneratedFromLeafState, args: generateArgs)
}

"""
Run `dagger sdk install` from a workspace config that lives in a
subdirectory of the git root, and capture the result.
"""
pub installInSubdir: SdkRun! {
SdkRun(container: subdirInstalledState, args: installArgs)
}

"""
Run `dagger module init` from a subdirectory workspace config and capture the
result.
"""
pub initSubdirModule: SdkRun! {
SdkRun(container: subdirInitializedState, args: subdirInitArgs)
}

"""
Run `dagger module init --path` from a subdirectory workspace config and
capture the result.
"""
pub initSubdirModuleAtPath: SdkRun! {
SdkRun(container: subdirPathInitializedState, args: subdirPathInitArgs)
}

"""
Run `dagger generate` from a subdirectory workspace config and capture the
result.
"""
pub generateSubdirWorkspace: SdkRun! {
SdkRun(container: subdirGeneratedState, args: generateArgs)
}

"""
Run a dagger command from the workspace root after the SDK is installed.
"""
Expand All @@ -164,6 +252,14 @@ type SdkTarget {
runOn(chainGeneratedFromLeafState, "/work", args)
}

"""
Run a dagger command from a subdirectory workspace config after its module is
scaffolded and generated.
"""
pub runInSubdir(args: [String!]!): SdkRun! {
runOn(subdirGeneratedState, subdirWorkdir, args)
}

"""
Run a dagger command from the workspace root after a module is scaffolded
and generated.
Expand Down Expand Up @@ -289,6 +385,68 @@ type SdkTarget {
pipe(chainInitializedState, "/work/" + chainLeafPath, generateArgs)
}

"""
Scratch workspace whose dagger.toml sits in a subdirectory of the git root,
the layout of a monorepo holding several project workspaces.

The SDK is vendored under that subdirectory, so the config location is the
only thing that differs from the root workspace. Everything is committed
before the first command, so later git status calls see only what the
commands wrote.
"""
let subdirWorkspace: Container! {
runner
.withDirectory(subdirWorkdir + "/" + vendorRoot, workspaceView, exclude: [".git", "**/.git"])
.withNewFile("/work/" + subdirWorkspaceConfigPath, "# Dagger workspace configuration\n")
.withWorkdir("/work")
.withExec(["git", "init", "-q"])
.withExec(["git", "add", "-A"])
.withExec([
"git",
"-c", "user.email=sdk-sdk@dagger.io",
"-c", "user.name=sdk-sdk",
"commit", "-q", "--allow-empty", "-m", "sdk-sdk subdirectory workspace",
])
}

"""
Subdirectory workspace state after `dagger sdk install`.
"""
let subdirInstalledState: Container! {
pipe(subdirWorkspace, subdirWorkdir, installArgs)
}

"""
Subdirectory workspace state after `dagger module init`.
"""
let subdirInitializedState: Container! {
pipe(subdirInstalledState, subdirWorkdir, subdirInitArgs)
}

"""
Subdirectory workspace state after `dagger generate`.
"""
let subdirGeneratedState: Container! {
pipe(subdirInitializedState, subdirWorkdir, generateArgs)
}

"""
Subdirectory workspace state after `dagger module init --path`.

It branches off the installed state rather than the initialized one, so the
default-path init failing does not mask what an explicit path does.
"""
let subdirPathInitializedState: Container! {
pipe(subdirInstalledState, subdirWorkdir, subdirPathInitArgs)
}

"""
Container path of the subdirectory holding the workspace config.
"""
let subdirWorkdir: String! {
"/work/" + subdirWorkspaceDir
}

"""
Workspace-relative path of the vendored SDK module source.
"""
Expand All @@ -308,6 +466,14 @@ type SdkTarget {
["generate"]
}

let subdirInitArgs: [String!]! {
["module", "init", sdkInstallName, subdirModuleName]
}

let subdirPathInitArgs: [String!]! {
["module", "init", sdkInstallName, subdirPathModuleName, "--path", subdirPathArg]
}

"""
Run a dagger command on a workspace state and capture the result.
"""
Expand Down