Trigger for when new language source sets are added?

Firstly to clarify the scope, this question is about the existing (old) model of doing language plugins. If someone wants to augment an answer with how it will work in the model, that is fine, but I would like to see if the problem firstly can be solved with the old way.

The problem: Let’s say I am adding a new source set to an existing JVM language as part of a plugin i.e. for integration tests I might have code roughly the equivalent of below:

    project.sourceSets {
        integrationTest { 
            java.srcDir project.file( "src/integrationTest/java" )
            resources.srcDir project.file( "src/integrationTest/resources" ) 

            compileClasspath = sourceSets.main.output + 
                configurations.integrationTestCompile 

            runtimeClasspath = output + compileClasspath + 
                configurations.integrationTestRuntime
        }
    }

that would add it for JavaPlugin. If I manually wanted to add support for GroovyPlugin, I could have added a line:

  groovy.srcDir project.file( "src/integrationTest/groovy" )

For one, that is tedious and seocndly, not very fleixble for otehr languages. What I am trying to figure out if there is a way to automativally do this every time a new language is added (i.e. the plugin is applied). So if I where to do

  apply plugin : 'scala'

I would want to see the equivalent of

  scala.srcDir project.file( "src/integrationTest/scala" )

happen via some form of event i.e. a whenObjectAdded kind of thing. I cannot do this on project.sourceSets as whenObjectAdded will trigger on the new SourceSet i.e. integrationTest being added, not on the SourceDirectorySet. Her one might think that there is a requirementto find a way to discover that the new plugin being added is a JVM language plugin. Ther is curretntly no way of categorising plugins which is probably a good thing. This means that another way has to be found.

What I suspect is that the following will work if done in order.

apply plugin : 'add.integration.tests'
apply plugin : 'scala'
apply plugin : 'another.jvm.language'

This is due to the fact that JVM language plugins currently are supposed to do something like

sourceSets.each { SourceSet srcSet ->
    final XyzSourceSet ss = new XyzSourceSet("Xyz language ${srcSet.name}",(project as ProjectInternal).fileResolver)
    new DslObject(srcSet).convention.plugins.put('xyz',ss)
    ss.xyz.srcDir("src/${srcSet.name}/xyz")
}

and at that point integrationTest will already be in sourceSets containers.

The problem is that we want to be independent of the order in which plugins are applied. What if the following written by a script author?

apply plugin : 'scala'
apply plugin : 'another.jvm.language'
apply plugin : 'add.integration.tests'

This is where I am stuck. How does the add.integration.tests plugin discover which DirectorySourceSets already exist?
`

The various JVM language plugins already do this. Since they use a configuration rule (i.e. SourceSets.all { }) it shouldn’t matter what order you apply the plugins in. Is this not the behavior you’re seeing, or does the SourceDirectorySet not live in the default location of src/${sourceSetName}/${lang}?

https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java#L89-L89

1 Like

Aha, that indirectly answered another question I had. now I was wondering what was the real plugin that needs to be applied for add.integration.tests to actually work. To put it in context, where java is applied, this in the acyal order in which plugins are applied in the background.

org.gradle.language.base.plugins.LifecycleBasePlugin
org.gradle.api.plugins.BasePlugin
org.gradle.api.plugins.ReportingBasePlugin
org.gradle.language.base.plugins.LanguageBasePlugin
org.gradle.api.plugins.JavaBasePlugin
org.gradle.api.plugins.JavaPlugin

we normally assume java and java-base to be ones that provide all the JVM goodness, however if I were to do

apply plugin : 'add.integration.tests'
apply plugin : 'java'

what would the plugin be that add.integration.tests needs to apply underneath to ensure that it get’s the correct functionality and also such that sourceSets.all {} would actually work for the java plugin?

Would it be LanguageBasePlugin ?

I needed to understand what is actually happening.

I am answering that one myself. It looks like that for old model JVM source sets, JavaBasePlugin is required, otherwise project.sourceSets will not exist.

What I have also come to realise is that when adding a new type of SourceSet, one is actually adding a convention, whether you realise it or not.

As a third point, I think that convention should also be to add a compilmentray xyzCompile and xyzRuntime for each JVM SourceSet that is added.

That is correct. If your plugin is registering source sets it will at least need to apply the ‘java-base’ plugin.

That is the convention. By applying the ‘java-base’ plugin the functionality you get is:

  1. A SourceSet container on Project (provided via JavaPluginConvention)
  2. A JavaCompile task for each SourceSet
  3. A ‘compile’ and ‘runtime’ Configuration for each SourceSet

The ‘groovy’ and ‘scala’ plugins additionally will add language specific compile tasks.

1 Like

Excellent!

So what I can thus say is that if one wants to add a new source set, then JavaBasePlugin is the minimum plugin required. However, if extending an existing sourceset, such as what an integration test source set would typically do, then JavaPlugin is required,.

If the integration test source set depends on the ‘main’ source set (which I expect it does) then yes, it makes sense that the ‘java’ plugin would be required.