Skip to content

Commit 892203d

Browse files
committed
Create a :spotlessRegisterDependencies task, and use it to resolve the Gradle 6+ deprecation warnings.
1 parent 28d8962 commit 892203d

File tree

6 files changed

+420
-2
lines changed

6 files changed

+420
-2
lines changed

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

+15
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ public void target(Object... targets) {
145145
this.target = parseTargetsIsExclude(targets, false);
146146
}
147147

148+
/** Sets the target to be empty without a warning. */
149+
public void targetEmptyForDeclaration() {
150+
this.target = getProject().files();
151+
}
152+
148153
/**
149154
* Sets which files will be excluded from formatting. Files to be formatted = (target - targetExclude).
150155
*
@@ -603,6 +608,16 @@ protected void setupTask(SpotlessTask task) {
603608
}
604609
task.setSteps(steps);
605610
task.setLineEndingsPolicy(getLineEndings().createPolicy(getProject().getProjectDir(), () -> task.target));
611+
if (root.registerDependenciesTask != null) {
612+
// if we have a register dependencies task
613+
if (root.project == root.project.getRootProject()) {
614+
// :spotlessRegisterDependencies depends on every SpotlessTask in the root
615+
root.registerDependenciesTask.dependsOn(task);
616+
} else {
617+
// and every SpotlessTask in a subproject depends on :spotlessRegisterDependencies
618+
task.dependsOn(root.registerDependenciesTask);
619+
}
620+
}
606621
}
607622

608623
/** Returns the project that this extension is attached to. */

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

+14-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ public class GradleProvisioner {
3232
private GradleProvisioner() {}
3333

3434
public static Provisioner fromProject(Project project) {
35+
RegisterDependenciesTask task = project.getPlugins().apply(SpotlessPlugin.class).getExtension().registerDependenciesTask;
36+
if (task == null) {
37+
return fromRootBuildscript(project);
38+
} else {
39+
if (project.getRootProject() == project) {
40+
return task.rootProvisioner;
41+
} else {
42+
return new RegisterDependenciesInRoot.SubProvisioner(task.rootProvisioner, project);
43+
}
44+
}
45+
}
46+
47+
static Provisioner fromRootBuildscript(Project project) {
3548
Objects.requireNonNull(project);
3649
return (withTransitives, mavenCoords) -> {
3750
try {
@@ -60,6 +73,5 @@ public static Provisioner fromProject(Project project) {
6073
};
6174
}
6275

63-
private static final Logger logger = Logger.getLogger(GradleProvisioner.class.getName());
64-
76+
static final Logger logger = Logger.getLogger(GradleProvisioner.class.getName());
6577
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright 2016 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.gradle.spotless;
17+
18+
import java.io.File;
19+
import java.util.Collection;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
import java.util.Objects;
23+
import java.util.Set;
24+
25+
import javax.annotation.Nullable;
26+
27+
import org.gradle.api.Project;
28+
import org.gradle.util.GradleVersion;
29+
30+
import com.diffplug.common.base.Preconditions;
31+
import com.diffplug.common.collect.ImmutableList;
32+
import com.diffplug.spotless.FormatterStep;
33+
import com.diffplug.spotless.Provisioner;
34+
35+
class RegisterDependenciesInRoot {
36+
static final GradleVersion STRICT_CONFIG_ACCESS_WARNING = GradleVersion.version("6.0");
37+
38+
static final String ENABLE_KEY = "spotless_register_dependencies_in_root";
39+
static final String TASK_NAME = "spotlessRegisterDependencies";
40+
41+
/** Determines if the "spotless_register_dependencies_in_root" mode is enabled. */
42+
public static boolean isEnabled(Project project) {
43+
Object enable = project.getRootProject().findProperty(ENABLE_KEY);
44+
if (Boolean.TRUE.equals(enable) || "true".equals(enable)) {
45+
return true;
46+
}
47+
boolean onlyOneProjectInEntireBuild = project == project.getRootProject()
48+
&& project.getChildProjects().isEmpty();
49+
if (onlyOneProjectInEntireBuild) {
50+
return false;
51+
}
52+
if (GradleVersion.current().compareTo(STRICT_CONFIG_ACCESS_WARNING) >= 0) {
53+
return true;
54+
}
55+
return false;
56+
}
57+
58+
/** Models a request to the provisioner. */
59+
private static class Request {
60+
final boolean withTransitives;
61+
final ImmutableList<String> mavenCoords;
62+
63+
public Request(boolean withTransitives, Collection<String> mavenCoords) {
64+
this.withTransitives = withTransitives;
65+
this.mavenCoords = ImmutableList.copyOf(mavenCoords);
66+
}
67+
68+
@Override
69+
public int hashCode() {
70+
return withTransitives ? mavenCoords.hashCode() : ~mavenCoords.hashCode();
71+
}
72+
73+
@Override
74+
public boolean equals(Object obj) {
75+
if (this == obj) {
76+
return true;
77+
} else if (obj instanceof Request) {
78+
Request o = (Request) obj;
79+
return o.withTransitives == withTransitives && o.mavenCoords.equals(mavenCoords);
80+
} else {
81+
return false;
82+
}
83+
}
84+
85+
@Override
86+
public String toString() {
87+
String coords = mavenCoords.toString();
88+
StringBuilder builder = new StringBuilder();
89+
builder.append(coords.substring(1, coords.length() - 1)); // strip off []
90+
if (withTransitives) {
91+
builder.append(" with transitives");
92+
} else {
93+
builder.append(" no transitives");
94+
}
95+
return builder.toString();
96+
}
97+
}
98+
99+
/** The provisioner used for all sub-projects. */
100+
static class SubProvisioner implements Provisioner {
101+
private final RootProvisioner root;
102+
private final Project project;
103+
104+
public SubProvisioner(RootProvisioner root, Project project) {
105+
this.root = Objects.requireNonNull(root);
106+
this.project = Objects.requireNonNull(project);
107+
}
108+
109+
@Override
110+
public Set<File> provisionWithTransitives(boolean withTransitives, Collection<String> mavenCoordinates) {
111+
return root.provisionForSub(project, withTransitives, mavenCoordinates);
112+
}
113+
}
114+
115+
/** The provisioner used for the root project. */
116+
static class RootProvisioner implements Provisioner {
117+
private final Project rootProject;
118+
119+
RootProvisioner(Project rootProject) {
120+
Preconditions.checkArgument(rootProject == rootProject.getRootProject());
121+
this.rootProject = rootProject;
122+
}
123+
124+
@Override
125+
public Set<File> provisionWithTransitives(boolean withTransitives, Collection<String> mavenCoordinates) {
126+
return doProvision(new Request(withTransitives, mavenCoordinates), true);
127+
}
128+
129+
private Map<Request, Set<File>> cache = new HashMap<>();
130+
131+
/** Guaranteed to return non-null for internal requests, but might return null for an external request which isn't cached already. */
132+
private synchronized @Nullable Set<File> doProvision(Request req, boolean isRoot) {
133+
Set<File> result = cache.get(req);
134+
if (result != null) {
135+
return result;
136+
}
137+
if (isRoot) {
138+
result = GradleProvisioner.fromRootBuildscript(rootProject).provisionWithTransitives(req.withTransitives, req.mavenCoords);
139+
cache.put(req, result);
140+
return result;
141+
} else {
142+
return null;
143+
}
144+
}
145+
146+
private Set<File> provisionForSub(Project project, boolean withTransitives, Collection<String> mavenCoordinates) {
147+
Request req = new Request(withTransitives, mavenCoordinates);
148+
Set<File> result = doProvision(req, false);
149+
if (result != null) {
150+
return result;
151+
} else {
152+
// if it wasn't cached, complain loudly and use the crappy workaround
153+
GradleProvisioner.logger.severe(warningMsg(req));
154+
return GradleProvisioner.fromRootBuildscript(project).provisionWithTransitives(withTransitives, mavenCoordinates);
155+
}
156+
}
157+
}
158+
159+
private static String warningMsg(Request requestedDeps) {
160+
FormatterStep beingResolved = FormatterStep.lazyStepBeingResolvedInThisThread();
161+
return String.format(
162+
"This subproject is using a formatter that was not used in the root project. To enable%n" +
163+
"performance optimzations (and avoid Gradle 7 deprecation warnings), you must declare%n" +
164+
"all of your formatters within the root project. For example, if your subproject has%n" +
165+
"a `java {}` block but your root project does not, just add a matching `java {}` block to%n" +
166+
"your root project. If you want to make it clear that it is intentional that the target%n" +
167+
"is empty, you can do this in your root build.gradle:%n" +
168+
"%n" +
169+
" spotless {%n" +
170+
" java {%n" +
171+
" targetEmptyForDeclaration()%n" +
172+
" [...same steps as subproject...]%n" +
173+
" }%n" +
174+
" }%n" +
175+
"%n" +
176+
"To help you figure out which block is missing, the step you are missing is%n" +
177+
" step name: %s%n" +
178+
" requested: %s%n",
179+
beingResolved == null ? "(unknown)" : beingResolved.getName(),
180+
requestedDeps);
181+
}
182+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2016 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.gradle.spotless;
17+
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.nio.charset.StandardCharsets;
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
import java.util.Set;
24+
25+
import org.gradle.api.DefaultTask;
26+
import org.gradle.api.tasks.Input;
27+
import org.gradle.api.tasks.Internal;
28+
import org.gradle.api.tasks.OutputFile;
29+
import org.gradle.api.tasks.TaskAction;
30+
31+
import com.diffplug.common.collect.Sets;
32+
import com.diffplug.common.io.Files;
33+
import com.diffplug.spotless.FormatterStep;
34+
35+
/**
36+
* The minimal task required to force all SpotlessTasks in the root
37+
* project to trigger their dependency resolution, so that they will
38+
* be cached for subproject tasks to slurp from.
39+
*/
40+
public class RegisterDependenciesTask extends DefaultTask {
41+
@Input
42+
public List<FormatterStep> getSteps() {
43+
List<FormatterStep> allSteps = new ArrayList<>();
44+
Set<SpotlessTask> alreadyAdded = Sets.newIdentityHashSet();
45+
for (Object dependsOn : getDependsOn()) {
46+
// in Gradle 2.14, we can have a single task listed as a dep twice,
47+
// and we can also have non-tasks listed as a dep
48+
if (dependsOn instanceof SpotlessTask) {
49+
SpotlessTask task = (SpotlessTask) dependsOn;
50+
if (alreadyAdded.add(task)) {
51+
allSteps.addAll(task.getSteps());
52+
}
53+
}
54+
}
55+
return allSteps;
56+
}
57+
58+
File unitOutput;
59+
60+
@OutputFile
61+
public File getUnitOutput() {
62+
return unitOutput;
63+
}
64+
65+
RegisterDependenciesInRoot.RootProvisioner rootProvisioner;
66+
67+
@Internal
68+
public RegisterDependenciesInRoot.RootProvisioner getRootProvisioner() {
69+
return rootProvisioner;
70+
}
71+
72+
void setup() {
73+
unitOutput = new File(getProject().getBuildDir(), "tmp/spotless-register-dependencies");
74+
rootProvisioner = new RegisterDependenciesInRoot.RootProvisioner(getProject());
75+
}
76+
77+
@TaskAction
78+
public void trivialFunction() throws IOException {
79+
Files.createParentDirs(unitOutput);
80+
Files.write("unit", unitOutput, StandardCharsets.UTF_8);
81+
}
82+
}

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

+14
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.LinkedHashMap;
2424
import java.util.Map;
2525

26+
import javax.annotation.Nullable;
27+
2628
import org.gradle.api.Action;
2729
import org.gradle.api.GradleException;
2830
import org.gradle.api.Project;
@@ -39,6 +41,7 @@
3941
public class SpotlessExtension {
4042
final Project project;
4143
final Task rootCheckTask, rootApplyTask;
44+
final @Nullable RegisterDependenciesTask registerDependenciesTask;
4245

4346
static final String EXTENSION = "spotless";
4447
static final String CHECK = "Check";
@@ -58,6 +61,17 @@ public SpotlessExtension(Project project) {
5861
rootApplyTask = project.task(EXTENSION + APPLY);
5962
rootApplyTask.setGroup(TASK_GROUP);
6063
rootApplyTask.setDescription(APPLY_DESCRIPTION);
64+
boolean registerDependenciesInRoot = RegisterDependenciesInRoot.isEnabled(project);
65+
if (registerDependenciesInRoot) {
66+
if (project.getRootProject() == project) {
67+
registerDependenciesTask = project.getTasks().create(RegisterDependenciesInRoot.TASK_NAME, RegisterDependenciesTask.class);
68+
registerDependenciesTask.setup();
69+
} else {
70+
registerDependenciesTask = project.getRootProject().getPlugins().apply(SpotlessPlugin.class).spotlessExtension.registerDependenciesTask;
71+
}
72+
} else {
73+
registerDependenciesTask = null;
74+
}
6175
}
6276

6377
/** Line endings (if any). */

0 commit comments

Comments
 (0)