Skip to content

Commit e4c4065

Browse files
committed
Separate IRFile / IRContainer into stable/super-stable API
1 parent a9930ba commit e4c4065

File tree

27 files changed

+664
-562
lines changed

27 files changed

+664
-562
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package org.scalajs.linker
14+
15+
import scala.concurrent._
16+
17+
import scala.util.{Success, Failure}
18+
19+
import scala.scalajs.js
20+
import scala.scalajs.js.annotation.JSImport
21+
import scala.scalajs.js.typedarray._
22+
import scala.scalajs.js.typedarray.TypedArrayBufferOps._
23+
24+
import java.nio._
25+
26+
import org.scalajs.linker.irio._
27+
import org.scalajs.linker.standard.{IRContainerImpl, IRFileImpl}
28+
29+
abstract class IRContainerPlatformExtensions private[linker] () {
30+
import NodeFS._
31+
import FS._
32+
import IRContainerPlatformExtensions._
33+
34+
def fromNodeClasspath(classpath: Seq[String])(
35+
implicit ec: ExecutionContext): Future[(Seq[IRContainer], Seq[String])] = {
36+
Future.traverse(classpath) { entry =>
37+
cbFuture[Stats](FS.stat(entry, _)).transformWith {
38+
case Success(stat) if stat.isDirectory =>
39+
fromDirectory(entry)
40+
41+
case Success(stat) if entry.endsWith(".jar") =>
42+
val c = new NodeJarIRContainer(entry, stat.mtime.toOption)
43+
Future.successful(Seq((c, entry)))
44+
45+
case Success(_) =>
46+
throw new IllegalArgumentException("Illegal classpath entry: " + entry)
47+
48+
case Failure(js.JavaScriptException(e: js.Error)) if isNotFound(e) =>
49+
Future.successful(Nil)
50+
51+
case Failure(t) =>
52+
throw t
53+
}
54+
}.map(_.flatten.unzip)
55+
}
56+
57+
private def fromDirectory(dir: String)(
58+
implicit ec: ExecutionContext): Future[Seq[(IRContainer, String)]] = {
59+
cbFuture[js.Array[Dirent]](FS.readdir(dir, ReadDirOpt, _)).flatMap { entries =>
60+
val (dirs, files) = entries.toSeq.partition(_.isDirectory)
61+
62+
val subdirFiles = Future.traverse(dirs) { e =>
63+
val path = Path.join(dir, e.name)
64+
fromDirectory(path)
65+
}
66+
67+
val irFileNames = files.map(_.name).filter(_.endsWith(".sjsir"))
68+
val directFiles = Future.traverse(irFileNames) { n =>
69+
val path = Path.join(dir, n)
70+
IRFile.fromNodePath(path).map(f => (IRContainer.fromIRFile(f), path))
71+
}
72+
73+
for {
74+
sdf <- subdirFiles
75+
df <- directFiles
76+
} yield sdf.flatten ++ df
77+
}
78+
}
79+
80+
private def isNotFound(e: js.Error): Boolean =
81+
(e.asInstanceOf[js.Dynamic].code: Any) == "ENOENT"
82+
}
83+
84+
private object IRContainerPlatformExtensions {
85+
import NodeFS._
86+
87+
private final class NodeJarIRContainer(path: String, version: Option[js.Date])
88+
extends IRContainerImpl(path, version.map(_.getTime.toString)) {
89+
import NodeFS._
90+
91+
def sjsirFiles(implicit ec: ExecutionContext): Future[List[IRFile]] = {
92+
for {
93+
arr <- cbFuture[Uint8Array](FS.readFile(path, _))
94+
zip <- JSZip.loadAsync(arr).toFuture
95+
files <- loadFromZip(zip)
96+
} yield {
97+
files.toList
98+
}
99+
}
100+
101+
private def loadFromZip(obj: JSZip.JSZip)(
102+
implicit ec: ExecutionContext): Future[Iterator[IRFile]] = {
103+
val entries = obj.files.valuesIterator
104+
.filter(e => e.name.endsWith(".sjsir") && !e.dir)
105+
106+
Future.traverse(entries) { entry =>
107+
entry.async(JSZipInterop.arrayBuffer).toFuture.map { buf =>
108+
IRFileImpl.fromMem(s"${this.path}:${entry.name}", version, new Int8Array(buf).toArray)
109+
}
110+
}
111+
}
112+
}
113+
114+
private object JSZipInterop {
115+
val arrayBuffer: String = "arraybuffer"
116+
}
117+
118+
@js.native
119+
@JSImport("jszip", JSImport.Default)
120+
private object JSZip extends js.Object {
121+
trait JSZip extends js.Object {
122+
val files: js.Dictionary[ZipObject]
123+
}
124+
125+
trait ZipObject extends js.Object {
126+
val name: String
127+
val dir: Boolean
128+
def async(tpe: JSZipInterop.arrayBuffer.type): js.Promise[ArrayBuffer]
129+
}
130+
131+
def loadAsync(data: Uint8Array): js.Promise[JSZip] = js.native
132+
}
133+
134+
@JSImport("path", JSImport.Namespace)
135+
@js.native
136+
private object Path extends js.Object {
137+
def join(paths: String*): String = js.native
138+
}
139+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package org.scalajs.linker
14+
15+
import scala.concurrent._
16+
17+
import scala.scalajs.js
18+
import scala.scalajs.js.typedarray._
19+
import scala.scalajs.js.typedarray.TypedArrayBufferOps._
20+
21+
import org.scalajs.linker.irio._
22+
import org.scalajs.linker.standard.IRFileImpl
23+
24+
import java.io.EOFException
25+
import java.nio._
26+
27+
import org.scalajs.ir
28+
29+
abstract class IRFilePlatformExtensions private[linker] () {
30+
import NodeFS._
31+
32+
def fromNodePath(path: String)(implicit ec: ExecutionContext): Future[IRFile] = {
33+
cbFuture[Stats](FS.stat(path, _)).map(stats =>
34+
new IRFilePlatformExtensions.NodeIRFileImpl(path, stats.mtime.toOption))
35+
}
36+
}
37+
38+
private object IRFilePlatformExtensions {
39+
import NodeFS._
40+
private final class NodeIRFileImpl(path: String, version: Option[js.Date])
41+
extends IRFileImpl(path, version.map(_.getTime.toString)) {
42+
43+
def entryPointsInfo(implicit ec: ExecutionContext): Future[ir.EntryPointsInfo] = {
44+
def loop(fd: Int, buf: ByteBuffer): Future[ir.EntryPointsInfo] = {
45+
val len = buf.remaining()
46+
val off = buf.position()
47+
48+
cbFuture[Int](FS.read(fd, buf.typedArray(), off, len, off, _)).map { bytesRead =>
49+
if (bytesRead <= 0)
50+
throw new EOFException
51+
52+
buf.position(buf.position() + bytesRead)
53+
buf.flip()
54+
ir.Serializers.deserializeEntryPointsInfo(buf)
55+
}.recoverWith {
56+
case _: BufferUnderflowException =>
57+
// Reset to write again.
58+
buf.position(buf.limit())
59+
buf.limit(buf.capacity())
60+
61+
val newBuf = if (buf.remaining() <= 0) {
62+
val newBuf = ByteBuffer.allocateDirect(buf.capacity() * 2)
63+
buf.flip()
64+
newBuf.put(buf)
65+
buf
66+
} else {
67+
buf
68+
}
69+
70+
loop(fd, newBuf)
71+
}
72+
}
73+
74+
val result = cbFuture[Int](FS.open(path, "r", _)).flatMap { fd =>
75+
loop(fd, ByteBuffer.allocateDirect(1024))
76+
.finallyWith(cbFuture[Unit](FS.close(fd, _)))
77+
}
78+
79+
IRFileImpl.withPathExceptionContext(path, result)
80+
}
81+
82+
def tree(implicit ec: ExecutionContext): Future[ir.Trees.ClassDef] = {
83+
val result = cbFuture[Uint8Array](FS.readFile(path, _)).map { arr =>
84+
ir.Serializers.deserialize(TypedArrayBuffer.wrap(arr.buffer))
85+
}
86+
87+
IRFileImpl.withPathExceptionContext(path, result)
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)