누군가 SBT를 사용하는 올바른 방법을 설명 할 수 있습니까?


100

나는 이것에 대해 옷장에서 내릴거야! SBT를 이해하지 못합니다. 내가 말 했으니 이제 제발 도와주세요.

모든 길은 로마로 통한다, 그것은 SBT에 대해 동일합니다 : 시작하려면 SBTSBT, SBT Launcher, SBT-extras, 등, 다음을 포함하고 저장소에 결정하는 여러 가지 방법이 있습니다. '최상의'방법이 있습니까?

때때로 나는 조금 길을 잃기 때문에 묻는 것입니다. SBT 문서는 매우 철저하고 완료,하지만 난 사용할 때 자신이 모르는 발견 build.sbt하거나 project/build.properties또는 project/Build.scalaproject/plugins.sbt.

그러면 재미있어집니다. Scala-IDE그리고 SBT- 함께 사용하는 올바른 방법은 무엇입니까? 가장 먼저 오는 것은 무엇입니까? 닭고기 또는 계란?

가장 중요한 것은 아마도 프로젝트에 포함 할 올바른 저장소와 버전을 어떻게 찾 느냐는 것입니다. 기계를 꺼내서 해킹을 시작하면 되나요? 나는 모든 것과 부엌 싱크대를 포함하는 프로젝트를 자주 찾는다. 그리고 나는 깨달았다.-내가 조금 길을 잃는 유일한 사람은 아니다.

간단한 예로, 지금은 새로운 프로젝트를 시작하고 있습니다. 나는의 최신 기능을 사용하려면 SLICKScala이 아마 SBT의 최신 버전이 필요합니다. 시작하기위한 올바른 요점은 무엇이며 그 이유는 무엇입니까? 어떤 파일에서 정의해야하며 어떻게 표시되어야합니까? 나는 이것을 작동시킬 수 있다는 것을 알고 있지만 모든 것이 어디로 가야하는지에 대한 전문가 의견을 정말로 원합니다 (왜 거기에 가야하는지 보너스가있을 것입니다).

저는 SBT지금까지 1 년 넘게 소규모 프로젝트에 사용 하고 있습니다. 나는 사용 SBT하고 SBT Extras(약간의 두통이 마술처럼 사라지기 때문에) 사용했지만 왜 둘 중 하나를 사용 해야하는지 잘 모르겠습니다. 나는 사물이 어떻게 조화를 이루는 지 ( SBT및 저장소) 이해하지 못하는 것에 대해 약간 실망하고 있으며, 이것이 인간의 용어로 설명 될 수 있다면 다음 사람이 이런 식으로 오는 많은 어려움을 덜어 줄 것이라고 생각합니다.


2
"Scala-IDE와 SBT가있다"는 것이 정확히 무엇을 의미합니까? sbt로 프로젝트를 정의하고 sbt는 ide (eclipse oder intellij) 프로젝트를 생성 할 수 있습니다. 그래서 SBT가 먼저 ...
Jan

2
@Jan Scala-IDE가 SBT를 빌드 관리자로 사용하기 때문에 언급했습니다. assembla.com/spaces/scala-ide/wiki/SBT-based_build_manager를 참조 하고 "SBT 프로젝트 파일을 정의 할 필요가 없습니다."라고 언급 한 게시물에서 아래를 참조하십시오 . 혼란 스러웠습니다.
Jack

확인. 내가 보통 intellij (또는 sublime)를 사용하여 스칼라를 편집하기 때문에 나는 그것을 몰랐다. 빌더가 자체 sbt 구성을 생성한다고 생각합니까?
1

2
@JacobusR Scala IDE가 SBT를 사용하여 프로젝트 소스를 빌드한다는 사실은 구현 세부 사항이며 사용자는 이에 대해 걱정할 필요가 없습니다 . 실제로 의미가 없습니다. Eclipse 외부 사용자는 SBT, Maven, Ant, ...로 프로젝트를 빌드 할 수 있으며 Scala IDE에는 아무런 차이가 없습니다. 한 가지 더, SBT 프로젝트가 있더라도 Scala IDE는 상관하지 않습니다. 즉, Build.scala클래스 경로를 설정하기 위해 사용자 를 찾지 않으므로 실제로 Eclipse .classpath를 생성 하려면 sbteclipse 가 필요합니다 . 도움이 되었기를 바랍니다.
Mirco Dotta 2012

1
@Jan Scala IDE가 혼란을 가중 시켰습니다. 예, 좋은 Scala 개발 환경 설정에 대한 더 큰 그림과 적절한 프로그래밍 워크 플로에 대한 확실한 지침을 제공하는 문서가 매우 유용 할 것입니다.
Jack

답변:


29

가장 중요한 것은 아마도 프로젝트에 포함 할 올바른 저장소와 버전을 어떻게 찾 느냐는 것입니다. 기계를 꺼내서 해킹을 시작하면 되나요? 나는 모든 것을 포함하는 프로젝트와 부엌 싱크대를 자주 찾습니다.

Scala 기반 종속성의 경우 저자가 권장하는대로 진행합니다. 예 : 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"

그런 다음 기계를 꺼내서 해킹을 시작하십시오. 운이 좋으면 동일한 jar에 의존하지만 호환되지 않는 버전을 사용하는 jar를 사용하지 않는 것이 좋습니다. Java 에코 시스템을 고려할 때 모든 항목과 주방 싱크를 포함하는 경우가 많으며 종속성을 제거하거나 필수 종속성이 누락되지 않았는지 확인하는 데 약간의 노력이 필요합니다.

간단한 예로, 지금은 새로운 프로젝트를 시작하고 있습니다. SLICK 및 Scala의 최신 기능을 사용하고 싶습니다.이를 위해서는 최신 버전의 SBT가 필요할 것입니다. 시작하기위한 올바른 요점은 무엇이며 그 이유는 무엇입니까?

나는 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

최대 사용 확인 showinspect 및 탭 완성을 설정, 의존성, 정의와 관련 설정의 실제 값에 익숙해 질 수 있습니다. 나는 당신이 발견하게 될 관계 inspect가 어디에도 문서화되어 있다고 믿지 않습니다 . 더 나은 방법이 있다면 그것에 대해 알고 싶습니다.


25

sbt를 사용하는 방법은 다음과 같습니다.

  1. sbt-extras 사용 -쉘 스크립트를 가져 와서 프로젝트의 루트에 추가하십시오.
  2. sbt 설정 project을위한 MyProject.scala파일이 있는 폴더를 만듭니다 . 나는 build.sbt접근 방식 보다 이것을 훨씬 선호합니다 -스칼라이고 더 유연합니다.
  3. 크리에이트 project/plugins.sbt파일을 당신의 IDE에 해당하는 플러그인을 추가 할 수 있습니다. eclipse, intellij 또는 ensime 용 프로젝트 파일을 생성 할 수 있도록 sbt-eclipse, sbt-idea 또는 ensime-sbt-cmd 중 하나입니다.
  4. 프로젝트의 루트에서 sbt를 시작하고 IDE 용 프로젝트 파일을 생성합니다.
  5. 이익

IDE 프로젝트 파일은 sbt에 의해 생성되었으므로 확인하지 않아도되지만 그렇게하려는 이유가있을 수 있습니다.

여기에서 이와 같은 설정 예를 볼 수 있습니다 .


좋은 답변에 감사드립니다. 나는 더 많은 근거를 다루고 있기 때문에 다른 답변을 수락했으며 당신의 이유도 정말 좋습니다. 내가 할 수 있다면 나는 둘 다 받아 들였을 것이다.
Jack

0

프로젝트 템플릿 및 시드와 함께 제공되는 sbt를 호출하는 멋진 방법 인 Typesafe Activator 사용 : 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
의심 스러울 때 믹스에 마법을 더하는 것이 문제를 해결할 가능성이 없다는 생각에 부분적 입니다.
Cubic 2014

0

설치

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-httplibraryDependencies? 또한, 대신 종속성을 추가 할 때 왜 작동 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.sbtproject

빌드 정의가 또 다른 Scala 프로젝트라는 점을 알리려면 sbt consoleProject. 그러면 클래스 경로에서 빌드 정의 프로젝트와 함께 Scala REPL이로드됩니다. 당신은 라인을 따라 수입을 볼 수 있습니다

import $9c2192aea3f1db3c251d

따라서 이제 build.sbtDSL 대신 Scala로 적절하게 호출하여 빌드 정의 프로젝트와 직접 상호 작용할 수 있습니다 . 예를 들어, 다음은fooTask

$9c2192aea3f1db3c251d.fooTask.eval

build.sbt루트 프로젝트 아래에는 빌드 정의 Scala 프로젝트를 정의하는 데 도움이되는 spcial DSL이 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작업하는 것이라고 생각할 수 있습니다 . 그러나 이것은 실제로 실행 수단 ab평행 println("hello") 그래서

a
b
hello

또는 주문 ab보장되지 않기 때문에

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 }

우리가 보는 곳 sumab.

다시 말해

  • 대한 실용적 의미, 사용.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.extractrunTask

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

주요 참조

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.