org.gradle.java.compile.incremental.SourceIncrementalJavaCompilationIntegrationTest.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 2013 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.incremental
import groovy.transform.NotYetImplemented
import org.gradle.integtests.fixtures.AbstractIntegrationSpec
import org.gradle.integtests.fixtures.CompilationOutputsFixture
import org.gradle.util.Requires
import org.gradle.util.TestPrecondition
import spock.lang.Issue
import spock.lang.Unroll
class SourceIncrementalJavaCompilationIntegrationTest extends AbstractIntegrationSpec {
CompilationOutputsFixture outputs
def setup() {
outputs = new CompilationOutputsFixture(file("build/classes"))
buildFile << """
apply plugin: 'java'
"""
}
private File java(String... classBodies) {
File out
for (String body : classBodies) {
def className = (body =~ /(?s).*?(?:class|interface|enum) (\w+) .*/)[0][1]
assert className: "unable to find class name"
def f = file("src/main/java/${className}.java")
f.createFile()
f.text = body
out = f
}
out
}
def "detects deletion of an isolated source class with an inner class"() {
def a = java """class A {
class InnerA {}
}"""
java "class B {}"
outputs.snapshot { run "compileJava" }
when:
assert a.delete()
run "compileJava"
then:
outputs.noneRecompiled() //B is not recompiled
outputs.deletedClasses 'A', 'A$InnerA' //inner class is also deleted
}
def "detects deletion of a source base class that leads to compilation failure"() {
def a = java "class A {}"
java "class B extends A {}"
outputs.snapshot { run "compileJava" }
when:
assert a.delete()
then:
fails "compileJava"
outputs.noneRecompiled()
outputs.deletedClasses 'A', 'B'
}
def "detects change of an isolated source class with an inner class"() {
java """class A {
class InnerA {}
}"""
java "class B {}"
outputs.snapshot { run "compileJava" }
when:
java """class A {
class InnerA { /* change */ }
}"""
run "compileJava"
then:
outputs.recompiledClasses 'A', 'A$InnerA'
}
def "detects change of an isolated class"() {
java "class A {}", "class B {}"
outputs.snapshot { run "compileJava" }
when:
java "class A { /* change */ }"
run "compileJava"
then:
outputs.recompiledClasses 'A'
}
def "detects deletion of an inner class"() {
java """class A {
class InnerA {}
}"""
java "class B {}"
outputs.snapshot { run "compileJava" }
when:
java "class A {}"
run "compileJava"
then:
outputs.recompiledClasses 'A'
outputs.deletedClasses 'A$InnerA'
}
def "detects rename of an inner class"() {
java """class A {
class InnerA {}
}"""
java "class B {}"
outputs.snapshot { run "compileJava" }
when:
java """class A {
class InnerA2 {}
}"""
run "compileJava"
then:
outputs.recompiledClasses 'A', 'A$InnerA2'
outputs.deletedClasses 'A$InnerA'
}
def "detects addition af a new class with an inner class"() {
java "class B {}"
outputs.snapshot { run "compileJava" }
when:
java """class A {
class InnerA {}
}"""
run "compileJava"
then:
outputs.recompiledClasses 'A', 'A$InnerA'
}
def "detects transitive dependencies"() {
java "class A {}", "class B extends A {}", "class C extends B {}", "class D {}"
outputs.snapshot { run "compileJava" }
when:
java "class A { /* change */ }"
run "compileJava"
then:
outputs.recompiledClasses 'A', 'B', 'C'
when:
outputs.snapshot()
java "class B { /* change */ }"
run "compileJava"
then:
outputs.recompiledClasses 'B', 'C'
}
def "detects transitive dependencies with inner classes"() {
java "class A {}", "class B extends A {}", "class D {}"
java """class C extends B {
class InnerC {}
}
"""
outputs.snapshot { run "compileJava" }
when:
java "class A { /* change */ }"
run "compileJava"
then:
outputs.recompiledClasses 'A', 'B', 'C', 'C$InnerC'
}
def "handles cycles in class dependencies"() {
java "class A {}", "class D {}"
java "class B extends A { C c; }", "class C extends B {}" //cycle
outputs.snapshot { run "compileJava" }
when:
java "class A { /* change */ }"
run "compileJava"
then:
outputs.recompiledClasses 'A', 'B', 'C'
}
@Unroll
def "change to #retention retention annotation class recompiles #desc"() {
def annotationClass = file("src/main/java/SomeAnnotation.java") << """
import java.lang.annotation.*;
@Retention(RetentionPolicy.$retention)
public @interface SomeAnnotation {}
"""
java "@SomeAnnotation class A {}", "class B {}"
outputs.snapshot { run "compileJava" }
when:
annotationClass.text += "/* change */"
run "compileJava"
then:
outputs.recompiledClasses(expected as String[])
where:
desc | retention | expected
'all' | 'SOURCE' | ['A', 'B', 'SomeAnnotation']
'annotated types' | 'CLASS' | ['SomeAnnotation', 'A']
'annotated types' | 'RUNTIME' | ['SomeAnnotation', 'A']
}
def "change to class referenced by an annotation recompiles annotated types"() {
java """
import java.lang.annotation.*;
@Retention(RetentionPolicy.CLASS)
public @interface B {
Class> value();
}
"""
def a = java "class A {}"
java "@B(A.class) class OnClass {}",
"class OnMethod { @B(A.class) void foo() {} }",
"class OnField { @B(A.class) String foo; }",
"class OnParameter { void foo(@B(A.class) int x) {} }"
outputs.snapshot { run "compileJava" }
when:
a.text += "/* change */"
run "compileJava"
then:
outputs.recompiledClasses("A", "OnClass", "OnMethod", "OnParameter", "OnField")
}
def "change to class referenced by an array value in an annotation recompiles annotated types"() {
java """
import java.lang.annotation.*;
@Retention(RetentionPolicy.CLASS)
public @interface B {
Class>[] value();
}
"""
def a = java "class A {}"
java "@B(A.class) class OnClass {}",
"class OnMethod { @B(A.class) void foo() {} }",
"class OnField { @B(A.class) String foo; }",
"class OnParameter { void foo(@B(A.class) int x) {} }"
outputs.snapshot { run "compileJava" }
when:
a.text += "/* change */"
run "compileJava"
then:
outputs.recompiledClasses("A", "OnClass", "OnMethod", "OnParameter", "OnField")
}
def "change to enum referenced by an annotation recompiles annotated types"() {
java """
import java.lang.annotation.*;
@Retention(RetentionPolicy.CLASS)
public @interface B {
A value();
}
"""
def a = java "enum A { FOO }"
java "@B(A.FOO) class OnClass {}",
"class OnMethod { @B(A.FOO) void foo() {} }",
"class OnField { @B(A.FOO) String foo; }",
"class OnParameter { void foo(@B(A.FOO) int x) {} }"
outputs.snapshot { run "compileJava" }
when:
a.text += "/* change */"
run "compileJava"
then:
outputs.recompiledClasses("A", "B", "OnClass", "OnMethod", "OnParameter", "OnField")
}
def "change to value in nested annotation recompiles annotated types"() {
java """
import java.lang.annotation.*;
@Retention(RetentionPolicy.CLASS)
public @interface B {
A value();
}
"""
java "public @interface A { Class> value(); }"
def c = java "class C {}"
java "@B(@A(C.class)) class OnClass {}",
"class OnMethod { @B(@A(C.class)) void foo() {} }",
"class OnField { @B(@A(C.class)) String foo; }",
"class OnParameter { void foo(@B(@A(C.class)) int x) {} }"
outputs.snapshot { run "compileJava" }
when:
c.text += "/* change */"
run "compileJava"
then:
outputs.recompiledClasses("C", "OnClass", "OnMethod", "OnParameter", "OnField")
}
def "changed class with private constant does not incur full rebuild"() {
java "class A {}", "class B { private final static int x = 1;}"
outputs.snapshot { run "compileJava" }
when:
java "class B { /* change */ }"
run "compileJava"
then:
outputs.recompiledClasses 'B'
}
def "changed class with used non-private constant incurs full rebuild"() {
java "class A { int foo() { return 1; } }", "class B { final static int x = 1;}"
outputs.snapshot { run "compileJava" }
when:
java "class B { /* change */ }"
run "compileJava"
then:
outputs.recompiledClasses 'B', 'A'
}
@NotYetImplemented
// Can re-enable with compiler plugins. See gradle/gradle#1474
def "changing an unused non-private constant incurs partial rebuild"() {
java "class A { int foo() { return 2; } }", "class B { final static int x = 1;}"
outputs.snapshot { run "compileJava" }
when:
java "class B { /* change */ }"
run "compileJava"
then:
outputs.recompiledClasses 'B'
}
def "dependent class with non-private constant does not incur full rebuild"() {
java "class A {}", "class B extends A { final static int x = 1;}", "class C {}"
outputs.snapshot { run "compileJava" }
when:
java "class A { /* change */ }"
run "compileJava"
then:
outputs.recompiledClasses 'B', 'A'
}
def "detects class changes in subsequent runs ensuring the class dependency data is refreshed"() {
java "class A {}", "class B {}", "class C {}"
outputs.snapshot { run "compileJava" }
when:
java "class B extends A {}"
run "compileJava"
then:
outputs.recompiledClasses('B')
when:
outputs.snapshot()
java "class A { /* change */ }"
run "compileJava"
then:
outputs.recompiledClasses('A', 'B')
}
def "handles multiple compile tasks within a single project"() {
java "class A {}", "class B extends A {}"
file("src/integTest/java/X.java") << "class X {}"
file("src/integTest/java/Y.java") << "class Y extends X {}"
//new separate compile task (integTestCompile)
file("build.gradle") << """
sourceSets { integTest.java.srcDir 'src/integTest/java' }
"""
outputs.snapshot { run "compileIntegTestJava", "compileJava" }
when: //when A class is changed
java "class A { String change; }"
run "compileIntegTestJava", "compileJava", "-i"
then: //only B and A are recompiled
outputs.recompiledClasses("A", "B")
when: //when X class is changed
outputs.snapshot()
file("src/integTest/java/X.java").text = "class X { String change;}"
run "compileIntegTestJava", "compileJava", "-i"
then: //only X and Y are recompiled
outputs.recompiledClasses("X", "Y")
}
def "recompiles classes from extra source directories"() {
buildFile << "sourceSets.main.java.srcDir 'extra-java'"
java("class B {}")
file("extra-java/A.java") << "class A extends B {}"
file("extra-java/C.java") << "class C {}"
outputs.snapshot { run "compileJava" }
when:
java("class B { String change; } ")
run "compileJava"
then:
outputs.recompiledClasses("B", "A")
}
def "recompilation considers changes from dependent sourceSet"() {
buildFile << """
sourceSets {
other {}
main { compileClasspath += sourceSets.other.output }
}
"""
java("class Main extends com.foo.Other {}")
file("src/other/java/com/foo/Other.java") << "package com.foo; public class Other {}"
outputs.snapshot { run "compileJava" }
when:
file("src/other/java/com/foo/Other.java").text = "package com.foo; public class Other { String change; }"
run "compileJava"
then:
outputs.recompiledClasses("Other", "Main")
}
def "recompilation does not process removed classes from dependent sourceSet"() {
def unusedClass = java("public class Unused {}")
// Need another class or :compileJava will always be considered UP-TO-DATE
java("public class Other {}")
file("src/test/java/BazTest.java") << "public class BazTest {}"
outputs.snapshot { run "compileTestJava" }
when:
file("src/test/java/BazTest.java").text = "public class BazTest { String change; }"
unusedClass.delete()
run "compileTestJava"
then:
outputs.recompiledClasses("BazTest")
outputs.deletedClasses("Unused")
}
def "detects changes to source in extra source directories"() {
buildFile << "sourceSets.main.java.srcDir 'extra-java'"
java("class A extends B {}")
file("extra-java/B.java") << "class B {}"
file("extra-java/C.java") << "class C {}"
outputs.snapshot { run "compileJava" }
when:
file("extra-java/B.java").text = "class B { String change; }"
run "compileJava"
then:
outputs.recompiledClasses("B", "A")
}
def "recompiles classes from extra source directory provided as #type"() {
given:
buildFile << "compileJava.source $method('extra-java')"
java("class B {}")
file("extra-java/A.java") << "class A extends B {}"
file("extra-java/C.java") << "class C {}"
outputs.snapshot { run "compileJava" }
when:
java("class B { String change; } ")
run "compileJava"
then:
outputs.recompiledClasses("B", "A")
where:
type | method
"File" | "file"
"DirectoryTree" | "fileTree"
}
def "detects changes to source in extra source directory provided as #type"() {
buildFile << "compileJava.source $method('extra-java')"
java("class A extends B {}")
file("extra-java/B.java") << "class B {}"
file("extra-java/C.java") << "class C {}"
outputs.snapshot { run "compileJava" }
when:
file("extra-java/B.java").text = "class B { String change; }"
run "compileJava"
then:
outputs.recompiledClasses("B", "A")
where:
type | method
"File" | "file"
"DirectoryTree" | "fileTree"
}
def "reports source type that does not support detection of source root"() {
buildFile << "compileJava.source([file('extra-java'), file('other'), file('text-file.txt')])"
java("class A extends B {}")
file("extra-java/B.java") << "class B {}"
file("extra-java/C.java") << "class C {}"
def textFile = file('text-file.txt')
textFile.text = "text file as root"
outputs.snapshot { run "compileJava" }
when:
file("extra-java/B.java").text = "class B { String change; }"
executer.withArgument "--info"
run "compileJava"
then:
outputs.recompiledClasses("A", "B", "C")
output.contains("Cannot infer source root(s) for source `file '${textFile.absolutePath}'`. Supported types are `File` (directories only), `DirectoryTree` and `SourceDirectorySet`.")
output.contains("Full recompilation is required because the source roots could not be inferred.")
}
def "missing files are ignored as source roots"() {
buildFile << """
compileJava {
source([
fileTree('missing-tree'),
file('missing-file')
])
}"""
java("class A extends B {}")
java("class B {}")
java("class C {}")
outputs.snapshot { run "compileJava" }
when:
java("class B { String change; }")
executer.withArgument "--info"
run "compileJava"
then:
outputs.recompiledClasses("A", "B")
}
def "can remove source root"() {
def toBeRemoved = file("to-be-removed")
buildFile << """
compileJava {
source([fileTree('to-be-removed')])
}"""
java("class A extends B {}")
java("class B {}")
toBeRemoved.file("C.java").text = "class C {}"
outputs.snapshot { run "compileJava" }
when:
toBeRemoved.deleteDir()
executer.withArgument "--info"
run "compileJava"
then:
outputs.recompiledClasses()
}
def "handles duplicate class across source directories"() {
//compiler does not allow this scenario, documenting it here
buildFile << "sourceSets.main.java.srcDir 'java'"
java("class A {}")
file("java/A.java") << "class A {}"
when:
fails "compileJava"
then:
failure.assertHasCause("Compilation failed")
}
@Issue("GRADLE-3426")
def "supports Java 1.2 dependencies"() {
java "class A {}"
buildFile << """
${jcenterRepository()}
dependencies { compile 'com.ibm.icu:icu4j:2.6.1' }
"""
expect:
succeeds "compileJava"
}
@Issue("GRADLE-3426")
def "fully recompiles when a non-analyzable jar is changed"() {
def a = java """
import com.ibm.icu.util.Calendar;
class A {
Calendar cal;
}
"""
buildFile << """
${jcenterRepository()}
if (hasProperty("withIcu")) {
dependencies { compile 'com.ibm.icu:icu4j:2.6.1' }
}
"""
succeeds "compileJava", "-PwithIcu"
when:
a.text = "class A {}"
then:
succeeds "compileJava", "--info"
outputContains("Full recompilation is required because LocaleElements_zh__PINYIN.class could not be analyzed for incremental compilation.")
}
@Issue("GRADLE-3495")
def "supports Java 1.1 dependencies"() {
java "class A {}"
buildFile << """
${jcenterRepository()}
dependencies { compile 'net.sf.ehcache:ehcache:2.10.2' }
"""
expect:
run "compileJava"
}
@Unroll("detects changes to class referenced through a #modifier field")
def "detects changes to class referenced through a field"() {
given:
java """class A {
$modifier B b;
void doSomething() {
Runnable r = b;
r.run();
}
}"""
java '''abstract class B implements Runnable { }'''
outputs.snapshot { run "compileJava" }
when:
file("src/main/java/B.java").text = "class B { }"
fails "compileJava"
then:
failure.assertHasErrorOutput 'Runnable r = b;'
where:
modifier << ['', 'static']
}
@Unroll("detects changes to class referenced through a #modifier array field")
def "detects changes to class referenced through an array field"() {
given:
java """class A {
$modifier B[] b;
void doSomething() {
Runnable r = b[0];
r.run();
}
}"""
java '''abstract class B implements Runnable { }'''
outputs.snapshot { run "compileJava" }
when:
file("src/main/java/B.java").text = "class B { }"
fails "compileJava"
then:
failure.assertHasErrorOutput 'Runnable r = b[0];'
where:
modifier << ['', 'static']
}
@Unroll("detects changes to class referenced through a #modifier multi-dimensional array field")
def "detects changes to class referenced through an multi-dimensional array field"() {
given:
java """class A {
$modifier B[][] b;
void doSomething() {
Runnable r = b[0][0];
r.run();
}
}"""
java '''abstract class B implements Runnable { }'''
outputs.snapshot { run "compileJava" }
when:
file("src/main/java/B.java").text = "class B { }"
fails "compileJava"
then:
failure.assertHasErrorOutput 'Runnable r = b[0][0];'
where:
modifier << ['', 'static']
}
def "detects changes to class referenced in method body"() {
given:
java '''class A {
void doSomething(Object b) {
Runnable r = (B) b;
r.run();
}
}'''
java '''abstract class B implements Runnable { }'''
outputs.snapshot { run "compileJava" }
when:
file("src/main/java/B.java").text = "class B { }"
fails "compileJava"
then:
failure.assertHasErrorOutput 'Runnable r = (B) b;'
}
def "detects changes to class referenced through return type"() {
given:
java '''class A {
B b() { return null; }
void doSomething() {
Runnable r = b();
r.run();
}
}'''
java '''abstract class B implements Runnable { }'''
outputs.snapshot { run "compileJava" }
when:
file("src/main/java/B.java").text = "class B { }"
fails "compileJava"
then:
failure.assertHasErrorOutput 'Runnable r = b();'
}
def "detects changes to class referenced through method signature"() {
given:
java '''class A {
Runnable go(B b) {
Runnable r = b;
r.run();
return b;
}
}'''
java '''abstract class B implements Runnable { }'''
outputs.snapshot { run "compileJava" }
when:
file("src/main/java/B.java").text = "class B { }"
fails "compileJava"
then:
failure.assertHasErrorOutput 'Runnable r = b;'
}
def "detects changes to class referenced through type argument in field"() {
given:
java '''class A {
java.util.List bs;
void doSomething() {
for (B b: bs) {
Runnable r = b;
r.run();
}
}
}'''
java '''abstract class B implements Runnable { }'''
outputs.snapshot { run "compileJava" }
when:
file("src/main/java/B.java").text = "class B { }"
fails "compileJava"
then:
failure.assertHasErrorOutput 'Runnable r = b;'
}
def "detects changes to class referenced through type argument in return type"() {
given:
java '''class A {
java.util.List bs() { return null; }
void doSomething() {
for (B b: bs()) {
Runnable r = b;
r.run();
}
}
}'''
java '''abstract class B implements Runnable { }'''
outputs.snapshot { run "compileJava" }
when:
file("src/main/java/B.java").text = "class B { }"
fails "compileJava"
then:
failure.assertHasErrorOutput 'Runnable r = b;'
}
def "detects changes to class referenced through type argument in parameter"() {
given:
java '''class A {
void doSomething(java.util.List bs) {
for (B b: bs) {
Runnable r = b;
r.run();
}
}
}'''
java '''abstract class B implements Runnable { }'''
outputs.snapshot { run "compileJava" }
when:
file("src/main/java/B.java").text = "class B { }"
fails "compileJava"
then:
failure.assertHasErrorOutput 'Runnable r = b;'
}
def "deletes empty packages dirs"() {
given:
def a = file('src/main/java/com/foo/internal/A.java') << """
package com.foo.internal;
public class A {}
"""
file('src/main/java/com/bar/B.java') << """
package com.bar;
public class B {}
"""
succeeds "compileJava"
a.delete()
when:
succeeds "compileJava"
then:
! file("build/classes/java/main/com/foo").exists()
}
def "recompiles types whose names look like inne classes even if they aren't"() {
given:
file('src/main/java/Test.java') << 'public class Test{}'
file('src/main/java/Test$$InnerClass.java') << 'public class Test$$InnerClass{}'
buildFile << '''
apply plugin: 'java'
'''.stripIndent()
when:
succeeds ':compileJava'
then:
executedAndNotSkipped ':compileJava'
file('build/classes/java/main/Test.class').assertExists()
file('build/classes/java/main/Test$$InnerClass.class').assertExists()
when:
file('src/main/java/Test.java').text = 'public class Test{ void foo() {} }'
succeeds ':compileJava'
then:
executedAndNotSkipped ':compileJava'
file('build/classes/java/main/Test.class').assertExists()
file('build/classes/java/main/Test$$InnerClass.class').assertExists()
}
def "incremental java compilation ignores empty packages"() {
given:
file('src/main/java/org/gradle/test/MyTest.java').text = """
package org.gradle.test;
class MyTest {}
"""
when:
run 'compileJava'
then:
executedAndNotSkipped(':compileJava')
when:
file('src/main/java/org/gradle/different').createDir()
run('compileJava')
then:
skipped(':compileJava')
}
@Requires(TestPrecondition.JDK9_OR_LATER)
def "recompiles when module info changes"() {
given:
java("""
import java.util.logging.Logger;
class Foo {
Logger logger;
}
""")
def moduleInfo = file("src/main/java/module-info.java")
moduleInfo.text = """
module foo {
requires java.logging;
}
"""
succeeds'compileJava'
when:
moduleInfo.text = """
module foo {
}
"""
then:
fails'compileJava'
result.assertHasErrorOutput("package java.util.logging is not visible")
}
def "recompiles all classes in a package if the package-info file changes"() {
given:
def packageFile = file("src/main/java/foo/package-info.java")
packageFile.text = """package foo;"""
file("src/main/java/foo/A.java").text = "package foo; class A {}"
file("src/main/java/foo/B.java").text = "package foo; public class B {}"
file("src/main/java/foo/bar/C.java").text = "package foo.bar; class C {}"
file("src/main/java/baz/D.java").text = "package baz; class D {}"
file("src/main/java/baz/E.java").text = "package baz; import foo.B; class E extends B {}"
outputs.snapshot { succeeds 'compileJava' }
when:
packageFile.text = """@Deprecated package foo;"""
succeeds 'compileJava'
then:
outputs.recompiledClasses("A", "B", "E", "package-info")
}
@Requires(TestPrecondition.JDK8_OR_LATER)
def "deletes headers when source file is deleted"() {
given:
buildFile << """
compileJava.options.headerOutputDirectory = file("build/headers/java/main")
"""
def source = java("""class Foo {
public native void foo();
}""")
java("""class Bar {
public native void bar();
}""")
succeeds "compileJava"
when:
source.delete()
succeeds "compileJava"
then:
file("build/headers/java/main/Foo.h").assertDoesNotExist()
file("build/headers/java/main/Bar.h").assertExists()
}
def "recompiles all dependents when no jar analysis is present"() {
given:
java """class A {
com.google.common.base.Splitter splitter;
}"""
java """class B {}"""
buildFile << """
${jcenterRepository()}
dependencies { compile 'com.google.guava:guava:21.0' }
"""
outputs.snapshot { succeeds 'compileJava' }
when:
executer.requireOwnGradleUserHomeDir()
java """class B {
//some change
}"""
then:
succeeds "compileJava"
outputs.recompiledClasses("A", "B")
}
def "does not recompile when a resource changes"() {
given:
buildFile << """
compileJava.inputs.dir 'src/main/resources'
"""
java("class A {}")
java("class B {}")
def resource = file("src/main/resources/foo.txt")
resource.text = 'foo'
outputs.snapshot { succeeds 'compileJava' }
when:
resource.text = 'bar'
then:
succeeds 'compileJava'
outputs.noneRecompiled()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy