'Isolating scala non-cross-published library in separate module within a multi module project in SBT

Kind of an annoying issue but there is a scala library I needed for a large project that is only published for 2.12. Ideally, I would isolate this library into it's own module as to not force the entire project to be in scala 2.12 as, ultimately, this project will need to be in scala3. Is there a way to have a multi-module sbt project that would allow a scala 2.12 project to depend on a scala3 project or co-exist alongside different scala versions?

This is roughly what I have so far (the example below I am actually using 2.13.8 but the idea should be the same):

lazy val scala213 = "2.13.8"
lazy val scala212 = "2.12.15"

lazy val supportedScalaVersions = List(scala212, scala213)

val Versions = 
  new {
    val parser = "9.0.20210312"
    val catseffect = "3.3.3"
}

val commonSettings = Seq(
  scalacOptions -= "-Xfatal-warnings"
)

def full(p: Project) = p % "test->test;compile->compile"

lazy val cypherparser = (project in file("modules/cypherparser"))
  .settings(
    scalaVersion := scala212,
    crossScalaVersions := supportedScalaVersions,
    libraryDependencies ++= Seq(
      "org.opencypher" % "parser-9.0" % Versions.parser
    )
  ).dependsOn(shared)

lazy val shared = (project in file("modules/shared"))
  .settings(
    scalaVersion := scala213,
    crossScalaVersions := supportedScalaVersions,
    commonSettings,
    libraryDependencies ++= Seq(
      "org.typelevel" %% "cats-effect" % Versions.catseffect,
      "org.typelevel" %% "munit-cats-effect-3" % "1.0.7" % Test,
    )
  )

lazy val root = (project in file("."))
  .aggregate(shared, cypherparser)
  .settings(
    crossScalaVersions := Nil,
    publish / skip := false
  )

Running the above I get

sbt.librarymanagement.ResolveException: Error downloading shared:shared_2.12:0.1.0-SNAPSHOT


Solution 1:[1]

So following the comments discussion, I decided it would be worthwhile to cross-build my shared module so that cypherparser could depend on it. It turns out to be pretty wonky to do this using sbt, however I discovered the sbt-projectmatrix plugin and with a bit of experimentation using the following

ThisBuild / version      := "0.1.0-SNAPSHOT"

lazy val scala213 = "2.13.8"
lazy val scala212 = "2.12.15"

val Versions = 
  new {
    val parser = "9.0.20210312"
    val catseffect = "3.3.3"
}

val commonSettings = Seq(
  scalacOptions -= "-Xfatal-warnings"
)

def full(p: Project) = p % "test->test;compile->compile"

lazy val cypherparser = (projectMatrix in file("modules/cypherparser"))
  .dependsOn(shared)
  .settings(
    libraryDependencies ++= Seq(
      "org.opencypher" % "parser-9.0" % Versions.parser
    )
  )
  .jvmPlatform(scalaVersions = Seq(scala212))

lazy val shared = (projectMatrix in file("modules/shared"))
  .settings(
    commonSettings,
    libraryDependencies ++= Seq(
      "org.typelevel" %% "cats-effect" % Versions.catseffect,
      "org.typelevel" %% "munit-cats-effect-3" % "1.0.7" % Test,
    )
  )
  .jvmPlatform(scalaVersions = Seq(scala212, scala213))

lazy val root = (project in file("."))
  // .aggregate(cypherparser)
  .aggregate(shared.projectRefs ++ cypherparser.projectRefs: _*)
  .settings(
    publish / skip := false
  )

... I can depend on shared as I would like. Magically, I can also aggregate these modules with different versions as well (still not quite sure how/why this works, I just followed the documentation). Note: you cannot use scalaVersion in the settings else this breaks things -- only declare scala versions within the platform declaration.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mason Edmison