Skip to content

Commit

Permalink
issues/150: Support Gradle's Configuration Cache
Browse files Browse the repository at this point in the history
  • Loading branch information
fmck3516 authored Jun 4, 2024
1 parent eb5f07d commit f013540
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.skippy.gradle;

import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSetContainer;

import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

/**
* A sub-set of relevant {@link Project} properties that are compatible with Gradle's Configuration Cache.
*/
class CachableProperties {

final boolean sourceSetContainerAvailable;
final List<File> classesDirs;
final SkippyPluginExtension skippyPluginExtension;
final Path projectDir;
final Path buildDir;

private CachableProperties(boolean sourceSetContainerAvailable, List<File> classesDirs, SkippyPluginExtension skippyExtension, Path projectDir, Path buildDir) {
this.sourceSetContainerAvailable = sourceSetContainerAvailable;
this.classesDirs = classesDirs;
this.skippyPluginExtension = skippyExtension;
this.projectDir = projectDir;
this.buildDir = buildDir;
}

static CachableProperties from(Project project) {
var sourceSetContainer = project.getExtensions().findByType(SourceSetContainer.class);
// new ArrayList<>() is a workaround for https://github.com/gradle/gradle/issues/26942
var classesDirs = new ArrayList<>(sourceSetContainer.stream().flatMap(sourceSet -> sourceSet.getOutput().getClassesDirs().getFiles().stream()).toList());
var skippyExtension = project.getExtensions().getByType(SkippyPluginExtension.class);
var projectDir = project.getProjectDir().toPath();
var buildDir = project.getLayout().getBuildDirectory().getAsFile().get().toPath();
return new CachableProperties(sourceSetContainer != null, classesDirs, skippyExtension, projectDir, buildDir);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,11 @@
import io.skippy.core.ClassFile;
import io.skippy.core.Profiler;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;

import java.io.File;
import java.nio.file.Path;
import java.util.*;

import static java.util.Comparator.comparing;

/**
* Collects {@link ClassFile}s across all {@link SourceSet}s in a project.
*
Expand All @@ -36,16 +33,11 @@
final class GradleClassFileCollector implements ClassFileCollector {

private final Path projectDir;
private final SourceSetContainer sourceSetContainer;
private final List<File> classesDirs;

/**
* C'tor
*
* @param sourceSetContainer a {@link SourceSetContainer}
*/
GradleClassFileCollector(Path projectDir, SourceSetContainer sourceSetContainer) {
GradleClassFileCollector(Path projectDir, List<File> classesDirs) {
this.projectDir = projectDir;
this.sourceSetContainer = sourceSetContainer;
this.classesDirs = classesDirs;
}

/**
Expand All @@ -57,22 +49,13 @@ final class GradleClassFileCollector implements ClassFileCollector {
public List<ClassFile> collect() {
return Profiler.profile("GradleClassFileCollector#collect", () -> {
var result = new ArrayList<ClassFile>();
for (var sourceSet : sourceSetContainer) {
result.addAll(collect(sourceSet));
for (var classesDir : classesDirs) {
result.addAll(sort(collect(classesDir, classesDir)));
}
return result;
});
}

private List<ClassFile> collect(SourceSet sourceSet) {
var classesDirs = sourceSet.getOutput().getClassesDirs().getFiles();
var result = new ArrayList<ClassFile>();
for (var classesDir : classesDirs) {
result.addAll(sort(collect(classesDir, classesDir)));
}
return result;
}

private List<ClassFile> collect(File outputFolder, File directory) {
var result = new LinkedList<ClassFile>();
File[] files = directory.listFiles();
Expand Down
48 changes: 12 additions & 36 deletions skippy-gradle/src/main/java/io/skippy/gradle/SkippyAnalyzeTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,60 +17,36 @@
package io.skippy.gradle;

import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.testing.Test;
import org.gradle.api.provider.Property;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.Internal;

import javax.inject.Inject;

import org.gradle.api.tasks.testing.TestDescriptor;
import org.gradle.api.tasks.testing.TestListener;
import org.gradle.api.tasks.testing.TestResult;

import java.util.ArrayList;
import java.util.List;

import static io.skippy.gradle.SkippyGradleUtils.*;

/**
* Informs Skippy that the relevant parts of the build (e.g., compilation and testing) have finished.
*/
class SkippyAnalyzeTask extends DefaultTask {
abstract class SkippyAnalyzeTask extends DefaultTask {

@Internal
abstract Property<CachableProperties> getSettings();

@ServiceReference
abstract Property<TestResultService> getTestResultService();

@Inject
public SkippyAnalyzeTask() {
setGroup("skippy");
var testFailedListener = new TestFailedListener();
getProject().getTasks().withType(Test.class, testTask -> testTask.addTestListener(testFailedListener));
doLast(task -> {
ifBuildSupportsSkippy(getProject(), skippyBuildApi -> {
for (var failedTest : testFailedListener.failedTests) {
ifBuildSupportsSkippy(getSettings().get(), skippyBuildApi -> {
for (var failedTest : getTestResultService().get().failedTests) {
skippyBuildApi.testFailed(failedTest.getClassName());
}
skippyBuildApi.buildFinished();
});
});
}

static private class TestFailedListener implements TestListener {
private final List<TestDescriptor> failedTests = new ArrayList<>();

@Override
public void beforeSuite(TestDescriptor testDescriptor) {
}

@Override
public void afterSuite(TestDescriptor testDescriptor, TestResult testResult) {
}

@Override
public void beforeTest(TestDescriptor testDescriptor) {
}

@Override
public void afterTest(TestDescriptor testDescriptor, TestResult testResult) {
if (testResult.getResultType() == TestResult.ResultType.FAILURE) {
failedTests.add(testDescriptor);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package io.skippy.gradle;

import org.gradle.api.DefaultTask;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;

import javax.inject.Inject;

Expand All @@ -29,13 +31,16 @@
*
* @author Florian McKee
*/
class SkippyCleanTask extends DefaultTask {
abstract class SkippyCleanTask extends DefaultTask {

@Input
abstract Property<CachableProperties> getSettings();

@Inject
public SkippyCleanTask() {
setGroup("skippy");
doLast(task -> {
ifBuildSupportsSkippy(getProject(), skippyBuildApi -> {
ifBuildSupportsSkippy(getSettings().get(), skippyBuildApi -> {
skippyBuildApi.deleteSkippyFolder();
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,22 @@

import io.skippy.core.SkippyBuildApi;
import io.skippy.core.SkippyRepository;
import org.gradle.api.Project;
import org.gradle.api.tasks.SourceSetContainer;

import java.util.function.Consumer;

final class SkippyGradleUtils {

static void ifBuildSupportsSkippy(Project project, Consumer<SkippyBuildApi> action) {
var sourceSetContainer = project.getExtensions().findByType(SourceSetContainer.class);
if (sourceSetContainer != null) {
var skippyExtension = project.getExtensions().getByType(SkippyPluginExtension.class);
var skippyConfiguration = skippyExtension.toSkippyConfiguration();
var projectDir = project.getProjectDir().toPath();
static void ifBuildSupportsSkippy(CachableProperties settings, Consumer<SkippyBuildApi> action) {
if (settings.sourceSetContainerAvailable) {
var skippyConfiguration = settings.skippyPluginExtension.toSkippyConfiguration();
var projectDir = settings.projectDir;
var skippyBuildApi = new SkippyBuildApi(
skippyConfiguration,
new GradleClassFileCollector(projectDir, sourceSetContainer),
new GradleClassFileCollector(projectDir, settings.classesDirs),
SkippyRepository.getInstance(
skippyConfiguration,
projectDir,
project.getLayout().getBuildDirectory().getAsFile().get().toPath()
settings.buildDir
)
);
action.accept(skippyBuildApi);
Expand Down
40 changes: 36 additions & 4 deletions skippy-gradle/src/main/java/io/skippy/gradle/SkippyPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import io.skippy.core.Profiler;
import org.gradle.api.Project;
import org.gradle.api.tasks.testing.Test;
import org.gradle.api.tasks.testing.TestDescriptor;
import org.gradle.api.tasks.testing.TestListener;
import org.gradle.api.tasks.testing.TestResult;
import org.gradle.testing.jacoco.plugins.JacocoPlugin;

import static io.skippy.gradle.SkippyGradleUtils.*;
Expand All @@ -39,14 +42,43 @@ final class SkippyPlugin implements org.gradle.api.Plugin<Project> {

@Override
public void apply(Project project) {
var testResultServiceProvider = project.getGradle().getSharedServices()
.registerIfAbsent("testResultService", TestResultService.class, spec -> {});
Profiler.clear();
project.getPlugins().apply(JacocoPlugin.class);
project.getExtensions().create("skippy", SkippyPluginExtension.class);
project.getTasks().register("skippyClean", SkippyCleanTask.class);
project.getTasks().register("skippyAnalyze", SkippyAnalyzeTask.class);
project.getTasks().withType(Test.class, testTask -> testTask.finalizedBy("skippyAnalyze"));
project.getTasks().register("skippyClean", SkippyCleanTask.class, task ->
task.getSettings().set(CachableProperties.from(project))
);
project.getTasks().register("skippyAnalyze", SkippyAnalyzeTask.class, task -> {
task.getSettings().set(CachableProperties.from(project));
task.usesService(testResultServiceProvider);
});
project.getTasks().withType(Test.class, testTask -> {
testTask.finalizedBy("skippyAnalyze");
testTask.addTestListener(new TestListener() {
@Override
public void beforeSuite(TestDescriptor testDescriptor) {
}

@Override
public void afterSuite(TestDescriptor testDescriptor, TestResult testResult) {

}

@Override
public void beforeTest(TestDescriptor testDescriptor) {

}

@Override
public void afterTest(TestDescriptor testDescriptor, TestResult testResult) {
testResultServiceProvider.get().afterTest(testDescriptor, testResult);
}
});
});
project.afterEvaluate(action ->
ifBuildSupportsSkippy(action.getProject(), skippyBuildApi -> skippyBuildApi.buildStarted())
ifBuildSupportsSkippy(CachableProperties.from(project), skippyBuildApi -> skippyBuildApi.buildStarted())
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.skippy.gradle;

import org.gradle.api.tasks.testing.TestDescriptor;
import org.gradle.api.tasks.testing.TestListener;
import org.gradle.api.tasks.testing.TestResult;

import java.util.ArrayList;
import java.util.List;

class TestFailedListener implements TestListener {
final List<TestDescriptor> failedTests = new ArrayList<>();

@Override
public void beforeSuite(TestDescriptor testDescriptor) {
}

@Override
public void afterSuite(TestDescriptor testDescriptor, TestResult testResult) {
}

@Override
public void beforeTest(TestDescriptor testDescriptor) {
}

@Override
public void afterTest(TestDescriptor testDescriptor, TestResult testResult) {
if (testResult.getResultType() == TestResult.ResultType.FAILURE) {
failedTests.add(testDescriptor);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.skippy.gradle;

import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import org.gradle.api.tasks.testing.TestDescriptor;
import org.gradle.api.tasks.testing.TestResult;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

abstract class TestResultService implements BuildService<BuildServiceParameters.None> {

final List<TestDescriptor> failedTests = new ArrayList<>();

@Inject
public TestResultService() {
}

void afterTest(TestDescriptor testDescriptor, TestResult testResult) {
if (testResult.getResultType() == TestResult.ResultType.FAILURE) {
failedTests.add(testDescriptor);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -44,10 +46,11 @@ public class GradleClassFileCollectorTest {
void testCollect() throws URISyntaxException {

var sourceSetContainer = mockSourceSetContainer("main", "test");
var classesDirs = sourceSetContainer.stream().flatMap(sourceSet -> sourceSet.getOutput().getClassesDirs().getFiles().stream()).toList();

var projectDir = Paths.get(GradleClassFileCollectorTest.class.getResource("build").toURI()).getParent();

var classFileCollector = new GradleClassFileCollector(projectDir, sourceSetContainer);
var classFileCollector = new GradleClassFileCollector(projectDir, classesDirs);

var classFiles = classFileCollector.collect();

Expand Down Expand Up @@ -115,7 +118,7 @@ private static SourceSetContainer mockSourceSetContainer(String... sourceSetDire
for (int i = 0; i < sourceSets.size(); i++) {
when(sourceSetContainer.getByName(sourceSetDirectories[i])).thenReturn(sourceSets.get(i));
}
when(sourceSetContainer.iterator()).thenReturn(sourceSets.iterator());
when(sourceSetContainer.stream()).thenReturn(sourceSets.stream());
return sourceSetContainer;
}

Expand Down

0 comments on commit f013540

Please sign in to comment.