Skip to content

Commit 932c33f

Browse files
authored
Merge pull request #3029 from jozanek/fix/2888-caller-jvm-options-win
Let caller-supplied JVM options override config javaOptions
2 parents c97b966 + bcb2ddd commit 932c33f

3 files changed

Lines changed: 67 additions & 4 deletions

File tree

frontend/src/main/scala/bloop/exec/JvmProcessForker.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ final class JvmForker(config: JdkConfig, classpath: Array[AbsolutePath]) extends
138138
opts: CommonOptions,
139139
extraClasspath: Array[AbsolutePath]
140140
): Task[Int] = {
141-
val jvmOptions = jargs ++ config.javaOptions
141+
// Caller options come last so they take precedence: for single-valued options
142+
// (e.g. -Duser.dir or -Xmx) the JVM honours the last occurrence
143+
val jvmOptions = config.javaOptions ++ jargs
142144
val fullClasspath = classpath ++ extraClasspath
143145
val fullClasspathStr = fullClasspath.map(_.syntax).mkString(File.pathSeparator)
144146

frontend/src/test/scala/bloop/ForkerSpec.scala

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ class ForkerSpec {
5858
cwd: AbsolutePath,
5959
args: Array[String],
6060
extraClasspath: Array[AbsolutePath] = Array.empty,
61-
envs: List[String] = Nil
61+
envs: List[String] = Nil,
62+
configJavaOptions: Array[String] = Array.empty,
63+
jargs: Array[String] = Array.empty
6264
)(
6365
op: (Int, List[(String, String)]) => Unit
6466
): Unit =
6567
TestUtil.checkAfterCleanCompilation(runnableProject, dependencies) { state =>
6668
val project = TestUtil.getProject(TestUtil.RootProject, state)
67-
val env = JdkConfig.default
69+
val env = JdkConfig.default.copy(javaOptions = configJavaOptions)
6870
val classpath = project.fullRuntimeClasspath(state.build.getDagFor(project), state.client)
6971
val config = JvmProcessForker(env, classpath)
7072
val logger = new RecordingLogger
@@ -76,7 +78,7 @@ class ForkerSpec {
7678
cwd,
7779
mainClass,
7880
args,
79-
Array.empty,
81+
jargs,
8082
envVars = envs,
8183
logger.asVerbose,
8284
opts,
@@ -223,6 +225,25 @@ class ForkerSpec {
223225
}
224226
}
225227

228+
@Test
229+
def callerJvmOptionsOverrideConfigJavaOptions(): Unit = TestUtil.withinWorkspace { tmp =>
230+
val injectedDir = tmp.resolve("injected-dir")
231+
val wantedDir = tmp.resolve("wanted-dir")
232+
Files.createDirectory(injectedDir.underlying)
233+
Files.createDirectory(wantedDir.underlying)
234+
run(
235+
tmp,
236+
Array.empty,
237+
configJavaOptions = Array(s"-Duser.dir=${injectedDir.syntax}"),
238+
jargs = Array(s"-Duser.dir=${wantedDir.syntax}")
239+
) {
240+
case (exitCode, messages) =>
241+
val expected = "info" -> s"CWD: ${wantedDir.underlying.toRealPath()}"
242+
assertEquals(0, exitCode.toLong)
243+
assert(messages.contains(expected), s"$messages did not contain $expected")
244+
}
245+
}
246+
226247
@Test
227248
def jdwpAgentArgKeepsDapDefaults(): Unit = {
228249
// The default debug mode (no fixed address, suspend) is what the DAP/Metals path relies on:

frontend/src/test/scala/bloop/dap/DebugProtocolSpec.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import java.nio.file.Files
55

66
import ch.epfl.scala.bsp
77

8+
import bloop.config.Config
89
import bloop.logging.RecordingLogger
910
import bloop.util.TestProject
1011
import bloop.util.TestUtil
@@ -90,6 +91,45 @@ object DebugProtocolSpec extends DebugBspBaseSuite {
9091
}
9192
}
9293

94+
test("caller jvm options override config java options") {
95+
TestUtil.withinWorkspace { workspace =>
96+
val main =
97+
"""|/main/scala/Main.scala
98+
|object Main {
99+
| def main(args: Array[String]): Unit = {
100+
| print(sys.props("world"))
101+
| }
102+
|}
103+
|""".stripMargin
104+
105+
val logger = new RecordingLogger(ansiCodesSupported = false)
106+
val jvmConfig = Config.JvmConfig(None, List("-Dworld=config"))
107+
val project = TestProject(workspace, "p", List(main), jvmConfig = Some(jvmConfig))
108+
109+
loadBspState(workspace, List(project), logger) { state =>
110+
val params = mainClassParams("Main", jvmOptions = List("-J-Dworld=user"))
111+
val output = state.withDebugSession(project, params) { client =>
112+
for {
113+
_ <- client.initialize()
114+
_ <- client.launch(noDebug = true)
115+
_ <- client.configurationDone()
116+
_ <- client.exited
117+
_ <- client.terminated
118+
output <- client.blockForAllOutput
119+
} yield output
120+
}
121+
122+
assertNoDiff(
123+
output.linesIterator
124+
.filterNot(_.contains("ERROR: JDWP Unable to get JNI 1.2 environment"))
125+
.filterNot(_.contains("JDWP exit error AGENT_ERROR_NO_JNI_ENV"))
126+
.mkString("\n"),
127+
"user"
128+
)
129+
}
130+
}
131+
}
132+
93133
// when the session detaches from the JVM, the JDI once again writes to the standard output
94134
flakyTest("restarted session does not contain JDI output", 3) {
95135
TestUtil.withinWorkspace { workspace =>

0 commit comments

Comments
 (0)