-
Notifications
You must be signed in to change notification settings - Fork 82
Expand file tree
/
Copy pathGradleProjectModelBuilder.java
More file actions
311 lines (293 loc) · 12.7 KB
/
GradleProjectModelBuilder.java
File metadata and controls
311 lines (293 loc) · 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.microsoft.gradle;
import com.microsoft.gradle.api.GradleClosure;
import com.microsoft.gradle.api.GradleDependencyNode;
import com.microsoft.gradle.api.GradleDependencyType;
import com.microsoft.gradle.api.GradleField;
import com.microsoft.gradle.api.GradleMethod;
import com.microsoft.gradle.api.GradleProjectModel;
import com.microsoft.gradle.api.GradleTask;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.ResolvableDependencies;
import org.gradle.api.artifacts.result.DependencyResult;
import org.gradle.api.artifacts.result.ResolutionResult;
import org.gradle.api.artifacts.result.ResolvedComponentResult;
import org.gradle.api.artifacts.result.ResolvedDependencyResult;
import org.gradle.api.initialization.dsl.ScriptHandler;
import org.gradle.api.internal.initialization.DefaultScriptHandler;
import org.gradle.api.internal.tasks.TaskContainerInternal;
import org.gradle.api.plugins.Convention;
import org.gradle.api.plugins.ExtensionsSchema;
import org.gradle.api.plugins.ExtensionsSchema.ExtensionSchema;
import org.gradle.api.reflect.TypeOf;
import org.gradle.api.tasks.JavaExec;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.testing.Test;
import org.gradle.internal.classpath.ClassPath;
import org.gradle.plugins.ide.internal.tooling.model.DefaultGradleProject;
import org.gradle.tooling.provider.model.ToolingModelBuilder;
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
import org.gradle.util.GradleVersion;
public class GradleProjectModelBuilder implements ToolingModelBuilder {
private static String MINIMAL_SUPPORTED_PLUGIN_CLOSURE_VERSION = "5.0";
private Set<GradleTask> cachedTasks = new HashSet<>();
private ToolingModelBuilderRegistry registry;
public GradleProjectModelBuilder(ToolingModelBuilderRegistry registry) {
this.registry = registry;
}
public boolean canBuild(String modelName) {
return modelName.equals(GradleProjectModel.class.getName());
}
public Object buildAll(String modelName, Project project) {
cachedTasks.clear();
DefaultGradleProject gradleProject = (DefaultGradleProject) this.registry
.getBuilder("org.gradle.tooling.model.GradleProject").buildAll(modelName, project);
if (gradleProject == null) {
return null;
}
GradleProjectModel rootModel = buildModel(project, project.getName(), gradleProject);
// add task selectors for root project
Set<String> taskNames = new HashSet<>();
for (GradleTask existingTask : rootModel.getTasks()) {
taskNames.add(existingTask.getName());
}
for (GradleTask task : cachedTasks) {
if (!taskNames.contains(task.getName())) {
taskNames.add(task.getName());
String path = task.getPath();
int index = path.lastIndexOf(":");
if (index > -1) {
// use task selector to run a task for all subprojects
path = path.substring(index);
}
GradleTask newTask = new DefaultGradleTask(task.getName(), task.getGroup(), path, project.getName(),
project.getBuildscript().getSourceFile().getAbsolutePath(), task.getRootProject(),
task.getDescription(), task.getDebuggable());
rootModel.getTasks().add(newTask);
}
}
return rootModel;
}
private GradleProjectModel buildModel(Project project, String rootProjectName, DefaultGradleProject gradleProject) {
if (project == null) {
return null;
}
ScriptHandler buildScript = project.getBuildscript();
ClassPath classpath = ((DefaultScriptHandler) buildScript).getScriptClassPath();
List<String> scriptClasspaths = new ArrayList<>();
classpath.getAsFiles().forEach((file) -> {
scriptClasspaths.add(file.getAbsolutePath());
});
GradleDependencyNode node = generateDefaultGradleDependencyNode(project);
List<String> plugins = getPlugins(project);
List<GradleClosure> closures = getPluginClosures(project);
List<GradleProjectModel> subModels = new ArrayList<>();
for (DefaultGradleProject subDefaultGradleProject : gradleProject.getChildren()) {
// Query sub projects when both gradleProject and project contain them
Map<String, Project> childProjects = project.getChildProjects();
String projectName = subDefaultGradleProject.getName();
if (childProjects.keySet().contains(projectName)) {
GradleProjectModel subModel = buildModel(childProjects.get(projectName), rootProjectName,
subDefaultGradleProject);
if (subModel != null) {
subModels.add(subModel);
}
}
}
List<GradleTask> tasks = getGradleTasks(project, rootProjectName, gradleProject);
return new DefaultGradleProjectModel(project.getParent() == null, project.getProjectDir().getAbsolutePath(),
subModels, tasks, node, plugins, closures, scriptClasspaths);
}
private GradleDependencyNode generateDefaultGradleDependencyNode(Project project) {
DefaultGradleDependencyNode rootNode = new DefaultGradleDependencyNode(project.getName(),
GradleDependencyType.PROJECT);
ConfigurationContainer configurationContainer = project.getConfigurations();
// iterate through a snapshot of apparent configurations, because resolving
// dependencies can trigger plugins dynamically adding other configurations
// (e.g. io.quarkus plugin)
for (String configName : new TreeSet<>(configurationContainer.getNames())) {
Configuration config = configurationContainer.getByName(configName);
if (!config.isCanBeResolved()) {
continue;
}
DefaultGradleDependencyNode configNode = new DefaultGradleDependencyNode(config.getName(),
GradleDependencyType.CONFIGURATION);
ResolvableDependencies incoming = config.getIncoming();
ResolutionResult resolutionResult = incoming.getResolutionResult();
ResolvedComponentResult rootResult = resolutionResult.getRoot();
Set<? extends DependencyResult> dependencies = rootResult.getDependencies();
Set<String> dependencySet = new HashSet<>();
for (DependencyResult dependency : dependencies) {
if (dependency instanceof ResolvedDependencyResult) {
DefaultGradleDependencyNode dependencyNode = resolveDependency(
(ResolvedDependencyResult) dependency, dependencySet);
configNode.addChildren(dependencyNode);
}
}
if (!configNode.getChildren().isEmpty()) {
rootNode.addChildren(configNode);
}
}
return rootNode;
}
private DefaultGradleDependencyNode resolveDependency(ResolvedDependencyResult result, Set<String> dependencySet) {
DefaultGradleDependencyNode dependencyNode = new DefaultGradleDependencyNode(
result.getSelected().getModuleVersion().getGroup() + ":"
+ result.getSelected().getModuleVersion().getName() + ":"
+ result.getSelected().getModuleVersion().getVersion(),
GradleDependencyType.DEPENDENCY);
if (dependencySet.add(dependencyNode.getName())) {
Set<? extends DependencyResult> dependencies = result.getSelected().getDependencies();
for (DependencyResult dependency : dependencies) {
if (dependency instanceof ResolvedDependencyResult) {
DefaultGradleDependencyNode childNode = resolveDependency((ResolvedDependencyResult) dependency,
dependencySet);
dependencyNode.addChildren(childNode);
}
}
}
return dependencyNode;
}
private List<String> getPlugins(Project project) {
Convention convention = project.getConvention();
return new ArrayList<>(convention.getPlugins().keySet());
}
private List<GradleClosure> getPluginClosures(Project project) {
if (GradleVersion.version(project.getGradle().getGradleVersion())
.compareTo(GradleVersion.version(MINIMAL_SUPPORTED_PLUGIN_CLOSURE_VERSION)) < 0) {
return Collections.emptyList();
}
Convention convention = project.getConvention();
ExtensionsSchema extensionsSchema = convention.getExtensionsSchema();
List<GradleClosure> closures = new ArrayList<>();
for (ExtensionSchema schema : extensionsSchema.getElements()) {
TypeOf<?> publicType = schema.getPublicType();
Class<?> concreteClass = publicType.getConcreteClass();
closures.addAll(buildClosure(schema.getName(), concreteClass));
}
return closures;
}
/**
* @param closureName
* @param concreteClass
* @return
*/
private List<DefaultGradleClosure> buildClosure(String closureName, Class<?> concreteClass) {
List<DefaultGradleClosure> closures = new ArrayList<>();
List<GradleMethod> methods = new ArrayList<>();
List<GradleField> fields = new ArrayList<>();
for (Method method : concreteClass.getMethods()) {
String name = method.getName();
List<String> parameterTypes = new ArrayList<>();
for (Class<?> parameterType : method.getParameterTypes()) {
parameterTypes.add(parameterType.getName());
}
// check for nested closure methods and include them in the final closure list
// for completions.
if (method.getGenericParameterTypes().length == 1) {
Type parameterType = method.getGenericParameterTypes()[0];
if (parameterType.getTypeName().startsWith("org.gradle.api.Action<")
&& (parameterType instanceof ParameterizedType)) {
Type[] actualTypeArguments = ((ParameterizedType) parameterType).getActualTypeArguments();
if (actualTypeArguments.length == 1) {
try {
closures.addAll(buildClosure(closureName.concat(".").concat(name),
concreteClass.getClassLoader().loadClass(actualTypeArguments[0].getTypeName())));
} catch (ClassNotFoundException e) {
// continue if we cannot find the extension class.
}
}
}
}
methods.add(new DefaultGradleMethod(name, parameterTypes, isDeprecated(method)));
int modifiers = method.getModifiers();
// See:
// https://docs.gradle.org/current/userguide/custom_gradle_types.html#managed_properties
// we offer managed properties for an abstract getter method
if (name.startsWith("get") && name.length() > 3 && Modifier.isPublic(modifiers)
&& Modifier.isAbstract(modifiers)) {
fields.add(new DefaultGradleField(name.substring(3, 4).toLowerCase() + name.substring(4),
isDeprecated(method)));
}
}
for (Field field : concreteClass.getFields()) {
fields.add(new DefaultGradleField(field.getName(), isDeprecated(field)));
}
closures.add(new DefaultGradleClosure(closureName, methods, fields));
return closures;
}
/**
* get Task information from the given models. DefaultGradleProject is used to
* get task list and Project is used to get debug information.
*
* @param project
* the given org.gradle.api.Project model
* @param rootProjectName
* the root project name
* @param gradleProject
* the given
* org.gradle.plugins.ide.internal.tooling.model.DefaultGradleProject
* model
* @return the task list of the corresponding project
*/
private List<GradleTask> getGradleTasks(Project project, String rootProjectName,
DefaultGradleProject gradleProject) {
List<GradleTask> tasks = new ArrayList<>();
gradleProject.getTasks().forEach((task) -> {
String group = task.getGroup() == null ? null : task.getGroup();
String description = task.getDescription() == null ? null : task.getDescription();
GradleTask newTask = new DefaultGradleTask(task.getName(), group, task.getPath(), gradleProject.getName(),
gradleProject.getBuildScript().getSourceFile().getAbsolutePath(), rootProjectName, description,
false);
tasks.add(newTask);
cachedTasks.add(newTask);
});
for (GradleTask gradleTask : tasks) {
// try to fetch debug information
TaskContainer taskContainer = project.getTasks();
if (taskContainer instanceof TaskContainerInternal) {
TaskContainerInternal taskContainerInternal = (TaskContainerInternal) taskContainer;
taskContainerInternal.discoverTasks();
taskContainerInternal.realize();
try {
Task task = taskContainerInternal.getByName(gradleTask.getName());
if ((task instanceof JavaExec || task instanceof Test)
&& (gradleTask instanceof DefaultGradleTask)) {
((DefaultGradleTask) gradleTask).setDebuggable(true);
}
} catch (Exception e) {
// for lazy tasks, `getByName()` will return an exception in some cases, we
// ignore them here
continue;
}
}
}
return tasks;
}
private boolean isDeprecated(AccessibleObject object) {
for (Annotation annotation : object.getDeclaredAnnotations()) {
if (annotation.toString().contains("Deprecated")) {
return true;
}
}
return false;
}
}