Skip to content

Commit b3486ac

Browse files
authored
Merge pull request #10746 from prolativ/add-release-flag
Fix #8634: Support -release option
2 parents 5539b2c + 54125ce commit b3486ac

18 files changed

+567
-111
lines changed

compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,30 @@ trait BCodeIdiomatic {
2424
import bTypes._
2525
import coreBTypes._
2626

27-
lazy val classfileVersion: Int = ctx.settings.target.value match {
28-
case "jvm-1.5" => asm.Opcodes.V1_5
29-
case "jvm-1.6" => asm.Opcodes.V1_6
30-
case "jvm-1.7" => asm.Opcodes.V1_7
31-
case "jvm-1.8" => asm.Opcodes.V1_8
32-
case "jvm-9" => asm.Opcodes.V9
27+
28+
29+
lazy val target =
30+
val releaseValue = Option(ctx.settings.release.value).filter(_.nonEmpty)
31+
val targetValue = Option(ctx.settings.Xtarget.value).filter(_.nonEmpty)
32+
val defaultTarget = "8"
33+
(releaseValue, targetValue) match
34+
case (Some(release), None) => release
35+
case (None, Some(target)) => target
36+
case (Some(release), Some(_)) =>
37+
report.warning(s"The value of ${ctx.settings.Xtarget.name} was overriden by ${ctx.settings.release.name}")
38+
release
39+
case (None, None) => "8" // least supported version by default
40+
41+
42+
lazy val classfileVersion: Int = target match {
43+
case "8" => asm.Opcodes.V1_8
44+
case "9" => asm.Opcodes.V9
45+
case "10" => asm.Opcodes.V10
46+
case "11" => asm.Opcodes.V11
47+
case "12" => asm.Opcodes.V12
48+
case "13" => asm.Opcodes.V13
49+
case "14" => asm.Opcodes.V14
50+
case "15" => asm.Opcodes.V15
3351
}
3452

3553
lazy val majorVersion: Int = (classfileVersion & 0xFF)

compiler/src/dotty/tools/dotc/classpath/DirectoryClassPath.scala

Lines changed: 102 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ import java.io.{File => JFile}
77
import java.net.URL
88
import java.nio.file.{FileSystems, Files}
99

10-
import dotty.tools.io.{AbstractFile, PlainFile, ClassPath, ClassRepresentation, EfficientClassPath}
10+
import dotty.tools.dotc.classpath.PackageNameUtils.{packageContains, separatePkgAndClassNames}
11+
import dotty.tools.io.{AbstractFile, PlainFile, ClassPath, ClassRepresentation, EfficientClassPath, JDK9Reflectors}
1112
import FileUtils._
13+
import PlainFile.toPlainFile
1214

1315
import scala.collection.JavaConverters._
1416
import scala.collection.immutable.ArraySeq
17+
import scala.util.control.NonFatal
1518

1619
/**
1720
* A trait allowing to look for classpath entries in directories. It provides common logic for
@@ -111,7 +114,7 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
111114
else Array()
112115
}
113116
protected def getName(f: JFile): String = f.getName
114-
protected def toAbstractFile(f: JFile): AbstractFile = new PlainFile(new dotty.tools.io.File(f.toPath))
117+
protected def toAbstractFile(f: JFile): AbstractFile = f.toPath.toPlainFile
115118
protected def isPackage(f: JFile): Boolean = f.isPackage
116119

117120
assert(dir != null, "Directory file in DirectoryFileLookup cannot be null")
@@ -122,15 +125,33 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo
122125

123126
object JrtClassPath {
124127
import java.nio.file._, java.net.URI
125-
def apply(): Option[ClassPath] =
126-
try {
127-
val fs = FileSystems.getFileSystem(URI.create("jrt:/"))
128-
Some(new JrtClassPath(fs))
129-
}
130-
catch {
131-
case _: ProviderNotFoundException | _: FileSystemNotFoundException =>
132-
None
128+
def apply(release: Option[String]): Option[ClassPath] = {
129+
import scala.util.Properties._
130+
if (!isJavaAtLeast("9")) None
131+
else {
132+
// Longer term we'd like an official API for this in the JDK
133+
// Discussion: http://mail.openjdk.java.net/pipermail/compiler-dev/2018-March/thread.html#11738
134+
135+
val currentMajorVersion: Int = JDK9Reflectors.runtimeVersionMajor(JDK9Reflectors.runtimeVersion()).intValue()
136+
release match {
137+
case Some(v) if v.toInt < currentMajorVersion =>
138+
try {
139+
val ctSym = Paths.get(javaHome).resolve("lib").resolve("ct.sym")
140+
if (Files.notExists(ctSym)) None
141+
else Some(new CtSymClassPath(ctSym, v.toInt))
142+
} catch {
143+
case NonFatal(_) => None
144+
}
145+
case _ =>
146+
try {
147+
val fs = FileSystems.getFileSystem(URI.create("jrt:/"))
148+
Some(new JrtClassPath(fs))
149+
} catch {
150+
case _: ProviderNotFoundException | _: FileSystemNotFoundException => None
151+
}
152+
}
133153
}
154+
}
134155
}
135156

136157
/**
@@ -157,20 +178,15 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
157178
/** Empty string represents root package */
158179
override private[dotty] def hasPackage(pkg: PackageName): Boolean = packageToModuleBases.contains(pkg.dottedString)
159180

160-
override private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = {
161-
def matches(packageDottedName: String) =
162-
if (packageDottedName.contains("."))
163-
packageOf(packageDottedName) == inPackage.dottedString
164-
else inPackage.isRoot
165-
packageToModuleBases.keysIterator.filter(matches).map(PackageEntryImpl(_)).toVector
166-
}
181+
override private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] =
182+
packageToModuleBases.keysIterator.filter(pack => packageContains(inPackage.dottedString, pack)).map(PackageEntryImpl(_)).toVector
167183

168184
private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] =
169185
if (inPackage.isRoot) Nil
170186
else
171187
packageToModuleBases.getOrElse(inPackage.dottedString, Nil).flatMap(x =>
172188
Files.list(x.resolve(inPackage.dirPathTrailingSlash)).iterator().asScala.filter(_.getFileName.toString.endsWith(".class"))).map(x =>
173-
ClassFileEntryImpl(new PlainFile(new dotty.tools.io.File(x)))).toVector
189+
ClassFileEntryImpl(x.toPlainFile)).toVector
174190

175191
override private[dotty] def list(inPackage: PackageName): ClassPathEntries =
176192
if (inPackage.isRoot) ClassPathEntries(packages(inPackage), Nil)
@@ -184,14 +200,75 @@ final class JrtClassPath(fs: java.nio.file.FileSystem) extends ClassPath with No
184200
def findClassFile(className: String): Option[AbstractFile] =
185201
if (!className.contains(".")) None
186202
else {
187-
val inPackage = packageOf(className)
188-
packageToModuleBases.getOrElse(inPackage, Nil).iterator.flatMap{x =>
203+
val (inPackage, _) = separatePkgAndClassNames(className)
204+
packageToModuleBases.getOrElse(inPackage, Nil).iterator.flatMap{ x =>
189205
val file = x.resolve(FileUtils.dirPath(className) + ".class")
190-
if (Files.exists(file)) new PlainFile(new dotty.tools.io.File(file)) :: Nil else Nil
206+
if (Files.exists(file)) file.toPlainFile :: Nil else Nil
191207
}.take(1).toList.headOption
192208
}
193-
private def packageOf(dottedClassName: String): String =
194-
dottedClassName.substring(0, dottedClassName.lastIndexOf("."))
209+
}
210+
211+
/**
212+
* Implementation `ClassPath` based on the \$JAVA_HOME/lib/ct.sym backing http://openjdk.java.net/jeps/247
213+
*/
214+
final class CtSymClassPath(ctSym: java.nio.file.Path, release: Int) extends ClassPath with NoSourcePaths {
215+
import java.nio.file.Path, java.nio.file._
216+
217+
private val fileSystem: FileSystem = FileSystems.newFileSystem(ctSym, null: ClassLoader)
218+
private val root: Path = fileSystem.getRootDirectories.iterator.next
219+
private val roots = Files.newDirectoryStream(root).iterator.asScala.toList
220+
221+
// http://mail.openjdk.java.net/pipermail/compiler-dev/2018-March/011737.html
222+
private def codeFor(major: Int): String = if (major < 10) major.toString else ('A' + (major - 10)).toChar.toString
223+
224+
private val releaseCode: String = codeFor(release)
225+
private def fileNameMatchesRelease(fileName: String) = !fileName.contains("-") && fileName.contains(releaseCode) // exclude `9-modules`
226+
private val rootsForRelease: List[Path] = roots.filter(root => fileNameMatchesRelease(root.getFileName.toString))
227+
228+
// e.g. "java.lang" -> Seq(/876/java/lang, /87/java/lang, /8/java/lang))
229+
private val packageIndex: scala.collection.Map[String, scala.collection.Seq[Path]] = {
230+
val index = collection.mutable.AnyRefMap[String, collection.mutable.ListBuffer[Path]]()
231+
val isJava12OrHigher = scala.util.Properties.isJavaAtLeast("12")
232+
rootsForRelease.foreach(root => Files.walk(root).iterator().asScala.filter(Files.isDirectory(_)).foreach { p =>
233+
val moduleNamePathElementCount = if (isJava12OrHigher) 1 else 0
234+
if (p.getNameCount > root.getNameCount + moduleNamePathElementCount) {
235+
val packageDotted = p.subpath(moduleNamePathElementCount + root.getNameCount, p.getNameCount).toString.replace('/', '.')
236+
index.getOrElseUpdate(packageDotted, new collection.mutable.ListBuffer) += p
237+
}
238+
})
239+
index
240+
}
241+
242+
/** Empty string represents root package */
243+
override private[dotty] def hasPackage(pkg: PackageName) = packageIndex.contains(pkg.dottedString)
244+
override private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = {
245+
packageIndex.keysIterator.filter(pack => packageContains(inPackage.dottedString, pack)).map(PackageEntryImpl(_)).toVector
246+
}
247+
private[dotty] def classes(inPackage: PackageName): Seq[ClassFileEntry] = {
248+
if (inPackage.isRoot) Nil
249+
else {
250+
val sigFiles = packageIndex.getOrElse(inPackage.dottedString, Nil).iterator.flatMap(p =>
251+
Files.list(p).iterator.asScala.filter(_.getFileName.toString.endsWith(".sig")))
252+
sigFiles.map(f => ClassFileEntryImpl(f.toPlainFile)).toVector
253+
}
254+
}
255+
256+
override private[dotty] def list(inPackage: PackageName): ClassPathEntries =
257+
if (inPackage.isRoot) ClassPathEntries(packages(inPackage), Nil)
258+
else ClassPathEntries(packages(inPackage), classes(inPackage))
259+
260+
def asURLs: Seq[URL] = Nil
261+
def asClassPathStrings: Seq[String] = Nil
262+
def findClassFile(className: String): Option[AbstractFile] = {
263+
if (!className.contains(".")) None
264+
else {
265+
val (inPackage, classSimpleName) = separatePkgAndClassNames(className)
266+
packageIndex.getOrElse(inPackage, Nil).iterator.flatMap { p =>
267+
val path = p.resolve(classSimpleName + ".sig")
268+
if (Files.exists(path)) path.toPlainFile :: Nil else Nil
269+
}.take(1).toList.headOption
270+
}
271+
}
195272
}
196273

197274
case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[ClassFileEntryImpl] with NoSourcePaths {
@@ -201,9 +278,7 @@ case class DirectoryClassPath(dir: JFile) extends JFileDirectoryLookup[ClassFile
201278
val relativePath = FileUtils.dirPath(className)
202279
val classFile = new JFile(dir, relativePath + ".class")
203280
if (classFile.exists) {
204-
val wrappedClassFile = new dotty.tools.io.File(classFile.toPath)
205-
val abstractClassFile = new PlainFile(wrappedClassFile)
206-
Some(abstractClassFile)
281+
Some(classFile.toPath.toPlainFile)
207282
}
208283
else None
209284
}
@@ -228,11 +303,7 @@ case class DirectorySourcePath(dir: JFile) extends JFileDirectoryLookup[SourceFi
228303
.map(ext => new JFile(dir, relativePath + "." + ext))
229304
.collectFirst { case file if file.exists() => file }
230305

231-
sourceFile.map { file =>
232-
val wrappedSourceFile = new dotty.tools.io.File(file.toPath)
233-
val abstractSourceFile = new PlainFile(wrappedSourceFile)
234-
abstractSourceFile
235-
}
306+
sourceFile.map(_.toPath.toPlainFile)
236307
}
237308

238309
private[dotty] def sources(inPackage: PackageName): Seq[SourceFileEntry] = files(inPackage)

compiler/src/dotty/tools/dotc/classpath/FileUtils.scala

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ object FileUtils {
3737
// FIXME: drop last condition when we stop being compatible with Scala 2.11
3838
}
3939

40+
private val SUFFIX_CLASS = ".class"
41+
private val SUFFIX_SCALA = ".scala"
42+
private val SUFFIX_JAVA = ".java"
43+
private val SUFFIX_SIG = ".sig"
44+
4045
def stripSourceExtension(fileName: String): String =
4146
if (endsScala(fileName)) stripClassExtension(fileName)
4247
else if (endsJava(fileName)) stripJavaExtension(fileName)
@@ -46,23 +51,25 @@ object FileUtils {
4651

4752
def dirPathInJar(forPackage: String): String = forPackage.replace('.', '/')
4853

54+
inline private def ends (filename:String, suffix:String) = filename.endsWith(suffix) && filename.length > suffix.length
55+
4956
def endsClass(fileName: String): Boolean =
50-
fileName.length > 6 && fileName.substring(fileName.length - 6) == ".class"
57+
ends (fileName, SUFFIX_CLASS) || fileName.endsWith(SUFFIX_SIG)
5158

5259
def endsScalaOrJava(fileName: String): Boolean =
5360
endsScala(fileName) || endsJava(fileName)
5461

5562
def endsJava(fileName: String): Boolean =
56-
fileName.length > 5 && fileName.substring(fileName.length - 5) == ".java"
63+
ends (fileName, SUFFIX_JAVA)
5764

5865
def endsScala(fileName: String): Boolean =
59-
fileName.length > 6 && fileName.substring(fileName.length - 6) == ".scala"
66+
ends (fileName, SUFFIX_SCALA)
6067

6168
def stripClassExtension(fileName: String): String =
62-
fileName.substring(0, fileName.length - 6) // equivalent of fileName.length - ".class".length
69+
fileName.substring(0, fileName.lastIndexOf('.'))
6370

6471
def stripJavaExtension(fileName: String): String =
65-
fileName.substring(0, fileName.length - 5)
72+
fileName.substring(0, fileName.length - 5) // equivalent of fileName.length - SUFFIX_JAVA.length
6673

6774
// probably it should match a pattern like [a-z_]{1}[a-z0-9_]* but it cannot be changed
6875
// because then some tests in partest don't pass

compiler/src/dotty/tools/dotc/classpath/PackageNameUtils.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ object PackageNameUtils {
1414
* @param fullClassName full class name with package
1515
* @return (package, simple class name)
1616
*/
17-
def separatePkgAndClassNames(fullClassName: String): (String, String) = {
17+
inline def separatePkgAndClassNames(fullClassName: String): (String, String) = {
1818
val lastDotIndex = fullClassName.lastIndexOf('.')
1919
if (lastDotIndex == -1)
2020
(RootPackage, fullClassName)
@@ -23,4 +23,15 @@ object PackageNameUtils {
2323
}
2424

2525
def packagePrefix(inPackage: String): String = if (inPackage == RootPackage) "" else inPackage + "."
26+
27+
/**
28+
* `true` if `packageDottedName` is a package directly nested in `inPackage`, for example:
29+
* - `packageContains("scala", "scala.collection")`
30+
* - `packageContains("", "scala")`
31+
*/
32+
def packageContains(inPackage: String, packageDottedName: String) = {
33+
if (packageDottedName.contains("."))
34+
packageDottedName.startsWith(inPackage) && packageDottedName.lastIndexOf('.') == inPackage.length
35+
else inPackage == ""
36+
}
2637
}

compiler/src/dotty/tools/dotc/classpath/ZipAndJarFileLookupFactory.scala

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,22 @@ sealed trait ZipAndJarFileLookupFactory {
2424
private val cache = new FileBasedCache[ClassPath]
2525

2626
def create(zipFile: AbstractFile)(using Context): ClassPath =
27-
if (ctx.settings.YdisableFlatCpCaching.value || zipFile.file == null) createForZipFile(zipFile)
28-
else createUsingCache(zipFile)
27+
val release = Option(ctx.settings.release.value).filter(_.nonEmpty)
28+
if (ctx.settings.YdisableFlatCpCaching.value || zipFile.file == null) createForZipFile(zipFile, release)
29+
else createUsingCache(zipFile, release)
2930

30-
protected def createForZipFile(zipFile: AbstractFile): ClassPath
31+
protected def createForZipFile(zipFile: AbstractFile, release: Option[String]): ClassPath
3132

32-
private def createUsingCache(zipFile: AbstractFile): ClassPath =
33-
cache.getOrCreate(zipFile.file.toPath, () => createForZipFile(zipFile))
33+
private def createUsingCache(zipFile: AbstractFile, release: Option[String]): ClassPath =
34+
cache.getOrCreate(zipFile.file.toPath, () => createForZipFile(zipFile, release))
3435
}
3536

3637
/**
3738
* Manages creation of classpath for class files placed in zip and jar files.
3839
* It should be the only way of creating them as it provides caching.
3940
*/
4041
object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory {
41-
private case class ZipArchiveClassPath(zipFile: File)
42+
private case class ZipArchiveClassPath(zipFile: File, override val release: Option[String])
4243
extends ZipArchiveFileLookup[ClassFileEntryImpl]
4344
with NoSourcePaths {
4445

@@ -141,9 +142,9 @@ object ZipAndJarClassPathFactory extends ZipAndJarFileLookupFactory {
141142
case class PackageInfo(packageName: String, subpackages: List[AbstractFile])
142143
}
143144

144-
override protected def createForZipFile(zipFile: AbstractFile): ClassPath =
145+
override protected def createForZipFile(zipFile: AbstractFile, release: Option[String]): ClassPath =
145146
if (zipFile.file == null) createWithoutUnderlyingFile(zipFile)
146-
else ZipArchiveClassPath(zipFile.file)
147+
else ZipArchiveClassPath(zipFile.file, release)
147148

148149
private def createWithoutUnderlyingFile(zipFile: AbstractFile) = zipFile match {
149150
case manifestRes: ManifestResources =>
@@ -162,6 +163,8 @@ object ZipAndJarSourcePathFactory extends ZipAndJarFileLookupFactory {
162163
private case class ZipArchiveSourcePath(zipFile: File)
163164
extends ZipArchiveFileLookup[SourceFileEntryImpl]
164165
with NoClassPaths {
166+
167+
def release: Option[String] = None
165168

166169
override def asSourcePathString: String = asClassPathString
167170

@@ -171,7 +174,7 @@ object ZipAndJarSourcePathFactory extends ZipAndJarFileLookupFactory {
171174
override protected def isRequiredFileType(file: AbstractFile): Boolean = file.isScalaOrJavaSource
172175
}
173176

174-
override protected def createForZipFile(zipFile: AbstractFile): ClassPath = ZipArchiveSourcePath(zipFile.file)
177+
override protected def createForZipFile(zipFile: AbstractFile, release: Option[String]): ClassPath = ZipArchiveSourcePath(zipFile.file)
175178
}
176179

177180
final class FileBasedCache[T] {

compiler/src/dotty/tools/dotc/classpath/ZipArchiveFileLookup.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ import dotty.tools.io.{EfficientClassPath, ClassRepresentation}
1717
*/
1818
trait ZipArchiveFileLookup[FileEntryType <: ClassRepresentation] extends EfficientClassPath {
1919
val zipFile: File
20+
def release: Option[String]
2021

2122
assert(zipFile != null, "Zip file in ZipArchiveFileLookup cannot be null")
2223

2324
override def asURLs: Seq[URL] = Seq(zipFile.toURI.toURL)
2425
override def asClassPathStrings: Seq[String] = Seq(zipFile.getPath)
2526

26-
private val archive = new FileZipArchive(zipFile.toPath)
27+
private val archive = new FileZipArchive(zipFile.toPath, release)
2728

2829
override private[dotty] def packages(inPackage: PackageName): Seq[PackageEntry] = {
2930
for {

0 commit comments

Comments
 (0)