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
| @Input |
| publicList<FormatterStep> getSteps() { |
| returnCollections.unmodifiableList(steps); |
| } |
| /** |
| * 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. |
| */ |
| abstractclassStrict<StateextendsSerializable> extendsLazyForwardingEquality<State> implementsFormatterStep { |
| privatestaticfinallongserialVersionUID = 1L; |
| |
| /** |
| * Implements the formatting function strictly in terms |
| * of the input data and the result of {@link #calculateState()}. |
| */ |
| protectedabstractStringformat(Statestate, StringrawUnix, Filefile) throwsException; |
| /** |
| * 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. |
| */ |
| protectedabstractTcalculateState() throwsException; |
| |
| /** Returns the underlying state, possibly triggering a call to {{@link #calculateState()}. */ |
| protectedfinalTstate() { |
| // double-checked locking for lazy evaluation of calculateState |
| if (state == null) { |
| synchronized (this) { |
| if (state == null) { |
| try { |
| state = calculateState(); |
| } catch (Exceptione) { |
| throwThrowingEx.asRuntime(e); |
| } |
| } |
| } |
| } |
| returnstate; // will always be nonnull at this point |
| } |
| |
| // override serialize output |
| privatevoidwriteObject(ObjectOutputStreamout) throwsIOException { |
| out.writeObject(state()); |
| } |
| |
| // override serialize input |
| @SuppressWarnings("unchecked") |
| privatevoidreadObject(ObjectInputStreamin) throwsIOException, 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 | /** Reads the license file from the given file. */ |
| privateLicenseHeaderStep(FilelicenseFile, Charsetencoding, Stringdelimiter, StringyearSeparator) throwsIOException { |
| this(newString(Files.readAllBytes(licenseFile.toPath()), encoding), delimiter, yearSeparator); |
| } |
- But most of them use
FileSignature, which will not relocate. - e.g.
ScalaFmtStep | staticfinalclassStateimplementsSerializable { |
| privatestaticfinallongserialVersionUID = 1L; |
| |
| finalJarStatejarState; |
| finalFileSignatureconfigSignature; |
| |
| State(Stringversion, Provisionerprovisioner, @NullableFileconfigFile) throwsIOException { |
| StringmavenCoordinate; |
| MatcherversionMatcher = 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 timestampts
| privatefinalString[] filenames; |
| privatefinallong[] filesizes; |
| privatefinallong[] lastModified; |
Worst of all, JarState is used by almost every single FormatterStep, and it has a FileSignature inside it
| publicfinalclassJarStateimplementsSerializable { |
| privatestaticfinallongserialVersionUID = 1L; |
| |
| privatefinalSet<String> mavenCoordinates; |
| @SuppressWarnings("unused") |
| privatefinalFileSignaturefileSignature; |
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
spotless/lib/src/main/java/com/diffplug/spotless/FormatterStep.java
Lines 58 to 72 in eb783cd
spotless/lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java
Lines 41 to 75 in eb783cd
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.
LicenseHeaderStepspotless/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java
Lines 142 to 145 in eb783cd
FileSignature, which will not relocate.ScalaFmtStepspotless/lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java
Lines 64 to 81 in eb783cd
FileSignatureuses filesystem absolute paths and lastModified timestamptsspotless/lib/src/main/java/com/diffplug/spotless/FileSignature.java
Lines 43 to 45 in eb783cd
Worst of all,
JarStateis used by almost every single FormatterStep, and it has aFileSignatureinside itspotless/lib/src/main/java/com/diffplug/spotless/JarState.java
Lines 42 to 47 in eb783cd