@@ -2,23 +2,34 @@ package com.sourcegraph.lsif_java.buildtools
22
33import java .io .File
44import java .io .IOException
5+ import java .net .URLClassLoader
56import java .nio .file .FileSystems
67import java .nio .file .FileVisitResult
78import java .nio .file .Files
89import java .nio .file .NoSuchFileException
910import java .nio .file .Path
1011import java .nio .file .Paths
1112import java .nio .file .SimpleFileVisitor
13+ import java .nio .file .StandardOpenOption
1214import java .nio .file .attribute .BasicFileAttributes
15+ import java .util .ServiceLoader
1316
1417import scala .collection .mutable .ListBuffer
1518import scala .jdk .CollectionConverters ._
19+ import scala .util .Failure
20+ import scala .util .Success
21+ import scala .util .Try
1622import scala .util .control .NonFatal
1723
24+ import scala .meta .pc .PresentationCompiler
25+
1826import com .sourcegraph .io .DeleteVisitor
27+ import com .sourcegraph .lsif_java .BuildInfo
1928import com .sourcegraph .lsif_java .Dependencies
2029import com .sourcegraph .lsif_java .Embedded
2130import com .sourcegraph .lsif_java .commands .IndexCommand
31+ import com .sourcegraph .semanticdb_javac .Semanticdb .TextDocument
32+ import com .sourcegraph .semanticdb_javac .Semanticdb .TextDocuments
2233import moped .json .DecodingContext
2334import moped .json .ErrorResult
2435import moped .json .JsonCodec
@@ -32,6 +43,7 @@ import moped.reporters.Diagnostic
3243import moped .reporters .Input
3344import os .CommandResult
3445import os .ProcessOutput .Readlines
46+ import os .SubprocessException
3547
3648/**
3749 * A custom build tool that is specifically made for lsif-java.
@@ -49,6 +61,16 @@ import os.ProcessOutput.Readlines
4961 */
5062class LsifBuildTool (index : IndexCommand ) extends BuildTool (" LSIF" , index) {
5163
64+ private val javaPattern = FileSystems
65+ .getDefault
66+ .getPathMatcher(" glob:**.java" )
67+ private val scalaPattern = FileSystems
68+ .getDefault
69+ .getPathMatcher(" glob:**.scala" )
70+ private val allPatterns = FileSystems
71+ .getDefault
72+ .getPathMatcher(" glob:**.{java,scala}" )
73+ private val moduleInfo = Paths .get(" module-info.java" )
5274 protected def defaultTargetroot : Path = Paths .get(" target" )
5375 private def configFile =
5476 index.workingDirectory.resolve(LsifBuildTool .ConfigFileName )
@@ -96,40 +118,168 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
96118 Files .createDirectories(tmp)
97119 Files .createDirectories(targetroot)
98120 val deps = Dependencies .resolveDependencies(config.dependencies.map(_.repr))
99- val semanticdbJar = Embedded .semanticdbJar(tmp)
100- val coursier = Embedded .coursier(tmp)
101- val actualClasspath = deps.classpath :+ semanticdbJar
102- val argsfile = targetroot.resolve(" javacopts.txt" )
103121 val sourceroot = index.workingDirectory
104122 if (! Files .isDirectory(sourceroot)) {
105123 throw new NoSuchFileException (sourceroot.toString())
106124 }
107- val allJavaFiles = collectAllJavaFiles(sourceroot)
108- val javaFiles = allJavaFiles
109- .filterNot(_.endsWith(moduleInfo))
110- .map(_.toString())
111- val moduleInfos = allJavaFiles.filter(_.endsWith(moduleInfo))
112- if (javaFiles.isEmpty) {
125+ val allSourceFiles = collectAllSourceFiles(sourceroot)
126+ val javaFiles = allSourceFiles.filter(path => javaPattern.matches(path))
127+ val scalaFiles = allSourceFiles.filter(path => scalaPattern.matches(path))
128+ if (javaFiles.isEmpty && scalaFiles.isEmpty) {
113129 index
114130 .app
115131 .warning(
116- s " doing nothing, no files matching pattern ' $sourceroot/**.java' "
132+ s " doing nothing, no files matching pattern ' $sourceroot/**.{ java,scala} ' "
117133 )
118134 return CommandResult (0 , Nil )
119135 }
120- def generatedDir (name : String ): String = {
121- Files .createDirectory(tmp.resolve(name)).toString()
136+ val errors = ListBuffer .empty[Try [Unit ]]
137+ errors += compileJavaFiles(tmp, deps, config, javaFiles)
138+ errors += compileScalaFiles(deps, scalaFiles)
139+ if (index.cleanup) {
140+ Files .walkFileTree(tmp, new DeleteVisitor )
141+ }
142+ val isSemanticdbGenerated = Files
143+ .isDirectory(targetroot.resolve(" META-INF" ))
144+ if (errors.nonEmpty && ! isSemanticdbGenerated) {
145+ CommandResult (1 , Nil )
146+ } else {
147+ if (isSemanticdbGenerated) {
148+ index
149+ .app
150+ .reporter
151+ .info(
152+ " Some SemanticDB files got generated even if there were compile errors. " +
153+ " In most cases, this means that lsif-java managed to index everything " +
154+ " except the locations that had compile errors and you can ignore the compile errors."
155+ )
156+ }
157+ CommandResult (0 , Nil )
158+ }
159+ }
160+
161+ private def compileScalaFiles (
162+ deps : Dependencies ,
163+ allScalaFiles : List [Path ]
164+ ): Try [Unit ] =
165+ Try {
166+ withScalaPresentationCompiler(deps) { compiler =>
167+ allScalaFiles.foreach { path =>
168+ try compileScalaFile(compiler, path)
169+ catch {
170+ case NonFatal (e) =>
171+ // We want to try and index as much as possible so we don't fail the entire
172+ // compilation even if a single file fails to compile.
173+ index.app.reporter.log(Diagnostic .exception(e))
174+ }
175+ }
176+ }
177+ }
178+
179+ private def compileScalaFile (
180+ compiler : PresentationCompiler ,
181+ path : Path
182+ ): Unit = {
183+ val input = Input .path(path)
184+ val textDocument = TextDocument
185+ .parseFrom(compiler.semanticdbTextDocument(path.toUri, input.text).get())
186+ .toBuilder
187+ .setUri(sourceroot.relativize(path).iterator().asScala.mkString(" /" ))
188+ val textDocuments = TextDocuments
189+ .newBuilder()
190+ .addDocuments(textDocument)
191+ .build()
192+ val relpath = sourceroot
193+ .relativize(path)
194+ .resolveSibling(path.getFileName.toString + " .semanticdb" )
195+ val out = targetroot
196+ .resolve(" META-INF" )
197+ .resolve(" semanticdb" )
198+ .resolve(relpath)
199+ Files .createDirectories(out.getParent)
200+ Files .write(
201+ out,
202+ textDocuments.toByteArray,
203+ StandardOpenOption .TRUNCATE_EXISTING ,
204+ StandardOpenOption .CREATE
205+ )
206+ }
207+
208+ private def withScalaPresentationCompiler [T ](
209+ deps : Dependencies
210+ )(fn : PresentationCompiler => T ): T = {
211+ val scalaVersion = deps
212+ .classpath
213+ .headOption
214+ .flatMap(jar => ScalaVersion .inferFromJar(jar))
215+ .getOrElse {
216+ throw new IllegalArgumentException (
217+ s " failed to infer the Scala version from the dependencies: " +
218+ pprint.PPrinter .BlackWhite .tokenize(deps.classpath).mkString
219+ )
220+ }
221+ val mtags = Dependencies .resolveDependencies(
222+ List (s " org.scalameta:mtags_ ${scalaVersion}: ${BuildInfo .mtags}" )
223+ )
224+ val scalaLibrary = mtags
225+ .classpath
226+ .filter(_.getFileName.toString.contains(" scala-library" ))
227+ val parent = new ScalaCompilerClassLoader (this .getClass.getClassLoader)
228+
229+ val jars = mtags.classpath.map(_.toUri.toURL).toArray
230+ val classloader = new URLClassLoader (jars, parent)
231+ val compilers = ServiceLoader
232+ .load(classOf [PresentationCompiler ], classloader)
233+ .iterator()
234+ if (compilers.hasNext) {
235+ val classpath = deps.classpath ++ scalaLibrary
236+ val argsfile = targetroot.resolve(" javacopts.txt" )
237+ Files .createDirectories(argsfile.getParent)
238+ Files .write(
239+ argsfile,
240+ List (" -classpath" , classpath.mkString(File .pathSeparator)).asJava,
241+ StandardOpenOption .CREATE ,
242+ StandardOpenOption .TRUNCATE_EXISTING
243+ )
244+ val compiler = compilers
245+ .next()
246+ .newInstance(" lsif-java" , classpath.asJava, List [String ]().asJava)
247+ try {
248+ fn(compiler)
249+ } finally {
250+ compiler.shutdown()
251+ }
252+ } else {
253+ throw new IllegalArgumentException (
254+ s " failed to load mtags presentation compiler for Scala version $scalaVersion"
255+ )
122256 }
257+ }
258+
259+ private def compileJavaFiles (
260+ tmp : Path ,
261+ deps : Dependencies ,
262+ config : Config ,
263+ allJavaFiles : List [Path ]
264+ ): Try [Unit ] = {
265+ val (moduleInfos, javaFiles) = allJavaFiles
266+ .partition(_.endsWith(moduleInfo))
267+ if (javaFiles.isEmpty)
268+ return Success (())
269+ val semanticdbJar = Embedded .semanticdbJar(tmp)
270+ val coursier = Embedded .coursier(tmp)
271+ val actualClasspath = deps.classpath :+ semanticdbJar
272+ val argsfile = targetroot.resolve(" javacopts.txt" )
123273 val arguments = ListBuffer .empty[String ]
124274 arguments += " -encoding"
125275 arguments += " utf8"
126276 arguments += " -nowarn"
127277 arguments += " -d"
128- arguments += generatedDir(" d" )
278+ arguments += generatedDir(tmp, " d" )
129279 arguments += " -s"
130- arguments += generatedDir(" s" )
280+ arguments += generatedDir(tmp, " s" )
131281 arguments += " -h"
132- arguments += generatedDir(" h" )
282+ arguments += generatedDir(tmp, " h" )
133283 arguments += " -classpath"
134284 arguments += actualClasspath.mkString(File .pathSeparator)
135285 arguments +=
@@ -142,7 +292,7 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
142292 arguments += " --module-source-path"
143293 arguments += sourceroot.toString
144294 } else {
145- arguments ++= javaFiles
295+ arguments ++= javaFiles.map(_.toString)
146296 }
147297 val quotedArguments = arguments.map(a => " \" " + a + " \" " )
148298 Files .write(argsfile, quotedArguments.asJava)
@@ -169,37 +319,18 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
169319 cwd = os.Path (sourceroot),
170320 check = false
171321 )
172- if (index.cleanup) {
173- Files .walkFileTree(tmp, new DeleteVisitor )
174- }
175- val isSemanticdbGenerated = Files
176- .isDirectory(targetroot.resolve(" META-INF" ))
177- if (result.exitCode != 0 && ! isSemanticdbGenerated) {
178- result
179- } else {
180- if (isSemanticdbGenerated) {
181- index
182- .app
183- .reporter
184- .info(
185- " Some SemanticDB files got generated even if there were compile errors. " +
186- " In most cases, this means that lsif-java managed to index everything " +
187- " except the locations that had compile errors and you can ignore the compile errors."
188- )
189- }
190- CommandResult (0 , Nil )
191- }
322+ if (result.exitCode == 0 )
323+ Success (())
324+ else
325+ Failure (SubprocessException (result))
192326 }
193327
194328 private def clean (): Unit = {
195329 Files .walkFileTree(targetroot, new DeleteVisitor )
196330 }
197331
198- private val moduleInfo = Paths .get(" module-info.java" )
199-
200332 /** Recursively collects all Java files in the working directory */
201- private def collectAllJavaFiles (dir : Path ): List [Path ] = {
202- val javaPattern = FileSystems .getDefault.getPathMatcher(" glob:**.java" )
333+ private def collectAllSourceFiles (dir : Path ): List [Path ] = {
203334 val buf = ListBuffer .empty[Path ]
204335 Files .walkFileTree(
205336 dir,
@@ -217,7 +348,7 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
217348 file : Path ,
218349 attrs : BasicFileAttributes
219350 ): FileVisitResult = {
220- if (javaPattern .matches(file)) {
351+ if (allPatterns .matches(file)) {
221352 buf += file
222353 }
223354 FileVisitResult .CONTINUE
@@ -231,6 +362,10 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) {
231362 buf.toList
232363 }
233364
365+ private def generatedDir (tmp : Path , name : String ): String = {
366+ Files .createDirectory(tmp.resolve(name)).toString()
367+ }
368+
234369 /**
235370 * Gets parsed from "junit:junit:4.13.1" strings inside lsif-java.json files.
236371 */
0 commit comments