sbt:在跨平台项目中生成共享源码

4

在 Scala 和 sbt 上构建我的项目时,我想要一个任务,在实际编译 Scala 之前运行,并生成一个包含项目版本信息的 Version.scala 文件。下面是我设计的一个任务:

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)
}

这个任务似乎可以完成,但问题在于如何插入它,因为它是一个跨项目的方案,目标是Scala/JVM、Scala/JS等。

在我开始操作之前,build.sbt 是这样的:

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

在文件系统中,我有:

  • shared/ — JS/JVM 构建之间共享的跨平台代码
  • jvm/ — 适用于 JVM 的代码
  • js/ — 适用于 JS 的代码

到目前为止,我想出的最好方法是将此任务添加到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 */)

这个方法可以运行,但是非常笨拙,不适用于“共享”代码库。它为JS和JVM生成了两个不同的Version.scala文件:

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

当然,无法从 shared 访问这些文件的内容,这就是我想要访问它的地方。

到目前为止,我想到了一个非常笨拙的解决方法:

  • 在 singleton 对象中声明了一个 var,在共享代码中使用
  • 在 JVM 和 JS 的主入口点中,我做的第一件事就是将该变量分配给与 Version.scala 中定义的常量相匹配

此外,我还尝试过使用sbt-buildinfo插件进行相同的操作 - 结果完全相同,它生成了针对每个平台的BuildInfo.scala,但我无法直接从共享源中使用。

是否有更好的解决方案可用?


“从shared无法访问这些文件的内容”是什么意思?从共享代码中调用特定于平台的代码是完全可能的。 - Travis Brown
对我来说,它不起作用——看起来shared被单独编译,而jvmjs不在其类路径中。此外,当前使用两个Version.scala文件的解决方案实际上证实了这一点:如果Version.scala可用,那么拥有相同名称的2个文件将会产生冲突。 - GreyCat
1个回答

2
考虑将sourceManaged指向shared/src/main/scala/src_managed目录,并将generateVersionTask限定在root项目中,如下所示:

最初的回答
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
  )

现在运行sbt compile命令应该会输出以下内容:最初的回答。
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 ...

谢谢,@Mario,这确实像魔法一样奏效!我感到很抱歉盲目地复制粘贴了这个解决方案,而不理解它为什么有效,以及为什么我的先前尝试失败了——但问题已经解决了! - GreyCat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接