4
votes

Building my project on Scala with sbt, I want to have a task that will run prior to actual Scala compilation and will generate a Version.scala file with project version information. Here's a task I've came up with:

lazy val generateVersionTask = Def.task {
  // Generate contents of Version.scala
  val contents = s"""package io.kaitai.struct
                    |
                    |object Version {
                    |  val name = "${name.value}"
                    |  val version = "${version.value}"
                    |}
                    |""".stripMargin

  // Update Version.scala file, if needed
  val file = (sourceManaged in Compile).value / "version" / "Version.scala"
  println(s"Version file generated: $file")
  IO.write(file, contents)
  Seq(file)
}

This task seems to work, but the problem is how to plug it in, given that it's a cross project, targeting Scala/JVM, Scala/JS, etc.

This is how build.sbt looked before I started touching it:

lazy val root = project.in(file(".")).
  aggregate(fooJS, fooJVM).
  settings(
    publish := {},
    publishLocal := {}
  )

lazy val foo = crossProject.in(file(".")).
  settings(
    name := "foo",
    version := sys.env.getOrElse("CI_VERSION", "0.1"),
    // ...
  ).
  jvmSettings(/* JVM-specific settings */).
  jsSettings(/* JS-specific settings */)

lazy val fooJVM = foo.jvm
lazy val fooJS = foo.js

and, on the filesystem, I have:

  • shared/ — cross-platform code shared between JS/JVM builds
  • jvm/ — JVM-specific code
  • js/ — JS-specific code

The best I've came up so far with is adding this task to foo crossProject:

lazy val foo = crossProject.in(file(".")).
  settings(
    name := "foo",
    version := sys.env.getOrElse("CI_VERSION", "0.1"),
    sourceGenerators in Compile += generateVersionTask.taskValue, // <== !
    // ...
  ).
  jvmSettings(/* JVM-specific settings */).
  jsSettings(/* JS-specific settings */)

This works, but in a very awkward way, not really compatible with "shared" codebase. It generates 2 distinct Version.scala files for JS and JVM:

sbt:root> compile
Version file generated: /foo/js/target/scala-2.12/src_managed/main/version/Version.scala
Version file generated: /foo/jvm/target/scala-2.12/src_managed/main/version/Version.scala

Naturally, it's impossible to access contents of these files from shared, and this is where I want to access it.

So far, I've came with a very sloppy workaround:

  • There is a var declared in singleton object in shared
  • in both JVM and JS main entry points, the very first thing I do is that I assign that variable to match constants defined in Version.scala

Also, I've tried the same trick with sbt-buildinfo plugin — the result is exactly the same, it generated per-platform BuildInfo.scala, which I can't use directly from shared sources.

Are there any better solutions available?

1
What do you mean by "it's impossible to access the contents of these files from shared"? It's totally possible to call platform-specific code from shared code.Travis Brown
@TravisBrown For me, it does not work — it looks like shared is compiled separately and jvm and js are not in its class path. Moreover, the mere fact that current solution with two Version.scala files work actually confirms that: if Version.scala were available, then it would have been a conflict of having 2 files with the same name.GreyCat

1 Answers

2
votes

Consider pointing sourceManaged to shared/src/main/scala/src_managed directory and scoping generateVersionTask to the root project like so

val sharedSourceManaged = Def.setting(
  baseDirectory.value / "shared" / "src" / "main" / "scala" / "src_managed"
)

lazy val root = project.in(file(".")).
  aggregate(fooJS, fooJVM).
  settings(
    publish := {},
    publishLocal := {},
    sourceManaged := sharedSourceManaged.value,
    sourceGenerators in Compile += generateVersionTask.taskValue,
    cleanFiles += sharedSourceManaged.value
  )

Now sbt compile should output something like

Version file generated: /Users/mario/IdeaProjects/scalajs-cross-compile-example/shared/src/main/scala/src_managed/version/Version.scala
...
[info] Compiling 3 Scala sources to /Users/mario/IdeaProjects/scalajs-cross-compile-example/js/target/scala-2.12/classes ...
[info] Compiling 1 Scala source to /Users/mario/IdeaProjects/scalajs-cross-compile-example/target/scala-2.12/classes ...
[info] Compiling 3 Scala sources to /Users/mario/IdeaProjects/scalajs-cross-compile-example/jvm/target/scala-2.12/classes ...