Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bugfix: Include semanticdb plugin path in java options processorpath #2072

Merged
merged 12 commits into from
Jun 28, 2023
Merged
30 changes: 26 additions & 4 deletions frontend/src/main/scala/bloop/data/Project.scala
Original file line number Diff line number Diff line change
Expand Up @@ -474,11 +474,33 @@ object Project {
options ++ ysemanticdb ++ sourceRoot
}

def enableJavaSemanticdbOptions(options: List[String]): List[String] = {
if (hasJavaSemanticDBEnabledInCompilerOptions(options)) options
def hasProcessorPath(options: List[String]): Boolean =
options.contains("-processorpath")

def includeSemnaticDBInProcessorPath(
gersonsosa marked this conversation as resolved.
Show resolved Hide resolved
options: List[String],
pluginPath: AbsolutePath
): List[String] = {
if (hasProcessorPath(options)) {
val (before, after) = options.splitAt(options.indexOf("-processorpath") + 1)
if (!after.head.contains(pluginPath)) {
before ::: s"${after.head}:$pluginPath" :: after.tail
} else options
} else options

}

def enableJavaSemanticdbOptions(
options: List[String],
pluginPath: AbsolutePath
): List[String] = {
val pluginOptions = includeSemnaticDBInProcessorPath(options, pluginPath)
gersonsosa marked this conversation as resolved.
Show resolved Hide resolved

if (hasJavaSemanticDBEnabledInCompilerOptions(pluginOptions))
pluginOptions
else {
val semanticdbOptions =
s"-Xplugin:semanticdb -sourceroot:${workspaceDir} -targetroot:javac-classes-directory" :: options
s"-Xplugin:semanticdb -sourceroot:${workspaceDir} -targetroot:javac-classes-directory" :: pluginOptions
if (project.javaVersionAtLeast("17", logger))
List(
"-J--add-exports",
Expand Down Expand Up @@ -531,7 +553,7 @@ object Project {
case None =>
scalaProjectWithRangePositions
case Some(pluginPath) =>
val javacOptionsWithSemanticDB = enableJavaSemanticdbOptions(javacOptions)
val javacOptionsWithSemanticDB = enableJavaSemanticdbOptions(javacOptions, pluginPath)
val classpathWithSemanticDB =
enableJavaSemanticdbClasspath(pluginPath, scalaProjectWithRangePositions.rawClasspath)
scalaProjectWithRangePositions.copy(
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/test/resources/scala-java-processorpath/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
bloopConfigDir in Global := baseDirectory.value / "bloop-config"
gersonsosa marked this conversation as resolved.
Show resolved Hide resolved

ThisBuild / scalaVersion := "2.12.17"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "example"
ThisBuild / organizationName := "example"

lazy val analyzerPath = taskKey[String]("get analyzer path")

lazy val `scala-java-processorpath` = (project in file("."))
.settings(
name := "scala-java-processorpath",
analyzerPath := {
def findPath(base: File): Seq[File] = {
val finder: PathFinder = (base / "src") ** "FoobarValueAnalyzer.java"
finder.get
}

val analyzerPath = findPath(baseDirectory.value)

analyzerPath.head.getAbsolutePath
}
)

javacOptions ++= Seq("-processorpath", analyzerPath.value)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=1.7.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package example;

import java.util.List;
import java.util.Set;

public abstract class FoobarValue {
public abstract int foo();
public abstract String bar();
public abstract List<Integer> buz();
public abstract Set<Long> crux();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package example;

import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.*;

public class FoobarValueAnalyzer implements Plugin {
private static class InternalTaskListener implements TaskListener {
private final JavacTask task;

public InternalTaskListener(JavacTask task) {
this.task = task;
}

@Override
public void started(TaskEvent event) {
if (event.getKind() == TaskEvent.Kind.ANALYZE) {
Trees trees = Trees.instance(task);
System.out.println("Starting analysis ... ");
Iterable<? extends CompilationUnitTree> compilationUnits;
try {
compilationUnits = task.parse();
for (CompilationUnitTree compilationUnit : compilationUnits) {
System.out.println("Analyzing " + compilationUnit.getSourceFile());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

@Override
public void finished(TaskEvent event) {
System.out.println("Finished");
}
}

@Override
public String getName() {
return "FoobarValueAnalyzer";
}

@Override
public void init(JavacTask task, String... args) {
task.addTaskListener(new InternalTaskListener(task));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Main extends App {
println("Hello world!")
}
25 changes: 25 additions & 0 deletions frontend/src/test/scala/bloop/bsp/BspBaseSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,31 @@ abstract class BspBaseSuite extends BaseSuite with BspClientTest {
}
}

def loadBspMetalsBuildFromResources(
buildName: String,
workspace: AbsolutePath,
logger: RecordingLogger,
bspClientName: String,
bloopExtraParams: BloopExtraBuildParams = BloopExtraBuildParams.empty
gersonsosa marked this conversation as resolved.
Show resolved Hide resolved
)(runTest: ManagedBspTestBuild => Unit): Unit = {
val testBuild = loadBuildFromResources(buildName, workspace, logger)
val testState = testBuild.state
val configDir = testState.build.origin
val bspLogger = new BspClientLogger(logger)
def bspCommand() = createBspCommand(configDir)
openBspConnection(
testState.state,
bspCommand,
configDir,
bspLogger,
clientName = bspClientName,
bloopExtraParams = bloopExtraParams
).withinSession { bspState =>
val bspTestBuild = ManagedBspTestBuild(bspState, testBuild.projects)
runTest(bspTestBuild)
}
}

def loadBspBuildFromResources(
buildName: String,
workspace: AbsolutePath,
Expand Down
79 changes: 79 additions & 0 deletions frontend/src/test/scala/bloop/bsp/BspMetalsClientSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,65 @@ class BspMetalsClientSpec(
}
}

test("compile is successful with semanticDB and javac processorpath") {
TestUtil.withinWorkspace { workspace =>
val logger = new RecordingLogger(ansiCodesSupported = false)

val projectName = "scala-java-processorpath"

val extraParams = BloopExtraBuildParams(
ownsBuildFiles = None,
clientClassesRootDir = None,
semanticdbVersion = Some(semanticdbVersion),
supportedScalaVersions = Some(List(testedScalaVersion)),
javaSemanticdbVersion = Some(javaSemanticdbVersion)
)

loadBspMetalsBuildFromResources(projectName, workspace, logger, "Metals", extraParams) {
build =>
val project = build.projectFor(projectName)
val state = build.state

assertNoDiff(logger.warnings.mkString(lineSeparator), "")
assertNoDiffInSettingsFile(
build.rawState.build.origin,
expectedConfig
)
assertScalacOptions(
state,
project,
s"""-Xplugin-require:semanticdb
|-P:semanticdb:failures:warning
|-P:semanticdb:sourceroot:$workspace
|-P:semanticdb:synthetics:on
|-Xplugin:$semanticdbJar
|-Yrangepos
|""".stripMargin
)
val javacOptions = state.javacOptions(project)._2.items.flatMap(_.options)
val javaSemanticDBJar = "semanticdb-javac-0.5.7.jar"
assert(
javacOptions(javacOptions.indexOf("-processorpath") + 1).contains(javaSemanticDBJar)
)

val compiledState = build.state.compile(project).toTestState
assert(compiledState.status == ExitStatus.Ok)

assertSemanticdbFileForProject(
"/main/scala/example/Main.scala",
compiledState,
projectName
)
assertSemanticdbFileForProject(
"/main/java/example/FoobarValueAnalyzer.java",
compiledState,
projectName
)
}

}
}

private val dummyFooScalaSources = List(
"""/Foo.scala
|class Foo
Expand All @@ -477,6 +536,26 @@ class BspMetalsClientSpec(

private val dummyFooScalaAndBarJavaSources = dummyFooScalaSources ++ dummyBarJavaSources

private def assertSemanticdbFileForProject(
sourceFileName: String,
state: TestState,
projectName: String
): Unit = {
val file = semanticdbFileProject(sourceFileName, state, projectName)
assertIsFile(file)
}

private def semanticdbFileProject(
sourceFileName: String,
state: TestState,
projectName: String
) = {
val project = state.build.getProjectFor(projectName).get
val classesDir = state.client.getUniqueClassesDirFor(project, forceGeneration = true)
val sourcePath = if (sourceFileName.startsWith("/")) sourceFileName else s"/$sourceFileName"
gersonsosa marked this conversation as resolved.
Show resolved Hide resolved
classesDir.resolve(s"META-INF/semanticdb/src/$sourcePath.semanticdb")
}

private def semanticdbFile(sourceFileName: String, state: TestState) = {
val projectA = state.build.getProjectFor("A").get
val classesDir = state.client.getUniqueClassesDirFor(projectA, forceGeneration = true)
Expand Down