Birisi SBT'yi kullanmanın doğru yolunu açıklayabilir mi?


100

Bunun üzerine dolaptan çıkıyorum! SBT'yi anlamıyorum. Orada, dedim, şimdi bana yardım et lütfen.

Tüm yollar Roma'ya çıkar ve bu SBT için aynıdır: başlamak için SBTorada SBT, SBT Launcher, SBT-extrasvb ve ardından dahildir ve havuzlarına karar vermek farklı yolları vardır. 'En iyi' yol var mı?

Soruyorum çünkü bazen biraz kayboluyorum. SBT belgeleri çok kapsamlı ve eksiksiz, ancak kendimi ne zaman build.sbtveya project/build.propertiesveya project/Build.scalaveya kullanacağımı bilmiyordum project/plugins.sbt.

Sonra eğlenceli oluyor, Scala-IDEve var SBT- Onları birlikte kullanmanın doğru yolu nedir? Önce ne gelir, tavuk mu yumurta mı?

En önemlisi muhtemelen, projenize dahil etmek için doğru depoları ve sürümleri nasıl bulursunuz? Bir bıçak çıkarıp ilerlemeye mi başladım? Sık sık her şeyi ve mutfak lavabosunu içeren projeler buluyorum ve sonra anlıyorum - biraz kaybolan tek kişi ben değilim.

Basit bir örnek olarak, şu anda yepyeni bir projeye başlıyorum. Ben en son özelliklerini kullanmak istiyorum SLICKve Scalave bu muhtemelen SBT son sürümünü gerektirmektedir. Başlamak için mantıklı nokta nedir ve neden? Hangi dosyada tanımlamalıyım ve nasıl görünmeli? Bunu çalıştırabileceğimi biliyorum, ancak gerçekten her şeyin nereye gitmesi gerektiğine dair bir uzman görüşü almak isterim (neden oraya gitmesi gerektiği bir bonus olacak).

SBTBir yılı aşkın süredir küçük projeler için kullanıyorum . Kullandım SBTve sonra SBT Extras(bazı baş ağrılarını sihirli bir şekilde yok ettiği için), ama neden birini veya diğerini kullanmam gerektiğinden emin değilim. İşlerin ( SBTve depoların) nasıl birbirine uyduğunu anlamadığım için biraz hayal kırıklığına uğradım ve eğer insan terimleriyle açıklanabilirse, bu şekilde gelen bir sonraki adamı çok fazla zorluktan kurtaracağını düşünüyorum.


2
"Scala-IDE ve SBT var" ile tam olarak neyi kastediyorsunuz? Projenizi sbt ile tanımlarsınız ve sbt bir ide (eclipse oder intellij) projesi oluşturabilir. Yani SBT ... önce gelir
Jan

2
@Jan Scala-IDE SBT'yi yapı yöneticisi olarak kullandığından bahsetmiştim. Assembla.com/spaces/scala-ide/wiki/SBT-based_build_manager'a bakın ve "SBT proje dosyanızı tanımlamanıza gerek yok" yazan gönderide aşağıya bakın . kafa karıştırıcı buldum.
Jack

tamam. Scala'yı düzenlemek için genellikle intellij (veya sublime) kullandığım için bunu bilmiyordum. Sanırım kurucu kendi sbt yapılandırmalarını oluşturuyor?
Jan

2
@JacobusR Scala IDE'nin projenizin kaynaklarını oluşturmak için SBT'yi kullanması bir uygulama detayıdır ve kullanıcıların bu konuda endişelenmesine gerek yoktur . Gerçekten 0 çıkarım var. Eclipse dışındaki kullanıcılar SBT, Maven, Ant, ... ile bir proje oluşturabilir ve bu Scala IDE için herhangi bir fark yaratmaz. Bir şey daha, bir SBT projeniz olsa bile, Scala IDE umursamıyor, yani Build.scalasınıf yolunu ayarlamanızı istemiyor ve bu yüzden Eclipse .classpath'i oluşturmak için aslında sbteclipse'e ihtiyacınız var . Bu yardımcı olur umarım.
Mirco Dotta

1
@Jan Scala IDE, karışıklığa ekledi ve evet, iyi bir Scala geliştirme ortamı kurma konusunda daha büyük bir resim veren dokümantasyon ve uygun programlama iş akışları için bazı sağlam rehberler çok kullanışlı olacaktır.
Jack

Yanıtlar:


29

En önemlisi muhtemelen, projenize dahil etmek için doğru depoları ve sürümleri nasıl bulursunuz? Bir bıçak çıkarıp ilerlemeye mi başlasam? Sıklıkla her şeyi ve mutfak lavabosunu içeren projeler buluyorum

Scala tabanlı bağımlılıklar için, yazarların önerdikleriyle giderdim. Örneğin: http://code.google.com/p/scalaz/#SBT aşağıdakilerin kullanılacağını belirtir:

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

Veya https://github.com/typesafehub/sbteclipse/ nereye ekleneceğine dair talimatlara sahiptir:

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

Java tabanlı bağımlılıklar için, orada neler olduğunu görmek için http://mvnrepository.com/ kullanıyorum ve ardından SBT sekmesine tıklıyorum. Örneğin http://mvnrepository.com/artifact/net.sf.opencsv/opencsv/2.3 aşağıdakilerin kullanılacağını belirtir:

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

Ardından, bıçağı çıkarın ve ileriye doğru ilerlemeye başlayın. Şanslıysanız, aynı kavanozlardan bazılarına bağlı olan ancak uyumsuz sürümleri olan kavanozları kullanmazsınız. Java ekosistemi göz önüne alındığında, genellikle her şeyi ve mutfak lavabosunu dahil edersiniz ve bağımlılıkları ortadan kaldırmak veya gerekli bağımlılıkları kaçırmadığınızdan emin olmak biraz çaba gerektirir.

Basit bir örnek olarak, şu anda yepyeni bir projeye başlıyorum. SLICK ve Scala'nın en son özelliklerini kullanmak istiyorum ve bu muhtemelen SBT'nin en son sürümünü gerektirecektir. Başlamak için mantıklı nokta nedir ve neden?

Bence mantıklı olan nokta, yavaş yavaş sbt'ye karşı bağışıklık oluşturmaktır .

Anladığınızdan emin olun:

  1. kapsam biçimi {<build-uri>}<project-id>/config:key(for task-key)
  2. ayarlardan 3 tatlar ( SettingKey, TaskKey, InputKey) - "Görev Tuşlar" bölümünü okuyun http://www.scala-sbt.org/release/docs/Getting-Started/Basic-Def

Bu 4 sayfayı her zaman açık tutun, böylece çeşitli tanımlara ve örneklere atlayıp bakabilirsiniz:

  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

Maksimum yararlanın showve inspect ve sekme tamamlanması ayarları bağımlılıkları, tanımlar ve ilgili ayarların gerçek değerleri tanımak için. Kullanarak keşfedeceğiniz ilişkilerin inspecthiçbir yerde belgelendiğine inanmıyorum . Daha iyi bir yol varsa bilmek istiyorum.


25

Sbt'yi kullanma şeklim:

  1. Sbt-extras kullanın - sadece kabuk betiğini alın ve projenizin kök dizinine ekleyin
  2. Sbt'yi kurmak için projectbir MyProject.scaladosya içeren bir klasör oluşturun . Bunu build.sbtyaklaşıma tercih ederim - bu ölçek ve daha esnek
  3. Bir project/plugins.sbtdosya oluşturun ve IDE'niz için uygun eklentiyi ekleyin. Ya sbt-eclipse, sbt-idea veya ensime-sbt-cmd, böylece eclipse, intellij veya ensime için proje dosyaları oluşturabilirsiniz.
  4. Projenizin kök dizininde sbt'yi başlatın ve IDE'niz için proje dosyalarını oluşturun
  5. Kar

IDE proje dosyalarını sbt tarafından oluşturuldukları için kontrol etmeye zahmet etmiyorum, ancak bunu yapmak isteyebileceğiniz nedenler olabilir.

Bunun gibi kurulmuş bir örneği burada görebilirsiniz .


Güzel cevap için teşekkür ederim. Diğer yanıtı da kabul ettim, çünkü daha fazla alanı kapsıyor ve seninkine de olumlu oy verdim çünkü gerçekten iyi. Yapabilseydim ikisini de kabul ederdim.
Jack

0

Proje şablonları ve tohumlarla birlikte gelen sbt'yi çağırmanın süslü bir yolu olan Typesafe Activator'ı kullanın: 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
Şüpheye düştüğünüzde, karışıma daha fazla sihir eklemenin problemlerinizi çözmeyeceği fikrini benimsiyorum.
Cubic

0

Kurulum

brew install sbt veya teknik açıdan bakıldığında şunlardan oluşan benzer kurulumlar

sbtTerminalden ç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 .sbtoptskö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 -helpsize tam yeri söylemelidir. Örneğin, vermek sbt fazla bellek için tek seferlik olarak yürütmek sbt -mem 4096veya kaydetmek -mem 4096içinde .sbtoptsveya sbtoptskalı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-httpolabilir 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 fooTaskbu 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-classesgibi 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-httpproje 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.sbtbunun yerine bildirilmesi gerekir .build.sbtproject

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.sbtDSL yerine Scala ile adlandırarak, yapı tanımlama projesiyle doğrudan etkileşime girebiliriz . Örneğin, aşağıdakifooTask

$9c2192aea3f1db3c251d.fooTask.eval

build.sbtkö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.sbtDSL, 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 hellosonra yürütmek ave sonra bgörev yapmak olduğunu düşünebilirsiniz . Ancak bu aslında yürütmek demektir ave biçinde paralel ve daha önce println("hello") bu kadar

a
b
hello

veya sırası nedeniyle ave bgaranti 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.taskDyntaklit 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 sumbağlıdır ve beklemek zorundadır ave b.

Diğer bir deyişle

  • uygulamalı anlamlar için kullanın.value
  • için monadic semantik kullanmak sequentialveyataskDyn

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 .valueDAG'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 xhenü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 .valuesö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.sequentialveyaDef.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.extractve 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.sbtdosyaya 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 valve 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.sbtHepsine 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/. .scalaDosya 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 enablePluginiç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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.