Skip to content

Make FileSignature machine-independent.  #566

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
    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

  • 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