Skip to content

Commit

Permalink
Merge pull request #277 from alexarchambault/native-image-tweaks
Browse files Browse the repository at this point in the history
Tweaks
  • Loading branch information
alexarchambault authored Jun 10, 2021
2 parents f092fce + ba0c57a commit ba7df6a
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 75 deletions.
90 changes: 67 additions & 23 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,72 @@ lazy val server = project
"org.jboss.xnio" % "xnio-nio" % "3.8.0.Final",
"org.scalameta" % "semanticdb-scalac-core" % Version.scalameta cross CrossVersion.full,
("org.scalameta" %% "mtags" % "0.10.4").cross(CrossVersion.full)
)
),
(Compile / packageBin) := {
import java.io.FileOutputStream
import java.nio.file.attribute.FileTime
import java.nio.file.Files
import java.util.zip._
import scala.collection.JavaConverters._
val base = (Compile / packageBin).value
val updated = base.getParentFile / s"${base.getName.stripSuffix(".jar")}-with-resources.jar"

val fos = new FileOutputStream(updated)
val zos = new ZipOutputStream(fos)

val zf = new ZipFile(base)
val buf = Array.ofDim[Byte](64 * 1024)
for (ent <- zf.entries.asScala) {
zos.putNextEntry(ent)
val is = zf.getInputStream(ent)
var read = -1
while ({
read = is.read(buf)
read >= 0
}) {
if (read > 0)
zos.write(buf, 0, read)
}
}
zf.close()

// FIXME - use fullOptJS: https://github.com/scalameta/metabrowse/issues/271
val _ = (js / Compile / fastOptJS / webpack).value
val targetDir = (js / Compile / npmUpdate).value
// scalajs-bundler does not support setting a custom output path so
// explicitly include only those files that are generated by webpack.
val includes: FileFilter =
"index.html" | "metabrowse.*.css" | "*-bundle.js" | "favicon.png"
val paths: PathFinder =
(
targetDir./("assets").allPaths +++
targetDir./("vs").allPaths +++
targetDir.*(includes)
) --- targetDir
val mappings = paths.get pair Path.relativeTo(targetDir)
val prefix = "metabrowse/server/assets/"
for ((f, path) <- mappings) {
if (f.isDirectory) {
val ent = new ZipEntry(prefix + path.stripSuffix("/") + "/")
ent.setLastModifiedTime(FileTime.fromMillis(f.lastModified()))
zos.putNextEntry(ent)
} else {
val ent = new ZipEntry(prefix + path)
ent.setLastModifiedTime(FileTime.fromMillis(f.lastModified()))
zos.putNextEntry(ent)
val b = Files.readAllBytes(f.toPath)
zos.write(b)
}
}

zos.finish()
zos.close()
fos.close()

updated
}
)
.dependsOn(cli)
.dependsOn(coreJVM)

lazy val cli = project
.in(file("metabrowse-cli"))
Expand Down Expand Up @@ -134,28 +197,9 @@ lazy val cli = project
case _ =>
Seq()
}
},
(Compile / resourceGenerators) += Def.task {
val zip = (Compile / resourceManaged).value / "metabrowse-assets.zip"
// FIXME - use fullOptJS: https://github.com/scalameta/metabrowse/issues/271
val _ = (js / Compile / fastOptJS / webpack).value
val targetDir = (js / Compile / npmUpdate).value
// scalajs-bundler does not support setting a custom output path so
// explicitly include only those files that are generated by webpack.
val includes: FileFilter =
"index.html" | "metabrowse.*.css" | "*-bundle.js" | "favicon.png"
val paths: PathFinder =
(
targetDir./("assets").allPaths +++
targetDir./("vs").allPaths +++
targetDir.*(includes)
) --- targetDir
val mappings = paths.get pair Path.relativeTo(targetDir)
IO.zip(mappings, zip)
Seq(zip)
}.taskValue
}
)
.dependsOn(coreJVM)
.dependsOn(server)

lazy val js = project
.in(file("metabrowse-js"))
Expand Down
33 changes: 0 additions & 33 deletions metabrowse-cli/src/main/scala/metabrowse/cli/MetabrowseCli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -358,38 +358,6 @@ class CliRunner(classpath: Seq[AbsolutePath], options: MetabrowseOptions) {
}
}

def writeAssets(): Unit = {
val root = target.toNIO
val inputStream = MetabrowseCli.getClass.getClassLoader
.getResourceAsStream("metabrowse-assets.zip")
if (inputStream == null)
sys.error("Failed to locate metabrowse-assets.zip on the classpath")
val zipStream = new ZipInputStream(inputStream)
val bytes = new Array[Byte](8012)
Stream
.continually(zipStream.getNextEntry)
.takeWhile(_ != null)
.filterNot(_.isDirectory)
.foreach { entry =>
val path = root.resolve(entry.getName)
if (Files.notExists(path))
Files.createDirectories(path.getParent)
val out = Files.newOutputStream(path, StandardOpenOption.CREATE)

def copyLoop(): Unit = {
val read = zipStream.read(bytes, 0, bytes.length)
if (read > 0) {
out.write(bytes, 0, read)
copyLoop()
}
}

copyLoop()
out.flush()
out.close()
}
}

def writeWorkspace(): Unit = {
import scala.collection.JavaConverters._
val workspace = d.Workspace(filenames.asScala.toSeq)
Expand All @@ -403,7 +371,6 @@ class CliRunner(classpath: Seq[AbsolutePath], options: MetabrowseOptions) {
val paths = scanSemanticdbs()
buildSymbolIndex(paths, paths.length)
writeSymbolIndex()
writeAssets()
writeWorkspace()
} finally {
display.stop()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,29 @@ import io.undertow.server.HttpHandler
import io.undertow.server.HttpServerExchange
import io.undertow.util.Headers
import java.io.ByteArrayOutputStream
import java.io.Closeable
import java.io.File
import java.io.InputStream
import java.net.URLClassLoader
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.util.Scanner
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import java.util.zip.GZIPOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import metabrowse.schema.SymbolIndex
import metabrowse.schema.SymbolIndexes
import metabrowse.schema.Workspace
import metabrowse.{schema => d}
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.meta.Dialect
import scala.meta.inputs.Input
Expand Down Expand Up @@ -55,6 +62,7 @@ class MetabrowseServer(
def stop(): Unit = {
server.stop()
global.askShutdown()
Option(state.get()).foreach(_.close())
}

/** Updates the running server to use a new sourcepath.
Expand All @@ -65,12 +73,14 @@ class MetabrowseServer(
*/
def replaceClasspath(sourcepath: Sourcepath): Unit = {
lock.synchronized {
if (state.get() != null) {
val prevState = state.get()
if (prevState != null) {
global.askShutdown()
prevState.close()
}
val newState = State(
OnDemandSymbolIndex.empty(),
new URLClassLoader(sourcepath.sources.map(_.toUri.toURL).toArray),
sourcepath.sources,
InteractiveSemanticdb.newCompiler(
sourcepath.classpath.mkString(File.pathSeparator),
scalacOptions
Expand Down Expand Up @@ -116,25 +126,78 @@ class MetabrowseServer(
// Mutable state:
case class State(
index: OnDemandSymbolIndex,
classLoader: URLClassLoader,
classPath: Seq[Path],
global: Global,
sourcepath: Sourcepath
)
) extends Closeable {
private lazy val useCl = java.lang.Boolean.getBoolean("metabrowse.ucl")
private lazy val classLoader = new URLClassLoader(
sourcepath.sources.map(_.toUri.toURL).toArray
)
private val zipFiles = new ConcurrentHashMap[Path, ZipFile]
private def zipFile(path: Path): ZipFile = {
var zf = zipFiles.get(path)
if (zf == null) {
var zf0: ZipFile = null
try {
zf0 = new ZipFile(path.toFile)
val prev = zipFiles.putIfAbsent(path, zf0)
if (prev == null) {
zf = zf0
zf0 = null
} else
zf = prev
} finally {
if (zf0 != null)
zf0.close()
}
}
zf
}
def source(name: String): Option[String] = {
val bytesOpt =
if (useCl)
withInputStream(classLoader.getResourceAsStream(name)) { is =>
if (is == null) None
else Some(InputStreamIO.readBytes(is))
} else {
val it =
for {
path <- classPath.iterator
zf = zipFile(path)
entry <- Option(zf.getEntry(name)).iterator
} yield
withInputStream(zf.getInputStream(entry))(
InputStreamIO.readBytes(_)
)
it.toStream.headOption
}
bytesOpt.map(new String(_, StandardCharsets.UTF_8))
}
def close(): Unit =
for (entry <- zipFiles.entrySet().asScala.toVector) {
entry.getValue.close()
zipFiles.remove(entry.getKey, entry.getValue)
}
}
private val state = new AtomicReference[State]()
def index = state.get().index
def classLoader = state.get().classLoader
def global = state.get().global
def sourcepath = state.get().sourcepath

private def withInputStream[T](is: => InputStream)(f: InputStream => T): T = {
var is0: InputStream = null
try {
is0 = is
f(is0)
} finally {
if (is0 != null)
is0.close()
}
}

// Static state:
private val lock = new Object()
private val assets = {
val in =
this.getClass.getClassLoader.getResourceAsStream("metabrowse-assets.zip")
val out = Files.createTempDirectory("metabrowse").resolve("assets.zip")
Files.copy(in, out)
FileIO.jarRootPath(AbsolutePath(out))
}
private val httpHandler = new HttpHandler {
override def handleRequest(exchange: HttpServerExchange): Unit = {
val bytes =
Expand Down Expand Up @@ -185,11 +248,19 @@ class MetabrowseServer(
Array.emptyByteArray
} else {
val actualPath = if (path == "/") "/index.html" else path
val file = assets.resolve(actualPath)
if (file.isFile) file.readAllBytes
else {
logger.warn(s"no such file: $file")
Array.emptyByteArray
withInputStream(
Thread
.currentThread()
.getContextClassLoader
.getResourceAsStream(
s"metabrowse/server/assets/${actualPath.stripPrefix("/")}"
)
) { is =>
if (is == null) {
logger.warn(s"no such file: $path")
Array.emptyByteArray
} else
InputStreamIO.readBytes(is)
}
}
}
Expand Down Expand Up @@ -235,11 +306,10 @@ class MetabrowseServer(
.stripSuffix(".semanticdb")
logger.info(path)
for {
in <- Option(classLoader.getResourceAsStream(path)).orElse {
text <- state.get().source(path).orElse {
logger.warn(s"no source file: $path")
None
}
text = new String(InputStreamIO.readBytes(in), StandardCharsets.UTF_8)
doc <- try {
val timeout = TimeUnit.SECONDS.toMillis(10)
val textDocument = if (path.endsWith(".java")) {
Expand Down

0 comments on commit ba7df6a

Please sign in to comment.