Help us understand the problem. What is going on with this article?

sbt を始めるためのまとめ -プロジェクト構成-

More than 1 year has passed since last update.

アカウンティング・サース・ジャパン Advent Calendar の15日目!

はじめに

自分がsbtを使い始める上で、まず公式のGetting Startedを読んでみたのですが、なかなか理解しにくい部分や、今後のためにまとめておきたい部分などがあったので、一部についてまとめ直しや補足をしています。これからsbtを始める方は公式のGetting Startedの補足に見ていただけばと思います。今回はsbtのプロジェクト構成の部分をまとめていきます。

公式の Getting Started
http://www.scala-sbt.org/0.13/docs/Getting-Started.html

sbtプロジェクトとビルド定義の関係

sbtプロジェクトの構成

sbtのプロジェクトは、マルチプロジェクトとして構成できます。単一のプロジェクトももちろん可能ですが、マルチプロジェクトにして嬉しいこととしては、複数のプロジェクトを1つのビルドにまとめたり、プロジェクト間にコンパイル時の依存関係をもたせらるところです。
例えば、lib, web, domain の3つのプロジェクトをもつ myapp プロジェクトの構造は以下のようになります。(testやresourceのディレクトリは省略)

myapp/
  lib/
    src/main/scala
    build.sbt
  web/
    src/main/scala
    build.sbt
  domain/
    src/main/scala
    build.sbt
  project/
  build.sbt

ここで、build.sbt や project/ はsbtのビルド定義のために配置します。詳しくは後ほど。
ちなみに、単一のプロジェクトの構成は以下のようになります。

myapp/
  src/main/scala
  project/
  build.sbt

sbtのビルド定義

sbtは利用者が設定したビルド定義を、sbtコマンド実行時に読み込んで、ビルド情報のMapを構築します。このMapをもとに、ビルドにまつわるさまざまなタスクが実行されます。このビルド定義はどこで指定されるのかというと、

  1. project/ 配下で, ビルドオブジェクト(sbt.Buildをextendsしたobject)を用意して定義する
    • 現在は.sbtによる定義の方が推奨されているようです
  2. グローバルなビルド定義: ~/.sbt/0.13/global.sbt
    • すべてのプロジェクトのビルドに適用される
  3. プラグインによるビルド定義
  4. 各プロジェクトの .sbt ファイルに定義されたビルド定義

となっているようです。上記の順番で、エントリが追加されたビルド情報のMapが生成されていき、重複する定義情報は後勝ちになります。(つまり、下の方が優先される)

project/ にビルド定義の詳細を押し込む

project/ は親プロジェクトに対するビルド定義プロジェクトになっていて、ここに.scalaファイルを配置することで、親プロジェクトのビルド定義(.sbt)から参照できるようになります。

myprj
  project/
    Build.scala
  build.sbt       -- project/Build.scalaを参照できる

project/*.scala もビルドされる必要があるので、sbtでは project/もsbtプロジェクトとしてあつかいます。(project/に対するビルドはメタビルドと呼びます)では、project/ に対するビルド定義はどのように行うかというと、project/*.sbtだったり、project/project/*.scalaを(必要に応じて)用意します。ということで、ビルド定義プロジェクトについては、再帰的に扱っているようです。

myprj
  project/
    project/
      Build.scala
    build.sbt       -- project/project/Build.scalaを参照する
  build.sbt

また、ビルド定義プロジェクト(project/)では、グローバルプラグイン(~/.sbt/0.13/plugins/) からビルド定義の影響を受けます。

では、project/*.scalaはどのような場合に使うべきかというと、build.sbtの見通しをよくするために、ビルドタスクの実装や共有する値の定義など、詳細にあたる部分を外に出しておきたい時に、project/*.scalaに押し込むというイメージだと理解しています。

ビルド定義(build.sbt)を記述する

sbtは上記のように各種ビルド定義からビルド情報のMapを構築しますが、それをどのようにbuild.sbtに定義するのかという話です。

キーとSetting[T]

ビルド情報Mapの元ネタになるのが、ビルド定義に記述されたSetting[T]です。Tはビルド定義情報Mapでの値の型を表しています。Setting[T]はキー := 値 のように定義できます。例えば、ルートプロジェクトにプロジェクト名(Setting[String])を定義するには、以下のようにbuild.sbtに記述します

build.sbt
lazy val root = (project in file(".")).
  settings(
    name := "myapp"
  )

ここで、nameはsbtで事前に定義されているキーで、SettingKey[String]の型をもっています。また、SettingKeyでは以下の := メソッドが定義されています。

def :=(v: T): Setting[T] = ...

つまり、SettingKey[T]に対する値を定義することで、Setting[T]を生成してビルド定義に追加していくというイメージです。
キーには3つの種類があり、その種類に応じてSetting[T]の定義となります。

キー Setting 説明
SettingKey[T] Setting[T] ビルド定義のロード時に一度だけ評価される
TaskKey[T] Setting[Task[T]] タスクの定義に用いる。タスクの実行時に毎回評価される
InputKey[T] Setting[InputTask[T]] コマンドラインからの入力を受け付けるタスクを定義する

さらに、 複数の値を持つキー(SettingKey[Seq[String]]など)については、+= や ++= で Setting[T]を生成できるようになっています。例えば定義済みのlibraryDependenciesキーは、SettingKey[Seq[ModuleID]]という型になっているので、ビルド定義では以下のように記載できます。

lazy val root = (project in file(".")).
  settings(
    name := "myapp",
    libraryDependencies ++= Seq(
      "xxx" % "aaa" % "1.0",
      "yyy" % "bbb" % "2.0"
    )
  )

キーはsbtで定義済みのキーに加え、自分で定義して利用することもできます。

マルチプロジェクト構成を定義する

ビルド定義(build.sbt)で、依存関係をもたせたマルチプロジェクトを定義するための要素としては、以下の2点です。

  • Aggregationを使って、複数のサブプロジェクトに対してタスクの実行をまとめる。
  • プロジェクト間のクラスパスの依存関係を定義する。
    • コンパイル時に自動的に依存関係に沿ったコンパイルが実行される。

例として、以下の構造のmyappプロジェクトを考えます。

myapp/       -- タスク実行時に、web,cliのタスクをまとめて実行したい
  lib/       
  test_lib/  
  web/       -- Compileではlib、Testではtest_libを参照
  cli/       -- Compileではlib、Testではtest_libを参照
  project/
  build.sbt

このマルチプロジェクトを実現するためには、以下のようにbuild.sbtに記載します。

build.sbt
lazy val root = (project in file(".")).
  settings(
    name := "myapp",
    ...
  ).
  aggregate(web, cli)

lazy val lib = (project in file("lib")).
  settings(
    name := "lib",
    ...
  )

lazy val testLib = (project in file("test_lib")).
  settings(
    name := "test-lib",
    ...
  )

lazy val web = (project in file("web")).
  settings(
    name := "web",
    ...
  ).
  dependsOn(
    lib,
    testLib % "compile->test")

lazy val cli = (project in file("cli")).
  settings(
    name := "cli",
    ...
  ).
  dependsOn(
    lib,
    testLib % "compile->test")

Project#aggregateや、Project#dependsOnによって、プロジェクト間の依存関係を定義しています。

おわりに

今回はsbtのプロジェクト構成の組み方と、キーの種類という基本的な部分についてまとめました。
mavenやgradleにもマルチプロジェクトは出てくるので、そこは何となく理解できましたが、
キーというのはsbt特有な考え方ですね。しかもこのキーはスコープの概念と合わさって実は奥が深かったりするのです。ということで次回は、キーのスコープ周りについてまとめたいと思います。最後に自分がsbtを試す時によく使ったコマンドをまとめておきます。

sbtコマンドまとめ

コマンド 説明
projects マルチプロジェクトの場合にプロジェクト一覧を表示
project <projectname> マルチプロジェクトの場合に指定プロジェクトに移動
plugins 有効なプラグインの一覧を表示
tasks タスクの一覧を表示
inspect <keyname> キーをインスペクトする。適用されるスコープやデリゲートの確認など
show <keyname> TaskKeyの実行結果を確認
<keyname> SettingKeyの値を確認
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away