Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add new language source directories in gradle plugin?

I want to have the following working gradle script for arbitrary language foo:

sourceSets {
    main {
        java {
            srcDir "${project.buildDir}/generated-sources/gen-java"
        }
        foo {
            srcDir "${project.buildDir}/generated-sources/gen-foo"
        }
    }
    test {
        java {
            srcDir "${project.buildDir}/generated-sources/gen-test-java"
        }
        foo {
            srcDir "${project.buildDir}/generated-sources/gen-test-foo"
        }
    }
}

I also want to have standard foo source directories at src/main/foo, src/test/foo.

How do I write a gradle plugin to achieve such functionality? Is there possibility to do that?

I have a "solution-like" workaround for my needs posted below but still want to understand the right way to add new language source directories.

like image 259
Sergey Fedorov Avatar asked Feb 04 '17 12:02

Sergey Fedorov


People also ask

What is source set in Gradle?

1.1 What is a Gradle SourceSet ? A SourceSet is a collection of java source files and additional resource files that are compiled and assembled together to be executed. The main idea of sourcesets is to group files with a common meaning for the project, with no need of separate them in another project.

What is compileJava in Gradle?

compileJava — JavaCompile. Depends on: All tasks which contribute to the compilation classpath, including jar tasks from projects that are on the classpath via project dependencies. Compiles production Java source files using the JDK compiler.

Which are the two types of plugins in Gradle?

There are two general types of plugins in Gradle, binary plugins and script plugins.

What is apply plugin in Gradle?

Applying a plugin to a project means that it allows the plugin to extend the project's capabilities. The plugins can do the things such as − Extend the basic Gradle model (e.g. add new DSL elements that can be configured).


2 Answers

To achieve such a functionality one should extend existing source sets (or at least main and test source sets). It looks like main and test source sets are defined in JavaPlugin. Extending is possible through Convention object.

public class FooPlugin implements Plugin<ProjectInternal> {
    @Override
    public void apply(ProjectInternal project) {
        project.getPluginManager().apply(JavaPlugin.class);
        FooExtension ext = project.getExtensions().create(
            "foo",
            FooExtension.class,
            project,
            project.getFileResolver()
        );
        SourceSetContainer cont = (SourceSetContainer) project.getProperties().get("sourceSets");
        cont.all((SourceSet ss) -> {
            String name = ss.getName();
            File sources = project.file("src/" + name + "/foo");
            FooSourceSet fss = ext.getSourceSetsContainer().maybeCreate(name);
            SourceDirectorySet sds = fss.getFoo();
            sds.srcDir(sources);
            Convention sourceSetConvention = (Convention) InvokerHelper.getProperty(ss, "convention");
            sourceSetConvention.getPlugins().put("foo", fss);
        });
        project.task("compileFoo");
    }
}

public class FooExtension {

    private final NamedDomainObjectContainer<FooSourceSet> sourceSetsContainer;

    public FooExtension(Project project, FileResolver fileResolver) {
        sourceSetsContainer = project.container(
            FooSourceSet.class,
            new FooSourceSetFactory(fileResolver)
        );
    }

    public NamedDomainObjectContainer<FooSourceSet> getSourceSetsContainer() {
        return sourceSetsContainer;
    }

    public void srcDir(String file) {
        sourceSetsContainer.getByName("main").getFoo().srcDir(file);
    }
}

public class FooSourceSetFactory implements NamedDomainObjectFactory<FooSourceSet> {

    private final FileResolver fileResolver;

    public FooSourceSetFactory(FileResolver fileResolver) {
        this.fileResolver = fileResolver;
    }

    @Override
    public FooSourceSet create(String name) {
        return new DefaultFooSourceSet(name, fileResolver);
    }
}

public interface FooSourceSet {
    public String getName();
    public SourceDirectorySet getFoo();
    public FooSourceSet foo(Closure clsr);
}

public class DefaultFooSourceSet implements FooSourceSet {

    final String name;
    final SourceDirectorySet foo;

    public DefaultFooSourceSet(String displayName, FileResolver fileResolver) {
        this.name = displayName;
        DefaultDirectoryFileTreeFactory ddftf = new DefaultDirectoryFileTreeFactory();
        foo = new DefaultSourceDirectorySet(name, fileResolver, ddftf);
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public SourceDirectorySet getFoo() {
        return foo;
    }

    @Override
    public FooSourceSet foo(Closure clsr) {
        ConfigureUtil.configure(clsr, foo);
        return this;
    }
}

public class CompileFooTask extends DefaultTask {

    @TaskAction
    public void compileFoo() {
        SourceSetContainer cont = (SourceSetContainer) getProject().getProperties().get("sourceSets");
        cont.all((SourceSet ss) -> {
            FooSourceSet fss = getProject()
                .getExtensions()
                .getByType(FooExtension.class)
                .getSourceSetsContainer()
                .maybeCreate(ss.getName());
            System.out.println("directories under " + ss.getName()
                + ": " + fss.getFoo().getSrcDirs());
        });
    }
}

Task compileFoo demonstrates that plugin actually works. Given the build script snippet from the question it prints the lines like these:

directories under main: [<root>/src/main/foo, <root>/build/generated-sources/gen-foo]
directories under test: [<root>/src/test/foo, <root>/build/generated-sources/gen-test-foo]
like image 119
Sergey Fedorov Avatar answered Oct 27 '22 09:10

Sergey Fedorov


The source code for your use case would be pretty long, but here's a couple of pointers that will hopefully get you going.

Take a look at the Scala Plugin. It does exactly what you need (as it looks like you are following the java conventions for sources), and also much more. If you are trying to write any plugin, I recommend checking out the whole gradle source code.

The exact places you want to see are:

  1. ScalaBasePlugin#configureSourceSetDefaults - this is where the top level configuration happens
  2. DefaultScalaSourceSet - the actual source set class

In your case, your probably just want to rename all scala strings to foo and remove all the configuration you don't need (such as the actual compilation).

The plugin illustrates how to add the default source directories and the method:

public ScalaSourceSet scala(Closure configureClosure) {
    configure(configureClosure, getScala());
    return this;
}

takes care of the addition of generated sources. Basically it just takes the default source directory factory and uses that. You can use injection to add all the default gradle factories (see the Scala plugin, simply using the javax.inject.Inject works).

You can also check the groovy plugin. Notice that e.g. the DefaultGroovySourceSet looks like the one in the scala plugin.

like image 27
MartinTeeVarga Avatar answered Oct 27 '22 09:10

MartinTeeVarga