Skip to content

Commit e915474

Browse files
authored
Improves spotlessPredeclare (#2925)
2 parents b0a3c2c + e8d38b4 commit e915474

7 files changed

Lines changed: 199 additions & 43 deletions

File tree

plugin-gradle/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1212
### Changes
1313
- Bump default `cleanthat` version `2.24` -> `2.25`. ([#2903](https://github.com/diffplug/spotless/pull/2903))
1414
- Bump default `eclipse-jdt` version from `4.35` to `4.39`. ([#2912](https://github.com/diffplug/spotless/pull/2912))
15+
- Make `spotlessPredeclare` visible to Gradle Kotlin DSL type-safe accessors. ([#2925](https://github.com/diffplug/spotless/pull/2925))
16+
- Allow `spotlessPredeclare` to be used directly without enabling it first in spotless extension. ([#2925](https://github.com/diffplug/spotless/pull/2925))
1517

1618
## [8.4.0] - 2026-03-18
1719
### Added

plugin-gradle/README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,17 +1959,26 @@ spotless {
19591959
By default, Spotless resolves dependencies on a per-project basis. For very large parallel builds, this can sometimes cause problems. As an alternative, Spotless can be configured to resolve all dependencies in the root project like so:
19601960
19611961
```gradle
1962-
spotless {
1963-
...
1964-
predeclareDeps()
1962+
spotlessPredeclare {
1963+
java { eclipse() }
1964+
kotlin { ktfmt('0.28') }
1965+
}
1966+
```
1967+
1968+
By default, `spotlessPredeclare` resolves dependencies from the root project's repositories. Alternatively, you can resolve dependencies from the buildscript repositories rather than the project repositories:
1969+
1970+
```gradle
1971+
buildscript {
1972+
repositories { mavenCentral() }
19651973
}
19661974
spotlessPredeclare {
1975+
fromBuildscriptRepositories()
19671976
java { eclipse() }
19681977
kotlin { ktfmt('0.28') }
19691978
}
19701979
```
19711980
1972-
Alternatively, you can also use `predeclareDepsFromBuildscript()` to resolve the dependencies from the buildscript repositories rather than the project repositories.
1981+
The older `spotless { predeclareDeps() }` and `spotless { predeclareDepsFromBuildscript() }` APIs are still supported.
19731982
19741983
If you use this feature, you will get an error if you use a formatter in a subproject which is not declared in the `spotlessPredeclare` block.
19751984

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,21 +308,46 @@ <T extends FormatExtension> T instantiateFormatExtension(Class<T> clazz) {
308308

309309
protected abstract void createFormatTasks(String name, FormatExtension formatExtension);
310310

311+
/**
312+
* Enables predeclared dependency resolution using the root project's {@code buildscript} repositories.
313+
*
314+
* @deprecated Configure the repository policy directly in {@code spotlessPredeclare} instead:
315+
* <pre>{@code
316+
* spotlessPredeclare {
317+
* fromBuildscriptRepositories()
318+
* java { googleJavaFormat("1.17.0") }
319+
* }
320+
* }</pre>
321+
*/
322+
@Deprecated
311323
public void predeclareDepsFromBuildscript() {
312324
if (project.getRootProject() != project) {
313325
throw new GradleException("predeclareDepsFromBuildscript can only be called from the root project");
314326
}
327+
project.getLogger().info("predeclareDepsFromBuildscript() is deprecated, use 'spotlessPredeclare { fromBuildscriptRepositories() }' directly instead.");
315328
predeclare(GradleProvisioner.Policy.ROOT_BUILDSCRIPT);
316329
}
317330

331+
/**
332+
* Enables predeclared dependency resolution using the root project's repositories.
333+
*
334+
* @deprecated Declare formats directly in {@code spotlessPredeclare} instead:
335+
* <pre>{@code
336+
* spotlessPredeclare {
337+
* java { googleJavaFormat("1.17.0") }
338+
* }
339+
* }</pre>
340+
*/
341+
@Deprecated
318342
public void predeclareDeps() {
319343
if (project.getRootProject() != project) {
320344
throw new GradleException("predeclareDeps can only be called from the root project");
321345
}
346+
project.getLogger().info("predeclareDeps() is deprecated, use 'spotlessPredeclare { ... }' directly instead.");
322347
predeclare(GradleProvisioner.Policy.ROOT_PROJECT);
323348
}
324349

325-
protected void predeclare(GradleProvisioner.Policy policy) {
326-
project.getExtensions().create(SpotlessExtensionPredeclare.class, EXTENSION_PREDECLARE, SpotlessExtensionPredeclare.class, project, policy);
350+
void predeclare(GradleProvisioner.Policy policy) {
351+
project.getExtensions().getByType(SpotlessExtensionPredeclare.class).enablePredeclare(policy);
327352
}
328353
}

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionPredeclare.java

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,43 +18,109 @@
1818
import java.util.SortedMap;
1919
import java.util.TreeMap;
2020

21+
import javax.annotation.Nullable;
22+
2123
import org.gradle.api.Action;
24+
import org.gradle.api.GradleException;
2225
import org.gradle.api.Project;
2326
import org.gradle.api.tasks.TaskProvider;
2427

2528
import com.diffplug.spotless.LazyForwardingEquality;
2629

2730
public class SpotlessExtensionPredeclare extends SpotlessExtension {
2831
private final SortedMap<String, FormatExtension> toSetup = new TreeMap<>();
29-
private final RegisterDependenciesTask registerDependenciesTask;
32+
private @Nullable GradleProvisioner.Policy policy;
33+
private boolean policyExplicit;
34+
private @Nullable RegisterDependenciesTask registerDependenciesTask;
3035

31-
public SpotlessExtensionPredeclare(Project project, GradleProvisioner.Policy policy) {
36+
public SpotlessExtensionPredeclare(Project project) {
3237
super(project);
33-
this.registerDependenciesTask = findRegisterDepsTask().get();
34-
SpotlessTaskService taskService = getSpotlessTaskService().get();
35-
taskService.registerDependenciesTask = registerDependenciesTask;
36-
taskService.predeclaredProvisioner = policy.dedupingProvisioner(project);
37-
taskService.predeclaredP2Provisioner = policy.dedupingP2Provisioner(project);
38-
project.afterEvaluate(unused -> toSetup.forEach((name, formatExtension) -> {
39-
for (Action<FormatExtension> lazyAction : formatExtension.lazyActions) {
40-
lazyAction.execute(formatExtension);
38+
project.afterEvaluate(unused -> {
39+
if (policy == null) {
40+
return;
41+
}
42+
RegisterDependenciesTask task = registerDependenciesTask;
43+
if (task == null) {
44+
throw new IllegalStateException("spotlessPredeclare was enabled without a register dependencies task.");
4145
}
42-
registerDependenciesTask.steps.addAll(formatExtension.steps);
43-
// needed to fix Deemon memory leaks (#1194), but this line came from https://github.com/diffplug/spotless/pull/1206
44-
LazyForwardingEquality.unlazy(registerDependenciesTask.steps);
45-
}));
46+
toSetup.forEach((name, formatExtension) -> {
47+
for (Action<FormatExtension> lazyAction : formatExtension.lazyActions) {
48+
lazyAction.execute(formatExtension);
49+
}
50+
task.steps.addAll(formatExtension.steps);
51+
// needed to fix Deemon memory leaks (#1194), but this line came from https://github.com/diffplug/spotless/pull/1206
52+
LazyForwardingEquality.unlazy(task.steps);
53+
});
54+
});
4655
}
4756

4857
@Override
4958
protected void createFormatTasks(String name, FormatExtension formatExtension) {
59+
enablePredeclareIfAbsent(GradleProvisioner.Policy.ROOT_PROJECT);
5060
toSetup.put(name, formatExtension);
5161
}
5262

5363
@Override
54-
protected void predeclare(GradleProvisioner.Policy policy) {
64+
void predeclare(GradleProvisioner.Policy policy) {
5565
throw new UnsupportedOperationException("predeclare can't be called from within `" + EXTENSION_PREDECLARE + "`");
5666
}
5767

68+
/**
69+
* Resolves predeclared dependencies from the root project's repositories.
70+
* <p>
71+
* This is the default behavior when any format is declared in {@code spotlessPredeclare}.
72+
*
73+
* @see SpotlessExtension#predeclareDeps()
74+
*/
75+
public void fromProjectRepositories() {
76+
enablePredeclare(GradleProvisioner.Policy.ROOT_PROJECT);
77+
}
78+
79+
/**
80+
* Resolves predeclared dependencies from the root project's {@code buildscript} repositories.
81+
* <p>
82+
* Use this when formatter dependencies should be resolved from {@code buildscript { repositories { ... } }}
83+
* instead of the root project's normal repositories.
84+
*
85+
* @see SpotlessExtension#predeclareDepsFromBuildscript()
86+
*/
87+
public void fromBuildscriptRepositories() {
88+
enablePredeclare(GradleProvisioner.Policy.ROOT_BUILDSCRIPT);
89+
}
90+
91+
void enablePredeclare(GradleProvisioner.Policy policy) {
92+
enablePredeclare(policy, true);
93+
}
94+
95+
private void enablePredeclareIfAbsent(GradleProvisioner.Policy policy) {
96+
enablePredeclare(policy, false);
97+
}
98+
99+
private void enablePredeclare(GradleProvisioner.Policy policy, boolean explicit) {
100+
if (this.policy != null) {
101+
if (!explicit || !policyExplicit) {
102+
if (explicit) {
103+
this.policy = policy;
104+
this.policyExplicit = true;
105+
configureProvisioners(policy);
106+
}
107+
return;
108+
}
109+
throw new GradleException("predeclared dependency resolution can only be configured once.");
110+
}
111+
this.policy = policy;
112+
this.policyExplicit = explicit;
113+
registerDependenciesTask = findRegisterDepsTask().get();
114+
configureProvisioners(policy);
115+
}
116+
117+
private void configureProvisioners(GradleProvisioner.Policy policy) {
118+
SpotlessTaskService taskService = getSpotlessTaskService().get();
119+
taskService.registerDependenciesTask = registerDependenciesTask;
120+
taskService.predeclaredProvisioner = policy.dedupingProvisioner(project);
121+
taskService.predeclaredP2Provisioner = policy.dedupingP2Provisioner(project);
122+
}
123+
58124
private TaskProvider<RegisterDependenciesTask> findRegisterDepsTask() {
59125
try {
60126
return findRegisterDepsTask(RegisterDependenciesTask.TASK_NAME);

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2025 DiffPlug
2+
* Copyright 2016-2026 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -50,6 +50,9 @@ public void apply(Project project) {
5050

5151
// setup the extension
5252
project.getExtensions().create(SpotlessExtension.class, SpotlessExtension.EXTENSION, SpotlessExtensionImpl.class, project);
53+
if (project.getRootProject() == project) {
54+
project.getExtensions().create(SpotlessExtensionPredeclare.class, SpotlessExtension.EXTENSION_PREDECLARE, SpotlessExtensionPredeclare.class, project);
55+
}
5356

5457
// clear spotless' cache when the user does a clean
5558
// resolution for: https://github.com/diffplug/spotless/issues/243#issuecomment-564323856

plugin-gradle/src/test/java/com/diffplug/gradle/spotless/MultiProjectTest.java

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,37 +92,60 @@ public void predeclaredFails() throws IOException {
9292
}
9393

9494
@Test
95-
public void predeclaredSucceeds() throws IOException {
95+
public void predeclaredSucceeds_deprecatedAPI() throws IOException {
9696
setFile("build.gradle").toLines(
9797
"plugins {",
9898
" id 'com.diffplug.spotless'",
9999
"}",
100100
"repositories { mavenCentral() }",
101101
"spotless { predeclareDeps() }",
102+
"",
102103
"spotlessPredeclare {",
103-
" java { googleJavaFormat('1.17.0') }",
104+
" java { googleJavaFormat('1.17.0') }",
104105
"}");
105106
createNSubprojects();
106107
gradleRunner().withArguments("spotlessApply").build();
107108
}
108109

109110
@Test
110-
public void predeclaredFromBuildscriptSucceeds() throws IOException {
111+
public void predeclaredFromBuildscriptSucceeds_deprecatedAPI() throws IOException {
111112
setFile("build.gradle").toLines(
113+
"buildscript {",
114+
" repositories { mavenCentral() }",
115+
"}",
112116
"plugins {",
113117
" id 'com.diffplug.spotless'",
114118
"}",
115119
"repositories { mavenCentral() }",
116120
"spotless { predeclareDepsFromBuildscript() }",
117121
"spotlessPredeclare {",
118-
" java { googleJavaFormat('1.17.0') }",
122+
" java { googleJavaFormat('1.17.0') }",
123+
"}");
124+
createNSubprojects();
125+
gradleRunner().withArguments("spotlessApply").build();
126+
}
127+
128+
@Test
129+
public void predeclaredFromBuildscriptInPredeclareBlockSucceeds() throws IOException {
130+
setFile("build.gradle").toLines(
131+
"buildscript {",
132+
" repositories { mavenCentral() }",
133+
"}",
134+
"plugins {",
135+
" id 'com.diffplug.spotless'",
136+
"}",
137+
"repositories { mavenCentral() }",
138+
"",
139+
"spotlessPredeclare {",
140+
" fromBuildscriptRepositories()",
141+
" java { googleJavaFormat('1.17.0') }",
119142
"}");
120143
createNSubprojects();
121144
gradleRunner().withArguments("spotlessApply").build();
122145
}
123146

124147
@Test
125-
public void predeclaredOrdering() throws IOException {
148+
public void predeclaredOrderingIsFlexible() throws IOException {
126149
setFile("build.gradle").toLines(
127150
"plugins {",
128151
" id 'com.diffplug.spotless'",
@@ -131,10 +154,9 @@ public void predeclaredOrdering() throws IOException {
131154
"spotlessPredeclare {",
132155
" java { googleJavaFormat('1.17.0') }",
133156
"}",
134-
"spotless { predeclareDepsFromBuildscript() }");
157+
"spotless { predeclareDeps() }");
135158
createNSubprojects();
136-
Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput())
137-
.contains("Could not find method spotlessPredeclare() for arguments");
159+
gradleRunner().withArguments("spotlessApply").build();
138160
}
139161

140162
@Test
@@ -168,7 +190,7 @@ public void predeclaredDepsRegression() throws IOException {
168190
}
169191

170192
@Test
171-
public void predeclaredUndeclared() throws IOException {
193+
public void predeclaredWithoutSpotlessBlockSucceeds() throws IOException {
172194
setFile("build.gradle").toLines(
173195
"plugins {",
174196
" id 'com.diffplug.spotless'",
@@ -178,7 +200,6 @@ public void predeclaredUndeclared() throws IOException {
178200
" java { googleJavaFormat('1.17.0') }",
179201
"}");
180202
createNSubprojects();
181-
Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput())
182-
.contains("Could not find method spotlessPredeclare() for arguments");
203+
gradleRunner().withArguments("spotlessApply").build();
183204
}
184205
}

0 commit comments

Comments
 (0)