-
Notifications
You must be signed in to change notification settings - Fork 486
Description
This is a prerequisite to making the gradle build cache useful across machines (#280). Here is why:
-
spotless formats by applying a series of steps - every step is capable of serializing its entire state, including config files, for the purpose of up-to-date checks
-
spotless/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java
Lines 143 to 146 in eb783cd
@Input public List<FormatterStep> getSteps() { return Collections.unmodifiableList(steps); } -
spotless/lib/src/main/java/com/diffplug/spotless/FormatterStep.java
Lines 58 to 72 in eb783cd
/** * Implements a FormatterStep in a strict way which guarantees correct and lazy implementation * of up-to-date checks. This maximizes performance for cases where the FormatterStep is not * actually needed (e.g. don't load eclipse setting file unless this step is actually running) * while also ensuring that gradle can detect changes in a step's settings to determine that * it needs to rerun a format. */ abstract class Strict<State extends Serializable> extends LazyForwardingEquality<State> implements FormatterStep { private static final long serialVersionUID = 1L; /** * Implements the formatting function strictly in terms * of the input data and the result of {@link #calculateState()}. */ protected abstract String format(State state, String rawUnix, File file) throws Exception; -
spotless/lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java
Lines 41 to 75 in eb783cd
/** * This function is guaranteed to be called at most once. * If the state is never required, then it will never be called at all. * * Throws exception because it's likely that there will be some IO going on. */ protected abstract T calculateState() throws Exception; /** Returns the underlying state, possibly triggering a call to {{@link #calculateState()}. */ protected final T state() { // double-checked locking for lazy evaluation of calculateState if (state == null) { synchronized (this) { if (state == null) { try { state = calculateState(); } catch (Exception e) { throw ThrowingEx.asRuntime(e); } } } } return state; // will always be nonnull at this point } // override serialize output private void writeObject(ObjectOutputStream out) throws IOException { out.writeObject(state()); } // override serialize input @SuppressWarnings("unchecked") private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { state = (T) Objects.requireNonNull(in.readObject()); } -
so far so good, this means that most steps will work with the gradle build cache. The problem is for some formatters that have config files.
- Some formatters capture the state of these config files by loading their content into a String, these will relocate okay
- e.g.
LicenseHeaderStep
spotless/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java
Lines 142 to 145 in eb783cd
/** Reads the license file from the given file. */ private LicenseHeaderStep(File licenseFile, Charset encoding, String delimiter, String yearSeparator) throws IOException { this(new String(Files.readAllBytes(licenseFile.toPath()), encoding), delimiter, yearSeparator); } - But most of them use
FileSignature
, which will not relocate. - e.g.
ScalaFmtStep
static final class State implements Serializable { private static final long serialVersionUID = 1L; final JarState jarState; final FileSignature configSignature; State(String version, Provisioner provisioner, @Nullable File configFile) throws IOException { String mavenCoordinate; Matcher versionMatcher = VERSION_PRE_2_0.matcher(version); if (versionMatcher.matches()) { mavenCoordinate = MAVEN_COORDINATE_PRE_2_0; } else { mavenCoordinate = MAVEN_COORDINATE; } this.jarState = JarState.from(mavenCoordinate + version, provisioner); this.configSignature = FileSignature.signAsList(configFile == null ? Collections.emptySet() : Collections.singleton(configFile)); }
-
FileSignature
uses filesystem absolute paths and lastModified timestamptsspotless/lib/src/main/java/com/diffplug/spotless/FileSignature.java
Lines 43 to 45 in eb783cd
private final String[] filenames; private final long[] filesizes; private final long[] lastModified;
-
Worst of all,
JarState
is used by almost every single FormatterStep, and it has aFileSignature
inside itspotless/lib/src/main/java/com/diffplug/spotless/JarState.java
Lines 42 to 47 in eb783cd
public final class JarState implements Serializable { private static final long serialVersionUID = 1L; private final Set<String> mavenCoordinates; @SuppressWarnings("unused") private final FileSignature fileSignature;