Question

I'm working with an application that loads plugins as jars from a directory. The plugins are to have the following layout:

plugin.myplugin.jar/
  com.xxx.xxx/<compiled classes for plugin>
  META-INF/MANIFEST.MF (defines the main entry point for the plugin)
  lib/
     com.fasterxml.jackson.core.jackson-core-2.3.1.jar
     ...
     ...
     com.netflix.rxjava.rxjava-core-0.16.1.jar
     ...
     <all dependencies used by the plugin in their original jar format>

I can do this manually by using the sbt-native-packager plugin to produce the top-level plugin.myplugin.jar and then copying the jars from target/universal/stage/lib into the lib directory within plugin.myplugin.jar using something like 7-zip. Doing this manually works... but I'm trying to automate this task though and currently I am at a bit of a loss as to how to override an sbt-native-packager task or write my own to do this. Any ideas?

Was it helpful?

Solution

This is how you could achieve what you want with pure SBT 0.13.x (the example should work for older versions in general, but maybe you'd have to use different operators).

build.sbt

import Path.flat

libraryDependencies ++= Seq(
  "com.fasterxml.jackson.core" % "jackson-core" % "2.3.1",
  "com.netflix.rxjava" % "rxjava-core" % "0.16.1"
  // maybe more dpendencies
)

packageOptions in (Compile, packageBin) +=
        Package.ManifestAttributes("PluginMainClass" -> "com.xxx.xxx.Class")

// this will copy all managed jars to the ./lib in your jar
mappings in (Compile, packageBin) ++= {
  val cp = (managedClasspath in Compile).value.files
  cp pair flatRebase("/lib")
}

// this will move all your compiled classes to folder ./com.xxx.xxx in your jar
mappings in (Compile, packageBin) ++= {
  val compiledClasses = (products in Compile).value ** "*.class"
  val classDirectoryBase = (classDirectory in Compile).value
  compiledClasses pair rebase(classDirectoryBase, "com.xxx.xxx")
}

You can then use package to build the jar. In the example as above the jar will look like this:

  Length      Date    Time    Name
---------  ---------- -----   ----
      301  2014-05-09 20:13   META-INF/MANIFEST.MF
        0  2014-05-09 20:13   /lib/
  7126003  2013-09-27 11:44   /lib/scala-library.jar
   197986  2013-12-28 02:01   /lib/jackson-core-2.3.1.jar
   663553  2014-01-15 08:17   /lib/rxjava-core-0.16.1.jar
---------                     -------
  7987843                     5 files

And the manifest will look like this

Manifest-Version: 1.0
Implementation-Vendor: default
Implementation-Title: q-23553321
Implementation-Version: 0.1-SNAPSHOT
Implementation-Vendor-Id: default
PluginMainClass: com.xxx.xxx.Class // MAIN CLASS OF THE PLUGIN
Specification-Vendor: default
Specification-Title: q-23553321
Specification-Version: 0.1-SNAPSHOT

Edit

Is it possible to get the compiled classes in a multi module build though? IE if I have /modules/X,Y,Z and I want the compiled classes from those projects bundled under "com.xxx.xxx" in the artifact?

The general answer is yes, if the classes are unique. To do that you can use Scopes. In the build.sbt, replace the mappings for classes with the following:

// define the configuration axis
val anyProjectsCompile = ScopeFilter(inAnyProject, inConfigurations(Compile))

// we want to have class directory and products together to use rebase in the next step
val classDirectoryProducts = Def.task {
  (classDirectory.value, products.value)
}

mappings in (Compile, packageBin) ++= {
  classDirectoryProducts.all(anyProjectsCompile).value.flatMap { case (classDir, prods) =>
    val compiledClasses = (prods ** "*.class").get
    compiledClasses pair rebase(classDir, "com.xxx.xxx")
  }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top