Skip to content

Commit e7a879d

Browse files
committed
First shot at fixing the configuration cache / remote build cache conflict.
1 parent 402f5d9 commit e7a879d

File tree

4 files changed

+121
-12
lines changed

4 files changed

+121
-12
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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.io.IOException;
19+
import java.util.AbstractList;
20+
import java.util.ArrayList;
21+
import java.util.Collection;
22+
23+
/**
24+
* Gradle requires three things:
25+
* - Gradle defines cache equality based on your serialized representation
26+
* - Combined with remote build cache, you cannot have any absolute paths in your serialized representation
27+
* - Combined with configuration cache, you must be able to roundtrip yourself through serialization
28+
*
29+
* These requirements are at odds with each other, as described in these issues
30+
* - Gradle issue to define custom equality https://github.com/gradle/gradle/issues/29816
31+
* - Spotless plea for developer cache instead of configuration cache https://github.com/diffplug/spotless/issues/987
32+
* - Spotless cache miss bug fixed by this class https://github.com/diffplug/spotless/issues/2168
33+
*
34+
* The point of this class is to create containers which can optimize the serialized representation for either
35+
* - roundtrip integrity
36+
* - equality
37+
*
38+
* It is a horrific hack, but it works, and it's the only way I can figure
39+
* to make Spotless work with all of Gradle's cache systems.
40+
*/
41+
public class ConfigurationCacheHack {
42+
static boolean SERIALIZE_FOR_ROUNDTRIP = false;
43+
44+
public enum OptimizeFor {
45+
ROUNDTRIP, EQUALITY,
46+
}
47+
48+
public static class StepList extends AbstractList<FormatterStep> {
49+
private final boolean optimizeForEquality;
50+
private transient ArrayList<FormatterStep> backingList = new ArrayList<>();
51+
52+
public StepList(OptimizeFor optimizeFor) {
53+
this.optimizeForEquality = optimizeFor == OptimizeFor.EQUALITY;
54+
}
55+
56+
@Override
57+
public void clear() {
58+
backingList.clear();
59+
}
60+
61+
@Override
62+
public boolean addAll(Collection<? extends FormatterStep> c) {
63+
return backingList.addAll(c);
64+
}
65+
66+
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
67+
out.defaultWriteObject();
68+
SERIALIZE_FOR_ROUNDTRIP = !optimizeForEquality;
69+
out.writeObject(backingList);
70+
}
71+
72+
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
73+
in.defaultReadObject();
74+
backingList = (ArrayList<FormatterStep>) in.readObject();
75+
}
76+
77+
@Override
78+
public FormatterStep get(int index) {
79+
return backingList.get(index);
80+
}
81+
82+
@Override
83+
public int size() {
84+
return backingList.size();
85+
}
86+
}
87+
}

lib/src/main/java/com/diffplug/spotless/Formatter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535

3636
import javax.annotation.Nullable;
3737

38+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
39+
3840
/** Formatter which performs the full formatting. */
3941
public final class Formatter implements Serializable, AutoCloseable {
4042
private static final long serialVersionUID = 1L;
@@ -57,11 +59,13 @@ private Formatter(String name, LineEnding.Policy lineEndingsPolicy, Charset enco
5759
}
5860

5961
// override serialize output
62+
@SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
6063
private void writeObject(ObjectOutputStream out) throws IOException {
6164
out.writeObject(name);
6265
out.writeObject(lineEndingsPolicy);
6366
out.writeObject(encoding.name());
6467
out.writeObject(rootDir.toString());
68+
ConfigurationCacheHack.SERIALIZE_FOR_ROUNDTRIP = true;
6569
out.writeObject(steps);
6670
out.writeObject(exceptionPolicy);
6771
}

lib/src/main/java/com/diffplug/spotless/FormatterStepSerializationRoundtrip.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class FormatterStepSerializationRoundtrip<RoundtripState extends Serializable, E
2626
private final String name;
2727
private final transient ThrowingEx.Supplier<RoundtripState> initializer;
2828
private @Nullable RoundtripState roundtripStateInternal;
29+
private @Nullable EqualityState equalityStateInternal;
2930
private final SerializedFunction<RoundtripState, EqualityState> equalityStateExtractor;
3031
private final SerializedFunction<EqualityState, FormatterFunc> equalityStateToFormatter;
3132

@@ -43,10 +44,13 @@ public String getName() {
4344

4445
@Override
4546
protected EqualityState stateSupplier() throws Exception {
46-
if (roundtripStateInternal == null) {
47-
roundtripStateInternal = initializer.get();
47+
if (equalityStateInternal == null) {
48+
if (roundtripStateInternal == null) {
49+
roundtripStateInternal = initializer.get();
50+
}
51+
equalityStateInternal = equalityStateExtractor.apply(roundtripStateInternal);
4852
}
49-
return equalityStateExtractor.apply(roundtripStateInternal);
53+
return equalityStateInternal;
5054
}
5155

5256
@Override
@@ -56,8 +60,14 @@ protected FormatterFunc stateToFormatter(EqualityState equalityState) throws Exc
5660

5761
// override serialize output
5862
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
59-
if (roundtripStateInternal == null) {
60-
roundtripStateInternal = ThrowingEx.get(initializer::get);
63+
if (ConfigurationCacheHack.SERIALIZE_FOR_ROUNDTRIP) {
64+
if (roundtripStateInternal == null) {
65+
roundtripStateInternal = ThrowingEx.get(initializer::get);
66+
}
67+
equalityStateInternal = null;
68+
} else {
69+
equalityStateInternal = ThrowingEx.get(this::stateSupplier);
70+
roundtripStateInternal = null;
6171
}
6272
out.defaultWriteObject();
6373
}

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import java.io.File;
1919
import java.nio.charset.Charset;
20-
import java.util.ArrayList;
2120
import java.util.Collections;
2221
import java.util.List;
2322
import java.util.Locale;
@@ -37,6 +36,7 @@
3736
import org.gradle.api.tasks.PathSensitivity;
3837
import org.gradle.work.Incremental;
3938

39+
import com.diffplug.spotless.ConfigurationCacheHack;
4040
import com.diffplug.spotless.FormatExceptionPolicy;
4141
import com.diffplug.spotless.FormatExceptionPolicyStrict;
4242
import com.diffplug.spotless.Formatter;
@@ -150,17 +150,25 @@ public File getOutputDirectory() {
150150
return outputDirectory;
151151
}
152152

153-
protected final List<FormatterStep> steps = new ArrayList<>();
153+
private final List<FormatterStep> stepsInternalRoundtrip = new ConfigurationCacheHack.StepList(ConfigurationCacheHack.OptimizeFor.ROUNDTRIP);
154+
private final List<FormatterStep> stepsInternalEquality = new ConfigurationCacheHack.StepList(ConfigurationCacheHack.OptimizeFor.EQUALITY);
155+
156+
@Internal
157+
public List<FormatterStep> getStepsInternalRoundtrip() {
158+
return Collections.unmodifiableList(stepsInternalRoundtrip);
159+
}
154160

155161
@Input
156-
public List<FormatterStep> getSteps() {
157-
return Collections.unmodifiableList(steps);
162+
public List<FormatterStep> getStepsInternalEquality() {
163+
return Collections.unmodifiableList(stepsInternalEquality);
158164
}
159165

160166
public void setSteps(List<FormatterStep> steps) {
161167
PluginGradlePreconditions.requireElementsNonNull(steps);
162-
this.steps.clear();
163-
this.steps.addAll(steps);
168+
this.stepsInternalRoundtrip.clear();
169+
this.stepsInternalEquality.clear();
170+
this.stepsInternalRoundtrip.addAll(steps);
171+
this.stepsInternalEquality.addAll(steps);
164172
}
165173

166174
/** Returns the name of this format. */
@@ -179,7 +187,7 @@ Formatter buildFormatter() {
179187
.lineEndingsPolicy(getLineEndingsPolicy().get())
180188
.encoding(Charset.forName(encoding))
181189
.rootDir(getProjectDir().get().getAsFile().toPath())
182-
.steps(steps)
190+
.steps(stepsInternalRoundtrip)
183191
.exceptionPolicy(exceptionPolicy)
184192
.build();
185193
}

0 commit comments

Comments
 (0)