[Gradle] Dependencies graph: show file dependencies IDEA-218166
[idea/community.git] / plugins / gradle / tooling-extension-impl / src / org / jetbrains / plugins / gradle / tooling / tasks / DependenciesReport.groovy
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package org.jetbrains.plugins.gradle.tooling.tasks
3
4 import com.google.gson.GsonBuilder
5 import com.intellij.openapi.externalSystem.model.project.dependencies.*
6 import groovy.transform.CompileStatic
7 import org.gradle.api.DefaultTask
8 import org.gradle.api.Describable
9 import org.gradle.api.Project
10 import org.gradle.api.artifacts.Configuration
11 import org.gradle.api.artifacts.Dependency
12 import org.gradle.api.artifacts.FileCollectionDependency
13 import org.gradle.api.artifacts.component.ModuleComponentIdentifier
14 import org.gradle.api.artifacts.component.ProjectComponentIdentifier
15 import org.gradle.api.artifacts.result.ResolutionResult
16 import org.gradle.api.file.FileCollection
17 import org.gradle.api.tasks.Input
18 import org.gradle.api.tasks.OutputFile
19 import org.gradle.api.tasks.TaskAction
20 import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableDependency
21 import org.gradle.api.tasks.diagnostics.internal.graph.nodes.RenderableModuleResult
22 import org.gradle.util.GradleVersion
23
24 @CompileStatic
25 class DependenciesReport extends DefaultTask {
26
27   @Input
28   List<String> configurations = []
29   @OutputFile
30   File outputFile
31
32   @TaskAction
33   void generate() {
34     Collection<Configuration> configurationList
35     if (configurations.isEmpty()) {
36       configurationList = project.configurations
37     }
38     else {
39       configurationList = new ArrayList<>()
40       for (configurationName in configurations) {
41         def configuration = project.configurations.findByName(configurationName)
42         if (configuration != null) {
43           configurationList.add(configuration)
44         }
45       }
46     }
47
48     def projectNameFunction = new ProjectNameFunction()
49     List<DependencyScopeNode> graph = []
50     for (configuration in configurationList) {
51       if (!configuration.isCanBeResolved()) continue
52       graph.add(doBuildDependenciesGraph(configuration, project, projectNameFunction))
53     }
54     outputFile.parentFile.mkdirs()
55     outputFile.text = new GsonBuilder().create().toJson(graph)
56   }
57
58   static DependencyScopeNode buildDependenciesGraph(Configuration configuration, Project project) {
59     return doBuildDependenciesGraph(configuration, project, new ProjectNameFunction())
60   }
61
62   private static DependencyScopeNode doBuildDependenciesGraph(Configuration configuration,
63                                                               Project project,
64                                                               ProjectNameFunction projectNameFunction) {
65     if (!project.configurations.contains(configuration)) {
66       throw new IllegalArgumentException("configurations of the project should be used")
67     }
68     ResolutionResult resolutionResult = configuration.getIncoming().getResolutionResult()
69     RenderableDependency root = new RenderableModuleResult(resolutionResult.root)
70     String configurationName = configuration.name
71     IdGenerator idGenerator = new IdGenerator()
72     long id = idGenerator.getId(root, configurationName)
73     String scopeDisplayName = "project " + project.path + " (" + configurationName + ")"
74     DependencyScopeNode node = new DependencyScopeNode(id, configurationName, scopeDisplayName, configuration.getDescription())
75     node.setResolutionState(root.resolutionState.name())
76     for (Dependency dependency : configuration.getAllDependencies()) {
77       if (dependency instanceof FileCollectionDependency) {
78         FileCollection fileCollection = ((FileCollectionDependency)dependency).getFiles();
79         if (fileCollection instanceof Configuration) continue;
80         def files = fileCollection.files
81         if (files.isEmpty()) continue
82
83         String displayName = null
84         if (fileCollection instanceof Describable) {
85           displayName = ((Describable)fileCollection).displayName
86         } else {
87           def string = fileCollection.toString()
88           if ("file collection" != string) {
89             displayName = string
90           }
91         }
92
93         if (displayName != null) {
94           long fileDepId = idGenerator.getId(displayName, configurationName)
95           node.dependencies.add(new FileCollectionDependencyNodeImpl(fileDepId, displayName, fileCollection.getAsPath()))
96         }
97         else {
98           for (File file : files) {
99             long fileDepId = idGenerator.getId(file.path, configurationName)
100             node.dependencies.add(new FileCollectionDependencyNodeImpl(fileDepId, file.name, file.path))
101           }
102         }
103       }
104     }
105
106     Map<Object, DependencyNode> added = [:]
107     for (RenderableDependency child in root.getChildren()) {
108       node.dependencies.add(toNode(child, configurationName, added, idGenerator, projectNameFunction))
109     }
110     return node
111   }
112
113   static private DependencyNode toNode(RenderableDependency dependency,
114                                        String configurationName,
115                                        Map<Object, DependencyNode> added,
116                                        IdGenerator idGenerator,
117                                        ProjectNameFunction projectNameFunction) {
118     long id = idGenerator.getId(dependency, configurationName)
119     DependencyNode alreadySeenNode = added.get(id)
120     if (alreadySeenNode != null) {
121       return new ReferenceNode(id)
122     }
123
124     AbstractDependencyNode node
125     if (dependency.id instanceof ProjectComponentIdentifier) {
126       ProjectComponentIdentifier projectId = dependency.id as ProjectComponentIdentifier
127       node = new ProjectDependencyNodeImpl(id, projectNameFunction.fun(projectId))
128     }
129     else if (dependency.id instanceof ModuleComponentIdentifier) {
130       ModuleComponentIdentifier moduleId = dependency.id as ModuleComponentIdentifier
131       node = new ArtifactDependencyNodeImpl(id, moduleId.group, moduleId.module, moduleId.version)
132     }
133     else {
134       node = new UnknownDependencyNode(id, dependency.name)
135     }
136     node.setResolutionState(dependency.resolutionState.name())
137     added.put(id, node)
138     Iterator<? extends RenderableDependency> iterator = dependency.getChildren().iterator()
139     while (iterator.hasNext()) {
140       RenderableDependency child = iterator.next()
141       node.dependencies.add(toNode(child, configurationName, added, idGenerator, projectNameFunction))
142     }
143     return node
144   }
145
146   static class ProjectNameFunction {
147     def is45OrNewer = GradleVersion.current() >= GradleVersion.version("4.5")
148
149     String fun(ProjectComponentIdentifier identifier) {
150       return is45OrNewer ? identifier.projectName : identifier.projectPath
151     }
152   }
153
154   private static class IdGenerator {
155     private Map<String, Long> idMap = new HashMap<>()
156     private long value
157
158     private long getId(String prefix, String configurationName) {
159       def key = prefix + '_' + configurationName
160       def id = idMap.get(key)
161       if (id == null) {
162         idMap[key] = ++value
163         id = value
164       }
165       return id
166     }
167
168     private long getId(RenderableDependency dependency, String configurationName) {
169       return getId(dependency.id.toString(), configurationName)
170     }
171   }
172 }