SBT генерирует код с использованием генератора, определенного проектом

Я хотел бы скомпилировать проект, который содержит генератор источника java, а затем скомпилировать сгенерированный код в рамках одного проекта. I.e: скомпилировать Generator.scala, запустить Generator.generate(outputDir), скомпилировать outputDir, пакет в банку. Я пробую это:

sourceGenerators in Compile <+= sourceManaged in Compile map { out =>
    Generator.generate(out / "generated")
}

но sbt жалуется

[error] Build.scala:1: object example is not a member of package org
[error] import org.example.Generator

В принципе, sbt не видит Генератора, определенного в компилируемом проекте. Можно ли это сделать с помощью sbt?

Ответы

Ответ 1

Итак, после копания в этом немного, я придумал решение. Во-первых, вам нужно разбить свой проект на два подпроекта. gen имеет весь источник, который содержит ваш код генератора. use зависит от gen и использует генератор.

    import sbt._
    import Keys._
    import java.io.{ File ⇒ JFile, FileOutputStream }

    object OverallBuild extends Build {

      lazy val root = Project(id = "overall", base = file(".")).aggregate(gen, use)

      lazy val gen = Project(id = "generate", base = file("gen"))

      val myCodeGenerator = TaskKey[Seq[File]]("mycode-generate", "Generate My Awesome Code")

      lazy val use = Project(id = "use", base = file("use"),
        settings = Defaults.defaultSettings ++ Seq(

          sourceGenerators in Compile <+= (myCodeGenerator in Compile),

          myCodeGenerator in Compile <<=
            (javaSource in Compile, dependencyClasspath in Runtime in gen) map {

              (javaSource, cp) ⇒ runMyCodeGenerator(javaSource, cp.files)

            })).dependsOn(gen)

      def runMyCodeGenerator(javaSource: File, cp: Seq[File]): Seq[File] = {
        val mainClass = "com.yourcompany.myCodeGenerator"
        val tmp = JFile.createTempFile("sources", ".txt")
        val os = new FileOutputStream(tmp)

        try {
          val i = new Fork.ForkScala(mainClass).fork(None, Nil, cp,
            Seq(javaSource.toString),
            None,
            false,
            CustomOutput(os)).exitValue()

          if (i != 0) {
            error("Trouble with code generator")
          }
        } finally {
          os.close()
        }
        scala.io.Source.fromFile(tmp).getLines.map(f ⇒ file(f)).toList
      }
    }

В этом случае я генерировал .java файлы, поэтому я передал генератор в javaSource.

Важно, чтобы при использовании sourceGenerators, как мы здесь, выполняемая задача должна вернуть Seq[File] всех файлов, которые были сгенерированы, чтобы sbt мог их управлять. В этой реализации наш генератор выводит имена файлов полного пути на стандартный, и мы сохраняем их во временном файле.

Как и во всех вещах Scala и, безусловно, SBT, вы можете делать все, просто нужно копать в него.

Ответ 2

Описание проекта компилируется при загрузке. Невозможно напрямую вызвать новый код, сгенерированный во время выполнения. Если я не думаю, что использую какое-то отражение, убедившись, что нет разметки JVM и каким-то образом загружены эти классы в загрузчик классов.

Единственный способ, которым я могу это сделать, - сделать проект внутри вашего определения проекта.

root
 - src
 - project/
   - Build.scala // normal project definition
   - project/
     - Build.scala // inner most

Во внутреннем большинстве определения проекта вы можете определить внешний src как папку src. Это даст вам скомпилированную версию генератора, доступную для реального проекта. Затем в обычном определении проекта добавьте импорт в генератор и используйте его так, как вы делали.

Я уверен, что внутренний проект будет загружен и скомпилирован только один раз. Если вы вносите изменения в генератор, вам нужно будет перезагрузить определение проекта sbt. Выход и повторное открытие - это самый простой/самый тупой способ сделать это, но это может помочь в тестировании. Последуйте более разумные способы перезагрузки, если они будут работать.