Repository navigation
Expand file tree
/
Copy pathmod.dang
More file actions
654 lines (600 loc) · 27.8 KB
/
Copy pathmod.dang
File metadata and controls
654 lines (600 loc) · 27.8 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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
"""
A Java scope this SDK generates: a Dagger module, or a plain Maven project
that calls modules through their clients.
"""
type Mod {
"""
Workspace-root-relative path of this module root — the module's stable
identity, independent of where the client is invoked from.
"""
rootPath: String!
"""
Workspace containing this module.
"""
let ws: Workspace!
"""
Whether the scope is a module. A plain Maven project gets the vendored SDK,
core and its client packages, compiled by the SDK profile of its pom.xml,
and nothing the module build or the entrypoint reads.
"""
let isModule: Boolean!
"""
Whether to commit the compiled SDK jar into the module (see JavaSdk.vendorSdkJar).
"""
let vendorSdkJar: Boolean!
"""
Module-relative directory to write the Dang entrypoint into, or null for a
module on the runtime.
"""
let dangEntrypoint: String
"""
The clients generated into packages of their own under clients/<name>, for a
module on the Dang entrypoint. A module on the runtime has none: its clients
are its dependencies, whose types the engine adds to its schema.
"""
let clients: [ModuleSource!]!
"""
The shared local Maven repository.
Mounted LOCKED, not shared: seeding it from the committed prebuilt repository
is a plain recursive copy, and two generations running at once race on it —
`cp: cannot create directory ...: File exists`. Generating two Java modules
concurrently is ordinary, so the mount serializes rather than the callers.
"""
let mavenRepo: CacheVolume! { cacheVolume("sdk-java-maven-m2") }
"""
Maven container used for codegen (pinned digest, matches the builtin runtime).
"""
let mvn: Container! {
container.from("maven:3.9.9-eclipse-temurin-21-alpine@sha256:4cbb8bf76c46b97e028998f2486ed014759a8e932480431039bdb93dffe6813e")
}
"""
The vendored Java SDK Maven reactor shipped with this module.
"""
let sdkSourceDir: Directory! { currentModule.source.directory("sdk") }
"""
Vendor the Java SDK as buildable source for a module:
src/main/java the hand-written SDK library
src/processor/java the annotation processor that generates the entrypoint
src/processor/resources/META-INF/services/...Processor
The returned directory is dropped at <module>/sdk.
"""
let prebuiltCodegenRepo: String! { "prebuilt/m2" }
let prebuiltCodegenPlugin: String! { "prebuilt/dagger-codegen-maven-plugin.jar" }
"""
A maven container with the codegen plugin available in the local repository.
Fast path: when the packager module has committed the plugin's local Maven
repository under prebuilt/m2, drop it into ~/.m2/repository with a plain copy —
no maven invocation. Otherwise fall back to installing a committed plugin jar,
and finally to compiling the plugin from the vendored sources.
"""
let codegenBase(introspectionJSON: File!): Container! {
let base = mvn
.withoutEntrypoint
.withMountedCache("/root/.m2", mavenRepo, sharing: CacheSharingMode.LOCKED)
.withMountedFile("/schema.json", introspectionJSON)
.withDirectory("/dagger-io", sdkSourceDir)
.withWorkdir("/dagger-io")
if (currentModule.source.exists(prebuiltCodegenRepo + "/io/dagger")) {
base
.withDirectory("/prebuilt-m2", currentModule.source.directory(prebuiltCodegenRepo))
.withExec(["sh", "-c", "mkdir -p /root/.m2/repository && cp -r /prebuilt-m2/. /root/.m2/repository/"])
} else if (currentModule.source.exists(prebuiltCodegenPlugin)) {
base
.withMountedFile("/codegen-plugin.jar", currentModule.source.file(prebuiltCodegenPlugin))
.withExec(["mvn", "install:install-file", "-Dfile=/dagger-io/pom.xml", "-Dpackaging=pom", "-DpomFile=/dagger-io/pom.xml", "--no-transfer-progress"])
.withExec(["mvn", "install:install-file", "-Dfile=/codegen-plugin.jar", "-DpomFile=/dagger-io/dagger-codegen-maven-plugin/pom.xml", "--no-transfer-progress"])
} else {
base.withExec(["mvn", "--projects", "dagger-codegen-maven-plugin", "--also-make", "install", "-T1C", "-Dmaven.test.skip=true", "-Dfmt.skip=true", "--no-transfer-progress"])
}
}
"""
Build and install the SDK + annotation processor jars, returning the maven
container.
Uses install (rather than generate-sources) so the compiled jars land in the
local Maven repository — the dagger-prebuilt-sdk pom profile compiles the
entrypoint against them instead of recompiling the vendored SDK sources. Jars
are installed under a per-module version so modules that resolve to different
schemas (different dependencies, or a different engine) never share a Maven
coordinate; the codegen plugin keeps its own fixed version and resolves from the
prebuilt repo. Cached across a module's own edits.
"""
let sdkBuilt(introspectionJSON: File!, name: String!): Container! {
let versioned = codegenBase(introspectionJSON)
.withExec(["mvn", "versions:set", "-DnewVersion=" + name, "-DgenerateBackupPoms=false", "--no-transfer-progress"])
let planned = if (clients.length == 0) {
versioned
} else {
versioned.withMountedDirectory("/plan", generationPlan(introspectionJSON))
}
let input = if (clients.length == 0) { "-Ddaggerengine.schema=/schema.json" } else { "-Ddaggerengine.plan=/plan" }
planned.withExec(["mvn", "--projects", "dagger-java-sdk,dagger-java-annotation-processor", "--also-make", "install", input, "-Ddaggerengine.version=" + engineVersion, "-Dmaven.test.skip=true", "-Dfmt.skip=true", "--no-transfer-progress"])
}
"""
What the codegen plugin generates for a scope with clients: core from the
scope's own schema, and one target per client. A client's schema is read in
the module's view, so it names only the core types the module's core has. A
plain project has no module, and reads it in the engine's view, as its core.
"""
let generationPlan(introspectionJSON: File!): Directory! {
let view = if (isModule) { ws.moduleSource(workspaceRef(rootPath)).engineVersion } else { engineVersion }
clients.reduce(directory.withFile("core/schema.json", introspectionJSON)) { plan, client =>
let entry = "target-" + client.moduleName
plan
.withFile(entry + "/schema.json", client.withEngineVersion(view).clientSchemaIntrospectionJSON)
.withNewFile(entry + "/module", client.moduleName + "\n")
.withNewFile(entry + "/descriptor", clientDescriptor(client))
}
}
"""
Where a client loads its target from: a git ref with the commit it resolved
to, or a module of the workspace by its path from the workspace root, which
serveModule reads in the calling module's own tree.
"""
let clientDescriptor(client: ModuleSource!): String! {
if (client.kind == ModuleSourceKind.GIT_SOURCE) {
"git\n" + client.asString + "\n" + client.pin + "\n"
} else {
"local\n" + workspaceRef(client.sourceRootSubpath) + "\n"
}
}
"""
The live engine version, without build metadata.
Codegen only reads the engine version off the CLI when it has to query the
schema itself. Here the schema is handed to it, so without this the version
stays whatever the pom happens to say, and generation cannot tell which shapes
the engine on the other end actually supports.
The `+<commit>` suffix is dropped: it changes on every engine build and would
make every module's SDK rebuild for no reason.
"""
let engineVersion: String! { version.split("+")[0] ?? version }
let vendoredSdk(introspectionJSON: File!, name: String!): Directory! {
let built = sdkBuilt(introspectionJSON, name)
directory
.withDirectory("src/main/java", built.directory("/dagger-io/dagger-java-sdk/src/main/java"))
.withDirectory(
"src/processor/java",
built.directory("/dagger-io/dagger-java-annotation-processor/src/main/java"),
)
.withNewFile(
"src/processor/resources/META-INF/services/javax.annotation.processing.Processor",
"io.dagger.annotation.processor.DaggerModuleAnnotationProcessor\n",
)
}
"""
Where the engine's core API is generated, relative to the module: the source
root of the client package io.dagger.client.modules.core.
"""
let coreClientPath: String! = "clients/core/src/main/java"
"""
Every package the SDK generated: core, and one package per client.
"""
let generatedPackages(introspectionJSON: File!, name: String!): Directory! {
sdkBuilt(introspectionJSON, name)
.directory("/dagger-io/dagger-java-sdk/target/generated-sources/dagger")
}
"""
The engine's core API, generated from the module's schema with the SDK.
"""
let coreClient(introspectionJSON: File!, name: String!): Directory! {
clients.reduce(generatedPackages(introspectionJSON, name)) { core, client =>
core.withoutDirectory(clientPackagePath(client))
}
}
"""
One client's package, as the source root of clients/<name>.
"""
let clientSources(introspectionJSON: File!, name: String!, client: ModuleSource!): Directory! {
let path = clientPackagePath(client)
directory.withDirectory(path, generatedPackages(introspectionJSON, name).directory(path))
}
"""
The directory of a client's package: the module name lowercased and stripped
to ASCII letters and digits, as ModulePackage.segmentFor names the package.
"""
let clientPackagePath(client: ModuleSource!): String! {
"io/dagger/client/modules/" + client.moduleName.toLower.replaceMatches(Regexp("[^a-z0-9]"), "")
}
let clientSourcePath(client: ModuleSource!): String! {
"clients/" + client.moduleName + "/src/main/java"
}
"""
Directories under clients/ that this SDK generated for a client the module
no longer has. A directory holding no generated package is not the SDK's.
"""
let staleClientDirs(committed: Directory!): [String!]! {
if (committed.exists("clients") == false) {
return [] :: [String!]!
}
let current = clients.map { client => client.moduleName }
committed.directory("clients").entries
.map { entry => entry.trimSuffix("/") }
.filter { dir =>
dir != "core" and current.contains(dir) == false and
committed.exists("clients/" + dir + "/src/main/java/io/dagger/client/modules")
}
}
"""
The pom.xml with this SDK's clients profile, which adds each client's
package as a compile root. The profile is rewritten on every generation,
and removed with the module's last client. It is active unless
dagger.clients.skip is set, because a profile active by default turns off
when any other profile turns on, and the SDK's own profiles do.
"""
let withClientsProfile(pom: String!): String! {
withMarkedProfile(pom, clientsProfileMarker, if (clients.length == 0) { "" } else { clientsProfile })
}
"""
The pom.xml with the profile between a marker's comments replaced: the old
one removed, and the new one, unless it is empty, added before the pom's
last </profiles>, or in a <profiles> of its own when the pom has none.
"""
let withMarkedProfile(pom: String!, marker: String!, profile: String!): String! {
let stripped = pom.replaceMatches(Regexp("(?s)\n[ \t]*<!-- " + marker + ":.*?<!-- /" + marker + " -->"), "")
if (profile == "") {
return stripped
}
if (stripped.contains("</profiles>")) {
stripped.replaceMatches(Regexp("(?s)^(.*)(\n[ \t]*</profiles>)"), "${1}" + profile + "${2}")
} else if (stripped.contains("</project>")) {
stripped.replaceMatches(
Regexp("(?s)^(.*)(\n[ \t]*</project>)"),
"${1}\n\n <profiles>" + profile + "\n </profiles>${2}",
)
} else {
raise "pom.xml has no </project>: it cannot hold the " + marker + " profile"
}
}
let clientsProfileMarker: String! = "dagger-java-sdk clients"
let clientsProfile: String! {
let sources = clients.map { client =>
"\n <source>" + clientSourcePath(client) + "</source>"
}
"\n <!-- " + clientsProfileMarker + ": generated from the project's clients. DO NOT EDIT. -->" +
"\n <profile>" +
"\n <id>dagger-clients</id>" +
"\n <activation>" +
"\n <property>" +
"\n <name>!dagger.clients.skip</name>" +
"\n </property>" +
"\n </activation>" +
"\n <build>" +
"\n <plugins>" +
"\n <plugin>" +
"\n <groupId>org.codehaus.mojo</groupId>" +
"\n <artifactId>build-helper-maven-plugin</artifactId>" +
"\n <executions>" +
"\n <execution>" +
"\n <id>add-dagger-client-sources</id>" +
"\n <goals>" +
"\n <goal>add-source</goal>" +
"\n </goals>" +
"\n <configuration>" +
"\n <sources>" +
sources.join("") +
"\n </sources>" +
"\n </configuration>" +
"\n </execution>" +
"\n </executions>" +
"\n </plugin>" +
"\n </plugins>" +
"\n </build>" +
"\n </profile>" +
"\n <!-- /" + clientsProfileMarker + " -->"
}
"""
The pom.xml of a plain project with this SDK's profile, which compiles the
vendored SDK and core with the dependencies they need, as a module's own
build does. It is rewritten on every generation, and active unless
dagger.sdk.skip is set, for the reason the clients profile is.
"""
let withSdkProfile(pom: String!): String! {
withMarkedProfile(pom, sdkProfileMarker, sdkProfile)
}
let sdkProfileMarker: String! = "dagger-java-sdk sdk"
"""
Keep its versions with the ones the module templates' pom.xml pins.
"""
let sdkProfile: String! {
"\n <!-- " + sdkProfileMarker + ": the Dagger Java SDK and core. DO NOT EDIT. -->" +
"\n <profile>" +
"\n <id>dagger-sdk</id>" +
"\n <activation>" +
"\n <property>" +
"\n <name>!dagger.sdk.skip</name>" +
"\n </property>" +
"\n </activation>" +
"\n <dependencyManagement>" +
"\n <dependencies>" +
"\n <dependency>" +
"\n <groupId>io.opentelemetry</groupId>" +
"\n <artifactId>opentelemetry-bom</artifactId>" +
"\n <version>1.61.0</version>" +
"\n <type>pom</type>" +
"\n <scope>import</scope>" +
"\n </dependency>" +
"\n </dependencies>" +
"\n </dependencyManagement>" +
"\n <dependencies>" +
sdkDependency("jakarta.json", "jakarta.json-api", "2.1.3", "") +
sdkDependency("jakarta.json.bind", "jakarta.json.bind-api", "3.0.1", "") +
sdkDependency("org.slf4j", "slf4j-api", "2.0.17", "") +
sdkDependency("io.opentelemetry", "opentelemetry-api", "", "") +
sdkDependency("io.opentelemetry", "opentelemetry-sdk", "", "") +
"\n <dependency>" +
"\n <groupId>io.opentelemetry</groupId>" +
"\n <artifactId>opentelemetry-exporter-otlp</artifactId>" +
"\n <exclusions>" +
"\n <exclusion>" +
"\n <groupId>io.opentelemetry</groupId>" +
"\n <artifactId>opentelemetry-exporter-sender-okhttp</artifactId>" +
"\n </exclusion>" +
"\n </exclusions>" +
"\n </dependency>" +
sdkDependency("io.opentelemetry", "opentelemetry-exporter-sender-jdk", "", "runtime") +
sdkDependency("org.eclipse", "yasson", "3.0.4", "runtime") +
"\n </dependencies>" +
"\n <build>" +
"\n <plugins>" +
"\n <plugin>" +
"\n <groupId>org.codehaus.mojo</groupId>" +
"\n <artifactId>build-helper-maven-plugin</artifactId>" +
"\n <version>3.3.0</version>" +
"\n <executions>" +
"\n <execution>" +
"\n <id>add-dagger-sdk-sources</id>" +
"\n <goals>" +
"\n <goal>add-source</goal>" +
"\n </goals>" +
"\n <configuration>" +
"\n <sources>" +
"\n <source>sdk/src/main/java</source>" +
"\n <source>" + coreClientPath + "</source>" +
"\n </sources>" +
"\n </configuration>" +
"\n </execution>" +
"\n </executions>" +
"\n </plugin>" +
"\n </plugins>" +
"\n </build>" +
"\n </profile>" +
"\n <!-- /" + sdkProfileMarker + " -->"
}
let sdkDependency(groupId: String!, artifactId: String!, version: String!, scope: String!): String! {
"\n <dependency>" +
"\n <groupId>" + groupId + "</groupId>" +
"\n <artifactId>" + artifactId + "</artifactId>" +
(if (version == "") { "" } else { "\n <version>" + version + "</version>" }) +
(if (scope == "") { "" } else { "\n <scope>" + scope + "</scope>" }) +
"\n </dependency>"
}
"""
Core's schema for a plain project: the client-facing one, with the host and
the engine API a module's schema hides. The engine serves core alone for a
module source with no runtime, as it installs nothing beside core.
"""
let projectCoreSchema: File! {
directory
.withNewFile("dagger-module.toml", "name = \"core-schema\"\n")
.asModuleSource
.withEngineVersion(engineVersion)
.clientSchemaIntrospectionJSON
}
"""
The Maven version a plain project's SDK is built under. One serves every
project: only a module's processor compile and its vendored jar read the
installed jars back.
"""
let projectSdkVersion: String! = "plain-project"
"""
Refuse a pom.xml that does not compile the core client. Generation compiles
against the prebuilt SDK jar, so a missing source root would otherwise surface
only when the engine builds the module to call it.
"""
let requireCoreClientSource(moduleSource: Directory!): Void {
if (moduleSource.file("pom.xml").contents.contains("<source>" + coreClientPath + "</source>") == false) {
raise "pom.xml does not compile the core client: in the add-dagger-sdk-sources execution of " +
"build-helper-maven-plugin, replace <source>sdk/src/generated/java</source> with " +
"<source>" + coreClientPath + "</source>"
}
null
}
"""
Capture the compiled SDK jar as a small local Maven repository to commit under
<module>/sdk/repo. Stored at a fixed version so the module pom's
dagger-vendored-sdk-jar profile resolves it; the runtime build then compiles
only the module's own code against the jar rather than the vendored sources.
"""
let vendoredSdkJar(introspectionJSON: File!, name: String!): Directory! {
let jar = sdkBuilt(introspectionJSON, name)
.withExec(["sh", "-c", "mkdir -p /out && cp /root/.m2/repository/io/dagger/dagger-java-sdk/" + name + "/dagger-java-sdk-" + name + ".jar /out/dagger-java-sdk-0.21.4.jar"])
.file("/out/dagger-java-sdk-0.21.4.jar")
directory
.withFile("io/dagger/dagger-java-sdk/0.21.4/dagger-java-sdk-0.21.4.jar", jar)
.withNewFile(
"io/dagger/dagger-java-sdk/0.21.4/dagger-java-sdk-0.21.4.pom",
"<project><modelVersion>4.0.0</modelVersion><groupId>io.dagger</groupId><artifactId>dagger-java-sdk</artifactId><version>0.21.4</version><packaging>jar</packaging></project>\n",
)
}
"""
Compile the module with the annotation processor enabled (proc=full) so it
emits io.dagger.gen.entrypoint.Entrypoint under
target/generated-sources/annotations, for vendoring under
<module>/src/generated/java, and the Dang entrypoint's main.dang under
target/classes/dagger/entrypoint.
"""
let processed(moduleDir: Directory!, name: String!): Container! {
mvn
.withoutEntrypoint
.withMountedCache("/root/.m2", mavenRepo, sharing: CacheSharingMode.LOCKED)
.withDirectory("/module", moduleDir)
.withWorkdir("/module")
.withEnvVariable("_DAGGER_JAVA_SDK_MODULE_NAME", name)
.withExec(["mvn", "compile", "-Ddagger.proc=full", "-Ddagger.sdk=prebuilt", "-Ddagger.sdk.version=" + name, "--no-transfer-progress"])
}
"""
The Dang entrypoint: main.dang from the annotation processor, and build.dang,
which builds the jar with the images the runtime pins. build.dang runs the
processor again under the name it ran under here, because the name decides
which object is the module's.
"""
let dangEntrypointDir(processedModule: Container!, name: String!): Directory! {
directory
.withFile("main.dang", processedModule.file("/module/target/classes/dagger/entrypoint/main.dang"))
.withNewFile(
"build.dang",
currentModule.source.file("entrypoint/build.dang.tmpl").contents
.replace("<maven-image>", dockerfileImage("runtime/images/maven/Dockerfile"))
.replace("<jre-image>", dockerfileImage("runtime/images/java/Dockerfile"))
.replace("<module-name>", name),
)
}
"""
Where a module on the Dang entrypoint lists every file generation wrote into
it with its SHA-256, which build.dang checks before a call.
"""
let generatedSumsPath: String! = "src/generated/sha256sums"
"""
The list at generatedSumsPath, in the format of sha256sum: the vendored SDK,
core, each client and src/generated, as ws holds them.
"""
let generatedSums(ws: Workspace!): String! {
let owned = ["sdk", "clients/core", "src/generated"] + clients.map { client => "clients/" + client.moduleName }
let tree = ws.directory(
"/",
include: owned.map { dir => joinPath(rootPath, dir) + "/**" },
exclude: [joinPath(rootPath, generatedSumsPath)],
)
let moduleTree = if (rootPath == ".") { tree } else { tree.directory(rootPath) }
mvn
.withoutEntrypoint
.withDirectory("/generated", moduleTree)
.withWorkdir("/generated")
.withExec(["sh", "-c", "find . -type f | LC_ALL=C sort | xargs sha256sum | sed 's| \\./| |'"])
.stdout
}
"""
The image a single-FROM Dockerfile of the runtime names, which Dependabot
keeps up to date.
"""
let dockerfileImage(path: String!): String! {
currentModule.source.file(path).contents.trimSpace.trimPrefix("FROM ")
}
"""
The workspace with this module's vendored SDK, core client and generated
entrypoint merged in, or with a plain project's vendored SDK, core and
clients.
"""
generated: Workspace! {
let committed = moduleDir(ws, rootPath)
if (isModule == false) {
let pom = withClientsProfile(withSdkProfile(committed.file("pom.xml").contents))
let vendored = vendoredSdk(projectCoreSchema, projectSdkVersion)
return withSdkAndClients(staleClientDirs(committed), vendored, projectCoreSchema, projectSdkVersion, pom)
}
requireCoreClientSource(committed)
let modSource = ws.moduleSource(workspaceRef(rootPath))
let name = modSource.moduleName
let introspectionJSON = modSource.introspectionSchemaJSON
let vendored = vendoredSdk(introspectionJSON, name)
let pom = withClientsProfile(committed.file("pom.xml").contents)
let stale = staleClientDirs(committed)
# 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,
# and the fresh client packages and pom in place, so the processor compiles
# the clients the module will be built with
let withoutStale = stale.reduce(committed) { dir, stalePath => dir.withoutDirectory("clients/" + stalePath) }
let baseDir = clients
.reduce(withoutStale) { dir, client =>
dir
.withoutDirectory(clientSourcePath(client))
.withDirectory(clientSourcePath(client), clientSources(introspectionJSON, name, client))
}
.withNewFile("pom.xml", pom)
.withoutDirectory("sdk")
.withDirectory("sdk", vendored)
.withoutDirectory("src/generated")
let processedModule = processed(baseDir, name)
let withJava = withSdkAndClients(stale, vendored, introspectionJSON, name, pom)
.withNewDirectory(
workspaceRef(joinPath(rootPath, "src/generated/java")),
processedModule.directory("/module/target/generated-sources/annotations"),
)
let staged = if (vendorSdkJar) {
withJava.withNewDirectory(
workspaceRef(joinPath(rootPath, "sdk/repo")),
vendoredSdkJar(introspectionJSON, name),
)
} else {
withJava
}
if (dangEntrypoint == null) {
return staged
}
let withEntrypoint = staged.withNewDirectory(
workspaceRef(joinPath(rootPath, dangEntrypoint ?? "")),
dangEntrypointDir(processedModule, name),
)
withEntrypoint.withNewFile(
workspaceRef(joinPath(rootPath, generatedSumsPath)),
generatedSums(withEntrypoint),
)
}
"""
The workspace with the pom.xml, the vendored SDK, core and one package per
client written, and the package of each client the scope no longer has
removed.
"""
let withSdkAndClients(
stale: [String!]!,
vendored: Directory!,
introspectionJSON: File!,
name: String!,
pom: String!,
): Workspace! {
let withClients = clients.reduce(
stale.reduce(ws) { w, stalePath => w.withoutDirectory(workspaceRef(joinPath(rootPath, "clients/" + stalePath))) },
) { w, client =>
w.withNewDirectory(
workspaceRef(joinPath(rootPath, "clients/" + client.moduleName)),
directory.withDirectory("src/main/java", clientSources(introspectionJSON, name, client)),
)
}
withClients
.withNewFile(workspaceRef(joinPath(rootPath, "pom.xml")), pom)
.withNewDirectory(workspaceRef(joinPath(rootPath, "sdk")), vendored)
.withNewDirectory(
workspaceRef(joinPath(rootPath, coreClientPath)),
coreClient(introspectionJSON, name),
)
}
"""
The module's committed source directory, workspace-rooted.
Maven build output is left out: a class file an IDE dropped under target/ is
newer than its source, so maven-compiler-plugin skips recompiling it and the
stale class wins over the entrypoint this generation is producing.
"""
let moduleDir(ws: Workspace!, modPathArg: String!): Directory! {
let buildOutputExclude = ["**/target/**"]
if (modPathArg == ".") {
ws.directory("/", include: ["**"], exclude: buildOutputExclude)
} else {
ws
.directory("/", include: [modPathArg + "/**"], exclude: buildOutputExclude)
.directory(modPathArg)
}
}
"""
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 }
}
"""
Join a module path with a sub-path, handling the root (".") module.
"""
let joinPath(modPathArg: String!, sub: String!): String! {
if (modPathArg == ".") { sub } else { modPathArg + "/" + sub }
}
}