Skip to content

Commit f7829b5

Browse files
committed
Make non-predeclared spotless compatible with isolated projects
1 parent 2bf655b commit f7829b5

File tree

11 files changed

+101
-53
lines changed

11 files changed

+101
-53
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,11 @@ public FormatExtension(SpotlessExtension spotless) {
9595
}
9696

9797
protected final Provisioner provisioner() {
98-
return spotless.getRegisterDependenciesTask().getTaskService().get().provisionerFor(spotless);
98+
return spotless.getSpotlessTaskService().get().provisionerFor(spotless);
9999
}
100100

101101
protected final P2Provisioner p2Provisioner() {
102-
return spotless.getRegisterDependenciesTask().getTaskService().get().p2ProvisionerFor(spotless);
102+
return spotless.getSpotlessTaskService().get().p2ProvisionerFor(spotless);
103103
}
104104

105105
private String formatName() {
@@ -1099,7 +1099,7 @@ protected void setupTask(SpotlessTask task) {
10991099
LineEnding lineEndings = getLineEndings();
11001100
task.setLineEndingsPolicy(
11011101
getProject().provider(() -> lineEndings.createPolicy(projectDir.getAsFile(), () -> totalTarget)));
1102-
spotless.getRegisterDependenciesTask().hookSubprojectTask(task);
1102+
spotless.getSpotlessTaskService().get().hookSubprojectTask(getProject(), task);
11031103
task.setupRatchet(getRatchetFrom() != null ? getRatchetFrom() : "");
11041104
}
11051105

@@ -1135,7 +1135,7 @@ public TaskProvider<SpotlessApply> createIndependentApplyTaskLazy(String taskNam
11351135
"Task name must not end with " + SpotlessExtension.APPLY);
11361136
TaskProvider<SpotlessTaskImpl> spotlessTask = spotless.project.getTasks()
11371137
.register(taskName + SpotlessTaskService.INDEPENDENT_HELPER, SpotlessTaskImpl.class, task -> {
1138-
task.init(spotless.getRegisterDependenciesTask().getTaskService());
1138+
task.init(spotless.getSpotlessTaskService());
11391139
setupTask(task);
11401140
// clean removes the SpotlessCache, so we have to run after clean
11411141
task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME);

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2023 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.
@@ -25,7 +25,6 @@
2525

2626
import org.gradle.api.DefaultTask;
2727
import org.gradle.api.provider.Provider;
28-
import org.gradle.api.services.BuildServiceRegistry;
2928
import org.gradle.api.tasks.Input;
3029
import org.gradle.api.tasks.Internal;
3130
import org.gradle.api.tasks.OutputFile;
@@ -64,8 +63,7 @@ void hookSubprojectTask(SpotlessTask task) {
6463
void setup() {
6564
Preconditions.checkArgument(getProject().getRootProject() == getProject(), "Can only be used on the root project");
6665
String compositeBuildSuffix = getName().substring(TASK_NAME.length()); // see https://github.com/diffplug/spotless/pull/1001
67-
BuildServiceRegistry buildServices = getProject().getGradle().getSharedServices();
68-
taskService = buildServices.registerIfAbsent("SpotlessTaskService" + compositeBuildSuffix, SpotlessTaskService.class, spec -> {});
66+
taskService = SpotlessTaskService.registerIfAbsent(getProject(), compositeBuildSuffix);
6967
usesService(taskService);
7068
getBuildEventsListenerRegistry().onTaskCompletion(taskService);
7169
unitOutput = new File(getProject().getLayout().getBuildDirectory().getAsFile().get(), "tmp/spotless-register-dependencies");

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

Lines changed: 6 additions & 28 deletions
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.
@@ -27,15 +27,14 @@
2727
import org.gradle.api.Action;
2828
import org.gradle.api.GradleException;
2929
import org.gradle.api.Project;
30-
import org.gradle.api.tasks.TaskContainer;
31-
import org.gradle.api.tasks.TaskProvider;
30+
import org.gradle.api.provider.Provider;
3231
import org.gradle.language.base.plugins.LifecycleBasePlugin;
3332

3433
import com.diffplug.spotless.LineEnding;
3534

3635
public abstract class SpotlessExtension {
3736
final Project project;
38-
private final RegisterDependenciesTask registerDependenciesTask;
37+
private final Provider<SpotlessTaskService> spotlessTaskService;
3938

4039
protected static final String TASK_GROUP = LifecycleBasePlugin.VERIFICATION_GROUP;
4140
protected static final String BUILD_SETUP_TASK_GROUP = "build setup";
@@ -52,11 +51,11 @@ public abstract class SpotlessExtension {
5251

5352
protected SpotlessExtension(Project project) {
5453
this.project = requireNonNull(project);
55-
this.registerDependenciesTask = findRegisterDepsTask().get();
54+
this.spotlessTaskService = SpotlessTaskService.registerIfAbsent(project, "");
5655
}
5756

58-
RegisterDependenciesTask getRegisterDependenciesTask() {
59-
return registerDependenciesTask;
57+
Provider<SpotlessTaskService> getSpotlessTaskService() {
58+
return spotlessTaskService;
6059
}
6160

6261
/** Line endings (if any). */
@@ -303,27 +302,6 @@ <T extends FormatExtension> T instantiateFormatExtension(Class<T> clazz) {
303302

304303
protected abstract void createFormatTasks(String name, FormatExtension formatExtension);
305304

306-
TaskProvider<RegisterDependenciesTask> findRegisterDepsTask() {
307-
try {
308-
return findRegisterDepsTask(RegisterDependenciesTask.TASK_NAME);
309-
} catch (Exception e) {
310-
// in a composite build there can be multiple Spotless plugins on the classpath, and they will each try to register
311-
// a task on the root project with the same name. That will generate casting errors, which we can catch and try again
312-
// with an identity-specific identifier.
313-
// https://github.com/diffplug/spotless/pull/1001 for details
314-
return findRegisterDepsTask(RegisterDependenciesTask.TASK_NAME + System.identityHashCode(RegisterDependenciesTask.class));
315-
}
316-
}
317-
318-
private TaskProvider<RegisterDependenciesTask> findRegisterDepsTask(String taskName) {
319-
TaskContainer rootProjectTasks = project.getRootProject().getTasks();
320-
if (!rootProjectTasks.getNames().contains(taskName)) {
321-
return rootProjectTasks.register(taskName, RegisterDependenciesTask.class, RegisterDependenciesTask::setup);
322-
} else {
323-
return rootProjectTasks.named(taskName, RegisterDependenciesTask.class);
324-
}
325-
}
326-
327305
public void predeclareDepsFromBuildscript() {
328306
if (project.getRootProject() != project) {
329307
throw new GradleException("predeclareDepsFromBuildscript can only be called from the root project");

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ protected void createFormatTasks(String name, FormatExtension formatExtension) {
6161
// create the SpotlessTask
6262
String taskName = EXTENSION + SpotlessPlugin.capitalize(name);
6363
TaskProvider<SpotlessTaskImpl> spotlessTask = tasks.register(taskName, SpotlessTaskImpl.class, task -> {
64-
task.init(getRegisterDependenciesTask().getTaskService());
64+
task.init(getSpotlessTaskService());
6565
task.setGroup(TASK_GROUP);
6666
task.getIdeHookState().set(ideHook);
6767
// clean removes the SpotlessCache, so we have to run after clean

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,28 @@
2020

2121
import org.gradle.api.Action;
2222
import org.gradle.api.Project;
23+
import org.gradle.api.tasks.TaskProvider;
2324

2425
import com.diffplug.spotless.LazyForwardingEquality;
2526

2627
public class SpotlessExtensionPredeclare extends SpotlessExtension {
2728
private final SortedMap<String, FormatExtension> toSetup = new TreeMap<>();
29+
private final RegisterDependenciesTask registerDependenciesTask;
2830

2931
public SpotlessExtensionPredeclare(Project project, GradleProvisioner.Policy policy) {
3032
super(project);
31-
getRegisterDependenciesTask().getTaskService().get().predeclaredProvisioner = policy.dedupingProvisioner(project);
32-
getRegisterDependenciesTask().getTaskService().get().predeclaredP2Provisioner = policy.dedupingP2Provisioner(project);
33+
this.registerDependenciesTask = findRegisterDepsTask().get();
34+
SpotlessTaskService taskService = getSpotlessTaskService().get();
35+
taskService.isUsingPredeclared = true;
36+
taskService.predeclaredProvisioner = policy.dedupingProvisioner(project);
37+
taskService.predeclaredP2Provisioner = policy.dedupingP2Provisioner(project);
3338
project.afterEvaluate(unused -> toSetup.forEach((name, formatExtension) -> {
3439
for (Action<FormatExtension> lazyAction : formatExtension.lazyActions) {
3540
lazyAction.execute(formatExtension);
3641
}
37-
getRegisterDependenciesTask().steps.addAll(formatExtension.steps);
42+
registerDependenciesTask.steps.addAll(formatExtension.steps);
3843
// needed to fix Deemon memory leaks (#1194), but this line came from https://github.com/diffplug/spotless/pull/1206
39-
LazyForwardingEquality.unlazy(getRegisterDependenciesTask().steps);
44+
LazyForwardingEquality.unlazy(registerDependenciesTask.steps);
4045
}));
4146
}
4247

@@ -49,4 +54,21 @@ protected void createFormatTasks(String name, FormatExtension formatExtension) {
4954
protected void predeclare(GradleProvisioner.Policy policy) {
5055
throw new UnsupportedOperationException("predeclare can't be called from within `" + EXTENSION_PREDECLARE + "`");
5156
}
57+
58+
private TaskProvider<RegisterDependenciesTask> findRegisterDepsTask() {
59+
try {
60+
return findRegisterDepsTask(RegisterDependenciesTask.TASK_NAME);
61+
} catch (Exception e) {
62+
// in a composite build there can be multiple Spotless plugins on the classpath, and they will each try to register
63+
// a task on the root project with the same name. That will generate casting errors, which we can catch and try again
64+
// with an identity-specific identifier.
65+
// https://github.com/diffplug/spotless/pull/1001 for details
66+
return findRegisterDepsTask(RegisterDependenciesTask.TASK_NAME + System.identityHashCode(RegisterDependenciesTask.class));
67+
}
68+
}
69+
70+
private TaskProvider<RegisterDependenciesTask> findRegisterDepsTask(String taskName) {
71+
return project.getTasks().register(taskName, RegisterDependenciesTask.class, RegisterDependenciesTask::setup);
72+
}
73+
5274
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import javax.inject.Inject;
2929

3030
import org.gradle.api.DefaultTask;
31+
import org.gradle.api.Project;
3132
import org.gradle.api.file.ConfigurableFileTree;
3233
import org.gradle.api.file.DirectoryProperty;
3334
import org.gradle.api.file.FileVisitDetails;
@@ -55,6 +56,7 @@
5556
* apply already did).
5657
*/
5758
public abstract class SpotlessTaskService implements BuildService<BuildServiceParameters.None>, AutoCloseable, OperationCompletionListener {
59+
protected boolean isUsingPredeclared = false;
5860
private final Map<String, SpotlessApply> apply = Collections.synchronizedMap(new HashMap<>());
5961
private final Map<String, SpotlessTask> source = Collections.synchronizedMap(new HashMap<>());
6062
private final Map<String, Provisioner> provisioner = Collections.synchronizedMap(new HashMap<>());
@@ -126,6 +128,21 @@ static void usesServiceTolerateTestFailure(DefaultTask task, Provider<SpotlessTa
126128
}
127129
}
128130

131+
public void hookSubprojectTask(Project project, SpotlessTask task) {
132+
// This check allows isolated projects support by not accessing the root project tasks unless really needed
133+
if (!isUsingPredeclared)
134+
return;
135+
136+
project.getRootProject().getTasks().withType(RegisterDependenciesTask.class, (registerTask) -> {
137+
registerTask.hookSubprojectTask(task);
138+
});
139+
}
140+
141+
public static Provider<SpotlessTaskService> registerIfAbsent(Project project, String suffix) {
142+
return project.getGradle().getSharedServices()
143+
.registerIfAbsent("SpotlessTaskService" + suffix, SpotlessTaskService.class, spec -> {});
144+
}
145+
129146
abstract static class ClientTask extends DefaultTask {
130147
@Internal
131148
abstract Property<File> getSpotlessCleanDirectory();

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

Lines changed: 3 additions & 4 deletions
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.
@@ -136,10 +136,9 @@ private void expectSuccess() throws Exception {
136136
private StringSelfie expectFailureAndConsoleToBe() throws Exception {
137137
BuildResult result = gradleRunner().withArguments("check").buildAndFail();
138138
String output = result.getOutput();
139-
int register = output.indexOf(":spotlessInternalRegisterDependencies");
140-
int firstNewlineAfterThat = output.indexOf('\n', register + 1);
139+
int firstTask = output.indexOf("> Task");
141140
int firstTry = output.indexOf("\n* Try:");
142-
String useThisToMatch = output.substring(firstNewlineAfterThat, firstTry).trim();
141+
String useThisToMatch = output.substring(firstTask, firstTry).trim();
143142
return Selfie.expectSelfie(useThisToMatch);
144143
}
145144
}

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

Lines changed: 2 additions & 2 deletions
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.
@@ -197,7 +197,7 @@ private void taskIsUpToDate(String task, boolean upToDate) throws IOException {
197197

198198
List<String> expected = outcomes(buildResult, upToDate ? TaskOutcome.UP_TO_DATE : TaskOutcome.SUCCESS);
199199
List<String> notExpected = outcomes(buildResult, upToDate ? TaskOutcome.SUCCESS : TaskOutcome.UP_TO_DATE);
200-
boolean everythingAsExpected = !expected.isEmpty() && notExpected.isEmpty() && buildResult.getTasks().size() - 1 == expected.size();
200+
boolean everythingAsExpected = !expected.isEmpty() && notExpected.isEmpty() && buildResult.getTasks().size() == expected.size();
201201
if (!everythingAsExpected) {
202202
fail("Expected all tasks to be " + (upToDate ? TaskOutcome.UP_TO_DATE : TaskOutcome.SUCCESS) + ", but instead was\n" + buildResultToString(buildResult));
203203
}

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

Lines changed: 37 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.
@@ -151,4 +151,40 @@ public void predeclaredUndeclared() throws IOException {
151151
Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput())
152152
.contains("Could not find method spotlessPredeclare() for arguments");
153153
}
154+
155+
@Test
156+
void nonPredeclaredSupportsIsolatedProjects() throws IOException {
157+
setFile("gradle.properties").toContent("org.gradle.unsafe.isolated-projects=true");
158+
setFile("build.gradle").toLines(
159+
"plugins {",
160+
" id 'com.diffplug.spotless'",
161+
"}",
162+
"repositories { mavenCentral() }",
163+
"",
164+
"spotless {",
165+
" java {",
166+
" target file('test.java')",
167+
" googleJavaFormat('1.17.0')",
168+
" }",
169+
"}");
170+
createNSubprojects();
171+
gradleRunner().withArguments("spotlessApply").build();
172+
}
173+
174+
@Test
175+
void predeclaredRequiresNonIsolatedProjects() throws IOException {
176+
setFile("gradle.properties").toContent("org.gradle.unsafe.isolated-projects=true");
177+
setFile("build.gradle").toLines(
178+
"plugins {",
179+
" id 'com.diffplug.spotless'",
180+
"}",
181+
"repositories { mavenCentral() }",
182+
"spotless { predeclareDeps() }",
183+
"spotlessPredeclare {",
184+
" java { googleJavaFormat('1.17.0') }",
185+
"}");
186+
createNSubprojects();
187+
Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput())
188+
.contains("Cannot access project");
189+
}
154190
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2023 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.
@@ -43,8 +43,6 @@ void duplicateConfigs() throws IOException {
4343
setFile("gradle.properties").toLines();
4444
String newestSupported = gradleRunner().withArguments("spotlessCheck").build().getOutput();
4545
Assertions.assertThat(newestSupported.replace("\r", ""))
46-
.startsWith(
47-
"> Task :spotlessInternalRegisterDependencies\n")
4846
.contains(
4947
"> Task :sub:spotlessJava\n",
5048
"> Task :sub:spotlessJavaCheck\n",

0 commit comments

Comments
 (0)