स्थापना
brew install sbt
या इसी तरह के sbt को स्थापित करता है जो तकनीकी रूप से बोलते हैं
जब आप sbt
टर्मिनल से निष्पादित करते हैं तो यह वास्तव में sbt लॉन्चर बैश स्क्रिप्ट चलाता है। व्यक्तिगत रूप से, मुझे इस त्रिमूर्ति के बारे में चिंता करने की ज़रूरत नहीं थी, और बस sbt का उपयोग करें जैसे कि यह एक ही बात थी।
विन्यास
किसी विशेष प्रोजेक्ट के लिए sbt को कॉन्फ़िगर करने के लिए प्रोजेक्ट .sbtopts
के रूट पर फाइल सेव करें । Sbt सिस्टम-वाइड संशोधित कॉन्फ़िगर करने के लिए /usr/local/etc/sbtopts
। निष्पादन sbt -help
आपको सटीक स्थान बताना चाहिए। उदाहरण के लिए, दे एसबीटी अधिक स्मृति के रूप में एक बंद निष्पादित sbt -mem 4096
, या बचाने -mem 4096
में .sbtopts
या sbtopts
के लिए स्मृति वृद्धि स्थायी रूप से प्रभावी करने के लिए।
परियोजना की संरचना
sbt new scala/scala-seed.g8
एक न्यूनतम हैलो वर्ल्ड 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
बिल्ड डेफिनिशन एक उचित स्काला प्रोजेक्ट है
यह प्रमुख मुहावरेदार sbt अवधारणाओं में से एक है। मैं एक प्रश्न के साथ समझाने की कोशिश करूंगा। मान लीजिए कि आप एक sbt कार्य को परिभाषित करना चाहते हैं, जो स्केलाज-http के साथ एक HTTP अनुरोध को निष्पादित करेगा। सहज रूप से हम अंदर की कोशिश कर सकते हैं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
वास्तव में आपके मुख्य प्रोजेक्ट से एक अलग स्काला प्रोजेक्ट का हिस्सा है । यह अलग स्काला प्रोजेक्ट 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
न कि उचित परियोजना की निर्भरता। इसलिए इसके project/build.sbt
बजाय घोषित किए जाने की आवश्यकता है build.sbt
, क्योंकि project
जहां बिल्ड स्कैला प्रोजेक्ट रहता है।
इस बिंदु को चलाने के लिए कि परिभाषा का निर्माण एक और स्काला परियोजना है, निष्पादित करें sbt consoleProject
। यह स्काला आरईपीएल को क्लासपाथ पर बिल्ड डेफिनिशन प्रोजेक्ट के साथ लोड करेगा। आप की तर्ज पर एक आयात देखना चाहिए
import $9c2192aea3f1db3c251d
तो अब हम सीधे build.sbt
DSL के बजाय Scala के साथ कॉल करके सीधे बिल्ड प्रोजेक्ट के साथ इंटरैक्ट कर सकते हैं । उदाहरण के लिए, निम्नलिखित निष्पादित करता हैfooTask
$9c2192aea3f1db3c251d.fooTask.eval
build.sbt
अंडर प्रोजेक्ट एक स्पेसियल डीएसएल है जो निर्माण परिभाषा स्केल परियोजना के तहत परिभाषित करने में मदद करता है project/
।
और परिभाषा स्कैला परियोजना का निर्माण कर सकते हैं, अपनी स्वयं की बिल्ड परिभाषा स्कैला परियोजना के तहत project/project/
और इतने पर हो सकते हैं। हम कहते हैं कि sbt पुनरावर्ती है ।
sbt डिफ़ॉल्ट रूप से समानांतर है
sbt कार्यों से DAG बनाता है। यह इसे कार्यों के बीच निर्भरता का विश्लेषण करने और उन्हें समानांतर में निष्पादित करने और यहां तक कि कटौती करने की अनुमति देता है। build.sbt
डीएसएल को इसे ध्यान में रखकर बनाया गया है, जो शुरू में आश्चर्यजनक शब्दार्थ को जन्म दे सकता है। आपको क्या लगता है कि निष्पादन का क्रम निम्नलिखित स्निपेट में है?
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
- के लिए monadic शब्दार्थ का उपयोग करें
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 में रिश्तों के बारे में है और इसका मतलब नहीं है
"अभी मुझे मूल्य दो"
इसके बजाय इसका मतलब कुछ ऐसा है
"मेरा कॉलर पहले मुझ पर निर्भर करता है, और एक बार मुझे पता है कि पूरा डीएजी एक साथ कैसे फिट होता है, मैं अनुरोध किए गए मूल्य के साथ अपने कॉलर को प्रदान करने में सक्षम होगा"
तो अब यह थोड़ा स्पष्ट हो सकता है कि x
अभी तक एक मूल्य क्यों नहीं सौंपा जा सकता है; संबंध निर्माण चरण में अभी तक कोई मूल्य उपलब्ध नहीं है।
हम स्पष्ट रूप से स्काला उचित और DSL भाषा के बीच शब्दार्थ में अंतर देख सकते हैं build.sbt
। यहाँ अंगूठे के कुछ नियम हैं जो मेरे लिए काम करते हैं
- DAG प्रकार की अभिव्यक्तियों से बना है
Setting[T]
- ज्यादातर मामलों में हम
.value
सिंटैक्स का उपयोग करते हैं और एसबीटी के बीच संबंध स्थापित करने का ध्यान रखेंगेSetting[T]
- कभी-कभी हमें मैन्युअल रूप से डीएजी के एक हिस्से को मोड़ना पड़ता है और इसके लिए हम उपयोग करते हैं
Def.sequential
याDef.taskDyn
- एक बार जब इन ऑर्डरिंग / रिलेशनशिप सिंटैमिक विषमताओं का ध्यान रखा जाता है, तो हम बाकी कार्यों के व्यापार तर्क के निर्माण के लिए सामान्य स्कैटलिक्स पर भरोसा कर सकते हैं।
कमांड बनाम टास्क
कमांड डीएजी से बाहर एक आलसी रास्ता है। आदेशों का उपयोग करके अपनी इच्छानुसार बिल्ड स्थिति और क्रमबद्ध कार्यों को म्यूट करना आसान है। लागत हम ढीले समालोचना और डीएजी द्वारा प्रदान किए जाने वाले कार्यों का समर्पण है, जिस तरह से कार्यों को प्राथमिकता दी जानी चाहिए। आप आदेशों को एक सत्र की स्थायी रिकॉर्डिंग के रूप में सोच सकते हैं जिसे कोई अंदर कर सकता है 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
फाइलें नहीं हैं, लेकिन रूट प्रोजेक्ट के तहत केवल एक ही मास्टर है जो अन्य सभी उप-परियोजनाओं को नियंत्रित करता है
- ऑटो प्लग के माध्यम से कार्यों को साझा करें
- सादे सेटिंग में सामान्य सेटिंग्स को फैक्टर करें
val
और स्पष्ट रूप से प्रत्येक उप-परियोजना में जोड़ें
मल्टी-प्रोजेक्ट बिल्ड
प्रत्येक subproject के लिए कई 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
मल्टी-प्रोजेक्ट बिल्ड्स में सामान्य सेटिंग्स फैक्टरिंग का एक सामान्य अभ्यास है
एक घाटी में सामान्य सेटिंग्स के अनुक्रम को परिभाषित करें और उन्हें प्रत्येक प्रोजेक्ट में जोड़ें। इस तरह से सीखने के लिए कम अवधारणाएँ।
उदाहरण के लिए
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
।
इंटेलीज और एसबीटी
कृपया निम्नलिखित सेटिंग सक्षम करें (जो कि डिफ़ॉल्ट रूप से सक्षम होनी चाहिए )
use sbt shell
के अंतर्गत
Preferences | Build, Execution, Deployment | sbt | sbt projects
मुख्य संदर्भ