-
Notifications
You must be signed in to change notification settings - Fork 461
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix issue #31 #45
Fix issue #31 #45
Changes from 1 commit
7e1fcad
6061363
c25ac3c
0faae70
2c45080
0e5deb4
cec7db3
7b6aea7
6113458
452fbd9
c1357f5
51f0531
eefdc50
40c00d0
4262b2b
fb613bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,19 +23,36 @@ | |
import java.util.List; | ||
import java.util.Locale; | ||
|
||
import com.diffplug.common.base.Errors; | ||
import org.gradle.api.DefaultTask; | ||
import org.gradle.api.GradleException; | ||
import org.gradle.api.tasks.Input; | ||
import org.gradle.api.tasks.InputFiles; | ||
import org.gradle.api.tasks.OutputFiles; | ||
import org.gradle.api.tasks.SkipWhenEmpty; | ||
import org.gradle.api.tasks.TaskAction; | ||
import org.gradle.api.tasks.incremental.IncrementalTaskInputs; | ||
|
||
public class FormatTask extends DefaultTask { | ||
// set by SpotlessExtension, but possibly overridden by FormatExtension | ||
@Input | ||
public Charset encoding = StandardCharsets.UTF_8; | ||
public LineEnding.Policy lineEndingsPolicy = LineEnding.UNIX_POLICY; | ||
@Input | ||
public LineEnding.Policy lineEndingPolicy = LineEnding.UNIX_POLICY; | ||
|
||
// set by FormatExtension | ||
@Input | ||
public boolean paddedCell = false; | ||
@InputFiles | ||
@OutputFiles | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You will have to split the task into two classes:
Only the |
||
@SkipWhenEmpty | ||
public Iterable<File> target; | ||
@Input | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When you have a List of complex objects as an input, these objects must:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One idea (I think from @oehme in the issue discussion) is looking at the bytecode of the function, but I'm inclined to declare bankruptcy on input properties being clean w.r.t. incremental compilation. That leaves two options: A) I see Spotless' priorities as such:
Based on these priorities, I'm inclined to go with option B. It lets us implement
If we get it to the point that we don't need an asterisk around correctness, then we can dump the old There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure nobody would use a The only real alternative I see is properly supporting up-to-date checks. Instead of referencing The worst thing that could happen after that is that the task is out-of-date unnecessarily when users use inline steps. The readme about "Making spotless faster" could then explain that users should write real classes with equals/hashcode instead to make the task fast. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree it's easy to implement all of Spotless' existing formatters in a serializable way. But I don't see an easy way to do this for custom user functions. #46 is a great example of why these custom functions are so useful, and why it's important for them to be easy. We've got this custom function up-to-date wrinkle, and we can push it onto users in two ways: A) We can make custom rule-writers deal with it
B) We can make people who are optimizing their build deal with it
If somebody's code has formatting problems, it's probably not because running the formatter is too slow, but because it was hard to find or write a formatting rule that did what they want. So I don't think we should take a mild performance issue and turn it into a correctness/customizability issue.
It would still be very useful for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think you misunderstood my point above.
This gives you the best of both worlds: Ease of use by default, performance on demand. No need for a 2-task solution. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is only true in very small projects. In a large project, when you change a single module, you want the other modules to be up-to-date, not spend time re-executing the formatter. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahhh. Thanks for clarification, I misunderstood. In the approach you describe, a But the simplicity of having only a single "check" class is very valuable, and it's a great feature for all tasks to always be correct. My gut is quite unhappy that Spotless' formatters are no longer just |
||
@SkipWhenEmpty | ||
public List<FormatterStep> steps = new ArrayList<>(); | ||
|
||
// set by plugin | ||
@Input | ||
public boolean check = false; | ||
|
||
/** Returns the name of this format. */ | ||
|
@@ -53,32 +70,41 @@ public String getFormatName() { | |
} | ||
|
||
@TaskAction | ||
public void format() throws Exception { | ||
public void format(IncrementalTaskInputs inputs) throws Exception { | ||
if (target == null) { | ||
throw new GradleException("You must specify 'Iterable<File> toFormat'"); | ||
} | ||
// combine them into the master formatter | ||
Formatter formatter = new Formatter(lineEndingsPolicy, encoding, getProject().getProjectDir().toPath(), steps); | ||
Formatter formatter = Formatter.builder() | ||
.lineEndingPolicy(lineEndingPolicy) | ||
.encoding(encoding) | ||
.projectDirectory(getProject().getProjectDir().toPath()) | ||
.steps(steps) | ||
.build(); | ||
|
||
// perform the check | ||
if (check) { | ||
formatCheck(formatter); | ||
formatCheck(formatter, inputs); | ||
} else { | ||
formatApply(formatter); | ||
formatApply(formatter, inputs); | ||
} | ||
} | ||
|
||
/** Checks the format. */ | ||
private void formatCheck(Formatter formatter) throws IOException { | ||
private void formatCheck(Formatter formatter, IncrementalTaskInputs inputs) throws IOException { | ||
List<File> problemFiles = new ArrayList<>(); | ||
|
||
for (File file : target) { | ||
inputs.outOfDate(input -> { | ||
File file = input.getFile(); | ||
getLogger().debug("Checking format on " + file); | ||
// keep track of the problem toFormat | ||
if (!formatter.isClean(file)) { | ||
problemFiles.add(file); | ||
} | ||
} | ||
Errors.rethrow() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you give a short explanation why this wrapping needed now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's to do with how, in Java 8, lambdas cannot throw checked exceptions unless the interface they target declares that it In this case, I decided to use Durian's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, that was a longer explanation than I expected to write. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, makes perfect sense. Didn't know that method threw a checked exception. It could possibly be changed to use an unchecked exception further down. |
||
.wrap(() -> { | ||
if (!formatter.isClean(file)) { | ||
problemFiles.add(file); | ||
} | ||
}).run(); | ||
}); | ||
|
||
if (paddedCell) { | ||
PaddedCellTaskMisc.check(this, formatter, problemFiles); | ||
|
@@ -100,15 +126,19 @@ GradleException formatViolationsFor(Formatter formatter, List<File> problemFiles | |
} | ||
|
||
/** Applies the format. */ | ||
private void formatApply(Formatter formatter) throws IOException { | ||
for (File file : target) { | ||
private void formatApply(Formatter formatter, IncrementalTaskInputs inputs) throws IOException { | ||
inputs.outOfDate(input -> { | ||
File file = input.getFile(); | ||
getLogger().debug("Applying format to " + file); | ||
// keep track of the problem toFormat | ||
if (paddedCell) { | ||
PaddedCellTaskMisc.apply(this, formatter, file); | ||
} else { | ||
formatter.applyFormat(file); | ||
} | ||
} | ||
Errors.rethrow() | ||
.wrap(() -> { | ||
if (paddedCell) { | ||
PaddedCellTaskMisc.apply(this, formatter, file); | ||
} else { | ||
formatter.applyFormat(file); | ||
} | ||
}).run(); | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't care which name we use, but this is a public member of a public class documented in the README, so changing it breaks API compat for everyday buildscripts. These changes are likely going to require a major version bump anyway, but I'd rather not churn the
lineEndingsPolicy
name.aside: I've enjoyed learning about a lot of Stream methods I didn't know about before, lots of good cleanup in various parts 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm glad you're enjoying learning more about Stream methods. :D