11package com .sourcegraph .lsif_java .buildtools
22
3- import java .io .File
4- import java .io .IOException
3+ import java .io .{File , FileOutputStream , IOException }
54import java .net .URLClassLoader
65import java .nio .file .FileSystems
76import java .nio .file .FileVisitResult
@@ -17,17 +16,14 @@ import java.util.Collections
1716import java .util .Optional
1817import java .util .ServiceLoader
1918import java .util .concurrent .TimeUnit
20-
2119import scala .collection .mutable .ListBuffer
2220import scala .jdk .CollectionConverters ._
2321import scala .util .Failure
2422import scala .util .Success
2523import scala .util .Try
2624import scala .util .control .NonFatal
27-
2825import scala .meta .pc .PresentationCompiler
2926import scala .meta .pc .PresentationCompilerConfig
30-
3127import com .sourcegraph .io .DeleteVisitor
3228import com .sourcegraph .lsif_java .BuildInfo
3329import com .sourcegraph .lsif_java .Dependencies
@@ -46,10 +42,23 @@ import moped.macros.ClassShape
4642import moped .parsers .JsonParser
4743import moped .reporters .Diagnostic
4844import moped .reporters .Input
45+ import org .jetbrains .kotlin .cli .common .arguments .K2JVMCompilerArguments
46+ import org .jetbrains .kotlin .cli .common .arguments .ParseCommandLineArgumentsKt .parseCommandLineArguments
47+ import org .jetbrains .kotlin .cli .common .messages .{
48+ CompilerMessageSeverity ,
49+ CompilerMessageSourceLocation ,
50+ MessageCollector ,
51+ MessageRenderer
52+ }
53+ import org .jetbrains .kotlin .cli .jvm .K2JVMCompiler
54+ import org .jetbrains .kotlin .config .Services
4955import os .CommandResult
5056import os .ProcessOutput .Readlines
5157import os .SubprocessException
5258
59+ import java .util .jar .JarFile
60+ import scala .language .postfixOps
61+
5362/**
5463 * A custom build tool that is specifically made for lsif-java.
5564 *
@@ -72,9 +81,12 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
7281 private val scalaPattern = FileSystems
7382 .getDefault
7483 .getPathMatcher(" glob:**.scala" )
84+ private val kotlinPattern = FileSystems
85+ .getDefault
86+ .getPathMatcher(" glob:**.kt" )
7587 private val allPatterns = FileSystems
7688 .getDefault
77- .getPathMatcher(" glob:**.{java,scala}" )
89+ .getPathMatcher(" glob:**.{java,scala,kt }" )
7890 private val moduleInfo = Paths .get(" module-info.java" )
7991
8092 override def usedInCurrentDirectory (): Boolean =
@@ -139,20 +151,22 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
139151 throw new NoSuchFileException (sourceroot.toString)
140152 }
141153 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) {
154+ val javaFiles = allSourceFiles.filter(javaPattern.matches)
155+ val scalaFiles = allSourceFiles.filter(scalaPattern.matches)
156+ val kotlinFiles = allSourceFiles.filter(kotlinPattern.matches)
157+ if (javaFiles.isEmpty && scalaFiles.isEmpty && kotlinFiles.isEmpty) {
145158 index
146159 .app
147160 .warning(
148- s " doing nothing, no files matching pattern ' $sourceroot/**.{java,scala}' "
161+ s " doing nothing, no files matching pattern ' $sourceroot/**.{java,scala,kt }' "
149162 )
150163 return CommandResult (0 , Nil )
151164 }
165+
152166 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+ errors += compileJavaFiles(tmp, deps, config, javaFiles)
168+ errors += compileScalaFiles(deps, scalaFiles )
169+ errors += compileKotlinFiles (deps, config, kotlinFiles )
156170 if (index.cleanup) {
157171 Files .walkFileTree(tmp, new DeleteVisitor )
158172 }
@@ -168,14 +182,142 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
168182 .info(
169183 " Some SemanticDB files got generated even if there were compile errors. " +
170184 " 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." +
185+ " except the locations that had compile errors and you can ignore the compile errors.\n " +
172186 errors.mkString(" \n " )
173187 )
174188 }
175189 CommandResult (0 , Nil )
176190 }
177191 }
178192
193+ private def compileKotlinFiles (
194+ deps : Dependencies ,
195+ config : Config ,
196+ allKotlinFiles : List [Path ]
197+ ): Try [Unit ] = {
198+ if (allKotlinFiles.isEmpty)
199+ return Success ()
200+ val filesPaths = allKotlinFiles.map(_.toString)
201+
202+ val plugin =
203+ Dependencies
204+ .resolveDependencies(
205+ List (
206+ s " com.sourcegraph:semanticdb-kotlinc: ${BuildInfo .semanticdbKotlincVersion}"
207+ ),
208+ transitive = false
209+ )
210+ .classpath
211+ .head
212+
213+ val self = config.dependencies.head
214+ val commonKotlinFiles : List [Path ] =
215+ Dependencies
216+ .kotlinMPPCommon(self.groupId, self.artifactId, self.version) match {
217+ case Some (common) =>
218+ val outdir = Files .createTempDirectory(" lsifjava-kotlin" )
219+ val file = common.toFile
220+ val basename = file
221+ .getName
222+ .substring(0 , file.getName.lastIndexOf(" ." ))
223+ val newFiles = ListBuffer [Path ]()
224+ val jar = new JarFile (file)
225+ val enu = jar.entries
226+ while (enu.hasMoreElements) {
227+ val entry = enu.nextElement
228+ val entryPath =
229+ if (entry.getName.startsWith(basename))
230+ entry.getName.substring(basename.length)
231+ else
232+ entry.getName
233+
234+ if (entry.isDirectory) {
235+ new File (outdir.toString, entryPath).mkdirs
236+ } else if (entry.getName.endsWith(" .kt" )) {
237+ val newFile = new File (outdir.toString, entryPath)
238+ newFiles.addOne(newFile.toPath)
239+ val istream = jar.getInputStream(entry)
240+ val ostream = new FileOutputStream (newFile)
241+ Iterator
242+ .continually(istream.read)
243+ .takeWhile(- 1 != )
244+ .foreach(ostream.write)
245+ ostream.close()
246+ istream.close()
247+ }
248+ }
249+ newFiles.toList
250+ case None =>
251+ List [Path ]()
252+ }
253+
254+ val kargs : K2JVMCompilerArguments = new K2JVMCompilerArguments ()
255+ val args = ListBuffer [String ](
256+ " -nowarn" ,
257+ " -no-reflect" ,
258+ " -no-stdlib" ,
259+ " -Xmulti-platform" ,
260+ " -Xno-check-actual" ,
261+ " -Xopt-in=kotlin.RequiresOptIn" ,
262+ " -Xopt-in=kotlin.ExperimentalUnsignedTypes" ,
263+ " -Xopt-in=kotlin.ExperimentalStdlibApi" ,
264+ " -Xopt-in=kotlin.ExperimentalMultiplatform" ,
265+ " -Xopt-in=kotlin.contracts.ExperimentalContracts" ,
266+ " -Xallow-kotlin-package" ,
267+ s " -Xplugin= $plugin" ,
268+ " -P" ,
269+ s " plugin:semanticdb-kotlinc:sourceroot= $sourceroot" ,
270+ " -P" ,
271+ s " plugin:semanticdb-kotlinc:targetroot= $targetroot" ,
272+ " -classpath" ,
273+ deps.classpathSyntax
274+ )
275+
276+ if (commonKotlinFiles.nonEmpty) {
277+ args +=
278+ s " -Xcommon-sources= ${commonKotlinFiles.map(_.toAbsolutePath.toString).mkString(" ," )}"
279+ }
280+
281+ args ++= filesPaths ++ commonKotlinFiles.map(_.toAbsolutePath.toString)
282+
283+ parseCommandLineArguments(args.asJava, kargs)
284+
285+ if (true )
286+ println(s " ARGS ${args.mkString(" " )}" )
287+
288+ val exit = new K2JVMCompiler ().exec(
289+ new MessageCollector {
290+ private val errors = new util.LinkedList [String ]
291+ override def clear (): Unit = errors.clear()
292+
293+ override def hasErrors : Boolean = ! errors.isEmpty
294+
295+ override def report (
296+ compilerMessageSeverity : CompilerMessageSeverity ,
297+ s : String ,
298+ compilerMessageSourceLocation : CompilerMessageSourceLocation
299+ ): Unit = {
300+ if (
301+ s.endsWith(" without a body must be abstract" ) ||
302+ s.endsWith(" must have a body" )
303+ )
304+ return // we get these when indexing the stdlib, no other solution found yet
305+ val msg = MessageRenderer
306+ .PLAIN_FULL_PATHS
307+ .render(compilerMessageSeverity, s, compilerMessageSourceLocation)
308+ System .err.println(msg)
309+ errors.push(msg)
310+ }
311+ },
312+ Services .EMPTY ,
313+ kargs
314+ )
315+ if (exit.getCode == 0 )
316+ Success (())
317+ else
318+ Failure (new Exception (exit.toString))
319+ }
320+
179321 private def compileScalaFiles (
180322 deps : Dependencies ,
181323 allScalaFiles : List [Path ]
0 commit comments