-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathmod.dang
More file actions
190 lines (169 loc) · 5.47 KB
/
Copy pathmod.dang
File metadata and controls
190 lines (169 loc) · 5.47 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
"""
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 {
generated.changes(ws)
}
}
"""
The workspace with this module's generated files merged in. The generate skip
marker is not applied here; `generate` applies it.
"""
pub generated: Workspace! {
let files = if (isModern) {
# Generated here: the runtime generates nothing, so the engine's
# generated context would be empty.
vendoredDir
} else {
# A pre-1.0 module is still generated by the engine's builtin Python SDK.
ws
.moduleSource("/" + rootPath)
.generatedContextDirectory
.directory(rootPath)
}
# Merge, don't replace: the generated context holds only generated files.
ws.withDirectory("/" + rootPath, files)
}
"""
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)
}
"""
The client library with the module's bindings, in a module-rooted directory
holding only `sdk/` so it merges onto the module without touching anything else.
"""
let vendoredDir: Directory! {
let schemaJSON = ws.moduleSource("/" + rootPath).introspectionSchemaJSON
directory.withDirectory(
vendorDirName,
library.withFile(generatedBindingsPath, bindings(schemaJSON)),
)
}
"""
Bindings generated from a module's schema, straight from the synced
environment: `uv run --isolated` built a throwaway one per module.
"""
let bindings(schemaJSON: File!): File! {
codegenEnv
.withMountedFile(schemaPath, schemaJSON)
.withExec([
codegenPython, "-m", "codegen", "generate", "-i", schemaPath, "-o", "/gen.py",
])
.file("/gen.py")
}
"""
The code generator's environment, synced once per SDK version and shared by
every module that generates.
"""
let codegenEnv: Container! {
codegenBase.withExec(["uv", "sync", "--frozen", "--no-dev", "--package", "codegen"])
}
let codegenBase: Container! {
container
.from(codegenImage)
.withoutEntrypoint
.withMountedCache("/root/.cache/uv", cacheVolume("python-sdk-uv"))
.withEnvVariable("UV_LINK_MODE", "copy")
.withEnvVariable("UV_COMPILE_BYTECODE", "1")
.withDirectory("/sdk", codegenSource)
.withWorkdir("/sdk")
}
let codegenPython: String! = "/sdk/.venv/bin/python"
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 vendors: the importable library and its license. The generator
runs here, never in a module, and the lock pins the generator's environment.
"""
let library: Directory! {
currentModule.source
.directory("sdk")
.filter(include: [
"LICENSE",
"README.md",
"src/**/*.py",
"src/**/*.typed",
# An optional import that provisions an engine; a module already has one.
"!src/dagger/provisioning/**",
])
.withFile("pyproject.toml", libraryPyproject)
}
"""
pyproject.toml without its development sections: vendored verbatim, it names
the absent codegen workspace member and uv refuses to install the library.
"""
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"
# musl runs the generator ~0.3s slower than glibc, but the glibc image is
# 25 MiB larger to pull, which costs more on the first generate.
let codegenImage: String! = "ghcr.io/astral-sh/uv:python3.14-alpine"
}