Skip to content

Commit

Permalink
Improve Jdeps to handle:- (#449)
Browse files Browse the repository at this point in the history
1) References to top level functions.
2) References to Java constants.
  • Loading branch information
jongerrish authored Jan 13, 2021
1 parent cb7e9ce commit 619be5e
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
package io.bazel.kotlin.plugin.jdeps

import com.google.devtools.build.lib.view.proto.Deps
import com.intellij.mock.MockProject
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import java.io.BufferedOutputStream
import java.io.File

class JdepsGenComponentRegistrar : ComponentRegistrar {

Expand All @@ -33,5 +19,6 @@ class JdepsGenComponentRegistrar : ComponentRegistrar {
val extension = JdepsGenExtension(project, configuration)
AnalysisHandlerExtension.registerExtension(project, extension)
ClassBuilderInterceptorExtension.registerExtension(project, extension)
StorageComponentContainerContributor.registerExtension(project, extension)
}
}
66 changes: 52 additions & 14 deletions src/main/kotlin/io/bazel/kotlin/plugin/jdeps/JdepsGenExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,32 @@ import org.jetbrains.kotlin.codegen.ClassBuilderFactory
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.codegen.inline.RemappingClassBuilder
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.useInstance
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaField
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass
import org.jetbrains.kotlin.load.kotlin.getContainingKotlinJvmBinaryClass
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
Expand Down Expand Up @@ -55,7 +68,7 @@ import java.nio.file.Paths
class JdepsGenExtension(
val project: MockProject,
val configuration: CompilerConfiguration) :
ClassBuilderInterceptorExtension, AnalysisHandlerExtension {
ClassBuilderInterceptorExtension, AnalysisHandlerExtension, StorageComponentContainerContributor {

companion object {

Expand All @@ -71,17 +84,24 @@ class JdepsGenExtension(
}

/**
* Returns the path of the jar archive file corresponding to the provided class descriptor.
* Returns the path of the jar archive file corresponding to the provided descriptor.
*
* @classDescriptor the class descriptor, typically obtained from compilation analyze phase
* @descriptor the descriptor, typically obtained from compilation analyze phase
* @return the path corresponding to the JAR where this class was loaded from, or null.
*/
fun getClassCanonicalPath(classDescriptor: ClassDescriptor): String? {
return when (val sourceElement: SourceElement = classDescriptor.source
fun getClassCanonicalPath(descriptor: DeclarationDescriptorWithSource): String? {
return when (val sourceElement: SourceElement = descriptor.source
) {
is JavaSourceElement ->
if (sourceElement.javaElement is BinaryJavaClass) {
(sourceElement.javaElement as BinaryJavaClass).virtualFile.canonicalPath
} else if (sourceElement.javaElement is BinaryJavaField) {
val containingClass = (sourceElement.javaElement as BinaryJavaField).containingClass
if (containingClass is BinaryJavaClass) {
containingClass.virtualFile.canonicalPath
} else {
null
}
} else {
// Ignore Java source local to this module.
null
Expand All @@ -98,6 +118,33 @@ class JdepsGenExtension(
private val classesCanonicalPaths: HashSet<String> = HashSet()
private var moduleDescriptor: ModuleDescriptor? = null

override fun registerModuleComponents(
container: StorageComponentContainer,
platform: TargetPlatform,
moduleDescriptor: ModuleDescriptor
) {
container.useInstance(ClasspathCollectingCallChecker(classesCanonicalPaths))
}

class ClasspathCollectingCallChecker(private val classesCanonicalPaths: HashSet<String>) : CallChecker {
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {

when (resolvedCall.resultingDescriptor) {
is FunctionDescriptor -> {
val virtualFileClass = resolvedCall.resultingDescriptor.getContainingKotlinJvmBinaryClass() as? VirtualFileKotlinClass ?: return
classesCanonicalPaths.add(virtualFileClass.file.path)
}
is FakeCallableDescriptorForObject -> {
getClassCanonicalPath(resolvedCall.resultingDescriptor)?.let { classesCanonicalPaths.add(it) }
}
is JavaPropertyDescriptor -> {
getClassCanonicalPath(resolvedCall.resultingDescriptor)?.let { classesCanonicalPaths.add(it) }
}
else -> return
}
}
}

override fun analysisCompleted(
project: Project,
module: ModuleDescriptor,
Expand All @@ -108,15 +155,6 @@ class JdepsGenExtension(
// to extract list of used classes differently (i.e not relaying on ClassBuilderInterceptor).
moduleDescriptor = module

// Capture import dependencies
files.forEach {file ->
file.importDirectives.forEach {import ->
import.importPath?.let {
moduleDepClasses.add(ClassId.topLevel(it.fqName))
}
}
}

return super.analysisCompleted(project, module, bindingTrace, files)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,49 @@ class KotlinBuilderJvmJdepsTest {
assertIncomplete(jdeps).isEmpty()
}

@Test
fun `java constant`() {

val dependentTarget = ctx.runCompileTask(Consumer { c: KotlinJvmTestBuilder.TaskBuilder ->
c.addSource("Constants.java",
"""
package something;
public interface Constants {
int HELLO_CONSTANT = 100;
}
""")
c.outputJar()
c.outputJavaJdeps()
c.compileJava()
})

val dependingTarget = ctx.runCompileTask(Consumer { c: KotlinJvmTestBuilder.TaskBuilder ->
c.addSource("AnotherClass.kt",
"""
package something.other
import something.Constants
class AnotherClass {
val ref = Constants.HELLO_CONSTANT
}
""")
c.outputJar()
c.compileKotlin()
c.outputJdeps()
c.addDirectDependencies(dependentTarget)
})
val jdeps = depsProto(dependingTarget)

assertThat(jdeps.ruleLabel).isEqualTo(dependingTarget.label())

assertExplicit(jdeps).containsExactly(dependentTarget.singleCompileJar())
assertImplicit(jdeps).isEmpty()
assertUnused(jdeps).isEmpty()
assertIncomplete(jdeps).isEmpty()
}

@Test
fun `unused dependency listed`() {
val dependentTarget = ctx.runCompileTask(Consumer { c: KotlinJvmTestBuilder.TaskBuilder ->
Expand Down Expand Up @@ -251,6 +294,42 @@ class KotlinBuilderJvmJdepsTest {
assertIncomplete(jdeps).isEmpty()
}

@Test
fun `references function`() {
val dependentTarget = ctx.runCompileTask(Consumer { c: KotlinJvmTestBuilder.TaskBuilder ->
c.addSource("ContainsFunction.kt",
"""
package dependency
fun someFunction() = 42
""")
c.outputJar()
c.outputJdeps()
c.compileKotlin()
})

val dependingTarget = ctx.runCompileTask(Consumer { c: KotlinJvmTestBuilder.TaskBuilder ->
c.addSource("HasFunctionDependency.kt",
"""
package something
import dependency.someFunction
val property2 = someFunction()
""")
c.outputJar()
c.compileKotlin()
c.addDirectDependencies(dependentTarget)
c.outputJdeps()
})
val jdeps = depsProto(dependingTarget)

assertThat(jdeps.ruleLabel).isEqualTo(dependingTarget.label())

assertExplicit(jdeps).containsExactly(dependentTarget.singleCompileJar())
assertImplicit(jdeps).isEmpty()
assertUnused(jdeps).isEmpty()
assertIncomplete(jdeps).isEmpty()
}

private fun depsProto(jdeps: io.bazel.kotlin.builder.Deps.Dep) =
Deps.Dependencies.parseFrom(BufferedInputStream(Files.newInputStream(Paths.get(jdeps.jdeps()!!))))

Expand Down

0 comments on commit 619be5e

Please sign in to comment.