-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbuild.sbt
More file actions
273 lines (258 loc) · 13.8 KB
/
Copy pathbuild.sbt
File metadata and controls
273 lines (258 loc) · 13.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
ThisBuild / tlBaseVersion := "0.3"
ThisBuild / organization := "org.polyvariant"
ThisBuild / organizationName := "Polyvariant"
ThisBuild / startYear := Some(2026)
ThisBuild / licenses := Seq(License.Apache2)
ThisBuild / developers := List(tlGitHubDev("kubukoz", "Jakub Kozłowski"))
ThisBuild / githubWorkflowPublishTargetBranches := Seq(
RefPredicate.Equals(Ref.Branch("main")),
RefPredicate.StartsWith(Ref.Tag("v")),
)
ThisBuild / scalaVersion := "3.3.8"
ThisBuild / tlJdkRelease := Some(11)
ThisBuild / tlFatalWarnings := false
ThisBuild / resolvers += Resolver.sonatypeCentralSnapshots
ThisBuild / mergifyStewardConfig ~= (_.map(_.withMergeMinors(true)))
// Smithy sources are formatted too (`smithyFmtAll` to fix), checked alongside scalafmt in the
// same sbt invocation rather than as a job of its own — it's fast, and a formatting failure is
// worth reporting all at once.
ThisBuild / githubWorkflowBuild ~= {
_.map {
case step: WorkflowStep.Sbt if step.name.exists(_.contains("formatting")) =>
step.withCommands(step.commands :+ "smithyFmtCheckAll")
case other => other
}
}
val smithy4sVersion = "0.19.11"
// Only the generated trait classes need this, and only against the stable parts of the model API
// (`AbstractTrait`, `ShapeId`, `Node`). A consumer whose build also pulls an older smithy-model —
// via alloy, say — resolves to the newer of the two, which is the direction Smithy tooling
// supports: a newer model library reads older models.
val smithyVersion = "1.73.0"
// Only needed to resolve the `alloy#*` shape IDs in the protocol's trait list at trait-codegen
// time; it is not a dependency of anything published. Keep at or above the alloy that the smithy4s
// version above builds against, so the trait list can't name a trait that smithy4s doesn't know.
val alloyVersion = "0.3.40"
val http4sVersion = "0.23.36"
val fs2Version = "3.13.0"
val weaverVersion = "0.13.0"
val commonSettings = Seq(
scalacOptions ++= Seq(
"-no-indent",
"-Wunused:all",
)
)
/** The protocol itself: the `org.polyvariant.ndjson#ndjsonRestJson` trait, and nothing else.
*
* Published as a plain Java artifact (no Scala suffix, no Scala library) because a protocol
* definition is not Scala-specific: it is consumed by `smithy-build`, by Smithy CLI validators,
* and by codegen for any language — none of which should have to pick a Scala version to depend on
* it. Scala users get it transitively via `http4s` below.
*
* `SmithyTraitCodegenPlugin` generates the Java trait classes from the model and packages the
* `.smithy` file under `META-INF/smithy`, so the trait resolves off the classpath.
*/
lazy val protocol = project
.in(file("modules/protocol"))
.enablePlugins(SmithyTraitCodegenPlugin)
.settings(
name := "smithy4s-ndjson-protocol",
// No previous release to check against yet — and note that 0.3.0 is *not* one: this module
// dropped out of that release when it was briefly aggregated from `rootJVM` instead of the
// root that `tlCiRelease` publishes (see `root` at the bottom of this file). So the first
// version to compare against is whichever release next publishes it.
//
// Note this is `tlMimaPreviousVersions := Set.empty`
// and *not* `disablePlugins(MimaPlugin)`: sbt-typelevel's `TypelevelPlugin` transitively
// requires `MimaPlugin`, so disabling the latter also switches off `TypelevelSonatypePlugin` —
// which is what sets `publishTo`. The module then fails to publish with "Repository for
// publishing is not specified", but only on a release run, long after CI has gone green.
tlMimaPreviousVersions := Set.empty,
crossPaths := false,
autoScalaLibrary := false,
// The trait's `@protocolDefinition` list names `alloy#*` traits (it mirrors
// `alloy#simpleRestJson`), so alloy's model has to be on the path for the shape IDs to resolve
// while the trait is generated.
//
// `Provided`, because this is a build-time model input and nothing else: the generated Java
// class references no alloy type, and a consumer of this artifact loads the trait from
// `META-INF/smithy`, where the list is already flat strings. The plugin appends this list to
// `libraryDependencies` verbatim and then finds the jar by scanning `update`, so the scope has
// to be one that still resolves — `Provided` does, while staying out of the published POM's
// compile scope. Left unscoped, every consumer of a protocol artifact would inherit alloy.
smithyTraitCodegenDependencies := List(
"com.disneystreaming.alloy" % "alloy-core" % alloyVersion % Provided
),
// The generated Java trait classes extend Smithy's own `AbstractTrait`, so the model library
// is needed to compile them — and by anything loading the trait through Smithy's ModelAssembler.
libraryDependencies += "software.amazon.smithy" % "smithy-model" % smithyVersion,
// javadoc rejects the lint flags sbt-typelevel passes to javac (`-Xlint:all` is not a javadoc
// option), so the doc task gets a pared-down set. Only `-source` survives, to keep the
// generated docs on the same language level as the compiled classes.
Compile / doc / javacOptions := Seq("-source", tlJdkRelease.value.fold("11")(_.toString)),
smithyTraitCodegenJavaPackage := "org.polyvariant.ndjson",
smithyTraitCodegenNamespace := "org.polyvariant.ndjson",
)
/** Puts `protocol`'s trait on the codegen model path, using the in-build artifact rather than a
* resolved one.
*
* `smithy4sInternalDependenciesAsJars` doesn't include jars from the "smithy4s" configuration, so
* an ordinary `dependsOn` isn't enough — this is the in-build equivalent of a `% Smithy4s`-scoped
* dependency, and it is what lets the modules below build against an unreleased protocol change in
* the same commit — including the very first one, before the protocol has ever been published.
*
* Downstream users need none of this: the trait reaches them on the ordinary classpath via
* `META-INF/smithy`, pulled in by `core`'s `smithy4sDependencies` manifest entry.
*/
lazy val buildTimeProtocolDependency =
Compile / smithy4sInternalDependenciesAsJars ++=
(protocol / Compile / fullClasspathAsJars).value.map(_.data)
/** Keeps the protocol namespace from being generated a second time.
*
* The trait has to be on the model path of every module that runs codegen (an annotated service
* can't be read without it), but only `core` should emit Scala for it — otherwise each consumer
* gets its own copy of `org.polyvariant.ndjson.NdjsonRestJson` and downstream classpaths end up
* with duplicates of one fully-qualified class.
*
* This uses the deprecated sbt key rather than its suggested replacement, the `smithy4sCodegen`
* Smithy metadata, on purpose: metadata applies to the assembled model as a whole, so declaring
* the exclusion in the protocol's own `.smithy` would also suppress generation in `core`, which is
* the one module that must generate it. The two sources are unioned, not overridden, so there is
* no per-module way to express this in metadata. Revisit if the key is actually removed.
*/
lazy val protocolGeneratedByCore =
Compile / smithy4sExcludedNamespaces := List("org.polyvariant.ndjson")
/** Records the protocol as a smithy-level dependency of `core` in its jar manifest, so a downstream
* build pulls the trait onto its own codegen model path automatically.
*
* The smithy4s plugin does this for you, but only for dependencies declared `% Smithy4s` in
* `libraryDependencies` — which an in-build module can't be, since nothing has published the
* current snapshot yet and the `% Smithy4s` entry would fail to resolve. So the entry is written
* directly; see https://github.com/disneystreaming/smithy4s/issues/1749 for the sbt setting that
* would make this unnecessary.
*
* The value format is the plugin's own (`moduleIdEncode`): comma-separated `org:name:revision`,
* with no Scala suffix because `protocol` sets `crossPaths := false`. `packageOptions` is appended
* to rather than assigned, so the plugin's own entry for `core`'s external `% Smithy4s`
* dependencies (currently none) would survive alongside this one.
*/
lazy val protocolManifestEntry =
Compile / packageBin / packageOptions += {
val manifest = new java.util.jar.Manifest()
manifest.getMainAttributes().put(java.util.jar.Attributes.Name.MANIFEST_VERSION, "1.0")
manifest
.getMainAttributes()
.putValue(
"smithy4sDependencies",
s"org.polyvariant:smithy4s-ndjson-protocol:${version.value}",
)
Package.JarManifest(manifest)
}
/** Points codegen at the Smithy sources in the shared (`CrossType.Pure`) source base.
*
* `smithy4sInputDirs` is derived from `sourceDirectory`, which a crossProject makes platform-local
* — so under `CrossType.Pure` it resolves to `modules/X/.jvm/src/main/smithy` and
* `modules/X/.native/src/main/smithy`, neither of which exists. A Smithy model is not
* platform-specific, so both platforms read the one copy at `modules/X/src/main/smithy` instead.
*
* The `src_managed` entry is kept: the plugin appends generated Smithy there per platform, and
* dropping it would break codegen that produces Smithy as an intermediate.
*/
lazy val sharedSmithySources =
Compile / smithy4sInputDirs := Seq(
(ThisProject / baseDirectory).value.getParentFile() / "src" / "main" / "smithy",
(Compile / sourceManaged).value / "smithy",
)
/** The generated Scala view of the protocol trait: `org.polyvariant.ndjson.NdjsonRestJson`.
*
* Its own module because that class must be generated exactly once in the build. Generating it
* from both `http4s` and `testFixtures` — which is what happened while each ran codegen over the
* protocol namespace itself — puts two copies of the same fully-qualified class on a downstream
* classpath.
*
* Note this deliberately does NOT `.dependsOn(protocol)`. `protocol` is a Java-only artifact
* (`crossPaths := false`, `autoScalaLibrary := false`), so a project dependency would pin this
* module — and everything downstream of it — to the JVM, which is what makes the Native
* cross-build below possible; it would also put a suffix-less artifact in the published POM. The
* trait is a build-time input only: it reaches codegen via `buildTimeProtocolDependency` (as a
* classpath of jars, which has no platform), and reaches downstream builds as the
* `smithy4sDependencies` manifest entry written by `protocolManifestEntry`.
*/
lazy val core = crossProject(JVMPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("modules/core"))
.enablePlugins(Smithy4sCodegenPlugin)
.settings(
name := "smithy4s-ndjson-core",
commonSettings,
tlMimaPreviousVersions := Set.empty,
libraryDependencies += "com.disneystreaming.smithy4s" %%% "smithy4s-core" % smithy4sVersion,
sharedSmithySources,
buildTimeProtocolDependency,
protocolManifestEntry,
)
/** The http4s interpreter for the protocol: `NdjsonRestJsonBuilder`, the counterpart to smithy4s's
* `SimpleRestJsonBuilder` for services that stream.
*/
lazy val http4s = crossProject(JVMPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("modules/http4s"))
.enablePlugins(Smithy4sCodegenPlugin)
.dependsOn(core, testFixtures % Test)
.settings(
name := "smithy4s-ndjson-http4s",
commonSettings,
libraryDependencies ++= Seq(
"com.disneystreaming.smithy4s" %%% "smithy4s-core" % smithy4sVersion,
"com.disneystreaming.smithy4s" %%% "smithy4s-json" % smithy4sVersion,
"com.disneystreaming.smithy4s" %%% "smithy4s-http4s" % smithy4sVersion,
"org.http4s" %%% "http4s-core" % http4sVersion,
"co.fs2" %%% "fs2-core" % fs2Version,
"org.typelevel" %%% "weaver-cats" % weaverVersion % Test,
"org.http4s" %%% "http4s-dsl" % http4sVersion % Test,
),
sharedSmithySources,
buildTimeProtocolDependency,
protocolGeneratedByCore,
)
/** A service exercising every shape the protocol admits (binary in, NDJSON out, plain JSON,
* metadata bindings), so the interpreter is tested against real codegen output rather than a
* hand-written stand-in.
*
* Its own module because the smithy4s sbt plugin only wires codegen into `Compile` — a
* `Test / smithy4sInputDirs` in `http4s` is accepted but never runs. Keeping it separate also
* keeps the fixtures out of the published jar.
*/
lazy val testFixtures = crossProject(JVMPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("modules/testfixtures"))
.enablePlugins(Smithy4sCodegenPlugin)
.disablePlugins(MimaPlugin)
.dependsOn(core)
.settings(
name := "smithy4s-ndjson-testfixtures",
commonSettings,
publish / skip := true,
sharedSmithySources,
buildTimeProtocolDependency,
protocolGeneratedByCore,
)
/** `protocol` is aggregated by the root, exactly as it was before the Native cross-build.
*
* `tlCrossRootProject.aggregate` takes cross-projects; a plain project goes through `.configure`,
* which attaches it to the top-level `root` and to each platform root alike. The top-level one is
* what matters: `sbt tlCiRelease` publishes from `root`, so anything not aggregated there is
* silently left out of the release.
*
* That is not hypothetical. 0.3.0 shipped `core` and `http4s` without `smithy4s-ndjson-protocol`,
* because this was briefly attached to `rootJVM` alone — an attempt to stop the Native `Test` job
* rebuilding a Java jar the JVM job already builds. That duplicate build is real but cheap;
* dropping an artifact from a release is neither, and sbt offers no way to subtract an aggregate
* from one platform root after the fact. So the redundant build stays.
*
* The Native modules see the trait regardless: it reaches their codegen through
* `buildTimeProtocolDependency`, a direct classpath reference that needs no aggregation.
*/
lazy val root = tlCrossRootProject
.aggregate(core, http4s, testFixtures)
.configure(_.aggregate(protocol))