Search code examples
scalasbtsbt-assemblymulti-project

Build multi-project fat jars with sbt-assembly


I have multi-project with the main module called root, consumer and producer modules with dependencies which depend on the core module. The core modules hold configuration related classes. I would like to build 2 separate jars for consumer and producer with separate main classes with sbt-assembly. However, when I try to build them individually like this sbt consumer/assembly or altogether by running sbt assemblyI get such an error and sbt cannot compile the whole project:

[error] (consumer / update) java.lang.IllegalArgumentException: a module is not authorized to depend on itself: demotest#demotest_2.11;0.1
[error] (producer / update) java.lang.IllegalArgumentException: a module is not authorized to depend on itself: demotest#demotest_2.11;0.1

What should I change in dependencies to package the jars properly? And should I run them separately or together?

lazy val overrides = Seq("com.fasterxml.jackson.core" % "jackson-core" % "2.9.5",
  "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.5",
  "com.fasterxml.jackson.module" % "jackson-module-scala_2.11" % "2.9.5")

lazy val commonSettings = Seq(
  name := "DemoTest",
  version := "0.1",
  scalaVersion := "2.11.8",
  resolvers += "Spark Packages Repo" at "http://dl.bintray.com/spark-packages/maven",
  dependencyOverrides ++= overrides
)

lazy val assemblySettings = Seq(
  assemblyMergeStrategy in assembly := {
    case PathList("org","aopalliance", xs @ _*) => MergeStrategy.last
    case PathList("javax", "inject", xs @ _*) => MergeStrategy.last
    case PathList("javax", "servlet", xs @ _*) => MergeStrategy.last
    case PathList("javax", "activation", xs @ _*) => MergeStrategy.last
    case PathList("org", "apache", xs @ _*) => MergeStrategy.last
    case PathList("com", "google", xs @ _*) => MergeStrategy.last
    case PathList("com", "esotericsoftware", xs @ _*) => MergeStrategy.last
    case PathList("com", "codahale", xs @ _*) => MergeStrategy.last
    case PathList("com", "yammer", xs @ _*) => MergeStrategy.last
    case PathList("org", "slf4j", xs @ _*) => MergeStrategy.last
    case PathList("org", "neo4j", xs @ _*) => MergeStrategy.last
    case PathList("com", "typesafe", xs @ _*) => MergeStrategy.last
    case PathList("net", "jpountz", xs @ _*) => MergeStrategy.last
    case PathList("META-INF", xs @ _*) => MergeStrategy.discard
    case "about.html" => MergeStrategy.rename
    case "META-INF/ECLIPSEF.RSA" => MergeStrategy.last
    case "META-INF/mailcap" => MergeStrategy.last
    case "META-INF/mimetypes.default" => MergeStrategy.last
    case "plugin.properties" => MergeStrategy.last
    case "log4j.properties" => MergeStrategy.last
    case x =>
      val oldStrategy = (assemblyMergeStrategy in assembly).value
      oldStrategy(x)
  }
)

lazy val sparkVersion = "2.2.0"

lazy val commonDependencies = Seq(
  "org.apache.kafka" %% "kafka" % "1.1.0",
  "org.apache.spark" %% "spark-core" % sparkVersion % "provided",
  "org.apache.spark" %% "spark-sql" % sparkVersion,
  "org.apache.spark" %% "spark-streaming" % sparkVersion,
  "org.apache.spark" %% "spark-streaming-kafka-0-10" % sparkVersion,
  "neo4j-contrib" % "neo4j-spark-connector" % "2.1.0-M4",
  "com.typesafe" % "config" % "1.3.0",
  "org.neo4j.driver" % "neo4j-java-driver" % "1.5.1",
  "com.opencsv" % "opencsv" % "4.1",
  "com.databricks" %% "spark-csv" % "1.5.0",
  "com.github.tototoshi" %% "scala-csv" % "1.3.5",
  "org.elasticsearch" %% "elasticsearch-spark-20" % "6.2.4"
)

lazy val root = (project in file("."))
  .aggregate(consumer, producer)



lazy val core = (project in file("core"))
  .settings(
    commonSettings,
    libraryDependencies ++= commonDependencies
  )

lazy val consumer = (project in file("consumer"))
  .settings(
    commonSettings,
    assemblySettings,
    libraryDependencies ++= commonDependencies)
  .settings(
    mainClass in assembly := Some("consumer.SparkConsumer"),
    assemblyJarName in assembly := "demo_consumer.jar"
  ).dependsOn(core)

lazy val producer = (project in file("producer"))
  .settings(
    commonSettings,
    assemblySettings,
    libraryDependencies ++= commonDependencies
  ).settings(
  mainClass in assembly := Some("producer.KafkaCheckinsProducer"),
  assemblyJarName in assembly := "demo_producer.jar"
).dependsOn(core)

Solution

  • The problem is in this line:

    lazy val commonSettings = Seq(
      name := "DemoTest",  // should not be defined at this level!
    )
    

    This name should be defined for root:

    lazy val root = (project in file("."))
      .aggregate(core, producer, consumer)
      .settings(
        name := "demotest"
      )
    

    The sub-modules will have "core", "producer" and "consumer" as their own names, unless name is not overriden in their settings.

    Please also note that in your example .aggregate does not include core, but it should indeed.