sbt plugin dynamically load user defined code?

496 views Asked by At

I am working on a sbt plugin that generates Scala models given a database using Slick code generator

I would ofcourse want users to override the code generator so my plugin needs to support this:

  • Anyway I can dynamically load a Scala class given a path to it in the build.sbt plugin keys? For example, in the user's parent build.sbt, she would provide something like codegen.override=com.company.project.CustomCodegenerator which looks like this

  • Related to above; the custom codegen may use some other libraries so a simple dynamic class load may not suffice. Anyway an sbt plugin can inherit the dependencies of the project using that plugin?

Here is the full discussion about this: https://github.com/papauschek/play-slick-evolutions-plugin/issues/1

1

There are 1 answers

0
Eugene Yokota On

At the end of the day you need to run some code to generate Scala source files.

Generating files

As you know, sbt has a hook for generating source files called sourceGenerators, which is documented in Generating files. You as the plugin author should provide a task that generates Seq[File] under (sourceManaged in Compile).value / "garfield" using Slick code generator as the default implementation. Let's call this generateModel. Your plugin could have the following settings:

sourceGenerators in Compile += generateModel.taskValue,
generateModel := defaultGenerateModel.value,
defaultGenerateModel := { ... }

If your build user wants to rewire generateModel, he or she could do so like this:

generateModel := {
  val file = (sourceManaged in Compile).value / "garfield" / "Foo.scala"
  IO.write(file, """case class Foo() {}""")
  Seq(file)
}

If the code generation is contained within the sbt plugin, like the above, you don't need to do any dynamic things. Since play-slick-evolutions-codegen-plugin depends on slick-codegen, this shouldn't be a problem.

Dynamically loading user's code

Since the question is directly on dynamically loading the user's code, I'd also put some pointers of that too.

  • One way is to use sbt.Run API from an existing configuration. This is equivalent of calling run task with some customized parameter. If you're generating code for Compile configuration, using the runner for any configuration that depends on it would not be a good idea.
  • Another similar way is to use sbt.Fork API. Forking lets you run code outside of the plugin.

Given sbt would automatically order tasks around based on the dependencies among them and runs multiple tasks in parallel, dynamically executing code is fraught with unexpected perils.