Skip to content

Make FileSignature machine-independent.  #566

Closed
@nedtwigg

Description

@nedtwigg

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
    public List<FormatterStep> getSteps() {
    return Collections.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.
    */
    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;

  • /**
    * 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
      /** 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 timestampts

  • Worst of all, JarState is used by almost every single FormatterStep, and it has a FileSignature inside it

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions