SBT make one project compile with only one version of scala - scala

In sbt with a multiproject setup, is there a way to make sure that one of the projects compiles with only version of Scala. Eg
/foo (scala-2.13.8)
/bar (scala-2.12.15, scala-2.13.8, scala-3.1.1)
/baz (scala-2.12.15, scala-2.13.8, scala-3.1.1)
So basically I want bar and baz to cross compile where as foo to compile only for scala-2.13.8
Is this possible? What would the build config look like?

It is possible if you don't have dependencies from baz or bar to foo.
For example, the following build.sbt will only compile foo with version 2.13.8 when you call sbt +compile
ThisBuild / scalaVersion := "2.13.8"
ThisBuild / crossScalaVersions := Seq("2.12.15", "2.13.8", "3.1.1")
val foo = project.settings(
crossScalaVersions := Seq("2.13.8")
)
val bar = project
val baz = project

Related

Can I create a proto jar for scalaVersion 2.11/2.12 and use it within the same sbt under different sub-project?

I have a set of .proto files (protobuf) which I generate java from using scalapb. I also have in the same sbt 2 sub-projects, one is scalaVersion 2.11 compatible (can't upgrade it to 2.12 due to missing packages) and the other one is scala 2.12.
I created a sub-project to hold my proto, and by default 2.12 is used and my 2.12 sub-project can use it, but my 2.11 can't.
I set the crossScalaVersions to 2.11/2.12, I compiled my project with both, which passed, but then even then I was unable to get the 2.11 sub-project to find that code.
I am "wondering" if that is something supported, or if there is a track I could use a single location to hold my .proto yet have my 2 sub-projects using the same sbt file use those.
lazy val scala212 = "2.12.13"
lazy val scala211 = "2.11.12"
lazy val supportedScalaVersion = List(scala212, scala211)
ThisBuild / scalaVersion := scala212
lazy val root = (project in file("."))
.aggregate(proto, subproject1, subproject2)
.settigns(
crossScalaVersions := Nil,
publish / skip := true
)
lazy val proto = project
.settings(
crossScalaVersions := supportedScalaVersions,
name := "proto",
libraryDependencies += "com.trueaccord.scalapb" %% "scalapb-runtime" % com.trueaccord.scalapb.compiler.Version.scalapbVersion % "protobuf",
PB.targets in Compile := Seq(
scalapb.gen(grpc = false) -> (sourceManaged in Compile).value / "protobuf"
)
)
lazy val subproject1 = project
.dependsOn(proto)
lazy val subproject2 = project
.settings(
scalaVersion := scala211
)
.dependsOn(proto)
So, from the above, if I do sbt "+ proto" I can compile both versions. If I do sbt subproject1/compile it works too. Using sbt subproject2/compile fails indicating that it cannot find the 2.11:proto jar file.
Either, I would like the above somehow to work nicely, or any other trick that I could generate the code from the same proto location but within subproject1/subproject2 would be appreciated.
You could try the sbt-projectmatrix plugin:
https://github.com/sbt/sbt-projectmatrix
The idea is to have separate sbt subprojects for the different Scala versions, so you can simply reference the relevant subproject when calling dependsOn.
I think this plugin is going to end up in sbt some day as it's a much better solution in general than the current built-in stateful cross compilation support, and it's developed by Eugene Yokota, who is also an sbt developer.

Evaluating a task using a different scalaVersion than the current

In a multiproject build, I have two projects, A and B, that are cross-compiled to Scala 2.12 and Scala 2.13. I would like to add a task to project A that depends on B's Scala 2.12 classpath (B / Compile / fullClasspath) regardless of the scala version used in A:
ThisBuild / scalaVersion := "2.13.2"
ThisBuild / crossScalaVersions := Seq("2.13.2", "2.12.10")
val foo = taskKey[Unit]("foo-task")
lazy val B = project.in(file("B"))
lazy val A = project.in(file("A")).settings(
foo := {
println((B / Compile / fullClasspath).value)
}
)
As is, running A/foo would print the B's scala-2.13 classpath. How can this be changed so that B's Scala 2.12 classpath is printed?
Context: during source generation for A, I would like to execute code from B inside SBT, so I needed the 2.12 classes of B, regardless of the version of A being compiled.
You can use sbt-cross instead of crossScalaVersions, then you'll have a separate subproject for each scala version.
https://github.com/lucidsoftware/sbt-cross

How to force sbt to resolve dependencies with scalaVersion compatible with the dependent project

Consider the following contents of some build.sbt and no source code in particular:
lazy val y = (project in file("y"))
.settings(
scalaVersion := "2.11.8",
)
.dependsOn(x)
lazy val x = (project in file("x"))
.settings(
crossScalaVersions := Seq("2.11.8", "2.12.6")
)
Here, sbt y/compile, fails with
sbt.librarymanagement.ResolveException: unresolved dependency: x#x_2.11;0.1.0-SNAPSHOT: not found
which is explained by sbt "show y/fullResolvers" referring to
Raw(ProjectResolver(inter-project, mapped: x#x_2.12;0.1.0-SNAPSHOT))
Why does it refer to _2.12? I suppose that since sbt "show y/allDependencies" lists
x:x:0.1.0-SNAPSHOT
as the dependency, which misses the scalaVersion and the _2.12, the sbt does not understand (unlike with external library dependencies) what scala version to look for. The _2.12 seems to be derived from the ThisBuild or Global value of scalaVersion which defaults to some 2.12.x, changing the scalaVersion at one of the scopes fixes the problem for the simplified example above, but if we add
lazy val z = (project in file("z"))
.settings(
scalaVersion := "2.12.6",
)
.dependsOn(x)
then what ever value we choose for scalaVersion in the Global / ThisBuild scope, either y or z will fail to build.
I am aware of sbt "+ y/compile" but why doesn't sbt y/compile correctly defaults to matching scalaVersion between the dependent project and the dependency? Or can it be made to pick the right scalaVersion when resolving dependencies?
sbt "show sbtVersion" gives me 1.2.1 but I have seen the same problem across different versions, no previous explanation online helped me understand / circumvent the problem.
You need to define a Scala version for the project. And it should be the most common version. You can see a general example at Cross building a project statefully. In your example you can do:
ThisBuild / scalaVersion := "2.11.8"
lazy val y = (project in file("y"))
.settings(
scalaVersion := "2.11.8"
)
.dependsOn(x)
lazy val x = (project in file("x"))
.settings(
crossScalaVersions := Seq("2.11.8", "2.12.6")
)
Or if for example you go with Scala 2.12 as the default, you can do:
ThisBuild / scalaVersion := "2.12.6"
lazy val y = (project in file("y"))
.settings(
scalaVersion := "2.12.6"
)
.dependsOn(x)
lazy val x = (project in file("x"))
.settings(
crossScalaVersions := Seq("2.11.8", "2.12.6")
)
Regarding the z requirement, I don't think it is possible. I think all subprojects should support the Scala version of the project.

Intertwined dependencies between sbt plugin and projects within multi-project build that uses the plugin itself

I'm developing a library that includes an sbt plugin. Naturally, I'm using sbt to build this (multi-project) library. My (simplified) project looks as follows:
myProject/ # Top level of library
-> models # One project in the multi-project sbt build.
-> src/main/scala/... # Defines common models for both sbt-plugin and framework
-> sbt-plugin # The sbt plugin build
-> src/main/scala/...
-> framework # The framework. Ideally, the sbt plugin is run as part of
-> src/main/scala/... # compiling this directory.
-> project/ # Multi-project build configuration
Is there a way to have the sbt-plugin defined in myProject/sbt-plugin be hooked into the build for myProject/framework all in a unified build?
Note: similar (but simpler) question: How to develop sbt plugin in multi-project build with projects that use it?
Is there a way to have the sbt-plugin defined in myProject/sbt-plugin be hooked into the build for myProject/framework all in a unified build?
I have a working example on Github eed3si9n/plugin-bootstrap. It's not super pretty, but it kind of works. We can take advantage of the fact that sbt is recursive.
The project directory is another build inside your build, which knows how to build your build. To distinguish the builds, we sometimes use the term proper build to refer to your build, and meta-build to refer to the build in project. The projects inside the metabuild can do anything any other project can do. Your build definition is an sbt project.
By extension, we can think of the sbt plugins to be library- or inter-project dependencies to the root project of your metabuild.
meta build definition (project/plugins.sbt)
In this example, think of the metabuild as a parallel universe or shadow world that has parallel multi-build structure as the proper build (root, model, sbt-plugin).
To reuse the source code from model and sbt-plugin subprojects in the proper build, we can re-create the multi-project build in the metabuild. This way we don't need to get into the circular dependency.
addSbtPlugin("com.eed3si9n" % "sbt-doge" % "0.1.5")
lazy val metaroot = (project in file(".")).
dependsOn(metaSbtSomething)
lazy val metaModel = (project in file("model")).
settings(
sbtPlugin := true,
scalaVersion := "2.10.6",
unmanagedSourceDirectories in Compile :=
mirrorScalaSource((baseDirectory in ThisBuild).value.getParentFile / "model")
)
lazy val metaSbtSomething = (project in file("sbt-plugin")).
dependsOn(metaModel).
settings(
sbtPlugin := true,
scalaVersion := "2.10.6",
unmanagedSourceDirectories in Compile :=
mirrorScalaSource((baseDirectory in ThisBuild).value.getParentFile / "sbt-plugin")
)
def mirrorScalaSource(baseDirectory: File): Seq[File] = {
val scalaSourceDir = baseDirectory / "src" / "main" / "scala"
if (scalaSourceDir.exists) scalaSourceDir :: Nil
else sys.error(s"Missing source directory: $scalaSourceDir")
}
When sbt loads up, it will build metaModel and metaSbtSomething first, and use metaSbtSomething as a plugin to your proper build.
If you have any other plugins you need you can just add it to project/plugins.sbt normally as I've added sbt-doge.
proper build (build.sbt)
The proper build looks like a normal multi-project build.
As you can see framework subproject uses SomethingPlugin. Important thing is that they share the source code, but the target directory is completely separated, so there are no interference once the proper build is loaded, and you are changing code around.
import Dependencies._
lazy val root = (project in file(".")).
aggregate(model, framework, sbtSomething).
settings(inThisBuild(List(
scalaVersion := scala210,
organization := "com.example"
)),
name := "Something Root"
)
// Defines common models for both sbt-plugin and framework
lazy val model = (project in file("model")).
settings(
name := "Something Model",
crossScalaVersions := Seq(scala211, scala210)
)
// The framework. Ideally, the sbt plugin is run as part of building this.
lazy val framework = (project in file("framework")).
enablePlugins(SomethingPlugin).
dependsOn(model).
settings(
name := "Something Framework",
crossScalaVersions := Seq(scala211, scala210),
// using sbt-something
somethingX := "a"
)
lazy val sbtSomething = (project in file("sbt-plugin")).
dependsOn(model).
settings(
sbtPlugin := true,
name := "sbt-something",
crossScalaVersions := Seq(scala210)
)
demo
In the SomethingPlugin example, I'm defining something task that uses foo.Model.x.
package foo
import sbt._
object SomethingPlugin extends AutoPlugin {
def requries = sbt.plugins.JvmPlugin
object autoImport {
lazy val something = taskKey[Unit]("")
lazy val somethingX = settingKey[String]("")
}
import autoImport._
override def projectSettings = Seq(
something := { println(s"something! ${Model.x}") }
)
}
Here's how we can invoke something task from the build:
Something Root> framework/something
something! 1
[success] Total time: 0 s, completed May 29, 2016 3:01:07 PM
1 comes from foo.Model.x, so this demonstrates that we are using the sbt-something plugin in framework subproject, and that the plugin is using metaModel.

SBT: How to set common scala version for multiproject

I have a multi-SBT-project in IntellJ Idea. My SBT file in the root dir looks like this:
name := "PlayRoot"
version := "1.0"
lazy val shapeless_learn = project.in(file("shapeless_learn")).dependsOn(common)
lazy val scalaz_learn = project.in(file("scalaz_learn")).dependsOn(common)
lazy val common = project.in(file("common"))
lazy val root = project.in(file(".")).aggregate(common, shapeless_learn, scalaz_learn)
scalaVersion := "2.11.7"
Then I have folders for each of the projects: ./common, ./shapeless_learn, ./scalaz_learn and each has its own build.sbt there. But for some reason I require to put in each of the subproject build.sbt files the line scalaVersion := "2.11.7".
If I forget to do that, the build fails with the message:
Error:Unresolved dependencies: common#common_2.10;0.1-SNAPSHOT: not found
See complete log in ...
For some reason if I do not specify that my scala version is 2.11.7, sbt falls back to 2.10 and tries to find common project that is built for 2.10 which I do not have.
I always keep forgetting adding scalaVersion := "2.11.7" to the newly created project and it keeps bugging me. I also would prefer sbt generating build.sbt with some default data, but instead it requires me not to forget to create it manually.
Is there any way I could set the single scala version for all projects and sub-projects in a single place? I figured that I could add a separate lazy val commonSettings = Seq { scalaVersion := "2.11.7" } in a root definition. And for each lazy val project definition I should add in the end .settings(commonSettings). This is nice, but still doesn't look beautiful enough - I should do this for every project definition. Is there a better way?
Is there any way I could create a template for a newly created project, so when I just put line lazy val newProject = ..., it will put an appropriate build.sbt file there with the contents I want?
Use
scalaVersion in ThisBuild := "2.11.7"
in the root build.sbt.