diff options
author | Paul Phillips <paulp@improving.org> | 2011-04-28 01:10:22 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-04-28 01:10:22 +0000 |
commit | 2700617052df7c49e6cc7459947e4337d9de987c (patch) | |
tree | c0c43ec849f8b034e5a86f584701ab2ad98d04c9 /src | |
parent | ff5cd2f6e8aa43a867cd4a395f42744c41b6fdf3 (diff) | |
download | scala-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.
Diffstat (limited to 'src')
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.") |