Чи може хтось пояснити правильний спосіб використання 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 (або піднесений) для редагування шкали, я цього не знав. Я думаю, що конструктор генерує власні конфігурації 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

Найголовніше - це, мабуть, як ви знайдете правильні сховища та версії, які слід включити до вашого проекту? Я просто витягую мачете і починаю рубати вперед? Я досить часто зустрічаю проекти, які включають все і кухонну раковину

Щодо залежностей, що базуються на Скалі, я б пішов з тим, що рекомендують автори. Наприклад: 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"

Потім витягніть мачете і починайте рубати свій шлях вперед. Якщо вам пощастило, ви не можете використовувати банки, які залежать від одних і тих же банок, але з несумісними версіями. Враховуючи екосистему 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 підходом - він масштабується і є більш гнучким
  3. Створити project/plugins.sbt файл та додайте відповідний плагін для вашої IDE. Або sbt-eclipse, sbt-idea або ensime-sbt-cmd, щоб ви могли генерувати файли проектів для затемнення, intellij або тимчасового часу.
  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
Я частково до думки, що, коли сумніваєтесь, додавання в суміш більше магії, швидше за все, не вирішить ваших проблем.
Кубік

0

Установка

brew install sbt або подібне встановлює sbt, з якого технічно кажучи складається

Після запуску sbtз терміналу він фактично запускає скрипт bash запуску sbt. Особисто мені ніколи не доводилося турбуватися про цю трійцю, а просто використовувати 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 проектом визначення складання на classpath. Ви повинні бачити імпорт уздовж рядків

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.