Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build meta from gradle init script #74

Merged
merged 3 commits into from
Nov 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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"
}
}
}
}
}
}
}