1. sbtによる実践例

sbtによる実践例 

このページでは、sbt 1をインストール済みであることを前提としています。

sbtの仕組みや理由を説明するのではなく、まずは例から始めましょう。

最小限のsbtビルドの作成 

$ mkdir foo-build
$ cd foo-build
$ touch build.sbt

sbtシェルの起動 

$ sbt
[info] Updated file /tmp/foo-build/project/build.properties: set sbt.version to 1.9.3
[info] welcome to sbt 1.9.3 (Eclipse Adoptium Java 17.0.8)
[info] Loading project definition from /tmp/foo-build/project
[info] loading settings for project foo-build from build.sbt ...
[info] Set current project to foo-build (in build file:/tmp/foo-build/)
[info] sbt server started at local:///Users/eed3si9n/.sbt/1.0/server/abc4fb6c89985a00fd95/sock
[info] started sbt server
sbt:foo-build>

sbtシェルの終了 

sbtシェルを終了するには、`exit`と入力するか、Ctrl+D(Unix)またはCtrl+Z(Windows)を使用します。

sbt:foo-build> exit

プロジェクトのコンパイル 

慣習として、`sbt:...>`または`>`プロンプトは、sbtインタラクティブシェルにいることを意味します。

$ sbt
sbt:foo-build> compile

コード変更時の再コンパイル 

`compile`コマンド(または他のコマンド)に`~`を接頭辞として付けると、プロジェクト内のソースファイルのいずれかが変更されるたびに、コマンドが自動的に再実行されます。例えば

sbt:foo-build> ~compile
[success] Total time: 0 s, completed 28 Jul 2023, 13:32:35
[info] 1. Monitoring source files for foo-build/compile...
[info]    Press <enter> to interrupt or '?' for more options.

ソースファイルの作成 

前のコマンドを実行したままにします。別のシェルまたはファイルマネージャーから、foo-buildディレクトリに次のネストされたディレクトリを作成します:`src/main/scala/example`。次に、お気に入りのエディターを使用して、`example`ディレクトリに`Hello.scala`を次のように作成します

package example

object Hello {
  def main(args: Array[String]): Unit = {
    println("Hello")
  }
}

この新しいファイルは、実行中のコマンドによって検出されるはずです。

[info] Build triggered by /tmp/foo-build/src/main/scala/example/Hello.scala. Running 'compile'.
[info] compiling 1 Scala source to /tmp/foo-build/target/scala-2.12/classes ...
[success] Total time: 0 s, completed 28 Jul 2023, 13:38:55
[info] 2. Monitoring source files for foo-build/compile...
[info]    Press <enter> to interrupt or '?' for more options.

Enterキーを押して`~compile`を終了します。

以前のコマンドの実行 

sbtシェルで、上矢印キーを2回押して、最初に実行した`compile`コマンドを探します。

sbt:foo-build> compile

ヘルプの取得 

`help`コマンドを使用して、使用可能なコマンドに関する基本的なヘルプを取得します。

sbt:foo-build> help

  <command> (; <command>)*                       Runs the provided semicolon-separated commands.
  about                                          Displays basic information about sbt and the build.
  tasks                                          Lists the tasks defined for the current project.
  settings                                       Lists the settings defined for the current project.
  reload                                         (Re)loads the current project or changes to plugins project or returns from it.
  new                                            Creates a new sbt build.
  new                                            Creates a new sbt build.
  projects                                       Lists the names of available projects or temporarily adds/removes extra builds to the session.

....

特定のタスクの説明を表示する

sbt:foo-build> help run
Runs a main class, passing along arguments provided on the command line.

アプリの実行 

sbt:foo-build> run
[info] running example.Hello
Hello
[success] Total time: 0 s, completed 28 Jul 2023, 13:40:31

sbtシェルからThisBuild / scalaVersionを設定する 

sbt:foo-build> set ThisBuild / scalaVersion := "2.13.12"
[info] Defining ThisBuild / scalaVersion
[info] The new value will be used by Compile / bspBuildTarget, Compile / dependencyTreeCrossProjectId and 50 others.
[info]  Run `last` for details.
[info] Reapplying settings...
[info] set current project to foo-build (in build file:/tmp/foo-build/)

`scalaVersion`設定を確認する

sbt:foo-build> scalaVersion
[info] 2.13.12

セッションをbuild.sbtに保存する 

`session save`を使用して、アドホック設定を保存できます。

sbt:foo-build> session save
[info] Reapplying settings...
[info] set current project to foo-build (in build file:/tmp/foo-build/)
[warn] build source files have changed
[warn] modified files:
[warn]   /tmp/foo-build/build.sbt
[warn] Apply these changes by running `reload`.
[warn] Automatically reload the build when source changes are detected by setting `Global / onChangedBuildSource := ReloadOnSourceChanges`.
[warn] Disable this warning by setting `Global / onChangedBuildSource := IgnoreSourceChanges`.

`build.sbt`ファイルには、次のように記述されるはずです。

ThisBuild / scalaVersion := "2.13.12"

プロジェクトに名前を付ける 

エディターを使用して、`build.sbt`を次のように変更します。

ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

lazy val hello = (project in file("."))
  .settings(
    name := "Hello"
  )

ビルドの再読み込み 

`reload`コマンドを使用してビルドを再読み込みします。このコマンドにより、`build.sbt`ファイルが再読み込みされ、その設定が適用されます。

sbt:foo-build> reload
[info] welcome to sbt 1.9.3 (Eclipse Adoptium Java 17.0.8)
[info] loading project definition from /tmp/foo-build/project
[info] loading settings for project hello from build.sbt ...
[info] set current project to Hello (in build file:/tmp/foo-build/)
sbt:Hello>

プロンプトが`sbt:Hello>`に変更されていることに注意してください。

libraryDependenciesにtoolkit-testを追加する 

エディターを使用して、`build.sbt`を次のように変更します。

ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

lazy val hello = project
  .in(file("."))
  .settings(
    name := "Hello",
    libraryDependencies += "org.scala-lang" %% "toolkit-test" % "0.1.7" % Test
  )

`reload`コマンドを使用して、`build.sbt`の変更を反映します。

sbt:Hello> reload

テストの実行 

sbt:Hello> test

インクリメンタルテストを継続的に実行する 

sbt:Hello> ~testQuick

テストの記述 

前のコマンドを実行したまま、エディターを使用して`src/test/scala/example/HelloSuite.scala`という名前のファイルを作成します。


class HelloSuite extends munit.FunSuite {
  test("Hello should start with H") {
    assert("hello".startsWith("H"))
  }
}

`~testQuick`が変更を検出するはずです。

[info] 2. Monitoring source files for hello/testQuick...
[info]    Press <enter> to interrupt or '?' for more options.
[info] Build triggered by /tmp/foo-build/src/test/scala/example/HelloSuite.scala. Running 'testQuick'.
[info] compiling 1 Scala source to /tmp/foo-build/target/scala-2.13/test-classes ...
HelloSuite:
==> X HelloSuite.Hello should start with H  0.004s munit.FailException: /tmp/foo-build/src/test/scala/example/HelloSuite.scala:4 assertion failed
3:  test("Hello should start with H") {
4:    assert("hello".startsWith("H"))
5:  }
    at munit.FunSuite.assert(FunSuite.scala:11)
    at HelloSuite.$anonfun$new$1(HelloSuite.scala:4)
[error] Failed: Total 1, Failed 1, Errors 0, Passed 0
[error] Failed tests:
[error]         HelloSuite
[error] (Test / testQuick) sbt.TestsFailedException: Tests unsuccessful

テストをパスさせる 

エディターを使用して、`src/test/scala/example/HelloSuite.scala`を次のように変更します。


class HelloSuite extends munit.FunSuite {
  test("Hello should start with H") {
    assert("Hello".startsWith("H"))
  }
}

テストがパスすることを確認し、Enterキーを押して継続的なテストを終了します。

ライブラリ依存関係の追加 

エディターを使用して、`build.sbt`を次のように変更します。

ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

lazy val hello = project
  .in(file("."))
  .settings(
    name := "Hello",
    libraryDependencies ++= Seq(
      "org.scala-lang" %% "toolkit" % "0.1.7",
      "org.scala-lang" %% "toolkit-test" % "0.1.7" % Test
    )
  )

`reload`コマンドを使用して、`build.sbt`の変更を反映します。

Scala REPLの使用 

ニューヨークの現在の天気を知ることができます。

sbt:Hello> console
[info] Starting scala interpreter...
Welcome to Scala 2.13.12 (OpenJDK 64-Bit Server VM, Java 17).
Type in expressions for evaluation. Or try :help.

scala> :paste
// Entering paste mode (ctrl-D to finish)

import sttp.client4.quick._
import sttp.client4.Response

val newYorkLatitude: Double = 40.7143
val newYorkLongitude: Double = -74.006
val response: Response[String] = quickRequest
  .get(
    uri"https://api.open-meteo.com/v1/forecast?latitude=$newYorkLatitude&longitude=$newYorkLongitude&current_weather=true"
  )
  .send()

println(ujson.read(response.body).render(indent = 2))

// press Ctrl+D

// Exiting paste mode, now interpreting.

{
  "latitude": 40.710335,
  "longitude": -73.99307,
  "generationtime_ms": 0.36704540252685547,
  "utc_offset_seconds": 0,
  "timezone": "GMT",
  "timezone_abbreviation": "GMT",
  "elevation": 51,
  "current_weather": {
    "temperature": 21.3,
    "windspeed": 16.7,
    "winddirection": 205,
    "weathercode": 3,
    "is_day": 1,
    "time": "2023-08-04T10:00"
  }
}
import sttp.client4.quick._
import sttp.client4.Response
val newYorkLatitude: Double = 40.7143
val newYorkLongitude: Double = -74.006
val response: sttp.client4.Response[String] = Response({"latitude":40.710335,"longitude":-73.99307,"generationtime_ms":0.36704540252685547,"utc_offset_seconds":0,"timezone":"GMT","timezone_abbreviation":"GMT","elevation":51.0,"current_weather":{"temperature":21.3,"windspeed":16.7,"winddirection":205.0,"weathercode":3,"is_day":1,"time":"2023-08-04T10:00"}},200,,List(:status: 200, content-encoding: deflate, content-type: application/json; charset=utf-8, date: Fri, 04 Aug 2023 10:09:11 GMT),List(),RequestMetadata(GET,https://api.open-meteo.com/v1/forecast?latitude=40.7143&longitude...

scala> :q // to quit

サブプロジェクトの作成 

`build.sbt`を次のように変更します。

ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

lazy val hello = project
  .in(file("."))
  .settings(
    name := "Hello",
    libraryDependencies ++= Seq(
      "org.scala-lang" %% "toolkit" % "0.1.7",
      "org.scala-lang" %% "toolkit-test" % "0.1.7" % Test
    )
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core"
  )

`reload`コマンドを使用して、`build.sbt`の変更を反映します。

すべてのサブプロジェクトのリスト表示 

sbt:Hello> projects
[info] In file:/tmp/foo-build/
[info]   * hello
[info]     helloCore

サブプロジェクトのコンパイル 

sbt:Hello> helloCore/compile

サブプロジェクトにtoolkit-testを追加する 

`build.sbt`を次のように変更します。

ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

val toolkitTest = "org.scala-lang" %% "toolkit-test" % "0.1.7"

lazy val hello = project
  .in(file("."))
  .settings(
    name := "Hello",
    libraryDependencies ++= Seq(
      "org.scala-lang" %% "toolkit" % "0.1.7",
      toolkitTest % Test
    )
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core",
    libraryDependencies += toolkitTest % Test
  )

コマンドのブロードキャスト 

`hello`に送信されたコマンドが`helloCore`にもブロードキャストされるように、aggregateを設定します。

ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

val toolkitTest = "org.scala-lang" %% "toolkit-test" % "0.1.7"

lazy val hello = project
  .in(file("."))
  .aggregate(helloCore)
  .settings(
    name := "Hello",
    libraryDependencies ++= Seq(
      "org.scala-lang" %% "toolkit" % "0.1.7",
      toolkitTest % Test
    )
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core",
    libraryDependencies += toolkitTest % Test
  )

`reload`の後、`~testQuick`は両方のサブプロジェクトで実行されるようになります。

sbt:Hello> ~testQuick

Enterキーを押して継続的なテストを終了します。

helloをhelloCoreに依存させる 

他のサブプロジェクトへの依存関係を追加するには、`.dependsOn(...)`を使用します。また、toolkitの依存関係を`helloCore`に移動しましょう。

ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

val toolkitTest = "org.scala-lang" %% "toolkit-test" % "0.1.7"

lazy val hello = project
  .in(file("."))
  .aggregate(helloCore)
  .dependsOn(helloCore)
  .settings(
    name := "Hello",
    libraryDependencies += toolkitTest % Test
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core",
    libraryDependencies += "org.scala-lang" %% "toolkit" % "0.1.7",
    libraryDependencies += toolkitTest % Test
  )

uJsonを使用してJSONを解析する 

`helloCore`でtoolkitのuJsonを使用しましょう。

ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

val toolkitTest = "org.scala-lang" %% "toolkit-test" % "0.1.7"

lazy val hello = project
  .in(file("."))
  .aggregate(helloCore)
  .dependsOn(helloCore)
  .settings(
    name := "Hello",
    libraryDependencies += toolkitTest % Test
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core",
    libraryDependencies += "org.scala-lang" %% "toolkit" % "0.1.7",
    libraryDependencies += toolkitTest % Test
  )

`reload`の後、`core/src/main/scala/example/core/Weather.scala`を追加します。

package example.core

import sttp.client4.quick._
import sttp.client4.Response

object Weather {
  def temp() = {
    val response: Response[String] = quickRequest
      .get(
        uri"https://api.open-meteo.com/v1/forecast?latitude=40.7143&longitude=-74.006&current_weather=true"
      )
      .send()
    val json = ujson.read(response.body)
    json.obj("current_weather")("temperature").num
  }
}

次に、`src/main/scala/example/Hello.scala`を次のように変更します。

package example

import example.core.Weather

object Hello {
  def main(args: Array[String]): Unit = {
    val temp = Weather.temp()
    println(s"Hello! The current temperature in New York is $temp C.")
  }
}

アプリを実行して、正常に動作するかどうかを確認しましょう。

sbt:Hello> run
[info] compiling 1 Scala source to /tmp/foo-build/core/target/scala-2.13/classes ...
[info] compiling 1 Scala source to /tmp/foo-build/target/scala-2.13/classes ...
[info] running example.Hello
Hello! The current temperature in New York is 22.7 C.

sbt-native-packagerプラグインの追加 

エディターを使用して、`project/plugins.sbt`を作成します。

addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.4")

次に、`JavaAppPackaging`を追加するために、`build.sbt`を次のように変更します。

ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

val toolkitTest = "org.scala-lang" %% "toolkit-test" % "0.1.7"

lazy val hello = project
  .in(file("."))
  .aggregate(helloCore)
  .dependsOn(helloCore)
  .enablePlugins(JavaAppPackaging)
  .settings(
    name := "Hello",
    libraryDependencies += toolkitTest % Test,
    maintainer := "A Scala Dev!"
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core",
    libraryDependencies += "org.scala-lang" %% "toolkit" % "0.1.7",
    libraryDependencies += toolkitTest % Test
  )

再読み込みして.zip配布物を生成する 

sbt:Hello> reload
...
sbt:Hello> dist
[info] Wrote /private/tmp/foo-build/target/scala-2.13/hello_2.13-0.1.0-SNAPSHOT.pom
[info] Main Scala API documentation to /tmp/foo-build/target/scala-2.13/api...
[info] Main Scala API documentation successful.
[info] Main Scala API documentation to /tmp/foo-build/core/target/scala-2.13/api...
[info] Wrote /tmp/foo-build/core/target/scala-2.13/hello-core_2.13-0.1.0-SNAPSHOT.pom
[info] Main Scala API documentation successful.
[success] All package validations passed
[info] Your package is ready in /tmp/foo-build/target/universal/hello-0.1.0-SNAPSHOT.zip

パッケージ化されたアプリを実行する方法は次のとおりです。

$ /tmp/someother
$ cd /tmp/someother
$ unzip -o -d /tmp/someother /tmp/foo-build/target/universal/hello-0.1.0-SNAPSHOT.zip
$ ./hello-0.1.0-SNAPSHOT/bin/hello
Hello! The current temperature in New York is 22.7 C.

アプリのDocker化 

これは機能させるために、Dockerデーモンが実行されている必要があります。

sbt:Hello> Docker/publishLocal
....
[info] Built image hello with tags [0.1.0-SNAPSHOT]

Docker化されたアプリを実行する方法は次のとおりです。

$ docker run hello:0.1.0-SNAPSHOT
Hello! The current temperature in New York is 22.7 C.

バージョンの設定 

`build.sbt`を次のように変更します。

ThisBuild / version := "0.1.0"
ThisBuild / scalaVersion := "2.13.12"
ThisBuild / organization := "com.example"

val toolkitTest = "org.scala-lang" %% "toolkit-test" % "0.1.7"

lazy val hello = project
  .in(file("."))
  .aggregate(helloCore)
  .dependsOn(helloCore)
  .enablePlugins(JavaAppPackaging)
  .settings(
    name := "Hello",
    libraryDependencies += toolkitTest % Test,
    maintainer := "A Scala Dev!"
  )

lazy val helloCore = project
  .in(file("core"))
  .settings(
    name := "Hello Core",
    libraryDependencies += "org.scala-lang" %% "toolkit" % "0.1.7",
    libraryDependencies += toolkitTest % Test
  )

scalaVersionの一時的な切り替え 

sbt:Hello> ++3.3.1!
[info] Forcing Scala version to 3.3.1 on all projects.
[info] Reapplying settings...
[info] Set current project to Hello (in build file:/tmp/foo-build/)

`scalaVersion`設定を確認する

sbt:Hello> scalaVersion
[info] helloCore / scalaVersion
[info]  3.3.1
[info] scalaVersion
[info]  3.3.1

この設定は`reload`後になくなります。

distタスクの検査 

`dist`の詳細を知るには、`help`と`inspect`を試してください。

sbt:Hello> help dist
Creates the distribution packages.
sbt:Hello> inspect dist

依存タスクに対して再帰的にinspectを呼び出すには、`inspect tree`を使用します。

sbt:Hello> inspect tree dist
[info] dist = Task[java.io.File]
[info]   +-Universal / dist = Task[java.io.File]
....

バッチモード 

ターミナルから直接sbtコマンドを渡して、sbtをバッチモードで実行することもできます。

$ sbt clean "testOnly HelloSuite"

**注記**: バッチモードで実行するには、毎回JVMの起動とJITコンパイルが必要となるため、**ビルドの実行速度が大幅に低下します**。日々のコーディングには、sbtシェルまたは`~testQuick`などの継続的なテストの使用をお勧めします。

sbt newコマンド 

シンプルな「Hello world」ビルドを迅速に設定するには、sbtのnewコマンドを使用できます。

$ sbt new scala/scala-seed.g8
....
A minimal Scala project.

name [My Something Project]: hello

Template applied in ./hello

プロジェクト名を求めるプロンプトが表示されたら、helloと入力してください。

これにより、helloという名前のディレクトリ下に新しいプロジェクトが作成されます。

クレジット 

このページは、William “Scala William” Narmontas氏によって書かれたEssential sbtチュートリアルに基づいています。