如何在多模块项目中使用自定义插件?

时间:2014-07-22 20:13:09

标签: scala sbt

在一个多模块项目中,一个模块使用自定义TaskKey实现自定义SBT插件,如何为另一个子模块的项目设置导入此插件。

如果使用插件的子模块在submodule1/build.sbt中定义,则不会加载submodule1/project/plugins.sbt

如果插件在project/plugins.sbt中注册,则在加载top / aggregate项目时它将失败,因为插件不一定已经构建。

有没有其他方法可以定义需要自定义依赖关系的自定义任务,以便子模块可以使用它?

1 个答案:

答案 0 :(得分:1)

以下是我最终如何运作:

import sbt._
import Keys._

object MyBuild extends Build {
  private lazy val myGenerator = 
    // Private project with generator code and its specific dependencies
    // (e.g. Javassist)
    Project(id = "my-generator", 
      base = file("project") / "my-generator").settings(
      name := "my-generator",
      javacOptions in Test ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
      autoScalaLibrary := false,
      scalacOptions += "-feature",
      resolvers += 
        "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/releases/",
      libraryDependencies ++= Seq( // Dependencies required to generate classes
        "org.javassist" % "javassist" % "3.18.2-GA")
    )

  // Some custom setting & task
  lazy val generatedClassDirectory = settingKey[File](
    "Directory where classes get generated")

  lazy val generatedClasses = taskKey[Seq[(File, String)]]("Generated classes")

  lazy val myProject =
    Project(id = "my-project", base = file("my-project")).settings(
      name := "my-project",
      javacOptions in Test ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
      autoScalaLibrary := false,
      scalacOptions += "-feature",
      libraryDependencies ++= Seq(/* ... */),
      generatedClassDirectory := { 
        // Defines setting for path to generated classes
        val dir = target.value / "generated_classes"
        if (!dir.exists) dir.mkdirs()
        dir
      },
      generatedClasses <<= Def.task { // Define task generating .class files
        // first get classloader including generator and its dependencies
        val cp = (fullClasspath in (myGenerator, Compile)).value
        val cl = classpath.ClasspathUtilities.toLoader(cp.files)

        // then loaded generator class, and instantiate with structural type
        val genClass = cl loadClass "my.custom.GeneratorClass"
        val generator = genClass.newInstance.
          asInstanceOf[{def writeTo(out: File): File}]

        // finally we can call the
        val outdir = generatedClassDirectory.value
        val generated = generator writeTo outdir
        val path = generated.getAbsolutePath

        // Mappings describing generated classes
        Seq[(File, String)](generated -> path.
          drop(outdir.getAbsolutePath.length+1))

      } dependsOn(compile in (myGenerator, Compile))/* awkward? */,
      managedClasspath in Compile := {
        // Add generated classes to compilation classpath, 
        // so it can be used in my-project sources
        val cp = (managedClasspath in Compile).value
        cp :+ Attributed.blank(generatedClassDirectory.value)
      },
      // Make sure custom class generation is done before compile
      compile in Compile <<= (compile in Compile) dependsOn generatedClasses,
      mappings in (Compile, packageBin) := {
        val ms = mappings.in(Compile, packageBin).value
        ms ++ generatedClasses.value // add generated classes to package
      }
    ).dependsOn(myGenerator/* required even if there dependsOn(compile in (myGenerator, Compile)) */)
}

不确定是否有更好的解决方案,尤其是关于重新申请dependsOn(compile in (myGenerator, Compile)).dependsOn(myGenerator)