Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Gradle tasks cacheable #295

Merged
merged 9 commits into from
Nov 15, 2023
2 changes: 1 addition & 1 deletion .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: Build Gradle Plugin
run: cd jte-gradle-plugin && ./gradlew publishToMavenLocal
- name: Run all the Gradle Plugin tests
run: cd test/gradle-test-wrapper && ./gradlew check
run: cd test/gradle-test-wrapper && ./gradlew --info check
marcospereira marked this conversation as resolved.
Show resolved Hide resolved


coverage:
Expand Down
2 changes: 1 addition & 1 deletion jte-gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ plugins {
}

repositories {
mavenCentral()
mavenLocal()
mavenCentral()
marcospereira marked this conversation as resolved.
Show resolved Hide resolved
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gg.jte.gradle;

import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.TaskAction;
Expand All @@ -10,6 +11,7 @@
import javax.inject.Inject;
import java.nio.file.Path;

@CacheableTask
public abstract class GenerateJteTask extends JteTaskBase {

private final WorkerExecutor workerExecutor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import gg.jte.ContentType;
import org.gradle.api.DefaultTask;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;

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

public abstract class JteTaskBase extends DefaultTask {
Expand All @@ -30,6 +30,7 @@ protected void setterCalled() {
}

@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
public Path getSourceDirectory() {
return extension.getSourceDirectory().get();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@CacheableTask
public class PrecompileJteTask extends JteTaskBase {

@Inject
Expand All @@ -27,6 +28,7 @@ public PrecompileJteTask(JteExtension extension)
}

@InputFiles
@CompileClasspath
public FileCollection getCompilePath() {
return extension.getCompilePath();
}
Expand Down
3 changes: 2 additions & 1 deletion test/gradle-test-wrapper/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ plugins {
}

repositories {
mavenLocal()
mavenCentral()
}

Expand All @@ -15,4 +16,4 @@ dependencies {
tasks.withType(Test).configureEach {
useJUnitPlatform()
systemProperty("gradle.matrix.versions", System.getProperty("gradle.matrix.versions", "DEFAULT"))
}
}
111 changes: 106 additions & 5 deletions test/gradle-test-wrapper/src/test/java/gg/jte/GradleMatrixTest.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package gg.jte;

import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.BuildTask;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
Expand All @@ -20,17 +26,24 @@ public class GradleMatrixTest {
public static final List<String> GRADLE_VERSIONS = getTestGradleVersions();
public static final String DEFAULT = "DEFAULT";

// Follows Gradle recommendation:
// https://docs.gradle.org/current/userguide/test_kit.html#sub:test-kit-build-cache
// Basically, it uses a temporary directory to avoid test runs to polute the build
// cache and interfer with each other.
@TempDir
public Path temporaryBuildCacheDir;

/**
* Use system property "gradle.matrix.versions" to test multiple versions. Note this may result in downloading those
* versions if they are not already present.
* @return
*/
private static List<String> getTestGradleVersions() {
String versionProperty = System.getProperty("gradle.matrix.versions", DEFAULT);
return Arrays.asList(versionProperty.split("[,\\s]+"));
}

public static final String TASK_NAME = ":check";

public static Stream<Arguments> runGradleBuild() throws IOException {
return Files.find(Paths.get(".."), 2, (p, attr) -> p.getFileName().toString().startsWith("settings.gradle"))
.map(Path::getParent)
Expand All @@ -40,18 +53,106 @@ public static Stream<Arguments> runGradleBuild() throws IOException {

@ParameterizedTest
@MethodSource
public void runGradleBuild(Path projectDir, String gradleVersion) {
public void runGradleBuild(Path projectDir, String gradleVersion) throws IOException {
// Clean the configuration cache. Makes it easier to run the tests locally
// multiple times in a predictable way. For reference, see:
// https://docs.gradle.org/current/userguide/configuration_cache.html#config_cache:usage:invalidate
deleteDir(projectDir.resolve(".gradle/configuration-cache"));

// Then run the task
BuildResult result = runner(projectDir, gradleVersion, TASK_NAME).build();

// Check task was successful
BuildTask task = result.task(TASK_NAME);
Assertions.assertNotNull(task, "Build result must have a task " + TASK_NAME);
Assertions.assertNotEquals(
TaskOutcome.FAILED,
task.getOutcome(),
String.format("Build failed in %s with Gradle Version %s", projectDir, gradleVersion)
);
}

public static Stream<Arguments> checkBuildCache() {
return Stream
.of(Paths.get("../jte-runtime-cp-test-models-gradle"), Paths.get("../jte-runtime-test-gradle-convention"))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what about the other test projects?

gg.jte.GradleMatrixTest#runGradleBuild()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are only interested in testing that the tasks are cacheable and not checking every project (done in the other above).

.flatMap(p -> GRADLE_VERSIONS.stream().map(v -> Arguments.arguments(p, v)));
}

@ParameterizedTest
@MethodSource
public void checkBuildCache(Path projectDir, String gradleVersion) throws IOException {
// Start the test with a clean slate. This ensures that the task outcome
// won't be something as `TaskOutcome.UP_TO_DATE`.
deleteDir(projectDir.resolve("build"));

// First run to populate the cache
runner(projectDir, gradleVersion, "--build-cache", TASK_NAME).build();

// Delete the build directory so that the next run uses
// will need to use the build cache.
deleteDir(projectDir.resolve("build"));

// The second run must use the build cache.
BuildResult result = runner(projectDir, gradleVersion, "--build-cache", TASK_NAME).build();

BuildTask mainTask = result.task(TASK_NAME);
Assertions.assertNotNull(mainTask, String.format("A task named %s must be part of the build result", TASK_NAME));
Assertions.assertNotEquals(
TaskOutcome.FAILED,
mainTask.getOutcome(),
String.format("Build failed in %s with Gradle Version %s", projectDir, gradleVersion)
);

// `generateJte` and `precompileJte` are the cacheable tasks we want to test.
List<BuildTask> tasks = Stream.of(":generateJte", ":precompileJte")
.map(result::task)
// When `generate` is executed, `precompile` is skipped, and vice versa, then we
// filter out the skipped one here.
.filter(task -> task != null && task.getOutcome() != TaskOutcome.SKIPPED)
.toList();

Assertions.assertFalse(tasks.isEmpty(), "At least one of :generateJte or :precompileJte tasks should be present");
tasks.forEach(task -> Assertions.assertEquals(
TaskOutcome.FROM_CACHE,
task.getOutcome(),
String.format("Expected outcome for task %s was %s, but got %s. Build output: \n %s", task.getPath(), TaskOutcome.FROM_CACHE, task.getOutcome(), result.getOutput())
));
}

private GradleRunner runner(Path projectDir, String gradleVersion, String ... extraArgs) {
List<String> arguments = new ArrayList<>(Arrays.asList(extraArgs));
arguments.addAll(
List.of(
"--configuration-cache",
"-Dtest.build.cache.dir=" + temporaryBuildCacheDir.toUri()
)
);

GradleRunner runner = GradleRunner.create()
.withProjectDir(projectDir.toFile())
.withTestKitDir(Paths.get("build").resolve(projectDir.getFileName()).toAbsolutePath().toFile())
.withArguments("--configuration-cache", TASK_NAME);
.withArguments(arguments);

if (!DEFAULT.equals(gradleVersion)) {
runner = runner.withGradleVersion(gradleVersion);
}
return runner;
}

BuildResult result = runner.build();
private void deleteDir(Path directoryToDelete) throws IOException {
if (Files.notExists(directoryToDelete)) return;
Files.walkFileTree(directoryToDelete, new SimpleFileVisitor<>() {
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}

Assertions.assertNotEquals(TaskOutcome.FAILED, result.task(TASK_NAME).getOutcome(), String.format("Build failed in %s with Gradle Version %s", projectDir, gradleVersion));
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
});
}
}
8 changes: 7 additions & 1 deletion test/jte-runtime-cp-test-models-gradle/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
}
}
}

buildCache {
local {
directory "${System.getProperty("test.build.cache.dir")}"
}
}
8 changes: 7 additions & 1 deletion test/jte-runtime-test-gradle-convention/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ pluginManagement {
mavenLocal()
gradlePluginPortal()
}
}
}

buildCache {
local {
directory "${System.getProperty("test.build.cache.dir")}"
}
}