Определение задачи sbt, которая вызывает метод из кода проекта?
Я использую SBT для создания проекта scala. Я хочу определить очень простую задачу, когда при вводе generate
в sbt:
sbt> generate
Он вызовет мой метод my.App.main(..)
для создания чего-то.
В myproject/src/main/scala/my
есть файл App.scala
, а упрощенный код выглядит следующим образом:
object App {
def main(args: Array[String]) {
val source = readContentOfFile("mysource.txt")
val result = convert(source)
writeToFile(result, "mytarget.txt");
}
// ignore some methods here
}
Я попытался добавить следующий код в myproject/build.sbt
:
lazy val generate = taskKey[Unit]("Generate my file")
generate := {
my.App.main(Array())
}
Но он не компилируется, так как он не может найти my.App
.
Затем я попытался добавить его в myproject/project/build.scala
:
import sbt._
import my._
object HelloBuild extends Build {
lazy val generate = taskKey[Unit]("Generate my file")
generate := {
App.main(Array())
}
}
Но он все еще не может быть скомпилирован, что он не может найти пакет my
.
Как определить такую задачу в SBT?
Ответы
Ответ 1
В формате .sbt
выполните следующие действия:
lazy val generate = taskKey[Unit]("Generate my file")
fullRunTask(generate, Compile, "my.App")
Это задокументировано на http://www.scala-sbt.org/0.13.2/docs/faq.html: "Как создать задачу пользовательского запуска в дополнение к запуску?"
Другой подход:
lazy val generate = taskKey[Unit]("Generate my file")
generate := (runMain in Compile).toTask(" my.App").value
который отлично работает в простых случаях, но не так настраивается.
Обновление: Совет Jacek для использования resourceGenerators
или sourceGenerators
вместо этого хорош, если он подходит для вашего прецедента - не могу сказать из вашего описания, что он делает.
Ответ 2
Другие ответы подходят к вопросу очень хорошо, но я думаю, что OP тоже может извлечь выгоду из моего:)
ОП спросил: "Я хочу определить очень простую задачу, что при вводе generate
в sbt вызывается мой метод my.App.main(..)
для создания чего-то". что может в конечном итоге усложнить сборку.
Sbt уже предлагает способ генерации файлов во время сборки - sourceGenerators
и resourceGenerators
- и я не могу заметить, что вам нужно определить отдельную задачу для этого, прочитав вопрос.
В Создание файлов (см. будущую версию документа в commit), вы можете прочитать:
sbt предоставляет стандартные привязки для добавления источника или генерации ресурсов задачи.
С помощью знания можно было бы подумать о следующем решении:
sourceGenerators in Compile += Def.task {
my.App.main(Array()) // it not going to work without one change, though
Seq[File]() // a workaround before the above change is in effect
}.taskValue
Чтобы выполнить эту работу, вы должны вернуть Seq[File]
, который содержит файлы, сгенерированные (а не пустые Seq[File]()
).
Основное изменение для работы кода - переместить класс my.App
в папку project
. Затем он становится частью определения сборки. Он также отражает то, что делает класс, поскольку он действительно является частью сборки, а не артефактом, являющимся его продуктом. Когда один и тот же код является частью сборки и самого артефакта, вы не разделяете разные проблемы. Если класс my.App
участвует в сборке, он должен принадлежать ему - следовательно, переход в папку project
.
Макет проекта будет выглядеть следующим образом:
$ tree
.
├── build.sbt
└── project
├── App.scala
└── build.properties
Разделение проблем (aka @joescii in da haus)
В ответе @joescii (который я процитирую в ответе) есть пункт - "сделать его отдельным проектом, который могут использовать другие проекты. Для этого вам нужно будет поместить ваш объект App
в отдельный проект и включить его как зависимость в project/project
", т.е.
Предположим, что у вас есть отдельный проект build-utils
с App.scala
под src/main/scala
. Это обычная конфигурация sbt с кодом Scala.
jacek:~/sandbox/so/generate-project-code
$ tree build-utils/
build-utils/
└── src
└── main
└── scala
└── App.scala
Вы можете проверить его как обычное приложение Scala, не испортив его с помощью sbt. Никакой дополнительной настройки не требуется (и освобождает ваш разум от sbt, что может быть полезно время от времени - меньше настроек всегда помогает).
В другом проекте - project-code
- который использует App.scala
, который должен быть базой для сборки, build.sbt выглядит следующим образом:
Проект-код/build.sbt
lazy val generate = taskKey[Unit]("Generate my file")
generate := {
my.App.main(Array())
}
Теперь самая важная часть - проводка между проектами, поэтому код App
отображается для сборки project-code
:
Проект-код/проект/build.sbt
lazy val buildUtils = RootProject(
uri("file:/Users/jacek/sandbox/so/generate-project-code/build-utils")
)
lazy val plugins = project in file(".") dependsOn buildUtils
С помощью определения (ов) сборки выполнение generate
дает вам следующее:
jacek:~/sandbox/so/generate-project-code/project-code
$ sbt
[info] Loading global plugins from /Users/jacek/.sbt/0.13/plugins
[info] Loading project definition from /Users/jacek/sandbox/so/generate-project-code/project-code/project
[info] Updating {file:/Users/jacek/sandbox/so/generate-project-code/build-utils/}build-utils...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Updating {file:/Users/jacek/sandbox/so/generate-project-code/project-code/project/}plugins...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.
[info] Compiling 1 Scala source to /Users/jacek/sandbox/so/generate-project-code/build-utils/target/scala-2.10/classes...
[info] Set current project to project-code (in build file:/Users/jacek/sandbox/so/generate-project-code/project-code/)
> generate
Hello from App.main
[success] Total time: 0 s, completed May 2, 2014 2:54:29 PM
Я изменил код App
следующим образом:
> eval "cat ../build-utils/src/main/scala/App.scala"!
package my
object App {
def main(args: Array[String]) {
println("Hello from App.main")
}
}
Структура проекта выглядит следующим образом:
jacek:~/sandbox/so/generate-project-code/project-code
$ tree
.
├── build.sbt
└── project
├── build.properties
└── build.sbt
Другие изменения aka goodies
Я также предлагаю некоторые другие изменения в коде генератора источника:
- Переместите код из метода
main
в отдельный метод, который возвращает созданные файлы и вызовет main
. Это упростит повторное использование кода в sourceGenerators
(без ненужного Array()
, чтобы вызвать его, а также явно возвращать файлы).
- Используйте
filter
или map
функции для convert
(чтобы добавить более функциональный вкус).
Ответ 3
Решение, которое предлагает @SethTisue, будет работать. Другой подход - сделать его отдельным проектом, который могут использовать другие проекты. Для этого вам нужно будет поместить ваш объект App
в отдельный проект и включить его как зависимость в project/project
, или разместить его как плагин sbt в идеале с включенным определением этой задачи.
Для примера того, как создать lib, который упакован как плагин, посмотрите snmp4s. Каталог gen
содержит код, который генерирует некоторые генерации кода (аналогично вашему коду App
), а каталог sbt
содержит оболочку плагина sbt для gen
.