Skip to content

Commit 82d6087

Browse files
author
Semion Sidorenko
committed
Add support for scala.js
1 parent 5d0c924 commit 82d6087

File tree

16 files changed

+624
-29
lines changed

16 files changed

+624
-29
lines changed

project/Scoverage.scala

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import sbt._
33
import sbtrelease.ReleasePlugin
44
import sbtrelease.ReleasePlugin.ReleaseKeys
55
import com.typesafe.sbt.pgp.PgpKeys
6+
import org.scalajs.sbtplugin.cross.CrossProject
7+
import org.scalajs.sbtplugin.cross.CrossType
8+
import org.scalajs.sbtplugin.ScalaJSPlugin
9+
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
610

711
object Scoverage extends Build {
812

913
val Org = "org.scoverage"
1014
val Scala = "2.11.4"
1115
val MockitoVersion = "1.9.5"
12-
val ScalatestVersion = "2.2.2"
16+
val ScalatestVersion = "3.0.0-SNAP5"
1317

1418
lazy val LocalTest = config("local") extend Test
1519

@@ -25,10 +29,7 @@ object Scoverage extends Build {
2529
resolvers := ("releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2") +: resolvers.value,
2630
concurrentRestrictions in Global += Tags.limit(Tags.Test, 1),
2731
javacOptions := Seq("-source", "1.6", "-target", "1.6"),
28-
libraryDependencies ++= Seq(
29-
"org.mockito" % "mockito-all" % MockitoVersion % "test",
30-
"org.scalatest" %% "scalatest" % ScalatestVersion % "test"
31-
),
32+
javaOptions += "-XX:MaxMetaspaceSize=2048m",
3233
publishTo <<= version {
3334
(v: String) =>
3435
val nexus = "https://oss.sonatype.org/"
@@ -70,17 +71,30 @@ object Scoverage extends Build {
7071
.settings(name := "scalac-scoverage")
7172
.settings(appSettings: _*)
7273
.settings(publishArtifact := false)
73-
.aggregate(plugin, runtime)
74+
.aggregate(plugin, runtime.jvm, runtime.js)
7475

75-
lazy val runtime = Project("scalac-scoverage-runtime", file("scalac-scoverage-runtime"))
76+
lazy val runtime = CrossProject("scalac-scoverage-runtime", file("scalac-scoverage-runtime"), CrossType.Full)
7677
.settings(name := "scalac-scoverage-runtime")
7778
.settings(appSettings: _*)
79+
.jvmSettings(libraryDependencies ++= Seq(
80+
"org.mockito" % "mockito-all" % MockitoVersion % "test",
81+
"org.scalatest" %% "scalatest" % ScalatestVersion % "test"
82+
))
83+
.jsSettings(
84+
libraryDependencies += "org.scalatest" %%% "scalatest" % ScalatestVersion,
85+
scalaJSStage := FastOptStage
86+
)
87+
88+
lazy val `scalac-scoverage-runtimeJVM` = runtime.jvm
89+
lazy val `scalac-scoverage-runtimeJS` = runtime.js
7890

7991
lazy val plugin = Project("scalac-scoverage-plugin", file("scalac-scoverage-plugin"))
80-
.dependsOn(runtime % "test")
92+
.dependsOn(`scalac-scoverage-runtimeJVM` % "test")
8193
.settings(name := "scalac-scoverage-plugin")
8294
.settings(appSettings: _*)
8395
.settings(libraryDependencies ++= Seq(
96+
"org.mockito" % "mockito-all" % MockitoVersion % "test",
97+
"org.scalatest" %% "scalatest" % ScalatestVersion % "test",
8498
"org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided",
8599
"org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided",
86100
"org.joda" % "joda-convert" % "1.6" % "test",
@@ -96,4 +110,4 @@ object Scoverage extends Build {
96110
Nil
97111
}
98112
})
99-
}
113+
}

project/ScoveragePhantomJSEnv.scala

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import org.scalajs.jsenv.phantomjs._
2+
import org.scalajs.jsenv._
3+
4+
import org.scalajs.core.tools.classpath._
5+
import org.scalajs.core.tools.io._
6+
import org.scalajs.core.tools.logging._
7+
8+
import org.scalajs.core.ir.Utils.{escapeJS, fixFileURI}
9+
10+
import java.io._
11+
12+
class ScoveragePhantomJSEnv (
13+
phantomjsPath: String = "phantomjs",
14+
addArgs: Seq[String] = Seq.empty,
15+
addEnv: Map[String, String] = Map.empty,
16+
override val autoExit: Boolean = true,
17+
jettyClassLoader: ClassLoader = null
18+
) extends PhantomJSEnv (phantomjsPath, addArgs, addEnv, autoExit, jettyClassLoader) {
19+
private[ScoveragePhantomJSEnv] trait WebsocketListener {
20+
def onRunning(): Unit
21+
def onOpen(): Unit
22+
def onClose(): Unit
23+
def onMessage(msg: String): Unit
24+
25+
def log(msg: String): Unit
26+
}
27+
override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile,
28+
logger: Logger, console: JSConsole): JSRunner = {
29+
new ScoveragePhantomRunner(classpath, code, logger, console)
30+
}
31+
32+
override def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile,
33+
logger: Logger, console: JSConsole): AsyncJSRunner = {
34+
new AsyncScoveragePhantomRunner(classpath, code, logger, console)
35+
}
36+
37+
override def comRunner(classpath: CompleteClasspath, code: VirtualJSFile,
38+
logger: Logger, console: JSConsole): ComJSRunner = {
39+
new ComScoveragePhantomRunner(classpath, code, logger, console)
40+
}
41+
42+
43+
protected class ScoveragePhantomRunner(classpath: CompleteClasspath,
44+
code: VirtualJSFile, logger: Logger, console: JSConsole
45+
) extends ExtRunner(classpath, code, logger, console)
46+
with AbstractScoveragePhantomRunner
47+
48+
protected class AsyncScoveragePhantomRunner(classpath: CompleteClasspath,
49+
code: VirtualJSFile, logger: Logger, console: JSConsole
50+
) extends AsyncExtRunner(classpath, code, logger, console)
51+
with AbstractScoveragePhantomRunner
52+
53+
protected class ComScoveragePhantomRunner(classpath: CompleteClasspath,
54+
code: VirtualJSFile, logger: Logger, console: JSConsole
55+
) extends ComPhantomRunner(classpath, code, logger, console)
56+
with ComJSRunner with AbstractScoveragePhantomRunner with WebsocketListener
57+
58+
protected trait AbstractScoveragePhantomRunner extends AbstractPhantomRunner {
59+
override protected def createTmpLauncherFile(): File = {
60+
val webF = createTmpWebpage()
61+
62+
val launcherTmpF = File.createTempFile("phantomjs-launcher", ".js")
63+
launcherTmpF.deleteOnExit()
64+
65+
val out = new FileWriter(launcherTmpF)
66+
67+
try {
68+
out.write(
69+
s"""// Scala.js Phantom.js launcher
70+
|var page = require('webpage').create();
71+
|var fs = require('fs');
72+
|var url = "${escapeJS(fixFileURI(webF.toURI).toASCIIString)}";
73+
|var autoExit = $autoExit;
74+
|page.onConsoleMessage = function(msg) {
75+
| console.log(msg);
76+
|};
77+
|page.onError = function(msg, trace) {
78+
| console.error(msg);
79+
| if (trace && trace.length) {
80+
| console.error('');
81+
| trace.forEach(function(t) {
82+
| console.error(' ' + t.file + ':' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
83+
| });
84+
| }
85+
|
86+
| phantom.exit(2);
87+
|};
88+
|page.onCallback = function(data) {
89+
| if (!data.action) {
90+
| console.error('Called callback without action');
91+
| phantom.exit(3);
92+
| } else if (data.action === 'exit') {
93+
| phantom.exit(data.returnValue || 0);
94+
| } else if (data.action === 'setAutoExit') {
95+
| if (typeof(data.autoExit) === 'boolean')
96+
| autoExit = data.autoExit;
97+
| else
98+
| autoExit = true;
99+
| } else if (data.action == 'require.fs') {
100+
| if(data.method == 'separator') {
101+
| return JSON.stringify(fs.separator);
102+
| } else {
103+
| var ret = fs[data.method].apply(this, data.args);
104+
| return JSON.stringify(ret);
105+
| }
106+
| } else {
107+
| console.error('Unknown callback action ' + data.action);
108+
| phantom.exit(4);
109+
| }
110+
|};
111+
|page.open(url, function (status) {
112+
| if (autoExit || status !== 'success')
113+
| phantom.exit(status !== 'success');
114+
|});
115+
|""".stripMargin)
116+
} finally {
117+
out.close()
118+
}
119+
120+
logger.debug(
121+
"PhantomJS using launcher at: " + launcherTmpF.getAbsolutePath())
122+
123+
launcherTmpF
124+
}
125+
}
126+
}
127+
128+
object ScoveragePhantomJSEnv extends PhantomJSEnv {
129+
}
130+
131+

project/plugins.sbt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.3.2")
55
addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3")
66

77
addSbtPlugin("com.github.gseitz" % "sbt-release" % "0.8.5")
8+
9+
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.3")

scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageCompiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ object ScoverageCompiler {
5151
dir
5252
}
5353

54-
private def runtimeClasses: File = new File("./scalac-scoverage-runtime/target/scala-2.11/classes")
54+
private def runtimeClasses: File = new File("./scalac-scoverage-runtime/shared/target/scala-2.11/classes")
5555

5656
private def findScalaJar(artifactId: String): File = findIvyJar("org.scala-lang", artifactId, ScalaVersion)
5757

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package scalajssupport
2+
3+
import scala.scalajs.js
4+
import js.Dynamic.{ global => g }
5+
6+
/**
7+
* This wraps RhinoFile, NodeFile, or PhantomFile depending on which javascript
8+
* environment is being used, and emulates a subset of the java.io.File API.
9+
*/
10+
class File(path: String) {
11+
import File._
12+
13+
val _file = jsFile(path)
14+
15+
def this(path: String, child: String) = {
16+
this(File.pathJoin(path, child))
17+
}
18+
19+
def delete(): Unit = {
20+
_file.delete()
21+
}
22+
def getAbsolutePath(): String = {
23+
_file.getAbsolutePath()
24+
}
25+
26+
def getName(): String = {
27+
_file.getName()
28+
}
29+
30+
def getPath(): String = {
31+
_file.getPath()
32+
}
33+
34+
def isDirectory(): Boolean = {
35+
_file.isDirectory()
36+
}
37+
38+
def mkdirs(): Unit = {
39+
_file.mkdirs()
40+
}
41+
42+
def listFiles(): Array[File] = {
43+
_file.listFiles().toArray
44+
}
45+
46+
def listFiles(filter: FileFilter): Array[File] = {
47+
_file.listFiles().filter(filter.accept).toArray
48+
}
49+
50+
def readFile(): String = {
51+
_file.readFile()
52+
}
53+
54+
override def toString: String = {
55+
getPath()
56+
}
57+
}
58+
59+
object File {
60+
val jsFile: JsFileObject = if (js.Dynamic.global.hasOwnProperty("Packages").asInstanceOf[Boolean])
61+
RhinoFile
62+
else if (!js.Dynamic.global.hasOwnProperty("window").asInstanceOf[Boolean])
63+
NodeFile
64+
else
65+
PhantomFile
66+
// Factorize this
67+
68+
def pathJoin(path: String, child: String): String =
69+
jsFile.pathJoin(path, child)
70+
71+
def write(path: String, data: String, mode: String = "a") =
72+
jsFile.write(path, data, mode)
73+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package scalajssupport
2+
3+
/**
4+
* Emulates a subset of the java.io.FileWriter API required for scoverage to work.
5+
*/
6+
class FileWriter(file: File, append: Boolean) {
7+
def this(file: File) = this(file, false)
8+
def this(file: String) = this(new File(file), false)
9+
def this(file: String, append: Boolean) = this(new File(file), append)
10+
11+
def append(csq: CharSequence) = {
12+
File.write(file.getPath, csq.toString)
13+
this
14+
}
15+
16+
def close(): Unit = {
17+
// do nothing as we don't open a FD to the file, as phantomJS does not use FDs
18+
}
19+
20+
override def finalize(): Unit = close()
21+
22+
def flush() = {}
23+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package scalajssupport
2+
3+
trait JsFile {
4+
def delete(): Unit
5+
def getAbsolutePath(): String
6+
7+
def getName(): String
8+
9+
def getPath(): String
10+
11+
def isDirectory(): Boolean
12+
13+
def mkdirs(): Unit
14+
15+
def listFiles(): Array[File]
16+
17+
def listFiles(filter: FileFilter): Array[File] = {
18+
listFiles().filter(filter.accept)
19+
}
20+
21+
def readFile(): String
22+
}
23+
24+
trait FileFilter {
25+
def accept(file: File): Boolean
26+
}
27+
28+
trait JsFileObject {
29+
def write(path: String, data: String, mode: String = "a")
30+
def pathJoin(path: String, child: String): String
31+
def apply(path: String): JsFile
32+
}

0 commit comments

Comments
 (0)