a70ebf92463ecf4944c9122fa03825a073618364
[idea/community.git] / plugins / gradle / tooling-extension-impl / src / org / jetbrains / plugins / gradle / tooling / util / DependencyResolverImpl.groovy
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
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
17
18 package org.jetbrains.plugins.gradle.tooling.util
19
20 import com.google.common.collect.ArrayListMultimap
21 import com.google.common.collect.Lists
22 import com.google.common.collect.Multimap
23 import org.gradle.api.Project
24 import org.gradle.api.artifacts.Configuration
25 import org.gradle.api.artifacts.Dependency
26 import org.gradle.api.artifacts.ModuleVersionIdentifier
27 import org.gradle.api.artifacts.ProjectDependency
28 import org.gradle.api.artifacts.ResolvedArtifact
29 import org.gradle.api.artifacts.SelfResolvingDependency
30 import org.gradle.api.artifacts.component.ComponentIdentifier
31 import org.gradle.api.artifacts.component.ModuleComponentIdentifier
32 import org.gradle.api.artifacts.component.ModuleComponentSelector
33 import org.gradle.api.artifacts.component.ProjectComponentIdentifier
34 import org.gradle.api.artifacts.component.ProjectComponentSelector
35 import org.gradle.api.artifacts.result.*
36 import org.gradle.api.plugins.WarPlugin
37 import org.gradle.api.specs.Specs
38 import org.gradle.api.tasks.SourceSet
39 import org.gradle.api.tasks.SourceSetContainer
40 import org.gradle.api.tasks.SourceSetOutput
41 import org.gradle.api.tasks.bundling.AbstractArchiveTask
42 import org.gradle.api.tasks.compile.AbstractCompile
43 import org.gradle.language.base.artifact.SourcesArtifact
44 import org.gradle.language.java.artifact.JavadocArtifact
45 import org.gradle.plugins.ide.idea.IdeaPlugin
46 import org.gradle.util.GradleVersion
47 import org.jetbrains.annotations.NotNull
48 import org.jetbrains.annotations.Nullable
49 import org.jetbrains.plugins.gradle.model.*
50
51 import java.util.regex.Matcher
52 import java.util.regex.Pattern
53
54 /**
55  * @author Vladislav.Soroka
56  * @since 8/19/2015
57  */
58 class DependencyResolverImpl implements DependencyResolver {
59
60   private static isArtifactResolutionQuerySupported = GradleVersion.current().compareTo(GradleVersion.version("2.0")) >= 0
61   private static isDependencySubstitutionsSupported = GradleVersion.current().compareTo(GradleVersion.version("2.5")) >= 0
62
63   @NotNull
64   private final Project myProject
65   private final boolean myIsPreview
66   private final boolean myDownloadJavadoc
67   private final boolean myDownloadSources
68
69   @SuppressWarnings("GroovyUnusedDeclaration")
70   DependencyResolverImpl(@NotNull Project project, boolean isPreview) {
71     myProject = project
72     myIsPreview = isPreview
73     myDownloadJavadoc = false
74     myDownloadSources = false
75   }
76
77   DependencyResolverImpl(@NotNull Project project, boolean isPreview, boolean downloadJavadoc, boolean downloadSources) {
78     myProject = project
79     myIsPreview = isPreview
80     myDownloadJavadoc = downloadJavadoc
81     myDownloadSources = downloadSources
82   }
83
84   @Override
85   Collection<ExternalDependency> resolveDependencies(@Nullable String configurationName) {
86     return resolveDependencies(configurationName, null)
87   }
88
89   Collection<ExternalDependency> resolveDependencies(@Nullable String configurationName, @Nullable String scope) {
90     if (configurationName == null) return Collections.emptyList()
91     def (result, resolvedFileDependencies) = resolveDependencies(myProject.configurations.findByName(configurationName), scope)
92     return result
93   }
94
95   @Override
96   Collection<ExternalDependency> resolveDependencies(@Nullable Configuration configuration) {
97     def (result, resolvedFileDependencies) = resolveDependencies(configuration, null)
98     return result
99   }
100
101   def resolveDependencies(@Nullable Configuration configuration, @Nullable String scope) {
102     if (configuration == null) return [Collections.emptyList(), Collections.emptyList()]
103     if (configuration.allDependencies.isEmpty()) return [Collections.emptyList(), Collections.emptyList()]
104
105     final Collection<ExternalDependency> result = new LinkedHashSet<>()
106
107     def resolvedFileDependencies = []
108     if (!myIsPreview && isArtifactResolutionQuerySupported) {
109       def jvmLibrary = null
110       try {
111         jvmLibrary = Class.forName('org.gradle.jvm.JvmLibrary')
112       }
113       catch (ClassNotFoundException ignored) {
114       }
115       if (jvmLibrary == null) {
116         try {
117           jvmLibrary = Class.forName('org.gradle.runtime.jvm.JvmLibrary')
118         }
119         catch (ClassNotFoundException ignored) {
120         }
121       }
122       if (jvmLibrary != null) {
123         Class[] artifactTypes = ([myDownloadSources?SourcesArtifact:null, myDownloadJavadoc?JavadocArtifact:null] - null) as Class[];
124         Set<ResolvedArtifact> resolvedArtifacts = configuration.resolvedConfiguration.lenientConfiguration.getArtifacts(Specs.SATISFIES_ALL)
125
126         Multimap<ModuleVersionIdentifier, ResolvedArtifact> artifactMap = ArrayListMultimap.create()
127         resolvedArtifacts.each { artifactMap.put(it.moduleVersion.id, it) }
128         //noinspection GroovyAssignabilityCheck
129         Set<ComponentArtifactsResult> componentResults = myProject.dependencies.createArtifactResolutionQuery()
130           .forComponents(resolvedArtifacts.collect { toComponentIdentifier(it.moduleVersion.id) })
131           .withArtifacts(jvmLibrary, artifactTypes)
132           .execute()
133           .getResolvedComponents()
134
135         Map<ComponentIdentifier, ComponentArtifactsResult> componentResultsMap = [:];
136         componentResults.each { componentResultsMap.put(it.id, it) }
137
138         Multimap<ModuleComponentIdentifier, ProjectDependency> configurationProjectDependencies = ArrayListMultimap.create()
139         configuration.incoming.dependencies.findAll { it instanceof ProjectDependency }.each {
140           configurationProjectDependencies.put(toComponentIdentifier(it.group, it.name, it.version), it as ProjectDependency)
141         }
142
143         ResolutionResult resolutionResult = configuration.incoming.resolutionResult
144         if(!configuration.resolvedConfiguration.hasError()) {
145           def fileDeps = new LinkedHashSet<File>(configuration.incoming.files.files);
146           artifactMap.values().each {
147             fileDeps.remove(it.file)
148           }
149           fileDeps.each {
150             def fileCollectionDependency = new DefaultFileCollectionDependency([it])
151             fileCollectionDependency.scope = scope
152             result.add(fileCollectionDependency)
153           }
154         }
155
156         def dependencyResultsTransformer = new DependencyResultsTransformer(artifactMap, componentResultsMap, configurationProjectDependencies, scope)
157         result.addAll(dependencyResultsTransformer.transform(resolutionResult.root.dependencies))
158
159         resolvedFileDependencies.addAll(dependencyResultsTransformer.resolvedDepsFiles)
160       }
161     }
162
163     if (myIsPreview || !isArtifactResolutionQuerySupported) {
164       def projectDependencies = findDependencies(configuration, configuration.allDependencies, scope)
165       result.addAll(projectDependencies);
166     }
167     def fileDependencies = findAllFileDependencies(configuration.allDependencies, scope)
168     result.addAll(fileDependencies - resolvedFileDependencies)
169
170     return [new ArrayList(result), resolvedFileDependencies]
171   }
172
173   @Override
174   Collection<ExternalDependency> resolveDependencies(@NotNull SourceSet sourceSet) {
175     Collection<ExternalDependency> result = new ArrayList<>()
176
177     // resolve compile dependencies
178     def compileConfigurationName = sourceSet.compileConfigurationName
179     def compileClasspathConfiguration = myProject.configurations.findByName(compileConfigurationName + 'Classpath')
180     def originCompileConfiguration = myProject.configurations.findByName(compileConfigurationName)
181     def compileConfiguration = compileClasspathConfiguration ?: originCompileConfiguration
182
183     def compileScope = 'COMPILE'
184     def (compileDependencies, resolvedCompileFileDependencies) = resolveDependencies(compileConfiguration, compileScope)
185     // resolve runtime dependencies
186     def runtimeConfigurationName = sourceSet.runtimeConfigurationName
187     def runtimeConfiguration = myProject.configurations.findByName(runtimeConfigurationName)
188
189     def runtimeScope = 'RUNTIME'
190     def (runtimeDependencies, resolvedRuntimeFileDependencies) = resolveDependencies(runtimeConfiguration, runtimeScope)
191
192     def providedScope = 'PROVIDED'
193
194     Multimap<Object, ExternalDependency> resolvedMap = ArrayListMultimap.create()
195
196     boolean checkCompileOnlyDeps = compileClasspathConfiguration && !originCompileConfiguration.resolvedConfiguration.hasError()
197     new DependencyTraverser(compileDependencies).each {
198       def resolvedObj = resolve(it)
199       resolvedMap.put(resolvedObj, it)
200
201       if (checkCompileOnlyDeps &&
202           (resolvedObj instanceof Collection ? !originCompileConfiguration.containsAll(((Collection)resolvedObj).toArray()) :
203            !originCompileConfiguration.contains(resolvedObj))) {
204         ((AbstractExternalDependency)it).scope = providedScope
205       }
206     }
207
208     new DependencyTraverser(runtimeDependencies).each {
209       Collection<ExternalDependency> dependencies = resolvedMap.get(resolve(it));
210       if (dependencies && !dependencies.isEmpty() && it.dependencies.isEmpty()) {
211         runtimeDependencies.remove(it)
212         ((AbstractExternalDependency)it).scope = dependencies.first().scope
213       }
214       else {
215         resolvedMap.put(resolve(it), it)
216       }
217     }
218
219     result.addAll(compileDependencies)
220     result.addAll(runtimeDependencies)
221     result.unique()
222
223     // merge file dependencies
224     def jvmLanguages = ['Java', 'Groovy', 'Scala']
225     def sourceSetCompileTaskPrefix = sourceSet.name == 'main' ? '' : sourceSet.name
226     def compileTasks = jvmLanguages.collect { 'compile' + sourceSetCompileTaskPrefix.capitalize() + it }
227
228     Map<File, Integer> compileClasspathOrder = new LinkedHashMap()
229     Set<File> compileClasspathFiles = new LinkedHashSet<>()
230
231     compileTasks.each {
232       def compileTask = myProject.tasks.findByName(it)
233       if (compileTask instanceof AbstractCompile) {
234         try {
235           def files = new ArrayList<>(compileTask.classpath.files)
236           files.removeAll(compileClasspathFiles)
237           compileClasspathFiles.addAll(files)
238         }
239         catch (ignore) {
240         }
241       }
242     }
243
244     try {
245       compileClasspathFiles = compileClasspathFiles.isEmpty() ? sourceSet.compileClasspath.files : compileClasspathFiles
246     }
247     catch (ignore) {
248     }
249     int order = 0;
250     for (File file : compileClasspathFiles) {
251       compileClasspathOrder.put(file, order++);
252     }
253     Map<File, Integer> runtimeClasspathOrder = new LinkedHashMap()
254     order = 0;
255     Set<File> runtimeClasspathFiles = new LinkedHashSet<File>()
256     try {
257       def files = sourceSet.runtimeClasspath.files
258       for (File file : files) {
259         runtimeClasspathOrder.put(file, order++);
260       }
261       runtimeClasspathFiles.addAll(files)
262     }
263     catch (ignore) {
264     }
265
266     runtimeClasspathFiles -= compileClasspathFiles
267     runtimeClasspathFiles -= sourceSet.output.files
268     compileClasspathFiles -= sourceSet.output.files
269
270     Multimap<String, File> resolvedDependenciesMap = ArrayListMultimap.create()
271     resolvedDependenciesMap.putAll(compileScope, resolvedCompileFileDependencies)
272     resolvedDependenciesMap.putAll(runtimeScope, resolvedRuntimeFileDependencies)
273     Project rootProject = myProject.rootProject
274
275     new DependencyTraverser(result).each {
276       def dependency = it
277       def scope = dependency.scope
278       order = -1;
279       if (dependency instanceof ExternalProjectDependency) {
280         ExternalProjectDependency projectDependency = dependency
281         def project = rootProject.findProject(projectDependency.projectPath)
282         def configuration = project?.configurations?.findByName(projectDependency.configurationName)
283         configuration?.allArtifacts?.files?.files?.each {
284           resolvedDependenciesMap.put(scope, it)
285           def classpathOrderMap = scope == compileScope ? compileClasspathOrder :
286                                   scope == runtimeScope ? runtimeClasspathOrder : null
287           if (classpathOrderMap) {
288             def fileOrder = classpathOrderMap.get(it)
289             if (fileOrder != null && (order == -1 || fileOrder < order)) {
290               order = fileOrder
291             }
292           }
293         }
294
295         //noinspection GrUnresolvedAccess
296         if (project.hasProperty("sourceSets") && (project.sourceSets instanceof SourceSetContainer) && project.sourceSets.main) {
297           //noinspection GrUnresolvedAccess
298           addSourceSetOutputDirsAsSingleEntryLibraries(result, project.sourceSets.main, runtimeClasspathOrder, runtimeScope)
299         }
300       }
301       else if (dependency instanceof ExternalLibraryDependency) {
302         resolvedDependenciesMap.put(scope, dependency.file)
303         def classpathOrderMap = scope == compileScope ? compileClasspathOrder :
304                                 scope == runtimeScope ? runtimeClasspathOrder : null
305         if (classpathOrderMap) {
306           def fileOrder = classpathOrderMap.get(dependency.file)
307           order = fileOrder != null ? fileOrder : -1
308         }
309       }
310       else if (dependency instanceof FileCollectionDependency) {
311         for (File file : dependency.files) {
312           resolvedDependenciesMap.put(scope, file)
313           def classpathOrderMap = scope == compileScope ? compileClasspathOrder :
314                                   scope == runtimeScope ? runtimeClasspathOrder : null
315           if (classpathOrderMap) {
316             def fileOrder = classpathOrderMap.get(file)
317             if (fileOrder != null && (order == -1 || fileOrder < order)) {
318               order = fileOrder
319             }
320             if (order == 0) break
321           }
322         }
323       }
324
325       if (dependency instanceof AbstractExternalDependency) {
326         dependency.classpathOrder = order
327       }
328     }
329
330     compileClasspathFiles.removeAll(resolvedDependenciesMap.get(compileScope))
331     compileClasspathFiles.removeAll(resolvedDependenciesMap.get(providedScope))
332     runtimeClasspathFiles.removeAll(resolvedDependenciesMap.get(runtimeScope))
333     runtimeClasspathFiles.removeAll(resolvedDependenciesMap.get(compileScope))
334     runtimeClasspathFiles.removeAll(resolvedDependenciesMap.get(providedScope))
335
336     Collection<ExternalDependency> fileDependencies = new ArrayList<>()
337     mapFileDependencies(runtimeClasspathFiles, runtimeScope, fileDependencies)
338     mapFileDependencies(compileClasspathFiles, compileScope, fileDependencies)
339
340     fileDependencies.each {
341       def dependency = it
342       def scope = dependency.scope
343       order = -1;
344       if (dependency instanceof ExternalLibraryDependency) {
345         def classpathOrderMap = scope == compileScope ? compileClasspathOrder :
346                                 scope == runtimeScope ? runtimeClasspathOrder : null
347         if (classpathOrderMap) {
348           def fileOrder = classpathOrderMap.get(dependency.file)
349           order = fileOrder != null ? fileOrder : -1
350         }
351       }
352       if (dependency instanceof AbstractExternalDependency) {
353         dependency.classpathOrder = order
354       }
355     }
356     result.addAll(fileDependencies)
357
358     if (!compileClasspathFiles.isEmpty()) {
359       final compileClasspathFilesDependency = new DefaultFileCollectionDependency(compileClasspathFiles)
360       compileClasspathFilesDependency.scope = compileScope
361
362       order = -1;
363       for (File file : compileClasspathFiles) {
364         def fileOrder = compileClasspathOrder.get(file)
365         if (fileOrder != null && (order == -1 || fileOrder < order)) {
366           order = fileOrder
367         }
368         if (order == 0) break
369       }
370
371       if (order != -1) {
372         compileClasspathFilesDependency.classpathOrder = order
373       }
374       result.add(compileClasspathFilesDependency)
375     }
376
377     if (!runtimeClasspathFiles.isEmpty()) {
378       final runtimeClasspathFilesDependency = new DefaultFileCollectionDependency(runtimeClasspathFiles)
379       runtimeClasspathFilesDependency.scope = runtimeScope
380
381       order = -1;
382       for (File file : runtimeClasspathFiles) {
383         def fileOrder = runtimeClasspathOrder.get(file)
384         if (fileOrder != null && (order == -1 || fileOrder < order)) {
385           order = fileOrder
386         }
387         if (order == 0) break
388       }
389
390       runtimeClasspathFilesDependency.classpathOrder = order
391       result.add(runtimeClasspathFilesDependency)
392     }
393
394     addSourceSetOutputDirsAsSingleEntryLibraries(result, sourceSet, runtimeClasspathOrder, runtimeScope)
395
396     // handle provided dependencies
397     def providedConfigurations = new LinkedHashSet<Configuration>()
398     resolvedMap = ArrayListMultimap.create()
399     new DependencyTraverser(result).each { resolvedMap.put(resolve(it), it) }
400     final IdeaPlugin ideaPlugin = myProject.getPlugins().findPlugin(IdeaPlugin.class);
401     if (ideaPlugin) {
402       def scopes = ideaPlugin.model.module.scopes
403       def providedPlusScopes = scopes.get(providedScope)
404       if (providedPlusScopes && providedPlusScopes.get("plus")) {
405         providedConfigurations.addAll(providedPlusScopes.get("plus"))
406       }
407     }
408     if (sourceSet.name == 'main' && myProject.plugins.findPlugin(WarPlugin)) {
409       providedConfigurations.add(myProject.configurations.findByName('providedCompile'))
410       providedConfigurations.add(myProject.configurations.findByName('providedRuntime'))
411     }
412     providedConfigurations.each {
413       def (providedDependencies, resolvedProvidedFileDependencies) = resolveDependencies(it, providedScope)
414       new DependencyTraverser(providedDependencies).each {
415         Collection<ExternalDependency> dependencies = resolvedMap.get(resolve(it));
416         if (!dependencies.isEmpty()) {
417           if (it.dependencies.isEmpty()) {
418             providedDependencies.remove(it)
419           }
420           dependencies.each {
421             ((AbstractExternalDependency)it).scope = providedScope
422           }
423         }
424         else {
425           resolvedMap.put(resolve(it), it)
426         }
427       }
428       result.addAll(providedDependencies)
429     }
430
431     return removeDuplicates(resolvedMap, result)
432   }
433
434   private static List<ExternalDependency> removeDuplicates(
435     ArrayListMultimap<Object, ExternalDependency> resolvedMap,  List<ExternalDependency> result) {
436     resolvedMap.asMap().values().each {
437       def toRemove = []
438       def isCompileScope = false
439       def isProvidedScope = false
440       it.each {
441         if (it.dependencies.isEmpty()) {
442           toRemove.add(it)
443           if(it.scope == 'COMPILE') isCompileScope = true
444           else if(it.scope == 'PROVIDED') isProvidedScope = true
445         }
446       }
447       if (toRemove.size() != it.size()) {
448         result.removeAll(toRemove)
449       }
450       else if (toRemove.size() > 1) {
451         toRemove.drop(1)
452         result.removeAll(toRemove)
453       }
454       if(!toRemove.isEmpty()) {
455         def retained = it - toRemove
456         if(!retained.isEmpty()) {
457           def retainedDependency = retained.first() as AbstractExternalDependency
458           if(retainedDependency instanceof AbstractExternalDependency && retainedDependency.scope != 'COMPILE') {
459             if(isCompileScope) retainedDependency.scope = 'COMPILE'
460             else if(isProvidedScope) retainedDependency.scope = 'PROVIDED'
461           }
462         }
463       }
464     }
465
466     return result.unique()
467   }
468
469   static def resolve(ExternalDependency dependency) {
470     if (dependency instanceof ExternalLibraryDependency) {
471       return dependency.file
472     } else if (dependency instanceof FileCollectionDependency) {
473       return dependency.files
474     } else if (dependency instanceof ExternalMultiLibraryDependency) {
475       return dependency.files
476     } else if (dependency instanceof ExternalProjectDependency) {
477       return dependency.projectDependencyArtifacts
478     }
479     null
480   }
481
482   private static void addSourceSetOutputDirsAsSingleEntryLibraries(
483     Collection<ExternalDependency> dependencies,
484     SourceSet sourceSet,
485     Map<File, Integer> classpathOrder,
486     String scope) {
487     Set<File> runtimeOutputDirs = sourceSet.output.dirs.files
488     runtimeOutputDirs.each {
489       final runtimeOutputDirsDependency = new DefaultFileCollectionDependency([it])
490       runtimeOutputDirsDependency.scope = scope
491       def fileOrder = classpathOrder.get(it)
492       runtimeOutputDirsDependency.classpathOrder = fileOrder != null ? fileOrder : -1
493       dependencies.add(runtimeOutputDirsDependency)
494     }
495   }
496
497
498   @Nullable
499   ExternalLibraryDependency resolveLibraryByPath(File file, String scope) {
500     File modules2Dir = new File(myProject.gradle.gradleUserHomeDir, "caches/modules-2/files-2.1");
501     return resolveLibraryByPath(file, modules2Dir, scope)
502   }
503
504   @Nullable
505   static ExternalLibraryDependency resolveLibraryByPath(File file, File modules2Dir, String scope) {
506     File sourcesFile = null;
507     def modules2Path = modules2Dir.canonicalPath
508     def filePath = file.canonicalPath
509     if (filePath.startsWith(modules2Path)) {
510       List<File> parents = new ArrayList<>()
511       File parent = file.parentFile;
512       while(parent && !parent.name.equals(modules2Dir.name)) {
513         parents.add(parent)
514         parent = parent.parentFile
515       }
516
517       def groupDir = parents.get(parents.size() - 1)
518       def artifactDir = parents.get(parents.size() - 2)
519       def versionDir = parents.get(parents.size() - 3)
520
521       def parentFile = versionDir
522       if (parentFile != null) {
523         def hashDirs = parentFile.listFiles()
524         if (hashDirs != null) {
525           for (File hashDir : hashDirs) {
526             def sourcesJars = hashDir.listFiles(new FilenameFilter() {
527               @Override
528               boolean accept(File dir, String name) {
529                 return name.endsWith("sources.jar")
530               }
531             })
532
533             if (sourcesJars != null && sourcesJars.length > 0) {
534               sourcesFile = sourcesJars[0];
535               break
536             }
537           }
538
539           def packaging = resolvePackagingType(file);
540           def classifier = resolveClassifier(artifactDir.name, versionDir.name, file);
541           return new DefaultExternalLibraryDependency(
542             name: artifactDir.name,
543             group: groupDir.name,
544             packaging: packaging,
545             classifier: classifier,
546             version: versionDir.name,
547             file: file,
548             source: sourcesFile,
549             scope: scope
550           )
551         }
552       }
553     }
554
555     null
556   }
557
558   def mapFileDependencies(Set<File> fileDependencies, String scope, Collection<ExternalDependency> dependencies) {
559     File modules2Dir = new File(myProject.gradle.gradleUserHomeDir, "caches/modules-2/files-2.1");
560     List toRemove = new ArrayList()
561     for (File file : fileDependencies) {
562       def libraryDependency = resolveLibraryByPath(file, modules2Dir, scope)
563       if (libraryDependency) {
564         dependencies.add(libraryDependency)
565         toRemove.add(file)
566       }
567       else {
568         //noinspection GrUnresolvedAccess
569         def name = file.name.lastIndexOf('.').with { it != -1 ? file.name[0..<it] : file.name }
570         def sourcesFile = new File(file.parentFile, name + '-sources.jar')
571         if (sourcesFile.exists()) {
572           libraryDependency = new DefaultExternalLibraryDependency(
573             file: file,
574             source: sourcesFile,
575             scope: scope
576           )
577           if (libraryDependency) {
578             dependencies.add(libraryDependency)
579             toRemove.add(file)
580           }
581         }
582       }
583     }
584
585     fileDependencies.removeAll(toRemove)
586   }
587
588   @Nullable
589   static String resolvePackagingType(File file) {
590     if (file == null) return 'jar'
591     def path = file.getPath()
592     int index = path.lastIndexOf('.');
593     if (index < 0) return 'jar';
594     return path.substring(index + 1)
595   }
596
597   @Nullable
598   static String resolveClassifier(String name, String version, File file) {
599     String libraryFileName = getNameWithoutExtension(file);
600     final String mavenLibraryFileName = "$name-$version";
601     if (!mavenLibraryFileName.equals(libraryFileName)) {
602       Matcher matcher = Pattern.compile("$name-$version-(.*)").matcher(libraryFileName);
603       if (matcher.matches()) {
604         return matcher.group(1);
605       }
606     }
607     return null
608   }
609
610   static String getNameWithoutExtension(File file) {
611     if (file == null) return null
612     def name = file.name
613     int i = name.lastIndexOf('.');
614     if (i != -1) {
615       name = name.substring(0, i);
616     }
617     return name;
618   }
619
620   private static toComponentIdentifier(ModuleVersionIdentifier id) {
621     return new ModuleComponentIdentifierImpl(id.getGroup(), id.getName(), id.getVersion());
622   }
623
624   private static toComponentIdentifier(@NotNull String group, @NotNull String module, @NotNull String version) {
625     return new ModuleComponentIdentifierImpl(group, module, version);
626   }
627
628   private static Set<ExternalDependency> findAllFileDependencies(
629     Collection<Dependency> dependencies, String scope) {
630     Set<ExternalDependency> result = new LinkedHashSet<>()
631
632     dependencies.each {
633       try {
634         if (it instanceof SelfResolvingDependency && !(it instanceof ProjectDependency)) {
635           def files = it.resolve()
636           if (files && !files.isEmpty()) {
637             final dependency = new DefaultFileCollectionDependency(files)
638             dependency.scope = scope
639             result.add(dependency)
640           }
641         }
642       }
643       catch (ignore) {
644       }
645     }
646
647     return result;
648   }
649
650   private Set<ExternalDependency> findDependencies(
651     Configuration configuration,
652     Collection<Dependency> dependencies,
653     String scope) {
654     Set<ExternalDependency> result = new LinkedHashSet<>()
655
656     Set<ResolvedArtifact> resolvedArtifacts = myIsPreview ? new HashSet<>() :
657                                               configuration.resolvedConfiguration.lenientConfiguration.getArtifacts(Specs.SATISFIES_ALL)
658
659     Multimap<MyModuleIdentifier, ResolvedArtifact> artifactMap = ArrayListMultimap.create()
660     resolvedArtifacts.each { artifactMap.put(toMyModuleIdentifier(it.moduleVersion.id), it) }
661
662     dependencies.each {
663       try {
664         if (it instanceof ProjectDependency) {
665           def project = it.getDependencyProject()
666           final projectDependency = new DefaultExternalProjectDependency(
667             name: project.name,
668             group: project.group,
669             version: project.version,
670             scope: scope,
671             projectPath: project.path,
672             configurationName: it.projectConfiguration.name
673           )
674           projectDependency.projectDependencyArtifacts = it.projectConfiguration.allArtifacts.files.files
675           result.add(projectDependency)
676         }
677         else if (it instanceof Dependency) {
678           def artifactsResult = artifactMap.get(toMyModuleIdentifier(it.name, it.group))
679           if (artifactsResult && !artifactsResult.isEmpty()) {
680             def artifact = artifactsResult.first()
681             def packaging = artifact.extension ?: 'jar'
682             def classifier = artifact.classifier
683             File sourcesFile = resolveLibraryByPath(artifact.file, scope)?.source;
684             def libraryDependency = new DefaultExternalLibraryDependency(
685               name: it.name,
686               group: it.group,
687               packaging: packaging,
688               classifier: classifier,
689               version: artifact.moduleVersion.id.version,
690               scope: scope,
691               file: artifact.file,
692               source: sourcesFile
693             )
694             result.add(libraryDependency)
695           }
696           else {
697             if (!(it instanceof SelfResolvingDependency) && !myIsPreview) {
698               final dependency = new DefaultUnresolvedExternalDependency(
699                 name: it.name,
700                 group: it.group,
701                 version: it.version,
702                 scope: scope,
703                 failureMessage: "Could not find " + it.group + ":" + it.name + ":" + it.version
704               )
705               result.add(dependency)
706             }
707           }
708         }
709       }
710       catch (ignore) {
711       }
712     }
713
714     return result;
715   }
716
717   static class DependencyResultsTransformer {
718     Collection<DependencyResult> handledDependencyResults
719     Multimap<ModuleVersionIdentifier, ResolvedArtifact> artifactMap
720     Map<ComponentIdentifier, ComponentArtifactsResult> componentResultsMap
721     Multimap<ModuleComponentIdentifier, ProjectDependency> configurationProjectDependencies
722     String scope
723     Set<File> resolvedDepsFiles = []
724
725     DependencyResultsTransformer(
726       Multimap<ModuleVersionIdentifier, ResolvedArtifact> artifactMap,
727       Map<ComponentIdentifier, ComponentArtifactsResult> componentResultsMap,
728       Multimap<ModuleComponentIdentifier, ProjectDependency> configurationProjectDependencies,
729       String scope) {
730       this.handledDependencyResults = Lists.newArrayList()
731       this.artifactMap = artifactMap
732       this.componentResultsMap = componentResultsMap
733       this.configurationProjectDependencies = configurationProjectDependencies
734       this.scope = scope
735     }
736
737     Set<ExternalDependency> transform(Collection<DependencyResult> dependencyResults) {
738
739       Set<ExternalDependency> dependencies = new LinkedHashSet<>()
740       dependencyResults.each { DependencyResult dependencyResult ->
741
742         // dependency cycles check
743         if (!handledDependencyResults.contains(dependencyResult)) {
744           handledDependencyResults.add(dependencyResult)
745
746           if (dependencyResult instanceof ResolvedDependencyResult) {
747             def componentResult = dependencyResult.selected
748             def componentSelector = dependencyResult.requested
749             def componentIdentifier = toComponentIdentifier(componentResult.moduleVersion)
750             def name = componentResult.moduleVersion.name
751             def group = componentResult.moduleVersion.group
752             def version = componentResult.moduleVersion.version
753             def selectionReason = componentResult.selectionReason.description
754             if (componentSelector instanceof ProjectComponentSelector) {
755               def projectDependencies = configurationProjectDependencies.get(componentIdentifier)
756               projectDependencies.each {
757                 if (it.projectConfiguration.name == Dependency.DEFAULT_CONFIGURATION) {
758                   final dependency = new DefaultExternalProjectDependency(
759                     name: name,
760                     group: group,
761                     version: version,
762                     scope: scope,
763                     selectionReason: selectionReason,
764                     projectPath: componentSelector.projectPath,
765                     configurationName: it.projectConfiguration.name
766                   )
767                   dependency.projectDependencyArtifacts = artifactMap.get(componentResult.moduleVersion).collect { it.file }
768                   dependency.projectDependencyArtifacts.each { resolvedDepsFiles.add(it) }
769
770                   if (componentResult != dependencyResult.from) {
771                     dependency.dependencies.addAll(
772                       transform(componentResult.dependencies)
773                     )
774                   }
775                   dependencies.add(dependency)
776                 }
777                 else {
778                   final dependency = new DefaultExternalProjectDependency(
779                     name: name,
780                     group: group,
781                     version: version,
782                     scope: scope,
783                     selectionReason: selectionReason,
784                     projectPath: componentSelector.projectPath,
785                     configurationName: it.projectConfiguration.name
786                   )
787                   dependency.projectDependencyArtifacts = artifactMap.get(componentResult.moduleVersion).collect { it.file }
788                   dependency.projectDependencyArtifacts.each { resolvedDepsFiles.add(it) }
789
790                   if (componentResult != dependencyResult.from) {
791                     dependency.dependencies.addAll(
792                       transform(componentResult.dependencies)
793                     )
794                   }
795                   dependencies.add(dependency)
796
797                   def files = []
798                   def artifacts = it.projectConfiguration.getArtifacts()
799                   if (artifacts && !artifacts.isEmpty()) {
800                     def artifact = artifacts.first()
801                     if (artifact.hasProperty("archiveTask") &&
802                         (artifact.archiveTask instanceof org.gradle.api.tasks.bundling.AbstractArchiveTask)) {
803                       def archiveTask = artifact.archiveTask as AbstractArchiveTask
804                       resolvedDepsFiles.add(new File(archiveTask.destinationDir, archiveTask.archiveName))
805
806                       def mainSpec = archiveTask.mainSpec
807                       def sourcePaths
808                       if (mainSpec.metaClass.respondsTo(mainSpec, 'getSourcePaths')) {
809                         sourcePaths = mainSpec.getSourcePaths()
810                       }
811                       else if (mainSpec.hasProperty('sourcePaths')) {
812                         sourcePaths = mainSpec.sourcePaths
813                       }
814                       if (sourcePaths) {
815                         (sourcePaths.flatten() as List).each { def path ->
816                           if (path instanceof String) {
817                             def file = new File(path)
818                             if (file.isAbsolute()) {
819                               files.add(file)
820                             }
821                           }
822                           else if (path instanceof SourceSetOutput) {
823                             files.addAll(path.files)
824                           }
825                         }
826                       }
827                     }
828                   }
829
830                   if(!files.isEmpty()) {
831                     final fileCollectionDependency = new DefaultFileCollectionDependency(files)
832                     fileCollectionDependency.scope = scope
833                     dependencies.add(fileCollectionDependency)
834                     resolvedDepsFiles.addAll(files)
835                   }
836                 }
837               }
838             }
839             if (componentSelector instanceof ModuleComponentSelector) {
840               def artifacts = artifactMap.get(componentResult.moduleVersion)
841               def artifact = artifacts?.find { true }
842
843               if (artifacts?.isEmpty()) {
844                 dependencies.addAll(
845                   transform(componentResult.dependencies)
846                 )
847               }
848               boolean first = true
849               artifacts?.each {
850                 artifact = it
851                 def packaging = it.extension ?: 'jar'
852                 def classifier = it.classifier
853                 final dependency
854                 if (isDependencySubstitutionsSupported && artifact.id.componentIdentifier instanceof ProjectComponentIdentifier) {
855                   def artifactComponentIdentifier = artifact.id.componentIdentifier as ProjectComponentIdentifier
856                   dependency = new DefaultExternalProjectDependency(
857                     name: name,
858                     group: group,
859                     version: version,
860                     scope: scope,
861                     selectionReason: selectionReason,
862                     projectPath: artifactComponentIdentifier.projectPath,
863                     configurationName: Dependency.DEFAULT_CONFIGURATION
864                   )
865                   dependency.projectDependencyArtifacts = artifactMap.get(componentResult.moduleVersion).collect { it.file }
866                   dependency.projectDependencyArtifacts.each { resolvedDepsFiles.add(it) }
867                 }
868                 else {
869                   dependency = new DefaultExternalLibraryDependency(
870                     name: name,
871                     group: group,
872                     packaging: packaging,
873                     classifier: classifier,
874                     version: version,
875                     scope: scope,
876                     selectionReason: selectionReason,
877                     file: artifact.file
878                   )
879
880                   def artifactsResult = componentResultsMap.get(componentIdentifier)
881                   if (artifactsResult) {
882                     def sourcesResult = artifactsResult.getArtifacts(SourcesArtifact)?.find { it instanceof ResolvedArtifactResult }
883                     if (sourcesResult) {
884                       dependency.setSource(((ResolvedArtifactResult)sourcesResult).getFile())
885                     }
886                     def javadocResult = artifactsResult.getArtifacts(JavadocArtifact)?.find { it instanceof ResolvedArtifactResult }
887                     if (javadocResult) {
888                       dependency.setJavadoc(((ResolvedArtifactResult)javadocResult).getFile())
889                     }
890                   }
891                 }
892                 if (first) {
893                   dependency.dependencies.addAll(
894                     transform(componentResult.dependencies)
895                   )
896                   first = false
897                 }
898
899                 dependencies.add(dependency)
900                 resolvedDepsFiles.add(artifact.file)
901               }
902             }
903           }
904
905           if (dependencyResult instanceof UnresolvedDependencyResult) {
906             def componentResult = dependencyResult.attempted
907             if (componentResult instanceof ModuleComponentSelector) {
908               final dependency = new DefaultUnresolvedExternalDependency(
909                 name: componentResult.module,
910                 group: componentResult.group,
911                 version: componentResult.version,
912                 scope: scope,
913                 failureMessage: dependencyResult.failure.message
914               )
915               dependencies.add(dependency)
916             }
917           }
918         }
919       }
920
921       return dependencies
922     }
923   }
924
925   private static toMyModuleIdentifier(ModuleVersionIdentifier id) {
926     return new MyModuleIdentifier(name: id.getName(), group: id.getGroup());
927   }
928
929   private static toMyModuleIdentifier(String name, String group) {
930     return new MyModuleIdentifier(name: name, group: group);
931   }
932
933   static class MyModuleIdentifier {
934     String name
935     String group
936
937     boolean equals(o) {
938       if (this.is(o)) return true
939       if (!(o instanceof MyModuleIdentifier)) return false
940
941       MyModuleIdentifier that = (MyModuleIdentifier)o
942
943       if (group != that.group) return false
944       if (name != that.name) return false
945
946       return true
947     }
948
949     int hashCode() {
950       int result = (group != null ? group.hashCode() : 0)
951       result = 31 * result + (name != null ? name.hashCode() : 0)
952       return result
953     }
954
955     @Override
956     String toString() {
957       return "$group:$name"
958     }
959   }
960 }