LoginSignup
4
2

More than 5 years have passed since last update.

sbt マルチプロジェクトビルド sbt-assemblyでそれぞれのjar生成

Last updated at Posted at 2019-04-11

既に存在する多くのロジックをコピペせずに、サブプロジェクトから既存のロジックをimportしてサブプロジェクトもjarとして生成したいと思ったので試した。

プロジェクト1つの状態でsbt-assemblyを使ってjar生成

プロジェクト1つの時代の元のディレクトリ構成(雰囲気)

app/src/
   /project/
   /target/
   /build.sbt

元のbuild.sbt

lazy val root = (project in file(".")).
  settings(
    name := "project1",
    version := "1.0",
    scalaVersion := "2.12.8",
    libraryDependencies ++= dervy,
    testTasks in Test := {},
    test in assembly := {}
  )

lazy val testTasks = taskKey[Unit]("test tasks.")


lazy val dervy = {
  Seq(
    使うライブラリ達
  )
}

parallelExecution in Test := false

assemblyMergeStrategy in assembly := {
  case PathList(ps @ _*) if ps.last endsWith ".html" => MergeStrategy.first
  case PathList("org", "slf4j", xs @ _*)             => MergeStrategy.first
  case PathList(ps @ _*) if ps.last endsWith ".properties" => MergeStrategy.first
  case PathList(ps @ _*) if ps.last endsWith ".xml" => MergeStrategy.first
  case PathList(ps @ _*) if ps.last endsWith ".types" => MergeStrategy.first
  case PathList(ps @ _*) if ps.last endsWith ".class" => MergeStrategy.first
  case PathList(ps @ _*) if ps.last endsWith ".txt" => MergeStrategy.first
  case "application.conf"                            => MergeStrategy.concat
  case "unwanted.txt"                                => MergeStrategy.discard
  case "BUILD"                                       => MergeStrategy.discard
  case PathList("META-INF", xs @ _*)                 => MergeStrategy.discard
  case x =>
    val oldStrategy = (assemblyMergeStrategy in assembly).value
    oldStrategy(x)
}

この状態でsbt assembly(addSbtPluginは設定済みの状態)を実行するとapp/target/scala-2.12/project1-assembly-1.0.jarが生成される。

これまでがプロジェクト1つの場合

マルチプロジェクトでsbt-assemblyを使ってそれぞれのjar生成

マルチプロジェクトのディレクトリ構成(雰囲気)

app/src/
   /project/
   /target/
   /sub-project/ <- 後にbuild.sbtにプロジェクト追加すると生成されるディレクトリ
   /build.sbt

サブプロジェクトを追加したbuild.sbt
変更点としてはassemblySettingsをまとめてそれぞれのプロジェクトに設定しているのと、subProjectを作成している点。
subProjectにはdependsOnでproject1のクラス等をimport出来るようにしている。
※各プロジェクトの設定はcommonSettingsを設定した方が良いがやってない。
マルチプロジェクト・ビルド 共通のセッティング

lazy val root = (project in file(".")).
  settings(
    name := "project1",
    version := "1.0",
    scalaVersion := "2.12.8",
    libraryDependencies ++= dervy,
    testTasks in Test := {},
    assemblySettings, // 追加
    test in assembly := {}
  )

lazy val subProject = (project in file("sub-project")).
  dependsOn(root).
  settings(
    name := "sub-project",
    version := "1.0",
    scalaVersion := "2.12.8",
    libraryDependencies ++= dervy,
    testTasks in Test := {},
    assemblySettings, // subProjectでも設定
    test in assembly := {}
  )

lazy val testTasks = taskKey[Unit]("test tasks.")


lazy val dervy = {
  Seq(
    使うライブラリ達
  )
}

parallelExecution in Test := false

// 設定をassemblySettingsにまとめる
lazy val assemblySettings = Seq(
  assemblyMergeStrategy in assembly := {
    case PathList(ps @ _*) if ps.last endsWith ".html" => MergeStrategy.first
    case PathList("org", "slf4j", xs @ _*)             => MergeStrategy.first
    case PathList(ps @ _*) if ps.last endsWith ".properties" => MergeStrategy.first
    case PathList(ps @ _*) if ps.last endsWith ".xml" => MergeStrategy.first
    case PathList(ps @ _*) if ps.last endsWith ".types" => MergeStrategy.first
    case PathList(ps @ _*) if ps.last endsWith ".class" => MergeStrategy.first
    case PathList(ps @ _*) if ps.last endsWith ".txt" => MergeStrategy.first
    case "application.conf"                            => MergeStrategy.concat
    case "unwanted.txt"                                => MergeStrategy.discard
    case "BUILD"                                       => MergeStrategy.discard
    case PathList("META-INF", xs @ _*)                 => MergeStrategy.discard
    case x =>
      val oldStrategy = (assemblyMergeStrategy in assembly).value
      oldStrategy(x)
  }
)

このようなbuild.sbtになっている状態でsbtを起動してprojectsコマンドと入力すると、rootsubProjectの2つが表示されるようになる

subProjectに切り替える場合project subProjectと入力すると切り替わる
この時点でsub-projectディレクトリが生成されていると思うのでsrc/main/scala/com/github/TakashiOshikawa/SubMain.scalaを作成して元々存在するロジックをsubProjectから利用してみる。

// ※パッケージ名は好きなように
package com.github.TakashiOshikawa

// ここでproject1側のクラスをimport出来ることを確認
import com.github.TakashiOshikawa.services.SomeService

object SubProjectMain {

  def main(args: Array[String]) = {
    println("sub project main !!")
    println(SomeService.run(1))
  }
}

これでsbt subProject/compilesbt subProject/runで実行できることを確認したらsbt-assemblyでjar生成していく

project1のコマンド sbt assembly
sub projectのコマンド sbt subProject/assembly

それぞれのjarは以下のように生成された
app/target/scala-2.12/project1-assembly-1.0.jar
app/sub-project/target/scala-2.12/sub-project-assembly-1.0.jar

まとめ

マルチプロジェクトの記事はそこそこあるが、サブプロジェクトのjarをsbt-assemblyで生成するのはあまり見かけなかったので少し手こずった。
そもそもプロジェクトごとにjar生成なんて殆どやることはなくて、単にプロジェクト分割して各レイヤーの依存方向を強制するために使うのが普通なのかどうなのか…

参照

https://github.com/sbt/sbt-assembly
https://www.scala-sbt.org/1.x/docs/ja/Multi-Project.html#%E5%85%B1%E9%80%9A%E3%81%AE%E3%82%BB%E3%83%83%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0
https://www.qoosky.io/techs/1ec18db8bc
https://pbassiner.github.io/blog/defining_multi-project_builds_with_sbt.html
https://stackoverflow.com/questions/37588407/how-to-make-multi-project-fat-jar-with-sbt-assembly

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2