1

I am trying to define a separate package task without modifying the original task in compile configuration. This new task will package only a subset of classes conforming an API which we need to be able to share with other teams so they can write plugins for our application. So the end result will be two jars, one with the full application and a second one with a subset of the classes.

I approached this problem by creating a different configuration which I called pluginApi and would redefine the packageBin task within this new configuration so it does not change the original definition of packageBin. This idea was taken from here:

How to create custom "package" task to jar up only specific package in SBT?

In my build.stb I have:

lazy val PluginApi = config("pluginApi") extend(Compile) describedAs("Custom plugin api configuration")

lazy val root = project in file(".") overrideConfigs (PluginApi)

This effectively creates my new configuration and I can call

sbt pluginApi:packageBin

Which generates the complete jar in the same way as compile:packageBin would do. I then try to modify the mappings in the new packageBin task with:

mappings in (PluginApi, packageBin) ~= { (ms: Seq[(File, String)]) =>
  ms filter { case (file, toPath) =>
    toPath.startsWith("some/path/defining/api")
  }
}

but this has no effect. I think the reason is because the call to pluginApi:packageBin is delegated to compile:packageBin rather than it being a cloned task.

I can redefine a new packageBin within the new scope like:

packageBin in PluginApi := {

}

However I would have to rewrite all packageBin functionality instead of reusing existing code. Also, in case that rewriting is unavoidable I am not sure how that implementation would be.

Could somebody provide an example about how to achieve this?

Community
  • 1
  • 1
Miquel
  • 4,741
  • 3
  • 20
  • 19

2 Answers2

2

You could have it done as follows

lazy val PluginApi = config("pluginApi").extend(Compile)

inConfig(PluginApi)(Defaults.compileSettings) // you have to have standard

mappings in (PluginApi, packageBin) := {
  val original = (mappings in (PluginApi, packageBin)).value
  original.filter { case (file, toPath) => toPath.startsWith("some/path/defining/api") }
}

unmanagedSourceDirectories in PluginApi := (unmanagedSourceDirectories in Compile).value

Note that, if you keep your sources in src/main/scala you'll have to override unmanagedSourceDirectories in the newly created configuration.

Normally the unmanagedSourceDirectories contains the configuration name. E.g. src/pluginApi/scala or src/pluginApi/java.

lpiepiora
  • 13,659
  • 1
  • 35
  • 47
  • Thanks for your answer. I am getting an error with this code: [info] error: `value` can only be called on a task within a task definition macro, such as :=, +=, ++=, or Def.task. on line val original = (mappings in (PluginApi, packageBin)).value. Any idea how this could be fixed? – Miquel Jul 18 '14 at 10:23
  • @Miquel no worries, if it works for you please upvote/accept, if not please let me know. – lpiepiora Jul 18 '14 at 10:25
  • The problem I described above was a typo. The code compiles and runs but I get the full jar, no filtering is happening. Any idea why? – Miquel Jul 18 '14 at 10:33
  • @Miquel are you sure you're filtering it correctly. You can print out `toPath` and `file` for debug purposes, to see if you're getting it right. Just do `println(toPath); toPath.startsWith("some/path/defining/api")`. – lpiepiora Jul 18 '14 at 10:35
  • I did just what you mentioned and nothing is printed, I also tried with an non existing path which should give an empty jar. It looks like the defined mappings are not being executed. – Miquel Jul 18 '14 at 10:38
  • Just to be sure, you execute `pluginApi:packageBin`, right? – lpiepiora Jul 18 '14 at 10:39
  • Yes, I do: sbt clean pluginApi:packageBin and it compiles and generates the jar with all classes inside – Miquel Jul 18 '14 at 10:40
  • 1
    Ok, it looks like I did not copy the code properly, I started again from scratch and it works, thanks a lot and apologies for the confusion – Miquel Jul 18 '14 at 10:44
  • 1
    It is interesting to note that while the solution works like a charm, the command sbt clean compile package pluginApi:packageBin will compile all classes twice. It might be better to have all classes intended for sharing within the config directory (src/pluginApi/scala) as @lpiepiora suggested – Miquel Jul 18 '14 at 15:24
0

I have had similar problems (with more than one jar per project). Our project uses ant - here you can do it, you just will repeat yourself a lot.

However, I have come to the conclusion that this scenario (2 JARs for one project) actually can be simplified by splitting the project - i.e. making 2 modules out of it. This way, I don't have to "fight" tools which assume project==artifact (like sbt, maybe maven?, IDEA's default setting,...). As a bonus point the compiler helps me to verify that my dependencies are correct, i.e. that I did not accidentally make my API package depend on the implementation package - when compiling everything together and only splitting classes apart in the JAR step, you do run the risk of getting an invalid dependency in your setup which you would only see when testing, because during compile time everything is compiled together.

Wolfgang Liebich
  • 400
  • 2
  • 12