From 5b269e92590bb1e9080dbf96514d41e209f22ed2 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 8 Sep 2016 23:53:34 +1000 Subject: SI-9833 Fix -nobootcp in the Unix scala script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was not having the desired effect of placing the Scala library on the JVM's regular classpath. This commit honours this setting. Note that the Windows scripts have never supported the use of bootclasspath, so no changes are required. The existing bug: ``` (java_use 1.8; ~/scala/2.11.8/bin/scala -nobootcp -debug -e 'print("")') /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/bin/java -Xmx256M -Xms32M -Xbootclasspath/a:/Users/jason/scala/2.11.8/lib/akka-actor_2.11-2.3.10.jar:/Users/jason/scala/2.11.8/lib/config-1.2.1.jar:/Users/jason/scala/2.11.8/lib/jline-2.12.1.jar:/Users/jason/scala/2.11.8/lib/scala-actors-2.11.0.jar:/Users/jason/scala/2.11.8/lib/scala-actors-migration_2.11-1.1.0.jar:/Users/jason/scala/2.11.8/lib/scala-compiler.jar:/Users/jason/scala/2.11.8/lib/scala-continuations-library_2.11-1.0.2.jar:/Users/jason/scala/2.11.8/lib/scala-continuations-plugin_2.11.8-1.0.2.jar:/Users/jason/scala/2.11.8/lib/scala-library.jar:/Users/jason/scala/2.11.8/lib/scala-parser-combinators_2.11-1.0.4.jar:/Users/jason/scala/2.11.8/lib/scala-reflect.jar:/Users/jason/scala/2.11.8/lib/scala-swing_2.11-1.0.2.jar:/Users/jason/scala/2.11.8/lib/scala-xml_2.11-1.0.4.jar:/Users/jason/scala/2.11.8/lib/scalap-2.11.8.jar -classpath "" -Dscala.home=/Users/jason/scala/2.11.8 -Dscala.usejavacp=true -Denv.emacs= scala.tools.nsc.MainGenericRunner print("") ``` Fixed by this patch: ``` ⚡ (java_use 1.8; qscala -nobootcp -debug -e 'print("")') /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/bin/java -Xmx256M -Xms32M -classpath /Users/jason/code/scala/build/quick/classes/repl-jline-embedded:/Users/jason/code/scala/build/quick/classes/repl-jline:/Users/jason/code/scala/build/quick/classes/repl:/Users/jason/code/scala/build/quick/classes/compiler:/Users/jason/code/scala/build/quick/classes/library:/Users/jason/code/scala/build/quick/classes/reflect:/Users/jason/code/scala/build/quick/classes/interactive:/Users/jason/.ivy2/cache/org.apache.ant/ant/jars/ant-1.9.4.jar:/Users/jason/.ivy2/cache/org.apache.ant/ant-launcher/jars/ant-launcher-1.9.4.jar:/Users/jason/.ivy2/cache/org.scala-lang.modules/scala-asm/bundles/scala-asm-5.1.0-scala-1.jar:/Users/jason/.ivy2/cache/org.scala-lang.modules/scala-xml_2.12.0-RC1/bundles/scala-xml_2.12.0-RC1-1.0.5.jar:/Users/jason/.ivy2/cache/jline/jline/jars/jline-2.14.1.jar -Dscala.home=/Users/jason/code/scala/build/quick -Dscala.usejavacp=true -Denv.emacs= scala.tools.nsc.MainGenericRunner -nc print("") ``` --- src/compiler/scala/tools/ant/templates/tool-unix.tmpl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl index 5e6b3c041e..7ab23eb556 100755 --- a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl +++ b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl @@ -160,7 +160,7 @@ while [[ $# -gt 0 ]]; do shift 2 ;; -nobootcp) - unset usebootcp + usebootcp="false" shift ;; -usebootcp) @@ -189,14 +189,13 @@ declare -a classpath_args # default to the boot classpath for speed, except on cygwin/mingw/msys because # JLine on Windows requires a custom DLL to be loaded. -unset usebootcp -if [[ -z "$cygwin$mingw$msys" ]]; then +if [[ "$usebootcp" != "false" && -z "$cygwin$mingw$msys" ]]; then usebootcp="true" fi # If using the boot classpath, also pass an empty classpath # to java to suppress "." from materializing. -if [[ -n $usebootcp ]]; then +if [[ "$usebootcp" == "true" ]]; then classpath_args=("-Xbootclasspath/a:$TOOL_CLASSPATH" -classpath "\"\"") else classpath_args=(-classpath "$TOOL_CLASSPATH") -- cgit v1.2.3 From bfa7ade0db6d36efc721e36dc41627dbd76b0176 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 16 Oct 2016 19:20:13 +1100 Subject: Adapt to the removal of sun.boot.class.path In Java 9. we can no longer introspect the boot classpath with a JVM provided system property. Instead, this commit passes a custom property which will be found by PathResolver when it constructs the compiler classpath. --- src/compiler/scala/tools/ant/templates/tool-unix.tmpl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl index 7ab23eb556..b5a238f7be 100755 --- a/src/compiler/scala/tools/ant/templates/tool-unix.tmpl +++ b/src/compiler/scala/tools/ant/templates/tool-unix.tmpl @@ -197,6 +197,14 @@ fi # to java to suppress "." from materializing. if [[ "$usebootcp" == "true" ]]; then classpath_args=("-Xbootclasspath/a:$TOOL_CLASSPATH" -classpath "\"\"") + # Note that the version numbers go 1.7, 1.8, 9, 10, ... + java_release="$(cat $JAVA_HOME/release | grep JAVA_VERSION)" + if [[ ! "$java_release" =~ JAVA_VERSION=\"1\. ]]; then + # Java 9 removed sun.boot.class.path, and the supposed replacement to at least see + # the appended boot classpath (jdk.boot.class.path.append) is not visible. + # So we have to pass a custom system property that PathResolver will find. + classpath_args+=("-Dscala.boot.class.path=$TOOL_CLASSPATH") + fi else classpath_args=(-classpath "$TOOL_CLASSPATH") fi -- cgit v1.2.3 From 159480f2504cc08f9cc35660cc33090a49e0228e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 24 Apr 2016 17:23:20 +1000 Subject: Support Java 9 modular runtime images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit http://openjdk.java.net/jeps/220 changes the layout of the JDK to encapsulate the provided libraries with the new module system. This commit modifies the compiler's classpath implementation to scan the new location of these, the `jrt://` virtual filesystem. This might need to be adjusted once we provide a means for users to specify the subset of modules that they want to depend on, but for now reclaims the ground we lost. ``` ⚡ (java_use 9-ea; qscala) Welcome to Scala 2.12.0-20160908-223617-7e4ebda (Java HotSpot(TM) 64-Bit Server VM, Java 9-ea). Type in expressions for evaluation. Or try :help. scala> import StackWalker._, java.util.stream._, scala.collection.JavaConverters._ import StackWalker._ import java.util.stream._ import scala.collection.JavaConverters._ scala> (() => StackWalker.getInstance(java.util.EnumSet.of(Option.RETAIN_CLASS_REFERENCE)).walk[Seq[String]]((s: java.util.stream.Stream[StackFrame]) => s.iterator.asScala.take(3).map(_.toString).toList)).apply().mkString("\n") res0: String = .$anonfun$res0$1(:21) .(:21) .() scala> ``` I've marked the new class, `NioFile` as `private[scala]` to justify the forward compatibility whitelist entry. In principle we could use NioFile more widely rather than `PlainFile` I tried this out in https://github.com/retronym/scala/commit/b2d0a17a which passed CI. But to be conservative, I'm not submitting that change at this point. --- bincompat-forward.whitelist.conf | 4 ++ .../tools/nsc/classpath/ClassPathFactory.scala | 6 +- .../tools/nsc/classpath/DirectoryClassPath.scala | 51 +++++++++++++- .../scala/tools/nsc/classpath/FileUtils.scala | 2 +- src/compiler/scala/tools/util/PathResolver.scala | 1 + src/reflect/scala/reflect/io/PlainFile.scala | 80 +++++++++++++++++++++- 6 files changed, 140 insertions(+), 4 deletions(-) diff --git a/bincompat-forward.whitelist.conf b/bincompat-forward.whitelist.conf index 4461974081..7ccd2ea8fa 100644 --- a/bincompat-forward.whitelist.conf +++ b/bincompat-forward.whitelist.conf @@ -25,6 +25,10 @@ filter { matchName="scala.reflect.runtime.Settings.Yvirtpatmat" problemName=DirectMissingMethodProblem }, + { + matchName="scala.reflect.io.PlainNioFile" + problemName=MissingClassProblem + }, # this one can be removed once there is a fix for # https://github.com/typesafehub/migration-manager/issues/147 { diff --git a/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala b/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala index 3a29f1ba11..80c5ec8828 100644 --- a/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala +++ b/src/compiler/scala/tools/nsc/classpath/ClassPathFactory.scala @@ -4,6 +4,7 @@ package scala.tools.nsc.classpath import scala.reflect.io.{AbstractFile, VirtualDirectory} +import scala.reflect.io.Path.string2path import scala.tools.nsc.Settings import FileUtils.AbstractFileOps import scala.tools.nsc.util.ClassPath @@ -52,7 +53,10 @@ class ClassPathFactory(settings: Settings) { protected def classesInPathImpl(path: String, expand: Boolean) = for { file <- expandPath(path, expand) - dir <- Option(AbstractFile.getDirectory(file)) + dir <- { + def asImage = if (file.endsWith(".jimage")) Some(AbstractFile.getFile(file)) else None + Option(AbstractFile.getDirectory(file)).orElse(asImage) + } } yield newClassPath(dir) private def createSourcePath(file: AbstractFile): ClassPath = diff --git a/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala b/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala index c4be59d7eb..133a656206 100644 --- a/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala +++ b/src/compiler/scala/tools/nsc/classpath/DirectoryClassPath.scala @@ -4,7 +4,9 @@ package scala.tools.nsc.classpath import java.io.File -import java.net.URL +import java.net.{URI, URL} +import java.nio.file.{FileSystems, Files, SimpleFileVisitor} +import java.util.function.IntFunction import java.util import java.util.Comparator @@ -119,6 +121,53 @@ trait JFileDirectoryLookup[FileEntryType <: ClassRepresentation] extends Directo def asClassPathStrings: Seq[String] = Seq(dir.getPath) } +object JImageDirectoryLookup { + import java.nio.file._, java.net.URI, scala.collection.JavaConverters._ + def apply(): List[ClassPath] = { + try { + val fs = FileSystems.getFileSystem(URI.create("jrt:/")) + val dir: Path = fs.getPath("/modules") + val modules = Files.list(dir).iterator().asScala.toList + modules.map(m => new JImageDirectoryLookup(fs, m.getFileName.toString)) + } catch { + case _: ProviderNotFoundException | _: FileSystemNotFoundException => + Nil + } + } +} +class JImageDirectoryLookup(fs: java.nio.file.FileSystem, module: String) extends DirectoryLookup[ClassFileEntryImpl] with NoSourcePaths { + import java.nio.file.Path, java.nio.file._ + type F = Path + val dir: Path = fs.getPath("/modules/" + module) + + protected def emptyFiles: Array[Path] = Array.empty + protected def getSubDir(packageDirName: String): Option[Path] = { + val packageDir = dir.resolve(packageDirName) + if (Files.exists(packageDir) && Files.isDirectory(packageDir)) Some(packageDir) + else None + } + protected def listChildren(dir: Path, filter: Option[Path => Boolean]): Array[Path] = { + import scala.collection.JavaConverters._ + val f = filter.getOrElse((p: Path) => true) + Files.list(dir).iterator().asScala.filter(f).toArray[Path] + } + protected def getName(f: Path): String = f.getFileName.toString + protected def toAbstractFile(f: Path): AbstractFile = new scala.reflect.io.PlainNioFile(f) + protected def isPackage(f: Path): Boolean = Files.isDirectory(f) && mayBeValidPackage(f.getFileName.toString) + + def asURLs: Seq[URL] = Seq(dir.toUri.toURL) + def asClassPathStrings: Seq[String] = asURLs.map(_.toString) + + def findClassFile(className: String): Option[AbstractFile] = { + val relativePath = FileUtils.dirPath(className) + ".class" + val classFile = dir.resolve(relativePath) + if (Files.exists(classFile)) Some(new scala.reflect.io.PlainNioFile(classFile)) else None + } + override protected def createFileEntry(file: AbstractFile): ClassFileEntryImpl = ClassFileEntryImpl(file) + override protected def isMatchingFile(f: Path): Boolean = Files.isRegularFile(f) && f.getFileName.toString.endsWith(".class") + override private[nsc] def classes(inPackage: String): Seq[ClassFileEntry] = files(inPackage) +} + case class DirectoryClassPath(dir: File) extends JFileDirectoryLookup[ClassFileEntryImpl] with NoSourcePaths { override def findClass(className: String): Option[ClassRepresentation] = findClassFile(className) map ClassFileEntryImpl diff --git a/src/compiler/scala/tools/nsc/classpath/FileUtils.scala b/src/compiler/scala/tools/nsc/classpath/FileUtils.scala index bbcfcb24ca..2ade83c6f9 100644 --- a/src/compiler/scala/tools/nsc/classpath/FileUtils.scala +++ b/src/compiler/scala/tools/nsc/classpath/FileUtils.scala @@ -63,7 +63,7 @@ object FileUtils { // probably it should match a pattern like [a-z_]{1}[a-z0-9_]* but it cannot be changed // because then some tests in partest don't pass - private def mayBeValidPackage(dirName: String): Boolean = + def mayBeValidPackage(dirName: String): Boolean = (dirName != "META-INF") && (dirName != "") && (dirName.charAt(0) != '.') def mkFileFilter(f: JFile => Boolean) = new FileFilter { diff --git a/src/compiler/scala/tools/util/PathResolver.scala b/src/compiler/scala/tools/util/PathResolver.scala index c351b6ace1..188cabbc8d 100644 --- a/src/compiler/scala/tools/util/PathResolver.scala +++ b/src/compiler/scala/tools/util/PathResolver.scala @@ -234,6 +234,7 @@ final class PathResolver(settings: Settings) { // Assemble the elements! def basis = List[Traversable[ClassPath]]( + JImageDirectoryLookup.apply(), // 0. The Java 9 classpath (backed by the jrt:/ virtual system) classesInPath(javaBootClassPath), // 1. The Java bootstrap class path. contentsOfDirsInPath(javaExtDirs), // 2. The Java extension class path. classesInExpandedPath(javaUserClassPath), // 3. The Java application class path. diff --git a/src/reflect/scala/reflect/io/PlainFile.scala b/src/reflect/scala/reflect/io/PlainFile.scala index eb0940e703..989081ebe0 100644 --- a/src/reflect/scala/reflect/io/PlainFile.scala +++ b/src/reflect/scala/reflect/io/PlainFile.scala @@ -40,7 +40,6 @@ class PlainFile(val givenPath: Path) extends AbstractFile { override def output = givenPath.toFile.outputStream() override def sizeOption = Some(givenPath.length.toInt) - override def toString = path override def hashCode(): Int = fpath.hashCode() override def equals(that: Any): Boolean = that match { case x: PlainFile => fpath == x.fpath @@ -91,3 +90,82 @@ class PlainFile(val givenPath: Path) extends AbstractFile { def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile = new PlainFile(givenPath / name) } + +private[scala] class PlainNioFile(nioPath: java.nio.file.Path) extends AbstractFile { + import java.nio.file._ + + assert(nioPath ne null) + + /** Returns the underlying File if any and null otherwise. */ + override def file: java.io.File = try { + nioPath.toFile + } catch { + case _: UnsupportedOperationException => null + } + + override def underlyingSource = Some(this) + + private val fpath = nioPath.toAbsolutePath.toString + + /** Returns the name of this abstract file. */ + def name = nioPath.getFileName.toString + + /** Returns the path of this abstract file. */ + def path = nioPath.toString + + /** The absolute file. */ + def absolute = new PlainNioFile(nioPath.toAbsolutePath) + + override def container: AbstractFile = new PlainNioFile(nioPath.getParent) + override def input = Files.newInputStream(nioPath) + override def output = Files.newOutputStream(nioPath) + override def sizeOption = Some(Files.size(nioPath).toInt) + override def hashCode(): Int = fpath.hashCode() + override def equals(that: Any): Boolean = that match { + case x: PlainNioFile => fpath == x.fpath + case _ => false + } + + /** Is this abstract file a directory? */ + def isDirectory: Boolean = Files.isDirectory(nioPath) + + /** Returns the time that this abstract file was last modified. */ + def lastModified: Long = Files.getLastModifiedTime(nioPath).toMillis + + /** Returns all abstract subfiles of this abstract directory. */ + def iterator: Iterator[AbstractFile] = { + try { + import scala.collection.JavaConverters._ + val it = Files.newDirectoryStream(nioPath).iterator() + it.asScala.map(new PlainNioFile(_)) + } catch { + case _: NotDirectoryException => Iterator.empty + } + } + + /** + * Returns the abstract file in this abstract directory with the + * specified name. If there is no such file, returns null. The + * argument "directory" tells whether to look for a directory or + * or a regular file. + */ + def lookupName(name: String, directory: Boolean): AbstractFile = { + val child = nioPath.resolve(name) + if ((Files.isDirectory(child) && directory) || (Files.isRegularFile(child) && !directory)) new PlainNioFile(child) + else null + } + + /** Does this abstract file denote an existing file? */ + def create(): Unit = if (!exists) Files.createFile(nioPath) + + /** Delete the underlying file or directory (recursively). */ + def delete(): Unit = + if (Files.isRegularFile(nioPath)) Files.deleteIfExists(nioPath) + else if (Files.isDirectory(nioPath)) new Directory(nioPath.toFile).deleteRecursively() + + /** Returns a plain file with the given name. It does not + * check that it exists. + */ + def lookupNameUnchecked(name: String, directory: Boolean): AbstractFile = + new PlainNioFile(nioPath.resolve(name)) +} -- cgit v1.2.3