Skip to content

Commit 0bda9f0

Browse files
authored
Merge pull request #3767 from Gedochao/maintenance/repl-with-java-24
Prevent the REPL from warning about restricted `java.lang.System` API on JDK 24
2 parents 8298cc0 + ccbbc4a commit 0bda9f0

File tree

7 files changed

+113
-43
lines changed

7 files changed

+113
-43
lines changed

modules/build/src/main/scala/scala/build/ReplArtifacts.scala

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import coursier.core.Repository
55
import coursier.util.Task
66
import dependency.*
77

8+
import java.io.File
9+
810
import scala.build.EitherCps.{either, value}
911
import scala.build.errors.BuildException
1012
import scala.build.internal.CsLoggerUtil.*
@@ -89,40 +91,69 @@ object ReplArtifacts {
8991
logger: Logger,
9092
cache: FileCache[Task],
9193
repositories: Seq[Repository],
92-
addScalapy: Option[String]
94+
addScalapy: Option[String],
95+
javaVersion: Int
9396
): Either[BuildException, ReplArtifacts] = either {
9497
val isScala2 = scalaParams.scalaVersion.startsWith("2.")
9598
val replDep =
96-
if (isScala2) dep"org.scala-lang:scala-compiler:${scalaParams.scalaVersion}"
99+
if isScala2 then dep"org.scala-lang:scala-compiler:${scalaParams.scalaVersion}"
97100
else dep"org.scala-lang::scala3-compiler:${scalaParams.scalaVersion}"
98101
val scalapyDeps =
99102
addScalapy.map(ver => dep"${Artifacts.scalaPyOrganization(ver)}::scalapy-core::$ver").toSeq
100-
val externalDeps = dependencies ++ scalapyDeps
101-
val replArtifacts =
103+
val externalDeps = dependencies ++ scalapyDeps
104+
val replArtifacts: Seq[(String, os.Path)] = value {
102105
Artifacts.artifacts(
103106
Seq(replDep).map(Positioned.none),
104107
repositories,
105108
Some(scalaParams),
106109
logger,
107110
cache.withMessage(s"Downloading Scala compiler ${scalaParams.scalaVersion}")
108111
)
109-
val depArtifacts = Artifacts.artifacts(
110-
externalDeps.map(Positioned.none),
111-
repositories,
112-
Some(scalaParams),
113-
logger,
114-
cache.withMessage(s"Downloading REPL dependencies")
115-
)
112+
}
113+
val depArtifacts: Seq[(String, os.Path)] = value {
114+
Artifacts.artifacts(
115+
externalDeps.map(Positioned.none),
116+
repositories,
117+
Some(scalaParams),
118+
logger,
119+
cache.withMessage(s"Downloading REPL dependencies")
120+
)
121+
}
116122
val mainClass =
117-
if (isScala2) "scala.tools.nsc.MainGenericRunner"
123+
if isScala2 then "scala.tools.nsc.MainGenericRunner"
118124
else "dotty.tools.repl.Main"
125+
val defaultReplJavaOpts = Seq("-Dscala.usejavacp=true")
126+
val jlineArtifacts =
127+
replArtifacts
128+
.map(_._2.toString)
129+
.filter(_.contains("jline"))
130+
val jlineJavaOpts: Seq[String] =
131+
if javaVersion >= 24 && jlineArtifacts.nonEmpty then {
132+
val modulePath = Seq("--module-path", jlineArtifacts.mkString(File.pathSeparator))
133+
val remainingOpts =
134+
if isScala2 then
135+
Seq(
136+
"--add-modules",
137+
"org.jline",
138+
"--enable-native-access=org.jline"
139+
)
140+
else
141+
Seq(
142+
"--add-modules",
143+
"org.jline.terminal",
144+
"--enable-native-access=org.jline.nativ"
145+
)
146+
modulePath ++ remainingOpts
147+
}
148+
else Seq.empty
149+
val replJavaOpts = defaultReplJavaOpts ++ jlineJavaOpts
119150
ReplArtifacts(
120-
replArtifacts = value(replArtifacts),
121-
depArtifacts = value(depArtifacts),
151+
replArtifacts = replArtifacts,
152+
depArtifacts = depArtifacts,
122153
extraClassPath = extraClassPath,
123154
extraSourceJars = Nil,
124155
replMainClass = mainClass,
125-
replJavaOpts = Seq("-Dscala.usejavacp=true"),
156+
replJavaOpts = replJavaOpts,
126157
addSourceJars = false
127158
)
128159
}

modules/cli/src/main/scala/scala/cli/commands/ScalaCommand.scala

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -244,16 +244,18 @@ abstract class ScalaCommand[T <: HasGlobalOptions](implicit myParser: Parser[T],
244244
}
245245
else if (shared.helpGroups.helpRepl) {
246246
val initialBuildOptions = buildOptionsOrExit(options)
247-
val artifacts = initialBuildOptions.artifacts(logger, Scope.Main).orExit(logger)
248-
val replArtifacts = value {
247+
val artifacts = initialBuildOptions.artifacts(logger, Scope.Main).orExit(logger)
248+
val javaVersion: Int = initialBuildOptions.javaHome().value.version
249+
val replArtifacts = value {
249250
ReplArtifacts.default(
250-
scalaParams,
251-
artifacts.userDependencies,
252-
Nil,
253-
logger,
254-
buildOptions.finalCache,
255-
Nil,
256-
None
251+
scalaParams = scalaParams,
252+
dependencies = artifacts.userDependencies,
253+
extraClassPath = Nil,
254+
logger = logger,
255+
cache = buildOptions.finalCache,
256+
repositories = Nil,
257+
addScalapy = None,
258+
javaVersion = javaVersion
257259
)
258260
}
259261
replArtifacts.replClassPath -> replArtifacts.replMainClass

modules/cli/src/main/scala/scala/cli/commands/repl/Repl.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,9 +452,10 @@ object Repl extends ScalaCommand[ReplOptions] with BuildCommandHelpers {
452452
cache,
453453
value(options.finalRepositories),
454454
addScalapy =
455-
if (setupPython)
455+
if setupPython then
456456
Some(options.notForBloopOptions.scalaPyVersion.getOrElse(Constants.scalaPyVersion))
457-
else None
457+
else None,
458+
javaVersion = options.javaHome().value.version
458459
)
459460
}
460461
}

modules/integration/src/test/scala/scala/cli/integration/RunJdkTestDefinitions.scala

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package scala.cli.integration
22

33
import com.eed3si9n.expecty.Expecty.expect
44

5-
import scala.util.Properties
5+
import scala.cli.integration.TestUtil.ProcOps
6+
import scala.util.{Properties, Try}
67

78
trait RunJdkTestDefinitions { _: RunTestDefinitions =>
89
def javaIndex(javaVersion: Int): String =
@@ -129,5 +130,26 @@ trait RunJdkTestDefinitions { _: RunTestDefinitions =>
129130
}
130131
}
131132
}
133+
134+
// the warnings were introduced in JDK 24, so we only test this for JDKs >= 24
135+
// the issue never affected Scala 2.12, so we skip it for that version
136+
if (
137+
!actualScalaVersion.startsWith("2.12") &&
138+
!useScalaInstallationWrapper &&
139+
Try(index.toInt).map(_ >= 24).getOrElse(false)
140+
)
141+
// TODO: test with Scala installation wrapper when the fix gets propagated there
142+
test(s"REPL does not warn about restricted java.lang.System API called on JDK $index") {
143+
TestInputs.empty.fromRoot { root =>
144+
TestUtil.withProcessWatching(
145+
proc = os.proc(TestUtil.cli, "repl", extraOptions, "--jvm", index)
146+
.spawn(cwd = root, stderr = os.Pipe)
147+
) { (proc, _, ec) =>
148+
proc.printStderrUntilJlineRevertsToDumbTerminal(proc) { s =>
149+
expect(!s.contains("A restricted method in java.lang.System has been called"))
150+
}(ec)
151+
}
152+
}
153+
}
132154
}
133155
}

modules/integration/src/test/scala/scala/cli/integration/RunWithWatchTestDefinitions.scala

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,12 @@
11
package scala.cli.integration
22

33
import com.eed3si9n.expecty.Expecty.expect
4-
import os.SubProcess
54

6-
import scala.concurrent.ExecutionContext
7-
import scala.concurrent.duration.{Duration, DurationInt}
5+
import scala.cli.integration.TestUtil.ProcOps
6+
import scala.concurrent.duration.DurationInt
87
import scala.util.{Properties, Try}
98

109
trait RunWithWatchTestDefinitions { _: RunTestDefinitions =>
11-
implicit class ProcOps(proc: SubProcess) {
12-
def printStderrUntilRerun(timeout: Duration)(implicit ec: ExecutionContext): Unit = {
13-
def rerunWasTriggered(): Boolean = {
14-
val stderrOutput = TestUtil.readLine(proc.stderr, ec, timeout)
15-
println(stderrOutput)
16-
stderrOutput.contains("re-run")
17-
}
18-
while (!rerunWasTriggered()) Thread.sleep(100L)
19-
}
20-
}
21-
2210
// TODO make this pass reliably on Mac CI
2311
if (!Properties.isMac || !TestUtil.isCI) {
2412
val expectedMessage1 = "Hello"

modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,12 +368,38 @@ object TestUtil {
368368
finally if (proc.isAlive()) {
369369
proc.destroy()
370370
Thread.sleep(200L)
371-
if (proc.isAlive()) proc.destroyForcibly()
371+
if (proc.isAlive()) proc.destroy(shutdownGracePeriod = 0)
372372
}
373373

374374
implicit class StringOps(a: String) {
375375
def countOccurrences(b: String): Int =
376376
if (b.isEmpty) 0 // Avoid infinite splitting
377377
else a.sliding(b.length).count(_ == b)
378378
}
379+
380+
def printStderrUntilCondition(
381+
proc: os.SubProcess,
382+
timeout: Duration = 90.seconds
383+
)(condition: String => Boolean)(
384+
f: String => Unit = _ => ()
385+
)(implicit ec: ExecutionContext): Unit = {
386+
def revertTriggered(): Boolean = {
387+
val stderrOutput = TestUtil.readLine(proc.stderr, ec, timeout)
388+
println(stderrOutput)
389+
f(stderrOutput)
390+
condition(stderrOutput)
391+
}
392+
393+
while (!revertTriggered()) Thread.sleep(100L)
394+
}
395+
396+
implicit class ProcOps(proc: os.SubProcess) {
397+
def printStderrUntilJlineRevertsToDumbTerminal(proc: os.SubProcess)(
398+
f: String => Unit
399+
)(implicit ec: ExecutionContext): Unit =
400+
TestUtil.printStderrUntilCondition(proc)(_.contains("creating a dumb terminal"))(f)
401+
402+
def printStderrUntilRerun(timeout: Duration)(implicit ec: ExecutionContext): Unit =
403+
TestUtil.printStderrUntilCondition(proc, timeout)(_.contains("re-run"))()
404+
}
379405
}

project/deps/package.mill.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ object Java {
8383
def minimumBloopJava: Int = 17
8484
def minimumInternalJava: Int = 16
8585
def defaultJava: Int = minimumBloopJava
86-
def mainJavaVersions: Seq[Int] = Seq(8, 11, 17, 21, 23)
86+
def mainJavaVersions: Seq[Int] = Seq(8, 11, 17, 21, 23, 24)
8787
def allJavaVersions: Seq[Int] =
8888
(mainJavaVersions ++ Seq(minimumBloopJava, minimumInternalJava, defaultJava)).distinct
8989
}

0 commit comments

Comments
 (0)