Lars Tackmann
Lars Tackmann

Reputation: 20895

SBT: Exclude class from Jar

I am converting a legacy jar project to SBT and for strange reasons that are not easily solved, this project comes with "javax/servlet/Servlet.class" inside it. So I need to somehow exclude this class from the jar file generated by package-bin. How do I accomplish this ?. Preferably I would like to exclude using a wildcard (i.e. javax.*).

The SBT assembly plugin does look like it has features that will do this, but I am worried that relying on sbt assembly means that my jar project will not work in a muliti module project (i.e. if I include it as a dependency in a war file then the war projects needs to be told to run assembly on the dependent jar project rather than package-bin - but I may be mistaken here).

Upvotes: 16

Views: 8269

Answers (2)

Mark Harrah
Mark Harrah

Reputation: 7019

Each task declares the other tasks and settings that it uses. You can use inspect to determine these inputs as described on Inspecting Settings and in a recent tutorial-style blog post by John Cheng.

In this case, the relevant task used by packageBin is mappings. The mappings task collects the files to be included in the jar and maps them to the path in the jar. Some background is explained on Mapping Files, but the result is that mappings produces a value of type Seq[(File, String)]. Here, the File is the input file providing the content and the String is the path in the jar.

So, to modify the mappings for the packageBin task, filter out the paths from the default mappings that you don't want to include:

mappings in (Compile,packageBin) ~= { (ms: Seq[(File, String)]) =>
  ms filter { case (file, toPath) =>
    toPath != "javax/servlet/Servlet.class"
  }
}

mappings in (Compile,packageBin) selects the mappings for the main package task (as opposed to test sources or the packageSrc task).

x ~= f means "set x to the result of applying function f to the previous value of x". (See More About Settings for details.)

The filter drops all pairs where the path corresponds to the Servlet class.

Upvotes: 16

Lars Tackmann
Lars Tackmann

Reputation: 20895

I came up with this solution, it defines a new compile task which depends on the previous compile task (thus effectively allowing me to hook in right after the source is compiled and before it's packaged)

def mySettings = {
  // add functionality to the standard compile task 
  inConfig(Compile)(Seq(compile in Compile <<= (target,streams,compile in Compile) map{
    (targetDirectory, taskStream, analysis) =>
      import taskStream.log
      // this runs after compile but before package-bin
      recursiveListFiles(targetDirectory, ".*javax.*".r) foreach { 
        file =>
          log.warn("deleting matched resource: " + file.getAbsolutePath())
          IO.delete(file)
      }
      analysis
    })) ++
    Seq(name := "MyProject", version := "1.0", exportJars := true)
}

def recursiveListFiles(f: File, r: Regex): Array[File] = {
  val these = f.listFiles
  val good = these.filter(f => r.findFirstIn(f.getName).isDefined)
  good ++ these.filter(_.isDirectory).flatMap(recursiveListFiles(_, r))
}

Its a little bit more complicated than what I had hoped but it allows me to do all sorts of modifications prior to packaging (in this case searching the target folder deleting all class files that matches a regular expression). Also it accomplished my second goal of sticking with the default SBT lifecycle.

Upvotes: 4

Related Questions