diffplug / spotless

Keep your code spotless
Apache License 2.0
4.57k stars 458 forks source link

Declare inputs and outputs to support up-to-date checking #31

Closed oehme closed 7 years ago

oehme commented 8 years ago

The spotless check task currently doesn't have any inputs and outputs declared and is thus never considered up-to-date. This is slowing down user's builds unnecessarily.

nedtwigg commented 8 years ago

Implemented incremental .gitattributes. Biggest need is to test incremental behavior, second is to finish out these checkboxes. I force pushed over your commit-revert pair.

jbduncan commented 8 years ago

Great, thanks @nedtwigg. I realise that those two commits were a blemish in an otherwise clean commit history, so thanks for getting rid of them for me.

jbduncan commented 8 years ago

@nedtwigg, how are things going on your end with testing the incremental inputs? Is there anything you need help with to move forward?

nedtwigg commented 8 years ago

@jbduncan Sorry, didn't mean to imply that I was working on testing. Only that testing was the project's greatest need. We've done a bunch of work, does any of it work? Is it faster?

Spotless is the fastest step in most of my builds, so this has always been at the bottom of my priority list. Anytime someone wants to contribute to a project I'm maintaining, enabling them becomes the top of my list. So I don't care a bunch whether this gets finished, but I do care about helping you or anyone else who wants this enough to build it.

In Spotless 2.x, every step needed to test that it applied formatting correctly. In Spotless 3.x, every step also needs to test it recognizes changes in its input properties. I don't know anything about testing Gradle's incremental build, so I see that as out of my wheelhouse, and into the wheelhouse of whoever wants this. Whenever there's a problem with Spotless and its guts (e.g. the .gitattributes nastiness) I'm happy to hop in again and help :)

jbduncan commented 8 years ago

Ah okay, I understand now what you meant. :)

Testing incremental behaviour (which I presume would involve microbenchmarking in some way) is something I'm also unfamiliar with. I've had a look at Chapter 39.4 of the Gradle user guide, but it doesn't seem to say anything on how testing should be done.

@oehme Do you have any advice on how I could test the correctness and performance of the parts of the code base we've "incrementalised" so far?

nedtwigg commented 8 years ago

I think @oehme mentioned this a little:

You can use TestKit to verify that your tasks are executed/skipped as expected.

The integration tests (which are already in the test suite) return a BuildResult where you can query the TaskOutcome. So that's how you can test the correctness.

For performance, I don't think we necessarily need benchmarks. I'd just run 2.x on a build a few times, then run 3.0.0-SNAPSHOT a few times, using the --profile flag to do the measuring. Basically, use this in a production-scenario, and roughly quantify how it's helping.

jbduncan commented 7 years ago

@nedtwigg Thank you very much for you advice. I've just pushed a new commit at https://github.com/diffplug/spotless/commit/5e568df64e2bf1203e808c1bf0dddd4e234d2ca3 which contains a bunch of new incremental-related test improvements, and I wonder if you'd have the time and willingness to review it at some point for me in the near future?

Regarding performance, I did some 'benchmarks' on my Windows laptop on both master and 3.x, running ./gradlew clean and then running ./gradlew build --profile 5 times on each of the two branches, which gave me the following results.

3.x (3.x 93354082f9e3e00984d7db3a72847c666e5306fc):
0. ./gradlew clean
1. ./gradlew build - 1 mins 36.233 secs (:spotlessCheck: 4.778 secs)
2. ./gradlew build - 12.302 secs (:spotlessCheck: 4.507 secs)
3. ./gradlew build - 12.34 secs (:spotlessCheck: 4.27 secs)
4. ./gradlew build - 12.294 secs (:spotlessCheck: 4.288 secs)
5. ./gradlew build - 12.33 secs (:spotlessCheck: 4.423 secs)

2.x (master b8fa86a51da32f294f8080b3042f40d9ab5dcfcd):
0. ./gradlew clean
1. ./gradlew build - 2 mins 13.168 secs (:spotlessCheck: 9.11 secs)
2. ./gradlew build - 15.599 secs (:spotlessCheck: 7.633 secs)
3. ./gradlew build - 15.599 secs (:spotlessCheck: 7.44 secs)
4. ./gradlew build - 15.258 secs (:spotlessCheck: 7.211 secs)
5. ./gradlew build - 15.349 secs (:spotlessCheck: 7.417 secs)

The time improvements seem very promising considering we've only incrementalised part of the code base (~3 seconds faster :spotlessCheck execution on 3.x).

I don't really know why the (1.) running times for 2.x and 3.x were so different (~1 mins 36 secs vs. ~2 mins 13 secs), but I think it was due to how both times the build script needed to download stuff from the Eclipse P2 repo, and it coincidentally took longer for it to download everything when I was testing 2.x (compared to 3.x).

nedtwigg commented 7 years ago

@jbduncan The new commit is a good start! I did some refactoring in ce2ca9cfb91efa3672bd87a49ffdf3825e9a441f, and then used that refactoring to point out a bug in b35c1ab48e7fbe69d53849f4a21606f0ba9abd81, which I tried and failed to figure out in 04751a855de284ff61ebfe6980206d2a0f113365.

@oehme We're having trouble with up-to-date-checking. Here's what's going on to save you from digging around:

If the build.gradle changes from this:

spotless { java {
    googleJavaFormat('1.1')
} }

to this:

spotless { java {
    googleJavaFormat('1.0')
} }

then the check task needs to rerun. This shows that it is not happening.

This shows that the FormatterStep themselves have the proper equality semantics, and this shows that a List<FormatterStep> has the proper equality semantics.

CheckFormatTask is the task in question, and BaseFormatTask is its parent class which actually has @Input public List<FormatterStep> steps = new ArrayList<>(); Does inheritance of annotated input properties not work? Are we doing something else wrong?

oehme commented 7 years ago

@jbduncan the tests you ran don't show the improvement you'd expect from an incremental build. I'm pretty sure you're just seeing a more efficient implementation of spotless itself.

If the task was up-to-date you'd see it marked as such on the command line and it would take a few ms, not several seconds.

jbduncan commented 7 years ago

Hi @oehme, I didn't actually expect my tests to show the full improvement in speed expected from a proper incremental build. This is because @nedtwigg and I haven't "incrementalised" all of the relevant parts of the Spotless code base yet - we still have to apply the required Serializable and equals()/hashCode() semantics to the following files:

I'd therefore argue that the results so far are actually really promising, as the timings are going in the right direction (down from ~7s to ~4s, with the millisecond range being our ultimate goal). :)

Here's an example of what I get from the terminal when I run ./gradlew build -x :test on the current SNAPSHOT of 3.x.

$ ./gradlew build -x :test
:compileJava UP-TO-DATE
:pluginDescriptors UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:publishPluginJar UP-TO-DATE
:javadoc UP-TO-DATE
:publishPluginJavaDocsJar UP-TO-DATE
:assemble UP-TO-DATE
:findbugsMain UP-TO-DATE
:pluginUnderTestMetadata UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:spotlessCheck
:spotlessFreshmarkCheck UP-TO-DATE
:spotlessJavaCheck UP-TO-DATE
:spotlessMiscCheck UP-TO-DATE
:spotlessCheck UP-TO-DATE

BUILD SUCCESSFUL

Total time: 4.567 secs
:check
:build

BUILD SUCCESSFUL

Total time: 15.061 secs
oehme commented 7 years ago

Interesting, all the spotless tasks in your example are marked as up-to-date. I'm curious where those 4s are spent then. Can you produce a build scan?

nedtwigg commented 7 years ago

@jbduncan's benchmarks were run against the Spotless codebase. spotlessCheck and spotlessApply in this case are run through a complex bootstrapping process - I don't think a buildscan will be helpful. I'll dig in to do some testing when the code works, but in the meantime I wouldn't spend much time interpreting these results.

For now, I think our real problem is the correctness issue I described in this comment.

jbduncan commented 7 years ago

@nedtwigg I'm not sure I understand why you don't think running a build scan would be helpful. Could you elaborate further for me?

nedtwigg commented 7 years ago

In order for a profiling result to be useful, it's important to change as few things as possible. That way you can trace changes in the result to their source. Running spotlessCheck on master and 3.x branches means that we're running different versions of spotless, to format different codebases, possibly with different build.gradle configurations. So if we find a difference, it's going to be a lot of work to figure out what it means, and even if we do that work we might make a mistake and draw the wrong conclusion.

The second problem is that it's difficult to apply a Gradle plugin to its own sourcecode. So Spotless does some tricks so that spotlessApply and spotlessCheck can work, which you can see here. A build scan would provide profiling data on these tricks, which is not helpful in figuring out Spotless' performance for end-users.

A better test would be to take a very large codebase, perhaps this one and run with two different versions of Spotless. That way the only variable is the version of spotless - not the codebase being formatted or the build.gradle. Change a single file, and make sure that spotlessCheck runs again, and how long does it take. Change the rule, and make sure that spotless runs again. etc.

Regardless of any profiling results, I think that everything besides this testcase is probably a distraction. The testcase shows a correctness problem, and rules out several possible causes. I'd like to focus @oehme's skills on that testcase rather than the profiling data.

oehme commented 7 years ago

@nedtwigg Your task needs to have getters for its properties, otherwise Gradle will not pick them up.

jbduncan commented 7 years ago

Great, that sounds relatively easy to fix. Thank you very much @oehme for looking into this problem for us. :D

I'm currently working on a fix, and I hope to have it done and committed today or tomorrow.

jbduncan commented 7 years ago

Hmm, I take that back - it's actually proving to be rather difficult for me to fix!

It seems that introducing getters is so reliant on all the right parts of the code being serializable (which we haven't completed yet) that it's causing serialization-related errors whenever I run gradlew check.

I've uploaded a commit of my working code to https://github.com/diffplug/spotless/commit/8d503aced53e48bb2df474fb4b944f24843a5d40.

@oehme, @nedtwigg, may I have your thoughts on this?

oehme commented 7 years ago

@jbduncan that's not specific to getters, that's how up-to-date checks work ;) Every input of a Gradle task needs to be serializable.

Your commit looks good to me.

jbduncan commented 7 years ago

How strange! I think my /.gradle folder must have been corrupted, because no matter whether I ran gradle check via IntelliJ IDEA or on the console, I was continuously getting different error messages to what Travis reported.

I've deleted my /.gradle folder and run gradle spotlessApply and gradle check again on my local machine, but now I get some failing tests. Hopefully Travis will report the same error message as I'm seeing here once it finishes scanning.

jbduncan commented 7 years ago

It looks like the Travis run for https://github.com/diffplug/spotless/commit/722e222e9e41509a2b99a9d205472c8a125a482e produced the same error message as on my local machine.

@oehme @nedtwigg Do you have any thoughts on where things may be going wrong?

nedtwigg commented 7 years ago

GoogleJavaFormatStepTest (the previously failing test) now passes. Great work!

All of the test failures are expected - they are failing on calls to checkRunsThenUpToDate() on steps which don't implement Serializable properly, so they are never up-to-date. The fi package with SerializablePredicate/Function needs to die. Lambdas will never implement serializable properly. Anywhere that SerializablePredicate/Function is needed will have to be replaced with FormatterStep.create or createLazy, which lets you use a lambda in a serializable way by capturing and serializing the state.

jbduncan commented 7 years ago

Hi @nedtwigg, many thanks for the compliment and your suggestion for moving forward. :)

I had a go at translating the Serializable(Predicate|Function) calls into FormatterStep calls this evening - but I'm stuck with turning the call to FormatterStep.filterByFile(Predicate<File>) in JavaExtension.setupTask(BaseFormatTask) into a serializable, non-lambda-expression-using FormatterStep that filters on a file...

(The FormatterStep.create* factories don't seem to give access to the file parameter from FormatterStep.format(String, File), so I don't think I can use those methods.)

...so I'm struggling to see the way forward again, and I wonder if you have any other suggestions for me?

nedtwigg commented 7 years ago

I just fixed the SerializablePredicate part in a0be07c5a7d29f980a71b93ac87b1034f5bceeca. Hopefully that shows the way forward for similar problems :)

jbduncan commented 7 years ago

Well, I've now solved some if not all the serialization problems in https://github.com/diffplug/spotless/commit/a1bb630a6ea94d2f90b5444bb7fa8172ca3da781. :)

However, as of https://github.com/diffplug/spotless/commit/83072238eaf1b17fd583adda07a3004984cf864e, the tests are complaining that running spotlessCheck twice doesn't cause it to report that everything's up-to-date, which suggests to me that the incremental build is no longer working, and that maybe I messed up with the serialization somewhere?

(I've confirmed the lack of incremental build by running spotlessCheck on the Spotless codebase twice on my laptop, which doesn't report any "UP-TO-DATE" messages next to the spotlessCheck subtasks).

So I'm left feeling rather stumped.

@oehme @nedtwigg Do you have any idea if I missed something or where I went wrong?

nedtwigg commented 7 years ago

I fixed the problems in d51d13a4432db653af971516785976ae4951528d, did some minor test reorg in b33c6ef8cb44a523eb2837655836961c5d3bb76f and 9a76f34a087d91e87fa0c12eb95471cec4af5a87, then did some refactoring in 86c38e66e8aa35d48b1f2674192eb1b0a3bdec9a.

I did a chunk of other work as well, but I'll need to clean it up before I push it up. I expect we'll have a first RC ready by the end of the week.

EDIT: replaced the last refactoring commit with a force push

oehme commented 7 years ago

Can I already write a custom rule as a class and attach it without needing the bump-method? If not, we need a way to do that. It's important so that users can write their rules e.g. in buildSrc or in a plugin and share it across projects or with others.

@Canonical
class MySpecialRule implements FormatterRule { //FormatterRule extends Serializable
  String format(String input) {
    input.toUpperCase()
  }
}

spotless {
  java {
    custom 'mySpecialRule', new MySpecialRule()
  }
}
jbduncan commented 7 years ago

@nedtwigg Cool, let me know when you've managed to push your remaining chunk of work, as I've noticed a few little areas for improvement, but I'm unsure if I'd be treading over the same lines of code that you're currently working on.

@oehme I admit I don't really know the answer to your question, and I'm unsure how we'd test it. @nedtwigg, does Spotless already allow users to write such class-based rules? If not, do you have any ideas for how we could allow a user to do so?

nedtwigg commented 7 years ago

Can I already write a custom rule as a class and attach it without needing the bump-method?

Yup.

spotless { java {
    addStep(new MyFormatterStep())     // explicit constructor call
    addStep(MyFormatterStep.create())  // might be constructor, or FormatterStep.create()
    custom 'stepName', { someCustomFunction } // you'll need to do the bump-thing

OO techniques make it hard to implement serialization, equality, and laziness all at once, but the FormatterStep.create methods make it a lot less error-prone, so I think I'll steer users in that direction. GoogleJavaFormat and EclipseFormatter are good examples, but some of those below will make even simpler ones:

The following do not support up-to-date checking, but it would be easy to fix them:

@jbduncan feel free to implement these or do cleanup as you wish. I'm going to do some exploratory work in a different branch for #56.

jbduncan commented 7 years ago

Will do @nedtwigg. :)

FormatExtension.customReplace* should now be incremental-build-ready (see https://github.com/diffplug/spotless/commit/52d70d6f5fdf6758e3cad7184fadd5b5134a464f).

I had a go at Freshmark, but when I tried replacing the following lines of code (in FreskMarkExtension)

customLazy(NAME, () -> {
    // defaults to all project properties
    if (properties == null) {
        properties = getProject().getProperties();
    }
    FreshMark freshMark = new FreshMark(properties, getProject().getLogger()::warn);
    return freshMark::compile;
});

with

addStep(FormatterStep.createLazy(NAME,
        () -> new FileSignature(target),
        key -> {
            // defaults to all project properties
            if (properties == null) {
                properties = getProject().getProperties();
            }
            return new FreshMark(properties, getProject().getLogger()::warn)::compile;
        }));

and ran gradlew build twice, it would throw the following exception

java.nio.file.AccessDeniedException: C:\Users\Jonathan\dev\Java\IntelliJ Projects\spotless\build\reports\tests\classes

suggesting to me that

  1. it's trying to read from build/ (I don't understand why it's trying to do so, as build/ should be filtered out when FormatExtension.parseTarget is called).
  2. my understanding of file serialization is lacking somewhere.
nedtwigg commented 7 years ago

FormatterSteps are independent of the files they are transforming. They are dependent on anything which determines their configuration. So EclipseFormatter.State needs to have the file that holds the formatter properties file, but it does not need the target files that it is transforming.

For FreshMark, the key should be the Map<String, String> of properties that it is using.

The FormatTask tracks which files are being transformed, not the FormatterSteps.

jbduncan commented 7 years ago

FormatterSteps are independent of the files they are transforming. They are dependent on anything which determines their configuration. So EclipseFormatter.State needs to have the file that holds the formatter properties file, but it does not need the target files that it is transforming.

Ahh, okay, I see now. Thanks for clearing up my confusion on this.

For FreshMark, the key should be the Map<String, String> of properties that it is using.

Hmm, it seems the default properties are not serializable, so the closest I can get to a version of properties which can be used as a key, is with the following code snippet.

addStep(FormatterStep.createLazy(NAME,
        () -> {
            // defaults to all project properties
            if (properties == null) {
                properties = getProject().getProperties();
            }
            // properties isn't serializable, so make a serializable copy
            return new LinkedHashMap<>(Maps.transformValues(properties, value -> (value == null) ? null : value.toString()));
        },
        key -> new FreshMark(key, getProject().getLogger()::warn)::compile));

The consequence of this solution is :spotlessFreshmarkCheck never reports as being UP-TO-DATE...

nedtwigg commented 7 years ago

Looks like we'll need a Freshmark.State class. Mapping them all to Map<String, String> is a great fix for up-to-date checking, but it's possible for some props to be actual java objects that need to be passed as the objects themselves, not string. So Map<String, ?> can be transient, and Map<String, String> can be the actual prop.

Possible cause 1: LinkedHashMap<> cares about order, and whatever gradle is using doesn't, so they're getting reordered randomly. Possible cause 2: There's a property like "currentTime: Date". Try some debug code that dumps all the properties in getProject().getProperties() and see if any are changing. Depending on what it is, it might be appropriate for the default behavior of this method to be that it is never up-to-date unless properties are manually specified. Or, maybe instead of passig getProject().getProperties(), we should manually parse gradle.properties ourselves.

oehme commented 7 years ago

Gradle definitely doesn't reorder things randomly ;)

My bet is on: There is either a timestamp property or a property that doesn't implement toString(), so you just get the identity hash code which is different on every invocation.

The solution is pretty simple: The user should be explicit about what properties should be passed to FreshMark. The default should be none.

nedtwigg commented 7 years ago

It's very handy that everything in gradle.properties shows up in freshmark without much work. One solution is to add a method addProps(File propertiesFile). But I still lean towards filtering out the individual property which is causing the issue.

oehme commented 7 years ago

There is no such individual property. Every build out there probably has dozens of properties that either are some kind of timestamp or simply don't have a toString() method. The properties are used by build script authors as a bucket to put all kinds of data.

nedtwigg commented 7 years ago

We could check for unimplemented toString() using System.identityHashCode() and filter them out. Regardless, you're convincing me that adding all props is the wrong way to go :) I'm still curious which is the troublemaker.

jbduncan commented 7 years ago

When I print out the properties during a run of gradle check, they look like this:

properties = {
    parent: null
    classLoaderScope: org.gradle.api.internal.initialization.DefaultClassLoaderScope@1fd793ea
    plugins: [org.gradle.api.plugins.HelpTasksPlugin@4493592c, org.gradle.language.base.plugins.LifecycleBasePlugin@6c33e52a, org.gradle.api.plugins.BasePlugin@32382b0c, org.gradle.api.plugins.ReportingBasePlugin@e0c5757, org.gradle.platform.base.plugins.ComponentBasePlugin@34613fbe, org.gradle.language.base.plugins.LanguageBasePlugin@41fcc7ce, org.gradle.platform.base.plugins.BinaryBasePlugin@7dea052b, org.gradle.api.plugins.JavaBasePlugin@1fca0c52, org.gradle.api.plugins.JavaPlugin@7729233b, com.diffplug.gradle.spotless.SpotlessPlugin@182e787a]
    configurations: [configuration ':archives', configuration ':compile', configuration ':compileClasspath', configuration ':compileOnly', configuration ':default', configuration ':runtime', configuration ':testCompile', configuration ':testCompileClasspath', configuration ':testCompileOnly', configuration ':testRuntime']
    logger: org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger@2378b9ca
    rootDir: C:\Users\Jonathan\dev\Java\IntelliJ Projects\spotless
    projectRegistry: org.gradle.api.internal.project.DefaultProjectRegistry@7ee85ce6
    path: :
    testResultsDirName: test-results
    targetCompatibility: 1.8
    ...
    rootProject: root project 'spotless'
    libsDirName: libs
    artifactIdLib: spotless-lib
    properties: {parent=null, classLoaderScope=org.gradle.api.internal.initialization.DefaultClassLoaderScope@1fd793ea, ...}
}

I dare not paste all the properties, because the list is massive.

But looking through the first few props, it seems obvious to me that a number of properties simply do not implement toString() and print out their identities instead, as @oehme suspected.

I didn't notice any timestamp-related property as I was glancing through the list, so I suspect that the objects which don't implement toString() are to blame.

Therefore I'd be inclined to completely throw out the safeguard currently in place to set the properties to getProject().getProperties() if the user doesn't specify them themselves.

@nedtwigg Do you have any ideas as to what we could do instead? Just throw an exception? Or log it and/or perform some other action?

oehme commented 7 years ago

Therefore I'd be inclined to completely throw out the safeguard currently in place to set the properties to getProject().getProperties() if the user doesn't specify them themselves.

I think that's the wrong default. It'll always be out of date. It should be no properties by default and the user should conciously decide what she needs.

nedtwigg commented 7 years ago

I think I agree with @oehme. I think we should add a propsFile argument which can load a .properties file.

jbduncan commented 7 years ago

@nedtwigg I admit I don't really understand what you mean by adding a propsFile argument (it wasn't clear to me if you wanted me to add it to FreshmarkExtension::new's parameter list or another method's), but my current solution is to turn FreshmarkExtension.java, which currently contains:

public class FreshMarkExtension extends FormatExtension {
    public static final String NAME = "freshmark";

    public Map<String, ?> properties;

    public FreshMarkExtension(SpotlessExtension root) {
        super(NAME, root);
        customLazy(NAME, () -> {
            // defaults to all project properties
            if (properties == null) {
                properties = getProject().getProperties();
            }
            FreshMark freshMark = new FreshMark(properties, getProject().getLogger()::warn);
            return freshMark::compile;
        });
    }

    public void properties(Map<String, ?> properties) {
        this.properties = properties;
    }
    ...
}

...into:

public class FreshMarkExtension extends FormatExtension {
    public static final String NAME = "freshmark";

    public FreshMarkExtension(SpotlessExtension root) {
        super(NAME, root);
    }

    public void properties(Map<String, ?> properties) {
        customLazy(NAME, () -> {
            FreshMark freshMark = new FreshMark(properties, getProject().getLogger()::warn);
            return freshMark::compile;
        });
    }
    ...
}

My solution seems to pass all the tests on my machine.

What do you think?

nedtwigg commented 7 years ago

customLazy tasks will always be out-of-date. We need to capture the state of the Map<String, String>, and move FreshMark into lib, using same Provisioner construct as EclipseFormatter and GoogleJavaFormat. If I'm being confusing, that's my bad, I might be able to communicate better by just writing it :)

jbduncan commented 7 years ago

Ooh, yes please! If you could write it down, then I think there's a much higher chance that I'd understand. :)

nedtwigg commented 7 years ago
jbduncan commented 7 years ago

Hi @nedtwigg, I've been working on incrementalising ImportSorterStep over the last few days, but I've observed that when I run ./gradlew check twice on the root project (before and after I've stashed my local changes away with git stash), it throws an exception at :spotlessFreshmarkCheck, and I'm struggling to tell what's causing it, so I wondered if you'd be happy to have a look into it?

Here is an abbreviated example of the stack trace I get.

Exception in thread "main" org.gradle.testkit.runner.UnexpectedBuildFailure: Unexpected build execution failure in C:\Users\Jonathan\dev\Java\IntelliJ Projects\spotless with arguments [-b, spotlessSelf.gradle, spotlessCheck, --stacktrace]

Output:
:spotlessFreshmarkCheck FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':spotlessFreshmarkCheck'.
> java.nio.file.AccessDeniedException: C:\Users\Jonathan\dev\Java\IntelliJ Projects\spotless\plugin-gradle\build\reports

* Try:
Run with --info or --debug option to get more log output.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':spotlessFreshmarkCheck'.
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:84)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:55)
    at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:61)
    ...
    at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:293)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
    at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
Caused by: java.io.UncheckedIOException: java.nio.file.AccessDeniedException: C:\Users\Jonathan\dev\Java\IntelliJ Projects\spotless\plugin-gradle\build\reports
    at com.diffplug.gradle.spotless.CheckFormatTask.addFileIfNotClean(CheckFormatTask.java:61)
    at com.diffplug.gradle.spotless.CheckFormatTask.lambda$check$0(CheckFormatTask.java:38)
    at org.gradle.api.internal.changedetection.changes.ChangesOnlyIncrementalTaskInputs.doOutOfDate(ChangesOnlyIncrementalTaskInputs.java:46)
    at org.gradle.api.internal.changedetection.changes.StatefulIncrementalTaskInputs.outOfDate(StatefulIncrementalTaskInputs.java:39)
    at org.gradle.api.internal.changedetection.changes.ChangesOnlyIncrementalTaskInputs.outOfDate(ChangesOnlyIncrementalTaskInputs.java:27)
    at com.diffplug.gradle.spotless.CheckFormatTask.check(CheckFormatTask.java:38)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
    at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$IncrementalTaskAction.doExecute(DefaultTaskClassInfoStore.java:163)
    at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.execute(DefaultTaskClassInfoStore.java:134)
    at org.gradle.api.internal.project.taskfactory.DefaultTaskClassInfoStore$StandardTaskAction.execute(DefaultTaskClassInfoStore.java:123)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:95)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:76)
    ... 71 more
Caused by: java.nio.file.AccessDeniedException: C:\Users\Jonathan\dev\Java\IntelliJ Projects\spotless\plugin-gradle\build\reports
    at com.diffplug.spotless.Formatter.isClean(Formatter.java:103)
    at com.diffplug.gradle.spotless.CheckFormatTask.addFileIfNotClean(CheckFormatTask.java:57)
    ... 82 more

BUILD FAILED

Total time: 2.188 secs

    at org.gradle.testkit.runner.internal.DefaultGradleRunner$1.execute(DefaultGradleRunner.java:222)
    at org.gradle.testkit.runner.internal.DefaultGradleRunner$1.execute(DefaultGradleRunner.java:219)
    at org.gradle.testkit.runner.internal.DefaultGradleRunner.run(DefaultGradleRunner.java:282)
    at org.gradle.testkit.runner.internal.DefaultGradleRunner.build(DefaultGradleRunner.java:219)
    at com.diffplug.gradle.spotless.SelfTest.runWithTestKit(SelfTest.java:132)
    at com.diffplug.gradle.spotless.SelfTestCheck.main(SelfTestCheck.java:20)
:plugin-gradle:spotlessCheck FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':plugin-gradle:spotlessCheck'.
> Process 'command 'C:\Program Files\Java\jdk1.8.0_101\bin\java.exe'' finished with non-zero exit value 1

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 17.819 secs
Process 'command 'C:\Program Files\Java\jdk1.8.0_101\bin\java.exe'' finished with non-zero exit value 1
14:31:03: External task execution finished 'build'.
nedtwigg commented 7 years ago

I've been having this too. The fix is to delete the .gradle directory. Dunno the cause, but self-applying the gradle plugin to its own directory is pretty difficult, and quite janky. Might be possible to fix by making spotlessSelf.gradle more specific.

jbduncan commented 7 years ago

@nedtwigg Thanks for the advice, deleting .gradle seems to work nicely as a short-term fix.

I've now made the import sorter related classes ready for incremental builds: https://github.com/diffplug/spotless/commit/5d365b3873351f8fc934db44aea80580a263c244.

Hmm, having thought about it, I wonder if the reason the build fails at :spotlessFreshmarkCheck on a second run is because it fails to deserialize the Freshmark-related classes that are serialized on the first run...

(By my understanding, the serialized bits are saved in files within .gradle, so it would make sense to me that deleting .gradle would 'fix' the problem, as there would be nothing for Gradle to deserialize.)

nedtwigg commented 7 years ago

If that is the case, it should be fixed since d3994cf15533f5e5f2021a04549c4b35dc00d784 on 12/7, because freshMark no longer adds all the project properties.

jbduncan commented 7 years ago

Hi @nedtwigg, thanks for pointing that out. I've investigated things further, and I now think the exception occurs because it tries to read a file from the plugin-gradle build directory.

I don't know why it throws an exception there, and probably more importantly, it's not clear to me why it's even trying to read from the build directory in the first place, as I implemented a filter at some point which prevents (or, at least should prevent) Spotless from reading a Gradle project's build/ and .gradle/ dirs.

@oehme Do you have any thoughts regarding this?

oehme commented 7 years ago

As far as I can recall your code only excluded the current projects build dir. That doesn't stop it from recursing into subproject folders and reading their build directory.

jbduncan commented 7 years ago

Thanks @oehme! It looks like running ./gradlew check twice on Spotless no longer causes to an exception to be thrown, so I'd say it's fixed now! (See commit https://github.com/diffplug/spotless/commit/e3ec14fe9e951e11027643ca573d7b3f97d66a43.)