org.gradle.java.compile.AbstractJavaCompileAvoidanceIntegrationSpec.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.java.compile
import org.gradle.integtests.fixtures.AbstractIntegrationSpec
import org.gradle.language.fixtures.HelperProcessorFixture
import spock.lang.Issue
abstract class AbstractJavaCompileAvoidanceIntegrationSpec extends AbstractIntegrationSpec {
def setup() {
settingsFile << """
include 'a', 'b'
"""
buildFile << """
allprojects {
apply plugin: 'java'
task emptyDirs(type: Sync) {
into 'build/empty-dirs'
from 'src/empty-dirs'
includeEmptyDirs = true
}
}
"""
}
def deactivateIncrementalCompile() {
buildFile << """
allprojects {
tasks.withType(JavaCompile) {
options.incremental = false
}
}
"""
}
def useJar() {
buildFile << """
allprojects {
jar {
from emptyDirs
}
}
"""
}
def useClassesDir() {
buildFile << """import static org.gradle.api.attributes.Usage.*;
allprojects {
configurations.apiElements.outgoing.variants {
classes {
attributes.attribute(USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_API_CLASSES))
artifact file: compileJava.destinationDir, builtBy: compileJava
artifact file: emptyDirs.destinationDir, builtBy: emptyDirs
artifact file: processResources.destinationDir, builtBy: processResources
}
}
}
"""
}
def "doesn't recompile when private element of implementation class changes"() {
given:
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
"""
def sourceFile = file("a/src/main/java/ToolImpl.java")
sourceFile << """
public class ToolImpl {
private String thing() { return null; }
private ToolImpl t = this;
}
"""
file("b/src/main/java/Main.java") << """
public class Main { ToolImpl t = new ToolImpl(); }
"""
when:
succeeds ':b:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// change signatures
sourceFile.text = """
public class ToolImpl {
private Number thing() { return null; }
private Object t = this;
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
when:
// add private elements
sourceFile.text = """
public class ToolImpl {
private Number thing() { return null; }
private Object t = this;
private static void someMethod() { }
private String s;
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
when:
// remove private elements
sourceFile.text = """
public class ToolImpl {
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
when:
// add public method, should change
sourceFile.text = """
public class ToolImpl {
public void execute() { String s = toString(); }
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava', ":b:compileJava"
when:
// add public field, should change
sourceFile.text = """
public class ToolImpl {
public static ToolImpl instance;
public void execute() { String s = toString(); }
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava', ":b:compileJava"
when:
// add public constructor to replace the default, should not change
sourceFile.text = """
public class ToolImpl {
public ToolImpl() { }
public static ToolImpl instance;
public void execute() { String s = toString(); }
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
when:
// add public constructor, should change
sourceFile.text = """
public class ToolImpl {
public ToolImpl() { }
public ToolImpl(String s) { }
public static ToolImpl instance;
public void execute() { String s = toString(); }
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava', ":b:compileJava"
}
def "doesn't recompile when comments and whitespace of implementation class changes"() {
given:
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
"""
def sourceFile = file("a/src/main/java/ToolImpl.java")
sourceFile << """
public class ToolImpl { }
"""
file("b/src/main/java/Main.java") << """
public class Main { ToolImpl t = new ToolImpl(); }
"""
when:
succeeds ':b:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// add comments, change whitespace
sourceFile.text = """
/**
* A thing
*/
public class ToolImpl {
// TODO - add some stuff
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
}
def "doesn't recompile when implementation class code changes"() {
given:
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
"""
def sourceFile = file("a/src/main/java/ToolImpl.java")
sourceFile << """
public class ToolImpl {
public Object s = String.valueOf(12);
public void execute() { int i = 12; }
}
"""
file("b/src/main/java/Main.java") << """
public class Main { ToolImpl t = new ToolImpl(); }
"""
when:
succeeds ':b:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// change method body and field initializer
sourceFile.text = """
public class ToolImpl {
public Object s = "12";
public void execute() { String s = toString(); }
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
when:
// add static initializer and constructor
sourceFile.text = """
public class ToolImpl {
static { }
public ToolImpl() { }
public Object s = "12";
public void execute() { String s = toString(); }
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
when:
// change static initializer and constructor
sourceFile.text = """
public class ToolImpl {
static { int i = 123; }
public ToolImpl() { System.out.println("created!"); }
public Object s = "12";
public void execute() { String s = toString(); }
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
}
def "recompiles when type of implementation class changes"() {
given:
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
"""
def sourceFile = file("a/src/main/java/org/ToolImpl.java")
sourceFile << """
package org;
public class ToolImpl { void m() { } }
"""
file("b/src/main/java/org/Main.java") << """
package org;
public class Main { void go(ToolImpl t) { t.m(); } }
"""
when:
succeeds ':b:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// change to interface
sourceFile.text = """
package org;
public interface ToolImpl { void m(); }
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// change to visibility
sourceFile.text = """
package org;
interface ToolImpl { void m(); }
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// change to interfaces
sourceFile.text = """
package org;
interface ToolImpl extends Runnable { void m(); }
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
}
def "recompiles when constant value of API changes"() {
given:
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
"""
def sourceFile = file("a/src/main/java/ToolImpl.java")
sourceFile << """
public class ToolImpl { public static final int CONST = 1; }
"""
file("b/src/main/java/Main.java") << """
public class Main { public static final int CONST2 = 1 + ToolImpl.CONST; }
"""
when:
succeeds ':b:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// change to constant value
sourceFile.text = """
public class ToolImpl { public static final int CONST = 10; }
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
}
def "recompiles when generic type signatures of implementation class changes"() {
given:
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
"""
def sourceFile = file("a/src/main/java/ToolImpl.java")
sourceFile << """
public interface ToolImpl { void m(java.util.List s); }
"""
file("b/src/main/java/Main.java") << """
public class Main { void go(ToolImpl t) { t.m(null); } }
"""
when:
succeeds ':b:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// add type parameters to interface
sourceFile.text = """
public interface ToolImpl { void m(java.util.List s); }
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// add type parameters to method
sourceFile.text = """
public interface ToolImpl { void m(java.util.List s); }
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// change type parameters on interface
sourceFile.text = """
public interface ToolImpl { void m(java.util.List s); }
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// change type parameters on method
sourceFile.text = """
public interface ToolImpl { void m(java.util.List s); }
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
}
def "doesn't recompile when private inner class changes"() {
given:
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
"""
def sourceFile = file("a/src/main/java/ToolImpl.java")
sourceFile << """
public class ToolImpl {
private class Thing { }
}
"""
file("b/src/main/java/Main.java") << """
public class Main { ToolImpl t = new ToolImpl(); }
"""
when:
succeeds ':b:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
// ABI change of inner class
sourceFile.text = """
public class ToolImpl {
private class Thing {
public long v;
}
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
when:
// Remove inner class
sourceFile.text = """
public class ToolImpl {
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
when:
// Anonymous class
sourceFile.text = """
public class ToolImpl {
private Object r = new Runnable() { public void run() { } };
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
when:
// Add classes
sourceFile.text = """
public class ToolImpl {
private Object r = new Runnable() {
public void run() {
class LocalThing { }
}
};
private static class C1 {
}
private class C2 {
public void go() {
Object r2 = new Runnable() { public void run() { } };
}
}
}
"""
then:
succeeds ':b:compileJava'
executedAndNotSkipped ':a:compileJava'
skipped ':b:compileJava'
}
def "doesn't recompile when implementation resource is changed in various ways"() {
given:
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
"""
def sourceFile = file("a/src/main/java/ToolImpl.java")
sourceFile << """
public class ToolImpl { public void execute() { int i = 12; } }
"""
def resourceFile = file("a/src/main/resources/a.properties")
resourceFile.text = "a = 12"
file("b/src/main/java/Main.java") << """
public class Main { ToolImpl t = new ToolImpl(); }
"""
when:
succeeds ':b:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
resourceFile.text = "a = 11"
then:
succeeds ':b:compileJava'
skipped ':a:compileJava'
skipped ':b:compileJava'
when:
resourceFile.delete()
then:
succeeds ':b:compileJava'
skipped ':a:compileJava'
skipped ':b:compileJava'
when:
file("a/src/main/resources/org/gradle/b.properties").createFile()
then:
succeeds ':b:compileJava'
skipped ':a:compileJava'
skipped ':b:compileJava'
}
def "doesn't recompile when empty directories are changed in various ways"() {
given:
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
"""
def sourceFile = file("a/src/main/java/ToolImpl.java")
sourceFile << """
public class ToolImpl { public void execute() { int i = 12; } }
"""
file("b/src/main/java/Main.java") << """
public class Main { ToolImpl t = new ToolImpl(); }
"""
file("a/src/empty-dirs/ignore-me.txt").createFile()
file("a/src/empty-dirs/a/dir").mkdirs()
when:
succeeds ':b:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
when:
file("a/src/empty-dirs/a/dir2").mkdirs()
then:
succeeds ':b:compileJava'
skipped ':a:compileJava'
skipped ':b:compileJava'
when:
file("a/src/empty-dirs/a/dir").deleteDir()
then:
succeeds ':b:compileJava'
skipped ':a:compileJava'
skipped ':b:compileJava'
}
def "recompiles source when annotation processor implementation on annotation processor classpath changes"() {
settingsFile << "include 'c'"
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
project(':c') {
configurations {
processor
}
dependencies {
compile project(':a')
processor project(':b')
}
compileJava.options.annotationProcessorPath = configurations.processor
task run(type: JavaExec) {
main = 'TestApp'
classpath = sourceSets.main.runtimeClasspath
}
}
"""
def fixture = new HelperProcessorFixture()
// The annotation
fixture.writeApiTo(file("a"))
// The processor and library
fixture.writeSupportLibraryTo(file("b"))
fixture.writeAnnotationProcessorTo(file("b"))
// The class that is the target of the processor
file('c/src/main/java/TestApp.java') << '''
@Helper
class TestApp {
public static void main(String[] args) {
System.out.println(new TestAppHelper().getValue()); // generated class
}
}
'''
when:
run(':c:run')
then:
executedAndNotSkipped(':a:compileJava')
executedAndNotSkipped(':b:compileJava')
executedAndNotSkipped(':c:compileJava')
outputContains('greetings')
when:
run(':c:run')
then:
skipped(':a:compileJava')
skipped(':b:compileJava')
skipped(':c:compileJava')
outputContains('greetings')
when:
// Update the library class
fixture.message = 'hello'
fixture.writeSupportLibraryTo(file("b"))
run(':c:run')
then:
skipped(':a:compileJava')
executedAndNotSkipped(':b:compileJava')
executedAndNotSkipped(':c:compileJava')
outputContains('hello')
when:
run(':c:run')
then:
skipped(':a:compileJava')
skipped(':b:compileJava')
skipped(':c:compileJava')
outputContains('hello')
when:
// Update the processor class
fixture.suffix = 'world'
fixture.writeAnnotationProcessorTo(file("b"))
run(':c:run')
then:
skipped(':a:compileJava')
executedAndNotSkipped(':b:compileJava')
executedAndNotSkipped(':c:compileJava')
outputContains('hello world')
}
def "ignores annotation processor implementation when included in the compile classpath but annotation processing is disabled"() {
settingsFile << "include 'c'"
buildFile << """
project(':b') {
dependencies {
compile project(':a')
}
}
project(':c') {
dependencies {
compile project(':b')
}
compileJava.options.annotationProcessorPath = files()
}
"""
def fixture = new HelperProcessorFixture()
fixture.writeSupportLibraryTo(file("a"))
fixture.writeApiTo(file("b"))
fixture.writeAnnotationProcessorTo(file("b"))
file('c/src/main/java/TestApp.java') << '''
@Helper
class TestApp {
public static void main(String[] args) {
}
}
'''
when:
run(':c:compileJava')
then:
executedAndNotSkipped(':a:compileJava')
executedAndNotSkipped(':b:compileJava')
executedAndNotSkipped(':c:compileJava')
when:
run(':c:compileJava')
then:
skipped(':a:compileJava')
skipped(':b:compileJava')
skipped(':c:compileJava')
when:
// Update the library class
fixture.message = 'hello'
fixture.writeSupportLibraryTo(file("a"))
run(':c:compileJava')
then:
executedAndNotSkipped(':a:compileJava')
skipped(':b:compileJava')
skipped(':c:compileJava')
when:
// Update the processor class
fixture.suffix = 'world'
fixture.writeAnnotationProcessorTo(file("b"))
run(':c:compileJava')
then:
skipped(':a:compileJava')
executedAndNotSkipped(':b:compileJava')
skipped(':c:compileJava')
}
def "change to transitive super-class in different project should trigger recompilation"() {
given:
settingsFile << "include 'c'"
buildFile << """
project(':a') {
dependencies {
compile project(':b')
}
}
project(':b') {
dependencies {
compile project(':c')
}
}
"""
file("a/src/main/java/A.java") << "public class A extends B { void a() { b(); String c = c(); } }"
file("b/src/main/java/B.java") << "public class B extends C { void b() { d(); } }"
file("c/src/main/java/C.java") << "public class C { String c() { return null; }; void d() {} }"
when:
succeeds ':a:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
executedAndNotSkipped ':c:compileJava'
when:
file("c/src/main/java/C.java").text = "public class C { void c() {}; void d() {} }"
then:
fails ':a:compileJava'
failure.assertHasErrorOutput 'String c = c()'
and:
executedAndNotSkipped ':b:compileJava'
executedAndNotSkipped ':c:compileJava'
}
def "change to transitive super-class in different project should trigger recompilation 2"() {
given:
settingsFile << "include 'c', 'd'"
buildFile << """
project(':a') {
dependencies {
compile project(':b')
}
}
project(':b') {
dependencies {
compile project(':c')
}
}
project(':c') {
dependencies {
compile project(':d')
}
}
"""
file("a/src/main/java/A.java") << "public class A extends B { void a() { b(); String d = d(); } }"
file("b/src/main/java/B.java") << "public class B extends C { void b() { } }"
file("c/src/main/java/C.java") << "public class C extends D { void c() { }; }"
file("d/src/main/java/D.java") << "public class D { String d() { return null; } }"
when:
succeeds ':a:compileJava'
then:
executedAndNotSkipped ':a:compileJava'
executedAndNotSkipped ':b:compileJava'
executedAndNotSkipped ':c:compileJava'
executedAndNotSkipped ':d:compileJava'
when:
file("d/src/main/java/D.java").text = "public class D { void d() { } }"
then:
fails ':a:compileJava'
failure.assertHasErrorOutput 'String d = d();'
and:
executedAndNotSkipped ':b:compileJava'
executedAndNotSkipped ':c:compileJava'
executedAndNotSkipped ':d:compileJava'
}
@Issue("gradle/gradle#1913")
def "detects changes in compile classpath"() {
given:
buildFile << """
${jcenterRepository()}
dependencies {
if (project.hasProperty('useCommons')) {
implementation 'org.apache.commons:commons-lang3:3.5'
}
// There MUST be at least 3 dependencies, in that specific order, for the bug to show up.
// The reason is that `IncrementalTaskInputs` reports wrong information about deletions at the
// beginning of a list, when the collection is ordered. It has been agreed not to fix it now, but
// rather change the incremental compiler not to rely on this incorrect information
implementation 'net.jcip:jcip-annotations:1.0'
implementation 'org.slf4j:slf4j-api:1.7.10'
}
"""
file("src/main/java/Client.java") << """import org.apache.commons.lang3.exception.ExceptionUtils;
public class Client {
public void doSomething() {
ExceptionUtils.rethrow(new RuntimeException("ok"));
}
}
"""
when:
executer.withArgument('-PuseCommons')
succeeds ':compileJava'
then:
noExceptionThrown()
when: "Apache Commons is removed from classpath"
fails ':compileJava'
then:
failure.assertHasCause('Compilation failed; see the compiler error output for details.')
}
def "detects changes in compile classpath order"() {
given:
['a', 'b'].each {
// Same class is defined in both project `a` and `b` but with a different ABI
// so one shadows the other depending on the order on classpath
file("$it/src/main/java/A.java") << """
public class A {
public static String m_$it() { return "ok"; }
}
"""
}
buildFile << """
${jcenterRepository()}
dependencies {
switch (project.getProperty('order') as int) {
case 0:
implementation 'org.apache.commons:commons-lang3:3.5'
implementation project(':a')
implementation project(':b')
break
case 1:
implementation 'org.apache.commons:commons-lang3:3.5'
implementation project(':b')
implementation project(':a')
}
}
"""
file("src/main/java/Client.java") << """import org.apache.commons.lang3.exception.ExceptionUtils;
public class Client {
public void doSomething() {
ExceptionUtils.rethrow(new RuntimeException(A.m_a()));
}
}
"""
when:
executer.withArgument('-Porder=0')
succeeds ':compileJava'
then:
noExceptionThrown()
when: "Order is changed"
executer.withArgument('-Porder=1')
fails ':compileJava'
then:
failure.assertHasCause('Compilation failed; see the compiler error output for details.')
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy