Kurulum
brew install sbt
veya teknik açıdan bakıldığında şunlardan oluşan benzer kurulumlar
sbt
Terminalden çalıştırdığınızda , aslında sbt başlatıcısı bash betiğini çalıştırır. Şahsen, bu üçlü hakkında asla endişelenmek zorunda kalmadım ve sbt'yi tek bir şeymiş gibi kullan.
Yapılandırma
Belirli bir proje için sbt'yi projenin .sbtopts
köküne kaydetme dosyasını yapılandırmak için . Sistem genelinde sbt'yi yapılandırmak için /usr/local/etc/sbtopts
. Yürütme sbt -help
size tam yeri söylemelidir. Örneğin, vermek sbt fazla bellek için tek seferlik olarak yürütmek sbt -mem 4096
veya kaydetmek -mem 4096
içinde .sbtopts
veya sbtopts
kalıcı olarak etkili olması için bellek artışı için.
Proje yapısı
sbt new scala/scala-seed.g8
minimal bir Hello World sbt proje yapısı oluşturur
.
├── 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
Sık kullanılan komutlar
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
Sayısız mermi
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
Yapı tanımı, uygun bir Scala projesidir
Bu, temel deyimsel sbt kavramlarından biridir. Bir soru ile açıklamaya çalışacağım. Scalaj-http ile bir HTTP isteğini yürütecek bir sbt görevi tanımlamak istediğinizi varsayalım. Sezgisel olarak aşağıdakileri içeride deneyebilirizbuild.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
...
}
Ancak bu eksik diyerek hata verecektir import scalaj.http._
. Yukarıya eklendiğimizde bu nasıl mümkün scalaj-http
olabilir libraryDependencies
? Dahası, bunun yerine bağımlılığı eklediğimizde neden çalışıyor project/build.sbt
?
// project/build.sbt
libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2"
Cevap şu ki fooTask
bu aslında ana projenizden ayrı bir Scala projesinin parçası . Bu farklı Scala projesi , derlenmiş sınıflarının bulunduğu project/
kendi target/
dizini olan dizinin altında bulunabilir . Aslında, altında aşağıdaki project/target/config-classes
gibi bir şeye kod çözen bir sınıf olmalı
object $9c2192aea3f1db3c251d extends scala.AnyRef {
lazy val fooTask : sbt.TaskKey[scala.Unit] = { /* compiled code */ }
lazy val root : sbt.Project = { /* compiled code */ }
}
Bunun fooTask
, isimli normal bir Scala nesnesinin üyesi olduğunu görüyoruz $9c2192aea3f1db3c251d
. Açıktır ki , uygun projenin bağımlılığı değil, scalaj-http
proje tanımlamasına bağlı olmalıdır $9c2192aea3f1db3c251d
. Bu nedenle , Scala projesinin inşa tanımının bulunduğu yer olduğu için project/build.sbt
bunun yerine bildirilmesi gerekir .build.sbt
project
Oluşturma tanımının yalnızca başka bir Scala projesi olduğu noktayı yönlendirmek için, çalıştırın sbt consoleProject
. Bu, Scala REPL'yi sınıf yolunda derleme tanımı projesiyle birlikte yükleyecektir. Satırları boyunca bir içe aktarma görmelisiniz
import $9c2192aea3f1db3c251d
Artık, build.sbt
DSL yerine Scala ile adlandırarak, yapı tanımlama projesiyle doğrudan etkileşime girebiliriz . Örneğin, aşağıdakifooTask
$9c2192aea3f1db3c251d.fooTask.eval
build.sbt
kök projesi altında Scala projesini tanımlamaya yardımcı olan özel bir DSL'dir project/
.
Ve Scala projesinin tanımını inşa et, kendi inşa tanımı Scala projesine sahip olabilir project/project/
ve benzeri. Sbt'nin yinelemeli olduğunu söylüyoruz .
sbt varsayılan olarak paraleldir
sbt , görevlerden DAG oluşturur . Bu, görevler arasındaki bağımlılıkları analiz etmesine ve bunları paralel olarak yürütmesine ve hatta tekilleştirme gerçekleştirmesine olanak tanır. build.sbt
DSL, başlangıçta şaşırtıcı anlambilimlere yol açabilecek bu düşünceyle tasarlanmıştır. Aşağıdaki kod parçasında yürütme sırasının ne olduğunu düşünüyorsunuz?
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
}
Sezgisel olarak, burada akışın önce yazdırıp hello
sonra yürütmek a
ve sonra b
görev yapmak olduğunu düşünebilirsiniz . Ancak bu aslında yürütmek demektir a
ve b
içinde paralel ve daha önce println("hello")
bu kadar
a
b
hello
veya sırası nedeniyle a
ve b
garanti edilmemektedir
b
a
hello
Belki paradoksal olarak, sbt'de paralel yapmak seriye göre daha kolaydır. Seri sipariş vermeye ihtiyacınız varsa, anlamak içinDef.sequential
veya Def.taskDyn
taklit etmek gibi özel şeyler kullanmanız gerekecektir .
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
benzer
for {
h <- Future(println("hello"))
a <- Future(println("a"))
b <- Future(println("b"))
} yield ()
bileşenler arasında bağımlılık olmadığını gördüğümüzde,
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
benzer
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 }
nerede göreceğimiz sum
bağlıdır ve beklemek zorundadır a
ve b
.
Diğer bir deyişle
- uygulamalı anlamlar için kullanın
.value
- için monadic semantik kullanmak
sequential
veyataskDyn
Bağımlılık oluşturma doğasının bir sonucu olarak anlamsal olarak kafa karıştırıcı başka bir pasajı düşünün value
, where yerine
`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.
val x = version.value
^
yazmak zorundayız
val x = settingKey[String]("")
x := version.value
Sözdiziminin .value
DAG'deki ilişkilerle ilgili olduğunu ve şu anlama gelmediğini unutmayın:
"bana hemen değeri ver"
bunun yerine şu anlama geliyor
"Arayanım önce bana güveniyor ve tüm DAG'nin birbirine nasıl uyduğunu öğrendiğimde, arayan kişiye istenen değeri sağlayabileceğim"
Öyleyse, neden x
henüz bir değerin atanamayacağı biraz daha açık olabilir; ilişki kurma aşamasında henüz bir değer yoktur.
Scala ve DSL dili arasında anlambilim açısından build.sbt
. İşte benim için çalışan birkaç önemli kural
- DAG, türdeki ifadelerden oluşur
Setting[T]
- Çoğu durumda biz sadece
.value
sözdizimi kullanırız ve sbt arasında ilişki kurmakla ilgilenirizSetting[T]
- Bazen DAG'nin bir bölümünü manuel olarak ayarlamamız gerekir ve bunun için
Def.sequential
veyaDef.taskDyn
- Bu sıralama / ilişki sözdizimsel tuhaflıkları halledildikten sonra, görevlerin geri kalan iş mantığını oluşturmak için olağan Scala semantiğine güvenebiliriz.
Komutlar ve Görevler
Komutlar, DAG'den çıkmanın tembel bir yoludur. Komutları kullanarak, derleme durumunu değiştirmek ve görevleri istediğiniz gibi serileştirmek kolaydır. Maliyet, DAG tarafından sağlanan görevlerin paralelleştirilmesini ve tekilleştirilmesini kaybetmemizdir; bu, görevler tercih edilen seçim olmalıdır. Komutları, bir kişinin içeride yapabileceği bir oturumun kalıcı bir kaydı olarak düşünebilirsiniz sbt shell
. Örneğin, verilen
vval x = settingKey[Int]("")
x := 13
lazy val f = taskKey[Int]("")
f := 1 + x.value
aşağıdaki oturumun çıktısını düşünün
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
Özellikle, inşa durumunu nasıl değiştirdiğimiz değil set x := 41
. Komutlar, örneğin yukarıdaki oturumun kalıcı bir kaydını yapmamızı sağlar.
commands += Command.command("cmd") { state =>
"x" :: "show f" :: "set x := 41" :: "show f" :: state
}
Ayrıca Project.extract
ve kullanarak komut türünü güvenli hale getirebiliriz.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
}
Kapsamlar
Aşağıdaki türden soruları cevaplamaya çalıştığımızda kapsamlar devreye girer
- Görev bir kez nasıl tanımlanır ve çok projeli derlemede tüm alt projeler için nasıl kullanılabilir hale getirilir?
- Ana sınıf yolunda test bağımlılıklarından nasıl kaçınılır?
sbt, eğik çizgi sözdizimi kullanılarak gezinilebilen çok eksenli bir kapsam alanına sahiptir, örneğin,
show root / Compile / compile / scalacOptions
| | | |
project configuration task key
Şahsen, kendimi nadiren kapsam hakkında endişelenmek zorunda buluyorum. Bazen sadece test kaynaklarını derlemek istiyorum
Test/compile
veya belki de belirli bir alt projeden belirli bir görevi, önce o projeye gitmek zorunda kalmadan yürütmek project subprojB
subprojB/Test/compile
Aşağıdaki genel kuralların kapsam oluşturma komplikasyonlarını önlemeye yardımcı olduğunu düşünüyorum
- birden fazla
build.sbt
dosyaya sahip değil, yalnızca diğer tüm alt projeleri kontrol eden kök proje altında tek bir ana dosyaya sahip
- otomatik eklentiler aracılığıyla görevleri paylaşın
- ortak ayarları düz Scala'ya ayırın
val
ve her bir alt projeye açıkça ekleyin
Çoklu proje oluşturma
Her alt proje için birden çok build.sbt dosyası yerine
.
├── 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
Hepsine hükmedecek tek bir efendimiz olsun
.
├── 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
Çoklu proje yapılarında ortak ayarları dışarıda bırakmanın yaygın bir uygulaması vardır
bir değerde bir dizi ortak ayar tanımlayın ve bunları her projeye ekleyin. Bu şekilde öğrenmek için daha az kavram.
Örneğin
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)
Projeler navigasyonu
projects // list all projects
project multi1 // change to particular project
Eklentiler
Unutmayın, inşa tanımı altında bulunan uygun bir Scala projesi project/
. .scala
Dosya oluşturarak bir eklenti tanımladığımız yer burasıdır
. // 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
İşte altında minimum bir otomatik eklentiproject/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
}
)
}
Geçersiz kılma
override def requires = plugins.JvmPlugin
etkin bir şekilde açıkça aramak zorunda kalmadan tüm alt projeler için eklenti imkan vermelidir enablePlugin
içinde build.sbt
.
IntelliJ ve sbt
Lütfen aşağıdaki ayarı etkinleştirin (bu, varsayılan olarak gerçekten etkinleştirilmelidir )
use sbt shell
altında
Preferences | Build, Execution, Deployment | sbt | sbt projects
Anahtar referanslar