Skip to content

Commit

Permalink
Add support for JS/CSS dependencies.
Browse files Browse the repository at this point in the history
  • Loading branch information
ochrons committed Mar 29, 2018
1 parent 2353c80 commit fd5ae23
Show file tree
Hide file tree
Showing 14 changed files with 288 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: scala
scala:
- 2.11.8
- 2.12.4
sudo: false
jdk:
- oraclejdk8
Expand Down
33 changes: 21 additions & 12 deletions client/src/main/scala/scalafiddle/client/Client.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ trait EditorAnnotationJS extends js.Object {
@js.native
trait CompilationResponseJS extends js.Object {
val jsCode: js.Array[String]
val jsDeps: js.Array[String]
val cssDeps: js.Array[String]
val annotations: js.Array[EditorAnnotationJS]
val log: String
}
Expand Down Expand Up @@ -80,14 +82,7 @@ class Client(editURL: String) {
false
}

def exec(s: String): Unit = {
Client.clear()

showStatus("RUNNING")
js.timers.setTimeout(20) {
Client.sendFrameCmd("code", s)
}
}
def exec(s: String): Unit = {}

val editor: Editor = new Editor(
Seq(
Expand Down Expand Up @@ -150,6 +145,8 @@ class Client(editURL: String) {
val r = js.JSON.parse(jsonStr).asInstanceOf[CompilationResponseJS]
CompilationResponse(
if (r.jsCode.isEmpty) None else Some(r.jsCode(0)),
r.jsDeps,
r.cssDeps,
r.annotations.map { a =>
EditorAnnotation(a.row, a.col, a.text, a.tpe)
},
Expand Down Expand Up @@ -213,13 +210,24 @@ class Client(editURL: String) {
}

def performCompile(opt: String): Future[Unit] = {
import js.JSConverters._

val cleared = beginCompilation()
val pendingCompileResult = compileServer(editor.code, opt)
for {
_ <- cleared
jsCodeOpt <- processCompilationResponse(pendingCompileResult)
} yield {
jsCodeOpt.foreach(exec)
jsCodeOpt.foreach {
case (code, jsDeps, cssDeps) =>
Client.clear()
showStatus("RUNNING")
js.timers.setTimeout(20) {
// pass compiled data to the iframe
val data = js.Dynamic.literal(code = code, jsDeps = jsDeps.toJSArray, cssDeps = cssDeps.toJSArray)
Client.sendFrameCmd("code", data)
}
}
}
}

Expand Down Expand Up @@ -329,7 +337,7 @@ class Client(editURL: String) {
}
}

def processCompilationResponse(res: Future[CompilationResponse]): Future[Option[String]] = {
def processCompilationResponse(res: Future[CompilationResponse]): Future[Option[(String, Seq[String], Seq[String])]] = {
res
.map { response =>
endCompilation()
Expand All @@ -344,7 +352,7 @@ class Client(editURL: String) {
.mkString("\n")
showError(allErrors)
}
response.jsCode
response.jsCode.map(code => (code, response.jsDeps, response.cssDeps))
}
.recover {
case e: Exception =>
Expand Down Expand Up @@ -423,8 +431,9 @@ object Client {
dom.console.error(s)
}

def sendFrameCmd(cmd: String, data: String = "") = {
def sendFrameCmd(cmd: String, data: js.Any = "") = {
val msg = js.Dynamic.literal(cmd = cmd, data = data)

try {
codeFrame.contentWindow.postMessage(msg, "*")
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ class CompileActor(out: ActorRef, manager: ActorRef) extends Actor with ActorLog
compilationFailCounter.increment()
sendOut(
CompilationResponse(None,
Nil,
Nil,
Seq(EditorAnnotation(0, 0, e.getMessage +: compiler.getLog, "ERROR")),
compiler.getLog.mkString("\n")))
}
Expand Down Expand Up @@ -153,7 +155,9 @@ class CompileActor(out: ActorRef, manager: ActorRef) extends Actor with ActorLog
if (logSpam.nonEmpty)
log.debug(s"Compiler errors: $logSpam")

CompilationResponse(res.map(processor), parseErrors(logSpam), logSpam)
val (jsDeps, cssDeps) = compiler.getExtDeps
log.debug(s"External dependencies: JS = $jsDeps, CSS = $cssDeps")
CompilationResponse(res.map(processor), jsDeps.map(_.url), cssDeps.map(_.url), parseErrors(logSpam), logSpam)
}

override def postStop(): Unit = {
Expand Down
36 changes: 20 additions & 16 deletions compiler-server/src/main/scala/scalafiddle/compiler/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,30 @@ import scala.reflect.io
import scala.tools.nsc.Settings
import scala.tools.nsc.reporters.StoreReporter
import scalafiddle.compiler.cache.{AutoCompleteCache, CompilerCache, LinkerCache}
import scalafiddle.shared.ExtLib
import scalafiddle.shared.{CSSLib, ExtLib, JSLib}

/**
* Handles the interaction between scala-js-fiddle and
* scalac/scalajs-tools to compile and optimize code submitted by users.
*/
class Compiler(libManager: LibraryManager, code: String) { self =>
val log = LoggerFactory.getLogger(getClass)
val sjsLogger = new Log4jLogger()
val blacklist = Set("<init>")
val dependencyRE = """ *// \$FiddleDependency (.+)""".r
private val codeLines = code.replaceAll("\r", "").split('\n')
val extLibDefs = codeLines.collect {
private val log = LoggerFactory.getLogger(getClass)
private val sjsLogger = new Log4jLogger()
private val blacklist = Set("<init>")
private val dependencyRE = """ *// \$FiddleDependency (.+)""".r
private val codeLines = code.replaceAll("\r", "").split('\n')
private val extLibDefs = codeLines.collect {
case dependencyRE(dep) => dep
}.toSet

lazy val extLibs = {
private lazy val extLibs = {
val userLibs = extLibDefs
.map(lib => ExtLib(lib))
.collect {
case lib if libManager.depLibs.contains(lib) => lib
case lib => throw new IllegalArgumentException(s"Library $lib is not allowed")
}
.map(
extLib =>
libManager.depLibs
.find(_.sameAs(extLib))
.getOrElse(throw new IllegalArgumentException(s"Library $extLib is not allowed")))
.toList

log.debug(s"Full dependencies: $userLibs")
Expand All @@ -43,7 +44,7 @@ class Compiler(libManager: LibraryManager, code: String) { self =>
/**
* Converts a bunch of bytes into Scalac's weird VirtualFile class
*/
def makeFile(src: Array[Byte]) = {
private def makeFile(src: Array[Byte]) = {
val singleFile = new io.VirtualFile("ScalaFiddle.scala")
val output = singleFile.output
output.write(src)
Expand Down Expand Up @@ -91,7 +92,7 @@ class Compiler(libManager: LibraryManager, code: String) { self =>
val compiler = CompilerCache.getOrUpdate(
extLibs, {
val settings = new Settings
settings.processArgumentString("-Ydebug -Ypartial-unification")
settings.processArgumentString("-Ydebug -Ypartial-unification -Ylog-classpath")
GlobalInitCompat.initGlobal(settings, new StoreReporter, libManager.compilerLibraries(extLibs))
}
)
Expand Down Expand Up @@ -173,9 +174,12 @@ class Compiler(libManager: LibraryManager, code: String) { self =>
output
}

def getLog = sjsLogger.logLines
def getExtDeps: (List[JSLib], List[CSSLib]) =
(ExtLib.resolveLibs(extLibs.toList, _.jsLibs.toList), ExtLib.resolveLibs(extLibs.toList, _.cssLibs.toList))

def getLog: Vector[String] = sjsLogger.logLines

def getInternalLog = sjsLogger.internalLogLines
def getInternalLog: Vector[String] = sjsLogger.internalLogLines

class Log4jLogger(minLevel: Level = Level.Debug) extends Logger {
var logLines = Vector.empty[String]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class LibraryManager(val depLibs: Seq[ExtLib]) {
val log = LoggerFactory.getLogger(getClass)
val timeout = 60.seconds

//log.debug(s"Libraries: ${depLibs.map(lib => s"${lib.toString}/${lib.jsLibs}/${lib.cssLibs}").mkString("\n")}")
val baseLibs = Seq(
s"/scala-library-${Config.scalaVersion}.jar",
s"/scala-reflect-${Config.scalaVersion}.jar",
Expand Down Expand Up @@ -99,10 +100,10 @@ class LibraryManager(val depLibs: Seq[ExtLib]) {
val results = Task
.gatherUnordered(libs.map { lib =>
val dep = lib match {
case ExtLib(group, artifact, version, false) =>
Dependency(Module(group, artifact + sjsVersion), lib.version, exclusions = exclusions)
case ExtLib(group, artifact, version, true) =>
Dependency(Module(group, s"${artifact}_${Config.scalaMainVersion}"), lib.version, exclusions = exclusions)
case ExtLib(group, artifact, version, false, _, _) =>
Dependency(Module(group, artifact + sjsVersion), version, exclusions = exclusions)
case ExtLib(group, artifact, version, true, _, _) =>
Dependency(Module(group, s"${artifact}_${Config.scalaMainVersion}"), version, exclusions = exclusions)
}
val start = Resolution(Set(dep))
val fetch = Fetch.from(repositories, Cache.fetch())
Expand All @@ -124,7 +125,7 @@ class LibraryManager(val depLibs: Seq[ExtLib]) {

val jars =
Task.gatherUnordered(depArts.map(da => Cache.file(da._2).map(f => (da._1, f.toPath)).run)).unsafePerformSync.collect {
case \/-((dep, path)) if path.toString.endsWith("jar") =>
case \/-((dep, path)) if path.toString.endsWith("jar") && dep.attributes.isEmpty =>
(dep, path.toString, new FileInputStream(path.toFile))
case -\/(error) =>
throw new Exception(s"Unable to load a library: ${error.describe}")
Expand Down Expand Up @@ -218,7 +219,9 @@ class LibraryManager(val depLibs: Seq[ExtLib]) {
}

def compilerLibraries(extLibs: Set[ExtLib]): Seq[AbstractFile] = {
commonLibraries4compiler ++ deps(extLibs).map(dep => dependency4compiler(dep))
val libs = commonLibraries4compiler ++ deps(extLibs).map(dep => dependency4compiler(dep))
log.debug(s"Compiler libraries: ${libs.map(_.path)}")
libs
}

val irCache = new IRFileCache
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class AbstractFlatDir(val path: String, val children: ArrayBuffer[AbstractFile]
}

class AbstractFlatJar(val flatJar: FlatJar, ffs: FlatFileSystem) {
val root = new AbstractFlatDir("", ArrayBuffer.empty)
val root = new AbstractFlatDir(flatJar.name, ArrayBuffer.empty)
val dirs = mutable.HashMap[String, AbstractFlatDir]("" -> root)

build()
Expand Down
2 changes: 1 addition & 1 deletion project/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ object Settings {
val dom = "0.9.4"
val scalatags = "0.6.7"
val async = "0.9.7"
val coursier = "1.0.0-RC11"
val coursier = "1.0.2"
val kamon = "0.6.7"
val base64 = "0.2.4"
}
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
resolvers += "typesafe" at "http://repo.typesafe.com/typesafe/releases/"

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.21")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.22")

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.2")

Expand Down
85 changes: 82 additions & 3 deletions router/src/main/resources/libraries.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,84 @@
[
"org.scala-js %%% scalajs-dom % 0.9.2",
"com.lihaoyi %%% upickle % 0.4.3",
"com.lihaoyi %%% scalatags % 0.6.5"
{
"group": "Web",
"libraries": [
{
"name": "Scala.js React",
"organization": "com.github.japgolly.scalajs-react",
"artifact": "core",
"doc": "japgolly/scalajs-react",
"versions": [
{
"version": "1.2.0",
"scalaVersions": ["2.11", "2.12"],
"jsDeps": [
"react % 16.2.0 % https://unpkg.com/react@16/umd/react.development.js",
"react-dom % 16.2.0 % https://unpkg.com/react-dom@16/umd/react-dom.development.js"
]
},
{
"version": "1.1.1",
"scalaVersions": ["2.11", "2.12"],
"extraDeps": [
"com.github.japgolly.scalajs-react %%% extra % 1.1.1"
],
"jsDeps": [
"react % 15.6.1 % https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-with-addons.min.js",
"react-dom % 15.6.1 % https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"
]
}
],
"compileTimeOnly": false
},
{
"name": "Scala.js React Components",
"organization": "com.olvind",
"artifact": "scalajs-react-components",
"doc": "chandu0101/scalajs-react-components",
"versions": [
{
"version": "1.0.0-M2",
"scalaVersions": ["2.11", "2.12"],
"extraDeps": [
],
"jsDeps": [
"react % 15.6.1 % https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-with-addons.min.js",
"react-dom % 15.6.1 % https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"
],
"cssDeps": [
"semantic-ui % 2.2.4 % https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.12/semantic.min.css"
]
}
],
"compileTimeOnly": false
},
{
"name": "Diode",
"organization": "io.suzaku",
"artifact": "diode",
"doc": "https://diode.suzaku.io",
"versions": [
{
"version": "1.1.3",
"scalaVersions": ["2.11", "2.12"]
},
{
"version": "1.1.2",
"scalaVersions": ["2.11", "2.12"]
},
{
"version": "1.1.0",
"organization": "me.chrons",
"scalaVersions": ["2.11", "2.12"]
},
{
"version": "1.0.0",
"organization": "me.chrons",
"scalaVersions": ["2.11"]
}
],
"compileTimeOnly": false
}
]
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ class CompilerManager extends Actor with ActorLogging {
// check that all libs are supported
val versionLibs = currentLibs.getOrElse(scalaVersion, Set.empty)
log.debug(s"Libraries:\n$versionLibs")
libs.foreach(lib => if (!versionLibs.contains(lib)) throw new IllegalArgumentException(s"Library $lib is not supported"))
libs.foreach(lib =>
if (!versionLibs.exists(_.sameAs(lib))) throw new IllegalArgumentException(s"Library $lib is not supported"))
// select the best available compiler server based on:
// 1) time of last activity
// 2) set of libraries
Expand Down
Loading

0 comments on commit fd5ae23

Please sign in to comment.