Skip to content
Merged
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
6 changes: 4 additions & 2 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ export default withMermaid(defineConfig<UniThemeConfig>({
text: 'Build Tooling',
items: [
{ text: 'sbt-uni Plugin', link: '/build/sbt-uni' },
{ text: 'sbt-uni-playwright Plugin', link: '/build/sbt-uni-playwright' }
{ text: 'sbt-uni-playwright Plugin', link: '/build/sbt-uni-playwright' },
{ text: 'sbt-uni-crossproject Plugin', link: '/build/sbt-uni-crossproject' }
]
}
],
Expand Down Expand Up @@ -325,7 +326,8 @@ export default withMermaid(defineConfig<UniThemeConfig>({
text: 'Build Tooling',
items: [
{ text: 'sbt-uni Plugin', link: '/build/sbt-uni' },
{ text: 'sbt-uni-playwright Plugin', link: '/build/sbt-uni-playwright' }
{ text: 'sbt-uni-playwright Plugin', link: '/build/sbt-uni-playwright' },
{ text: 'sbt-uni-crossproject Plugin', link: '/build/sbt-uni-crossproject' }
]
}
]
Expand Down
111 changes: 111 additions & 0 deletions docs/build/sbt-uni-crossproject.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# sbt-uni-crossproject Plugin

`sbt-uni-crossproject` lets one source tree cross-build for **JVM, Scala.js, and
Scala Native**. It is a small, uni-owned re-implementation of
[portable-scala/sbt-crossproject](https://github.com/portable-scala/sbt-crossproject),
which has not been ported to sbt 2.x.

It provides just the surface uni uses: `crossProject(...)` with the
**`CrossType.Pure`** layout — shared sources in `<base>/src`, platform-specific
code in `<base>/.jvm`, `.js`, and `.native`.

::: warning sbt 2.x only
The plugin targets **sbt 2.x** (its metabuild runs on Scala 3). It is not
published for sbt 1.x — if you are on sbt 1.x, use the original
`sbt-crossproject` instead.
:::

::: tip One plugin, all three platforms
Unlike upstream's three separate artifacts (`sbt-crossproject` +
`sbt-scalajs-crossproject` + `sbt-scala-native-crossproject`), this is a single
plugin that depends on both `sbt-scalajs` and `sbt-scala-native` and defines all
three platforms.
:::

## Installation

In `project/plugins.sbt`, add the Scala.js and Scala Native plugins (for their
`scalaJSLinkerConfig` / `nativeConfig` settings) and this one:

```scala
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.22.0")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.12")
addSbtPlugin("org.wvlet.uni" % "sbt-uni-crossproject" % "__UNI_VERSION__")
```

The plugin is triggered automatically, so `crossProject`, the platforms, and
`CrossType` are available in `build.sbt` without an explicit `enablePlugins`.

## Usage

```scala
lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("core"))
.settings(/* shared settings */)
.jvmSettings(/* JVM-only */)
.jsSettings(/* Scala.js-only */)
.nativeSettings(/* Scala Native-only */)

lazy val app = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("app"))
.dependsOn(core, core % "test")

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

In sbt, it is more idiomatic and common to use the Test configuration object (e.g., core % Test) rather than the string literal "test" (e.g., core % "test"). Since the scripted tests for this plugin also use core % Test, updating this example to use Test improves consistency and follows standard sbt practices.

Suggested change
.dependsOn(core, core % "test")
.dependsOn(core, core % Test)

```

`crossProject` must be assigned directly to a `val` — it derives the project id
from the **val name** (just like sbt's own `project`). So `val core = …` defines
the sub-projects `coreJVM`, `coreJS`, and `coreNative`, which you reach as
`core.jvm`, `core.js`, and `core.native` (e.g. when aggregating or depending on a
single platform):

```scala
lazy val root = project
.in(file("."))
.aggregate(core.jvm, core.js, core.native, app.jvm, app.js, app.native)

sbt> coreJVM/test
```

::: tip sbt 2.x dependencies
On sbt 2.x, `%%` already encodes the platform suffix (`_sjs1` for Scala.js,
`_native0.5` for Native), so use `%%` (not the old `%%%`) for your cross-platform
library dependencies inside `.settings(...)`.
:::

## Directory layout (`CrossType.Pure`)

```
core
├── .js // core/.js/src/main/scala — Scala.js-only sources
├── .jvm // core/.jvm/src/main/scala — JVM-only sources
├── .native // core/.native/src/main/scala — Scala Native-only sources
└── src // core/src/main/scala — shared across all platforms
// core/src/main/scala-3, scala-2.13, ... — shared, version-specific
```

Shared sources live under `<base>/src`. Scala-version-specific shared sources
(`src/main/scala-3`, `src/main/scala-2.13`, …) are picked up too when `crossPaths`
is enabled (the default). The same structure applies under `src/test` for tests.

## Supported API

The cross-project value supports the methods uni's own build relies on:

| Method | Description |
| ------------------------------------------------- | ------------------------------------------------------------- |
| `.crossType(CrossType.Pure \| CrossType.Full)` | Choose the directory layout (default `Pure`) |
| `.in(file("..."))` | Set the cross-project base directory |
| `.settings(...)` | Settings applied to every platform project |
| `.jvmSettings` / `.jsSettings` / `.nativeSettings`| Settings applied to one platform only |
| `.configure(...)` / `.jvmConfigure` / `.jsConfigure` / `.nativeConfigure` | Apply `Project => Project` transforms |
Comment on lines +100 to +101

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 methods .jvmSettings, .jsSettings, .nativeSettings, .jvmConfigure, .jsConfigure, and .nativeConfigure all accept arguments (e.g., settings definitions or project transformers). Adding parentheses/parameter placeholders (like (...)) to these method names in the table makes them consistent with other methods like .settings(...) and .configure(...).

Suggested change
| `.jvmSettings` / `.jsSettings` / `.nativeSettings`| Settings applied to one platform only |
| `.configure(...)` / `.jvmConfigure` / `.jsConfigure` / `.nativeConfigure` | Apply `Project => Project` transforms |
| .jvmSettings(...) / .jsSettings(...) / .nativeSettings(...) | Settings applied to one platform only |
| .configure(...) / .jvmConfigure(...) / .jsConfigure(...) / .nativeConfigure(...) | Apply Project => Project transforms |

| `.enablePlugins(...)` / `.disablePlugins(...)` | Enable/disable sbt plugins on every platform project |

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 CrossProject class also exposes a public .configs(...) method (defined on line 55 of CrossProject.scala) to add configurations to all platform projects. It would be beneficial to document this method in the API table to ensure completeness.

Suggested change
| `.enablePlugins(...)` / `.disablePlugins(...)` | Enable/disable sbt plugins on every platform project |
| .enablePlugins(...) / .disablePlugins(...) | Enable/disable sbt plugins on every platform project |
| .configs(...) | Add configurations to every platform project |

| `.dependsOn(other, other % "test")` | Depend on another cross-project, optionally scoped to a config|

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

Using the Test configuration object is more idiomatic in sbt than the string literal "test". Updating this row to use Test aligns with standard sbt practices.

Suggested change
| `.dependsOn(other, other % "test")` | Depend on another cross-project, optionally scoped to a config|
| .dependsOn(other, other % Test) | Depend on another cross-project, optionally scoped to a config |

| `.aggregate(other)` | Aggregate another cross-project |
| `.jvm` / `.js` / `.native` | The underlying single-platform `Project` for that platform |

::: tip Scope
Only the `CrossType.Pure` (and `CrossType.Full`) layouts are supported. Upstream
features uni does not use — partially-shared sources, the `Dummy` layout, and
`withoutSuffixFor` — are intentionally omitted.
:::