Skip to content

Commit

Permalink
Merge pull request #74 from sourcegraph/nsc/gradle-initscript
Browse files Browse the repository at this point in the history
  • Loading branch information
Strum355 authored Nov 6, 2020
2 parents bba3736 + cd00ab7 commit 9b0870d
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 34 deletions.
80 changes: 52 additions & 28 deletions src/main/kotlin/lsifjava/BuildToolInterface.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,86 @@ import org.gradle.tooling.GradleConnector
import org.gradle.tooling.model.eclipse.EclipseProject
import org.gradle.tooling.model.idea.IdeaProject
import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths

interface BuildToolInterface {
fun getClasspaths(): List<Classpath>
fun getSourceDirectories(): List<List<Path>>
fun javaSourceVersions(): List<String?>
val classpaths: List<Classpath>
val sourceDirectories: List<List<Path>>
val javaSourceVersions: List<String?>
}

// TODO(nsc) exclusions? lazy eval?
class GradleInterface(private val projectDir: CanonicalPath): AutoCloseable, BuildToolInterface {
private val projectConnection by lazy {
private val initScriptName = "projectClasspathFinder.gradle"

private val artifactPattern by lazy(LazyThreadSafetyMode.NONE) {
"lsifjava (.+)(?:\r?\n)".toRegex()
}

private val projectConnection by lazy(LazyThreadSafetyMode.NONE) {
GradleConnector.newConnector()
.forProjectDirectory(projectDir.path.toFile())
.connect()
}

private val eclipseModel by lazy {
private val eclipseModel by lazy(LazyThreadSafetyMode.NONE) {
projectConnection.getModel(EclipseProject::class.java)
}

private val ideaModel by lazy {
private val ideaModel by lazy(LazyThreadSafetyMode.NONE) {
projectConnection.getModel(IdeaProject::class.java)
}

// is this even *correct*? Is the order the same?
override fun getClasspaths(): List<Classpath> {
val eclipseClasspaths = getClasspathsFromEclipse()
return ideaModel.children.mapIndexed { i, it ->
Classpath(it.dependencies
.filterIsInstance<IdeaSingleEntryLibraryDependency>()
.map { it.file.canonicalPath }
.toSet()
) + eclipseClasspaths[i]
}
}
override val classpaths: List<Classpath> get() {
val initScriptClasspath = kotlin.runCatching { getClasspathFromInitScript() }.getOrDefault(Classpath(setOf()))

val eclipseClasspaths = kotlin.runCatching { eclipseClasspath() }.getOrDefault(listOf())

private fun getClasspathsFromEclipse() = eclipseClasspath(eclipseModel)
val ideaClasspath = kotlin.runCatching { ideaClasspath() }.getOrDefault(listOf())

private fun eclipseClasspath(project: EclipseProject): List<Classpath> {
return ideaClasspath.mapIndexed {i, it -> it + eclipseClasspaths[i] + initScriptClasspath }
}

private fun ideaClasspath() = ideaModel.children.map { it ->
Classpath(it.dependencies
.filterIsInstance<IdeaSingleEntryLibraryDependency>()
.map { it.file.canonicalPath }
.toSet()
)
}

private fun eclipseClasspath(project: EclipseProject = eclipseModel): List<Classpath> {
val classPaths = arrayListOf<Classpath>()
classPaths += Classpath(project.classpath.map { it.file.canonicalPath }.toSet())
project.children.forEach { eclipseClasspath(it).toCollection(classPaths) }
return classPaths
}

override fun getSourceDirectories(): List<List<Path>> {
private fun getClasspathFromInitScript(): Classpath {
val config = File.createTempFile("lsifjava", ".gradle")
config.deleteOnExit()

config.bufferedWriter().use { configWriter ->
ClassLoader.getSystemResourceAsStream(initScriptName)!!.bufferedReader().use { configReader ->
configReader.copyTo(configWriter)
}
}

// Unix only for now. To be revisited
val (stdout, stderr) = execAndReadStdoutAndStderr("./gradlew -I ${config.absolutePath} lsifjavaAllGradleDeps", projectDir.path)

val artifacts = artifactPattern.findAll(stdout)
.mapNotNull { it.groups[1] }
.map { Paths.get(it.value).toFile().canonicalPath }
.toSet()

return Classpath(artifacts)
}

override val sourceDirectories: List<List<Path>> get() {
return ideaModel.children.flatMap { module ->
module.contentRoots.map { root ->
root.sourceDirectories.map { Paths.get(it.directory.canonicalPath) } +
Expand All @@ -78,7 +110,7 @@ class GradleInterface(private val projectDir: CanonicalPath): AutoCloseable, Bui
return sourceDirs
}

override fun javaSourceVersions() = javaSourceVersion(eclipseModel)
override val javaSourceVersions: List<String?> get() = javaSourceVersion(eclipseModel)

// get rid of String?, use parent version or else fallback
private fun javaSourceVersion(project: EclipseProject): List<String?> {
Expand All @@ -89,12 +121,4 @@ class GradleInterface(private val projectDir: CanonicalPath): AutoCloseable, Bui
}

override fun close() = projectConnection.close()
}

inline class Classpath(private val classpaths: Set<String>) {
operator fun plus(other: Set<String>) = Classpath(classpaths.union(other))

operator fun plus(other: Classpath) = Classpath(classpaths.union(other.classpaths))

override fun toString() = classpaths.joinToString(":")
}
16 changes: 10 additions & 6 deletions src/main/kotlin/lsifjava/FileCollector.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ fun buildIndexerMap(
): Map<Path, DocumentIndexer> {
val indexers = mutableMapOf<Path, DocumentIndexer>()

val classpaths = buildToolInterface.getClasspaths()
val sourceVersions = buildToolInterface.javaSourceVersions()
val classpaths = buildToolInterface.classpaths.merge()

val sourceVersions = buildToolInterface.javaSourceVersions

val fileBuildInfo = Channel<FileBuildInfo>()

Expand All @@ -39,21 +40,24 @@ fun buildIndexerMap(
indexers, emitter, javacDiagListener, verbose,
)
}
}.join()
}
}

return indexers
}

// NOTE: classpaths is the total collection of the found classpaths for all
// sub-projects. This is a bit of a brute force mash together but it appears to please
// the javac
private fun CoroutineScope.launchFileTreeWalkers(
buildToolInterface: BuildToolInterface,
fileBuildInfoChannel: Channel<FileBuildInfo>,
classpaths: List<Classpath>,
classpaths: Classpath,
sourceVersions: List<String?>,
) = launch(Dispatchers.IO) {
buildToolInterface.getSourceDirectories().forEachIndexed { i, paths ->
buildToolInterface.sourceDirectories.forEachIndexed { i, paths ->
launch {
val collector = AsyncFileCollector(fileBuildInfoChannel, classpaths[i], sourceVersions[i], this)
val collector = AsyncFileCollector(fileBuildInfoChannel, classpaths, sourceVersions[i], this)
paths.asSequence().filter { Files.exists(it) }.forEach { Files.walkFileTree(it, collector) }
}
}
Expand Down
40 changes: 40 additions & 0 deletions src/main/kotlin/lsifjava/UtilTypes.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package lsifjava

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.ByteArrayOutputStream
import java.io.Writer
import java.nio.file.Path

Expand All @@ -13,3 +17,39 @@ object NoopWriter: Writer() {
override fun flush() = Unit
override fun write(p0: CharArray, p1: Int, p2: Int) = Unit
}

inline class Classpath(private val classpaths: Set<String>) {
operator fun plus(other: Set<String>) = Classpath(classpaths.union(other))

operator fun plus(other: Classpath) = Classpath(classpaths.union(other.classpaths))

fun size() = classpaths.size

override fun toString() = classpaths.joinToString(":")
}

fun List<Classpath>.merge() = when(this.isEmpty()) {
false -> this.reduce { acc, classpath -> acc + classpath }
else -> {
println("no classpaths were inferred, symbol resolution for external dependencies may fail.")
Classpath(setOf())
}
}

fun execAndReadStdoutAndStderr(shellCommand: String, directory: Path): Pair<String, String> {
val process = Runtime.getRuntime().exec(shellCommand, null, directory.toFile())
val output = ByteArrayOutputStream().writer()
val errors = ByteArrayOutputStream().writer()

runBlocking(Dispatchers.IO) {
launch {
process.inputStream.bufferedReader().use { it.copyTo(output) }
}

launch {
process.errorStream.bufferedReader().use { it.copyTo(errors) }
}
}

return Pair(output.toString(), errors.toString())
}
44 changes: 44 additions & 0 deletions src/main/resources/projectClasspathFinder.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
allprojects { project ->
task lsifjavaAllGradleDeps {
doLast {
if (project.hasProperty('android')) {
project.android.getBootClasspath().each {
println "lsifjava $it"
}
if (project.android.hasProperty('applicationVariants')) {
project.android.applicationVariants.all { variant ->
try {
variant.getCompileClasspath().each {
println "lsifjava $it"
}
} catch(ignored){}
}
}
} else {
// Print the list of all dependencies jar files.
project.configurations.findAll {
it.metaClass.respondsTo(it, "isCanBeResolved") ? it.isCanBeResolved() : false
}.each {
it.resolve().each {
def inspected = it.inspect()

if (inspected.endsWith("jar")) {
if (!inspected.contains("zip!")) {
println "lsifjava $it"
}
} else if (inspected.endsWith("aar")) {
// If the dependency is an AAR file we try to determine the location
// of the classes.jar file in the exploded aar folder.
def splitted = inspected.split("/")
def namespace = splitted[-5]
def name = splitted[-4]
def version = splitted[-3]
def explodedPath = "$project.buildDir/intermediates/exploded-aar/$namespace/$name/$version/jars/classes.jar"
println "lsifjava $explodedPath"
}
}
}
}
}
}
}

0 comments on commit 9b0870d

Please sign in to comment.