Sbt 使用编译器插件定义配置

Sbt 使用编译器插件定义配置,sbt,Sbt,我想创建一个与默认配置相同的编译配置,但添加了一个编译器插件。在我的特殊情况下,我希望有一个“dev”配置,但要使用linter插件(),因为它会降低编译速度,并且不需要在生产或连续集成中运行它 这就是我所尝试的: lazy val Dev=config(“Dev”)扩展编译 lazy val root=(文件中的项目(“.”).configs(Dev).settings( inConfig(Dev)(addCompilerPlugin(“org.psywerx.hairyfort”%%“lin

我想创建一个与默认配置相同的编译配置,但添加了一个编译器插件。在我的特殊情况下,我希望有一个“dev”配置,但要使用linter插件(),因为它会降低编译速度,并且不需要在生产或连续集成中运行它

这就是我所尝试的:

lazy val Dev=config(“Dev”)扩展编译
lazy val root=(文件中的项目(“.”).configs(Dev).settings(
inConfig(Dev)(addCompilerPlugin(“org.psywerx.hairyfort”%%“linter”%%“0.1.12”):*)
它应该可以工作,因为当我
检查dev:libraryDependencies
时,它就是我所期望的-它有
org.psywerx.hairyfotr:linter:0.1.12:plugin->default(compile)
。通常,如果我添加具有“plugin”作用域的库,它对默认设置有效:

libraryDependencies += ("org.psywerx.hairyfotr" %% "linter" % "0.1.12" % "plugin"

如果我在另一个配置下添加它,它就不起作用了,所以这里肯定还有其他事情发生。

查看源代码中的Defaults.scala,似乎compile命令总是从compile范围中获取选项。因此,如果我是正确的,您只能有一组编译选项

scalacOptions
的行为方式与此相同,这似乎证实了这一点,这也是为什么我看不到这些类似问题的非黑客答案的原因:

我很高兴被证明是错的

编辑:FWIW,可能无法在同一项目中定义另一个scalac选项配置文件,但您可以在“不同”的项目中这样做:


这样做的缺点是它有一个单独的输出目录,因此会占用更多的空间,更重要的是,不会在两个项目之间进行增量编译。然而,在花了一些时间思考之后,这可能是出于设计。毕竟,即使Linter没有,一些scalac编译选项可能会改变输出。这将使试图将增量编译的元数据从一组scalac选项保留到另一组选项变得毫无意义。因此,不同的scalac选项确实需要不同的目标目录。

这解决了问题,但并不是完全按照要求的方式。下面是完整的
build.sbt

libraryDependencies ++= Seq(
  "org.psywerx.hairyfotr" %% "linter" % "0.1.14" % "test")

val linter = Command.command("linter")(state => {
  val linterJar = for {
    (newState, result) <- Project.runTask(fullClasspath in Test, state)
    cp <- result.toEither.right.toOption
    linter <- cp.find(
      _.get(moduleID.key).exists(mId =>
        mId.organization == "org.psywerx.hairyfotr" &&
          mId.name == "linter_2.11"))
  } yield linter.data.absolutePath

  val res = Project.runTask(scalacOptions, state)
  res match {
    case Some((newState, result)) =>
      result.toEither.right.foreach { defaultScalacOptions =>
        Project.runTask(compile in Test,
          Project.extract(state).append(
            scalacOptions := defaultScalacOptions ++ linterJar.map(p => Seq(s"-Xplugin:$p")).getOrElse(Seq.empty),
            newState))
      }
    case None => sys.error("Couldn't get defaultScalacOptions")
  }
  state
})

lazy val root = (project in file(".")).configs(Test).settings(commands ++= Seq(linter))
libraryDependencies++=Seq(
“org.psywerx.hairyfotr”%%“linter”%%“0.1.14”%”测试)
val linter=Command.Command(“linter”)(状态=>{
val linterJar=for{
(新闻状态,结果)
Project.runTask(在测试中编译,
Project.extract(state.append)(
scalacOptions:=defaultScalacOptions++linterJar.map(p=>Seq(s“-Xplugin:$p”)).getOrElse(Seq.empty),
新闻状态)
}
case None=>sys.error(“无法获取DefaultScalaOptions”)
}
状态
})
lazy val root=(文件中的项目(“.”).configs(Test).settings(commands++=Seq(linter))
返回未修改状态意味着不更改项目设置。因此,如果您运行
sbt linter
,您应该使用附加的
scalacOptions
来编译项目,但是如果您在同一个sbt会话中运行
compile
,它将不会使用这些附加设置

这里需要技巧的是,
scalacOptions
实际上是一个
TaskKey
,而不是
SettingKey
。我不知道为什么会这样,但要得到它的价值,你必须运行这个任务。一个原因可能是,在sbt中,您无法根据任务进行设置,但可以根据任务进行任务设置。换句话说,
scalacOptions
可以依赖于另一个任务值,可能在内部是这样,我没有检查。如果目前的答案对你有用,我可以试着想想更优雅的方法来达到同样的效果


编辑:修改代码,为linter插件指定
scalacOptions
。请注意,该插件必须是一个托管依赖项,而不仅仅是一个下载的jar,才能使该解决方案工作。如果你想让它不受管理,这是有办法的,但我现在就不谈了。此外,为了便于演示,我还自由地将其用于测试代码。

这个插件应该什么时候运行?当你说
sbt compile
?我假设你在项目设置中添加了
autoCompilerPlugins:=true
?我希望插件在“dev:compile”上运行,而不是在“compile”上运行。如果你能换个方式做,我可以接受。是的,我也尝试过添加“autoCompilerPlugins:=true”,即使在编译阶段如果我只添加“addCompilerPlugin(…)”插件是在没有它的情况下激活的,但实际上我还没有运行这个插件,这让我很尴尬。但是,如果您确定问题的根源是
scalacOptions
,那么您可以选择以下选项:创建一个新的
命令
,在该命令中,您将通过更改
scalacOptions
,来修改项目范围,并使用修改后的范围手动运行
compile
任务。不是100%确定,但它具有相同的模式。我确实考虑过重新定义一个命令,但仅仅创建另一组编译设置似乎需要很多努力。这并不需要很多努力,您可以复制和修改现有的
scalacSettings
。我很快会在回答中发布一个示例。好的,在查看Defaults.scala之后,我确信它使用了ScalaOptions机制-它基本上添加了一个-Xplugin:path。所以,好吧,也许这不需要太多的努力,但看起来不像在特定范围下配置那样优雅。当然,我很想看看你的解决方案。这意味着编译将被调用两次?听起来不太令人鼓舞,尤其是对于一个可能需要几分钟时间的编译来说。此代码最重要的更改是需要根据插件类路径更改ScalaOptions,就像在调用autoPlugins的compilerPluginConfig中所做的那样。不,它将被调用一次。第一个溜溜球
libraryDependencies ++= Seq(
  "org.psywerx.hairyfotr" %% "linter" % "0.1.14" % "test")

val linter = Command.command("linter")(state => {
  val linterJar = for {
    (newState, result) <- Project.runTask(fullClasspath in Test, state)
    cp <- result.toEither.right.toOption
    linter <- cp.find(
      _.get(moduleID.key).exists(mId =>
        mId.organization == "org.psywerx.hairyfotr" &&
          mId.name == "linter_2.11"))
  } yield linter.data.absolutePath

  val res = Project.runTask(scalacOptions, state)
  res match {
    case Some((newState, result)) =>
      result.toEither.right.foreach { defaultScalacOptions =>
        Project.runTask(compile in Test,
          Project.extract(state).append(
            scalacOptions := defaultScalacOptions ++ linterJar.map(p => Seq(s"-Xplugin:$p")).getOrElse(Seq.empty),
            newState))
      }
    case None => sys.error("Couldn't get defaultScalacOptions")
  }
  state
})

lazy val root = (project in file(".")).configs(Test).settings(commands ++= Seq(linter))