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.
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.
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.
There are two general types of plugins in Gradle, binary plugins and script plugins.
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).
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]
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:
ScalaBasePlugin#configureSourceSetDefaults
- this is where the top level configuration happensDefaultScalaSourceSet
- the actual source set classIn 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With