Skip to content

feature: Add minimal sbt 2.x crossproject plugin (sbt-uni-crossproject)#623

Merged
xerial merged 3 commits into
mainfrom
feature/sbt-uni-crossproject
Jun 30, 2026
Merged

feature: Add minimal sbt 2.x crossproject plugin (sbt-uni-crossproject)#623
xerial merged 3 commits into
mainfrom
feature/sbt-uni-crossproject

Conversation

@xerial

@xerial xerial commented Jun 30, 2026

Copy link
Copy Markdown
Member

Why

portable-scala/sbt-crossproject (and its sbt-scalajs-crossproject / sbt-scala-native-crossproject companions) have not been ported to sbt 2.x, which blocks uni's eventual sbt 2.x migration. Uni only ever uses the CrossType.Pure layout and a small slice of the crossProject API, so this re-implements just that surface as a uni-owned plugin rather than waiting for an upstream port.

What

A new standalone sbt 2.x build sbt-uni-crossproject/, modelled on sbt-uni / sbt-uni-playwright, publishing one plugin org.wvlet.uni:sbt-uni-crossproject:

  • crossProject(JVMPlatform, JSPlatform, NativePlatform) with .crossType(CrossType.Pure), .in, .settings, .jvmSettings/.jsSettings/.nativeSettings, .configure variants, .enablePlugins, .dependsOn (incl. % Test), .aggregate, and .jvm/.js/.native accessors.
  • Single plugin (vs. upstream's three artifacts) depending on both sbt-scalajs 1.22.0 and sbt-scala-native 0.5.12 (both published for sbt 2.x as _sbt2_3), defining all three platforms.
  • The crossProject macro derives the id from the enclosing val name by replicating sbt 2.x's own KeyMacro.definingValName, so val core = ... yields coreJVM/coreJS/coreNative.

See adr/2026-06-30-sbt-uni-crossproject.md for the design and the non-obvious traps (the val-name macro, why new CrossProject(...) is required internally, the given Conversion[Builder, CrossProject] build trigger, load-time baseDirectory resolution for shared sources).

Validation

scripted crossproject/basic cross-builds a CrossType.Pure core + dependent app across all three platforms: compiles JVM/JS/Native (Native to NIR only — no clang/Node needed), compiles shared Test sources, asserts jvmSettings/jsSettings/nativeSettings route only to their own platform, and runs the JVM app whose runtime assertion proves the shared, per-platform, and scala-3 cross-version sources all resolve. Wired into CI as test_sbt_uni_crossproject; released by release-sbt-uni-crossproject.yml.

Note: the main uni build stays on sbt 1.x with upstream sbt-crossproject for now — this plugin unblocks the migration but does not perform it.

🤖 Generated with Claude Code

portable-scala/sbt-crossproject has not been ported to sbt 2.x, which blocks
uni's eventual sbt 2.x migration. Uni only uses the CrossType.Pure layout and a
small slice of the crossProject API, so re-implement just that surface as a
uni-owned plugin rather than wait for an upstream port.

Single plugin (not upstream's three artifacts) depending on sbt-scalajs and
sbt-scala-native, defining all three platforms. The crossProject macro replicates
sbt 2.x's own KeyMacro.definingValName to derive ids from the enclosing val name.
Validated by a scripted test that cross-builds JVM/JS/Native with shared,
per-platform, and scala-3 cross-version sources plus a `core % Test` dependency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the feature New feature label Jun 30, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces sbt-uni-crossproject, a minimal, uni-owned sbt 2.x re-implementation of portable-scala/sbt-crossproject supporting the CrossType.Pure layout. It includes the core platform definitions, a macro to derive project IDs from enclosing val names, and builder conversions, along with a scripted test suite for validation. The review feedback suggests minor code simplifications in CrossProject.scala, specifically simplifying the ClasspathDependency reference and removing an unnecessary vararg splice when applying settings.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +66 to +70
platform ->
(ClasspathDep
.ClasspathDependency(LocalProject(project.id), dep.configuration): ClasspathDep[
ProjectReference
])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The qualification ClasspathDep.ClasspathDependency is redundant and can be simplified. Since ClasspathDependency is a standard type alias and constructor helper in the sbt package (which is imported via import sbt.*), you can reference it directly. This also removes the need for the explicit type annotation : ClasspathDep[ProjectReference] as the compiler can infer it automatically.

          platform -> ClasspathDependency(LocalProject(project.id), dep.configuration)

val projectID = id + platform.sbtSuffix
val project = platform.enable(
Project(projectID, _crossType.platformDir(base, platform))
.settings((name := id) +: shared*)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The vararg splice * is unnecessary here because Seq[Setting[?]] is already a SettingsDefinition and can be passed directly to settings. Removing the * splice simplifies the expression and avoids any potential parsing ambiguity.

            .settings((name := id) +: shared)

…roject

2.0.1 is the latest final release; RC10 was carried over from the sibling
plugin builds. publishLocal + scripted verified on 2.0.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add sbt-scalafmt (2.6.1) to the build, format the sources to the mirrored
.scalafmt.conf style, and run scalafmtCheckAll in CI before scripted — keeping
this build consistent with sbt-uni and sbt-uni-playwright. The crossProject
macro still expands and the scripted test still passes after formatting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
xerial added a commit that referenced this pull request Jun 30, 2026
## Why

The `sbt-uni` and `sbt-uni-playwright` sbt 2.x plugin builds were pinned
to `2.0.0-RC10`. **sbt 2.0.1** (final) is now available, so move them to
it. (Follow-up to #623, which uses 2.0.1 for the new
`sbt-uni-crossproject` plugin.)

## What

- Bump `sbt.version` to `2.0.1` in all seven `build.properties` across
both builds (plugin metabuilds + their `sbt-test` projects).
- Refresh the now-stale `sbt-scalafmt` note in
`sbt-uni-playwright/project/plugin.sbt`: its "needs RC11+" blocker no
longer applies on 2.0.1, so re-adding scalafmt is now possible (left as
a follow-up to keep this change focused).

## Validation

Both builds compile on 2.0.1:
- `sbt-uni-playwright` (`uni-jsenv-playwright` + plugin) — clean.
- `sbt-uni` — compiles against a locally published `uni`; only the two
pre-existing `thisProjectRef`-excluded-from-cache lint warnings remain
(not introduced by this bump).

The full scripted suites for both run in CI (`test_sbt_plugin`,
`test_sbt_uni_playwright`).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@xerial xerial merged commit 78a33f7 into main Jun 30, 2026
15 checks passed
@xerial xerial deleted the feature/sbt-uni-crossproject branch June 30, 2026 19:47
xerial added a commit that referenced this pull request Jun 30, 2026
## What

Adds a **Build Tooling** reference page for `sbt-uni-crossproject`
(merged in #623), mirroring the `sbt-uni-playwright` page, and links it
in both duplicated sidebars in `config.mts`.

Covers: installation (with `__UNI_VERSION__`), the `crossProject(...)`
usage and val-name → `coreJVM`/`coreJS`/`coreNative` derivation, the
`CrossType.Pure` directory layout, the `%%`-on-sbt-2.x note, and a
supported-API table — all verified against the merged source.

## Validation

`pnpm docs:build` succeeds (no dead links).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
xerial added a commit that referenced this pull request Jun 30, 2026
## Why

The main build ran on **sbt 1.x**. The blockers are now gone:
- `sbt-scalajs-crossproject` + `sbt-scala-native-crossproject` →
**`sbt-uni-crossproject`** (#623)
- third-party `scala-js-env-playwright` → **`uni-jsenv-playwright`**
(#613)
- `sbt-revolver` (unused in the build, no sbt 2.x release) →
**`sbt-uni`**'s `uniRestart`/`uniStop`/`uniStatus`

## What

- `project/build.properties` → **sbt 2.0.1**; metabuild plugins swapped
(see table in the ADR). Dropped unused `sbt-buildinfo` and the
now-built-in `addDependencyTreePlugin`; bumped `sbt-pack` 0.23 → 1.0.0.
- Because `sbt-uni-crossproject` reproduces the `crossProject` /
`CrossType.Pure` API, the project definitions are **unchanged**. Only
sbt-2.x source adjustments:
- **root → `uni-root`** — sbt 2.x derives output dirs from the project
*name*, which collided with the `uni` library project (*"Overlapping
output directories"*).
- **`%%%` → `%%`** everywhere; the platform-less
`scalajs-test-interface_2.13` named directly with a single `%` (in a JS
project, any cross-version injects the unwanted `_sjs1`).
- **`Test / jsEnv` wrapped in `Def.uncached(...)`** (JSEnv isn't
serializable; sbt 2.x caches settings).
- `import scala.language.implicitConversions` for `core.jvm` used as
`ProjectReference`; `xs: _*` → `xs*`; infix `classifier` →
`.classifier(...)`.
- Docs: CLAUDE.md + the book's cross-build install snippet updated to
the sbt 2.x / `sbt-uni-crossproject` approach. New ADR
`adr/2026-06-30-sbt2-main-build-migration.md`.
- **CI needs no change** — the repo's `./sbt` runner reads
`build.properties` (→ 2.0.1) and `code_format` uses the kept
`sbt-scalafmt`.

## Validation (all on sbt 2.0.1, run locally)

| Platform | Result |
|---|---|
| JVM (`projectJVM/test`) | 1655 passed, 4 pending · +39 netty/book |
| JS / NodeJSEnv | uniJS ~1420 · testJS 71 passed |
| JS / Playwright Chromium (`domTest`) | 374 passed |
| Native (`projectNative/test`) | uniNative 1417 (+9 pending) ·
testNative 71 passed |

`scalafmtCheckAll` and `pnpm docs:build` pass. (`uni-core` reports 0
tests on every platform — it has no `src/test`, not a regression.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant