Skip to content
Open
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
137 changes: 137 additions & 0 deletions docs/users/cross-building.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
id: cross-building
title: Cross-building setups
---

Cross-building the same codebase with Scala 2.13 and Scala 3.3+ is common, but
it collides with how Scalafix resolves rules and compiler options. A rule or
build-tool flag that only works on Scala 2 can crash Scalafix when the Scala 3
compilation unit is processed, and vice versa. Split your configuration per
Scala version and let the build tool wire the right file.

## Why separate configs are required today

* Scala 2 only scalac flags such as `-Ywarn-unused-import` or SemanticDB options
using `-P:semanticdb` belong in the build tool. Those settings differ per
target and cannot be inferred by Scalafix.
* Some built-in or community rules rely on compiler symbols present only in a
single Scala major version.
* The CLI currently ingests a single `.scalafix.conf`; there is no per-target
override like sbt’s `CrossVersion`.

## Limitations

Scalafix cannot conditionally toggle rules or scalac options based on the input
Scala version. Provide a separate config file per Scala version and have sbt,
mill, or your CLI invocation choose the right file along with the correct
compiler flags. See [issue #1747](https://github.com/scalacenter/scalafix/issues/1747)
for additional background.
Comment on lines +6 to +28
Copy link
Collaborator

Choose a reason for hiding this comment

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

I still find this quite hard to follow since it mixes context/rationale, problem and solution space. Here is an attempt at doing this with one paragraph for each:

In a codebase cross-building against several binary versions of Scala, it is advised to run Scalafix several times, for each Scala version. That allows to cover version-specific files and to benefit from compiler diagnostics from several Scala versions for common files.

However, since rules can be specific or only support a subset of features for a given Scala version, a different set of rules or rule features might have to be selected for each run, in order to avoid failing the entire run and preventing relevant rules to run.

Since nothing in the current API allows rules to advertize their limitations programmatically to Scalafix, this page explains how to do it manually.


## File layout suggestion

Keep shared defaults in one file and let version-specific files `include` it via
the HOCON `include` syntax. One convenient layout is:

```
.
└── scalafix/
├── common.conf
├── scala2.conf
└── scala3.conf
```
Comment on lines +33 to +41
Copy link
Collaborator

Choose a reason for hiding this comment

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

In most OSS projects I have seen, files are at the root, where .scalafix.conf is looked up by default - I think adding an extra directory complicates discovery.

https://github.com/search?q=.scalafix-scala3.conf&type=code

What about flatteing the layour here and across the doc?

  • .scalafix-common.conf
  • .scalafix-scala2.conf
  • .scalafix-scala3.conf


`common.conf`
```scala
rules = [
DisableSyntax,
OrganizeImports
]

DisableSyntax {
noFinalize = true
}
```

`scala2.conf`
```scala
include "common.conf"

rules += RemoveUnused

// Scala 2 only rule settings
RemoveUnused {
imports = true
}
```

`scala3.conf`
```scala
include "common.conf"

rules += LeakingImplicitClassVal

// Scala 3 specific tweaks go here
OrganizeImports {
groupedImports = Keep
}
```
Comment on lines +55 to +77
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are these configuration files solving real limitations as described in the intro? AFAIK RemoveUnused.imports is fine with Scala 3 and LeakingImplicitClassVal is not related to Scala 3 (more to Scala 3 actually).


### Multiple include files

You may split out even more granular snippets (for example `linting.conf`,
`rewrites.conf`) and include them from both `scala2.conf` and `scala3.conf`. The
HOCON syntax supports nested includes, so feel free to create the hierarchy that
matches your team conventions.

## Selecting the right config in sbt
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
## Selecting the right config in sbt
## Selecting the right configuration file
### sbt


Point `scalafixConfig` at the version-specific file, typically inside a helper
setting applied to all cross-built projects:

```scala
import scalafix.sbt.ScalafixPlugin.autoImport._

lazy val commonSettings = Seq(
scalafixConfig := {
val base = (ThisBuild / baseDirectory).value / "scalafix"
val file =
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _)) => base / "scala3.conf"
case _ => base / "scala2.conf"
}
Some(file)
}
)

lazy val core = project
.settings(commonSettings)
.settings(
scalaVersion := "3.3.3",
crossScalaVersions := Seq("2.13.14", "3.3.3")
)
```

For builds that already differentiate per configuration (`Compile`, `Test`,
`IntegrationTest`), you can set `Compile / scalafixConfig` and
`Test / scalafixConfig` separately if the inputs diverge.

## Command-line usage

When invoking the CLI directly, pass the desired config with `--config`:

```
scalafix --config scalafix/scala2.conf
scalafix --config scalafix/scala3.conf
```

Your CI job can loop over each target Scala version, selecting the matching
config before running `scalafix --check`.

## Recommendations

* Keep rules that truly work on both versions inside `common.conf`.
* Manage scalac and SemanticDB flags inside the build tool per target; Scalafix
configs focus solely on rules and rule-specific settings.
* Document in `README.md` or `CONTRIBUTING.md` which rules run on which Scala
version to reduce confusion for new contributors.

Comment on lines +130 to +137
Copy link
Collaborator

Choose a reason for hiding this comment

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

the first item is already mentioned above and the 2 last are not related to scalafix, so I would simply drop that

Suggested change
## Recommendations
* Keep rules that truly work on both versions inside `common.conf`.
* Manage scalac and SemanticDB flags inside the build tool per target; Scalafix
configs focus solely on rules and rule-specific settings.
* Document in `README.md` or `CONTRIBUTING.md` which rules run on which Scala
version to reduce confusion for new contributors.

3 changes: 3 additions & 0 deletions website/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
"users/configuration": {
"title": "Configuration"
},
"users/cross-building": {
"title": "Cross-building setups"
},
"users/installation": {
"title": "Installation"
},
Expand Down
7 changes: 6 additions & 1 deletion website/sidebars.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"users": {
"Usage": ["users/installation", "users/configuration", "users/suppression"],
"Usage": [
"users/installation",
"users/configuration",
"users/cross-building",
"users/suppression"
],
"Rules": [
"rules/overview",
"rules/DisableSyntax",
Expand Down