Cài đặt
brew install sbt
hoặc số lượt cài đặt tương tự sbt mà về mặt kỹ thuật mà nói bao gồm
Khi bạn thực thi sbt
từ thiết bị đầu cuối, nó thực sự chạy tập lệnh bash của trình khởi chạy sbt. Cá nhân tôi, tôi không bao giờ phải lo lắng về ba ngôi này, và chỉ sử dụng sbt như thể nó là một thứ duy nhất.
Cấu hình
Để cấu hình sbt cho một dự án cụ thể lưu .sbtopts
tệp ở gốc của dự án. Để cấu hình sửa đổi toàn hệ thống sbt /usr/local/etc/sbtopts
. Việc thực thi sbt -help
sẽ cho bạn biết vị trí chính xác. Ví dụ: để cung cấp thêm bộ nhớ cho sbt khi thực thi một lần sbt -mem 4096
, hoặc lưu -mem 4096
vào .sbtopts
hoặc sbtopts
để tăng bộ nhớ có hiệu lực vĩnh viễn.
Cấu trúc dự án
sbt new scala/scala-seed.g8
tạo cấu trúc dự án Hello World sbt tối thiểu
.
├── 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
Các lệnh thường xuyên
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
Vô số vỏ
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
Định nghĩa xây dựng là một dự án Scala phù hợp
Đây là một trong những khái niệm sbt thành ngữ chính. Tôi sẽ cố gắng giải thích bằng một câu hỏi. Giả sử bạn muốn xác định một nhiệm vụ sbt sẽ thực thi một yêu cầu HTTP với scalaj-http. Bằng trực giác, chúng ta có thể thử những điều sau bên trongbuild.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
...
}
Tuy nhiên điều này sẽ báo lỗi thiếu import scalaj.http._
. Làm thế nào điều này có thể thực hiện được khi chúng tôi, ngay phía trên, được thêm scalaj-http
vào libraryDependencies
? Hơn nữa, tại sao nó hoạt động khi thay vào đó, chúng ta thêm phần phụ thuộc vào project/build.sbt
?
// project/build.sbt
libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2"
Câu trả lời là đó fooTask
thực sự là một phần của dự án Scala riêng biệt với dự án chính của bạn. Dự án Scala khác nhau này có thể được tìm thấy trong project/
thư mục có target/
thư mục riêng nơi các lớp được biên dịch của nó cư trú. Trên thực tế, bên dưới project/target/config-classes
nên có một lớp dịch ngược thành một cái gì đó như
object $9c2192aea3f1db3c251d extends scala.AnyRef {
lazy val fooTask : sbt.TaskKey[scala.Unit] = { /* compiled code */ }
lazy val root : sbt.Project = { /* compiled code */ }
}
Chúng ta thấy đó fooTask
chỉ đơn giản là một thành viên của một đối tượng Scala thông thường được đặt tên $9c2192aea3f1db3c251d
. Rõ ràng scalaj-http
phải là sự phụ thuộc của dự án xác định $9c2192aea3f1db3c251d
chứ không phải là sự phụ thuộc của dự án thích hợp. Do đó, nó cần phải được khai báo trong project/build.sbt
thay vì build.sbt
, bởi vì project
dự án Scala định nghĩa xây dựng nằm ở đâu.
Để thúc đẩy quan điểm rằng định nghĩa xây dựng chỉ là một dự án Scala khác, hãy thực thi sbt consoleProject
. Điều này sẽ tải Scala REPL với dự án định nghĩa xây dựng trên classpath. Bạn sẽ thấy một nhập dọc theo các dòng
import $9c2192aea3f1db3c251d
Vì vậy, bây giờ chúng ta có thể tương tác trực tiếp với dự án định nghĩa xây dựng bằng cách gọi nó bằng Scala thích hợp thay vì build.sbt
DSL. Ví dụ, các thực thi saufooTask
$9c2192aea3f1db3c251d.fooTask.eval
build.sbt
trong dự án gốc là một DSL spcial giúp xác định định nghĩa xây dựng dự án Scala theo project/
.
Và xây dựng định nghĩa dự án Scala, có thể có định nghĩa xây dựng dự án Scala của riêng mình theo project/project/
và như vậy. Chúng tôi nói rằng sbt là đệ quy .
sbt là song song theo mặc định
sbt xây dựng DAG ngoài nhiệm vụ. Điều này cho phép nó phân tích sự phụ thuộc giữa các tác vụ và thực thi chúng song song và thậm chí thực hiện việc khử trùng lặp. build.sbt
DSL được thiết kế với tâm trí này, có thể dẫn đến ngữ nghĩa ban đầu đáng ngạc nhiên. Bạn nghĩ thứ tự thực hiện trong đoạn mã sau là gì?
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
}
Theo trực giác, người ta có thể nghĩ rằng quy trình ở đây là đầu tiên in ra hello
sau đó thực thi a
, sau đó là b
tác vụ. Tuy nhiên điều này thực sự có nghĩa là thực hiện a
và b
trong song song , và trước khi println("hello")
quá
a
b
hello
hoặc vì thứ tự của a
và b
không được đảm bảo
b
a
hello
Có lẽ nghịch lý là trong sbt thì làm song song dễ hơn nối tiếp. Nếu bạn cần sắp xếp thứ tự, bạn sẽ phải sử dụng những thứ đặc biệt như Def.sequential
hoặc Def.taskDyn
để mô phỏng để hiểu .
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
tương tự như
for {
h <- Future(println("hello"))
a <- Future(println("a"))
b <- Future(println("b"))
} yield ()
nơi chúng tôi thấy không có sự phụ thuộc giữa các thành phần, trong khi
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
tương tự như
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 }
nơi chúng ta thấy sum
phụ thuộc vào và phải chờ đợi a
và b
.
Nói cách khác
- đối với ngữ nghĩa ứng dụng , sử dụng
.value
- để sử dụng ngữ nghĩa đơn nguyên
sequential
hoặctaskDyn
Hãy xem xét một đoạn mã khác khó hiểu về mặt ngữ nghĩa do bản chất xây dựng phụ thuộc của value
, where thay vì
`value` can only be used within a task or setting macro, such as :=, +=, ++=, Def.task, or Def.setting.
val x = version.value
^
chúng ta phải viết
val x = settingKey[String]("")
x := version.value
Lưu ý rằng cú pháp .value
là về các mối quan hệ trong DAG và không có nghĩa là
"cho tôi giá trị ngay bây giờ"
thay vào đó nó có nghĩa là
"Người gọi của tôi phụ thuộc vào tôi trước tiên và khi tôi biết cách toàn bộ DAG khớp với nhau, tôi sẽ có thể cung cấp cho người gọi của mình giá trị được yêu cầu"
Vì vậy, bây giờ có thể rõ ràng hơn một chút tại sao x
vẫn chưa thể gán giá trị; chưa có giá trị nào trong giai đoạn xây dựng mối quan hệ.
Chúng ta có thể thấy rõ sự khác biệt về ngữ nghĩa giữa ngôn ngữ Scala thích hợp và ngôn ngữ DSL trong build.sbt
. Dưới đây là một số quy tắc ngón tay cái phù hợp với tôi
- DAG được tạo ra từ các biểu thức của loại
Setting[T]
- Trong hầu hết các trường hợp, chúng tôi chỉ sử dụng
.value
cú pháp và sbt sẽ đảm nhận việc thiết lập mối quan hệ giữaSetting[T]
- Đôi khi, chúng tôi phải tinh chỉnh thủ công một phần của DAG và chúng tôi sử dụng
Def.sequential
hoặcDef.taskDyn
- Khi những điều kỳ lạ về thứ tự / mối quan hệ này được xử lý, chúng ta có thể dựa vào ngữ nghĩa Scala thông thường để xây dựng phần còn lại của logic nghiệp vụ của các tác vụ.
Lệnh so với Nhiệm vụ
Các lệnh là một cách lười biếng ra khỏi DAG. Sử dụng các lệnh, bạn có thể dễ dàng thay đổi trạng thái xây dựng và tuần tự hóa các tác vụ theo ý muốn. Cái giá phải trả là chúng ta mất khả năng song song và loại trừ các nhiệm vụ do DAG cung cấp, theo cách nào thì các nhiệm vụ sẽ là lựa chọn ưu tiên. Bạn có thể coi các lệnh như một kiểu ghi lại vĩnh viễn một phiên mà người ta có thể thực hiện bên trong sbt shell
. Ví dụ, cho
vval x = settingKey[Int]("")
x := 13
lazy val f = taskKey[Int]("")
f := 1 + x.value
xem xét kết quả của phiên sau
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
Đặc biệt không phải là cách chúng ta thay đổi trạng thái xây dựng với set x := 41
. Các lệnh cho phép chúng tôi ghi lại vĩnh viễn phiên ở trên, ví dụ:
commands += Command.command("cmd") { state =>
"x" :: "show f" :: "set x := 41" :: "show f" :: state
}
Chúng tôi cũng có thể làm cho loại lệnh an toàn bằng cách sử dụng Project.extract
và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
}
Phạm vi
Phạm vi phát huy tác dụng khi chúng tôi cố gắng trả lời các loại câu hỏi sau
- Làm thế nào để xác định nhiệm vụ một lần và cung cấp nó cho tất cả các dự án con trong xây dựng nhiều dự án?
- Làm thế nào để tránh có các phụ thuộc kiểm tra vào classpath chính?
sbt có một không gian phạm vi nhiều trục có thể được điều hướng bằng cách sử dụng cú pháp gạch chéo , ví dụ:
show root / Compile / compile / scalacOptions
| | | |
project configuration task key
Cá nhân, tôi hiếm khi thấy mình phải lo lắng về phạm vi. Đôi khi tôi chỉ muốn biên dịch các nguồn thử nghiệm
Test/compile
hoặc có thể thực hiện một nhiệm vụ cụ thể từ một dự án con cụ thể mà không cần phải điều hướng đến dự án đó với project subprojB
subprojB/Test/compile
Tôi nghĩ rằng các quy tắc ngón tay cái sau đây giúp tránh các biến chứng trong phạm vi
- không có nhiều
build.sbt
tệp mà chỉ có một tệp chính duy nhất trong dự án gốc kiểm soát tất cả các dự án con khác
- chia sẻ nhiệm vụ qua plugin tự động
- đưa ra các cài đặt chung thành Scala đơn giản
val
và thêm rõ ràng nó vào từng dự án con
Xây dựng nhiều dự án
Thay thế nhiều tệp build.sbt cho mỗi dự án con
.
├── 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
Có một bậc thầy duy nhất build.sbt
để cai trị tất cả
.
├── 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
Có một thực tế phổ biến là tính các cài đặt chung trong các bản dựng nhiều dự án
xác định một chuỗi các cài đặt chung trong val và thêm chúng vào từng dự án. Ít khái niệm hơn để học theo cách đó.
ví dụ
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)
Điều hướng dự án
projects // list all projects
project multi1 // change to particular project
bổ sung
Hãy nhớ định nghĩa xây dựng là một dự án Scala thích hợp nằm trong đó project/
. Đây là nơi chúng tôi xác định một plugin bằng cách tạo .scala
các tệp
. // 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
Đây là một plugin tự động tối thiểu dướiproject/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
}
)
}
Ghi đè
override def requires = plugins.JvmPlugin
hiệu quả nên kích hoạt các plugin cho tất cả các tiểu dự án mà không cần phải gọi một cách rõ ràng enablePlugin
trong build.sbt
.
IntelliJ và sbt
Vui lòng bật cài đặt sau (cài đặt này thực sự phải được bật theo mặc định )
use sbt shell
Dưới
Preferences | Build, Execution, Deployment | sbt | sbt projects
Tài liệu tham khảo chính