-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathmod.dang
More file actions
202 lines (182 loc) · 6.39 KB
/
Copy pathmod.dang
File metadata and controls
202 lines (182 loc) · 6.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
"""
A Dagger module that uses the Python SDK.
"""
type Mod {
"""
Workspace-root-relative path of this module root.
"""
pub rootPath: String!
"""
The workspace this module belongs to.
"""
let ws: Workspace!
"""
Marker filename that skips generate when found at or above this module root.
"""
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: rootPath) != null
}
"""
Manage this module's Python build configuration (pyproject.toml).
"""
pub config: ModConfig! {
ModConfig(path: rootPath, ws: ws)
}
"""
Generate this module.
If the generate skip marker is present, the changeset is empty.
"""
pub generate: Changeset! {
if (skipGenerate) {
ws.changes(ws)
} else {
# 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 = if (isModern) {
# A dagger-module.toml module is generated here, by this SDK's own code
# generator. Its runtime generates nothing, so asking the engine for a
# generated context would come back empty.
vendoredDir(stagedWs)
} else {
# A pre-1.0 module keeps being generated by the runtime its dagger.json
# names, which is the engine's builtin Python SDK.
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)
}
}
"""
Whether this module uses the 1.0 dagger-module.toml config.
"""
let isModern: Boolean! {
let configPath = if (rootPath == ".") { "dagger-module.toml" } else { rootPath + "/dagger-module.toml" }
ws.directory("/", include: [configPath]).exists(configPath)
}
"""
This SDK's client library, with bindings generated against the module's own
schema, laid out the way it is vendored into a module.
A module-rooted directory holding only `sdk/`, so the caller can merge it onto
the module without touching anything else the module owns.
"""
let vendoredDir(stagedWs: Workspace!): Directory! {
let schemaJSON = stagedWs.moduleSource("/" + rootPath).introspectionSchemaJSON
directory.withDirectory(
vendorDirName,
library.withFile(generatedBindingsPath, bindings(schemaJSON)),
)
}
"""
Client bindings generated from a module's schema by this SDK's code generator.
"""
let bindings(schemaJSON: File!): File! {
codegenBase
.withMountedFile(schemaPath, schemaJSON)
.withExec([
"uv", "run", "--isolated", "--frozen", "--package", "codegen",
"python", "-m", "codegen", "generate", "-i", schemaPath, "-o", "/gen.py",
])
.file("/gen.py")
}
"""
Container with this SDK's client library and code generator mounted.
"""
let codegenBase: Container! {
container
.from(codegenImage)
.withoutEntrypoint
.withMountedCache("/root/.cache/uv", cacheVolume("python-sdk-uv"))
.withEnvVariable("UV_LINK_MODE", "copy")
.withDirectory("/sdk", codegenSource)
.withWorkdir("/sdk")
}
"""
What the code generator needs to run: the library plus the generator itself,
and the lock that pins the generator's own dependencies.
"""
let codegenSource: Directory! {
currentModule.source.directory("sdk").filter(include: [
"pyproject.toml",
"uv.lock",
"src/**/*.py",
"src/**/*.typed",
"codegen/pyproject.toml",
"codegen/**/*.py",
])
}
"""
What a module actually needs vendored: the importable client library, its
license, and a project file describing just that.
The code generator runs in this SDK, never in a module, and the library's own
lock pins the generator's development environment rather than the module's.
"""
let library: Directory! {
currentModule.source
.directory("sdk")
.filter(include: [
"LICENSE",
"README.md",
"src/**/*.py",
"src/**/*.typed",
# The library imports this under suppress(ModuleNotFoundError) and says
# it "doesn't make sense in modules": it provisions an engine for a
# standalone script, which a module already has.
"!src/dagger/provisioning/**",
])
.withFile("pyproject.toml", libraryPyproject)
}
"""
The library's project file with its development sections removed.
As published, it declares the code generator as a uv workspace member and a
dev dependency. Vendoring that verbatim without the generator makes `uv`
refuse to install the library at all, so the sections that only describe
developing this SDK are dropped.
"""
let libraryPyproject: File! {
codegenBase
.withFile(stripScriptPath, currentModule.source.file("helpers/vendor-pyproject/strip_dev_sections.py"))
.withExec(["python", stripScriptPath, "pyproject.toml", "/library-pyproject.toml"])
.file("/library-pyproject.toml")
}
let stripScriptPath: String! = "/strip-dev-sections.py"
let vendorDirName: String! = "sdk"
let generatedBindingsPath: String! = "src/dagger/client/gen.py"
let schemaPath: String! = "/schema.json"
let codegenImage: String! = "ghcr.io/astral-sh/uv:python3.14-alpine"
}