Skip to content

Commit b8c7b3d

Browse files
authored
Merge branch 'main' into flexmark-java
2 parents 540aead + bebaa59 commit b8c7b3d

31 files changed

+1920
-38
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ output = [
4242
'| Toggle with [`spotless:off` and `spotless:on`](plugin-gradle/#spotlessoff-and-spotlesson) | {{yes}} | {{yes}} | {{no}} | {{no}} |',
4343
'| [Ratchet from](https://github.com/diffplug/spotless/tree/main/plugin-gradle#ratchet) `origin/main` or other git ref | {{yes}} | {{yes}} | {{no}} | {{no}} |',
4444
'| Define [line endings using git](https://github.com/diffplug/spotless/tree/main/plugin-gradle#line-endings-and-encodings-invisible-stuff) | {{yes}} | {{yes}} | {{yes}} | {{no}} |',
45-
'| Fast incremental format and up-to-date check | {{yes}} | {{no}} | {{no}} | {{no}} |',
45+
'| Fast incremental format and up-to-date check | {{yes}} | {{yes}} | {{no}} | {{no}} |',
4646
'| Fast format on fresh checkout using buildcache | {{yes}} | {{no}} | {{no}} | {{no}} |',
4747
lib('generic.EndWithNewlineStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
4848
lib('generic.IndentStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
@@ -82,7 +82,7 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}}
8282
| Toggle with [`spotless:off` and `spotless:on`](plugin-gradle/#spotlessoff-and-spotlesson) | :+1: | :+1: | :white_large_square: | :white_large_square: |
8383
| [Ratchet from](https://github.com/diffplug/spotless/tree/main/plugin-gradle#ratchet) `origin/main` or other git ref | :+1: | :+1: | :white_large_square: | :white_large_square: |
8484
| Define [line endings using git](https://github.com/diffplug/spotless/tree/main/plugin-gradle#line-endings-and-encodings-invisible-stuff) | :+1: | :+1: | :+1: | :white_large_square: |
85-
| Fast incremental format and up-to-date check | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
85+
| Fast incremental format and up-to-date check | :+1: | :+1: | :white_large_square: | :white_large_square: |
8686
| Fast format on fresh checkout using buildcache | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
8787
| [`generic.EndWithNewlineStep`](lib/src/main/java/com/diffplug/spotless/generic/EndWithNewlineStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
8888
| [`generic.IndentStep`](lib/src/main/java/com/diffplug/spotless/generic/IndentStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ artifactIdGradle=spotless-plugin-gradle
1616
# Build requirements
1717
VER_JAVA=1.8
1818
VER_SPOTBUGS=4.5.0
19+
VER_JSR_305=3.0.2
1920

2021
# Dependencies provided by Spotless plugin
2122
VER_SLF4J=[1.6,2.0[

gradle/java-setup.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,5 @@ tasks.named('spotbugsMain') {
3535
dependencies {
3636
compileOnly 'net.jcip:jcip-annotations:1.0'
3737
compileOnly "com.github.spotbugs:spotbugs-annotations:${VER_SPOTBUGS}"
38-
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
38+
compileOnly "com.google.code.findbugs:jsr305:${VER_JSR_305}"
3939
}

plugin-maven/CHANGES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
44

55
## [Unreleased]
66
### Added
7-
* Added support for Markdown with `flexmark` at `0.62.2` ([#1011](https://github.com/diffplug/spotless/pull/1011)).
7+
* Incremental up-to-date checking ([#935](https://github.com/diffplug/spotless/pull/935)).
8+
* Support for Markdown with `flexmark` at `0.62.2` ([#1011](https://github.com/diffplug/spotless/pull/1011)).
89

910
## [2.17.7] - 2021-12-16
1011
### Fixed

plugin-maven/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,47 @@ If your project has not been rigorous with copyright headers, and you'd like to
923923

924924
<a name="ratchet"></a>
925925

926+
## Incremental up-to-date checking and formatting
927+
928+
**This feature is turned off by default.**
929+
930+
Execution of `spotless:check` and `spotless:apply` for large projects can take time.
931+
By default, Spotless Maven plugin needs to read and format each source file.
932+
Repeated executions of `spotless:check` or `spotless:apply` are completely independent.
933+
934+
If your project has many source files managed by Spotless and formatting takes a long time, you can
935+
enable incremental up-to-date checking with the following configuration:
936+
937+
```xml
938+
<configuration>
939+
<upToDateChecking>
940+
<enabled>true</enabled>
941+
</upToDateChecking>
942+
<!-- ... define formats ... -->
943+
</configuration>
944+
```
945+
946+
With up-to-date checking enabled, Spotless creates an index file in the `target` directory.
947+
The index file contains source file paths and corresponding last modified timestamps.
948+
It allows Spotless to skip already formatted files that have not changed.
949+
950+
**Note:** the index file is located in the `target` directory. Executing `mvn clean` will delete
951+
the index file, and Spotless will need to check/format all the source files.
952+
953+
Spotless will remove the index file when up-to-date checking is explicitly turned off with the
954+
following configuration:
955+
956+
```xml
957+
<configuration>
958+
<upToDateChecking>
959+
<enabled>false</enabled>
960+
</upToDateChecking>
961+
<!-- ... define formats ... -->
962+
</configuration>
963+
```
964+
965+
Consider using this configuration if you experience issues with up-to-date checking.
966+
926967
## How can I enforce formatting gradually? (aka "ratchet")
927968

928969
If your project is not currently enforcing formatting, then it can be a noisy transition. Having a giant commit where every single file gets changed makes the history harder to read. To address this, you can use the `ratchet` feature:

plugin-maven/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ dependencies {
7979

8080
compileOnly "org.apache.maven:maven-plugin-api:${VER_MAVEN_API}"
8181
compileOnly "org.apache.maven.plugin-tools:maven-plugin-annotations:${VER_MAVEN_API}"
82+
compileOnly "org.apache.maven:maven-core:${VER_MAVEN_API}"
8283
compileOnly "org.eclipse.aether:aether-api:${VER_ECLIPSE_AETHER}"
8384
compileOnly "org.eclipse.aether:aether-util:${VER_ECLIPSE_AETHER}"
8485

@@ -95,6 +96,7 @@ dependencies {
9596
testImplementation "org.apache.maven:maven-plugin-api:${VER_MAVEN_API}"
9697
testImplementation "org.eclipse.aether:aether-api:${VER_ECLIPSE_AETHER}"
9798
testImplementation "org.codehaus.plexus:plexus-resources:${VER_PLEXUS_RESOURCES}"
99+
testImplementation "org.apache.maven:maven-core:${VER_MAVEN_API}"
98100
}
99101

100102
task cleanMavenProjectDir(type: Delete) { delete MAVEN_PROJECT_DIR }
@@ -159,6 +161,7 @@ task createPomXml(dependsOn: installLocalDependencies) {
159161
mavenApiVersion : VER_MAVEN_API,
160162
eclipseAetherVersion : VER_ECLIPSE_AETHER,
161163
spotlessLibVersion : libVersion,
164+
jsr305Version : VER_JSR_305,
162165
additionalDependencies : additionalDependencies
163166
]
164167

plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222
import java.util.ArrayList;
2323
import java.util.Arrays;
2424
import java.util.Collections;
25+
import java.util.HashMap;
2526
import java.util.HashSet;
2627
import java.util.List;
28+
import java.util.Map;
29+
import java.util.Map.Entry;
2730
import java.util.Objects;
2831
import java.util.Optional;
2932
import java.util.Set;
3033
import java.util.function.Predicate;
34+
import java.util.function.Supplier;
3135
import java.util.regex.Pattern;
3236
import java.util.stream.Collectors;
3337
import java.util.stream.Stream;
@@ -37,6 +41,7 @@
3741
import org.apache.maven.plugin.MojoExecutionException;
3842
import org.apache.maven.plugins.annotations.Component;
3943
import org.apache.maven.plugins.annotations.Parameter;
44+
import org.apache.maven.project.MavenProject;
4045
import org.codehaus.plexus.resource.ResourceManager;
4146
import org.codehaus.plexus.resource.loader.FileResourceLoader;
4247
import org.codehaus.plexus.util.FileUtils;
@@ -54,6 +59,8 @@
5459
import com.diffplug.spotless.maven.generic.Format;
5560
import com.diffplug.spotless.maven.generic.LicenseHeader;
5661
import com.diffplug.spotless.maven.groovy.Groovy;
62+
import com.diffplug.spotless.maven.incremental.UpToDateChecker;
63+
import com.diffplug.spotless.maven.incremental.UpToDateChecking;
5764
import com.diffplug.spotless.maven.java.Java;
5865
import com.diffplug.spotless.maven.kotlin.Kotlin;
5966
import com.diffplug.spotless.maven.markdown.Markdown;
@@ -85,6 +92,9 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo {
8592
@Parameter(property = "spotless.check.skip", defaultValue = "false")
8693
private boolean checkSkip;
8794

95+
@Parameter(defaultValue = "${project}", required = true, readonly = true)
96+
private MavenProject project;
97+
8898
@Parameter(defaultValue = "${repositorySystemSession}", required = true, readonly = true)
8999
private RepositorySystemSession repositorySystemSession;
90100

@@ -151,13 +161,36 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo {
151161
@Parameter(property = LicenseHeaderStep.spotlessSetLicenseHeaderYearsFromGitHistory)
152162
private String setLicenseHeaderYearsFromGitHistory;
153163

154-
protected abstract void process(Iterable<File> files, Formatter formatter) throws MojoExecutionException;
164+
@Parameter
165+
private UpToDateChecking upToDateChecking;
166+
167+
protected abstract void process(Iterable<File> files, Formatter formatter, UpToDateChecker upToDateChecker) throws MojoExecutionException;
155168

156169
@Override
157170
public final void execute() throws MojoExecutionException {
171+
if (shouldSkip()) {
172+
getLog().info(String.format("Spotless %s skipped", goal));
173+
return;
174+
}
175+
158176
List<FormatterFactory> formatterFactories = getFormatterFactories();
177+
FormatterConfig config = getFormatterConfig();
178+
179+
Map<FormatterFactory, Supplier<Iterable<File>>> formatterFactoryToFiles = new HashMap<>();
159180
for (FormatterFactory formatterFactory : formatterFactories) {
160-
execute(formatterFactory);
181+
Supplier<Iterable<File>> filesToFormat = () -> collectFiles(formatterFactory, config);
182+
formatterFactoryToFiles.put(formatterFactory, filesToFormat);
183+
}
184+
185+
try (FormattersHolder formattersHolder = FormattersHolder.create(formatterFactoryToFiles, config);
186+
UpToDateChecker upToDateChecker = createUpToDateChecker(formattersHolder.getFormatters())) {
187+
for (Entry<Formatter, Supplier<Iterable<File>>> entry : formattersHolder.getFormattersWithFiles().entrySet()) {
188+
Formatter formatter = entry.getKey();
189+
Iterable<File> files = entry.getValue().get();
190+
process(files, formatter, upToDateChecker);
191+
}
192+
} catch (PluginException e) {
193+
throw e.asMojoExecutionException();
161194
}
162195
}
163196

@@ -173,21 +206,7 @@ private boolean shouldSkip() {
173206
return false;
174207
}
175208

176-
private void execute(FormatterFactory formatterFactory) throws MojoExecutionException {
177-
if (shouldSkip()) {
178-
getLog().info(String.format("Spotless %s skipped", goal));
179-
return;
180-
}
181-
182-
FormatterConfig config = getFormatterConfig();
183-
List<File> files = collectFiles(formatterFactory, config);
184-
185-
try (Formatter formatter = formatterFactory.newFormatter(files, config)) {
186-
process(files, formatter);
187-
}
188-
}
189-
190-
private List<File> collectFiles(FormatterFactory formatterFactory, FormatterConfig config) throws MojoExecutionException {
209+
private List<File> collectFiles(FormatterFactory formatterFactory, FormatterConfig config) {
191210
Optional<String> ratchetFrom = formatterFactory.ratchetFrom(config);
192211
try {
193212
final List<File> files;
@@ -212,11 +231,11 @@ private List<File> collectFiles(FormatterFactory formatterFactory, FormatterConf
212231
.filter(shouldInclude)
213232
.collect(toList());
214233
} catch (IOException e) {
215-
throw new MojoExecutionException("Unable to scan file tree rooted at " + baseDir, e);
234+
throw new PluginException("Unable to scan file tree rooted at " + baseDir, e);
216235
}
217236
}
218237

219-
private List<File> collectFilesFromGit(FormatterFactory formatterFactory, String ratchetFrom) throws MojoExecutionException {
238+
private List<File> collectFilesFromGit(FormatterFactory formatterFactory, String ratchetFrom) {
220239
MatchPatterns includePatterns = MatchPatterns.from(
221240
withNormalizedFileSeparators(getIncludes(formatterFactory)));
222241
MatchPatterns excludePatterns = MatchPatterns.from(
@@ -227,7 +246,7 @@ private List<File> collectFilesFromGit(FormatterFactory formatterFactory, String
227246
dirtyFiles = GitRatchetMaven
228247
.instance().getDirtyFiles(baseDir, ratchetFrom);
229248
} catch (IOException e) {
230-
throw new MojoExecutionException("Unable to scan file tree rooted at " + baseDir, e);
249+
throw new PluginException("Unable to scan file tree rooted at " + baseDir, e);
231250
}
232251

233252
List<File> result = new ArrayList<>();
@@ -241,8 +260,7 @@ private List<File> collectFilesFromGit(FormatterFactory formatterFactory, String
241260
return result;
242261
}
243262

244-
private List<File> collectFilesFromFormatterFactory(FormatterFactory formatterFactory)
245-
throws MojoExecutionException, IOException {
263+
private List<File> collectFilesFromFormatterFactory(FormatterFactory formatterFactory) throws IOException {
246264
String includesString = String.join(",", getIncludes(formatterFactory));
247265
String excludesString = String.join(",", getExcludes(formatterFactory));
248266

@@ -260,11 +278,11 @@ private static String withTrailingSeparator(String path) {
260278
return path.endsWith(File.separator) ? path : path + File.separator;
261279
}
262280

263-
private Set<String> getIncludes(FormatterFactory formatterFactory) throws MojoExecutionException {
281+
private Set<String> getIncludes(FormatterFactory formatterFactory) {
264282
Set<String> configuredIncludes = formatterFactory.includes();
265283
Set<String> includes = configuredIncludes.isEmpty() ? formatterFactory.defaultIncludes() : configuredIncludes;
266284
if (includes.isEmpty()) {
267-
throw new MojoExecutionException("You must specify some files to include, such as '<includes><include>src/**/*.blah</include></includes>'");
285+
throw new PluginException("You must specify some files to include, such as '<includes><include>src/**/*.blah</include></includes>'");
268286
}
269287
return includes;
270288
}
@@ -304,4 +322,12 @@ private List<FormatterStepFactory> getFormatterStepFactories() {
304322
.filter(Objects::nonNull)
305323
.collect(toList());
306324
}
325+
326+
private UpToDateChecker createUpToDateChecker(Iterable<Formatter> formatters) {
327+
if (upToDateChecking != null && upToDateChecking.isEnabled()) {
328+
getLog().info("Up-to-date checking enabled");
329+
return UpToDateChecker.forProject(project, formatters, getLog());
330+
}
331+
return UpToDateChecker.noop(project, getLog());
332+
}
307333
}

plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Objects;
2525
import java.util.Optional;
2626
import java.util.Set;
27+
import java.util.function.Supplier;
2728
import java.util.stream.Collectors;
2829

2930
import org.apache.maven.plugins.annotations.Parameter;
@@ -71,10 +72,10 @@ public final Set<String> excludes() {
7172
return excludes == null ? emptySet() : Sets.newHashSet(excludes);
7273
}
7374

74-
public final Formatter newFormatter(List<File> filesToFormat, FormatterConfig config) {
75+
public final Formatter newFormatter(Supplier<Iterable<File>> filesToFormat, FormatterConfig config) {
7576
Charset formatterEncoding = encoding(config);
7677
LineEnding formatterLineEndings = lineEndings(config);
77-
LineEnding.Policy formatterLineEndingPolicy = formatterLineEndings.createPolicy(config.getFileLocator().getBaseDir(), () -> filesToFormat);
78+
LineEnding.Policy formatterLineEndingPolicy = formatterLineEndings.createPolicy(config.getFileLocator().getBaseDir(), filesToFormat);
7879

7980
FormatterStepConfig stepConfig = stepConfig(formatterEncoding, config);
8081
List<FormatterStepFactory> factories = gatherStepFactories(config.getGlobalStepFactories(), stepFactories);
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2021 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.maven;
17+
18+
import java.io.File;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import java.util.Map.Entry;
22+
import java.util.Set;
23+
import java.util.function.Supplier;
24+
25+
import com.diffplug.spotless.Formatter;
26+
27+
class FormattersHolder implements AutoCloseable {
28+
29+
private final Map<Formatter, Supplier<Iterable<File>>> formatterToFiles;
30+
31+
FormattersHolder(Map<Formatter, Supplier<Iterable<File>>> formatterToFiles) {
32+
this.formatterToFiles = formatterToFiles;
33+
}
34+
35+
static FormattersHolder create(Map<FormatterFactory, Supplier<Iterable<File>>> formatterFactoryToFiles, FormatterConfig config) {
36+
Map<Formatter, Supplier<Iterable<File>>> formatterToFiles = new HashMap<>();
37+
try {
38+
for (Entry<FormatterFactory, Supplier<Iterable<File>>> entry : formatterFactoryToFiles.entrySet()) {
39+
FormatterFactory formatterFactory = entry.getKey();
40+
Supplier<Iterable<File>> files = entry.getValue();
41+
42+
Formatter formatter = formatterFactory.newFormatter(files, config);
43+
formatterToFiles.put(formatter, files);
44+
}
45+
} catch (RuntimeException openError) {
46+
try {
47+
close(formatterToFiles.keySet());
48+
} catch (Exception closeError) {
49+
openError.addSuppressed(closeError);
50+
}
51+
throw openError;
52+
}
53+
54+
return new FormattersHolder(formatterToFiles);
55+
}
56+
57+
Iterable<Formatter> getFormatters() {
58+
return formatterToFiles.keySet();
59+
}
60+
61+
Map<Formatter, Supplier<Iterable<File>>> getFormattersWithFiles() {
62+
return formatterToFiles;
63+
}
64+
65+
@Override
66+
public void close() {
67+
try {
68+
close(formatterToFiles.keySet());
69+
} catch (Exception e) {
70+
throw new RuntimeException("Unable to close formatters", e);
71+
}
72+
}
73+
74+
private static void close(Set<Formatter> formatters) throws Exception {
75+
Exception error = null;
76+
for (Formatter formatter : formatters) {
77+
try {
78+
formatter.close();
79+
} catch (Exception e) {
80+
if (error == null) {
81+
error = e;
82+
} else {
83+
error.addSuppressed(e);
84+
}
85+
}
86+
}
87+
if (error != null) {
88+
throw error;
89+
}
90+
}
91+
}

0 commit comments

Comments
 (0)