Skip to content

Commit 351e803

Browse files
authored
Merge pull request #350 from olafurpg/kotlin
2 parents d296b26 + fc58dea commit 351e803

File tree

71 files changed

+10107
-3649
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+10107
-3649
lines changed

build.sbt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ lazy val V =
1818
def scala3 = "3.0.1"
1919
def metals = "0.10.6-M1"
2020
def scalameta = "4.4.26"
21+
def semanticdbKotlinc = "0.0.2"
2122
def testcontainers = "0.39.3"
2223
def requests = "0.6.5"
2324
}
@@ -156,6 +157,7 @@ lazy val cli = project
156157
"sbtSourcegraphVersion" ->
157158
com.sourcegraph.sbtsourcegraph.BuildInfo.version,
158159
"semanticdbVersion" -> V.scalameta,
160+
"semanticdbKotlincVersion" -> V.semanticdbKotlinc,
159161
"mtagsVersion" -> V.metals,
160162
"scala211" -> V.scala211,
161163
"scala212" -> V.scala212,
@@ -172,7 +174,8 @@ lazy val cli = project
172174
"org.scala-lang.modules" %% "scala-xml" % "1.3.0",
173175
"com.lihaoyi" %% "requests" % V.requests,
174176
"org.scalameta" %% "moped" % V.moped,
175-
"org.scalameta" %% "ascii-graphs" % "0.1.2"
177+
"org.scalameta" %% "ascii-graphs" % "0.1.2",
178+
"org.jetbrains.kotlin" % "kotlin-compiler-embeddable" % "1.5.21"
176179
),
177180
(Compile / resourceGenerators) +=
178181
Def

lsif-java/src/main/scala/com/sourcegraph/lsif_java/Dependencies.scala

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import java.io.File
44
import java.nio.file.Path
55

66
import scala.concurrent.duration.Duration
7+
import scala.util.Try
78
import scala.xml.XML
89

9-
import com.sourcegraph.lsif_java.BuildInfo
1010
import coursier.Fetch
1111
import coursier.Repositories
1212
import coursier.Resolve
@@ -41,6 +41,37 @@ object Dependencies {
4141
Repositories.centralGcs
4242
)
4343

44+
/**
45+
* Attempts to find the "common definitions" JAR for a potentially
46+
* MultiPlatform Project. We only support JVM for now, native and JS are not
47+
* supported. If it ends with '-jvm', we search for a JAR with the classifier
48+
* truncated. If it does not end with -jvm, we search for a JAR with the
49+
* -common classifier. This is non-exhaustive, and the classifiers are
50+
* completely arbitrary.
51+
*/
52+
def kotlinMPPCommon(
53+
group: String,
54+
artifact: String,
55+
version: String
56+
): Option[Path] =
57+
Try {
58+
val task = Fetch[Task](Cache.default)
59+
.withClassifiers(Set(Classifier.sources))
60+
.addRepositories(defaultExtraRepositories: _*)
61+
62+
if (artifact.endsWith("-jvm")) {
63+
val dependency = Dependencies
64+
.parseDependency(s"$group:${artifact.stripSuffix("-jvm")}:$version")
65+
val result = task.addDependencies(dependency).runResult()
66+
return Some(result.files.head.toPath)
67+
}
68+
69+
val dependency = Dependencies
70+
.parseDependency(s"$group:$artifact-common:$version")
71+
val result = task.addDependencies(dependency).runResult()
72+
result.files.head.toPath
73+
}.toOption
74+
4475
def resolveDependencies(
4576
dependencies: List[String],
4677
transitive: Boolean = true

lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/LsifBuildTool.scala

Lines changed: 158 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.sourcegraph.lsif_java.buildtools
22

33
import java.io.File
4+
import java.io.FileOutputStream
45
import java.io.IOException
56
import java.net.URLClassLoader
67
import java.nio.file.FileSystems
@@ -17,9 +18,11 @@ import java.util.Collections
1718
import java.util.Optional
1819
import java.util.ServiceLoader
1920
import java.util.concurrent.TimeUnit
21+
import java.util.jar.JarFile
2022

2123
import scala.collection.mutable.ListBuffer
2224
import scala.jdk.CollectionConverters._
25+
import scala.language.postfixOps
2326
import scala.util.Failure
2427
import scala.util.Success
2528
import scala.util.Try
@@ -46,6 +49,14 @@ import moped.macros.ClassShape
4649
import moped.parsers.JsonParser
4750
import moped.reporters.Diagnostic
4851
import moped.reporters.Input
52+
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
53+
import org.jetbrains.kotlin.cli.common.arguments.ParseCommandLineArgumentsKt.parseCommandLineArguments
54+
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
55+
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
56+
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
57+
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
58+
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
59+
import org.jetbrains.kotlin.config.Services
4960
import os.CommandResult
5061
import os.ProcessOutput.Readlines
5162
import os.SubprocessException
@@ -72,9 +83,12 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
7283
private val scalaPattern = FileSystems
7384
.getDefault
7485
.getPathMatcher("glob:**.scala")
86+
private val kotlinPattern = FileSystems
87+
.getDefault
88+
.getPathMatcher("glob:**.kt")
7589
private val allPatterns = FileSystems
7690
.getDefault
77-
.getPathMatcher("glob:**.{java,scala}")
91+
.getPathMatcher("glob:**.{java,scala,kt}")
7892
private val moduleInfo = Paths.get("module-info.java")
7993

8094
override def usedInCurrentDirectory(): Boolean =
@@ -139,20 +153,26 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
139153
throw new NoSuchFileException(sourceroot.toString)
140154
}
141155
val allSourceFiles = collectAllSourceFiles(sourceroot)
142-
val javaFiles = allSourceFiles.filter(path => javaPattern.matches(path))
143-
val scalaFiles = allSourceFiles.filter(path => scalaPattern.matches(path))
144-
if (javaFiles.isEmpty && scalaFiles.isEmpty) {
156+
val javaFiles = allSourceFiles.filter(javaPattern.matches)
157+
val scalaFiles = allSourceFiles.filter(scalaPattern.matches)
158+
val kotlinFiles = allSourceFiles.filter(kotlinPattern.matches)
159+
if (javaFiles.isEmpty && scalaFiles.isEmpty && kotlinFiles.isEmpty) {
145160
index
146161
.app
147162
.warning(
148-
s"doing nothing, no files matching pattern '$sourceroot/**.{java,scala}'"
163+
s"doing nothing, no files matching pattern '$sourceroot/**.{java,scala,kt}'"
149164
)
150165
return CommandResult(0, Nil)
151166
}
152-
val errors = ListBuffer.empty[Try[Unit]]
153-
compileJavaFiles(tmp, deps, config, javaFiles)
154-
.recover(e => errors.addOne(Failure(e)))
155-
compileScalaFiles(deps, scalaFiles).recover(e => errors.addOne(Failure(e)))
167+
168+
val compileAttemtps = ListBuffer.empty[Try[Unit]]
169+
compileAttemtps += compileJavaFiles(tmp, deps, config, javaFiles)
170+
compileAttemtps += compileScalaFiles(deps, scalaFiles)
171+
compileAttemtps += compileKotlinFiles(deps, config, kotlinFiles)
172+
val errors = compileAttemtps.collect { case Failure(exception) =>
173+
exception
174+
}
175+
156176
if (index.cleanup) {
157177
Files.walkFileTree(tmp, new DeleteVisitor)
158178
}
@@ -168,14 +188,141 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
168188
.info(
169189
"Some SemanticDB files got generated even if there were compile errors. " +
170190
"In most cases, this means that lsif-java managed to index everything " +
171-
"except the locations that had compile errors and you can ignore the compile errors." +
172-
errors.mkString("\n")
191+
"except the locations that had compile errors and you can ignore the compile errors."
173192
)
193+
errors.foreach { error =>
194+
index.app.reporter.info(error.getMessage())
195+
}
174196
}
175197
CommandResult(0, Nil)
176198
}
177199
}
178200

201+
private def compileKotlinFiles(
202+
deps: Dependencies,
203+
config: Config,
204+
allKotlinFiles: List[Path]
205+
): Try[Unit] = {
206+
if (allKotlinFiles.isEmpty)
207+
return Success()
208+
val filesPaths = allKotlinFiles.map(_.toString)
209+
210+
val plugin =
211+
Dependencies
212+
.resolveDependencies(
213+
List(
214+
s"com.sourcegraph:semanticdb-kotlinc:${BuildInfo.semanticdbKotlincVersion}"
215+
),
216+
transitive = false
217+
)
218+
.classpath
219+
.head
220+
221+
val self = config.dependencies.head
222+
val commonKotlinFiles: List[Path] =
223+
Dependencies
224+
.kotlinMPPCommon(self.groupId, self.artifactId, self.version) match {
225+
case Some(common) =>
226+
val outdir = Files.createTempDirectory("lsifjava-kotlin")
227+
val file = common.toFile
228+
val basename = file
229+
.getName
230+
.substring(0, file.getName.lastIndexOf("."))
231+
val newFiles = ListBuffer[Path]()
232+
val jar = new JarFile(file)
233+
val enu = jar.entries
234+
while (enu.hasMoreElements) {
235+
val entry = enu.nextElement
236+
val entryPath =
237+
if (entry.getName.startsWith(basename))
238+
entry.getName.substring(basename.length)
239+
else
240+
entry.getName
241+
242+
if (entry.isDirectory) {
243+
new File(outdir.toString, entryPath).mkdirs
244+
} else if (entry.getName.endsWith(".kt")) {
245+
val newFile = new File(outdir.toString, entryPath)
246+
newFiles.addOne(newFile.toPath)
247+
val istream = jar.getInputStream(entry)
248+
val ostream = new FileOutputStream(newFile)
249+
Iterator
250+
.continually(istream.read)
251+
.takeWhile(-1 !=)
252+
.foreach(ostream.write)
253+
ostream.close()
254+
istream.close()
255+
}
256+
}
257+
newFiles.toList
258+
case None =>
259+
List[Path]()
260+
}
261+
262+
val kargs: K2JVMCompilerArguments = new K2JVMCompilerArguments()
263+
val args = ListBuffer[String](
264+
"-nowarn",
265+
"-no-reflect",
266+
"-no-stdlib",
267+
"-Xmulti-platform",
268+
"-Xno-check-actual",
269+
"-Xopt-in=kotlin.RequiresOptIn",
270+
"-Xopt-in=kotlin.ExperimentalUnsignedTypes",
271+
"-Xopt-in=kotlin.ExperimentalStdlibApi",
272+
"-Xopt-in=kotlin.ExperimentalMultiplatform",
273+
"-Xopt-in=kotlin.contracts.ExperimentalContracts",
274+
"-Xallow-kotlin-package",
275+
s"-Xplugin=$plugin",
276+
"-P",
277+
s"plugin:semanticdb-kotlinc:sourceroot=$sourceroot",
278+
"-P",
279+
s"plugin:semanticdb-kotlinc:targetroot=$targetroot",
280+
"-classpath",
281+
deps.classpathSyntax
282+
)
283+
284+
if (commonKotlinFiles.nonEmpty) {
285+
args +=
286+
s"-Xcommon-sources=${commonKotlinFiles.map(_.toAbsolutePath.toString).mkString(",")}"
287+
}
288+
289+
args ++= filesPaths ++ commonKotlinFiles.map(_.toAbsolutePath.toString)
290+
291+
parseCommandLineArguments(args.asJava, kargs)
292+
293+
val exit = new K2JVMCompiler().exec(
294+
new MessageCollector {
295+
private val errors = new util.LinkedList[String]
296+
override def clear(): Unit = errors.clear()
297+
298+
override def hasErrors: Boolean = !errors.isEmpty
299+
300+
override def report(
301+
compilerMessageSeverity: CompilerMessageSeverity,
302+
s: String,
303+
compilerMessageSourceLocation: CompilerMessageSourceLocation
304+
): Unit = {
305+
if (
306+
s.endsWith("without a body must be abstract") ||
307+
s.endsWith("must have a body")
308+
)
309+
return // we get these when indexing the stdlib, no other solution found yet
310+
val msg = MessageRenderer
311+
.PLAIN_FULL_PATHS
312+
.render(compilerMessageSeverity, s, compilerMessageSourceLocation)
313+
index.app.reporter.debug(msg)
314+
errors.push(msg)
315+
}
316+
},
317+
Services.EMPTY,
318+
kargs
319+
)
320+
if (exit.getCode == 0)
321+
Success(())
322+
else
323+
Failure(new Exception(exit.toString))
324+
}
325+
179326
private def compileScalaFiles(
180327
deps: Dependencies,
181328
allScalaFiles: List[Path]

0 commit comments

Comments
 (0)