Hardbyte
Hardbyte

Reputation: 1659

How to trigger source generation with sbt

I have an sbt sub-project which compiles messages.json files into new java sources. I've set the task up to run before running tests and before compiling the primary project, or run manually via a new command "gen-messages".

The problem is the message generation takes some time, it always generates all sources, and it is running too often. Some tasks like running tests with coverage end up generating and compiling the messages twice!

How can I monitor the sources to the generator, and only run the source generation if something has changed/or the expected output java files are missing?

Secondly how would I go about running the generator only on changed messages.json files?

Currently the sbt commands I'm using are:

lazy val settingsForMessageGeneration =
  ((test in Test) <<= (test in Test) dependsOn(messageGenerationCommand)) ++
  ((compile in Compile) <<= (compile in Compile) dependsOn(messageGenerationCommand)) ++
  (messageGenerationCommand <<= messageGenerationTask) ++
  (sourceGenerators in Compile += messageGenerationTask.taskValue)

lazy val messageGenerationCommand = TaskKey[scala.collection.Seq[File]]("gen-messages")

lazy val messageGenerationTask = (
  sourceManaged,
  fullClasspath in Compile in messageGenerator,
  runner in Compile in messageGenerator,
  streams
) map { (dir, cp, r, s) =>
    lazy val f = getFileTree(new File("./subProjectWithMsgSources/src/")).filter(_.getName.endsWith("messages.json"))
    f.foreach({ te =>
      val messagePackagePath = te.getAbsolutePath().replace("messages.json", "msg").replace("./", "")
      val messagePath = te.getAbsolutePath().replace("./", "")
      val fi = new File(messagePackagePath)
      if (!fi.exists()) {
        fi.mkdirs()
      }

      val ar = List("-d", messagePackagePath, messagePath)
      toError(r.run("com.my.MessageClassGenerator", cp.files, ar, s.log))
    })
    getFileTree(new File("./subProjectWithMsgSources/src/"))
      .filter(_.getName.endsWith("/msg/*.java"))
      .to[scala.collection.Seq]
}

The message generator creates a directory with the newly created java files - no other content will be in that directory.

Related Questions

Upvotes: 2

Views: 645

Answers (1)

Martin
Martin

Reputation: 2028

You can use sbt.FileFunction.cached to run your source generator only when your input files or output files have been changed.

The idea is to factor your actual source generation to a function Set[File] => Set[File], and call it via FileFunction.cached.

lazy val settingsForMessageGeneration =
  ((test in Test) <<= (test in Test) dependsOn(messageGenerationCommand)) ++
  ((compile in Compile) <<= (compile in Compile) dependsOn(messageGenerationCommand)) ++
  (messageGenerationCommand <<= messageGenerationTask) ++
  (sourceGenerators in Compile += messageGenerationTask.taskValue)

lazy val messageGenerationCommand = TaskKey[scala.collection.Seq[File]]("gen-messages")

lazy val messageGenerationTask = (
  sourceManaged,
  fullClasspath in Compile in messageGenerator,
  runner in Compile in messageGenerator,
  streams
) map { (dir, cp, r, s) =>
  lazy val f = getFileTree(new File("./subProjectWithMsgSources/src/")).filter(_.getName.endsWith("messages.json"))

  def gen(sources: Set[File]): Set[File] = {
    sources.foreach({ te =>
      val messagePackagePath = te.getAbsolutePath().replace("messages.json", "msg").replace("./", "")
      val messagePath = te.getAbsolutePath().replace("./", "")
      val fi = new File(messagePackagePath)
      if (!fi.exists()) {
        fi.mkdirs()
      }

      val ar = List("-d", messagePackagePath, messagePath)
      toError(r.run("com.my.MessageClassGenerator", cp.files, ar, s.log))
    })
    getFileTree(new File("./subProjectWithMsgSources/src/"))
      .filter(_.getName.endsWith("/msg/*.java"))
      .to[scala.collection.immutable.Set]
  }
  val func = FileFunction.cached(s.cacheDirectory / "gen-messages", FilesInfo.hash) { gen }
  func(f.toSet).toSeq
}

Upvotes: 3

Related Questions