Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build jar from play framework 2.5.x

I try to build jar from clean project with sbt-assembly how describes in docs:

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

import AssemblyKeys._

assemblySettings

mainClass in assembly := Some("play.core.server.ProdServerStart")

fullClasspath in assembly += Attributed.blank(PlayKeys.playPackageAssets.value)

but it gives me a lot of deduplicate errors. How to build "fat" jar from play with sbt-assembly?

like image 486
zella Avatar asked Apr 25 '16 09:04

zella


1 Answers

There are several bits that need to be setup to build a play application as a fat jar.

Starting with the asssembly plugin. There must be a file named assembly.sbt located directly in the project directory. To be exact and eliminate confusion if your project is named MyPlayProject the file must be located at "MyPlayProject/project/assembly.sbt" and should contain only the following.

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3"

Obviously the version is subject to changed but it should work. This will add the assembly plugin to your project and does not work if added to the plugins.sbt file like other plugins.

As a matter of covering all basis also make sure you have the standard sbt items covered including project/build.properties. Note that for Play 2.5.x sbt version 13.8 or greater is required per https://www.playframework.com/documentation/2.5.x/Migration25#sbt-upgrade-to-0.13.11

sbt.version=0.13.11

And the likely critical part causing your issues is part of the build.sbt file which should include the merge strategy. There are a number of files that are standard in jar files (e.g. MANIFEST.MF etc) and you must do something to handle these duplicated files when you combine all the jars into a single fat jar. Basic example

assemblyMergeStrategy in assembly := {
  case r if r.startsWith("reference.conf") => MergeStrategy.concat
  case PathList("META-INF", m) if m.equalsIgnoreCase("MANIFEST.MF") => MergeStrategy.discard
  case x => MergeStrategy.first
}

Your mileage may very with the specific cases to be handled but this is pretty basic for a standard a play fat jar.

Some basic details on the above merge strategy as follows:

  • Merge all jar's reference.conf to single file for fat jar. I forget the specific issue that lead to this but I don't think you will be able to run your play application as fat jar without this step. I have no proof of this)
  • Discard the MANIFEST.MF Files for each jar. Many examples online with show this as 'case PathList("META-INF", xs @ _*) => MergeStrategy.discard'. This works by removing anything in the META-INF directory of the jar. However starting Play 2.4 dependency inject has been getting pushed pretty hard with play and if using dependency injection there is a library dependency on net.sf.ehcache which includes service files required to use dependency injection. The fix is to leave all the other files and just discard the MANIFEST.MF files specifically as I have done or just remove everything and don't use anything dependency injection (not recommended)
  • A general catch all case that keeps the first of any duplicate files and discards the others. Useful when you may have multiple common dependencies on the same library and there is no reason the keep multiple copies.

Since I can't clarify with comments here is a complete sample build.sbt file.

name := """MyPlayProject"""

version := "1.0"

lazy val `root` = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.11.8"

// Set JS Engine to use
JsEngineKeys.engineType := JsEngineKeys.EngineType.Node

// Set repository details for resolving additional depenecies
resolvers ++= Seq(
  "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases",
  "ClouderaRepo" at "https://repository.cloudera.com/content/repositories/releases"
)

// Specifies dependencies to use in project
libraryDependencies ++= Seq(
  "org.apache.kafka" % "kafka_2.11" % "0.9.0.1",
  jdbc,
  cache,
  ws,
  specs2 % Test
)

// Add an additional source content route besides the default
unmanagedResourceDirectories in Test <+=  baseDirectory ( _ /"target/web/public/test" )

unmanagedSourceDirectories in Compile += baseDirectory.value / "src2" / "main" / "scala"

sourceDirectory in Compile <<= baseDirectory / "src2/main/scala"

scalaSource in Compile <<= baseDirectory / "src2/main/scala"

// Informs SBT Assembly how to handle duplicated files when combining project and dependency jars into a single fat jar
assemblyMergeStrategy in assembly := {
  case n if n.startsWith("reference.conf") => MergeStrategy.concat
  case PathList("META-INF", xs @ _*) => MergeStrategy.discard
  case x => MergeStrategy.first
}

I would have left comments for more details before answering so I could have been more exact with my answer but the question is a bit old and that said my rep is not high enough anyways... hope this helps.

P.S. I found your question while searching for help with my own merge issues migrating play 2.3.4 to 2.5.4. This is why I changed the META-INF merge strategy to only discard the MANIFEST.MF, otherwise it causes the below exception. I am re-posting it with my answer in hopes it might get picked up in search results since I found very few initially when I was searching it.

Oops, cannot start the server.
com.google.inject.CreationException: Unable to create injector, see the following errors:

1) Error in custom provider, net.sf.ehcache.CacheException: java.lang.AssertionError: No net.sf.ehcache.EhcacheInit services found
  while locating play.api.cache.CacheManagerProvider
  while locating net.sf.ehcache.CacheManager
    for field at play.api.cache.NamedEhCacheProvider.manager(Cache.scala:211)
  while locating play.api.cache.NamedEhCacheProvider
  at com.google.inject.util.Providers$GuicifiedProviderWithDependencies.initialize(Providers.java:149)
  at play.api.cache.EhCacheModule.play$api$cache$EhCacheModule$$bindCache$1(Cache.scala:184):
Binding(interface net.sf.ehcache.Ehcache qualified with QualifierInstance(@play.cache.NamedCache(value=play)) to ProviderTarget(play.api.cache.NamedEhCacheProvider@45312be2)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)

1 error
    at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:466)
    at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:176)
    at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110)
    at com.google.inject.Guice.createInjector(Guice.java:96)
    at com.google.inject.Guice.createInjector(Guice.java:84)
    at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:181)
    at play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:123)
    at play.api.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.scala:21)
    at play.core.server.ProdServerStart$.start(ProdServerStart.scala:47)
    at play.core.server.ProdServerStart$.main(ProdServerStart.scala:22)
    at play.core.server.ProdServerStart.main(ProdServerStart.scala)
Caused by: net.sf.ehcache.CacheException: java.lang.AssertionError: No net.sf.ehcache.EhcacheInit services found
    at net.sf.ehcache.LibraryInit.init(LibraryInit.java:55)
    at net.sf.ehcache.CacheManager.init(CacheManager.java:366)
    at net.sf.ehcache.CacheManager.<init>(CacheManager.java:259)
    at net.sf.ehcache.CacheManager.newInstance(CacheManager.java:1037)
    at net.sf.ehcache.CacheManager.newInstance(CacheManager.java:936)
    at net.sf.ehcache.CacheManager.create(CacheManager.java:904)
    at play.api.cache.CacheManagerProvider.get$lzycompute(Cache.scala:205)
    at play.api.cache.CacheManagerProvider.get(Cache.scala:202)
    at play.api.cache.CacheManagerProvider.get(Cache.scala:201)
    at com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:81)
    at com.google.inject.internal.BoundProviderFactory.provision(BoundProviderFactory.java:72)
    at com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:61)
    at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:62)
    at com.google.inject.internal.SingleFieldInjector.inject(SingleFieldInjector.java:54)
    at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:132)
    at com.google.inject.internal.MembersInjectorImpl$1.call(MembersInjectorImpl.java:93)
    at com.google.inject.internal.MembersInjectorImpl$1.call(MembersInjectorImpl.java:80)
    at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1103)
    at com.google.inject.internal.MembersInjectorImpl.injectAndNotify(MembersInjectorImpl.java:80)
    at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:62)
    at com.google.inject.internal.InjectorImpl.injectMembers(InjectorImpl.java:984)
    at com.google.inject.util.Providers$GuicifiedProviderWithDependencies.initialize(Providers.java:149)
    at com.google.inject.util.Providers$GuicifiedProviderWithDependencies$$FastClassByGuice$$2a7177aa.invoke(<generated>)
    at com.google.inject.internal.cglib.reflect.$FastMethod.invoke(FastMethod.java:53)
    at com.google.inject.internal.SingleMethodInjector$1.invoke(SingleMethodInjector.java:57)
    at com.google.inject.internal.SingleMethodInjector.inject(SingleMethodInjector.java:91)
    at com.google.inject.internal.MembersInjectorImpl.injectMembers(MembersInjectorImpl.java:132)
    at com.google.inject.internal.MembersInjectorImpl$1.call(MembersInjectorImpl.java:93)
    at com.google.inject.internal.MembersInjectorImpl$1.call(MembersInjectorImpl.java:80)
    at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092)
    at com.google.inject.internal.MembersInjectorImpl.injectAndNotify(MembersInjectorImpl.java:80)
    at com.google.inject.internal.Initializer$InjectableReference.get(Initializer.java:174)
    at com.google.inject.internal.Initializer.injectAll(Initializer.java:108)
    at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:174)
    ... 9 more
Caused by: java.lang.AssertionError: No net.sf.ehcache.EhcacheInit services found
    at net.sf.ehcache.LibraryInit.initService(LibraryInit.java:78)
    at net.sf.ehcache.LibraryInit.init(LibraryInit.java:50)
    ... 42 more
like image 193
DVS Avatar answered Sep 22 '22 10:09

DVS