Need Advice on Best Practices for Cross-Version Scala Library Compatibility

Hello there,

I am seeking advice on best practices for guaranteeing compatibility across several Scala versions, especially 2.13 and 3.x, as I am currently maintaining a small open-source Scala library. Despite reading some material and forum posts, I still have some questions.

When dealing with cross-compilation between 2.13 and 3.x, are tools like sbt-crossproject typically advised, or are there more contemporary or community-preferred alternatives?

When publishing cross-version libraries, how do contributors usually deal with changes in syntax or behaviour? Are macros and conditional compilation the most practical options?

Are there any ongoing initiatives or conversations about tools enhancements that would facilitate this process for maintainers?

I’m eager to bring my library up to community standards, and I’d want to know about the workflows of seasoned contributors or maintainers. I would greatly appreciate any advice, illustrations, or references to repositories that adhere to best principles!

Thanks in advance for your help and assistance.

2 Likes
  1. Newer alternative to sbt-crossproject is sbt-projectmatrix (optionally supplemented with sbt-commandmatrix). It works on a similar principles:

    • separate directories for shares and Scala version/platform code
    • separate projects per each platform but, contrary to crossproject, also for each Scala version (so +[scala version] syntax becomes obsolete)
    • cross projects can depend on one another
    • contrary to crossproject you don’t have to use val aJvm = a.jvm val aJs = a.js syntax
  2. You should avoid writing code that depends on changing syntax. You can utilize compiler flags and compiler plugins to get the code identical on all supported versions wherever possible.

    Where it’s impossible you have to write a file with separate implementation in each version specific directory and define in a way that could be referred to in platform agnostic files (e.g. separate implicit that could be used in each file, well, implicitly, an extension method to align the differences in API, extending a trait defined separately for each platform etc)

  3. As far as I can tell, you han flags to make cross-compiling with Scala 3 easier, and you have options to align the behavior between Scala 2 kind-projector and build-in type-lambdas on Scala 3.

    Other than that compiler and tooling maintainers try to incentivize community to migrate towards Scala 3, because projects build with Scala 3 would not require releasing a new artifact with every minor version update, cross-compiling for it, migrating to new Scala version, etc. So while Scala 2.13 would be supported indefinitely, for existing projects that would never migrate or migrate in some unforeseeable future, there are no initiatives that would promote creating new Scala 2 libraries, since it would be a mixed message.

  4. Workflows… well, it good to have configured:

    • releasing artifacts automatically, e.g. normal commits on SNAPSHOTs and tags as releases
    • configuring MiMa checking to preserve backward compatibility
    • building artifacts on the lowest supported JVM version (code compiled for Java 21 cannot be run on Java 8)
    • adding repository to Scala Steward - and actually merging the PRs!
    • enabling tons on compiler linters, Scalafmt formatting checking, (maybe even Scalafix migrations, unless they meddle with the code that should compile against older Scala as well)
    • testing stuff, so that Community Build could pick up regressions
    • CONTRIBUTING.md, documentation (ideally one that checks whether the code samples compile), optionally sbt-welcome, to have some onboarding for contributors
2 Likes