kotlin-language-server icon indicating copy to clipboard operation
kotlin-language-server copied to clipboard

Use eclipse gradle plugin to get classpath with sources

Open aiguofer opened this issue 2 years ago • 9 comments

I had originally created #410. The ability to navigate to sources is pretty crucial for me. This PR uses the built in Eclipse Gradle plugin to generate the Classpath with sources. The plugin creates a .classpath file, which we read and parse to generate the set of ClassPathEntry for Gradle.

This PR would still need some cleanup, but figured I'd put it out to see what others think of it.

There is still an issue when you navigate to a source where it tries to load it as a kotlin script and fails with something like:

Internal error: java.lang.IllegalArgumentException: Unable to find script compilation configuration for the script KtFile: /var/folders/6x/nmdbvc9d15d93v99zh7nqgym0000gp/T/kotlinlangserver14114182911595465166/ElementList8841194890478318470.java

aiguofer avatar Mar 10 '23 03:03 aiguofer

Thanks for looking into this! That's a neat idea, though generating files in the user's project directory is something I'm trying to use sparingly. Ideally we could use the Gradle tooling API to get the dependencies from Gradle directly, see

  • #85

The tooling API offers an EclipseModel which effectively should provide the same information that is generated for the .classpath.

fwcd avatar Mar 11 '23 05:03 fwcd

@fwcd thanks for the link and context! I agree with your assessment on the other thread... it seems like a lot of work to create a whole new IDE plugin for LSP. I imagine most of what is needed to generate the classpath can be obtained from the EclipseModel or EclipseProject.

I did think about the files in the user project, which is why I delete the .classpath file at the end of the operation. That being said, it might be more performant if we skip the whole xml serialization/deserialization. I'll see if I can modify this to create a task that instantiates an EclipseProject and prints out the dependencies similar to how it's currently done.

aiguofer avatar Mar 13 '23 16:03 aiguofer

@fwcd figured out how to use the EclipseModel directly. Since some of the dependencies might be resolved more than once with different methods, I first build a map of path -> source (and prefer entries with source) then print out the individual items.

There are 2 major problems I'm still having (happy to create issues if needed) with go-to-definition. I'm currentyl using {"externalSources":{"autoConvertToKotlin":false,"useKlsScheme":false}} (with useKlsScheme=true none of the go-to-def work, and autoConvertToKotlin=true fails for most files):

  • Go-to-definition isn't working for external kotlin libraries:
async2    Go-to-definition at .../flight/SlFlightServer.kt 36:38
async2    Found declaration descriptor public final fun logger(func: () -> kotlin.Unit): mu.KLogger defined in mu.KotlinLogging[DeserializedSimpleFunctionDescriptor@686ee9]
async2    Couldn't find definition at .../flight/SlFlightServer.kt 36:38
  • When I go to a definition of an external library, the file that gets opened isn't considered part of the project, so I can't go to definitions from that file. Based on the logs, my guess is it's trying to parse it as a Kotlin script file:
Internal error: java.lang.IllegalArgumentException: Unable to find script compilation configuration for the script KtFile: /var/folders/6x/nmdbvc9d15d93v99zh7nqgym0000gp/T/kotlinlangserver10938469402867462880/JsonProperty1664481689349846736.java

aiguofer avatar Mar 15 '23 18:03 aiguofer

Nice! What I had in mind would be to go a step further and resolve the EclipseModel directly from within the language server (ideally so we could drop the xyzClasspathFinder.gradle eventually). This would require using the Gradle tooling API.

fwcd avatar Mar 15 '23 19:03 fwcd

@fwcd so I've been playing around a bit with the Tooling API. It's pretty straightforward to get the EclipseProject and the classes from that, but I haven't found how to get project.android, project.sourceSets, or the kotlin extension. We might need to include the jars for those plugins explicitly and figure out how gradle resolves those internally.

Here's a function showing how to get the sources using the tooling api:

    private fun getDepsWithSources(): Set<ClassPathEntry> {
        val connector = GradleConnector.newConnector().forProjectDirectory(projectDirectory.toFile())
        val classpaths = mutableSetOf<ClassPathEntry>()

        connector.connect().use { connection ->
            try {
                val eclipseModel = connection.getModel(EclipseProject::class.java)
                classpaths.addAll(
                    eclipseModel.classpath.map {
                        LOG.info { "Adding path from tools API: ${it.file.path}" }
                        ClassPathEntry(it.file.toPath(), it.file?.toPath()) }.toSet()
                )
            } catch(e: Exception) {
                LOG.error("Error: $e")
            }
        }
        return classpaths
    }

aiguofer avatar Mar 29 '23 17:03 aiguofer

Its been a 2+ years since I last used the eclipse model, but I remember back when using it with https://github.com/sourcegraph/scip-java that it was pretty suboptimal and often missed things when tested across a number of repositories across github (at one point i took the union of the eclipse + IDEA models + an init script from this repo as they both missed some that the other didnt). We're currently using this custom task, id be interested in knowing whether you know of how this compares to it off-hand (from my experience it worked better than the eclipse model, but again its been a long while so my memory may be very fuzzy)

Strum355 avatar Apr 14 '23 10:04 Strum355

@Strum355 Interesting! honestly I have no idea how it compares. I've just started using Kotlin at work in the past 6 months and hate using IntelliJ; I've been trying to use Emacs but I can't seem to get anywhere near the ability to explore dependencies using the language server so I figured I'd take a stab at this. I don't really get much time to mess around with it though.

I've been trying to find com.sourcegraph.gradle.semanticdb.SemanticdbGradlePlugin but can't find the code for it... what exactly is that doing?

Do you think it would make sense for kotlin-language-server to use the same plugin? Alternatively, do you think the approach from https://github.com/sourcegraph/scip-java/pull/74/files worked well?

aiguofer avatar May 14 '23 01:05 aiguofer

@Strum355 Interesting! honestly I have no idea how it compares. I've just started using Kotlin at work in the past 6 months and hate using IntelliJ; I've been trying to use Emacs but I can't seem to get anywhere near the ability to explore dependencies using the language server so I figured I'd take a stab at this. I don't really get much time to mess around with it though.

I've been trying to find com.sourcegraph.gradle.semanticdb.SemanticdbGradlePlugin but can't find the code for it... what exactly is that doing?

I should've pinned the link to a specific commit, our gradle work just underwent some changes that Im not a part of :grimacing: we're not using init scripts anymore, but rather a gradle plugin itself From what I can grok, this is the gradle task that we now use to gather the set of dependencies. I hope your scala-fu is up to scratch :sweat_smile: (mine isnt) We abandoned EclipseProject and IdeaProject long ago fwiw I dont believe we have explicit tested support for Android as well, which may throw a spanner in the works here

Strum355 avatar May 17 '23 23:05 Strum355

ahh thanks @Strum355! One of the issues is that we're trying to get the source jars to add to the classpath entries, but only the EclipseProject and IdeaProject resolve those AFAIK.

aiguofer avatar May 18 '23 00:05 aiguofer