sbt plugin dynamically load user defined code?
Asked Answered
C

1

8

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

Cinda answered 19/11, 2014 at 19:6 Comment(3)
I don't think you can use dependencies from project in your plugin due to class loader separation. But I think my answer to this question is somehow similar to what you want to achieve.Lot
Just a hint: Don't do code generation. Consider using small, "stackable" macros, each filling a very special purpose. It's quite hard to find bugs in generated code and to deal with syntax errors in generated code. With macros you manipulate the AST directly, so there are fewer chances to mess something up.Confessor
@stefan.schwetschke: Can you explain more what you mean? To be clear, I did not write the code generator myself - just using slick's one. All I am doing is writing an sbt plugin to invoke it automatically during compile time...Cinda
C
3

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.

Clustered answered 9/1, 2015 at 7:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.