Skip to content

Commit 7cb75da

Browse files
author
Horacio Sanson
committed
Fix gradle dependency resolver.
- Removed eclipse, dsl and idea resolvers. These returned Ok even when no dependencies were found, thus the last Gradle Cli resolver was never invoked. - Simplified the Gradle CLI resolver based on vim-android plugin. - Resolves also the platforms android API dependencies. - Resolves also the build intermediate classes (Fixes #7) - Resolves all gradle dependencies of the current project and all subprojects.
1 parent a86e326 commit 7cb75da

File tree

2 files changed

+73
-177
lines changed

2 files changed

+73
-177
lines changed

src/main/kotlin/org/javacs/kt/classpath/gradleDependencyResolver.kt

Lines changed: 14 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,7 @@ import java.io.File
99
import java.nio.file.FileSystems
1010
import java.nio.file.Files
1111
import java.nio.file.Path
12-
import java.nio.file.Paths
13-
import java.util.regex.Pattern
14-
import org.gradle.tooling.*
15-
import org.gradle.tooling.model.*
16-
import org.gradle.tooling.model.eclipse.*
17-
import org.gradle.tooling.model.idea.*
18-
import org.gradle.kotlin.dsl.tooling.models.*
12+
import org.gradle.tooling.GradleConnector
1913

2014
fun readBuildGradle(buildFile: Path): Set<Path> {
2115
val projectDirectory = buildFile.getParent()
@@ -26,10 +20,6 @@ fun readBuildGradle(buildFile: Path): Set<Path> {
2620
// The first successful dependency resolver is used
2721
// (evaluating them from top to bottom)
2822
var dependencies = firstNonNull<Set<Path>>(
29-
{ tryResolving("dependencies using Gradle task") { readDependenciesViaTask(projectDirectory) } },
30-
{ tryResolving("dependencies using Eclipse project model") { readDependenciesViaEclipseProject(connection) } },
31-
{ tryResolving("dependencies using Kotlin DSL model") { readDependenciesViaKotlinDSL(connection) } },
32-
{ tryResolving("dependencies using IDEA model") { readDependenciesViaIdeaProject(connection) } },
3323
{ tryResolving("dependencies using Gradle dependencies CLI") { readDependenciesViaGradleCLI(projectDirectory) } }
3424
).orEmpty()
3525

@@ -42,20 +32,17 @@ fun readBuildGradle(buildFile: Path): Set<Path> {
4232
}
4333

4434
private fun createTemporaryGradleFile(): File {
45-
val temp = File.createTempFile("tempGradle", ".config")
4635
val config = File.createTempFile("classpath", ".gradle")
4736

37+
LOG.info("Creating temporary gradle file ${config.absolutePath}")
38+
4839
config.bufferedWriter().use { configWriter ->
4940
ClassLoader.getSystemResourceAsStream("classpathFinder.gradle").bufferedReader().use { configReader ->
5041
configReader.copyTo(configWriter)
5142
}
5243
}
5344

54-
temp.bufferedWriter().use {
55-
it.write("rootProject { apply from: '${config.absolutePath.replace("\\", "\\\\")}'} ")
56-
}
57-
58-
return temp
45+
return config
5946
}
6047

6148
private fun getGradleCommand(workspace: Path): Path {
@@ -67,101 +54,27 @@ private fun getGradleCommand(workspace: Path): Path {
6754
}
6855
}
6956

70-
val jarArtifactOutputLine by lazy { Pattern.compile("^.+?\\.jar$") }
71-
72-
private fun readDependenciesViaTask(directory: Path): Set<Path>? {
73-
val gradle = getGradleCommand(directory)
74-
val config = createTemporaryGradleFile()
75-
76-
val gradleCommand = "$gradle -I ${config.absolutePath} classpath"
77-
val classpathCommand = Runtime.getRuntime().exec(gradleCommand, null, directory.toFile())
78-
val stdout = classpathCommand.inputStream
79-
val dependencies = mutableSetOf<Path>()
80-
81-
stdout.bufferedReader().use { reader ->
82-
reader.lines().forEach {
83-
val line = it.toString().trim()
84-
if (!line.startsWith("Download") && jarArtifactOutputLine.matcher(line).matches()) {
85-
dependencies.add(Paths.get(line))
86-
}
87-
}
88-
}
89-
90-
classpathCommand.waitFor()
91-
92-
if (dependencies.size > 0) {
93-
return dependencies
94-
} else {
95-
return null
96-
}
97-
}
98-
99-
private fun readDependenciesViaEclipseProject(connection: ProjectConnection): Set<Path> {
100-
val dependencies = mutableSetOf<Path>()
101-
val project: EclipseProject = connection.getModel(EclipseProject::class.java)
102-
103-
for (dependency in project.classpath) {
104-
dependencies.add(dependency.file.toPath())
105-
}
106-
107-
return dependencies
108-
}
109-
110-
private fun readDependenciesViaIdeaProject(connection: ProjectConnection): Set<Path> {
111-
val dependencies = mutableSetOf<Path>()
112-
val project: IdeaProject = connection.getModel(IdeaProject::class.java)
113-
114-
for (child in project.children) {
115-
for (dependency in child.dependencies) {
116-
if (dependency is ExternalDependency) {
117-
dependencies.add(dependency.file.toPath())
118-
}
119-
}
120-
}
121-
122-
return dependencies
123-
}
124-
125-
private fun readDependenciesViaKotlinDSL(connection: ProjectConnection): Set<Path> {
126-
val project: KotlinBuildScriptModel = connection.getModel(KotlinBuildScriptModel::class.java)
127-
return project.classPath.map { it.toPath() }.toSet()
128-
}
129-
13057
private fun readDependenciesViaGradleCLI(projectDirectory: Path): Set<Path>? {
13158
LOG.fine("Attempting dependency resolution through CLI...")
59+
val config = createTemporaryGradleFile()
13260
val gradle = getGradleCommand(projectDirectory)
133-
val classpathCommand = "$gradle dependencies --configuration=compileClasspath --console=plain"
134-
val testClasspathCommand = "$gradle dependencies --configuration=testCompileClasspath --console=plain"
135-
val dependencies = findGradleCLIDependencies(classpathCommand, projectDirectory)
136-
val testDependencies = findGradleCLIDependencies(testClasspathCommand, projectDirectory)
137-
138-
return dependencies?.union(testDependencies.orEmpty()).orEmpty()
61+
val cmd = "$gradle -I ${config.absolutePath} kotlinLSPDeps --console=plain"
62+
LOG.fine(" -- executing $cmd")
63+
val dependencies = findGradleCLIDependencies(cmd, projectDirectory)
64+
return dependencies
13965
}
14066

14167
private fun findGradleCLIDependencies(command: String, projectDirectory: Path): Set<Path>? {
142-
return parseGradleCLIDependencies(execAndReadStdout(command, projectDirectory))
68+
val result = execAndReadStdout(command, projectDirectory)
69+
LOG.fine(result)
70+
return parseGradleCLIDependencies(result)
14371
}
14472

145-
private val artifactPattern by lazy { "[\\S]+:[\\S]+:[\\S]+( -> )*([\\d.]+)*".toRegex() }
146-
private val jarMatcher by lazy { FileSystems.getDefault().getPathMatcher("glob:**.jar") }
73+
private val artifactPattern by lazy { "kotlin-lsp-gradle (.+)\n".toRegex() }
14774

14875
private fun parseGradleCLIDependencies(output: String): Set<Path>? {
14976
val artifacts = artifactPattern.findAll(output)
150-
.map { findGradleArtifact(parseArtifact(it.value, it.groups[2]?.value)) }
77+
.mapNotNull { FileSystems.getDefault().getPath(it.groups[1]?.value) }
15178
.filterNotNull()
15279
return artifacts.toSet()
15380
}
154-
155-
private fun findGradleArtifact(artifact: Artifact): Path? {
156-
val jarPath = gradleCaches
157-
?.resolve(artifact.group)
158-
?.resolve(artifact.artifact)
159-
?.resolve(artifact.version)
160-
?.let { dependencyFolder ->
161-
Files.walk(dependencyFolder)
162-
.filter { jarMatcher.matches(it) }
163-
.findFirst()
164-
}
165-
?.orElse(null)
166-
return jarPath
167-
}
Lines changed: 59 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,66 @@
1-
boolean isResolvable(Configuration conf) {
2-
// isCanBeResolved was added in Gradle 3.3. Previously, all configurations were resolvable
3-
if (Configuration.class.declaredMethods.any { it.name == 'isCanBeResolved' }) {
4-
return conf.canBeResolved
5-
}
6-
return true
7-
}
1+
allprojects { project ->
82

9-
File getBuildCacheForDependency(File dependency) {
10-
String name = dependency.getName()
11-
String home = System.getProperty("user.home")
12-
String gradleCache = home + File.separator + '.gradle' + File.separator + 'caches' + File.separator
13-
if (file(gradleCache).exists()) {
14-
String include = 'transforms*' + File.separator + '**' + File.separator + name + File.separator + '**' + File.separator + 'classes.jar'
15-
return fileTree(dir: gradleCache, include: include).files.find { it.isFile() }
16-
} else {
17-
return zipTree(dependency).files.find { it.isFile() && it.name.endsWith('jar') }
18-
}
19-
}
3+
task kotlinLSPDeps {
4+
task -> doLast {
5+
System.out.println ""
6+
System.out.println "gradle-version " + gradleVersion
7+
System.out.println "kotlin-lsp-project " + project.name
8+
9+
if(project.hasProperty('android')) {
10+
11+
def rootDir = project.rootDir
2012

21-
task classpath {
22-
doLast {
23-
HashSet<String> classpathFiles = new HashSet<String>()
24-
for (proj in allprojects) {
25-
for (conf in proj.configurations) {
26-
if (isResolvable(conf)) {
27-
for (dependency in conf) {
28-
classpathFiles += dependency
29-
}
30-
}
31-
}
13+
def level = "21"
14+
if(project.android.defaultConfig.targetSdkVersion != null) {
15+
level = project.android.defaultConfig.targetSdkVersion.getApiLevel()
16+
System.out.println "kotlin-lsp-target android-" + level
17+
}
3218

33-
def rjava = proj.getBuildDir().absolutePath + File.separator + "intermediates" + File.separator + "classes" + File.separator + "debug"
34-
def rFiles = new File(rjava)
35-
if (rFiles.exists()) {
36-
classpathFiles += rFiles
37-
}
19+
def localProperties = new File(rootDir, "local.properties")
3820

39-
if (proj.hasProperty("android")) {
40-
classpathFiles += proj.android.getBootClasspath()
41-
if (proj.android.hasProperty("applicationVariants")) {
42-
proj.android.applicationVariants.all { v ->
43-
if (v.hasProperty("javaCompile")) {
44-
classpathFiles += v.javaCompile.classpath
45-
}
46-
if (v.hasProperty("compileConfiguration")) {
47-
v.compileConfiguration.each { dependency ->
48-
classpathFiles += dependency
49-
}
50-
}
51-
if (v.hasProperty("runtimeConfiguration")) {
52-
v.runtimeConfiguration.each { dependency ->
53-
classpathFiles += dependency
54-
}
55-
}
56-
if (v.hasProperty("getApkLibraries")) {
57-
println v.getApkLibraries()
58-
classpathFiles += v.getApkLibraries()
59-
}
60-
if (v.hasProperty("getCompileLibraries")) {
61-
classpathFiles += v.getCompileLibraries()
62-
}
63-
}
64-
}
21+
if (localProperties.exists()) {
22+
Properties properties = new Properties()
23+
localProperties.withInputStream { instr -> properties.load(instr) }
24+
def sdkDir = properties.getProperty('sdk.dir')
25+
def androidJarPath = sdkDir + "/platforms/android-" + level + "/android.jar"
26+
System.out.println "kotlin-lsp-gradle " + androidJarPath
27+
}
6528

66-
if (proj.android.hasProperty("libraryVariants")) {
67-
proj.android.libraryVariants.all { v ->
68-
classpathFiles += v.javaCompile.classpath.files
69-
}
70-
}
71-
}
29+
if(project.android.hasProperty('applicationVariants')) {
30+
project.android.applicationVariants.all { variant ->
31+
variant.getCompileClasspath().each {
32+
System.out.println "kotlin-lsp-gradle " + it
33+
}
34+
}
35+
}
7236

73-
HashSet<String> computedPaths = new HashSet<String>()
74-
for (dependency in classpathFiles) {
75-
if (dependency.name.endsWith("jar")) {
76-
println dependency
77-
} else if (dependency != null) {
78-
println getBuildCacheForDependency(dependency)
79-
}
80-
}
81-
}
82-
}
37+
def buildClasses = project.getBuildDir().absolutePath + File.separator + "intermediates" + File.separator + "classes" + File.separator + "debug"
38+
System.out.println "kotlin-lsp-gradle " + buildClasses
39+
40+
} else {
41+
// Print the list of all dependencies jar files.
42+
project.configurations.findAll {
43+
it.metaClass.respondsTo(it, "isCanBeResolved") ? it.isCanBeResolved() : false
44+
}.each {
45+
it.resolve().each {
46+
if(it.inspect().endsWith("jar")) {
47+
System.out.println "kotlin-lsp-gradle " + it
48+
} else if(it.inspect().endsWith("aar")) {
49+
// If the dependency is an AAR file we try to determine the location
50+
// of the classes.jar file in the exploded aar folder.
51+
def splitted = it.inspect().split("/")
52+
def namespace = splitted[-5]
53+
def name = splitted[-4]
54+
def version = splitted[-3]
55+
def explodedPath = "$project.buildDir/intermediates/exploded-aar" +
56+
"/$namespace/$name/$version/jars/classes.jar"
57+
System.out.println "kotlin-lsp-gradle " + explodedPath
58+
}
59+
}
60+
}
61+
}
62+
}
63+
}
8364
}
65+
66+

0 commit comments

Comments
 (0)