Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a custom class loader for a module dependency in SBT

I have a multi-module SBT build consisting of api, core and third-party. The structure is roughly this:

api
|- core
|- third-party

The code for third-party implements api and is copied verbatim from somewhere else, so I don't really want to touch it.

Because of the way third-party is implemented (heavy use of singletons), I can't just have core depend on third-party. Specifically, I only need to use it via the api, but I need to have multiple, isolated copies of third-party at runtime. (This allows me to have multiple "singletons" at the same time.)

If I'm running outside of my SBT build, I just do this:

def createInstance(): foo.bar.API = {
  val loader = new java.net.URLClassLoader("path/to/third-party.jar", parent)
  loader.loadClass("foo.bar.Impl").asSubclass(classOf[foo.bar.API]).newInstance()
}

But the problem is that I don't know how to figure out at runtime what I should give as an argument to URLClassLoader if I'm running via sbt core/run.

like image 764
larsrh Avatar asked Apr 11 '15 14:04

larsrh


1 Answers

This should work, though I didn't quite tested it with your setup.

The basic idea is to let sbt write the classpath into a file that you can use at runtime. sbt-buildinfo already provides a good basis for this, so I'm gonna use it here, but you might extract just the relevant part and not use this plugin as well.

Add this to your project definition:

lazy val core = project enablePlugins BuildInfoPlugin settings (
  buildInfoKeys := Seq(BuildInfoKey.map(exportedProducts in (`third-party`, Runtime)) {
    case (_, classFiles) ⇒ ("thirdParty", classFiles.map(_.data.toURI.toURL)) 
  })
  ...

At runtime, use this:

def createInstance(): foo.bar.API = {
  val loader = new java.net.URLClassLoader(buildinfo.BuildInfo.thirdParty.toArray, parent)
  loader.loadClass("foo.bar.Impl").asSubclass(classOf[foo.bar.API]).newInstance()
}

exportedProducts only contains the compiled classes for the project (e.g. .../target/scala-2.10/classes/). Depending on your setup, you might want to use fullClasspath instead (which also contains the libraryDependencies and dependent projects) or any other classpath related key.

like image 152
knutwalker Avatar answered Nov 09 '22 07:11

knutwalker