Skip to content

Deploy über-JARs. Restart processes. (port of codahale/assembly-sbt)

License

Notifications You must be signed in to change notification settings

sbt/sbt-assembly

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sbt-assembly

Deploy fat JARs. Restart processes.

sbt-assembly is a sbt 0.10+ port of an awesome sbt plugin by codahale:

assembly-sbt is a simple-build-tool plugin for building a single JAR file of your project which includes all of its dependencies, allowing to deploy the damn thing as a single file without dicking around with shell scripts and lib directories or, worse, welding your configuration to your deployable in the form of a WAR file.

Requirements

  • sbt
  • The burning desire to have a simple deploy procedure.

Reporting Issues & Contributing

Before you email me, please read Issue Reporting Guideline carefully. Twice. (Don't email me)

Setup

Using Published Plugin

For sbt 0.12 and 0.13 add sbt-assembly as a dependency in project/plugins.sbt:

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

Using Source Dependency

Alternately, you can have sbt checkout and build the plugin's source from version control.

Specify sbt-assembly.git as a dependency in project/project/build.scala:

import sbt._

object Plugins extends Build {
  lazy val root = Project("root", file(".")) dependsOn(
    uri("git:https://github.com/sbt/sbt-assembly.git#XX") // where XX is tag or sha1
  )
}

(You may need to check this project's tags to see what the most recent release is. I'm notoriously crap about updating the version numbers in my READMEs.)

Usage

Applying the Plugin to a Project (Adding the assembly Task)

First, make sure that you've added the plugin to your build (either the published builds or source from Git).

If you're using build.sbt add this:

import AssemblyKeys._ // put this at the top of the file

assemblySettings

If you are using build.scala:

import sbt._
import Keys._
import sbtassembly.Plugin._
import AssemblyKeys._

object Builds extends Build {
  lazy val buildSettings = Defaults.defaultSettings ++ Seq(
    version := "0.1-SNAPSHOT",
    organization := "com.example",
    scalaVersion := "2.10.1"
  )

  lazy val app = Project("app", file("app"),
    settings = buildSettings ++ assemblySettings) settings(
      // your settings here
    )
}

Note: Due to SI-3488, you have to put your settings in settings(...) method esp. if you rewire mergeStrategy.

Now you'll have an awesome new assembly task which will compile your project, run your tests, and then pack your class files and all your dependencies into a single JAR file: target/scala_X.X.X/projectname-assembly-X.X.X.jar.

> assembly

If you specify a mainClass in assembly in build.sbt (or just let it autodetect one) then you'll end up with a fully executable JAR, ready to rock.

Here is the list of the keys you can rewire for assembly task.

target                        assembly-jar-name             test
assembly-option               main-class                    full-classpath
dependency-classpath          assembly-excluded-files       assembly-excluded-jars

For example the name of the jar can be set as follows in build.sbt:

jarName in assembly := "something.jar"

To skip the test during assembly,

test in assembly := {}

To exclude Scala library,

assembleArtifact in packageScala := false

To exclude the class files from the main sources,

assembleArtifact in packageBin := false

To exclude some jar file, first consider using "provided" dependency. The dependency will be part of compilation, but excluded from the runtime. Next, try creating a custom configuration that describes your classpath. If all efforts fail, here's a way to exclude jars:

excludedJars in assembly <<= (fullClasspath in assembly) map { cp => 
  cp filter {_.data.getName == "compile-0.1.0.jar"}
}

To exclude specific files, customize merge strategy.

To make a jar containing only the dependencies, type

> assembly-package-dependency

NOTE: If you use -jar option for java, it will ignore -cp, so if you have multiple jars you have to use -cp and pass the main class: java -cp "jar1.jar:jar2.jar" Main

To set an explicit main class,

mainClass in assembly := Some("com.example.Main")

Merge Strategy

If multiple files share the same relative path (e.g. a resource named application.conf in multiple dependency JARs), the default strategy is to verify that all candidates have the same contents and error out otherwise. This behavior can be configured on a per-path basis using either one of the following built-in strategies or writing a custom one:

  • MergeStrategy.deduplicate is the default described above
  • MergeStrategy.first picks the first of the matching files in classpath order
  • MergeStrategy.last picks the last one
  • MergeStrategy.singleOrError bails out with an error message on conflict
  • MergeStrategy.concat simply concatenates all matching files and includes the result
  • MergeStrategy.filterDistinctLines also concatenates, but leaves out duplicates along the way
  • MergeStrategy.rename renames the files originating from jar files
  • MergeStrategy.discard simply discards matching files

The mapping of path names to merge strategies is done via the setting assembly-merge-strategy which can be augmented as follows:

mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) =>
  {
    case PathList("javax", "servlet", xs @ _*)         => MergeStrategy.first
    case PathList(ps @ _*) if ps.last endsWith ".html" => MergeStrategy.first
    case "application.conf" => MergeStrategy.concat
    case "unwanted.txt"     => MergeStrategy.discard
    case x => old(x)
  }
}

NOTE:

  • mergeStrategy in assembly expects a function, you can't do mergeStrategy in assembly := MergeStrategy.first!
  • Some files must be discarded or renamed otherwise, you're breaking the zip or legal license. Use the pattern above.

By the way, the first case pattern in the above using PathList(...) is how you can pick javax/servlet/* from the first jar. If the default MergeStrategy.deduplicate is not working for you, that likely means you have multiple versions of some library pulled by your dependency graph. The real solution is to fix that dependency graph. You can work around it by MergeStrategy.first but don't be surprised when you see ClassNotFoundException.

Here is the default:

  val defaultMergeStrategy: String => MergeStrategy = { 
    case "reference.conf" | "rootdoc.txt" =>
      MergeStrategy.concat
    case PathList(ps @ _*) if isReadme(ps.last) || isLicenseFile(ps.last) =>
      MergeStrategy.rename
    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.deduplicate
      }
    case _ => MergeStrategy.deduplicate
  }

Custom MergeStrategys can find out where a particular file comes from using the sourceOfFileForMerge method on sbtassembly.AssemblyUtils, which takes the temporary directory and one of the files passed into the strategy as parameters.

Caching

By default for performance reasons, the result of unzipping any dependency jars to disk is cached from run-to-run. This feature can be disabled by setting:

assemblyCacheUnzip in assembly := false

In addition the fat JAR is cached so its timestamp changes only when the input changes. This feature requires checking the SHA-1 hash of all *.class files, and the hash of all dependency *.jar files. If there are a large number of class files, this could take a long time, although with hashing of jar files, rather than their contents, the speed has recently been improved. This feature can be disabled by setting:

assemblyCacheOutput in assembly := false

Publishing

If you wish to publish your assembled artifact along with the publish task and all of the other artifacts, you can add an assembly classifier (or other):

artifact in (Compile, assembly) ~= { art =>
  art.copy(`classifier` = Some("assembly"))
}

addArtifact(artifact in (Compile, assembly), assembly)

License

Copyright (c) 2010-2013 e.e d3si9n, Coda Hale

Published under The MIT License, see LICENSE

About

Deploy über-JARs. Restart processes. (port of codahale/assembly-sbt)

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Scala 99.8%
  • Other 0.2%