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 6 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
32 changes: 27 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,36 @@ class GenBCode extends Phase {
superCallsMap.put(sym, old + calls)
}

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

def outputDir(implicit ctx: Context): AbstractFile = {
val path = Directory(ctx.settings.outputDir.value)
if (path.extension == "jar") {
if (jarFS == null) {
path.delete()
jarFS = JarFS.create(path)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make sure that the jar is empty or all files are rewritten.

}
jarFS.getRoot()
}
else new PlainDirectory(path)
}

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 if (jarFS ne null) {
jarFS.close()
jarFS = null
}
}
}

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
28 changes: 28 additions & 0 deletions compiler/src/dotty/tools/io/JarFS.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dotty.tools
package io

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

import java.util.jar._

import scala.collection.JavaConverters._

class JarFS private (private[this] var jarFS: FileSystem) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add some documentation :).


def getRoot(): AbstractFile = {
val root = jarFS.getRootDirectories.iterator().next()
new PlainDirectory(Directory(root))
}

def close() = jarFS.close()
}

object JarFS {
def create(path: Path): JarFS = {
assert(path.extension == "jar")
val env = Map("create" -> "true").asJava
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add these links in comments if they're useful to understand the code

val jarUri = java.net.URI.create("jar:file:" + path.toAbsolute.path)
new JarFS(FileSystems.newFileSystem(jarUri, env))
}
}
Loading