Skip to content

Commit 40011e2

Browse files
authored
Merge pull request #3382 from dotty-staging/output-to-jar
Support -d with .jar paths
2 parents 5b33591 + 2e79154 commit 40011e2

20 files changed

+154
-104
lines changed

compiler/src/dotty/tools/backend/jvm/GenBCode.scala

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import dotty.tools.dotc.core.Phases.Phase
77
import dotty.tools.dotc.core.Names.TypeName
88

99
import scala.collection.mutable
10+
import scala.collection.JavaConverters._
1011
import scala.tools.asm.{ClassVisitor, CustomAttr, FieldVisitor, MethodVisitor}
1112
import scala.tools.nsc.backend.jvm._
1213
import dotty.tools.dotc
@@ -26,14 +27,16 @@ import Denotations._
2627
import Phases._
2728
import java.lang.AssertionError
2829
import java.io.{DataOutputStream, File => JFile}
30+
import java.nio.file.{Files, FileSystem, FileSystems, Path => JPath}
31+
32+
import dotty.tools.io.{Directory, File, Jar}
2933

3034
import scala.tools.asm
3135
import scala.tools.asm.tree._
3236
import dotty.tools.dotc.util.{DotClass, Positions}
3337
import tpd._
3438
import StdNames._
35-
36-
import dotty.tools.io.{AbstractFile, Directory, PlainDirectory}
39+
import dotty.tools.io._
3740

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

49-
def outputDir(implicit ctx: Context): AbstractFile =
50-
new PlainDirectory(ctx.settings.outputDir.value)
52+
private[this] var myOutput: AbstractFile = _
53+
54+
protected def outputDir(implicit ctx: Context): AbstractFile = {
55+
if (myOutput eq null) {
56+
val path = Directory(ctx.settings.outputDir.value)
57+
myOutput =
58+
if (path.extension == "jar") JarArchive.create(path)
59+
else new PlainDirectory(path)
60+
}
61+
myOutput
62+
}
5163

5264
def run(implicit ctx: Context): Unit = {
5365
new GenBCodePipeline(entryPoints.toList,
5466
new DottyBackendInterface(outputDir, superCallsMap.toMap)(ctx))(ctx).run(ctx.compilationUnit.tpdTree)
5567
entryPoints.clear()
5668
}
69+
70+
override def runOn(units: List[CompilationUnit])(implicit ctx: Context) = {
71+
try super.runOn(units)
72+
finally myOutput match {
73+
case jar: JarArchive =>
74+
jar.close()
75+
case _ =>
76+
}
77+
}
5778
}
5879

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

6182
var tree: Tree = _
6283

compiler/src/dotty/tools/dotc/Run.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class Run(comp: Compiler, ictx: Context) {
6060
assert(ctx.runId <= Periods.MaxPossibleRunId)
6161

6262
def getSource(fileName: String): SourceFile = {
63-
val f = new PlainFile(fileName)
63+
val f = new PlainFile(io.Path(fileName))
6464
if (f.isDirectory) {
6565
ctx.error(s"expected file, received directory '$fileName'")
6666
NoSource

compiler/src/dotty/tools/dotc/classpath/ClassPathFactory.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package dotty.tools.dotc.classpath
55

66
import dotty.tools.io.{AbstractFile, VirtualDirectory}
7-
import dotty.tools.io.Path.string2path
87
import dotty.tools.dotc.config.Settings
98
import FileUtils.AbstractFileOps
109
import dotty.tools.io.ClassPath

compiler/src/dotty/tools/dotc/config/OutputDirs.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class OutputDirs {
3131
if (dir != null && dir.isDirectory)
3232
dir
3333
// was: else if (allowJar && dir == null && Path.isJarOrZip(name, false))
34-
else if (allowJar && dir == null && Jar.isJarOrZip(name, false))
34+
else if (allowJar && dir == null && Jar.isJarOrZip(File(name), false))
3535
new PlainFile(Path(name))
3636
else
3737
throw new FatalError(name + " does not exist or is not a directory"))

compiler/src/dotty/tools/dotc/config/PathResolver.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,11 @@ object PathResolver {
8080
def scalaHome = Environment.scalaHome
8181
def scalaHomeDir = Directory(scalaHome)
8282
def scalaHomeExists = scalaHomeDir.isDirectory
83-
def scalaLibDir = Directory(scalaHomeDir / "lib")
84-
def scalaClassesDir = Directory(scalaHomeDir / "classes")
83+
def scalaLibDir = (scalaHomeDir / "lib").toDirectory
84+
def scalaClassesDir = (scalaHomeDir / "classes").toDirectory
8585

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

8989
def scalaLibDirFound: Option[Directory] =
9090
if (scalaLibAsJar.isFile) Some(scalaLibDir)

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class ScalaSettings extends Settings.SettingGroup {
1818
val javaextdirs = PathSetting("-javaextdirs", "Override java extdirs classpath.", Defaults.javaExtDirs)
1919
val sourcepath = PathSetting("-sourcepath", "Specify location(s) of source files.", "") // Defaults.scalaSourcePath
2020
val classpath = PathSetting("-classpath", "Specify where to find user class files.", defaultClasspath) withAbbreviation "-cp"
21-
val outputDir = DirectorySetting("-d", "directory|jar", "destination for generated classfiles.", Directory(Path(".")))
21+
val outputDir = PathSetting("-d", "directory|jar", "destination for generated classfiles.", ".")
2222
val priorityclasspath = PathSetting("-priorityclasspath", "class path that takes precedence over all other paths (or testing only)", "")
2323

2424
/** Other settings */

compiler/src/dotty/tools/dotc/config/Settings.scala

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import scala.util.{ Try, Success, Failure }
66
import reflect.ClassTag
77
import core.Contexts._
88
import scala.annotation.tailrec
9-
import dotty.tools.io.{ Directory, Path }
9+
import dotty.tools.io.{ Directory, File, Path }
1010

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

2726
class SettingsState(initialValues: Seq[Any]) {
2827
private[this] var values = ArrayBuffer(initialValues: _*)
@@ -141,6 +140,15 @@ object Settings {
141140
else if (!choices.contains(argRest))
142141
fail(s"$arg is not a valid choice for $name", args)
143142
else update(argRest, args)
143+
case (StringTag, arg :: args) if name == "-d" =>
144+
Path(arg) match {
145+
case _: Directory =>
146+
update(arg, args)
147+
case p if p.extension == "jar" =>
148+
update(arg, args)
149+
case _ =>
150+
fail(s"'$arg' does not exist or is not a directory", args)
151+
}
144152
case (StringTag, arg2 :: args2) =>
145153
update(arg2, args2)
146154
case (IntTag, arg2 :: args2) =>
@@ -161,10 +169,6 @@ object Settings {
161169
case Success(v) => update(v, args)
162170
case Failure(ex) => fail(ex.getMessage, args)
163171
}
164-
case (DirectoryTag, arg :: args) =>
165-
val path = Path(arg)
166-
if (path.isDirectory) update(Directory(path), args)
167-
else fail(s"'$arg' does not exist or is not a directory", args)
168172
case (_, Nil) =>
169173
missingArg
170174
}
@@ -274,6 +278,9 @@ object Settings {
274278
def PathSetting(name: String, descr: String, default: String): Setting[String] =
275279
publish(Setting(name, descr, default))
276280

281+
def PathSetting(name: String, helpArg: String, descr: String, default: String): Setting[String] =
282+
publish(Setting(name, descr, default, helpArg))
283+
277284
def PhasesSetting(name: String, descr: String, default: String = ""): Setting[List[String]] =
278285
publish(Setting(name, descr, if (default.isEmpty) Nil else List(default)))
279286

@@ -285,8 +292,5 @@ object Settings {
285292

286293
def OptionSetting[T: ClassTag](name: String, descr: String): Setting[Option[T]] =
287294
publish(Setting(name, descr, None, propertyClass = Some(implicitly[ClassTag[T]].runtimeClass)))
288-
289-
def DirectorySetting(name: String, helpArg: String, descr: String, default: Directory): Setting[Directory] =
290-
publish(Setting(name, descr, default, helpArg))
291295
}
292296
}

compiler/src/dotty/tools/io/AbstractFile.scala

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import java.io.{
1010
ByteArrayOutputStream
1111
}
1212
import java.net.URL
13-
import java.nio.file.{FileAlreadyExistsException, Files}
13+
import java.nio.file.{FileAlreadyExistsException, Files, Paths}
1414

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

2829
/**
2930
* If the specified File exists and is a regular file, returns an
3031
* abstract regular file backed by it. Otherwise, returns `null`.
3132
*/
32-
def getFile(file: File): AbstractFile =
33-
if (file.isFile) new PlainFile(file) else null
34-
35-
/** Returns "getDirectory(new File(path))". */
36-
def getDirectory(path: Path): AbstractFile = getDirectory(path.toFile)
33+
def getFile(path: Path): AbstractFile =
34+
if (path.isFile) new PlainFile(path) else null
3735

3836
/**
3937
* If the specified File exists and is either a directory or a
4038
* readable zip or jar archive, returns an abstract directory
4139
* backed by it. Otherwise, returns `null`.
4240
*/
43-
def getDirectory(file: File): AbstractFile =
44-
if (file.isDirectory) new PlainFile(file)
45-
else if (file.isFile && Path.isExtensionJarOrZip(file.jpath)) ZipArchive fromFile file
41+
def getDirectory(path: Path): AbstractFile =
42+
if (path.isDirectory) new PlainFile(path)
43+
else if (path.isFile && Path.isExtensionJarOrZip(path.jpath)) ZipArchive fromFile path.toFile
4644
else null
4745

4846
/**
@@ -51,11 +49,8 @@ object AbstractFile {
5149
* Otherwise, returns `null`.
5250
*/
5351
def getURL(url: URL): AbstractFile =
54-
if (url.getProtocol == "file") {
55-
val f = new java.io.File(url.getPath)
56-
if (f.isDirectory) getDirectory(f)
57-
else getFile(f)
58-
} else null
52+
if (url.getProtocol != "file") null
53+
else new PlainFile(new Path(Paths.get(url.toURI)))
5954

6055
def getResources(url: URL): AbstractFile = ZipArchive fromManifestURL url
6156
}

compiler/src/dotty/tools/io/Directory.scala

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
package dotty.tools.io
1010

11-
import java.nio.file.Files
11+
import java.nio.file.{Files, Paths}
1212
import java.util.stream.Collectors
1313

1414
import scala.collection.JavaConverters._
@@ -19,18 +19,12 @@ import scala.collection.JavaConverters._
1919
object Directory {
2020
import scala.util.Properties.userDir
2121

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

25-
def apply(path: Path): Directory = path.toDirectory
26-
def apply(jpath: JPath): Directory = new Directory(jpath)
27-
28-
// Like File.makeTemp but creates a directory instead
29-
def makeTemp(prefix: String = Path.randomPrefix, suffix: String = null, dir: JFile = null): Directory = {
30-
val path = File.makeTemp(prefix, suffix, dir)
31-
path.delete()
32-
path.createDirectory()
33-
}
26+
def apply(path: String): Directory = apply(Paths.get(path))
27+
def apply(path: JPath): Directory = new Directory(path)
3428
}
3529

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

5754
def dirs: Iterator[Directory] = list collect { case x: Directory => x }
5855
def files: Iterator[File] = list collect { case x: File => x }

compiler/src/dotty/tools/io/File.scala

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import java.io.{
1212
FileInputStream, FileOutputStream, BufferedWriter, OutputStreamWriter,
1313
BufferedOutputStream, IOException, PrintWriter
1414
}
15-
import java.nio.file.Files
15+
import java.nio.file.{Files, Paths}
1616
import java.nio.file.StandardOpenOption._
1717

1818
import scala.io.Codec
@@ -22,14 +22,9 @@ import scala.io.Codec
2222
object File {
2323
def pathSeparator = java.io.File.pathSeparator
2424
def separator = java.io.File.separator
25-
def apply(path: Path)(implicit codec: Codec) = new File(path.jpath)(codec)
2625

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

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

6156
/** Obtains a OutputStream. */
62-
def outputStream(append: Boolean = false) =
63-
if (append) Files.newOutputStream(jpath, APPEND)
64-
else Files.newOutputStream(jpath)
57+
def outputStream(append: Boolean = false) =
58+
if (append) Files.newOutputStream(jpath, CREATE, APPEND)
59+
else Files.newOutputStream(jpath, CREATE, TRUNCATE_EXISTING)
6560
def bufferedOutput(append: Boolean = false) = new BufferedOutputStream(outputStream(append))
6661

6762
/** Obtains an OutputStreamWriter wrapped around a FileOutputStream.

compiler/src/dotty/tools/io/Jar.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import scala.annotation.tailrec
3535
// static Attributes.Name SPECIFICATION_VERSION
3636

3737
class Jar(file: File) extends Iterable[JarEntry] {
38-
def this(jfile: JFile) = this(File(jfile))
38+
def this(jfile: JFile) = this(File(jfile.toPath))
3939
def this(path: String) = this(File(path))
4040

4141
protected def errorFn(msg: String): Unit = Console println msg
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package dotty.tools.io
2+
3+
import java.nio.file.{Files, FileSystem, FileSystems}
4+
5+
import scala.collection.JavaConverters._
6+
7+
/**
8+
* This class implements an [[AbstractFile]] backed by a jar
9+
* that be can used as the compiler's output directory.
10+
*/
11+
class JarArchive private (root: Directory) extends PlainDirectory(root) {
12+
def close() = jpath.getFileSystem().close()
13+
}
14+
15+
object JarArchive {
16+
/** Create a new jar file. Overwrite if file already exists */
17+
def create(path: Path): JarArchive = {
18+
require(path.extension == "jar")
19+
20+
path.delete()
21+
22+
// creating a new zip file system by using the JAR URL syntax:
23+
// https://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html
24+
val env = Map("create" -> "true").asJava
25+
val uri = java.net.URI.create("jar:file:" + path.toAbsolute.path)
26+
val fs = FileSystems.newFileSystem(uri, env)
27+
28+
val root = fs.getRootDirectories().iterator.next()
29+
new JarArchive(Directory(root))
30+
}
31+
}

0 commit comments

Comments
 (0)