モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

目次目次を開く/閉じる

酢豚の基本的な使い方 (sbt)

モーダルを閉じる

ステッカーを選択してください

お支払い手続きへ
モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2016/01/25
最終更新最終更新
2018/05/30
記事区分記事区分
一般公開

sbt は Scala および Java を主な対象としたビルドツールです。Scala Build Tool の略ではありませんが、Simple Build Tool という明示的な記述も公式ドキュメントなどには見当りません。以下 sbt の基本的な使用例をまとめます。使用した sbt のバージョンは 0.13 です。

公式ドキュメント

インストール方法

sbt の実体は jar ファイルです。OSX, Windows, Linux 等、JVM (1.6 以上) が動作する環境であればどのプラットフォームでも動作します。Macports, Homebrew, msi インストーラ, yum RPM などが提供されていますが、結局のところ、シェルスクリプトまたはバッチファイルで JAR を実行しているだけです。CentOS で手動インストールする例を記載します。

JVM が必要なため、JDK または JRE をインストールします。

sudo yum install java-1.8.0-openjdk-devel

sbt JAR をダウンロードして適当なディレクトリに保存します。

wget https://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.9/sbt-launch.jar
mv sbt-launch.jar ~/bin/

JAR を実行するシェルスクリプトを用意します。

~/bin/sbt

#!/bin/bash
SBT_OPTS="-Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxMetaspaceSize=256M"
java $SBT_OPTS -jar `dirname $0`/sbt-launch.jar "$@"

パーミッションを設定します。

chmod u+x ~/bin/sbt

サンプルコードのビルド

mkdir hello
cd hello/
vi hw.scala

hw.scala

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

ビルド

sbt
...
> run
> exit

初回実行時は非常に時間がかかります。~/.sbt/ および ~/.ivy2/ に Scala の JAR 等、sbt の動作に必要な JAR をダウンロードするためです。

ディレクトリ構成

先程のサンプルコード hw.scala のように sbt コマンドはプロジェクトのベースディレクトリのファイルもビルドできます。しかしながら、ファイル分割された一般的なプロジェクトの場合は以下のようなディレクトリ構成でファイルを整理します。これは Maven と同じ構成です。

ベースディレクトリ/
  .gitignore
  build.sbt
  project/
    build.properties
    <その他の設定ファイル .scala または .sbt>
    target/
      <コンパイル結果 class ファイルなど>
  lib/
    <ライブラリ JAR ファイル>
  src/
    main/
      resources/
        <jar に含めたいファイル>
      scala/
        <Scala ソースコード>
      java/
        <Java ソースコード>
    test/
      resources
        <test jar に含めたいファイル>
      scala/
        <Scala テストコード>
      java/
        <Java テストコード>
  target/
    <成果物 JAR など>

.gitignore を利用する場合は target/ を記載します。/target/ でも target でもなく target/ です。target ディレクトリは様々な階層に生成されます。

バージョンの固定

設定ファイルに Scala および sbt のバージョンを記載することで、例えば複数人で開発する場合でも同じ開発環境が構築できます。バージョンの差異によって生じるバグを回避できます。

Scala バージョンの固定

scalaVersion を指定することで Scala バージョンを固定できます。初回ビルド時は自動で該当の Scala JAR をダウンロードして ~/.ivy2/ に保存します。無指定の場合は sbt が動作のために利用している Scala バージョンが利用されます。

build.sbt

lazy val root = (project in file(".")).
  settings(
    name := "my-scala-app",
    version := "1.0",
    scalaVersion := "2.11.7"
  )

sbt バージョンの固定

sbt バージョンによる差異が基本的にありませんが、sbt のバージョンも固定しておくことはよいことです。

project/build.properties

sbt.version=0.13.9

よく使用するコマンド

インタラクティブモード

$ sbt
> run

バッチモード

$ sbt run

ファイルの変化を検知して自動コンパイル

$ sbt
> ~ compile

または

$ sbt "~ compile"

target に生成されたファイルを削除

$ sbt clean

コンパイルしてテストを実行

$ sbt test

Scala インタープリタを起動

$ sbt console

Main クラスを実行

$ sbt run

JAR 成果物を生成

$ sbt package

jar コマンドで成果物の中身を確認してみましょう。

$ jar tf target/scala-2.11/hello_2.11-1.0.jar
META-INF/MANIFEST.MF
Hi$.class
Hi.class

src/main/scalasrc/main/java をコンパイルしたクラスファイルおよび src/main/resources が格納されています。

ヘルプ

$ sbt help
$ sbt "help package"

設定の再読み込み

build.sbt, project/*.scala, project/*.sbt ファイルを編集した場合は reload を実行して再読み込みする必要があります。

$ sbt
> reload

ライブラリの使用方法

sbt でライブラリを利用する方法は二種類あります。

  • Unmanaged dependencies
    • lib ディレクトリに jar ファイルを置くことで利用
  • Managed dependencies
    • build.sbt に設定を記載することでインターネットから jar ファイルをダウンロードして利用
    • Maven の pom.xml に記載する plugin と同等の概念

Unmanaged dependencies 設定例

SWT で GUI ツールを作成できます。

build.sbt

lazy val root = (project in file(".")).
  settings(
    name := "hello",
    version := "1.0",
    scalaVersion := "2.11.7",
    mainClass in assembly := Some("com.mycompany.app.Hi")
  )

.gitignore

target/

lib/swt.jar

<動作させる予定の OS 用にダウンロードしたもの>

project/build.properties

sbt.version=0.13.9

src/main/scala/hw.scala

package com.mycompany.app

import org.eclipse.swt.SWT
import org.eclipse.swt.layout.RowLayout
import org.eclipse.swt.widgets.Button
import org.eclipse.swt.widgets.Display
import org.eclipse.swt.widgets.Shell
import org.eclipse.swt.widgets.Text

object Hi {
  def main(args: Array[String]) {

    val display = new Display()

    val shell = new Shell(display)
    shell.setText("SWT アプリケーション")
    shell.setLayout(new RowLayout())

    val button = new Button(shell, SWT.NULL)
    button.setText("押してください")

    shell.open()

    while(!shell.isDisposed()) {
      if(!display.readAndDispatch()) {
        display.sleep()
      }
    }
    display.dispose()

  }
}

sbt-assembly プラグインを利用すると Scala JAR や lib 以下の JAR をすべて含めた全部入りの fat JAR を生成できます。Maven の Apache Maven Assembly Plugin や Apache Maven Shade Plugin のようなものです。

project/assembly.sbt

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.1")

ビルド

$ sbt
> assembly

実行例 (OSX で SWT アプリケーションを実行する場合は XstartOnFirstThread が必要です)

$ java -XstartOnFirstThread -jar ./target/scala-2.11/hello-assembly-1.0.jar

Managed dependencies 設定例

Java のロガー Logback を利用してみます。

build.sbt

lazy val root = (project in file(".")).
  settings(
    name := "hello",
    version := "1.0",
    scalaVersion := "2.11.7",
    libraryDependencies ++= Seq(
      "ch.qos.logback" % "logback-classic" % "1.1.3",
      "org.slf4j" % "slf4j-api" % "1.7.12"
    )
  )

.gitignore

target/
/logs/

project/build.properties

sbt.version=0.13.9

src/main/scala/hw.scala

import org.slf4j.Logger
import org.slf4j.LoggerFactory

object Hi {
  def main(args: Array[String]) {
    val logger = LoggerFactory.getLogger("Hi")
    logger.info("info: {}", 1)
    logger.warn("warn: {}", 2)
    logger.error("error: {}", 3)
  }
}

src/main/resources/logback.xml

<configuration>
  <property name="LOG_DIR" value="./logs" />
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_DIR}/myapp.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- daily rollover  -->
      <fileNamePattern>${LOG_DIR}/myapp.%d{yyyy-MM-dd}.log</fileNamePattern>
      <!-- keep 30 days' worth of history -->
      <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
    </encoder>
  </appender>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>
  <root level="info">
    <appender-ref ref="FILE" />
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

ビルド

$ sbt
> run
[info] Running Hi
info: 1
warn: 2
error: 3
[success] Total time: 2 s, completed 2016/01/26 15:48:23

ログが生成されました。

$ cat logs/myapp.log
2016-01-26 15:47:08,646 INFO [run-main-0] Hi [hw.scala:7] info: 1
2016-01-26 15:47:08,662 WARN [run-main-0] Hi [hw.scala:8] warn: 2
2016-01-26 15:47:08,662 ERROR [run-main-0] Hi [hw.scala:9] error: 3

JAR ファイルをキャッシュ保存

bundler の package のようなオプションが sbt にもあります。ネットワーク上にある JAR ファイルの消失に備えてプロジェクト内に保存しておきたい場合に有効化します。

lazy val root = (project in file(".")).
  settings(
    ...

    // Copy all managed dependencies to <build-root>/lib_managed/
    //   This is essentially a project-local cache and is different
    //   from the lib_managed/ in sbt 0.7.x.  There is only one
    //   lib_managed/ in the build root (not per-project).
    retrieveManaged := true,

    ...
  )

複数プロジェクトを管理

複数の関連プロジェクトを同じ sbt で管理することができます。

lazy val commonSettings = Seq(
    scalaVersion := "2.11.7",
    retrieveManaged := true,
    libraryDependencies ++= Seq(
    )
)

lazy val root = (project in file(".")).
  aggregate(module1, module2).
  dependsOn(module1, module2).
  settings(commonSettings: _*).
  settings(
    libraryDependencies ++= Seq(
    )
  )

lazy val module1 = (project in file("module1")).
  settings(commonSettings: _*).
  settings(
    name := "myapp-module2",
    version := "1.0",
    mainClass in assembly := Some("myapp.module1.Main"),
    libraryDependencies ++= Seq(
    )
  )

lazy val module2 = (project in file("module2")).
  settings(commonSettings: _*).
  settings(
    name := "myapp-module2",
    version := "1.0",
    mainClass in assembly := Some("myapp.module2.Main"),
    libraryDependencies ++= Seq(
    )
  )
  • dependsOn(module1, module2) によって root プロジェクトで module1, module2 のクラスを import して利用可能
  • aggregate(module1, module2) によって root プロジェクトで compile などを実行すると module1, module2 でも compile が実行される

ソースファイルなどはそれぞれの階層で管理します。

  • root: /src/*
  • module1: /module1/src/*
  • module2: /module2/src/*

コンソールでのプロジェクトの切り替え

$ sbt
> projects
[info] In file:/paty/to/myapp/
[info]    module1
[info]    module2
[info]  * root
> project module1
[info] Set current project to myapp-module1 (in build file:/paty/to/myapp/)
> projects
[info] In file:/paty/to/myapp/
[info]  * module1
[info]    module2
[info]    root

現在のプロジェクトでタスクを実行

> run

プロジェクトを指定してタスクを実行

> root/run
> module1/run
> module2/run

sbt-assembly 時の Merge エラー対応

上記 assembly タスクを実行すると、依存 JAR などがすべて JARに Merge されてパッケージングされます。その際、重複した名前のファイルが複数の JAR に含まれていると Merge 時にコンフリクトします。

[error] (*:assembly) deduplicate: different file contents found in the following:

コンフリクトを解決するための規則を予め build.sbt に記載しておくことでこれを回避できます。

lazy val root = (project in file(".")).
  settings(
    name := "my-scala-app",
    version := "1.0",
    scalaVersion := "2.11.7",
    assemblyMergeStrategy in assembly := {
      case PathList("javax", "servlet", xs @ _*) => MergeStrategy.first
      case PathList("path", "to", "file.txt") => MergeStrategy.discard
      case "unwanted.txt" => MergeStrategy.discard
      case x =>
        val oldStrategy = (assemblyMergeStrategy in assembly).value
        oldStrategy(x)
    }
  )

上記設定では以下のようにコンフリクトを解決します。パターンマッチを利用して Path 毎に設定します。

  • /javax/servlet/* 最初に出現したものを JAR に含める
  • /path/to/file.txt JAR に含めない
  • /unwanted.txt JAR に含めない
  • その他は既定 (コンフリクト時にはエラー)

その他

Likeボタン(off)0
詳細設定を開く/閉じる
アカウント プロフィール画像

Software Engineer @ Tokyo

記事の執筆者にステッカーを贈る

有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

>>さらに詳しくステッカーを贈る
ステッカーを贈る コンセプト画像

Feedbacks

Feedbacks コンセプト画像

    ログインするとコメントを投稿できます。

    ログインする

    関連記事

    • Scala 文字列の処理
      書式指定 object Main { def main(args: Array[String]): Unit = { println("%d + %d = %d".format(1, 1, 2)) //=> 1 + 1 = 2 } } 文字列の比較 ヒアドキュメント 他の言語でいう「ヒアドキュメント」のようなものは """ で囲うことで実現できます。 object Main ...
      したくんしたくん5/18/2018に更新
      いいねアイコン画像0
    • Scala 日付に関する処理
      Date クラスを文字列にフォーマット import java.util.Date object Main { def main(args: Array[String]): Unit = { // format は Date に限らない文字列用の機能です。 println("%d-%d-%d" format (1, 1, 1)) //=> 1-1-1 printl...
      したくんしたくん5/5/2018に更新
      いいねアイコン画像0
    • Scala 関数のサンプルコード
      「デフォルト引数」および「Unit 型を返す関数」 object HelloWorld { def main(args: Array[String]): Unit = { def myPrint(myArg: String = "default_value") = println(myArg + "!") val result = myPrint() //=> defau...
      したくんしたくん5/26/2018に更新
      いいねアイコン画像0
    • Scala 組み込みの制御構造
      if-else 条件分岐で知られる if-else は三項演算子のようにも使用されます。 object HelloWorld { def main(args: Array[String]): Unit = { val myVal = if (!args.isEmpty) args(0) else "default" println(myVal) } ...
      したくんしたくん9/7/2021に更新
      いいねアイコン画像0
    • Scala の基本文法
      Scala は JVM 上で動作するバイトコードにコンパイルできる言語です。JAVA よりも柔軟な記述ができます。事前にこちらからダウンロードおよびインストールしておいてください。基本的な文法をまとめます。 変数および定数 object HelloWorld { def main(args: Array[String]): Unit = { val constVal = 1 //
      したくんしたくん9/7/2021に更新
      いいねアイコン画像0