Skip to content

Support -d with .jar paths #3382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions compiler/src/dotty/tools/backend/jvm/GenBCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dotty.tools.dotc.core.Phases.Phase
import dotty.tools.dotc.core.Names.TypeName

import scala.collection.mutable
import scala.collection.JavaConverters._
import scala.tools.asm.{ClassVisitor, CustomAttr, FieldVisitor, MethodVisitor}
import scala.tools.nsc.backend.jvm._
import dotty.tools.dotc
Expand All @@ -26,14 +27,16 @@ import Denotations._
import Phases._
import java.lang.AssertionError
import java.io.{DataOutputStream, File => JFile}
import java.nio.file.{Files, FileSystem, FileSystems, Path => JPath}

import dotty.tools.io.{Directory, File, Jar}

import scala.tools.asm
import scala.tools.asm.tree._
import dotty.tools.dotc.util.{DotClass, Positions}
import tpd._
import StdNames._

import dotty.tools.io.{AbstractFile, Directory, PlainDirectory}
import dotty.tools.io._

class GenBCode extends Phase {
def phaseName: String = "genBCode"
Expand All @@ -46,17 +49,35 @@ class GenBCode extends Phase {
superCallsMap.put(sym, old + calls)
}

def outputDir(implicit ctx: Context): AbstractFile =
new PlainDirectory(ctx.settings.outputDir.value)
private[this] var myOutput: AbstractFile = _

protected def outputDir(implicit ctx: Context): AbstractFile = {
if (myOutput eq null) {
val path = Directory(ctx.settings.outputDir.value)
myOutput =
if (path.extension == "jar") JarArchive.create(path)
else new PlainDirectory(path)
}
myOutput
}

def run(implicit ctx: Context): Unit = {
new GenBCodePipeline(entryPoints.toList,
new DottyBackendInterface(outputDir, superCallsMap.toMap)(ctx))(ctx).run(ctx.compilationUnit.tpdTree)
entryPoints.clear()
}

override def runOn(units: List[CompilationUnit])(implicit ctx: Context) = {
try super.runOn(units)
finally myOutput match {
case jar: JarArchive =>
jar.close()
case _ =>
}
}
}

class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInterface)(implicit val ctx: Context) extends BCodeSyncAndTry{
class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInterface)(implicit val ctx: Context) extends BCodeSyncAndTry {

var tree: Tree = _

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Run(comp: Compiler, ictx: Context) {
assert(ctx.runId <= Periods.MaxPossibleRunId)

def getSource(fileName: String): SourceFile = {
val f = new PlainFile(fileName)
val f = new PlainFile(io.Path(fileName))
if (f.isDirectory) {
ctx.error(s"expected file, received directory '$fileName'")
NoSource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package dotty.tools.dotc.classpath

import dotty.tools.io.{AbstractFile, VirtualDirectory}
import dotty.tools.io.Path.string2path
import dotty.tools.dotc.config.Settings
import FileUtils.AbstractFileOps
import dotty.tools.io.ClassPath
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/OutputDirs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class OutputDirs {
if (dir != null && dir.isDirectory)
dir
// was: else if (allowJar && dir == null && Path.isJarOrZip(name, false))
else if (allowJar && dir == null && Jar.isJarOrZip(name, false))
else if (allowJar && dir == null && Jar.isJarOrZip(File(name), false))
new PlainFile(Path(name))
else
throw new FatalError(name + " does not exist or is not a directory"))
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/config/PathResolver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ object PathResolver {
def scalaHome = Environment.scalaHome
def scalaHomeDir = Directory(scalaHome)
def scalaHomeExists = scalaHomeDir.isDirectory
def scalaLibDir = Directory(scalaHomeDir / "lib")
def scalaClassesDir = Directory(scalaHomeDir / "classes")
def scalaLibDir = (scalaHomeDir / "lib").toDirectory
def scalaClassesDir = (scalaHomeDir / "classes").toDirectory

def scalaLibAsJar = File(scalaLibDir / "scala-library.jar")
def scalaLibAsDir = Directory(scalaClassesDir / "library")
def scalaLibAsJar = (scalaLibDir / "scala-library.jar").toFile
def scalaLibAsDir = (scalaClassesDir / "library").toDirectory

def scalaLibDirFound: Option[Directory] =
if (scalaLibAsJar.isFile) Some(scalaLibDir)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ScalaSettings extends Settings.SettingGroup {
val javaextdirs = PathSetting("-javaextdirs", "Override java extdirs classpath.", Defaults.javaExtDirs)
val sourcepath = PathSetting("-sourcepath", "Specify location(s) of source files.", "") // Defaults.scalaSourcePath
val classpath = PathSetting("-classpath", "Specify where to find user class files.", defaultClasspath) withAbbreviation "-cp"
val outputDir = DirectorySetting("-d", "directory|jar", "destination for generated classfiles.", Directory(Path(".")))
val outputDir = PathSetting("-d", "directory|jar", "destination for generated classfiles.", ".")
val priorityclasspath = PathSetting("-priorityclasspath", "class path that takes precedence over all other paths (or testing only)", "")

/** Other settings */
Expand Down
22 changes: 13 additions & 9 deletions compiler/src/dotty/tools/dotc/config/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import scala.util.{ Try, Success, Failure }
import reflect.ClassTag
import core.Contexts._
import scala.annotation.tailrec
import dotty.tools.io.{ Directory, Path }
import dotty.tools.io.{ Directory, File, Path }

// import annotation.unchecked
// Dotty deviation: Imports take precedence over definitions in enclosing package
Expand All @@ -22,7 +22,6 @@ object Settings {
val ListTag = ClassTag(classOf[List[_]])
val VersionTag = ClassTag(classOf[ScalaVersion])
val OptionTag = ClassTag(classOf[Option[_]])
val DirectoryTag = ClassTag(classOf[Directory])

class SettingsState(initialValues: Seq[Any]) {
private[this] var values = ArrayBuffer(initialValues: _*)
Expand Down Expand Up @@ -141,6 +140,15 @@ object Settings {
else if (!choices.contains(argRest))
fail(s"$arg is not a valid choice for $name", args)
else update(argRest, args)
case (StringTag, arg :: args) if name == "-d" =>
Path(arg) match {
case _: Directory =>
update(arg, args)
case p if p.extension == "jar" =>
update(arg, args)
case _ =>
fail(s"'$arg' does not exist or is not a directory", args)
}
case (StringTag, arg2 :: args2) =>
update(arg2, args2)
case (IntTag, arg2 :: args2) =>
Expand All @@ -161,10 +169,6 @@ object Settings {
case Success(v) => update(v, args)
case Failure(ex) => fail(ex.getMessage, args)
}
case (DirectoryTag, arg :: args) =>
val path = Path(arg)
if (path.isDirectory) update(Directory(path), args)
else fail(s"'$arg' does not exist or is not a directory", args)
case (_, Nil) =>
missingArg
}
Expand Down Expand Up @@ -274,6 +278,9 @@ object Settings {
def PathSetting(name: String, descr: String, default: String): Setting[String] =
publish(Setting(name, descr, default))

def PathSetting(name: String, helpArg: String, descr: String, default: String): Setting[String] =
publish(Setting(name, descr, default, helpArg))

def PhasesSetting(name: String, descr: String, default: String = ""): Setting[List[String]] =
publish(Setting(name, descr, if (default.isEmpty) Nil else List(default)))

Expand All @@ -285,8 +292,5 @@ object Settings {

def OptionSetting[T: ClassTag](name: String, descr: String): Setting[Option[T]] =
publish(Setting(name, descr, None, propertyClass = Some(implicitly[ClassTag[T]].runtimeClass)))

def DirectorySetting(name: String, helpArg: String, descr: String, default: Directory): Setting[Directory] =
publish(Setting(name, descr, default, helpArg))
}
}
27 changes: 11 additions & 16 deletions compiler/src/dotty/tools/io/AbstractFile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.io.{
ByteArrayOutputStream
}
import java.net.URL
import java.nio.file.{FileAlreadyExistsException, Files}
import java.nio.file.{FileAlreadyExistsException, Files, Paths}

/**
* An abstraction over files for use in the reflection/compiler libraries.
Expand All @@ -21,28 +21,26 @@ import java.nio.file.{FileAlreadyExistsException, Files}
* @version 1.0, 23/03/2004
*/
object AbstractFile {
/** Returns "getFile(new File(path))". */
def getFile(path: String): AbstractFile = getFile(File(path))
def getFile(path: Path): AbstractFile = getFile(path.toFile)
def getDirectory(path: String): AbstractFile = getDirectory(Directory(path))
def getFile(path: JPath): AbstractFile = getFile(File(path))
def getDirectory(path: JPath): AbstractFile = getDirectory(Directory(path))

/**
* If the specified File exists and is a regular file, returns an
* abstract regular file backed by it. Otherwise, returns `null`.
*/
def getFile(file: File): AbstractFile =
if (file.isFile) new PlainFile(file) else null

/** Returns "getDirectory(new File(path))". */
def getDirectory(path: Path): AbstractFile = getDirectory(path.toFile)
def getFile(path: Path): AbstractFile =
if (path.isFile) new PlainFile(path) else null

/**
* If the specified File exists and is either a directory or a
* readable zip or jar archive, returns an abstract directory
* backed by it. Otherwise, returns `null`.
*/
def getDirectory(file: File): AbstractFile =
if (file.isDirectory) new PlainFile(file)
else if (file.isFile && Path.isExtensionJarOrZip(file.jpath)) ZipArchive fromFile file
def getDirectory(path: Path): AbstractFile =
if (path.isDirectory) new PlainFile(path)
else if (path.isFile && Path.isExtensionJarOrZip(path.jpath)) ZipArchive fromFile path.toFile
else null

/**
Expand All @@ -51,11 +49,8 @@ object AbstractFile {
* Otherwise, returns `null`.
*/
def getURL(url: URL): AbstractFile =
if (url.getProtocol == "file") {
val f = new java.io.File(url.getPath)
if (f.isDirectory) getDirectory(f)
else getFile(f)
} else null
if (url.getProtocol != "file") null
else new PlainFile(new Path(Paths.get(url.toURI)))

def getResources(url: URL): AbstractFile = ZipArchive fromManifestURL url
}
Expand Down
27 changes: 12 additions & 15 deletions compiler/src/dotty/tools/io/Directory.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

package dotty.tools.io

import java.nio.file.Files
import java.nio.file.{Files, Paths}
import java.util.stream.Collectors

import scala.collection.JavaConverters._
Expand All @@ -19,18 +19,12 @@ import scala.collection.JavaConverters._
object Directory {
import scala.util.Properties.userDir

private def normalizePath(s: String) = Some(apply(Path(s).normalize))
def Current: Option[Directory] = if (userDir == "") None else normalizePath(userDir)
def Current: Option[Directory] =
if (userDir == "") None
else Some(apply(userDir).normalize)

def apply(path: Path): Directory = path.toDirectory
def apply(jpath: JPath): Directory = new Directory(jpath)

// Like File.makeTemp but creates a directory instead
def makeTemp(prefix: String = Path.randomPrefix, suffix: String = null, dir: JFile = null): Directory = {
val path = File.makeTemp(prefix, suffix, dir)
path.delete()
path.createDirectory()
}
def apply(path: String): Directory = apply(Paths.get(path))
def apply(path: JPath): Directory = new Directory(path)
}

/** An abstraction for directories.
Expand All @@ -49,10 +43,13 @@ class Directory(jpath: JPath) extends Path(jpath) {
/** An iterator over the contents of this directory.
*/
def list: Iterator[Path] =
jpath.toFile.listFiles match {
case null => Iterator.empty
case xs => xs.iterator.map(x => Path(x.toPath))
if (isDirectory) {
val fileStream = Files.list(jpath)
val files = fileStream.toArray(size => new Array[JPath](size))
fileStream.close()
files.iterator.map(Path.apply)
}
else Iterator.empty

def dirs: Iterator[Directory] = list collect { case x: Directory => x }
def files: Iterator[File] = list collect { case x: File => x }
Expand Down
17 changes: 6 additions & 11 deletions compiler/src/dotty/tools/io/File.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import java.io.{
FileInputStream, FileOutputStream, BufferedWriter, OutputStreamWriter,
BufferedOutputStream, IOException, PrintWriter
}
import java.nio.file.Files
import java.nio.file.{Files, Paths}
import java.nio.file.StandardOpenOption._

import scala.io.Codec
Expand All @@ -22,14 +22,9 @@ import scala.io.Codec
object File {
def pathSeparator = java.io.File.pathSeparator
def separator = java.io.File.separator
def apply(path: Path)(implicit codec: Codec) = new File(path.jpath)(codec)

// Create a temporary file, which will be deleted upon jvm exit.
def makeTemp(prefix: String = Path.randomPrefix, suffix: String = null, dir: JFile = null) = {
val jfile = java.io.File.createTempFile(prefix, suffix, dir)
jfile.deleteOnExit()
apply(jfile)
}
def apply(path: String)(implicit codec: Codec): File = apply(Paths.get(path))
def apply(path: JPath)(implicit codec: Codec): File = new File(path)
}

/** An abstraction for files. For character data, a Codec
Expand Down Expand Up @@ -59,9 +54,9 @@ class File(jpath: JPath)(implicit constructorCodec: Codec) extends Path(jpath) w
def inputStream() = Files.newInputStream(jpath)

/** Obtains a OutputStream. */
def outputStream(append: Boolean = false) =
if (append) Files.newOutputStream(jpath, APPEND)
else Files.newOutputStream(jpath)
def outputStream(append: Boolean = false) =
if (append) Files.newOutputStream(jpath, CREATE, APPEND)
else Files.newOutputStream(jpath, CREATE, TRUNCATE_EXISTING)
def bufferedOutput(append: Boolean = false) = new BufferedOutputStream(outputStream(append))

/** Obtains an OutputStreamWriter wrapped around a FileOutputStream.
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/io/Jar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import scala.annotation.tailrec
// static Attributes.Name SPECIFICATION_VERSION

class Jar(file: File) extends Iterable[JarEntry] {
def this(jfile: JFile) = this(File(jfile))
def this(jfile: JFile) = this(File(jfile.toPath))
def this(path: String) = this(File(path))

protected def errorFn(msg: String): Unit = Console println msg
Expand Down
31 changes: 31 additions & 0 deletions compiler/src/dotty/tools/io/JarArchive.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dotty.tools.io

import java.nio.file.{Files, FileSystem, FileSystems}

import scala.collection.JavaConverters._

/**
* This class implements an [[AbstractFile]] backed by a jar
* that be can used as the compiler's output directory.
*/
class JarArchive private (root: Directory) extends PlainDirectory(root) {
def close() = jpath.getFileSystem().close()
}

object JarArchive {
/** Create a new jar file. Overwrite if file already exists */
def create(path: Path): JarArchive = {
require(path.extension == "jar")

path.delete()

// creating a new zip file system by using the JAR URL syntax:
// https://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html
val env = Map("create" -> "true").asJava
val uri = java.net.URI.create("jar:file:" + path.toAbsolute.path)
val fs = FileSystems.newFileSystem(uri, env)

val root = fs.getRootDirectories().iterator.next()
new JarArchive(Directory(root))
}
}
Loading