summaryrefslogtreecommitdiff
path: root/examples/scala-js/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala
blob: 68571421c19678ed32a0be0b9e304a033c8a3bba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
package scala.tools.nsc

/* Super hacky overriding of the MainGenericRunner used by partest */

import scala.scalajs.ir

import scala.scalajs.tools.sem.Semantics
import scala.scalajs.tools.classpath._
import scala.scalajs.tools.classpath.builder._
import scala.scalajs.tools.logging._
import scala.scalajs.tools.io._
import scala.scalajs.tools.optimizer.ScalaJSOptimizer
import scala.scalajs.tools.optimizer.ScalaJSClosureOptimizer
import scala.scalajs.tools.optimizer.ParIncOptimizer
import scala.scalajs.tools.env.JSConsole

import scala.scalajs.sbtplugin.env.rhino.RhinoJSEnv
import scala.scalajs.sbtplugin.env.nodejs.NodeJSEnv
import scala.scalajs.sbtplugin.JSUtils._

import scala.tools.partest.scalajs.ScalaJSPartestOptions._

import java.io.File
import scala.io.Source

import Properties.{ versionString, copyrightString }
import GenericRunnerCommand._

class ScalaConsoleJSConsole extends JSConsole {
  def log(msg: Any) = scala.Console.out.println(msg.toString)
}

class MainGenericRunner {
  def errorFn(ex: Throwable): Boolean = {
    ex.printStackTrace()
    false
  }
  def errorFn(str: String): Boolean = {
    scala.Console.err println str
    false
  }

  val optMode = OptMode.fromId(sys.props("scalajs.partest.optMode"))

  def noWarnMissing = {
    import ScalaJSOptimizer._

    for {
      fname <- sys.props.get("scalajs.partest.noWarnFile").toList
      line  <- Source.fromFile(fname).getLines
      if !line.startsWith("#")
    } yield line.split('.') match {
      case Array(className) =>             NoWarnClass(className)
      case Array(className, methodName) => NoWarnMethod(className, methodName)
    }
  }

  def readSemantics() = {
    val opt = sys.props.get("scalajs.partest.compliantSems")
    opt.fold(Semantics.Defaults) { str =>
      val sems = str.split(',')
      Semantics.compliantTo(sems.toList)
    }
  }

  def process(args: Array[String]): Boolean = {
    val command = new GenericRunnerCommand(args.toList, (x: String) => errorFn(x))
    import command.{ settings, howToRun, thingToRun }

    if (!command.ok) return errorFn("\n" + command.shortUsageMsg)
    else if (settings.version) return errorFn("Scala code runner %s -- %s".format(versionString, copyrightString))
    else if (command.shouldStopWithInfo) return errorFn("shouldStopWithInfo")

    if (howToRun != AsObject)
      return errorFn("Scala.js runner can only run an object")

    // Load basic Scala.js classpath (used for running or further packaging)
    val usefulClasspathEntries = for {
      url <- settings.classpathURLs
      f = urlToFile(url)
      if (f.isDirectory || f.getName.startsWith("scalajs-library"))
    } yield f
    val classpath =
      PartialClasspathBuilder.build(usefulClasspathEntries).resolve()

    val logger    = new ScalaConsoleLogger(Level.Warn)
    val jsConsole = new ScalaConsoleJSConsole

    val mainObjName = ir.Definitions.encodeClassName(thingToRun)
    val baseRunner  = runnerJSFile(mainObjName, command.arguments)
    val semantics   = readSemantics()

    def fastOpted = fastOptimize(classpath, mainObjName, logger, semantics)
    def fullOpted = fullOptimize(classpath, mainObjName, logger,
      baseRunner, semantics.optimized)

    val runner = {
      if (optMode == FullOpt)
        fullOptRunner()
      else
        baseRunner
    }

    val env =
      if (optMode == NoOpt) new RhinoJSEnv(semantics)
      else new NodeJSEnv

    val runClasspath = optMode match {
      case NoOpt         => classpath
      case FastOpt       => fastOpted
      case FullOpt       => fullOpted
    }

    env.jsRunner(runClasspath, runner, logger, jsConsole).run()

    true
  }

  private def runnerJSFile(mainObj: String, args: List[String]) = {
    val jsObj = "ScalaJS.m." + mainObj
    val jsArgs = argArray(args)
    new MemVirtualJSFile("Generated launcher file").
      withContent(s"$jsObj().main__AT__V($jsArgs);")
  }

  /** constructs a scala.Array[String] with the given elements */
  private def argArray(args: List[String]) = {
    s"""ScalaJS.makeNativeArrayWrapper(
          ScalaJS.d.T.getArrayOf(),
          ${listToJS(args)})"""
  }

  private def fastOptimize(
      classpath: IRClasspath,
      mainObjName: String,
      logger: Logger,
      semantics: Semantics) = {
    import ScalaJSOptimizer._

    val optimizer = newScalaJSOptimizer(semantics)
    val output = WritableMemVirtualJSFile("partest fastOpt file")

    optimizer.optimizeCP(
        Inputs(classpath,
            manuallyReachable = fastOptReachable(mainObjName),
            noWarnMissing = noWarnMissing
        ),
        OutputConfig(
            output        = output,
            wantSourceMap = false,
            checkIR       = true
            ),
        logger)
  }

  private def fastOptReachable(mainObjName: String) = {
    import ScalaJSOptimizer._
    List(
      ReachObject(mainObjName),
      ReachMethod(mainObjName + '$', "main__AT__V", static = false)
    )
  }

  private def fullOptimize(
      classpath: IRClasspath,
      mainObjName: String,
      logger: Logger,
      runner: VirtualJSFile,
      semantics: Semantics) = {
    import ScalaJSClosureOptimizer._

    val fastOptimizer = newScalaJSOptimizer(semantics)
    val fullOptimizer = new ScalaJSClosureOptimizer(semantics)
    val output = WritableMemVirtualJSFile("partest fullOpt file")
    val exportFile = fullOptExportFile(runner)

    fullOptimizer.optimizeCP(fastOptimizer, Inputs(
      input = ScalaJSOptimizer.Inputs(
        classpath,
        manuallyReachable = fastOptReachable(mainObjName),
        noWarnMissing = noWarnMissing),
      additionalExports = exportFile :: Nil),
      OutputConfig(
        output,
        checkIR = true,
        wantSourceMap = false),
    logger)

  }

  private def newScalaJSOptimizer(semantics: Semantics) =
    new ScalaJSOptimizer(semantics, new ParIncOptimizer(_))

  /** generates an exporter statement for the google closure compiler that runs
   *  what the normal test would
   */
  private def fullOptExportFile(runnerFile: VirtualJSFile) = {
    new MemVirtualJSFile("partest fullOpt exports").withContent(
      s"""this["runFullOptPartest"] = function() { ${runnerFile.content} };"""
    )
  }

  private def fullOptRunner() = new MemVirtualJSFile("partest fullOpt runner").
    withContent("runFullOptPartest();")

  private def urlToFile(url: java.net.URL) = {
    try {
      new File(url.toURI())
    } catch {
      case e: java.net.URISyntaxException => new File(url.getPath())
    }
  }
}

object MainGenericRunner extends MainGenericRunner {
  def main(args: Array[String]) {
    if (!process(args))
      sys.exit(1)
  }
}