summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2011-04-28 01:10:22 +0000
committerPaul Phillips <paulp@improving.org>2011-04-28 01:10:22 +0000
commit2700617052df7c49e6cc7459947e4337d9de987c (patch)
treec0c43ec849f8b034e5a86f584701ab2ad98d04c9
parentff5cd2f6e8aa43a867cd4a395f42744c41b6fdf3 (diff)
downloadscala-2700617052df7c49e6cc7459947e4337d9de987c.tar.gz
scala-2700617052df7c49e6cc7459947e4337d9de987c.tar.bz2
scala-2700617052df7c49e6cc7459947e4337d9de987c.zip
Upgraded -d so you can output classes directly ...
Upgraded -d so you can output classes directly to a jar. Very (very) loosely based on a patch from dmharrah. Like dmharrah before me, I see little if any change in compile times, which I find difficult to explain. Closes #27, review by dmharrah.
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala92
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala103
-rw-r--r--src/compiler/scala/tools/nsc/settings/MutableSettings.scala21
-rw-r--r--src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala4
4 files changed, 152 insertions, 68 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala
new file mode 100644
index 0000000000..2f0d86c993
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/BytecodeWriters.scala
@@ -0,0 +1,92 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2011 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package backend.jvm
+
+import ch.epfl.lamp.fjbg._
+import java.io.{ DataOutputStream, OutputStream }
+import scala.tools.nsc.io.{ AbstractFile, Path }
+import scala.tools.nsc.util.ScalaClassLoader
+import scala.tools.util.Javap
+import java.util.jar.{ JarEntry, JarOutputStream }
+
+/** For the last mile: turning generated bytecode in memory into
+ * something you can use. Has implementations for writing to class
+ * files, jars, and disassembled/javap output.
+ */
+trait BytecodeWriters {
+ val global: Global
+ import global._
+
+ private def outputDirectory(sym: Symbol): AbstractFile = (
+ settings.outputDirs.outputDirFor {
+ atPhase(currentRun.flattenPhase.prev)(sym.sourceFile)
+ }
+ )
+ private def getFile(base: AbstractFile, cls: JClass, suffix: String): AbstractFile = {
+ var dir = base
+ val pathParts = cls.getName().split("[./]").toList
+ for (part <- pathParts.init) {
+ dir = dir.subdirectoryNamed(part)
+ }
+ dir.fileNamed(pathParts.last + suffix)
+ }
+ private def getFile(sym: Symbol, cls: JClass, suffix: String): AbstractFile =
+ getFile(outputDirectory(sym), cls, suffix)
+
+ trait BytecodeWriter {
+ def writeClass(label: String, jclass: JClass, sym: Symbol): Unit
+ def close(): Unit = ()
+ }
+
+ class DirectToJarfileWriter(val jarFile: AbstractFile) extends BytecodeWriter {
+ private val out = new JarOutputStream(jarFile.bufferedOutput)
+ def writeClass(label: String, jclass: JClass, sym: Symbol) {
+ val path = jclass.getName + ".class"
+ out putNextEntry new JarEntry(path)
+ val dataStream = new DataOutputStream(out)
+ try jclass writeTo dataStream
+ finally dataStream.flush()
+ informProgress("added " + label + path + " to jar")
+ }
+ override def close() = out.close()
+ }
+
+ trait JavapBytecodeWriter extends BytecodeWriter {
+ val baseDir = Path(settings.Ygenjavap.value)
+
+ def emitJavap(bytes: Array[Byte], javapFile: io.File) {
+ val pw = javapFile.printWriter()
+ val javap = new Javap(ScalaClassLoader.getSystemLoader(), pw) {
+ override def findBytes(path: String): Array[Byte] = bytes
+ }
+
+ try javap(Seq("-verbose", "dummy")) foreach (_.show())
+ finally pw.close()
+ }
+ abstract override def writeClass(label: String, jclass: JClass, sym: Symbol) {
+ super.writeClass(label, jclass, sym)
+
+ val bytes = getFile(sym, jclass, ".class").toByteArray
+ val segments = jclass.getName().split("[./]")
+ val javapFile = segments.foldLeft(baseDir)(_ / _) changeExtension "javap" toFile
+
+ javapFile.parent.createDirectory()
+ emitJavap(bytes, javapFile)
+ }
+ }
+
+ trait ClassBytecodeWriter extends BytecodeWriter {
+ def writeClass(label: String, jclass: JClass, sym: Symbol) {
+ val outfile = getFile(sym, jclass, ".class")
+ val outstream = new DataOutputStream(outfile.bufferedOutput)
+
+ try jclass writeTo outstream
+ finally outstream.close()
+ informProgress("wrote '" + label + "' to " + outfile)
+ }
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala
index ac0cb97d45..1a485ebb27 100644
--- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala
+++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala
@@ -7,20 +7,19 @@
package scala.tools.nsc
package backend.jvm
-import java.io.DataOutputStream
+import java.io.{ DataOutputStream, OutputStream }
import java.nio.ByteBuffer
import scala.collection.{ mutable, immutable }
-import mutable.{ ListBuffer, LinkedHashSet }
import scala.reflect.generic.{ PickleFormat, PickleBuffer }
import scala.tools.reflect.SigParser
import scala.tools.nsc.io.{ AbstractFile, Path }
import scala.tools.nsc.util.ScalaClassLoader
import scala.tools.nsc.symtab._
import scala.tools.nsc.symtab.classfile.ClassfileConstants._
-
import ch.epfl.lamp.fjbg._
import JAccessFlags._
import JObjectType.{ JAVA_LANG_STRING, JAVA_LANG_OBJECT }
+import java.util.jar.{ JarEntry, JarOutputStream }
/** This class ...
*
@@ -28,7 +27,7 @@ import JObjectType.{ JAVA_LANG_STRING, JAVA_LANG_OBJECT }
* @version 1.0
*
*/
-abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
+abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with BytecodeWriters {
import global._
import icodes._
import icodes.opcodes._
@@ -45,27 +44,48 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
/** Create a new phase */
override def newPhase(p: Phase): Phase = new JvmPhase(p)
+ private def outputDirectory(sym: Symbol): AbstractFile = (
+ settings.outputDirs.outputDirFor {
+ atPhase(currentRun.flattenPhase.prev)(sym.sourceFile)
+ }
+ )
+ private def getFile(base: AbstractFile, cls: JClass, suffix: String): AbstractFile = {
+ var dir = base
+ val pathParts = cls.getName().split("[./]").toList
+ for (part <- pathParts.init) {
+ dir = dir.subdirectoryNamed(part)
+ }
+ dir.fileNamed(pathParts.last + suffix)
+ }
+ private def getFile(sym: Symbol, cls: JClass, suffix: String): AbstractFile =
+ getFile(outputDirectory(sym), cls, suffix)
+
/** JVM code generation phase
*/
class JvmPhase(prev: Phase) extends ICodePhase(prev) {
def name = phaseName
override def erasedTypes = true
+ def apply(cls: IClass) = sys.error("no implementation")
- override def run {
+ override def run() {
// we reinstantiate the bytecode generator at each run, to allow the GC
// to collect everything
- val codeGenerator = new BytecodeGenerator
if (settings.debug.value) inform("[running phase " + name + " on icode]")
if (settings.Xdce.value)
for ((sym, cls) <- icodes.classes if inliner.isClosureClass(sym) && !deadCode.liveClosures(sym))
icodes.classes -= sym
- classes.values foreach codeGenerator.genClass
- classes.clear
- }
-
- def apply(cls: IClass) {
- error("no implementation")
+ val bytecodeWriter = settings.outputDirs.getSingleOutput match {
+ case Some(f) if f hasExtension "jar" =>
+ new DirectToJarfileWriter(f)
+ case _ =>
+ if (settings.Ygenjavap.isDefault) new ClassBytecodeWriter { }
+ else new ClassBytecodeWriter with JavapBytecodeWriter { }
+ }
+ val codeGenerator = new BytecodeGenerator(bytecodeWriter)
+ classes.values foreach (codeGenerator genClass _)
+ bytecodeWriter.close()
+ classes.clear()
}
}
@@ -81,8 +101,10 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
* Java bytecode generator.
*
*/
- class BytecodeGenerator extends BytecodeUtil {
+ class BytecodeGenerator(bytecodeWriter: BytecodeWriter) extends BytecodeUtil {
+ def this() = this(new ClassBytecodeWriter { })
def debugLevel = settings.debuginfo.indexOfChoice
+ import bytecodeWriter.writeClass
val MIN_SWITCH_DENSITY = 0.7
val INNER_CLASSES_FLAGS =
@@ -139,18 +161,6 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
super.javaName(sym)
}
- protected def emitJavap(bytes: Array[Byte], javapFile: io.File) {
- import scala.tools.util.Javap
- val pw = javapFile.printWriter()
- try {
- val javap = new Javap(ScalaClassLoader.getSystemLoader(), pw) {
- override def findBytes(path: String): Array[Byte] = bytes
- }
- javap(Seq("-verbose", "dummy")) foreach (_.show())
- }
- finally pw.close()
- }
-
/** Write a class to disk, adding the Scala signature (pickled type
* information) and inner classes.
*
@@ -159,19 +169,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
*/
def emitClass(jclass: JClass, sym: Symbol) {
addInnerClasses(jclass)
- val outfile = getFile(sym, jclass, ".class")
- val outstream = new DataOutputStream(outfile.bufferedOutput)
- jclass writeTo outstream
- outstream.close()
- informProgress("wrote " + outfile)
-
- if (!settings.Ygenjavap.isDefault) {
- val segments = jclass.getName().split("[./]")
- val javapFile = segments.foldLeft(Path(settings.Ygenjavap.value))(_ / _) changeExtension "javap" toFile
-
- javapFile.parent.createDirectory()
- emitJavap(outfile.toByteArray, javapFile)
- }
+ writeClass("" + sym.name, jclass, sym)
}
/** Returns the ScalaSignature annotation if it must be added to this class,
@@ -219,7 +217,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
var isRemoteClass: Boolean = false
var isParcelableClass = false
- private val innerClassBuffer = new ListBuffer[Symbol]
+ private val innerClassBuffer = new mutable.ListBuffer[Symbol]
def genClass(c: IClass) {
clasz = c
@@ -301,6 +299,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
val ssa = scalaSignatureAddingMarker(jclass, c.symbol)
addGenericSignature(jclass, c.symbol, c.symbol.owner)
addAnnotations(jclass, c.symbol.annotations ++ ssa)
+
addEnclosingMethodAttribute(jclass, c.symbol)
emitClass(jclass, c.symbol)
@@ -407,11 +406,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
jcode.emitRETURN()
// write the bean information class file.
- val outfile = getFile(c.symbol, beanInfoClass, ".class")
- val outstream = new DataOutputStream(outfile.bufferedOutput)
- beanInfoClass writeTo outstream
- outstream.close()
- informProgress("wrote BeanInfo " + outfile)
+ writeClass("BeanInfo ", beanInfoClass, c.symbol)
}
/** Add the given 'throws' attributes to jmethod */
@@ -981,6 +976,9 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
*/
def addForwarders(jclass: JClass, moduleClass: Symbol) {
assert(moduleClass.isModuleClass)
+ if (settings.debug.value)
+ log("Dumping mirror class for object: " + moduleClass)
+
val className = jclass.getName
val linkedClass = moduleClass.companionClass
val linkedModule = linkedClass.companionSymbol
@@ -1031,7 +1029,9 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
* instead be made to add the forwarder methods to the companion class.
*/
def dumpMirrorClass(clasz: Symbol, sourceFile: String) {
- val mirrorName = javaName(clasz).init // drops "$"
+ import JAccessFlags._
+ val moduleName = javaName(clasz) // + "$"
+ val mirrorName = moduleName.substring(0, moduleName.length() - 1)
val mirrorClass = fjbgContext.JClass(ACC_SUPER | ACC_PUBLIC | ACC_FINAL,
mirrorName,
JAVA_LANG_OBJECT.getName,
@@ -1803,19 +1803,6 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid {
(sym.isJavaDefined && sym.isNonBottomSubClass(ClassfileAnnotationClass))
}
- /** Return an abstract file for the given class symbol, with the desired suffix.
- * Create all necessary subdirectories on the way.
- */
- def getFile(sym: Symbol, cls: JClass, suffix: String): AbstractFile = {
- val sourceFile = atPhase(currentRun.flattenPhase.prev)(sym.sourceFile)
- var dir: AbstractFile = settings.outputDirs.outputDirFor(sourceFile)
- val pathParts = cls.getName().split("[./]").toList
- for (part <- pathParts.init) {
- dir = dir.subdirectoryNamed(part)
- }
- dir.fileNamed(pathParts.last + suffix)
- }
-
/** Merge adjacent ranges. */
private def mergeEntries(ranges: List[(Int, Int)]): List[(Int, Int)] =
(ranges.foldLeft(Nil: List[(Int, Int)]) { (collapsed: List[(Int, Int)], p: (Int, Int)) => (collapsed, p) match {
diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
index 477c2a528e..adb580af7f 100644
--- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
@@ -8,7 +8,7 @@ package scala.tools
package nsc
package settings
-import io.{ AbstractFile, VirtualDirectory }
+import io.{ AbstractFile, Path, PlainFile, VirtualDirectory }
import scala.tools.util.StringOps
import scala.collection.mutable.ListBuffer
import scala.io.Source
@@ -239,20 +239,25 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal
checkDir(AbstractFile.getDirectory(outDir), outDir))
/** Check that dir is exists and is a directory. */
- private def checkDir(dir: AbstractFile, name: String): AbstractFile = {
- if ((dir eq null) || !dir.isDirectory)
+ private def checkDir(dir: AbstractFile, name: String, allowJar: Boolean = false): AbstractFile = (
+ if (dir != null && dir.isDirectory)
+ dir
+ else if (allowJar && dir == null && Path.isJarOrZip(name, false))
+ new PlainFile(Path(name))
+ else
throw new FatalError(name + " does not exist or is not a directory")
- dir
- }
+ )
/** Set the single output directory. From now on, all files will
* be dumped in there, regardless of previous calls to 'add'.
*/
def setSingleOutput(outDir: String) {
val dst = AbstractFile.getDirectory(outDir)
- setSingleOutput(checkDir(dst, outDir))
+ setSingleOutput(checkDir(dst, outDir, true))
}
+ def getSingleOutput: Option[AbstractFile] = singleOutDir
+
/** Set the single output directory. From now on, all files will
* be dumped in there, regardless of previous calls to 'add'.
*/
@@ -310,7 +315,7 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal
singleOutDir match {
case Some(d) =>
d match {
- case _: VirtualDirectory => Nil
+ case _: VirtualDirectory | _: io.ZipArchive => Nil
case _ => List(d.lookupPathUnchecked(srcPath, false))
}
case None =>
@@ -485,7 +490,7 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal
class OutputSetting private[nsc](
private[nsc] val outputDirs: OutputDirs,
default: String)
- extends StringSetting("-d", "directory", "Specify where to place generated class files", default) {
+ extends StringSetting("-d", "directory|jar", "destination for generated classfiles.", default) {
value = default
override def value_=(str: String) {
super.value_=(str)
diff --git a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala
index cc960e55dd..c5b477c7bd 100644
--- a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala
@@ -36,10 +36,10 @@ trait StandardScalaSettings {
val help = BooleanSetting ("-help", "Print a synopsis of standard options")
val make = ChoiceSetting ("-make", "policy", "Recompilation detection policy", List("all", "changed", "immediate", "transitive", "transitivenocp"), "all")
. withDeprecationMessage ("this option is unmaintained. Use sbt or an IDE for selective recompilation.")
- val nowarn = BooleanSetting ("-nowarn", "Generate no warnings")
+ val nowarn = BooleanSetting ("-nowarn", "Generate no warnings.")
val optimise: BooleanSetting // depends on post hook which mutates other settings
val print = BooleanSetting ("-print", "Print program with Scala-specific features removed.")
- val target = ChoiceSetting ("-target", "target", "Specify for which target object files should be built", List("jvm-1.5", "msil"), "jvm-1.5")
+ val target = ChoiceSetting ("-target", "target", "Target platform for object files.", List("jvm-1.5", "msil"), "jvm-1.5")
val unchecked = BooleanSetting ("-unchecked", "Enable detailed unchecked (erasure) warnings.")
val uniqid = BooleanSetting ("-uniqid", "Uniquely tag all identifiers in debugging output.")
val usejavacp = BooleanSetting ("-usejavacp", "Utilize the java.class.path in classpath resolution.")