Skip to content

Commit

Permalink
Support Task Configuration Avoidance (apache#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmc24 committed Mar 5, 2020
1 parent 2bfbcb3 commit 33b182c
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 83 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Add support for testing multiple Kotlin versions
* Update plugin's own build to address some deprecation warnings of APIs being removed in Gradle 7
* Add tests for Kotlin DSL usage (#61)
* Support [Task Configuration Avoidance](https://docs.gradle.org/current/userguide/task_configuration_avoidance.html) (#97); thanks to [dcabasson](https://github.com/dcabasson) for the collaboration

## 0.18.0
* Use reproducible file order for plugin archives
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Examples:

```groovy
// Option 1: configure compilation task (avro plugin will automatically match)
tasks.withType(JavaCompile) {
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
}
// Option 2: just configure avro plugin
Expand Down Expand Up @@ -255,15 +255,17 @@ apply plugin: "java"
apply plugin: "com.commercehub.gradle.plugin.avro-base"
dependencies {
compile "org.apache.avro:avro:1.9.2"
implementation "org.apache.avro:avro:1.9.2"
}
task generateAvro(type: com.commercehub.gradle.plugin.avro.GenerateAvroJavaTask) {
def generateAvro = tasks.register("generateAvro", com.commercehub.gradle.plugin.avro.GenerateAvroJavaTask) {
source("src/avro")
outputDir = file("dest/avro")
}
compileJava.source(generateAvro.outputs)
tasks.named("compileJava").configure {
source(generateAvro)
}
```

# File Processing
Expand Down Expand Up @@ -387,13 +389,13 @@ Example using base plugin with support for both IDL and JSON protocol files in `
```groovy
apply plugin: "com.commercehub.gradle.plugin.avro-base"
task("generateProtocol", type: com.commercehub.gradle.plugin.avro.GenerateAvroProtocolTask) {
def generateProtocol = tasks.register("generateProtocol", com.commercehub.gradle.plugin.avro.GenerateAvroProtocolTask) {
source file("src/main/avro")
include("**/*.avdl")
outputDir = file("build/generated-avro-main-avpr")
}
task("generateSchema", type: com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
tasks.register("generateSchema", com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
dependsOn generateProtocol
source file("src/main/avro")
source file("build/generated-avro-main-avpr")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void apply(final Project project) {

private static void configureExtension(final Project project) {
final AvroExtension avroExtension = createExtensionWithObjectFactory(project, AVRO_EXTENSION_NAME, DefaultAvroExtension.class);
project.getTasks().withType(GenerateAvroJavaTask.class).all(task -> {
project.getTasks().withType(GenerateAvroJavaTask.class).configureEach(task -> {
task.getOutputCharacterEncoding().convention(avroExtension.getOutputCharacterEncoding());
task.getStringType().convention(avroExtension.getStringType());
task.getFieldVisibility().convention(avroExtension.getFieldVisibility());
Expand Down
109 changes: 53 additions & 56 deletions src/main/java/com/commercehub/gradle/plugin/avro/AvroPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.SourceTask;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.plugins.ide.idea.GenerateIdeaModule;
import org.gradle.plugins.ide.idea.IdeaPlugin;
Expand All @@ -50,28 +51,27 @@ public void apply(final Project project) {
}

private static void configureTasks(final Project project) {
getSourceSets(project).all(sourceSet -> {
GenerateAvroProtocolTask protoTask = configureProtocolGenerationTask(project, sourceSet);
GenerateAvroJavaTask javaTask = configureJavaGenerationTask(project, sourceSet, protoTask);
configureTaskDependencies(project, sourceSet, javaTask);
getSourceSets(project).configureEach(sourceSet -> {
TaskProvider<GenerateAvroProtocolTask> protoTaskProvider = configureProtocolGenerationTask(project, sourceSet);
TaskProvider<GenerateAvroJavaTask> javaTaskProvider = configureJavaGenerationTask(project, sourceSet, protoTaskProvider);
configureTaskDependencies(project, sourceSet, javaTaskProvider);
});
}

private static void configureIntelliJ(final Project project) {
project.getPlugins().withType(IdeaPlugin.class).all(ideaPlugin -> {
project.getPlugins().withType(IdeaPlugin.class).configureEach(ideaPlugin -> {
SourceSet mainSourceSet = getMainSourceSet(project);
SourceSet testSourceSet = getTestSourceSet(project);
project.getTasks().withType(GenerateIdeaModule.class).all(generateIdeaModule ->
project.getTasks().withType(GenerateAvroJavaTask.class).all(generateAvroJavaTask ->
generateIdeaModule.doFirst(task -> project.mkdir(generateAvroJavaTask.getOutputDir()))));
IdeaModule module = ideaPlugin.getModel().getModule();
module.setSourceDirs(new SetBuilder<File>()
.addAll(module.getSourceDirs())
.add(getAvroSourceDir(project, mainSourceSet))
.add(getGeneratedOutputDir(project, mainSourceSet, JAVA_EXTENSION).map(Directory::getAsFile).get())
.build());
module.setTestSourceDirs(new SetBuilder<File>()
.addAll(module.getTestSourceDirs())
.add(getAvroSourceDir(project, testSourceSet))
.add(getGeneratedOutputDir(project, testSourceSet, JAVA_EXTENSION).map(Directory::getAsFile).get())
.build());
// IntelliJ doesn't allow source directories beneath an excluded directory.
// Thus, we remove the build directory exclude and add all non-generated sub-directories as excludes.
Expand All @@ -83,59 +83,60 @@ private static void configureIntelliJ(final Project project) {
}
module.setExcludeDirs(excludeDirs.build());
});
project.getTasks().withType(GenerateIdeaModule.class).configureEach(generateIdeaModule ->
generateIdeaModule.doFirst(task ->
project.getTasks().withType(GenerateAvroJavaTask.class, generateAvroJavaTask ->
project.mkdir(generateAvroJavaTask.getOutputDir().get()))));
}

private static GenerateAvroProtocolTask configureProtocolGenerationTask(final Project project, final SourceSet sourceSet) {
private static TaskProvider<GenerateAvroProtocolTask> configureProtocolGenerationTask(final Project project,
final SourceSet sourceSet) {
String taskName = sourceSet.getTaskName("generate", "avroProtocol");
GenerateAvroProtocolTask task = project.getTasks().create(taskName, GenerateAvroProtocolTask.class);
task.setDescription(
String.format("Generates %s Avro protocol definition files from IDL files.", sourceSet.getName()));
task.setGroup(GROUP_SOURCE_GENERATION);
task.source(getAvroSourceDir(project, sourceSet));
task.include("**/*." + IDL_EXTENSION);
task.setClasspath(project.getConfigurations().getByName(RUNTIME_CLASSPATH_CONFIGURATION_NAME));
task.getOutputDir().convention(getGeneratedOutputDir(project, sourceSet, PROTOCOL_EXTENSION));
return task;
return project.getTasks().register(taskName, GenerateAvroProtocolTask.class, task -> {
task.setDescription(
String.format("Generates %s Avro protocol definition files from IDL files.", sourceSet.getName()));
task.setGroup(GROUP_SOURCE_GENERATION);
task.source(getAvroSourceDir(project, sourceSet));
task.include("**/*." + IDL_EXTENSION);
task.setClasspath(project.getConfigurations().getByName(RUNTIME_CLASSPATH_CONFIGURATION_NAME));
task.getOutputDir().convention(getGeneratedOutputDir(project, sourceSet, PROTOCOL_EXTENSION));
});
}

private static GenerateAvroJavaTask configureJavaGenerationTask(final Project project, final SourceSet sourceSet,
GenerateAvroProtocolTask protoTask) {
private static TaskProvider<GenerateAvroJavaTask> configureJavaGenerationTask(final Project project, final SourceSet sourceSet,
TaskProvider<GenerateAvroProtocolTask> protoTaskProvider) {
String taskName = sourceSet.getTaskName("generate", "avroJava");
GenerateAvroJavaTask task = project.getTasks().create(taskName, GenerateAvroJavaTask.class);
task.setDescription(String.format("Generates %s Avro Java source files from schema/protocol definition files.",
sourceSet.getName()));
task.setGroup(GROUP_SOURCE_GENERATION);
task.source(getAvroSourceDir(project, sourceSet));
task.source(protoTask.getOutputDir());
task.source(protoTask.getOutputs());
task.include("**/*." + SCHEMA_EXTENSION, "**/*." + PROTOCOL_EXTENSION);
task.getOutputDir().convention(getGeneratedOutputDir(project, sourceSet, JAVA_EXTENSION));

sourceSet.getJava().srcDir(task.getOutputDir());

final JavaCompile compileJavaTask = getCompileJavaTask(project, sourceSet);
compileJavaTask.source(task.getOutputDir());
compileJavaTask.source(task.getOutputs());

task.getOutputCharacterEncoding().convention(project.provider(() ->
Optional.ofNullable(compileJavaTask.getOptions().getEncoding()).orElse(Charset.defaultCharset().name())));

return task;
TaskProvider<GenerateAvroJavaTask> javaTaskProvider = project.getTasks().register(taskName, GenerateAvroJavaTask.class, task -> {
task.setDescription(String.format("Generates %s Avro Java source files from schema/protocol definition files.",
sourceSet.getName()));
task.setGroup(GROUP_SOURCE_GENERATION);
task.source(getAvroSourceDir(project, sourceSet));
task.source(protoTaskProvider);
task.include("**/*." + SCHEMA_EXTENSION, "**/*." + PROTOCOL_EXTENSION);
task.getOutputDir().convention(getGeneratedOutputDir(project, sourceSet, JAVA_EXTENSION));

sourceSet.getJava().srcDir(task.getOutputDir());

JavaCompile compileJavaTask = project.getTasks().named(sourceSet.getCompileJavaTaskName(), JavaCompile.class).get();
task.getOutputCharacterEncoding().convention(project.provider(() ->
Optional.ofNullable(compileJavaTask.getOptions().getEncoding()).orElse(Charset.defaultCharset().name())));
});
project.getTasks().named(sourceSet.getCompileJavaTaskName(), JavaCompile.class, compileJavaTask -> {
compileJavaTask.source(javaTaskProvider);
});
return javaTaskProvider;
}

private static void configureTaskDependencies(final Project project, final SourceSet sourceSet, final GenerateAvroJavaTask javaTask) {
private static void configureTaskDependencies(final Project project, final SourceSet sourceSet,
final TaskProvider<GenerateAvroJavaTask> javaTaskProvider) {
project.getPluginManager().withPlugin("org.jetbrains.kotlin.jvm", appliedPlugin ->
project.getTasks().matching(task -> {
String compilationTaskName = sourceSet.getCompileTaskName("kotlin");
return compilationTaskName.equals(task.getName());
})
.all(task -> {
if (task instanceof SourceTask) {
((SourceTask) task).source(javaTask.getOutputs());
} else {
task.dependsOn(javaTask);
}
}));
project.getTasks()
.matching(task -> sourceSet.getCompileTaskName("kotlin").equals(task.getName()))
.withType(SourceTask.class)
.configureEach(task ->
task.source(javaTaskProvider.get().getOutputs())
)
);
}

private static File getAvroSourceDir(Project project, SourceSet sourceSet) {
Expand All @@ -147,10 +148,6 @@ private static Provider<Directory> getGeneratedOutputDir(Project project, Source
return project.getLayout().getBuildDirectory().dir(generatedOutputDirName);
}

private static JavaCompile getCompileJavaTask(Project project, SourceSet sourceSet) {
return (JavaCompile) project.getTasks().getByName(sourceSet.getCompileJavaTaskName());
}

private static SourceSetContainer getSourceSets(Project project) {
return project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class AvroBasePluginFunctionalSpec extends FunctionalSpec {
def "can generate java files from json schema"() {
given:
buildFile << """
|task("generateAvroJava", type: com.commercehub.gradle.plugin.avro.GenerateAvroJavaTask) {
|tasks.register("generateAvroJava", com.commercehub.gradle.plugin.avro.GenerateAvroJavaTask) {
| source file("src/main/avro")
| include("**/*.avsc")
| outputDir = file("build/generated-main-avro-java")
Expand All @@ -45,7 +45,7 @@ class AvroBasePluginFunctionalSpec extends FunctionalSpec {
def "can generate json schema files from json protocol"() {
given:
buildFile << """
|task("generateSchema", type: com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
|tasks.register("generateSchema", com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
| source file("src/main/avro")
| include("**/*.avpr")
| outputDir = file("build/generated-main-avro-avsc")
Expand All @@ -68,11 +68,11 @@ class AvroBasePluginFunctionalSpec extends FunctionalSpec {
def "can generate json schema files from IDL"() {
given:
buildFile << """
|task("generateProtocol", type: com.commercehub.gradle.plugin.avro.GenerateAvroProtocolTask) {
|tasks.register("generateProtocol", com.commercehub.gradle.plugin.avro.GenerateAvroProtocolTask) {
| source file("src/main/avro")
| outputDir = file("build/generated-avro-main-avpr")
|}
|task("generateSchema", type: com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
|tasks.register("generateSchema", com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
| dependsOn generateProtocol
| source file("build/generated-avro-main-avpr")
| include("**/*.avpr")
Expand All @@ -97,12 +97,12 @@ class AvroBasePluginFunctionalSpec extends FunctionalSpec {
def "example of converting both IDL and json protocol simultaneously"() {
given:
buildFile << """
|task("generateProtocol", type: com.commercehub.gradle.plugin.avro.GenerateAvroProtocolTask) {
|tasks.register("generateProtocol", com.commercehub.gradle.plugin.avro.GenerateAvroProtocolTask) {
| source file("src/main/avro")
| include("**/*.avdl")
| outputDir = file("build/generated-avro-main-avpr")
|}
|task("generateSchema", type: com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
|tasks.register("generateSchema", com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
| dependsOn generateProtocol
| source file("src/main/avro")
| source file("build/generated-avro-main-avpr")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,26 @@ class AvroPluginFunctionalSpec extends FunctionalSpec {
result.output.contains("* $errorFilePath: \"enum\" is not a defined name. The type of the \"gender\" " +
"field must be a defined name or a {\"type\": ...} expression.")
}

@SuppressWarnings(["GStringExpressionWithinString"])
def "avro plugin correctly uses task configuration avoidance"() {
given:
buildFile << """
|def configuredTasks = []
|tasks.configureEach {
| configuredTasks << it
|}
|gradle.buildFinished {
| println "Configured tasks: \${configuredTasks*.path}"
|}
|""".stripMargin()
when:
def result = run("help")

then:
def taskMatcher = result.output =~ /(?m)^Configured tasks: (.*)$/
taskMatcher.find()
def configuredTasks = taskMatcher.group(1)
configuredTasks == "[:help]"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ class BuildCacheSupportFunctionalSpec extends FunctionalSpec {
given: "a project is built once with build cache enabled"
copyResource("mail.avpr", avroDir)
buildFile << """
task("generateSchema", type: com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
source file("src/main/avro")
include("**/*.avpr")
outputDir = file("build/generated-main-avro-avsc")
}
"""
|tasks.register("generateSchema", com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
| source file("src/main/avro")
| include("**/*.avpr")
| outputDir = file("build/generated-main-avro-avsc")
|}
|""".stripMargin()
run("generateSchema", "--build-cache")

and: "the project is cleaned"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class CustomConversionFunctionalSpec extends FunctionalSpec {
copyResource("customConversion.avpr", avroDir)
applyAvroPlugin()
buildFile << """
|task("generateSchema", type: com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
|tasks.register("generateSchema", com.commercehub.gradle.plugin.avro.GenerateAvroSchemaTask) {
| source file("src/main/avro")
| include("**/*.avpr")
| outputDir = file("build/generated-main-avro-avsc")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class EncodingFunctionalSpec extends FunctionalSpec {
addAvroDependency()
copyResource("idioma.avsc", avroDir)
buildFile << """
|compileJava {
|tasks.named("compileJava").configure {
| options.encoding = '${encoding}'
|}
|""".stripMargin()
Expand Down Expand Up @@ -84,7 +84,7 @@ class EncodingFunctionalSpec extends FunctionalSpec {
|avro {
| outputCharacterEncoding = ${outputCharacterEncoding}
|}
|task("generateAvroJava", type: com.commercehub.gradle.plugin.avro.GenerateAvroJavaTask) {
|tasks.register("generateAvroJava", com.commercehub.gradle.plugin.avro.GenerateAvroJavaTask) {
| source file("src/main/avro")
| include("**/*.avsc")
| outputDir = file("build/generated-main-avro-java")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ class GenerateAvroProtocolTaskFunctionalSpec extends FunctionalSpec {
applyPlugin("java") // Jar task appears to only work with the java plugin applied
buildFile << """
|configurations.create("shared")
|task sharedIdlJar(type: Jar) {
|tasks.register("sharedIdlJar", Jar) {
| from "src/shared"
|}
|dependencies {
| shared sharedIdlJar.outputs.files
|}
|task("generateProtocol", type: com.commercehub.gradle.plugin.avro.GenerateAvroProtocolTask) {
|tasks.register("generateProtocol", com.commercehub.gradle.plugin.avro.GenerateAvroProtocolTask) {
| classpath = configurations.shared
| source file("src/dependent")
| outputDir = file("build/protocol")
Expand All @@ -56,7 +56,7 @@ class GenerateAvroProtocolTaskFunctionalSpec extends FunctionalSpec {
given: "a build that declares another task's output in the classpath"
applyAvroPlugin()
buildFile << """
|task sharedIdlJar(type: Jar) {
|tasks.register("sharedIdlJar", Jar) {
| from "src/shared"
|}
|dependencies {
Expand Down
Loading

0 comments on commit 33b182c

Please sign in to comment.