org.gradle.internal.classpath.BuildScriptClasspathInstrumentationIntegrationTest.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 2023 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.internal.classpath
import org.gradle.api.Action
import org.gradle.api.internal.artifacts.ivyservice.CacheLayout
import org.gradle.api.internal.cache.StringInterner
import org.gradle.api.internal.initialization.transform.utils.DefaultInstrumentationAnalysisSerializer
import org.gradle.integtests.fixtures.AbstractIntegrationSpec
import org.gradle.integtests.fixtures.cache.FileAccessTimeJournalFixture
import org.gradle.test.fixtures.HttpRepository
import org.gradle.test.fixtures.file.TestFile
import org.gradle.test.fixtures.server.http.MavenHttpRepository
import org.gradle.test.fixtures.server.http.RepositoryHttpServer
import org.gradle.test.precondition.Requires
import org.gradle.test.preconditions.IntegTestPreconditions
import org.gradle.util.internal.GFileUtils
import org.junit.Rule
import spock.lang.Issue
import java.nio.file.Files
import java.util.function.Supplier
import java.util.regex.Pattern
import java.util.stream.Collectors
import static org.gradle.api.internal.initialization.transform.utils.InstrumentationTransformUtils.ANALYSIS_OUTPUT_DIR
import static org.gradle.api.internal.initialization.transform.utils.InstrumentationTransformUtils.DEPENDENCY_ANALYSIS_FILE_NAME
import static org.gradle.api.internal.initialization.transform.utils.InstrumentationTransformUtils.MERGE_OUTPUT_DIR
import static org.gradle.api.internal.initialization.transform.utils.InstrumentationTransformUtils.TYPE_HIERARCHY_ANALYSIS_FILE_NAME
import static org.gradle.util.internal.TextUtil.normaliseFileSeparators
class BuildScriptClasspathInstrumentationIntegrationTest extends AbstractIntegrationSpec implements FileAccessTimeJournalFixture {
@Rule
public final RepositoryHttpServer server = new RepositoryHttpServer(temporaryFolder)
def serializer = new DefaultInstrumentationAnalysisSerializer(new StringInterner())
def setup() {
requireOwnGradleUserHomeDir("We test content in the global cache")
}
def "buildSrc and included builds should be cached in global cache"() {
given:
withBuildSrc()
withIncludedBuild()
buildFile << """
buildscript {
dependencies {
classpath "org.test:included"
}
}
"""
when:
run("tasks")
then:
gradleUserHomeOutput("original/buildSrc.jar").exists()
gradleUserHomeOutput("instrumented/instrumented-buildSrc.jar").exists()
gradleUserHomeOutput("original/included-1.0.jar").exists()
gradleUserHomeOutput("instrumented/instrumented-included-1.0.jar").exists()
}
def "buildSrc and included build should be just instrumented and not upgraded"() {
given:
withBuildSrc()
withIncludedBuild()
buildFile << """
buildscript {
dependencies {
classpath "org.test:included"
}
}
"""
when:
run("tasks", "--info")
then:
allTransformsFor("buildSrc.jar") == ["ProjectDependencyInstrumentingArtifactTransform"]
allTransformsFor("included-1.0.jar") == ["ProjectDependencyInstrumentingArtifactTransform"]
}
def "external dependencies should not be copied to the global artifact transform cache"() {
given:
buildFile << """
buildscript {
${mavenCentralRepository()}
dependencies {
classpath "org.apache.commons:commons-lang3:3.8.1"
}
}
"""
when:
run("tasks", "--info")
then:
// InstrumentationAnalysisTransform is duplicated since InstrumentationAnalysisTransform result is also an input to MergeInstrumentationAnalysisTransform
allTransformsFor("commons-lang3-3.8.1.jar") ==~ ["InstrumentationAnalysisTransform", "InstrumentationAnalysisTransform", "MergeInstrumentationAnalysisTransform", "ExternalDependencyInstrumentingArtifactTransform"]
gradleUserHomeOutputs("original/commons-lang3-3.8.1.jar").isEmpty()
gradleUserHomeOutput("instrumented/instrumented-commons-lang3-3.8.1.jar").exists()
}
def "directories should be instrumented"() {
given:
withIncludedBuild("first")
withIncludedBuild("second")
buildFile << """
buildscript {
dependencies {
classpath(files("./first/build/classes/java/main"))
classpath(files("./second/build/classes/java/main"))
}
}
"""
when:
executer.inDirectory(file("first")).withTasks("classes").run()
executer.inDirectory(file("second")).withTasks("classes").run()
run("tasks", "--info")
then:
allTransformsFor("main") ==~ [
// Only the folder name is reported, so we cannot distinguish first and second
// InstrumentationAnalysisTransform is duplicated since InstrumentationAnalysisTransform
// result is also an input to MergeInstrumentationAnalysisTransform
"InstrumentationAnalysisTransform",
"InstrumentationAnalysisTransform",
"MergeInstrumentationAnalysisTransform",
"ExternalDependencyInstrumentingArtifactTransform",
"InstrumentationAnalysisTransform",
"InstrumentationAnalysisTransform",
"MergeInstrumentationAnalysisTransform",
"ExternalDependencyInstrumentingArtifactTransform"
]
}
def "order of entries in the effective classpath stays the same as in the original classpath"() {
given:
withIncludedBuild()
mavenRepo.module("org", "commons", "3.2.1").publish()
buildFile << """
import java.nio.file.Paths
buildscript {
repositories {
maven { url "${mavenRepo.uri}" }
}
dependencies {
classpath "${first[0]}"
classpath "${second[0]}"
}
}
Thread.currentThread().getContextClassLoader().getURLs()
.eachWithIndex { artifact, idx -> println "classpath[\$idx]==\${Paths.get(artifact.toURI()).toFile().name}" }
"""
when:
run("help")
then:
outputContains("classpath[0]==${first[1]}")
outputContains("classpath[1]==${second[1]}")
where:
first | second
["org.test:included", "included-1.0.jar"] | ["org:commons:3.2.1", "commons-3.2.1.jar"]
["org:commons:3.2.1", "commons-3.2.1.jar"] | ["org.test:included", "included-1.0.jar"]
}
@Issue("https://github.com/gradle/gradle/issues/28114")
def "buildSrc can monkey patch external plugins even after instrumentation"() {
given:
withExternalPlugin("myPlugin", "my.plugin") {
"""throw new RuntimeException("A bug in a plugin");"""
}
withBuildSrc()
file("buildSrc/src/main/java/test/gradle/MyPlugin.java") << """
package test.gradle;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class MyPlugin implements Plugin {
public void apply(Project project) {
System.out.println("MyPlugin patched from buildSrc");
}
}
"""
settingsFile << """
pluginManagement {
repositories {
maven { url "${mavenRepo.uri}" }
}
}
"""
buildFile << """
plugins {
id("my.plugin") version "1.0"
}
"""
when:
executer.inDirectory(file("external-plugin")).withTasks("publish").run()
run("help")
then:
outputContains("MyPlugin patched from buildSrc")
}
def "classpath can contain non-existing file"() {
given:
buildFile << """
buildscript { dependencies { classpath files("does-not-exist.jar") } }
"""
when:
succeeds()
then:
noExceptionThrown()
}
def "should analyze plugin artifacts"() {
given:
multiProjectJavaBuild("subproject", "api", "animals") {
file("$it/api/src/main/java/org/gradle/api/Plugin.java") << "package org.gradle.api; public interface Plugin {}"
file("$it/api/src/main/java/org/gradle/api/Task.java") << "package org.gradle.api; public interface Task {}"
file("$it/api/src/main/java/org/gradle/api/DefaultTask.java") << "package org.gradle.api; public class DefaultTask implements Task {}"
file("$it/api/src/main/java/org/gradle/api/GradleException.java") << "package org.gradle.api; public class GradleException {}"
file("$it/animals/src/main/java/test/gradle/test/Dogs.java").createFile().text = """
package test.gradle.test;
import org.gradle.api.*;
class GermanShepherd extends Dog implements Animal {
public void plugin() {
((Plugin) null).toString();
new GradleException();
}
}
abstract class Dog implements Mammal {
public void task() {
((Task) null).toString();
((DefaultTask) null).toString();
}
}
interface Mammal extends Animal {
}
interface Animal {
}
"""
}
executer.inDirectory(file("subproject")).withTasks("jar").run()
buildFile << """
buildscript {
repositories {
maven { url "$mavenRepo.uri" }
}
dependencies {
classpath(files("./subproject/animals/build/libs/animals-1.0.jar"))
}
}
"""
when:
run("tasks", "--info")
then:
// InstrumentationAnalysisTransform is duplicated since InstrumentationAnalysisTransform result is also an input to MergeInstrumentationAnalysisTransform
allTransformsFor("animals-1.0.jar") ==~ ["InstrumentationAnalysisTransform", "InstrumentationAnalysisTransform", "MergeInstrumentationAnalysisTransform", "ExternalDependencyInstrumentingArtifactTransform"]
def typeHierarchyAnalysis = typeHierarchyAnalysisOutput("animals-1.0.jar")
typeHierarchyAnalysis.exists()
serializer.readTypeHierarchyAnalysis(typeHierarchyAnalysis) == [
"test/gradle/test/Dog": ["test/gradle/test/Mammal"] as Set,
"test/gradle/test/GermanShepherd": ["test/gradle/test/Animal", "test/gradle/test/Dog"] as Set,
"test/gradle/test/Mammal": ["test/gradle/test/Animal"] as Set
]
def dependencyAnalysis = dependencyAnalysisOutput("animals-1.0.jar")
dependencyAnalysis.exists()
serializer.readDependencyAnalysis(dependencyAnalysis).dependencies == [
"org/gradle/api/DefaultTask": [] as Set,
"org/gradle/api/GradleException": [] as Set,
"org/gradle/api/Plugin": [] as Set,
"org/gradle/api/Task": [] as Set,
"test/gradle/test/Animal": [] as Set,
"test/gradle/test/Dog": [] as Set,
"test/gradle/test/Mammal": [] as Set,
]
}
def "should output only org.gradle supertypes for class dependencies"() {
given:
multiProjectJavaBuild("subproject") {
file("$it/impl/src/main/java/A.java") << "public class A extends B {}"
file("$it/api/src/main/java/B.java") << "public class B extends C {}"
file("$it/api/src/main/java/C.java") << "import org.gradle.D; public class C extends D {}"
file("$it/api/src/main/java/org/gradle/D.java") << "package org.gradle; public class D extends E {}"
file("$it/api/src/main/java/org/gradle/E.java") << "package org.gradle; public class E {}"
}
buildFile << """
buildscript {
dependencies {
// Add them as separate jars
classpath(files("./subproject/impl/build/libs/impl-1.0.jar"))
classpath(files("./subproject/api/build/libs/api-1.0.jar"))
}
}
"""
when:
executer.inDirectory(file("subproject")).withTasks("jar").run()
run("tasks")
then:
mergeAnalysisOutput("impl-1.0.jar").exists()
mergeAnalysisOutput("api-1.0.jar").exists()
def implMergeAnalysis = mergeAnalysisOutput("impl-1.0.jar")
serializer.readDependencyAnalysis(implMergeAnalysis).dependencies == [
"B": ["org/gradle/D", "org/gradle/E"] as Set
]
def apiMergeAnalysis = mergeAnalysisOutput("api-1.0.jar")
serializer.readDependencyAnalysis(apiMergeAnalysis).dependencies == [
"C": ["org/gradle/D", "org/gradle/E"] as Set,
"org/gradle/D": ["org/gradle/D", "org/gradle/E"] as Set,
"org/gradle/E": ["org/gradle/E"] as Set
]
}
@Requires(
value = IntegTestPreconditions.NotConfigCached,
reason = "Cc doesn't get invalidated when file dependency changes"
)
def "should re-instrument jar if classpath changes and class starts extending a Gradle core class transitively"() {
given:
multiProjectJavaBuild("subproject") {
file("$it/impl/src/main/java/A.java") << "public class A extends B {}"
file("$it/api/src/main/java/B.java") << "public class B {}"
}
buildFile << """
buildscript {
dependencies {
// Add them as separate jars
classpath(files("./subproject/impl/build/libs/impl-1.0.jar"))
classpath(files("./subproject/api/build/libs/api-1.0.jar"))
}
}
"""
when:
executer.inDirectory(file("subproject")).withTasks("jar").run()
run("tasks")
then:
gradleUserHomeOutputs("instrumented/instrumented-api-1.0.jar").size() == 1
gradleUserHomeOutputs("instrumented/instrumented-impl-1.0.jar").size() == 1
when:
file("subproject/api/src/main/java/B.java").text = "import org.gradle.C; public class B extends C {}"
file("subproject/api/src/main/java/org/gradle/C.java") << "package org.gradle; public class C {}"
executer.inDirectory(file("subproject")).withTasks("jar").run()
run("tasks")
then:
gradleUserHomeOutputs("instrumented/instrumented-api-1.0.jar").size() == 2
gradleUserHomeOutputs("instrumented/instrumented-impl-1.0.jar").size() == 2
}
@Requires(
value = IntegTestPreconditions.NotConfigCached,
reason = "Cc doesn't get invalidated when file dependency changes"
)
def "should not re-instrument jar if classpath changes but class doesn't extend Gradle core class"() {
given:
multiProjectJavaBuild("subproject") {
file("$it/impl/src/main/java/A.java") << "public class A extends B {}"
file("$it/api/src/main/java/B.java") << "public class B {}"
}
buildFile << """
buildscript {
dependencies {
// Add them as separate jars
classpath(files("./subproject/impl/build/libs/impl-1.0.jar"))
classpath(files("./subproject/api/build/libs/api-1.0.jar"))
}
}
"""
when:
executer.inDirectory(file("subproject")).withTasks("jar").run()
run("tasks")
then:
gradleUserHomeOutputs("instrumented/instrumented-api-1.0.jar").size() == 1
gradleUserHomeOutputs("instrumented/instrumented-impl-1.0.jar").size() == 1
when:
file("subproject/api/src/main/java/B.java").text = "public class B extends C {}"
file("subproject/api/src/main/java/C.java") << "public class C {}"
executer.inDirectory(file("subproject")).withTasks("jar").run()
run("tasks")
then:
gradleUserHomeOutputs("instrumented/instrumented-api-1.0.jar").size() == 2
gradleUserHomeOutputs("instrumented/instrumented-impl-1.0.jar").size() == 1
}
@Issue("https://github.com/gradle/gradle/issues/28301")
def "instrumentation and upgrades don't fail a build when repository is changed from remote to local"() {
def mavenRemote = new MavenHttpRepository(server, "/repo", HttpRepository.MetadataType.DEFAULT, mavenRepo)
def remoteModule = mavenRemote.module("test.gradle", "test-plugin", "0.2").publish()
remoteModule.pom.expectDownload()
remoteModule.artifact.expectDownload()
def artifactCoordinates = "${remoteModule.groupId}:${remoteModule.artifactId}:${remoteModule.version}"
when:
buildFile.text = """
buildscript {
repositories { maven { url "${mavenRemote.uri}" } }
dependencies { classpath "$artifactCoordinates" }
}
"""
then:
run("help")
when:
buildFile.text = """
buildscript {
repositories { maven { url "${normaliseFileSeparators(mavenRepo.uri.toString())}" } }
dependencies { classpath "$artifactCoordinates" }
}
"""
then:
run("help")
}
@Issue("https://github.com/gradle/gradle/issues/28496")
def "instrumentation and upgrades don't fail a build when we have two copies of the same artifact on the classpath"() {
file("jars/first").mkdirs()
file("jars/second").mkdirs()
def jar = file("jars/first/test-plugin-0.2.jar").with {
it.createFile()
}
GFileUtils.copyFile(jar, file("jars/second/test-plugin-0.2.jar"))
when:
buildFile.text = """
buildscript {
dependencies {
classpath(files("./jars/first/test-plugin-0.2.jar"))
classpath(files("./jars/second/test-plugin-0.2.jar"))
}
}
"""
then:
run("help")
}
def "external dependencies merge analysis is #mergeRun if type hierarchy #typeHierachyChange for local project"() {
given:
withIncludedBuild()
file("included/src/main/java/Foo.java") << "class Foo {}"
file("included/src/main/java/Bar.java") << "class Bar {}"
buildFile << """
import java.nio.file.Paths
buildscript {
repositories {
${mavenCentralRepository()}
}
dependencies {
classpath "org.test:included"
classpath "org.apache.commons:commons-lang3:3.8.1"
}
}
"""
when:
run("tasks")
then:
typeHierarchyAnalysisOutputs("commons-lang3-3.8.1.jar").size() == 1
dependencyAnalysisOutputs("commons-lang3-3.8.1.jar").size() == 1
mergeAnalysisOutputs("commons-lang3-3.8.1.jar").size() == 1
when:
file("included/src/main/java/Bar.java").text = barContentOnChange
run("tasks")
then:
typeHierarchyAnalysisOutputs("commons-lang3-3.8.1.jar").size() == 1
dependencyAnalysisOutputs("commons-lang3-3.8.1.jar").size() == 1
mergeAnalysisOutputs("commons-lang3-3.8.1.jar").size() == expectedFinalMergeAnalysisOutputs
where:
mergeRun | typeHierachyChange | barContentOnChange | expectedFinalMergeAnalysisOutputs
"re-run" | "is changed" | "class Bar extends Foo {}" | 2
"not re-run" | "is not changed" | "class Bar { public void bar() {} }" | 1
}
def "project dependency analysis is #analysisRun if classes are #classChangeDescription and recompiled"() {
given:
withIncludedBuild()
file("included/src/main/java/Foo.java") << "class Foo {}"
file("included/src/main/java/Bar.java") << "class Bar {}"
buildFile << """
buildscript {
repositories {
${mavenCentralRepository()}
}
dependencies {
classpath "org.test:included"
classpath "org.apache.commons:commons-lang3:3.8.1"
}
}
"""
when:
run(":included:clean", "tasks")
then:
// Artifact name == "main" since in transform we get "classes//main" directory
// as an input and we write a file name to the metadata as artifact name
def artifactName = "main"
typeHierarchyAnalysisOutputs(artifactName).size() == 1
dependencyAnalysisOutputs(artifactName).size() == 1
mergeAnalysisOutputs(artifactName).size() == 0
when:
file("included/src/main/java/Bar.java").text = barContentOnChange
run(":included:clean", "tasks")
then:
typeHierarchyAnalysisOutputs(artifactName).size() == expectedFinalAnalysisOutputs
dependencyAnalysisOutputs(artifactName).size() == expectedFinalAnalysisOutputs
mergeAnalysisOutputs(artifactName).size() == 0
where:
analysisRun | classChangeDescription | barContentOnChange | expectedFinalAnalysisOutputs
"not re-run" | "not changed" | "class Bar {}" | 1
"re-run" | "changed" | "class Bar { private void bar() {} }" | 2
}
def withBuildSrc() {
file("buildSrc/src/main/java/Thing.java") << "class Thing { }"
file("buildSrc/settings.gradle") << "\n"
}
def withIncludedBuild(String folderName = "included") {
file("$folderName/src/main/java/Thing.java") << "class Thing {}"
file("$folderName/build.gradle") << """
plugins {
id("java-library")
}
group = "org.test"
version = "1.0"
"""
file("$folderName/settings.gradle") << "rootProject.name = 'included'"
settingsFile << """
includeBuild("./$folderName")
"""
}
def javaBuild(String projectName = "included", Action init) {
file("$projectName/build.gradle") << """
plugins {
id("java-library")
}
group = "org.test"
version = "1.0"
"""
file("$projectName/settings.gradle") << """
rootProject.name = '$projectName'
"""
init(projectName)
}
def multiProjectJavaBuild(String projectName = "included", Action init) {
multiProjectJavaBuild(projectName, "api", "impl", init)
}
def multiProjectJavaBuild(String rootName = "included", String apiProjectName, String implProjectName, Action init) {
file("$rootName/$apiProjectName/build.gradle") << """
plugins {
id("java-library")
}
group = "org.test"
version = "1.0"
"""
file("$rootName/$implProjectName/build.gradle") << """
plugins {
id("java-library")
}
group = "org.test"
version = "1.0"
dependencies {
implementation project(":$apiProjectName")
}
"""
file("$rootName/settings.gradle") << """
rootProject.name = '$rootName'
include("$apiProjectName", "$implProjectName")
"""
init(rootName)
}
def withExternalPlugin(String name, String pluginId, Supplier implementationBody = {}) {
def implementationClass = name.capitalize()
def folderName = "external-plugin"
file("$folderName/build.gradle") << """
plugins {
id("java-gradle-plugin")
id("maven-publish")
}
group = "$pluginId"
version = "1.0"
publishing {
repositories {
maven {
url '${mavenRepo.uri}'
}
}
}
gradlePlugin {
plugins {
${name} {
id = '${pluginId}'
implementationClass = 'test.gradle.$implementationClass'
}
}
}
"""
file("$folderName/src/main/java/test/gradle/${implementationClass}.java") << """
package test.gradle;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class $implementationClass implements Plugin {
public void apply(Project project) {
${implementationBody.get()}
}
}
"""
file("$folderName/settings.gradle") << "rootProject.name = '$folderName'"
}
List allTransformsFor(String fileName) {
List transforms = []
def pattern = Pattern.compile("Transforming " + fileName + ".* with (.*)")
for (def line : output.readLines()) {
def matcher = pattern.matcher(line)
if (matcher.matches()) {
transforms.add(matcher.group(1))
}
}
return transforms
}
Set analyzeOutputs(String artifactName, String fileName, File cacheDir = getCacheDir()) {
return findOutputs("$ANALYSIS_OUTPUT_DIR/$DEPENDENCY_ANALYSIS_FILE_NAME", cacheDir).findAll {
serializer.readMetadataOnly(it).artifactName == artifactName
}.collect {
it.parentFile.listFiles().findAll { it.name == fileName }
}.flatten() as Set
}
Set typeHierarchyAnalysisOutputs(String artifactName, File cacheDir = getCacheDir()) {
return analyzeOutputs(artifactName, TYPE_HIERARCHY_ANALYSIS_FILE_NAME, cacheDir)
}
TestFile typeHierarchyAnalysisOutput(String artifactName, File cacheDir = getCacheDir()) {
def analysis = typeHierarchyAnalysisOutputs(artifactName, cacheDir)
if (analysis.size() == 1) {
return analysis.first()
}
throw new AssertionError("Could not find exactly one type hierarchy analysis for $artifactName: $analysis")
}
Set dependencyAnalysisOutputs(String artifactName, File cacheDir = getCacheDir()) {
return analyzeOutputs(artifactName, DEPENDENCY_ANALYSIS_FILE_NAME, cacheDir)
}
TestFile dependencyAnalysisOutput(String artifactName, File cacheDir = getCacheDir()) {
def analysis = dependencyAnalysisOutputs(artifactName, cacheDir)
if (analysis.size() == 1) {
return analysis.first()
}
throw new AssertionError("Could not find exactly one dependency analysis for $artifactName: $analysis")
}
Set mergeAnalysisOutputs(String artifactName, File cacheDir = getCacheDir()) {
return findOutputs("$MERGE_OUTPUT_DIR/$DEPENDENCY_ANALYSIS_FILE_NAME", cacheDir).findAll {
serializer.readMetadataOnly(it).artifactName == artifactName
}.collect { it } as Set
}
TestFile mergeAnalysisOutput(String artifactName, File cacheDir = getCacheDir()) {
def analysis = mergeAnalysisOutputs(artifactName, cacheDir)
if (analysis.size() == 1) {
return analysis.first()
}
throw new AssertionError("Could not find exactly one merge dependency analysis for $artifactName: $analysis")
}
TestFile gradleUserHomeOutput(String outputEndsWith, File cacheDir = getCacheDir()) {
findOutput(outputEndsWith, cacheDir)
}
TestFile findOutput(String outputEndsWith, File cacheDir) {
def dirs = findOutputs(outputEndsWith, cacheDir)
if (dirs.size() == 1) {
return dirs.first()
}
throw new AssertionError("Could not find exactly one output directory for $outputEndsWith: $dirs")
}
Set gradleUserHomeOutputs(String outputEndsWith, File cacheDir = getCacheDir()) {
findOutputs(outputEndsWith, cacheDir)
}
Set findOutputs(String outputEndsWith, File cacheDir) {
return Files.find(cacheDir.toPath(), 4, (path, attributes) -> normaliseFileSeparators(path.toString()).endsWith(outputEndsWith))
.map { new TestFile(it.toFile()) }
.collect(Collectors.toSet())
}
TestFile getCacheDir() {
return getGradleVersionedCacheDir().file(CacheLayout.TRANSFORMS.getName())
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy