Может кто-нибудь объяснить, как правильно использовать SBT?


100

Я выхожу из туалета на этом! Я не понимаю SBT. Вот, я сказал, теперь помогите мне, пожалуйста.

Все дороги ведут в Рим, и это то же самое для SBT: Для того, чтобы начать работу с SBTтам SBT, SBT Launcher, SBT-extrasи т.д., а затем Существуют различные способы , чтобы включить и принять решение о хранилищах. Есть ли лучший способ?

Я спрашиваю, потому что иногда я немного теряюсь. Документация по SBT очень тщательная и полная, но я не знаю, когда использовать build.sbtили project/build.propertiesили project/Build.scalaили project/plugins.sbt.

Тогда это становится забавой, есть Scala-IDEи SBT- Как правильно использовать их вместе? Что идет первым, курица или яйцо?

Вероятно, самое важное - как найти нужные репозитории и версии для включения в свой проект? Я просто вытащу мачет и начну продвигаться вперед? Я довольно часто нахожу проекты, которые включают все, включая кухонную раковину, и тогда понимаю - я не единственный, кто немного теряется.

В качестве простого примера, прямо сейчас я начинаю совершенно новый проект. Я хочу использовать новейшие функции SLICKи , Scalaи это, вероятно , потребуется последняя версия SBT. С чего начать и почему? В каком файле я должен его определить и как он должен выглядеть? Я знаю, что могу заставить это работать, но мне бы очень хотелось получить экспертное мнение о том, куда все должно идти (почему это должно идти, будет бонус).

Я использую SBTдля небольших проектов уже более года. Я использовал SBTа затем SBT Extras(так как это заставляло некоторые головные боли волшебным образом исчезать), но я не уверен, почему я должен использовать тот или другой. Я просто немного расстраиваюсь из-за того, что не понимаю, как вещи сочетаются друг с другом ( SBTи репозиториями), и думаю, что это избавит следующего парня, который пойдет этим путем, от множества трудностей, если это можно будет объяснить в человеческих терминах.


2
Что именно вы имеете в виду под «есть Scala-IDE и SBT»? Вы определяете свой проект с помощью sbt, и sbt может сгенерировать проект ide (eclipse oder intellij). Итак, SBT на первом месте ...
января

2
@Jan Я упомянул об этом, потому что Scala-IDE использует SBT в качестве диспетчера сборки. См. Assembla.com/spaces/scala-ide/wiki/SBT-based_build_manager и ниже в сообщении, в котором они упоминают «Нет необходимости определять файл вашего проекта SBT». что меня сбивает с толку.
Джек

хорошо. Поскольку я обычно использую intellij (или возвышенный) для редактирования scala, я этого не знал. Я предполагаю, что строитель генерирует собственные конфиги sbt?
янв.

2
@JacobusR Тот факт, что Scala IDE использует SBT для создания исходных кодов вашего проекта, является деталью реализации, и пользователям не нужно об этом беспокоиться. На самом деле есть 0 последствий. Пользователи вне Eclipse могут создавать проекты с помощью SBT, Maven, Ant, ..., и это не повлияет на Scala IDE. Еще одна вещь, даже если у вас есть проект SBT, Scala IDE не заботится, т. Е. Она не ищет, чтобы вы Build.scalaнастраивали путь к классам, и именно поэтому вам действительно нужен sbteclipse для генерации Eclipse .classpath. Надеюсь это поможет.
Mirco Dotta

1
@Jan Scala IDE добавила путаницы, и да, документация, которая дает более широкую картину по настройке хорошей среды разработки Scala, и некоторые твердые рекомендации по подходящим рабочим процессам программирования, будут очень кстати.
Джек

Ответы:


29

Вероятно, самое важное - как найти нужные репозитории и версии для включения в свой проект? Я просто вытащу мачет и начну продвигаться вперед? Я довольно часто нахожу проекты, которые включают все, и кухонную мойку

Для зависимостей на основе Scala я бы согласился с рекомендациями авторов. Например: http://code.google.com/p/scalaz/#SBT указывает на использование:

libraryDependencies += "org.scalaz" %% "scalaz-core" % "6.0.4"

Или на https://github.com/typesafehub/sbteclipse/ есть инструкции, где добавить:

addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.0-RC1")

Для зависимостей на основе Java я использую http://mvnrepository.com/, чтобы посмотреть, что там есть, затем щелкаю вкладку SBT. Например, http://mvnrepository.com/artifact/net.sf.opencsv/opencsv/2.3 указывает на использование:

libraryDependencies += "net.sf.opencsv" % "opencsv" % "2.3"

Затем вытащите мачете и начните продвигаться вперед. Если вам повезет, вы не закончите использовать jar-файлы, которые зависят от некоторых из тех же jar-файлов, но с несовместимыми версиями. Учитывая экосистему Java, вы часто заканчиваете тем, что включаете все и кухонную раковину, и требуются некоторые усилия, чтобы устранить зависимости или убедиться, что вы не пропустите необходимые зависимости.

В качестве простого примера, прямо сейчас я начинаю совершенно новый проект. Я хочу использовать последние функции SLICK и Scala, и для этого, вероятно, потребуется последняя версия SBT. С чего начать и почему?

Я думаю, что разумным является постепенное формирование иммунитета к СБТ .

Убедитесь, что вы понимаете:

  1. формат прицелов {<build-uri>}<project-id>/config:key(for task-key)
  2. в 3 ароматы настройки ( SettingKey, TaskKey, InputKey) - прочитайте раздел под названием «Ключи задач» в http://www.scala-sbt.org/release/docs/Getting-Started/Basic-Def

Всегда держите эти 4 страницы открытыми, чтобы вы могли прыгать и искать различные определения и примеры:

  1. http://www.scala-sbt.org/release/docs/Getting-Started/Basic-Def
  2. http://www.scala-sbt.org/release/docs/Detailed-Topics/index
  3. http://harrah.github.com/xsbt/latest/sxr/Keys.scala.html
  4. http://harrah.github.com/xsbt/latest/sxr/Defaults.scala.html

Максимально используйте кнопки showи inspect и заполнение вкладок, чтобы ознакомиться с фактическими значениями параметров, их зависимостями, определениями и соответствующими параметрами. Я не верю, что отношения, которые вы обнаружите при использовании inspect, где-либо задокументированы. Если есть лучший способ, я хочу знать об этом.


25

Я использую sbt:

  1. Используйте sbt-extras - просто получите сценарий оболочки и добавьте его в корень своего проекта.
  2. Создайте projectпапку с MyProject.scalaфайлом для настройки sbt. Я предпочитаю это build.sbtподходу - это scala и более гибкий
  3. Создайте project/plugins.sbtфайл и добавьте соответствующий плагин для своей IDE. Либо sbt-eclipse, sbt-idea, либо ensime-sbt-cmd, чтобы вы могли создавать файлы проекта для eclipse, intellij или ensime.
  4. Запустите sbt в корне вашего проекта и сгенерируйте файлы проекта для вашей IDE.
  5. Прибыль

Я не беспокоюсь о проверке файлов проекта IDE, поскольку они генерируются sbt, но могут быть причины, по которым вы захотите это сделать.

Вы можете увидеть пример настроить , как это здесь .


Спасибо за хороший ответ. Я принял другой ответ, потому что он охватывает больше вопросов, и поддержал ваш, потому что он тоже действительно хорош. Я бы принял оба, если бы мог.
Джек

0

Используйте Typesafe Activator, причудливый способ вызова sbt, который поставляется с шаблонами проектов и семенами: https://typesafe.com/activator

Activator new

Fetching the latest list of templates...

Browse the list of templates: http://typesafe.com/activator/templates
Choose from these featured templates or enter a template name:
 1) minimal-java
 2) minimal-scala
 3) play-java
 4) play-scala
(hit tab to see a list of all templates)

5
Я неравнодушен к мысли, что в случае сомнений добавление большего количества магии в микс вряд ли решит ваши проблемы.
Cubic

0

Монтаж

brew install sbt или аналогичные установки sbt, которые технически состоят из

Когда вы выполняете sbtс терминала, он фактически запускает bash-скрипт sbt launcher. Лично мне никогда не приходилось беспокоиться об этой троице, я просто использовал sbt, как будто это что-то одно.

Конфигурация

Чтобы настроить sbt для конкретного проекта, сохраните .sbtoptsфайл в корне проекта. Чтобы настроить sbt в масштабе всей системы, измените /usr/local/etc/sbtopts. Выполнение sbt -helpдолжно сказать вам точное местоположение. Например, чтобы дать SBT больше памяти , как один-офф выполнить sbt -mem 4096, или сохранить -mem 4096в .sbtoptsили sbtoptsдля увеличения памяти вступили в силу на постоянной основе .

 Структура проекта

sbt new scala/scala-seed.g8 создает минимальную структуру проекта Hello World sbt

.
├── README.md  // most important part of any software project
├── build.sbt  // build definition of the project
├── project    // build definition of the build (sbt is recursive - explained below)
├── src        // test and main source code
└── target     // compiled classes, deployment package

Частые команды

test                                                // run all test
testOnly                                            // run only failed tests
testOnly -- -z "The Hello object should say hello"  // run one specific test
run                                                 // run default main
runMain example.Hello                               // run specific main
clean                                               // delete target/
package                                             // package skinny jar
assembly                                            // package fat jar
publishLocal                                        // library to local cache
release                                             // library to remote repository
reload                                              // after each change to build definition

Множество снарядов

scala              // Scala REPL that executes Scala language (nothing to do with sbt)
sbt                // sbt REPL that executes special sbt shell language (not Scala REPL)
sbt console        // Scala REPL with dependencies loaded as per build.sbt
sbt consoleProject // Scala REPL with project definition and sbt loaded for exploration with plain Scala langauage

Определение сборки - это правильный проект Scala

Это одна из ключевых идиоматических концепций SBT. Постараюсь объяснить вопросом. Допустим, вы хотите определить задачу sbt, которая будет выполнять HTTP-запрос с помощью scalaj-http. Интуитивно мы могли бы попробовать следующее внутриbuild.sbt

libraryDependencies +=  "org.scalaj" %% "scalaj-http" % "2.4.2"

val fooTask = taskKey[Unit]("Fetch meaning of life")
fooTask := {
  import scalaj.http._ // error: cannot resolve symbol
  val response = Http("http://example.com").asString
  ...
}

Однако это будет ошибка, говорящая об отсутствии import scalaj.http._. Как это возможно , когда мы, прямо выше, добавляют scalaj-httpк libraryDependencies? Кроме того, почему это работает, когда вместо этого мы добавляем зависимость в project/build.sbt?

// project/build.sbt
libraryDependencies +=  "org.scalaj" %% "scalaj-http" % "2.4.2"

Ответ заключается в том, что fooTaskэто часть отдельного проекта Scala от вашего основного проекта. Этот другой проект Scala можно найти в project/каталоге, у которого есть собственный target/каталог, в котором находятся его скомпилированные классы. Фактически, ниже project/target/config-classesдолжен быть класс, который декомпилируется во что-то вроде

object $9c2192aea3f1db3c251d extends scala.AnyRef {
  lazy val fooTask : sbt.TaskKey[scala.Unit] = { /* compiled code */ }
  lazy val root : sbt.Project = { /* compiled code */ }
}

Мы видим, что fooTaskэто просто член обычного объекта Scala с именем $9c2192aea3f1db3c251d. Ясно, что это scalaj-httpдолжна быть зависимость определения проекта, $9c2192aea3f1db3c251dа не зависимость правильного проекта. Следовательно, он должен быть объявлен project/build.sbtвместо build.sbt, потому что projectименно там находится проект Scala определения сборки.

Чтобы понять, что определение сборки - это просто еще один проект Scala, выполните sbt consoleProject. Это загрузит Scala REPL с проектом определения сборки в путь к классам. Вы должны увидеть импорт по строкам

import $9c2192aea3f1db3c251d

Итак, теперь мы можем напрямую взаимодействовать с проектом определения сборки, вызывая его с помощью собственно Scala вместо build.sbtDSL. Например, следующее выполняетfooTask

$9c2192aea3f1db3c251d.fooTask.eval

build.sbtВ корневом проекте есть специальный DSL, который помогает определить проект Scala с определением сборки project/.

И проект Scala определения сборки, может иметь собственный проект Scala определения сборки project/project/и так далее. Мы говорим, что sbt рекурсивен .

sbt по умолчанию параллелен

sbt строит DAG из задач. Это позволяет ему анализировать зависимости между задачами и выполнять их параллельно и даже выполнять дедупликацию. build.sbtDSL разработан с учетом этого, что может привести к изначально неожиданной семантике. Как вы думаете, какой порядок выполнения в следующем фрагменте?

def a = Def.task { println("a") }
def b = Def.task { println("b") }
lazy val c = taskKey[Unit]("sbt is parallel by-default")
c := {
  println("hello")
  a.value
  b.value
}

Интуитивно можно подумать, что поток здесь - это сначала печать, helloзатем выполнение a, а затем bзадание. Однако это на самом деле означает , что выполнить aи bв параллель , и прежде , чем println("hello") так

a
b
hello

или из-за порядка aи bне гарантируется

b
a
hello

Как это ни парадоксально, в sbt проще сделать параллельный, чем последовательный. Если вам нужен серийный заказ, вам придется использовать специальные вещи, такие как Def.sequentialили Def.taskDynимитация для понимания .

def a = Def.task { println("a") }
def b = Def.task { println("b") }
lazy val c = taskKey[Unit]("")
c := Def.sequential(
  Def.task(println("hello")),
  a,
  b
).value

похоже на

for {
  h <- Future(println("hello"))
  a <- Future(println("a"))
  b <- Future(println("b"))
} yield ()

где мы видим, что между компонентами нет зависимости, в то время как

def a = Def.task { println("a"); 1 }
def b(v: Int) = Def.task { println("b"); v + 40 }
def sum(x: Int, y: Int) = Def.task[Int] { println("sum"); x + y }
lazy val c = taskKey[Int]("")
c := (Def.taskDyn {
  val x = a.value
  val y = Def.task(b(x).value)
  Def.taskDyn(sum(x, y.value))
}).value

похоже на

def a = Future { println("a"); 1 }
def b(v: Int) = Future { println("b"); v + 40 }
def sum(x: Int, y: Int) = Future { x + y }

for {
  x <- a
  y <- b(x)
  c <- sum(x, y)
} yield { c }

где мы видим, sumзависит от и должно ждать aи b.

Другими словами

  • для прикладной семантики используйте.value
  • для использования монадической семантики sequentialилиtaskDyn

Рассмотрим еще один семантически сбивающий с толку фрагмент из-за природы построения зависимостей value, где вместо

`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.
val x = version.value
                ^

мы должны написать

val x = settingKey[String]("")
x := version.value

Обратите внимание, что синтаксис .valueкасается отношений в группе DAG и не означает

"дай мне ценность прямо сейчас"

вместо этого это означает что-то вроде

"мой вызывающий абонент зависит в первую очередь от меня, и как только я узнаю, как вся группа DAG совмещена, я смогу предоставить вызывающему абоненту запрошенное значение"

Так что теперь может быть немного яснее, почему xеще нельзя присвоить значение; пока нет ценности на стадии построения отношений.

Мы ясно видим разницу в семантике между собственно Scala и языком DSL в build.sbt. Вот несколько полезных для меня правил

  • DAG состоит из выражений типа Setting[T]
  • В большинстве случаев мы просто используем .valueсинтаксис, а sbt позаботится об установлении связи междуSetting[T]
  • Иногда нам приходится вручную настраивать часть DAG, и для этого мы используем Def.sequentialилиDef.taskDyn
  • Как только эти синтаксические странности упорядочения / взаимосвязи устранены, мы можем полагаться на обычную семантику Scala для построения остальной бизнес-логики задач.

 Команды против задач

Команды - это ленивый выход из DAG. Используя команды, легко изменить состояние сборки и сериализовать задачи по своему усмотрению. Цена состоит в том, что мы теряем распараллеливание и дедупликацию задач, предоставляемых DAG, поэтому задачи должны быть предпочтительным выбором. Вы можете думать о командах как о чем-то вроде постоянной записи сеанса, который можно делать внутри sbt shell. Например, учитывая

vval x = settingKey[Int]("")
x := 13
lazy val f = taskKey[Int]("")
f := 1 + x.value

рассмотрим результат следующего сеанса

sbt:root> x
[info] 13
sbt:root> show f
[info] 14
sbt:root> set x := 41
[info] Defining x
[info] The new value will be used by f
[info] Reapplying settings...
sbt:root> show f
[info] 42

В частности, не то, как мы изменяем состояние сборки с помощью set x := 41. Команды позволяют нам делать постоянную запись вышеуказанного сеанса, например

commands += Command.command("cmd") { state =>
  "x" :: "show f" :: "set x := 41" :: "show f" :: state
}

Мы также можем сделать команду типобезопасной, используя Project.extractиrunTask

commands += Command.command("cmd") { state =>
  val log = state.log
  import Project._
  log.info(x.value.toString)
  val (_, resultBefore) = extract(state).runTask(f, state)
  log.info(resultBefore.toString)
  val mutatedState = extract(state).appendWithSession(Seq(x := 41), state)
  val (_, resultAfter) = extract(mutatedState).runTask(f, mutatedState)
  log.info(resultAfter.toString)
  mutatedState
}

Области применения

Области видимости вступают в игру, когда мы пытаемся ответить на следующие типы вопросов

  • Как определить задачу один раз и сделать ее доступной для всех подпроектов в многопроектной сборке?
  • Как избежать тестовых зависимостей от основного пути к классам?

sbt имеет многоосное пространство области видимости, по которому можно перемещаться с помощью синтаксиса косой черты , например,

show  root   /  Compile         /  compile   /   scalacOptions
        |        |                  |             |
     project    configuration      task          key

Лично мне редко приходится беспокоиться о размахе. Иногда хочется скомпилировать только тестовые исходники

Test/compile

или, возможно, выполнить конкретную задачу из определенного подпроекта без предварительного перехода к этому проекту с помощью project subprojB

subprojB/Test/compile

Я думаю, что следующие практические правила помогут избежать осложнений при оценке объема работ.

  • не имеют нескольких build.sbtфайлов, а только один главный в корневом проекте, который контролирует все другие подпроекты
  • делиться задачами через автоматические плагины
  • вынести общие настройки в простой Scala valи явно добавить их в каждый подпроект

Многопроектная сборка

Вместо нескольких файлов build.sbt для каждого подпроекта

.
├── README.md
├── build.sbt                  // OK
├── multi1
│   ├── build.sbt              // NOK
│   ├── src
│   └── target
├── multi2
│   ├── build.sbt              // NOK
│   ├── src
│   └── target
├── project                    // this is the meta-project
│   ├── FooPlugin.scala        // custom auto plugin
│   ├── build.properties       // version of sbt and hence Scala for meta-project
│   ├── build.sbt              // OK - this is actually for meta-project 
│   ├── plugins.sbt            // OK
│   ├── project
│   └── target
└── target

Имейте одного хозяина, build.sbtчтобы управлять ими всеми

.
├── README.md
├── build.sbt                  // single build.sbt to rule theme all
├── common
│   ├── src
│   └── target
├── multi1
│   ├── src
│   └── target
├── multi2
│   ├── src
│   └── target
├── project
│   ├── FooPlugin.scala
│   ├── build.properties
│   ├── build.sbt
│   ├── plugins.sbt
│   ├── project
│   └── target
└── target

Существует обычная практика выделения общих настроек в многопроектных сборках.

определить последовательность общих настроек в val и добавить их в каждый проект. Меньше концепций, которые нужно изучать таким образом.

например

lazy val commonSettings = Seq(
  scalacOptions := Seq(
    "-Xfatal-warnings",
    ...
  ),
  publishArtifact := true,
  ...
)

lazy val root = project
  .in(file("."))
  .settings(settings)
  .aggregate(
    multi1,
    multi2
  )
lazy val multi1 = (project in file("multi1")).settings(commonSettings)
lazy val multi2 = (project in file("multi2")).settings(commonSettings)

Навигация по проектам

projects         // list all projects
project multi1   // change to particular project

Плагины

Помните, что определение сборки - это правильный проект Scala, который находится внутри project/. Здесь мы определяем плагин, создавая .scalaфайлы

.                          // directory of the (main) proper project
├── project
│   ├── FooPlugin.scala    // auto plugin
│   ├── build.properties   // version of sbt library and indirectly Scala used for the plugin
│   ├── build.sbt          // build definition of the plugin
│   ├── plugins.sbt        // these are plugins for the main (proper) project, not the meta project
│   ├── project            // the turtle supporting this turtle
│   └── target             // compiled binaries of the plugin

Вот минимальный автоматический плагин подproject/FooPlugin.scala

object FooPlugin extends AutoPlugin {
  object autoImport {
      val barTask = taskKey[Unit]("")
  }

  import autoImport._

  override def requires = plugins.JvmPlugin  // avoids having to call enablePlugin explicitly
  override def trigger = allRequirements

  override lazy val projectSettings = Seq(
    scalacOptions ++= Seq("-Xfatal-warnings"),
    barTask := { println("hello task") },
    commands += Command.command("cmd") { state =>
      """eval println("hello command")""" :: state
    }   
  )
}

Отмена

override def requires = plugins.JvmPlugin

должны эффективно включить плагин для всех суб-проектов без необходимости вызывать явно enablePluginв build.sbt.

IntelliJ и sbt

Пожалуйста, включите следующий параметр (который действительно должен быть включен по умолчанию )

use sbt shell

под

Preferences | Build, Execution, Deployment | sbt | sbt projects

Ключевые ссылки

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.