Skip to content
This repository has been archived by the owner on Jan 27, 2020. It is now read-only.

Commit

Permalink
Test and document built-cache friendliness
Browse files Browse the repository at this point in the history
  • Loading branch information
tbroyer committed Feb 11, 2018
1 parent 109f044 commit 9b55b4d
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 0 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ compileGroovy {
}
```

## Build cache

Compilation tasks are still [cacheable](https://docs.gradle.org/current/userguide/build_cache.html) with a few caveats:
* Only one _language_ can be used per source set (i.e. either `src/main/java` or `src/main/groovy` but not both), unless Groovy joint compilation is used (putting Java files in `src/main/groovy`), or tasks are configured to use distinct generated sources destination directories.
* Groovy compilation tasks are only fully cacheable starting with Gradle 4.3.
In previous versions, the tasks won't be relocatable and will only be cacheable if files in the annotation processor path do not change (e.g. when using a project dependency and that project is rebuilt, even if the classes come from the build cache).
This due to a bug/limitation in Gradle preventing the plugin to rely on `options.annotationProcessorPath`, and having no mean to tell Gradle to use classpath normalization; this was fixed in Gradle 4.3.

## Usage with IDEs

IDE configuration is provided on a best-effort basis.
Expand Down
235 changes: 235 additions & 0 deletions src/test/groovy/net/ltgt/gradle/apt/AptPluginIntegrationSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.gradle.testkit.runner.TaskOutcome
import org.gradle.util.GradleVersion
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Requires
import spock.lang.Specification
import spock.lang.Unroll

Expand Down Expand Up @@ -672,4 +673,238 @@ class AptPluginIntegrationSpec extends Specification {
where:
gradleVersion << IntegrationTestHelper.GRADLE_VERSIONS
}

@Requires({ IntegrationTestHelper.GRADLE_VERSIONS.any { GradleVersion.version(it) >= GradleVersion.version("3.5") } })
@Unroll
def "is build-cache friendly, with Gradle #gradleVersion"() {
given:
settingsFile << """\
include 'annotations'
include 'processor'
include 'core'
buildCache {
local(DirectoryBuildCache) {
directory = new File(rootDir, 'build-cache')
}
}
""".stripIndent()

buildFile << """\
project('annotations') {
apply plugin: 'java'
}
project('processor') {
apply plugin: 'groovy'
dependencies {
compile localGroovy()
}
}
project('core') {
apply plugin: 'groovy'
apply plugin: 'net.ltgt.apt'
dependencies {
compileOnly project(':annotations')
annotationProcessor project(':processor')
testCompile localGroovy()
testCompileOnly project(':annotations')
testAnnotationProcessor project(':processor')
}
compileJava {
aptOptions.processorArgs = ['foo': 'bar']
}
compileTestGroovy {
groovyOptions.javaAnnotationProcessing = true
}
// Get Gradle 4.x to behave like previous versions
// This works here because we don't use more than one "language" per source set
sourceSets.main.output.classesDir = new File(buildDir, 'classes/main')
sourceSets.test.output.classesDir = new File(buildDir, 'classes/test')
}
""".stripIndent()

def f = new File(testProjectDir.newFolder('annotations', 'src', 'main', 'java', 'annotations'), 'Helper.java')
f.createNewFile()
f << """\
package annotations;
public @interface Helper {
}
""".stripIndent()

f = new File(testProjectDir.newFolder('processor', 'src', 'main', 'groovy', 'processor'), 'HelperProcessor.groovy')
f.createNewFile()
f << """\
package processor
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.RoundEnvironment
import javax.annotation.processing.SupportedAnnotationTypes
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.lang.model.util.ElementFilter
import javax.tools.Diagnostic
import javax.tools.FileObject
import javax.tools.StandardLocation
@SupportedAnnotationTypes(HelperProcessor.HELPER)
class HelperProcessor extends AbstractProcessor {
static final String HELPER = "annotations.Helper"
@Override
SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest()
}
@Override
boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(
processingEnv.getElementUtils().getTypeElement(HELPER))).each { element ->
String helperName = "\${element.getSimpleName()}Helper"
try {
FileObject f = processingEnv.getFiler().createSourceFile("\${element.getQualifiedName()}Helper", element)
f.openWriter().withWriter { w ->
w << ""\"\\
package \${processingEnv.getElementUtils().getPackageOf(element)};
class \${element.getSimpleName()}Helper {
static String getValue() { return "\${element.getQualifiedName()}"; }
}
""\".stripIndent()
}
} catch (IOException e) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage())
}
}
return false
}
}
""".stripIndent()
f = new File(testProjectDir.newFolder('processor', 'src', 'main', 'resources', 'META-INF', 'services'), 'javax.annotation.processing.Processor')
f.createNewFile()
f << """\
processor.HelperProcessor
""".stripIndent()

// Java file in (main) sources
f = new File(testProjectDir.newFolder('core', 'src', 'main', 'java', 'core'), 'HelloWorldJ.java')
f.createNewFile()
f << """\
package core;
import annotations.Helper;
@Helper
class HelloWorldJ {
String sayHello(String name) {
return "Hello, " + name + "! I'm " + HelloWorldJHelper.getValue() + ".";
}
}
""".stripIndent()

// Java file in Groovy (test) sources
f = new File(testProjectDir.newFolder('core', 'src', 'test', 'groovy', 'test'), 'HelloWorldJ.java')
f.createNewFile()
f << """\
package test;
import annotations.Helper;
@Helper
class HelloWorldJ {
String sayHello(String name) {
return "Hello, " + name + "! I'm " + HelloWorldJHelper.getValue() + ".";
}
}
""".stripIndent()

// Groovy file in (test) sources
f = new File(f.parentFile, 'HelloWorldG.groovy')
f.createNewFile()
f << """\
package test;
import annotations.Helper;
@Helper
class HelloWorldG {
String sayHello(String name) {
"Hello, \${name}! I'm \${HelloWorldGHelper.getValue()}.";
}
}
""".stripIndent()

expect:

when:
def result = GradleRunner.create()
.withGradleVersion(gradleVersion)
.withProjectDir(testProjectDir.root)
.withArguments('--build-cache', ':core:testClasses')
.build()

then:
result.task(':annotations:compileJava').outcome == TaskOutcome.SUCCESS
result.task(':processor:compileJava').outcome == TaskOutcome.NO_SOURCE
result.task(':processor:compileGroovy').outcome == TaskOutcome.SUCCESS
result.task(':core:compileJava').outcome == TaskOutcome.SUCCESS
result.task(':core:compileGroovy').outcome == TaskOutcome.NO_SOURCE
result.task(':core:compileTestJava').outcome == TaskOutcome.NO_SOURCE
result.task(':core:compileTestGroovy').outcome == TaskOutcome.SUCCESS
result.task(':core:classes').outcome == TaskOutcome.SUCCESS
result.task(':core:testClasses').outcome == TaskOutcome.SUCCESS
new File(testProjectDir.root, 'core/build/generated/source/apt/main/core/HelloWorldJHelper.java').exists()
new File(testProjectDir.root, 'core/build/generated/source/apt/test/test/HelloWorldJHelper.java').exists()
new File(testProjectDir.root, 'core/build/generated/source/apt/test/test/HelloWorldGHelper.java').exists()
new File(testProjectDir.root, 'core/build/classes/main/core/HelloWorldJ.class').exists()
new File(testProjectDir.root, 'core/build/classes/main/core/HelloWorldJHelper.class').exists()
new File(testProjectDir.root, 'core/build/classes/test/test/HelloWorldJ.class').exists()
new File(testProjectDir.root, 'core/build/classes/test/test/HelloWorldJHelper.class').exists()
new File(testProjectDir.root, 'core/build/classes/test/test/HelloWorldG.class').exists()
new File(testProjectDir.root, 'core/build/classes/test/test/HelloWorldGHelper.class').exists()

when:
final usesClasspathNormalization = GradleVersion.version(gradleVersion) >= GradleVersion.version("4.3")
// Reuse JARs for GroovyCompile 'til Gradle 4.3 where options.annotationProcessorPath cannot be used,
// and classpath normalization cannot be declared through TaskInputs.
if (usesClasspathNormalization) {
new File(testProjectDir.root, 'annotations/build').deleteDir()
new File(testProjectDir.root, 'processor/build').deleteDir()
}
new File(testProjectDir.root, 'core/build').deleteDir()
result = GradleRunner.create()
.withGradleVersion(gradleVersion)
.withProjectDir(testProjectDir.root)
.withArguments('--build-cache', ':core:testClasses')
.build()

then:
result.task(':annotations:compileJava').outcome == (usesClasspathNormalization ? TaskOutcome.FROM_CACHE : TaskOutcome.UP_TO_DATE)
result.task(':processor:compileJava').outcome == TaskOutcome.NO_SOURCE
result.task(':processor:compileGroovy').outcome == (usesClasspathNormalization ? TaskOutcome.FROM_CACHE : TaskOutcome.UP_TO_DATE)
result.task(':core:compileJava').outcome == TaskOutcome.FROM_CACHE
result.task(':core:compileGroovy').outcome == TaskOutcome.NO_SOURCE
result.task(':core:compileTestJava').outcome == TaskOutcome.NO_SOURCE
result.task(':core:compileTestGroovy').outcome == TaskOutcome.FROM_CACHE
result.task(':core:classes').outcome == TaskOutcome.UP_TO_DATE
result.task(':core:testClasses').outcome == TaskOutcome.UP_TO_DATE
new File(testProjectDir.root, 'core/build/generated/source/apt/main/core/HelloWorldJHelper.java').exists()
new File(testProjectDir.root, 'core/build/generated/source/apt/test/test/HelloWorldJHelper.java').exists()
new File(testProjectDir.root, 'core/build/generated/source/apt/test/test/HelloWorldGHelper.java').exists()
new File(testProjectDir.root, 'core/build/classes/main/core/HelloWorldJ.class').exists()
new File(testProjectDir.root, 'core/build/classes/main/core/HelloWorldJHelper.class').exists()
new File(testProjectDir.root, 'core/build/classes/test/test/HelloWorldJ.class').exists()
new File(testProjectDir.root, 'core/build/classes/test/test/HelloWorldJHelper.class').exists()
new File(testProjectDir.root, 'core/build/classes/test/test/HelloWorldG.class').exists()
new File(testProjectDir.root, 'core/build/classes/test/test/HelloWorldGHelper.class').exists()

where:
gradleVersion << IntegrationTestHelper.GRADLE_VERSIONS.findAll { GradleVersion.version(it) >= GradleVersion.version("3.5") }
}
}

0 comments on commit 9b55b4d

Please sign in to comment.