Question

In a multi-project SBT build, how should one explicitly suppress the mainClass attribute in SBT-assembly?

I've searched extensively but cannot seem to find out how to prevent a main class from being set in the jar built by sbt-assembly in a multi-project build.

In a single-project build, this seems to work as long as there are at least two classes that might potentially be invoked by command line:

import sbt._
import Keys._

import sbtassembly.Plugin._
import sbtassembly.AssemblyUtils._
import AssemblyKeys._

object TestBuild extends Build {
  lazy val buildSettings = Defaults.defaultSettings ++ assemblySettings ++ Seq(
    version := "0.1-SNAPSHOT",
    organization := "com.organization",
    scalaVersion := "2.10.2",
    mergeStrategy in assembly := mergeFirst
  )

  lazy val root = Project(id = "test-assembly",
    base = file("."),
    settings = buildSettings) settings(
    mainClass in assembly := None
  )
  lazy val mergeFirst: String => MergeStrategy = { 
    case "reference.conf" | "rootdoc.txt" => MergeStrategy.concat
    case PathList("META-INF", xs @ _*) =>
    (xs map {_.toLowerCase}) match {
      case ("manifest.mf" :: Nil) | ("index.list" :: Nil) | ("dependencies" :: Nil) =>
      MergeStrategy.discard
      case ps @ (x :: xs) if ps.last.endsWith(".sf") || ps.last.endsWith(".dsa") =>
      MergeStrategy.discard
      case "plexus" :: xs =>
      MergeStrategy.discard
      case "services" :: xs =>
      MergeStrategy.filterDistinctLines
      case ("spring.schemas" :: Nil) | ("spring.handlers" :: Nil) =>
      MergeStrategy.filterDistinctLines
      case _ => MergeStrategy.first
    }
    case _ => MergeStrategy.first
  }
}

However, it appears that the mainClass := None isn't even necessary. In fact, even if it is left there, a main class will still be set in the manifest if there is only one candidate class.

Unforunately, in a multi-project build, I could not prevent a main class from being set even by including an additional class as a dummy entry-point.

After a lot of fiddling around, I found that I could prevent a main class from being set by setting mainClass to None in several scopes independently. In particular, this does the trick:

mainClass in assembly := None
mainClass in packageBin := None
nainClass in Compile := None
mainClass in run := None

I could not find this requirement stated in the documentation and cannot figure out why this is necessary. Setting mainClass in (Compile, run) := None does not work. They really need to be scoped separately.

Is this the proper way to manually suppress a main class or am I missing something? If not a bug, I think this should be documented somewhere, especially given that the behavior is not consistent between single- and multi-project builds.

Was it helpful?

Solution

Main-Class and all other attributes in jar are ultimately determined by packageOptions in assembly, so all you could remove Package.MainClass from it as follows:

lazy val root = Project(id = "test-assembly",
  base = file("."),
  settings = buildSettings) settings(
  packageOptions in assembly ~= { os => os filterNot {_.isInstanceOf[Package.MainClass]} }
)

This should work for multi-project builds too.

Note: sbt-assembly caches the output jar based on the metadata of the source input, so testing settings around assembly requires clearing the cache by calling clean.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top