Skip to content

Commit 98726c0

Browse files
committed
Add support for alternative system images to the compiler
This PR adds a new `-system` / `--system` setting to scalac which mimics its counterpart in javac. This fixes the biggest problem (at least for us) of scala/bug#13015. It is now possible to compile code against an older system image without enforcing strict module access. scalac generally does not enforce modules (scala/scala-dev#529) but it does when using `-release` (with class lookup based on `ct.sym`) and there was no way to opt out of these restrictions. The usual opt-out in javac is `--add-exports` but it is not supported for system modules in combination with `--release` (https://bugs.openjdk.org/browse/JDK-8178152) so there is no expectation that scalac could support it. Instead the solution for javac is to replace `--release` with a combination of `-source`, `-target` and a system image for the target version via `--system`. This combination, unlike `--release`, can be used with `--add-exports`. If scalac adds full module support at a later time (with access restrictions enabled by default) it will also need to support `--add-exports` and allow its use in combination with `--system`. I am also un-deprecating `-target` (which was deprecated in favor of `-release`) because it now has a legitimate use in combination with an alternative system image (just like in javac where it serves the same purpose and is not deprecated, either).
1 parent bea510c commit 98726c0

File tree

5 files changed

+23
-12
lines changed

5 files changed

+23
-12
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
148148
def optimizerClassPath(base: ClassPath): ClassPath =
149149
base match {
150150
case AggregateClassPath(entries) if entries.head.isInstanceOf[CtSymClassPath] =>
151-
JrtClassPath(release = None, unsafe = None, closeableRegistry) match {
151+
JrtClassPath(release = None, settings.systemPathValue, unsafe = None, closeableRegistry) match {
152152
case jrt :: Nil => AggregateClassPath(entries.drop(1).prepended(jrt))
153153
case _ => base
154154
}

src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package scala.tools.nsc.classpath
1515
import java.io.{Closeable, File}
1616
import java.net.{URI, URL}
1717
import java.nio.file._
18+
import java.util.Collections
1819

1920
import scala.jdk.CollectionConverters._
2021
import scala.reflect.internal.JDK9Reflectors
@@ -137,9 +138,9 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
137138
}
138139

139140
object JrtClassPath {
140-
private val jrtClassPathCache = new FileBasedCache[Unit, JrtClassPath]()
141+
private val jrtClassPathCache = new FileBasedCache[Option[String], JrtClassPath]()
141142
private val ctSymClassPathCache = new FileBasedCache[String, CtSymClassPath]()
142-
def apply(release: Option[String], unsafe: Option[List[String]], closeableRegistry: CloseableRegistry): List[ClassPath] =
143+
def apply(release: Option[String], systemPath: Option[String], unsafe: Option[List[String]], closeableRegistry: CloseableRegistry): List[ClassPath] =
143144
if (!isJavaAtLeast("9")) Nil
144145
else {
145146
// TODO escalate errors once we're sure they are fatal
@@ -155,14 +156,14 @@ object JrtClassPath {
155156
val ct = createCt(version, closeableRegistry)
156157
unsafe match {
157158
case Some(pkgs) if pkgs.nonEmpty =>
158-
createJrt(closeableRegistry) match {
159+
createJrt(systemPath, closeableRegistry) match {
159160
case Nil => ct
160161
case jrts => ct.appended(new FilteringJrtClassPath(jrts.head, pkgs: _*))
161162
}
162163
case _ => ct
163164
}
164165
case _ =>
165-
createJrt(closeableRegistry)
166+
createJrt(systemPath, closeableRegistry)
166167
}
167168
}
168169
private def createCt(v: String, closeableRegistry: CloseableRegistry): List[ClassPath] =
@@ -176,10 +177,15 @@ object JrtClassPath {
176177
} catch {
177178
case NonFatal(_) => Nil
178179
}
179-
private def createJrt(closeableRegistry: CloseableRegistry): List[JrtClassPath] =
180+
private def createJrt(systemPath: Option[String], closeableRegistry: CloseableRegistry): List[JrtClassPath] =
180181
try {
181-
val fs = FileSystems.getFileSystem(URI.create("jrt:/"))
182-
val classPath = jrtClassPathCache.getOrCreate((), Nil, () => new JrtClassPath(fs), closeableRegistry, checkStamps = false)
182+
val classPath = jrtClassPathCache.getOrCreate(systemPath, Nil, () => {
183+
val fs = systemPath match {
184+
case Some(javaHome) => FileSystems.newFileSystem(URI.create("jrt:/"), Collections.singletonMap("java.home", javaHome))
185+
case None => FileSystems.getFileSystem(URI.create("jrt:/"))
186+
}
187+
new JrtClassPath(fs, systemPath.isDefined)
188+
}, closeableRegistry, checkStamps = false)
183189
List(classPath)
184190
} catch {
185191
case _: ProviderNotFoundException | _: FileSystemNotFoundException => Nil
@@ -207,7 +213,7 @@ final class FilteringJrtClassPath(delegate: JrtClassPath, allowed: String*) exte
207213
*
208214
* The implementation assumes that no classes exist in the empty package.
209215
*/
210-
final class JrtClassPath(fs: FileSystem) extends ClassPath with NoSourcePaths {
216+
final class JrtClassPath(fs: FileSystem, closeFS: Boolean) extends ClassPath with NoSourcePaths with Closeable {
211217
type F = Path
212218
private val dir: Path = fs.getPath("/packages")
213219

@@ -253,6 +259,9 @@ final class JrtClassPath(fs: FileSystem) extends ClassPath with NoSourcePaths {
253259
}.take(1).toList.headOption
254260
}
255261
}
262+
263+
def close(): Unit =
264+
if (closeFS) fs.close()
256265
}
257266

258267
/**

src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ trait StandardScalaSettings { _: MutableSettings =>
3232
val javaextdirs = PathSetting ("-javaextdirs", "Override java extdirs classpath.", Defaults.javaExtDirs) withAbbreviation "--java-extension-directories"
3333
val sourcepath = PathSetting ("-sourcepath", "Specify location(s) of source files.", "") withAbbreviation "--source-path" // Defaults.scalaSourcePath
3434
val rootdir = PathSetting ("-rootdir", "The absolute path of the project root directory, usually the git/scm checkout. Used by -Wconf.", "") withAbbreviation "--root-directory"
35+
val systemPath = PathSetting ("-system", "Override location of Java system modules", "") withAbbreviation "--system"
3536

3637
/** Other settings.
3738
*/
@@ -75,11 +76,13 @@ trait StandardScalaSettings { _: MutableSettings =>
7576
val current = setting.value.toInt
7677
if (!isJavaAtLeast("9") && current > 8) errorFn.apply("-release is only supported on JVM 9 and higher")
7778
if (target.valueSetByUser.map(_.toInt > current).getOrElse(false)) errorFn("-release cannot be less than -target")
79+
if (systemPath.isSetByUser) errorFn("-release cannot be used with -system")
7880
//target.value = setting.value // this would trigger deprecation
7981
}
8082
.withAbbreviation("--release")
8183
.withAbbreviation("-java-output-version")
8284
def releaseValue: Option[String] = release.valueSetByUser
85+
def systemPathValue: Option[String] = systemPath.valueSetByUser
8386
val target =
8487
ChoiceSetting("-target", "target", "Target platform for object files.", AllTargetVersions, "8")
8588
.withPreSetHook(normalizeTarget)
@@ -90,7 +93,6 @@ trait StandardScalaSettings { _: MutableSettings =>
9093
// .withAbbreviation("--Xtarget")
9194
// .withAbbreviation("-Xtarget")
9295
.withAbbreviation("-Xunchecked-java-output-version")
93-
.withDeprecationMessage("Use -release instead to compile against the correct platform API.")
9496
def targetValue: String = target.valueSetByUser.orElse(releaseValue).getOrElse(target.value)
9597
val unchecked = BooleanSetting ("-unchecked", "Enable additional warnings where generated code depends on assumptions. See also -Wconf.") withAbbreviation "--unchecked" withPostSetHook { s =>
9698
if (s.value) Wconf.tryToSet(List(s"cat=unchecked:w"))

src/compiler/scala/tools/util/PathResolver.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ final class PathResolver(settings: Settings, closeableRegistry: CloseableRegistr
270270
sourcesInPath(sourcePath) // 7. The Scala source path.
271271
)
272272

273-
private def jrt: List[ClassPath] = JrtClassPath.apply(settings.releaseValue, settings.unsafe.valueSetByUser, closeableRegistry)
273+
private def jrt: List[ClassPath] = JrtClassPath.apply(settings.releaseValue, settings.systemPathValue, settings.unsafe.valueSetByUser, closeableRegistry)
274274

275275
lazy val containers = basis.flatten.distinct
276276

test/junit/scala/tools/nsc/classpath/JrtClassPathTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class JrtClassPathTest {
2727
val elements = new ClassPathFactory(settings, closeableRegistry).classesInPath(resolver.Calculated.javaBootClassPath)
2828
AggregateClassPath(elements)
2929
}
30-
else JrtClassPath(None, None, closeableRegistry).head
30+
else JrtClassPath(None, None, None, closeableRegistry).head
3131

3232
assertEquals(Nil, cp.classes(""))
3333
assertTrue(cp.packages("java").toString, cp.packages("java").exists(_.name == "java.lang"))

0 commit comments

Comments
 (0)