Code generation problems, buildSrc and buildscript dependency on another module

Our build is doing code generation and we have a use case where one sub-project generates domain objects from a model, and another sub-project generates persistence for these objects.

The persistence itself is relatively complex and we have special model for it - we found it nice to implement the model in Groovy, where we can reference the generated domain classes directly, rather than use strings to specify classes and such. The generator reads the config script, which builds a model and then spews the custom persistence code.

The problem is that the generator resides in one of the sub-projects that Gradle will build, which is not present during the configuration phase. Just in case, we tried it with:

buildscript {
   dependencies {
     classpath project(":domain-objects")
  }
 }

And the build failed with an error that Gradle can not resolve the “:domain-objects” dependencies. We did make sure that the buildscript section has the repos set up, but to no help.

Our next attempt was to use the JavaExec task - the problem there was that while it was easy to add project(":domain-objects") to the classpath, it was impossible to add the rest of the build classpath. I did try:

classpath project(':stream').configurations.default,
           buildscript.configurations.classpath

But it didn’t work - after some poking, I found that buildscript.configurations.classpath was empty…

Finally we made it work with this:

task generate(type: JavaExec) {
    ext.modelRoot = file('src/model/groovy', PathValidation.DIRECTORY)
    ext.destDir = generatedSrc
    inputs.source modelRoot
    outputs.dir destDir
      main = '....gradle.codegen.StoreGenerator'
    args = [modelRoot, destDir, 'com.ubs.start.stores']
    classpath project(':stream').configurations.default,
              project(':stores').configurations.testRuntime,
   // workaround for fetching groovy
              rootProject.file('buildSrc/build/libs/buildSrc.jar')
// <---- ugly
       doFirst {
        delete fileTree(dir: generatedSrc, excludes: ['**/.svn/**'])
    }
}

That works, but is completely bypassing the dependency management - is there a better way?

Today, the only clean solutions are to keep the code generator in ‘buildSrc’, or to have a separate build for the code generator

If the code generator needs to be a regular subproject, and must be used from the same build, you’ll have to resort to one trick or another. Here is what I did for one of my own projects: https://github.com/spockframework/spock/blob/groovy-1.8/buildSrc/build.gradle

This gets tricky when the code generator (in my case it’s a report generator) has in turn dependencies on other subprojects. I solved this with Groovy duck typing, avoiding compile-time dependencies on those subprojects. Also, my code generator gets compiled twice (unless it’s up-to-date), but I can live with that.

Hi Peter, the trick with adding the source root from another project would not help much in my case, as at the time when the generator is compiled, the classes referred by the config have not been generated yet. We already use dynamic types for that.

To make it clearer, the files involved are:

:buildSrc - generator.groovy

:project1 - model1.json (in project1) - generated1.java (generated from model1.json) - generated1.class (compiled from generated 1.java)

:project2 - model2.groovy (refers to generated1.class from project1) - generated2.java

The generator does not have compile time dependency on any of the projects. When executed, it will create a shell with prepopulated bindings and execute model2.groovy. That would populate a model that the generator will use to create generated2.java

The problem stems from the fact that I can not get the generated1.class in the buildscript classpath of project2. Technically it should be possible by using separate classloader per project.

The alternative was to run the generator in a forked JVM, but then the problem was that there was no dependency notation to refer to the buildSrc project, so I could not pass it as a classpath.

The general problem is not so much class loaders, but that Gradle cannot currently execute tasks from one project before evaluating the build scripts of other projects, because that doesn’t fit its configuration time / execution time model. However, in your case it should be possible to solve the problem by improving the generator to load classes from its own configurable class path, rather than the build script class path.

1 Like