|
| 1 | +# sbt 2 migration status |
| 2 | + |
| 3 | +sbt 2.0.0 went GA on 2026-06-14. It is a Scala 3-based rewrite: build definitions and |
| 4 | +plugins compile against a Scala 3 API, every plugin needs a dedicated sbt 2 artifact, and |
| 5 | +all tasks are cached by default (results need a `sjsonnew.JsonFormat`, or must opt out via |
| 6 | +`Def.uncached`). Migrating the whole build is currently **blocked** on upstream releases, |
| 7 | +but the groundwork that can be done safely on sbt 1.x has been done. |
| 8 | + |
| 9 | +> ⚠️ **Pre-release versions in use.** To get onto the sbt 2-capable lines, this branch now |
| 10 | +> depends on **Play 3.1.0-M9** (milestone — no GA yet), **scalatestplus-play 8.0.0-M2** |
| 11 | +> (milestone), and **Scala 3.8.4** (the "Next" line, *not* the 3.3 LTS). These are not |
| 12 | +> production-stable; the branch is a migration staging ground until those lines reach GA. |
| 13 | +
|
| 14 | +## Done (already on this branch) |
| 15 | + |
| 16 | +The sbt-1.x-only prep below is fully backward compatible. The Play 3.1.0 upgrade after it |
| 17 | +pulls in the pre-release versions noted above. Everything is verified: all modules compile, |
| 18 | +`scalafmt*Check` is clean, all 50 tests pass, and the codegen plugin compiles on both the |
| 19 | +sbt 1.x and sbt 2.0.0 APIs. |
| 20 | + |
| 21 | +- **Upgraded to the latest sbt 1.x** — `project/build.properties` `1.9.8 → 1.12.12`. This |
| 22 | + is the recommended first migration step and unblocks plugins that now require sbt 1.12.9+. |
| 23 | +- **Bumped sbt-scalafmt** `2.5.2 → 2.6.1` (cross-published for sbt 1.x and 2.x; requires |
| 24 | + sbt ≥ 1.12.9). The scalafmt *engine* stays pinned at 3.7.15 in `.scalafmt.conf`, so |
| 25 | + formatting output is unchanged. |
| 26 | +- **Removed `com.lucidchart % sbt-cross`** — it was unused (no `crossBuild`/`CrossPlugin` |
| 27 | + references anywhere) and is abandoned (last release 2019) with no sbt 2 build. One blocker |
| 28 | + eliminated outright. |
| 29 | +- **Cross-built our own sbt plugin `smithy4play-sbt-codegen` for sbt 2.** It now compiles |
| 30 | + against both the sbt 1.x and sbt 2.0.0 APIs: |
| 31 | + - `crossScalaVersions := Seq("2.12.21", "3.8.4")` with a `pluginCrossBuild / sbtVersion` |
| 32 | + mapping (`2.12 → 1.12.12`, else `2.0.0`). Scala **3.8.4** matches sbt 2.0.0's own build |
| 33 | + (TASTy forward-compat requires our plugin's Scala ≥ sbt's). |
| 34 | + - A small hand-rolled `PluginCompat` shim in `src/main/scala-2` and `src/main/scala-3` |
| 35 | + bridges the two API differences we hit: |
| 36 | + 1. `Attributed#data` on a classpath is `File` (sbt 1) vs `xsbti.HashedVirtualFileRef` |
| 37 | + (sbt 2) — `PluginCompat.toFiles(...)` resolves both to `Seq[File]` via `fileConverter`. |
| 38 | + 2. `Def.uncached { ... }` opts the redefined `Compile / compile` task out of sbt 2's |
| 39 | + result cache (its `CompileAnalysis` return type has no `JsonFormat`). On sbt 1.x the |
| 40 | + shim provides `Def.uncached` as a no-op identity extension. |
| 41 | + - The meta-build compiles the plugin sources directly (`project/build.sbt`), so it also |
| 42 | + picks up the `scala-2` shim dir and depends on nothing new. |
| 43 | + |
| 44 | +### Play 3.1.0 upgrade (pulls in pre-release versions — see warning above) |
| 45 | + |
| 46 | +- **Play `3.0.11 → 3.1.0-M9`** (`project/Dependencies.scala` + `project/plugins.sbt`). This |
| 47 | + is the only sbt 2-capable Play line; GA is not out. Its sbt-plugin still publishes an |
| 48 | + sbt 1.x axis, so it works on sbt 1.12.12 today. |
| 49 | +- **Scala `3.3.8 → 3.8.4`** (`project/Dependencies.scala`). *Forced* by Play 3.1.0-M9, which |
| 50 | + is built with Scala 3.8.3 — the 3.3.8 compiler cannot read its newer TASTy (manifests as |
| 51 | + `AssertionError: ... has non-class parent` while reading `scala3-library` 3.8.3). 3.8.4 is |
| 52 | + the latest Next and matches sbt 2.0.0's own Scala version. **Leaves the 3.3 LTS line.** |
| 53 | +- **scalatestplus-play `7.0.2 → 8.0.0-M2`** — 7.0.x targets Play 3.0.x; 8.0.0-Mx tracks 3.1.0. |
| 54 | +- **Jackson pin `2.14.3 → 2.21.2`** (`build.sbt`, both override blocks) — Play 3.1.0 / Pekko |
| 55 | + 1.5.0 require `jackson-core`'s `StreamReadConstraints` (added in 2.15); the old 2.14.3 pin |
| 56 | + threw `ClassNotFoundException` at Guice injector creation. (`jackson-annotations` is `2.21`.) |
| 57 | +- **`javax.inject` → `jakarta.inject`** in 9 hand-written sources (core, mcp, test |
| 58 | + controllers/middlewares) — Play 3.1 completed the Jakarta EE namespace migration. |
| 59 | + |
| 60 | +> We deliberately did **not** use the Scala Center `sbt2-compat` library even though it |
| 61 | +> provides exactly these helpers (`toFiles`, `Def.uncached`). When cross-building from an |
| 62 | +> sbt 1.x launcher, `addSbtPlugin` generates the wrong sbt-2 cross-coordinate |
| 63 | +> (`sbt2-compat_3_2.0`) while the artifact is published as `sbt2-compat_sbt2_3`, so it |
| 64 | +> fails to resolve. The hand-rolled shim is tiny, dependency-free, and avoids that. |
| 65 | +
|
| 66 | +## Blockers (cannot migrate until these ship) |
| 67 | + |
| 68 | +| Dependency | Needs | Status (2026-06-16) | |
| 69 | +|---|---|---| |
| 70 | +| `smithy4s-sbt-codegen` + `smithy4s-core` | sbt 2 codegen plugin | **No release.** sbt 2 support targets the 0.19.x line — PRs [disneystreaming/smithy4s#1974](https://github.com/disneystreaming/smithy4s/pull/1974) (series/0.19) and [#1973](https://github.com/disneystreaming/smithy4s/pull/1973) (backport to 0.18) opened 2026-06-15, unmerged. **Primary blocker.** | |
| 71 | +| `io.gatling % gatling-sbt` | sbt 2 cross-build | No sbt 2 build started (still pinned to Scala 2.12 / sbt 1.x). Used by `smithy4play-gatling`. | |
| 72 | +| `com.codecommit %% sbt-github-packages` | sbt 2 build or replacement | Abandoned since 2021, no sbt 2. Used for publishing — see replacement note below. | |
| 73 | + |
| 74 | +Ready / no longer blocking: `sbt-scalafmt` 2.6.1, `sbt-scoverage` 2.4.4, `sbt-jmh` 0.4.8 |
| 75 | +(all publish an sbt 2 axis); `sbt-cross` removed; Play moved to the 3.1.0 line (sbt 2-capable, |
| 76 | +currently on milestone M9 — wait for 3.1.0 GA before considering this production-stable). |
| 77 | + |
| 78 | +## Remaining steps for the actual switch (when blockers clear) |
| 79 | + |
| 80 | +1. Bump `smithy4s` to the first 0.19.x that publishes an sbt 2 codegen plugin; regenerate |
| 81 | + and fix any generated-code/runtime changes (treat as its own migration — see the |
| 82 | + dependency check, this is a breaking minor). |
| 83 | +2. Move Play from milestone **3.1.0-M9 → 3.1.0 GA** once released (and scalatestplus-play |
| 84 | + 8.0.0-M2 → GA); re-pin Jackson if the GA bumps it. Re-evaluate staying on Scala 3.8.x |
| 85 | + vs. a future LTS that supports the same TASTy. |
| 86 | +3. Resolve `gatling-sbt` — either wait for an sbt 2 build or move `smithy4play-gatling` to |
| 87 | + run Gatling another way. |
| 88 | +4. Replace `sbt-github-packages`. GitHub Packages is a plain Maven repo, so the plugin can |
| 89 | + be dropped in favour of native sbt config (no sbt 2 plugin needed): |
| 90 | + ```scala |
| 91 | + publishTo := Some("GitHub" at "https://maven.pkg.github.com/innFactory/smithy4play") |
| 92 | + credentials += Credentials( |
| 93 | + "GitHub Package Registry", "maven.pkg.github.com", "innFactory", sys.env("GITHUB_TOKEN") |
| 94 | + ) |
| 95 | + ``` |
| 96 | + (plus a matching resolver for consumers). Verify against the existing |
| 97 | + `publishSmithy4Play` / `publishLocalBundle` aliases in `build.sbt`. |
| 98 | +5. Set `project/build.properties` `sbt.version = 2.x`, convert build definitions per the |
| 99 | + [migration guide](https://www.scala-sbt.org/2.x/docs/en/changes/migrating-from-sbt-1.x.html), |
| 100 | + and re-run the whole build. |
| 101 | +6. **Publishing the codegen plugin for sbt 2:** the cross-build config is in place, but the |
| 102 | + sbt-2 artifact must be *published from an sbt 2.x launcher* so it gets the correct |
| 103 | + `_sbt2_3` coordinate. An sbt 1.x launcher would publish it as `_3_2.0`, which sbt 2 |
| 104 | + consumers won't resolve. Wire this into the release once the project itself runs on sbt 2. |
| 105 | + |
| 106 | +## References |
| 107 | + |
| 108 | +- sbt 2.0.0 release / "Last mile towards sbt 2": <https://www.scala-lang.org/blog/2026/04/14/last-mile-towards-sbt2.html> |
| 109 | +- Migrating plugins with sbt2-compat: <https://www.scala-lang.org/blog/2026/03/02/sbt2-compat.html> |
| 110 | +- Migrating from sbt 1.x: <https://www.scala-sbt.org/2.x/docs/en/changes/migrating-from-sbt-1.x.html> |
| 111 | +- sbt 2.x plugin migration tracker: <https://github.com/sbt/sbt/wiki/sbt-2.x-plugin-migration> |
0 commit comments