8000 Migrate the build to sbt 2 by cquiroz · Pull Request #869 · gemini-hlsw/clue · GitHub
[go: up one dir, main page]

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,38 +68,38 @@ jobs:

- name: Check headers and formatting
if: matrix.java == 'temurin@25' && matrix.os == 'ubuntu-22.04'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck lucumaScalafmtCheck lucumaScalafixCheck lucumaSlackNotifyCheck
run: sbt 'project ${{ matrix.project }}; ++ ${{ matrix.scala }}; headerCheckAll; scalafmtCheckAll; project /; scalafmtSbtCheck; lucumaScalafmtCheck; lucumaScalafixCheck; lucumaSlackNotifyCheck'

- name: Check scalafix lints
if: matrix.java == 'temurin@25' && matrix.os == 'ubuntu-22.04'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' 'scalafixAll --check'
run: sbt 'project ${{ matrix.project }}; ++ ${{ matrix.scala }}; scalafixAll --check'

- name: scalaJSLink
if: matrix.project == 'rootJS'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/scalaJSLinkerResult
run: sbt 'project ${{ matrix.project }}; ++ ${{ matrix.scala }}; Test/scalaJSLinkerResult'

- name: Test affected projects
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' lucumaTestAffected
run: sbt 'project ${{ matrix.project }}; ++ ${{ matrix.scala }}; lucumaTestAffected'

- name: Check binary compatibility
if: matrix.java == 'temurin@25' && matrix.os == 'ubuntu-22.04'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' mimaReportBinaryIssues
run: sbt 'project ${{ matrix.project }}; ++ ${{ matrix.scala }}; mimaReportBinaryIssues'

- name: Generate API documentation
if: matrix.java == 'temurin@25' && matrix.os == 'ubuntu-22.04'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' doc
run: sbt 'project ${{ matrix.project }}; ++ ${{ matrix.scala }}; doc'

- name: Test the sbt plugin against sbt 1.x
if: matrix.project == 'rootJVM'
run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' '++ 2.12.20' sbtPlugin/test
run: sbt 'project ${{ matrix.project }}; ++ ${{ matrix.scala }}; ++ 2.12.20; sbtPlugin/scripted'

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v'))
run: mkdir -p http4s/.jvm/target http4s/.js/target otel4s/.js/target sbt-plugin/target otel4s/.jvm/target gen/rules/target core/.js/target core/.jvm/target scalajs/target model/.jvm/target model/.js/target project/target
run: mkdir -p project/target target/out/jvm/scala-2.12.20/sbt-clue target/out/jvm/scala-2.13.18/clue-generator target/out/jvm/scala-3.8.4/clue-core target/out/jvm/scala-3.8.4/clue-http4s target/out/jvm/scala-3.8.4/clue-model target/out/jvm/scala-3.8.4/clue-otel4s target/out/sjs1/scala-3.8.4/clue-core target/out/sjs1/scala-3.8.4/clue-http4s target/out/sjs1/scala-3.8.4/clue-model target/out/sjs1/scala-3.8.4/clue-otel4s target/out/sjs1/scala-3.8.4/clue-scalajs

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v'))
run: tar cf targets.tar http4s/.jvm/target http4s/.js/target otel4s/.js/target sbt-plugin/target otel4s/.jvm/target gen/rules/target core/.js/target core/.jvm/target scalajs/target model/.jvm/target model/.js/target project/target
run: tar cf targets.tar project/target target/out/jvm/scala-2.12.20/sbt-clue target/out/jvm/scala-2.13.18/clue-generator target/out/jvm/scala-3.8.4/clue-core target/out/jvm/scala-3.8.4/clue-http4s target/out/jvm/scala-3.8.4/clue-model target/out/jvm/scala-3.8.4/clue-otel4s target/out/sjs1/scala-3.8.4/clue-core target/out/sjs1/scala-3.8.4/clue-http4s target/out/sjs1/scala-3.8.4/clue-model target/out/sjs1/scala-3.8.4/clue-otel4s target/out/sjs1/scala-3.8.4/clue-scalajs

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v'))
Expand Down
22 changes: 14 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ Published under `edu.gemini`. Scala 3 (`3.8.4`) is the primary version; some mod

## Build & test commands

This is an sbt build. A vendored launcher (`sbt-launch.jar`) and a Nix flake (`flake.nix`, `direnv`) are present, but the standard entry point is `sbt`.
This is an **sbt 2.0.8** build. A vendored launcher (`sbt-launch.jar`) and a Nix flake (`flake.nix`, `direnv`) are present, but the standard entry point is `sbt`.

sbt takes a single `;`-separated argument, so quote multi-command invocations: `sbt "clean; compile"`.

- **Compile everything:** `sbt compile` (or `sbt Test/compile`)
- **Run all tests:** `sbt test`
- **Test a single module:** `sbt model/test`, `sbt core/test`, `sbt http4s/test`, etc. Cross-built modules need a platform suffix: `sbt modelJVM/test`, `sbt coreJS/test`.
- **Run all tests:** `sbt testFull`. Plain `test` is incremental and caches its success by content hash, surviving `clean`.
- **Test a single module:** `sbt model/testFull`, `sbt core/testFull`, etc. Cross-built modules need a platform suffix: `sbt modelJVM/testFull`, `sbt coreJS/testFull`.
- **Run a single test (MUnit):** `sbt 'core/testOnly clue.SomeSuite'` — MUnit also supports `*Suite -- --tests=substring` to filter test cases.
- **Format / format-check:** `sbt scalafmtAll` / `sbt scalafmtCheckAll` (scalafmt 3.x, config generated by sbt-lucuma — do not hand-edit `.scalafmt.conf`).
- **Lint (scalafix):** `sbt 'scalafixAll'` / `sbt 'scalafixAll --check'`.
- **sbt plugin tests:** `sbt sbtPlugin/scripted` (scripted tests; `sbtPlugin/test` is wired to run them and publishes the rules/model/core modules locally first).
- **Lint (scalafix):** `sbt "scalafix; Test/scalafix"` (add `--check` for the CI form).
- **sbt plugin tests:** `sbt sbtPlugin/scripted` covers the sbt 1 cross-build (Scala 2.12, the project's default), `sbt "++ 3.8.4; sbtPlugin/scripted"` the sbt 2 one. Both publish the rules/model/core modules locally first. `test` is an `InputTask`, so scripted cannot be wired into `Test / test`.

CI (`.github/workflows/ci.yml`) runs on Java 17 and 25; the build targets JDK 17 (`tlJdkRelease = 17`). This is a Typelevel-style build (`tl*` settings, `tlCrossRootProject`).

Expand Down Expand Up @@ -68,11 +70,15 @@ Per `clue-plugin-integration.md`: add `sbt-clue` to `project/plugins.sbt`, `.ena

## Version pinning (gotcha)

Different modules deliberately use different Scala versions: most are Scala 3 (`3.8.4`), `gen/rules` is **Scala 2.13.18** (Scalafix rules run on 2.13), and `sbt-plugin` cross-builds **Scala 2.12.20** (for sbt 1.13.0) and **Scala 3.8.4** (for sbt 2.0.7). Don't assume a single version across the build.
Different modules deliberately use different Scala versions: most are Scala 3 (`3.8.4`), `gen/rules` is **Scala 2.13.18** (Scalafix rules run on 2.13), and `sbt-plugin` cross-builds **Scala 2.12.20** (for sbt 1.13.0) and **Scala 3.8.4** (for sbt 2.0.8). Don't assume a single version across the build.

`ThisBuild / scalaVersion` is pinned to **3.8.4**, the version sbt 2.0.8 itself runs on, overriding sbt-lucuma's default. `sbt-clue` must match sbt's Scala or the build loader refuses its TASTy, and pinning the whole build keeps the plugin inside the CI matrix. Raising the build past sbt's Scala means giving `sbt-plugin` its own pin and its own CI step.

`(pluginCrossBuild / sbtVersion)` maps the Scala binary version of `sbt-plugin` to the sbt version. The default is Scala 2.12 / sbt 1, so a plain `publishLocal` publishes the Scala 3 libraries plus the sbt 1 plugin.

`(pluginCrossBuild / sbtVersion)` maps the Scala binary version of `sbt-plugin` to the sbt version. The default is Scala 2.12 / sbt 1, so a plain `publishLocal` publishes the Scala 3 libraries plus the sbt 1 plugin. Use `sbt '++ 3.8.4' sbtPlugin/test` for the sbt 2 side.
`genTests` sets `allowMismatchScala := true`: the suite is Scala 3 but depends on the Scala 2.13 rule jar, which sbt rejects by default.

The two sbt versions differ in API. `sbt-plugin/src/main/scala/clue/sbt/CluePlugin.scala` is shared, and the per-version shims are in `sbt-plugin/src/main/scala-2.12/` and `sbt-plugin/src/main/scala-3/`. The `sbt2-compat` plugin supplies `Def.uncached`, which every side-effecting task needs, because sbt 2 caches task results by default.
The two sbt versions differ in API. `sbt-plugin/src/main/scala/clue/sbt/CluePlugin.scala` is shared, and the per-version shims are in `sbt-plugin/src/main/scala-2.12/` and `sbt-plugin/src/main/scala-3/`. The `sbt2-compat` plugin supplies `Def.uncached`, which every side-effecting task needs, because sbt 2 caches task results.

## Conventions

Expand Down
19 changes: 11 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
lazy val V = _root_.scalafix.sbt.BuildInfo

lazy val scala3 = "3.8.4"

ThisBuild / tlBaseVersion := "0.60"
Global / onChangedBuildSource := ReloadOnSourceChanges

// sbt 2.0.8 runs on Scala 3.8.4, and `sbt-clue` has to match it.
ThisBuild / scalaVersion := scala3
ThisBuild / crossScalaVersions := List(scala3)

// The CI matrix covers Scala 3 only, which builds `sbt-clue` for sbt 2.x. Run the scripted tests
// of the sbt 1.x cross-build (Scala 2.12) in a separate step.
ThisBuild / githubWorkflowBuild += WorkflowStep.Sbt(
List("++ 2.12.20", "sbtPlugin/test"),
List("++ 2.12.20; sbtPlugin/scripted"),
name = Some("Test the sbt plugin against sbt 1.x"),
cond = Some("matrix.project == 'rootJVM'")
)
Expand Down Expand Up @@ -51,9 +57,6 @@ lazy val root = tlCrossRootProject
genTests,
sbtPlugin
)
.settings(
name := "clue"
)

lazy val model =
crossProject(JVMPlatform, JSPlatform)
Expand Down Expand Up @@ -200,6 +203,8 @@ lazy val genTests =
.enablePlugins(ScalafixTestkitPlugin, NoPublishPlugin)
.disablePlugins(ScalafixPlugin)
.settings(
// The rule is built for Scala 2.13 (scalafix runs on 2.13) but the testkit suite is Scala 3.
allowMismatchScala := true,
libraryDependencies ~= (_.filterNot(_.name == "scalafix-testkit")),
libraryDependencies ++= Settings.Libraries.ScalaFixTestkit.value
.map(_.cross(CrossVersion.constant(V.scala213))),
Expand All @@ -218,12 +223,12 @@ lazy val sbtPlugin =
.settings(
moduleName := "sbt-clue",
scalaVersion := "2.12.20",
crossScalaVersions := List("2.12.20", "3.8.4"),
crossScalaVersions 10BC8 := List("2.12.20", scala3),
scalacOptions := Nil,
(pluginCrossBuild / sbtVersion) := {
scalaBinaryVersion.value match {
case "2.12" => "1.13.0"
case _ => "2.0.7"
case _ => "2.0.8"
}
},
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % V.scalafixVersion),
Expand Down Expand Up @@ -252,8 +257,6 @@ lazy val sbtPlugin =
"coreModule" -> (core.jvm / moduleName).value
),
buildInfoOptions += BuildInfoOption.PackagePrivate,
Test / test :=
scripted.toTask("").value,
scripted := scripted
.dependsOn(
genRules / publishLocal,
Expand Down
66 changes: 32 additions & 34 deletions project/Settings.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._
import sbt.Def
import sbt.librarymanagement._
import sbt.*

object Settings {

Expand Down Expand Up @@ -30,152 +28,152 @@ object Settings {

val Cats = Def.setting(
Seq(
"org.typelevel" %%% "cats-core" % cats
"org.typelevel" %% "cats-core" % cats
)
)

val CatsEffect = Def.setting(
Seq(
"org.typelevel" %%% "cats-effect" % catsEffect
"org.typelevel" %% "cats-effect" % catsEffect
)
)

val CatsTestkit = Def.setting(
Seq(
"org.typelevel" %%% "cats-testkit" % cats % "test"
"org.typelevel" %% "cats-testkit" % cats % "test"
)
)

val Circe = Def.setting(
Seq(
"io.circe" %%% "circe-core",
"io.circe" %%% "circe-generic",
"io.circe" %%% "circe-parser",
"io.circe" %%% "circe-testing"
"io.circe" %% "circe-core",
"io.circe" %% "circe-generic",
"io.circe" %% "circe-parser",
"io.circe" %% "circe-testing"
).map(_ % circe)
)

val DisciplineMUnit = Def.setting(
Seq[ModuleID](
"org.typelevel" %%% "discipline-munit" % disciplineMUnit % "test"
"org.typelevel" %% "discipline-munit" % disciplineMUnit % "test"
)
)

val Fs2 = Def.setting(
Seq(
"co.fs2" %%% "fs2-core" % fs2
"co.fs2" %% "fs2-core" % fs2
)
)

val Grackle = Def.setting(
Seq(
"org.typelevel" %%% "grackle-core" % grackle
"org.typelevel" %% "grackle-core" % grackle
)
)

val Http4sCirce = Def.setting(
Seq(
"org.http4s" %%% "http4s-circe" % http4s
"org.http4s" %% "http4s-circe" % http4s
)
)

val Http4sCore = Def.setting(
Seq(
"org.http4s" %%% "http4s-core" % http4s
"org.http4s" %% "http4s-core" % http4s
)
)

val Http4sClient = Def.setting(
Seq(
"org.http4s" %%% "http4s-client" % http4s
"org.http4s" %% "http4s-client" % http4s
)
)

val Http4sJDKClient = Def.setting(
Seq(
"org.http4s" %%% "http4s-jdk-http-client" % http4sJDKClient
"org.http4s" %% "http4s-jdk-http-client" % http4sJDKClient
)
)

val Jawn = Def.setting(
Seq(
"org.typelevel" %%% "jawn-ast" % jawn
"org.typelevel" %% "jawn-ast" % jawn
)
)

val Kittens = Def.setting(
Seq(
"org.typelevel" %%% "kittens" % kittens
"org.typelevel" %% "kittens" % kittens
)
)

val Log4Cats = Def.setting(
Seq(
"org.typelevel" %%% "log4cats-core" % log4Cats,
"org.typelevel" %%% "log4cats-testing" % log4Cats % "test"
"org.typelevel" %% "log4cats-core" % log4Cats,
"org.typelevel" %% "log4cats-testing" % log4Cats % "test"
)
)

val Monocle = Def.setting(
Seq(
"dev.optics" %%% "monocle-core" % monocle,
"dev.optics" %%% "monocle-macro" % monocle
"dev.optics" %% "monocle-core" % monocle,
"dev.optics" %% "monocle-macro" % monocle
)
)

val MonocleLaw = Def.setting(
Seq(
"dev.optics" %%% "monocle-law" % monocle
"dev.optics" %% "monocle-law" % monocle
)
)

val MUnit = Def.setting(
Seq[ModuleID](
"org.scalameta" %%% "munit" % munit % "test"
"org.scalameta" %% "munit" % munit % "test"
)
)

val MUnitCatsEffect = Def.setting(
Seq[ModuleID](
"org.typelevel" %%% "munit-cats-effect" % munitCatsEffect % "test"
"org.typelevel" %% "munit-cats-effect" % munitCatsEffect % "test"
)
)

val Otel4s = Def.setting(
Seq(
"org.typelevel" %%% "otel4s-core" % otel4s,
"org.typelevel" %%% "otel4s-semconv" % otel4s,
"org.typelevel" %%% "otel4s-semconv-experimental" % otel4s
"org.typelevel" %% "otel4s-core" % otel4s,
"org.typelevel" %% "otel4s-semconv" % otel4s,
"org.typelevel" %% "otel4s-semconv-experimental" % otel4s
)
)

val Otel4sTestkit = Def.setting(
Seq(
"org.typelevel" %%% "otel4s-sdk-testkit" % otel4s % "test"
"org.typelevel" %% "otel4s-sdk-testkit" % otel4s % "test"
)
)

val Http4sOtel4sMiddleware = Def.setting(
Seq(
"org.http4s" %%% "http4s-otel4s-middleware-trace-client" % http4sOtel4sMiddleware
"org.http4s" %% "http4s-otel4s-middleware-trace-client" % http4sOtel4sMiddleware
)
)

val ScalaFix = Def.setting(
Seq(
"ch.epfl.scala" %%% "scalafix-core" % scalaFix
"ch.epfl.scala" %% "scalafix-core" % scalaFix
)
)

val ScalaFixTestkit = Def.setting(
Seq(
"ch.epfl.scala" %%% "scalafix-testkit" % scalaFix % "test"
"ch.epfl.scala" %% "scalafix-testkit" % scalaFix % "test"
).map(_.cross(CrossVersion.full))
)

val ScalaJsDom = Def.setting(
Seq(
"org.scala-js" %%% "scalajs-dom" % scalaJsDom
"org.scala-js" %% "scalajs-dom" % scalaJsDom
)
)

Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version = 1.13.0
sbt.version = 2.0.8
6 changes: 5 additions & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
addSbtPlugin("edu.gemini" % "sbt-lucuma-lib" % "0.16.4")
resolvers += "gemini-hlsw".at(
"https://raw.githubusercontent.com/gemini-hlsw/maven-repo/master/releases"
)

addSbtPlugin("edu.gemini" % "sbt-lucuma-lib" % "0.17-04f40af-SNAPSHOT")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.9")
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.2")
Loading
0