Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .dagger/modules/e2e/dagger.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "e2e",
"engineVersion": "v0.21.4",
"engineVersion": "v1.0.0-0",
"sdk": {
"source": "dang"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
engineVersion = "v1.0.0-0"

[runtime]
source = "python"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "mixed-discovery-app",
"engineVersion": "v1.0.0-0",
"sdk": {
"source": "python"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[project]
name = "mixed-discovery-app"
version = "0.1.0"
requires-python = ">=3.14"
dependencies = ["dagger-io"]

[build-system]
requires = ["uv_build>=0.8.4,<0.9.0"]
build-backend = "uv_build"

[tool.uv.sources]
dagger-io = { path = "sdk", editable = true }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from dagger import object_type


@object_type
class MixedDiscoveryApp:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

4 changes: 4 additions & 0 deletions .dagger/modules/e2e/fixtures/toml/app/dagger-module.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
engineVersion = "v1.0.0-0"

[runtime]
source = "python"
1 change: 1 addition & 0 deletions .dagger/modules/e2e/fixtures/toml/app/nested/.keep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

54 changes: 40 additions & 14 deletions .dagger/modules/e2e/main.dang
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ type E2e {
let depsModulePath: String! = fixtureRoot + "/deps/app"
let configModulePath: String! = fixtureRoot + "/config/app"
let configuredModulePath: String! = fixtureRoot + "/config/configured"
let tomlModulePath: String! = fixtureRoot + "/toml/app"
let mixedDiscoveryModulePath: String! = fixtureRoot + "/mixed-discovery/ancestor/work/app"
let mixedDiscoveryNestedPath: String! = mixedDiscoveryModulePath + "/nested/deeper"

let generatedMarkerPath: String! = "sdk/src/dagger/client/gen.py"
let generatedMarkerContents: String! = "Code generated by dagger."
Expand Down Expand Up @@ -73,12 +76,47 @@ type E2e {
let nested = pythonSdk.mod(ws, path: lookupNestedPath)
let exact = pythonSdk.mod(ws, path: lookupModulePath, findUp: false)

assert(nested.path == lookupModulePath, "find-up lookup returned the wrong module path")
assert(exact.path == lookupModulePath, "exact lookup returned the wrong module path")
assert(nested.rootPath == lookupModulePath, "find-up lookup returned the wrong module path")
assert(exact.rootPath == lookupModulePath, "exact lookup returned the wrong module path")

null
}

"""
Lookup must choose the nearest module config even when its filename differs
from an ancestor config, and modern TOML modules must be supported.
"""
pub mixedConfigLookupCheck(ws: Workspace!): Void @check {
let mod = pythonSdk.mod(ws, path: tomlModulePath + "/nested")
assert(mod.rootPath == tomlModulePath, "lookup chose an outer dagger.json instead of the nearer dagger-module.toml")
null
}

"""
Managed-module discovery is cwd-aware and supports both config formats.
"""
pub moduleDiscoveryCheck(ws: Workspace!): Void @check {
let roots = pythonSdk.modules(ws).{{rootPath}}.map { mod => mod.rootPath }
assert(contains(roots, generateModulePath), "root discovery missed a legacy Python module")
assert(contains(roots, tomlModulePath), "root discovery missed a TOML Python module")
assert(contains(roots, nonPythonModulePath) == false, "discovery included a non-Python module")

let nestedWs = ws.directory("/").asWorkspace(cwd: lookupNestedPath)
let scoped = pythonSdk.modules(nestedWs).{{rootPath, path}}
let scopedRoots = scoped.map { mod => mod.rootPath }
let scopedPaths = scoped.map { mod => mod.path }
assert(scoped.length == 1, "nested cwd should see only its nearest managed module")
assert(contains(scopedRoots, lookupModulePath), "nested cwd resolved the wrong managed module")
assert(contains(scopedPaths, ".."), "module path should be relative to the nested cwd")

let mixedWs = ws.directory("/").asWorkspace(cwd: mixedDiscoveryNestedPath)
let mixed = pythonSdk.modules(mixedWs).{{rootPath, path}}
assert(mixed.length == 1, "mixed-config cwd should see only its nearest managed module")
assert(contains(mixed.map { mod => mod.rootPath }, mixedDiscoveryModulePath), "mixed-config discovery chose the farther dagger-module.toml")
assert(contains(mixed.map { mod => mod.path }, "../.."), "mixed-config module path should be relative to the nested cwd")
null
}

"""
initModule should seed SDK-owned template files without running codegen and
without writing engine-owned files (dagger.json / dagger-module.toml).
Expand Down Expand Up @@ -153,18 +191,6 @@ type E2e {
null
}

"""
initClient should not materialize files itself. The engine records the client
and owns the generated context changeset.
"""
pub initClientCheck(ws: Workspace!): Void @check {
let changes = pythonSdk.initClient(ws, path: outputRoot + "/client", module: generateModulePath, dev: true)

assert(changes.isEmpty, "initClient should return an empty SDK changeset")

null
}

"""
config.get should reflect pyproject.toml and report unset values as null
rather than guessing, and config.set should edit only pyproject.toml.
Expand Down
4 changes: 2 additions & 2 deletions dagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"dependencies": [
{
"name": "polyfill",
"source": "github.com/dagger/sdk-sdk/polyfill@main",
"pin": "a16466390cacd68bd72f7ae9910d990966fb2226"
"source": "github.com/dagger/polyfill@main",
"pin": "e90bbfc4843258a877a3a95b8db1571e7981e65f"
}
]
}
6 changes: 6 additions & 0 deletions dagger.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ path = ".dagger/modules/e2e/fixtures/deps/app"

[[modules.python-sdk.as-sdk.modules]]
path = ".dagger/modules/e2e/fixtures/skip/app"

[[modules.python-sdk.as-sdk.modules]]
path = ".dagger/modules/e2e/fixtures/toml/app"

[[modules.python-sdk.as-sdk.modules]]
path = ".dagger/modules/e2e/fixtures/mixed-discovery/ancestor/work/app"
20 changes: 20 additions & 0 deletions docs/cwd-aware-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# CWD-aware module discovery

The Python SDK delegates config discovery to `github.com/dagger/polyfill`, then
intersects the discovered directories with `currentModule.asSDK.modules`. The
engine's managed-module list remains authoritative while the caller's current
directory determines scope.

Discovery returns modules at or below the cwd and, when the cwd has no module
config, its nearest enclosing module. Both `dagger-module.toml` and legacy
`dagger.json` are considered together, so the nearest config wins regardless of
filename. Virtual environments and installed packages are excluded.

```console
dagger check -l
dagger call e-2-e mixed-config-lookup-check
dagger call e-2-e module-discovery-check
```

The fixtures cover mixed nested config formats, modern and legacy configs,
non-Python exclusion, root discovery, and discovery from inside a module.
29 changes: 24 additions & 5 deletions mod.dang
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ A Dagger module that uses the Python SDK.
"""
type Mod {
"""
Workspace-relative path of this module root.
Workspace-root-relative path of this module root.
"""
pub path: String!
pub rootPath: String!

"""
The workspace this module belongs to.
Expand All @@ -17,18 +17,37 @@ type Mod {
"""
let skipGenerateFilename: String!

"""
Module root relative to the client's cwd.
"""
pub path: String! {
let cwd = ws.cwd.trimPrefix("/").trimSuffix("/")
if (rootPath == cwd) {
"."
} else if (cwd == "") {
rootPath
} else if (rootPath.trimPrefix(cwd + "/") != rootPath) {
rootPath.trimPrefix(cwd + "/")
} else if (rootPath == "." or cwd.trimPrefix(rootPath + "/") != cwd) {
let depth = if (rootPath == ".") { 0 } else { rootPath.split("/").length }
cwd.split("/").dropFirst(depth).map { segment => ".." }.join("/")
} else {
rootPath
}
}

"""
Whether this module or an ancestor contains the configured generate skip marker.
"""
pub skipGenerate: Boolean! {
ws.findUp(name: skipGenerateFilename, from: path) != null
ws.findUp(name: skipGenerateFilename, from: rootPath) != null
}

"""
Manage this module's Python build configuration (pyproject.toml).
"""
pub config: ModConfig! {
ModConfig(path: path, ws: ws)
ModConfig(path: rootPath, ws: ws)
}

"""
Expand All @@ -39,7 +58,7 @@ type Mod {
if (skipGenerate) {
polyfill.workspace(ws).fork.changes
} else {
polyfill.workspace(ws).moduleSource("/" + path).generate.changes
polyfill.workspace(ws).moduleSource("/" + rootPath).generate.changes
}
}
}
Loading