설치
brew install sbt
또는 기술적으로 말하면 다음과 같은 유사한 설치 sbt
sbt
터미널에서 실행할 때 실제로 sbt 실행기 bash 스크립트를 실행합니다. 개인적으로 저는이 삼위 일체에 대해 걱정할 필요가 없었고 sbt를 하나의 물건 인 것처럼 사용합니다.
구성
프로젝트 .sbtopts
의 루트에 있는 특정 프로젝트 저장 파일에 대해 sbt를 구성하려면 . 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 개념 중 하나입니다. 나는 질문으로 설명하려고 노력할 것이다. scalaj-http로 HTTP 요청을 실행할 sbt 작업을 정의하고 싶다고 가정 해 보겠습니다. 직관적으로 우리는 내부에서 다음을 시도 할 수 있습니다.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
단순히 이름이 $9c2192aea3f1db3c251d
. 분명히 적절한 프로젝트의 종속성이 아니라 scalaj-http
프로젝트 정의의 종속성이어야합니다 $9c2192aea3f1db3c251d
. 따라서 빌드 정의 Scala 프로젝트가있는 곳 이기 때문에 project/build.sbt
대신 에서 선언해야 합니다.build.sbt
project
빌드 정의가 또 다른 Scala 프로젝트라는 점을 알리려면 sbt consoleProject
. 그러면 클래스 경로에서 빌드 정의 프로젝트와 함께 Scala REPL이로드됩니다. 당신은 라인을 따라 수입을 볼 수 있습니다
import $9c2192aea3f1db3c251d
따라서 이제 build.sbt
DSL 대신 Scala로 적절하게 호출하여 빌드 정의 프로젝트와 직접 상호 작용할 수 있습니다 . 예를 들어, 다음은fooTask
$9c2192aea3f1db3c251d.fooTask.eval
build.sbt
루트 프로젝트 아래에는 빌드 정의 Scala 프로젝트를 정의하는 데 도움이되는 spcial DSL이 project/
있습니다.
그리고 빌드 정의 Scala 프로젝트는 자체 빌드 정의 Scala 프로젝트를 가질 수 있습니다 project/project/
. 우리는 sbt가 재귀 적이 라고 말합니다 .
sbt는 기본적으로 병렬입니다.
sbt 는 작업에서 DAG 를 빌드 합니다. 이를 통해 작업 간의 종속성을 분석하고 병렬로 실행하고 중복 제거를 수행 할 수도 있습니다. build.sbt
DSL은이를 염두에두고 설계 되었기 때문에 처음에는 놀라운 의미를 가질 수 있습니다. 다음 스 니펫에서 실행 순서가 무엇이라고 생각하십니까?
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
아직 값을 할당 할 수 없는지 좀 더 명확해질 수 있습니다. 관계 구축 단계에서는 아직 사용할 수있는 가치가 없습니다.
.NET에서 Scala 고유와 DSL 언어 간의 의미 체계 차이를 명확하게 볼 수 있습니다 build.sbt
. 여기 저에게 맞는 엄지 손가락 규칙이 몇 가지 있습니다.
- DAG는 유형의 표현으로 만들어집니다.
Setting[T]
- 대부분의 경우 우리는 단순히
.value
구문을 사용 하고 sbt는Setting[T]
- 때때로 우리는 DAG의 일부를 수동으로 조정해야하며이를 위해
Def.sequential
또는Def.taskDyn
- 이러한 순서 / 관계 구문 적 이상이 처리되면 작업의 나머지 비즈니스 로직을 구축하기 위해 일반적인 스칼라 의미론에 의존 할 수 있습니다.
명령 대 작업
명령은 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
플러그인
빌드 정의는 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
주요 참조