Skip to content

Commit 236f480

Browse files
authored
Make IDE hook work with configuration cache, take 2 (#2308, revises #2278, fixes #1082)
2 parents 7feb013 + f80e923 commit 236f480

File tree

5 files changed

+135
-37
lines changed

5 files changed

+135
-37
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2024 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless;
17+
18+
import java.util.Locale;
19+
20+
/** These FormatterStep are meant to be used for testing only. */
21+
public class TestingOnly {
22+
public static FormatterStep lowercase() {
23+
return FormatterStep.create("lowercase", "lowercaseStateUnused", unused -> TestingOnly::lowercase);
24+
}
25+
26+
private static String lowercase(String raw) {
27+
return raw.toLowerCase(Locale.ROOT);
28+
}
29+
30+
public static FormatterStep diverge() {
31+
return FormatterStep.create("diverge", "divergeStateUnused", unused -> TestingOnly::diverge);
32+
}
33+
34+
private static String diverge(String raw) {
35+
return raw + " ";
36+
}
37+
}

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/IdeHook.java

+26-5
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,34 @@
1919
import java.io.IOException;
2020
import java.nio.file.Files;
2121

22+
import javax.annotation.Nullable;
23+
24+
import org.gradle.api.Project;
25+
2226
import com.diffplug.common.base.Errors;
2327
import com.diffplug.common.io.ByteStreams;
2428
import com.diffplug.spotless.DirtyState;
2529
import com.diffplug.spotless.Formatter;
30+
import com.diffplug.spotless.NoLambda;
2631

2732
class IdeHook {
33+
static class State extends NoLambda.EqualityBasedOnSerialization {
34+
final @Nullable String path;
35+
final boolean useStdIn;
36+
final boolean useStdOut;
37+
38+
State(Project project) {
39+
path = (String) project.findProperty(PROPERTY);
40+
if (path != null) {
41+
useStdIn = project.hasProperty(USE_STD_IN);
42+
useStdOut = project.hasProperty(USE_STD_OUT);
43+
} else {
44+
useStdIn = false;
45+
useStdOut = false;
46+
}
47+
}
48+
}
49+
2850
final static String PROPERTY = "spotlessIdeHook";
2951
final static String USE_STD_IN = "spotlessIdeHookUseStdIn";
3052
final static String USE_STD_OUT = "spotlessIdeHookUseStdOut";
@@ -33,9 +55,8 @@ private static void dumpIsClean() {
3355
System.err.println("IS CLEAN");
3456
}
3557

36-
static void performHook(SpotlessTaskImpl spotlessTask) {
37-
String path = (String) spotlessTask.getProject().property(PROPERTY);
38-
File file = new File(path);
58+
static void performHook(SpotlessTaskImpl spotlessTask, IdeHook.State state) {
59+
File file = new File(state.path);
3960
if (!file.isAbsolute()) {
4061
System.err.println("Argument passed to " + PROPERTY + " must be an absolute path");
4162
return;
@@ -50,7 +71,7 @@ static void performHook(SpotlessTaskImpl spotlessTask) {
5071
}
5172
}
5273
byte[] bytes;
53-
if (spotlessTask.getProject().hasProperty(USE_STD_IN)) {
74+
if (state.useStdIn) {
5475
bytes = ByteStreams.toByteArray(System.in);
5576
} else {
5677
bytes = Files.readAllBytes(file.toPath());
@@ -63,7 +84,7 @@ static void performHook(SpotlessTaskImpl spotlessTask) {
6384
System.err.println("Run 'spotlessDiagnose' for details https://github.com/diffplug/spotless/blob/main/PADDEDCELL.md");
6485
} else {
6586
System.err.println("IS DIRTY");
66-
if (spotlessTask.getProject().hasProperty(USE_STD_OUT)) {
87+
if (state.useStdOut) {
6788
dirty.writeCanonicalTo(System.out);
6889
} else {
6990
dirty.writeCanonicalTo(file);

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionImpl.java

+6-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2022 DiffPlug
2+
* Copyright 2016-2024 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -48,15 +48,15 @@ public SpotlessExtensionImpl(Project project) {
4848

4949
@Override
5050
protected void createFormatTasks(String name, FormatExtension formatExtension) {
51-
boolean isIdeHook = project.hasProperty(IdeHook.PROPERTY);
51+
IdeHook.State ideHook = new IdeHook.State(project);
5252
TaskContainer tasks = project.getTasks();
5353

5454
// create the SpotlessTask
5555
String taskName = EXTENSION + SpotlessPlugin.capitalize(name);
5656
TaskProvider<SpotlessTaskImpl> spotlessTask = tasks.register(taskName, SpotlessTaskImpl.class, task -> {
5757
task.init(getRegisterDependenciesTask().getTaskService());
5858
task.setGroup(TASK_GROUP);
59-
task.setEnabled(!isIdeHook);
59+
task.getIdeHookState().set(ideHook);
6060
// clean removes the SpotlessCache, so we have to run after clean
6161
task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME);
6262
});
@@ -75,23 +75,18 @@ protected void createFormatTasks(String name, FormatExtension formatExtension) {
7575
TaskProvider<SpotlessApply> applyTask = tasks.register(taskName + APPLY, SpotlessApply.class, task -> {
7676
task.init(spotlessTask.get());
7777
task.setGroup(TASK_GROUP);
78-
task.setEnabled(!isIdeHook);
78+
task.setEnabled(ideHook.path == null);
7979
task.dependsOn(spotlessTask);
8080
});
8181
rootApplyTask.configure(task -> {
82-
task.dependsOn(applyTask);
83-
84-
if (isIdeHook) {
85-
// the rootApplyTask is no longer just a marker task, now it does a bit of work itself
86-
task.doLast(unused -> IdeHook.performHook(spotlessTask.get()));
87-
}
82+
task.dependsOn(ideHook.path == null ? applyTask : spotlessTask);
8883
});
8984

9085
TaskProvider<SpotlessCheck> checkTask = tasks.register(taskName + CHECK, SpotlessCheck.class, task -> {
9186
SpotlessTaskImpl source = spotlessTask.get();
9287
task.setGroup(TASK_GROUP);
9388
task.init(source);
94-
task.setEnabled(!isIdeHook);
89+
task.setEnabled(ideHook.path == null);
9590
task.dependsOn(source);
9691

9792
// if the user runs both, make sure that apply happens first,

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskImpl.java

+13
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@
2929
import org.gradle.api.GradleException;
3030
import org.gradle.api.file.DirectoryProperty;
3131
import org.gradle.api.file.FileSystemOperations;
32+
import org.gradle.api.provider.Property;
3233
import org.gradle.api.provider.Provider;
3334
import org.gradle.api.tasks.CacheableTask;
35+
import org.gradle.api.tasks.Input;
3436
import org.gradle.api.tasks.Internal;
37+
import org.gradle.api.tasks.Optional;
3538
import org.gradle.api.tasks.TaskAction;
3639
import org.gradle.work.ChangeType;
3740
import org.gradle.work.FileChange;
@@ -46,6 +49,10 @@
4649

4750
@CacheableTask
4851
public abstract class SpotlessTaskImpl extends SpotlessTask {
52+
@Input
53+
@Optional
54+
abstract Property<IdeHook.State> getIdeHookState();
55+
4956
@Internal
5057
abstract DirectoryProperty getProjectDir();
5158

@@ -69,6 +76,12 @@ Provider<SpotlessTaskService> getTaskServiceProvider() {
6976

7077
@TaskAction
7178
public void performAction(InputChanges inputs) throws Exception {
79+
IdeHook.State ideHook = getIdeHookState().getOrNull();
80+
if (ideHook != null && ideHook.path != null) {
81+
IdeHook.performHook(this, ideHook);
82+
return;
83+
}
84+
7285
SpotlessTaskService taskService = getTaskService().get();
7386
taskService.registerSourceAlreadyRan(this);
7487
if (target == null) {

plugin-gradle/src/test/java/com/diffplug/gradle/spotless/IdeHookTest.java

+53-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2021 DiffPlug
2+
* Copyright 2016-2024 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,14 +19,19 @@
1919
import java.io.IOException;
2020
import java.io.Writer;
2121
import java.nio.charset.StandardCharsets;
22+
import java.util.stream.Stream;
2223

2324
import org.assertj.core.api.Assertions;
25+
import org.gradle.testkit.runner.GradleRunner;
2426
import org.junit.jupiter.api.BeforeEach;
25-
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.api.TestInstance;
28+
import org.junit.jupiter.params.ParameterizedTest;
29+
import org.junit.jupiter.params.provider.MethodSource;
2630

2731
import com.diffplug.common.base.StringPrinter;
2832
import com.diffplug.common.io.Files;
2933

34+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
3035
class IdeHookTest extends GradleIntegrationHarness {
3136
private String output, error;
3237
private File dirty, clean, diverge, outofbounds;
@@ -40,11 +45,11 @@ void before() throws IOException {
4045
"spotless {",
4146
" format 'misc', {",
4247
" target 'DIRTY.md', 'CLEAN.md'",
43-
" custom 'lowercase', { str -> str.toLowerCase(Locale.ROOT) }",
48+
" addStep com.diffplug.spotless.TestingOnly.lowercase()",
4449
" }",
4550
" format 'diverge', {",
4651
" target 'DIVERGE.md'",
47-
" custom 'diverge', { str -> str + ' ' }",
52+
" addStep com.diffplug.spotless.TestingOnly.diverge()",
4853
" }",
4954
"}");
5055
dirty = new File(rootFolder(), "DIRTY.md");
@@ -57,12 +62,16 @@ void before() throws IOException {
5762
Files.write("ABC".getBytes(StandardCharsets.UTF_8), outofbounds);
5863
}
5964

60-
private void runWith(String... arguments) throws IOException {
65+
private static Stream<Boolean> configurationCacheProvider() {
66+
return Stream.of(false, true);
67+
}
68+
69+
private void runWith(boolean configurationCache, String... arguments) throws IOException {
6170
StringBuilder output = new StringBuilder();
6271
StringBuilder error = new StringBuilder();
6372
try (Writer outputWriter = new StringPrinter(output::append).toWriter();
6473
Writer errorWriter = new StringPrinter(error::append).toWriter();) {
65-
gradleRunner()
74+
gradleRunner(configurationCache)
6675
.withArguments(arguments)
6776
.forwardStdOutput(outputWriter)
6877
.forwardStdError(errorWriter)
@@ -72,37 +81,60 @@ private void runWith(String... arguments) throws IOException {
7281
this.error = error.toString();
7382
}
7483

75-
@Test
76-
void dirty() throws IOException {
77-
runWith("spotlessApply", "--quiet", "-PspotlessIdeHook=" + dirty.getAbsolutePath(), "-PspotlessIdeHookUseStdOut");
84+
protected GradleRunner gradleRunner(boolean configurationCache) throws IOException {
85+
if (configurationCache) {
86+
setFile("gradle.properties").toContent("org.gradle.unsafe.configuration-cache=true");
87+
setFile("settings.gradle").toContent("enableFeaturePreview(\"STABLE_CONFIGURATION_CACHE\")");
88+
return super.gradleRunner().withGradleVersion(GradleVersionSupport.STABLE_CONFIGURATION_CACHE.version);
89+
} else {
90+
File gradleProps = new File(rootFolder(), "gradle.properties");
91+
if (gradleProps.exists()) {
92+
gradleProps.delete();
93+
}
94+
File settingsGradle = new File(rootFolder(), "settings.gradle");
95+
if (settingsGradle.exists()) {
96+
settingsGradle.delete();
97+
}
98+
return super.gradleRunner();
99+
}
100+
}
101+
102+
@ParameterizedTest
103+
@MethodSource("configurationCacheProvider")
104+
void dirty(boolean configurationCache) throws IOException {
105+
runWith(configurationCache, "spotlessApply", "--quiet", "-PspotlessIdeHook=" + dirty.getAbsolutePath(), "-PspotlessIdeHookUseStdOut");
78106
Assertions.assertThat(output).isEqualTo("abc");
79107
Assertions.assertThat(error).startsWith("IS DIRTY");
80108
}
81109

82-
@Test
83-
void clean() throws IOException {
84-
runWith("spotlessApply", "--quiet", "-PspotlessIdeHook=" + clean.getAbsolutePath(), "-PspotlessIdeHookUseStdOut");
110+
@ParameterizedTest
111+
@MethodSource("configurationCacheProvider")
112+
void clean(boolean configurationCache) throws IOException {
113+
runWith(configurationCache, "spotlessApply", "--quiet", "-PspotlessIdeHook=" + clean.getAbsolutePath(), "-PspotlessIdeHookUseStdOut");
85114
Assertions.assertThat(output).isEmpty();
86115
Assertions.assertThat(error).startsWith("IS CLEAN");
87116
}
88117

89-
@Test
90-
void diverge() throws IOException {
91-
runWith("spotlessApply", "--quiet", "-PspotlessIdeHook=" + diverge.getAbsolutePath(), "-PspotlessIdeHookUseStdOut");
118+
@ParameterizedTest
119+
@MethodSource("configurationCacheProvider")
120+
void diverge(boolean configurationCache) throws IOException {
121+
runWith(configurationCache, "spotlessApply", "--quiet", "-PspotlessIdeHook=" + diverge.getAbsolutePath(), "-PspotlessIdeHookUseStdOut");
92122
Assertions.assertThat(output).isEmpty();
93123
Assertions.assertThat(error).startsWith("DID NOT CONVERGE");
94124
}
95125

96-
@Test
97-
void outofbounds() throws IOException {
98-
runWith("spotlessApply", "--quiet", "-PspotlessIdeHook=" + outofbounds.getAbsolutePath(), "-PspotlessIdeHookUseStdOut");
126+
@ParameterizedTest
127+
@MethodSource("configurationCacheProvider")
128+
void outofbounds(boolean configurationCache) throws IOException {
129+
runWith(configurationCache, "spotlessApply", "--quiet", "-PspotlessIdeHook=" + outofbounds.getAbsolutePath(), "-PspotlessIdeHookUseStdOut");
99130
Assertions.assertThat(output).isEmpty();
100131
Assertions.assertThat(error).isEmpty();
101132
}
102133

103-
@Test
104-
void notAbsolute() throws IOException {
105-
runWith("spotlessApply", "--quiet", "-PspotlessIdeHook=build.gradle", "-PspotlessIdeHookUseStdOut");
134+
@ParameterizedTest
135+
@MethodSource("configurationCacheProvider")
136+
void notAbsolute(boolean configurationCache) throws IOException {
137+
runWith(configurationCache, "spotlessApply", "--quiet", "-PspotlessIdeHook=build.gradle", "-PspotlessIdeHookUseStdOut");
106138
Assertions.assertThat(output).isEmpty();
107139
Assertions.assertThat(error).contains("Argument passed to spotlessIdeHook must be an absolute path");
108140
}

0 commit comments

Comments
 (0)