11package com .sourcegraph .lsif_java .buildtools
22
33import java .io .File
4+ import java .io .FileOutputStream
45import java .io .IOException
56import java .net .URLClassLoader
67import java .nio .file .FileSystems
@@ -17,9 +18,11 @@ import java.util.Collections
1718import java .util .Optional
1819import java .util .ServiceLoader
1920import java .util .concurrent .TimeUnit
21+ import java .util .jar .JarFile
2022
2123import scala .collection .mutable .ListBuffer
2224import scala .jdk .CollectionConverters ._
25+ import scala .language .postfixOps
2326import scala .util .Failure
2427import scala .util .Success
2528import scala .util .Try
@@ -46,6 +49,14 @@ import moped.macros.ClassShape
4649import moped .parsers .JsonParser
4750import moped .reporters .Diagnostic
4851import 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
4960import os .CommandResult
5061import os .ProcessOutput .Readlines
5162import 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