Adakah yang bisa menjelaskan cara yang benar untuk menggunakan SBT?


100

Aku keluar dari lemari ini! Saya tidak mengerti SBT. Di sana, saya mengatakannya, sekarang tolong bantu saya.

Semua jalan menuju Roma, dan itu adalah sama untuk SBT: Untuk memulai SBTada SBT, SBT Launcher, SBT-extras, dll, dan kemudian ada berbagai cara untuk memasukkan dan memutuskan repositori. Apakah ada cara 'terbaik'?

Saya bertanya karena terkadang saya sedikit tersesat. Dokumentasi SBT sangat lengkap dan menyeluruh, tetapi saya tidak tahu kapan harus menggunakan build.sbtatau project/build.propertiesatau project/Build.scalaatau project/plugins.sbt.

Kemudian menjadi menyenangkan, ada Scala-IDEdan SBT- Apa cara yang benar untuk menggunakannya bersama? Apa yang lebih dulu, ayam atau telur?

Yang paling penting mungkin, bagaimana Anda menemukan repositori dan versi yang tepat untuk disertakan dalam proyek Anda? Apakah saya baru saja mengeluarkan parang dan mulai meretas jalan ke depan? Saya cukup sering menemukan proyek yang mencakup semuanya dan wastafel dapur, dan kemudian saya menyadari - saya bukan satu-satunya yang sedikit tersesat.

Sebagai contoh sederhana, saat ini, saya sedang memulai proyek baru. Saya ingin menggunakan fitur terbaru dari SLICKdan Scaladan ini mungkin memerlukan SBT versi terbaru. Apa hal yang waras untuk memulai, dan mengapa? Dalam file apa saya harus mendefinisikannya dan bagaimana tampilannya? Saya tahu saya bisa mendapatkan ini berfungsi, tetapi saya benar-benar ingin pendapat ahli tentang ke mana semuanya harus pergi (mengapa harus pergi akan ada bonus).

Saya telah menggunakan SBTuntuk proyek kecil selama lebih dari setahun sekarang. Saya menggunakan SBTdan kemudian SBT Extras(karena itu membuat beberapa sakit kepala hilang secara ajaib), tetapi saya tidak yakin mengapa saya harus menggunakan yang satu atau yang lain. Saya hanya merasa sedikit frustrasi karena tidak memahami bagaimana segala sesuatunya cocok ( SBTdan repositori), dan berpikir itu akan menyelamatkan orang yang datang dengan cara ini dari banyak kesulitan jika hal ini dapat dijelaskan dalam istilah manusia.


2
Apa sebenarnya yang Anda maksud dengan "ada Scala-IDE dan SBT"? Anda mendefinisikan proyek Anda dengan sbt dan sbt dapat menghasilkan proyek ide (eclipse oder intellij). Jadi SBT diutamakan ...
Jan

2
@Jan Saya sebutkan itu karena Scala-IDE menggunakan SBT sebagai manajer pembangunan. Lihat assembla.com/spaces/scala-ide/wiki/SBT-based_build_manager dan lebih rendah di posting mereka menyebutkan "Tidak perlu mendefinisikan file proyek SBT Anda." yang menurut saya membingungkan.
Jack

baik. Karena saya biasanya menggunakan intellij (atau sublime) untuk mengedit scala, saya tidak tahu itu. Saya kira pembangun menghasilkan konfigurasi sbt sendiri?
Jan

2
@JacobusR Fakta bahwa Scala IDE menggunakan SBT untuk membangun sumber proyek Anda adalah detail implementasi, dan pengguna tidak perlu khawatir tentang ini. Benar-benar ada 0 implikasi. Di luar pengguna Eclipse dapat membangun proyek dengan SBT, Maven, Ant, ..., dan itu tidak akan membuat perbedaan apa pun untuk Scala IDE. Satu hal lagi, bahkan jika Anda memiliki proyek SBT, Scala IDE tidak peduli, yaitu, tidak mencari Anda Build.scalauntuk menyiapkan classpath, dan itulah mengapa Anda benar-benar membutuhkan sbteclipse untuk menghasilkan Eclipse .classpath. Semoga ini membantu.
Mirco Dotta

1
@Jan Scala IDE menambah kebingungan, dan ya, dokumentasi yang memberikan gambaran yang lebih besar tentang pengaturan lingkungan pengembangan Scala yang baik dan beberapa panduan yang solid dari alur kerja pemrograman yang sesuai akan sangat berguna.
Jack

Jawaban:


29

Yang paling penting mungkin, bagaimana Anda menemukan repositori dan versi yang tepat untuk disertakan dalam proyek Anda? Apakah saya baru saja mengeluarkan parang dan mulai meretas jalan ke depan? Saya cukup sering menemukan proyek yang mencakup semuanya dan wastafel dapur

Untuk dependensi berbasis Scala, saya akan mengikuti apa yang penulis rekomendasikan. Misalnya: http://code.google.com/p/scalaz/#SBT menunjukkan untuk menggunakan:

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

Atau https://github.com/typesafehub/sbteclipse/ memiliki instruksi tentang tempat untuk menambahkan:

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

Untuk dependensi berbasis Java, saya menggunakan http://mvnrepository.com/ untuk melihat apa yang ada di luar sana, lalu klik pada tab SBT. Misalnya http://mvnrepository.com/artifact/net.sf.opencsv/opencsv/2.3 menunjukkan untuk menggunakan:

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

Kemudian tarik keluar parang dan mulailah meretas jalan ke depan. Jika Anda beruntung, Anda tidak akan menggunakan toples yang bergantung pada beberapa toples yang sama tetapi dengan versi yang tidak kompatibel. Mengingat ekosistem Java, Anda sering kali berakhir dengan memasukkan semuanya dan wastafel dapur dan perlu beberapa upaya untuk menghilangkan ketergantungan atau memastikan Anda tidak melewatkan ketergantungan yang diperlukan.

Sebagai contoh sederhana, saat ini, saya sedang memulai proyek baru. Saya ingin menggunakan fitur SLICK dan Scala terbaru dan ini mungkin memerlukan SBT versi terbaru. Apa hal yang waras untuk memulai, dan mengapa?

Saya pikir hal yang waras adalah membangun kekebalan sbt secara bertahap .

Pastikan Anda mengerti:

  1. format cakupan {<build-uri>}<project-id>/config:key(for task-key)
  2. 3 rasa pengaturan ( SettingKey, TaskKey, InputKey) - baca bagian yang disebut "Task Keys" di http://www.scala-sbt.org/release/docs/Getting-Started/Basic-Def

Biarkan 4 halaman itu terbuka setiap saat sehingga Anda dapat melompat dan mencari berbagai definisi dan contoh:

  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

Manfaatkan showdan inspect dan penyelesaian tab secara maksimal untuk membiasakan diri dengan nilai sebenarnya dari pengaturan, ketergantungannya, definisi dan pengaturan terkait. Saya tidak percaya hubungan yang akan Anda temukan menggunakan inspectdidokumentasikan di mana pun. Jika ada cara yang lebih baik saya ingin mengetahuinya.


25

Cara saya menggunakan sbt adalah:

  1. Gunakan sbt-extras - dapatkan saja skrip shell dan tambahkan ke root proyek Anda
  2. Buat projectfolder dengan MyProject.scalafile untuk menyiapkan sbt. Saya lebih suka ini daripada build.sbtpendekatan - ini scala dan lebih fleksibel
  3. Buat project/plugins.sbtfile dan tambahkan plugin yang sesuai untuk IDE Anda. Baik sbt-eclipse, sbt-idea atau ensime-sbt-cmd sehingga Anda dapat menghasilkan file proyek untuk eclipse, intellij atau ensime.
  4. Luncurkan sbt di root proyek Anda dan buat file proyek untuk IDE Anda
  5. Keuntungan

Saya tidak repot-repot memeriksa file proyek IDE karena dibuat oleh sbt, tetapi mungkin ada alasan Anda ingin melakukannya.

Anda dapat melihat contoh penyiapan seperti ini di sini .


Terima kasih atas jawaban yang bagus. Saya menerima jawaban yang lain, karena itu mencakup lebih banyak tempat, dan memilih alasan Anda, itu juga sangat bagus. Saya akan menerima keduanya jika saya bisa.
Jack

0

Gunakan Typeafe Activator, cara mewah untuk memanggil sbt, yang dilengkapi dengan template dan seed proyek: 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
Saya parsial pada gagasan bahwa, jika ragu, menambahkan lebih banyak keajaiban ke dalam campuran tidak mungkin menyelesaikan masalah Anda.
Kubik

0

Instalasi

brew install sbt atau instalasi serupa sbt yang secara teknis terdiri dari

Ketika Anda mengeksekusi sbtdari terminal, itu benar-benar menjalankan skrip sbt launcher bash. Secara pribadi, saya tidak perlu khawatir tentang trinitas ini, dan hanya menggunakan sbt seolah-olah itu adalah satu hal.

Konfigurasi

Untuk mengkonfigurasi sbt untuk proyek tertentu, simpan .sbtoptsfile di root proyek. Untuk mengkonfigurasi modifikasi sbt di seluruh sistem /usr/local/etc/sbtopts. Pelaksana sbt -helpharus memberi tahu Anda lokasi yang tepat. Misalnya, untuk memberikan sbt lebih banyak memori saat satu kali dijalankan sbt -mem 4096, atau simpan -mem 4096dalam .sbtoptsatau sbtoptsagar peningkatan memori berlaku secara permanen.

 Struktur proyek

sbt new scala/scala-seed.g8 membuat struktur proyek Hello World sbt minimal

.
├── 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

Perintah yang sering

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

Segudang cangkang

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

Definisi build adalah proyek Scala yang tepat

Ini adalah salah satu konsep kunci idiomatik sbt. Saya akan mencoba menjelaskan dengan sebuah pertanyaan. Katakanlah Anda ingin mendefinisikan tugas sbt yang akan menjalankan permintaan HTTP dengan scalaj-http. Secara intuitif kita mungkin mencoba yang berikut di dalambuild.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
  ...
}

Namun ini akan kesalahan mengatakan hilang import scalaj.http._. Bagaimana ini mungkin ketika kita, tepat di atas, ditambahkan scalaj-httpke libraryDependencies? Lebih jauh, mengapa ini berfungsi ketika, sebagai gantinya, kita menambahkan ketergantungan ke project/build.sbt?

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

Jawabannya adalah itu fooTasksebenarnya adalah bagian dari proyek Scala yang terpisah dari proyek utama Anda. Proyek Scala yang berbeda ini dapat ditemukan di bawah project/direktori yang memiliki target/direktori sendiri tempat kelas yang dikompilasinya berada. Sebenarnya, di bawah project/target/config-classesharus ada kelas yang mendekompilasi menjadi seperti

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

Kami melihat itu fooTaskhanyalah anggota dari objek Scala biasa bernama $9c2192aea3f1db3c251d. Jelas scalaj-httpharus menjadi ketergantungan proyek yang mendefinisikan $9c2192aea3f1db3c251ddan bukan ketergantungan proyek yang tepat. Oleh karena itu perlu dideklarasikan project/build.sbtsebagai pengganti build.sbt, karena di projectsitulah definisi build proyek Scala berada.

Untuk menunjukkan bahwa definisi build hanyalah proyek Scala lainnya, jalankan sbt consoleProject. Ini akan memuat Scala REPL dengan proyek definisi build di classpath. Anda akan melihat impor di sepanjang baris

import $9c2192aea3f1db3c251d

Jadi sekarang kita dapat berinteraksi langsung dengan proyek definisi pembangunan dengan memanggilnya dengan Scala proper, bukan build.sbtDSL. Misalnya, eksekusi berikut inifooTask

$9c2192aea3f1db3c251d.fooTask.eval

build.sbtunder root project adalah DSL khusus yang membantu menentukan definisi build proyek Scala di bawah project/.

Dan membangun definisi proyek Scala, dapat memiliki proyek Scala definisi membangun sendiri di bawah project/project/dan seterusnya. Kami mengatakan sbt bersifat rekursif .

sbt sejajar secara default

sbt membangun DAG dari tugas. Ini memungkinkannya untuk menganalisis ketergantungan antara tugas dan menjalankannya secara paralel dan bahkan melakukan deduplikasi. build.sbtDSL dirancang dengan pemikiran ini, yang mungkin mengarah pada semantik awalnya yang mengejutkan. Menurut Anda, bagaimana urutan eksekusi dalam cuplikan berikut?

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
}

Secara intuitif orang mungkin berpikir aliran di sini adalah mencetak terlebih dahulu hellokemudian mengeksekusi a, dan kemudian btugas. Namun hal ini sebenarnya berarti mengeksekusi adan bdi paralel , dan sebelum println("hello") jadi

a
b
hello

atau karena urutan adan btidak dijamin

b
a
hello

Mungkin secara paradoks, di sbt lebih mudah melakukan paralel daripada serial. Jika Anda memerlukan pengurutan serial, Anda harus menggunakan hal-hal khusus seperti Def.sequentialatau Def.taskDynuntuk meniru pemahaman .

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

mirip dengan

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

di mana kami melihat tidak ada ketergantungan antar komponen, sementara

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

mirip dengan

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 }

di mana kita melihat sumtergantung dan harus menunggu adan b.

Dengan kata lain

  • untuk semantik aplikatif , gunakan.value
  • untuk penggunaan semantik monadsequential atautaskDyn

Pertimbangkan cuplikan lain yang secara semantik membingungkan sebagai akibat dari sifat membangun ketergantungan value, where, dan bukannya

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

kita harus menulis

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

Perhatikan bahwa sintaks .valueadalah tentang hubungan di DAG dan tidak berarti

"beri aku nilainya sekarang"

sebaliknya itu berarti sesuatu seperti

"Penelepon saya bergantung pada saya dulu, dan setelah saya tahu bagaimana seluruh DAG cocok, saya akan dapat memberikan nilai yang diminta kepada penelepon saya"

Jadi sekarang mungkin sedikit lebih jelas mengapa xbelum bisa diberi nilai; belum ada nilai yang tersedia dalam tahap membangun hubungan.

Kami dapat dengan jelas melihat perbedaan dalam semantik antara Scala proper dan bahasa DSL di build.sbt. Berikut adalah beberapa aturan praktis yang cocok untuk saya

  • DAG terbuat dari ekspresi tipe Setting[T]
  • Dalam kebanyakan kasus kami hanya menggunakan .valuesintaks dan sbt akan menjaga hubungan antaraSetting[T]
  • Kadang-kadang kami harus mengubah bagian DAG secara manual dan untuk itu kami menggunakan Def.sequentialatauDef.taskDyn
  • Setelah keanehan sintatis pengurutan / hubungan ini diatasi, kita dapat mengandalkan semantik Scala yang biasa untuk membangun logika bisnis tugas lainnya.

 Perintah vs Tugas

Perintah adalah jalan keluar malas dari DAG. Dengan menggunakan perintah, mudah untuk mengubah status build dan membuat serial tugas sesuai keinginan. Kerugiannya adalah kita kehilangan kesejajaran dan deduplikasi tugas yang disediakan oleh DAG, yang mana tugas harus menjadi pilihan yang lebih disukai. Anda dapat menganggap perintah sebagai semacam rekaman permanen dari sesi yang mungkin dilakukan di dalam sbt shell. Misalnya, diberikan

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

pertimbangkan hasil dari sesi berikutnya

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

Secara khusus, bukan cara kami mengubah status build dengan set x := 41. Perintah memungkinkan kita membuat rekaman permanen dari sesi di atas, misalnya

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

Kita juga bisa membuat perintah type-safe menggunakan Project.extractdanrunTask

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
}

Cakupan

Cakupan mulai berperan saat kami mencoba menjawab jenis pertanyaan berikut

  • Bagaimana cara mendefinisikan tugas sekali dan membuatnya tersedia untuk semua subproyek dalam multiproyek build?
  • Bagaimana cara menghindari ketergantungan pengujian pada jalur kelas utama?

sbt memiliki ruang lingkup multi-sumbu yang dapat dinavigasi menggunakan sintaks garis miring , misalnya,

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

Secara pribadi, saya jarang merasa khawatir tentang ruang lingkup. Terkadang saya hanya ingin mengumpulkan sumber pengujian

Test/compile

atau mungkin menjalankan tugas tertentu dari subproyek tertentu tanpa harus menavigasi ke proyek itu terlebih dahulu dengan project subprojB

subprojB/Test/compile

Saya pikir aturan praktis berikut membantu menghindari komplikasi pelingkupan

  • tidak memiliki banyak build.sbtfile tetapi hanya satu file master di bawah proyek root yang mengontrol semua subproyek lainnya
  • berbagi tugas melalui plugin otomatis
  • faktor keluar pengaturan umum menjadi Scala biasa valdan secara eksplisit menambahkannya ke setiap sub-proyek

Pembangunan multi-proyek

Alih-alih beberapa file build.sbt untuk setiap subproyek

.
├── 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

Miliki satu master build.sbtuntuk mengatur semuanya

.
├── 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

Ada praktik umum dalam memfaktorkan setelan umum dalam build multi-project

menentukan urutan pengaturan umum di val dan menambahkannya ke setiap proyek. Lebih sedikit konsep untuk dipelajari dengan cara itu.

sebagai contoh

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)

Proyek navigasi

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

Plugin

Ingat definisi build adalah proyek Scala yang tepat yang berada di bawah project/. Di sinilah kami mendefinisikan plugin dengan membuat .scalafile

.                          // 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

Berikut adalah plugin otomatis minimal di bawahproject/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
    }   
  )
}

Timpa

override def requires = plugins.JvmPlugin

harus efektif mengaktifkan plugin untuk semua sub-proyek tanpa harus memanggil secara eksplisit enablePlugindalam build.sbt.

IntelliJ dan sbt

Harap aktifkan pengaturan berikut (yang seharusnya diaktifkan secara default )

use sbt shell

dibawah

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

Referensi kunci

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.