@@ -7,18 +7,31 @@ import coursier.util.Task
7
7
import dependency ._
8
8
import org .scalajs .testing .adapter .{TestAdapterInitializer => TAI }
9
9
10
- import java .io .File
10
+ import java .io .{ File , InputStream , OutputStream }
11
11
12
12
import scala .build .EitherCps .{either , value }
13
13
import scala .build .errors .{BuildException , ScalaJsLinkingError }
14
14
import scala .build .internal .Util .{DependencyOps , ModuleOps }
15
15
import scala .build .internal .{ExternalBinaryParams , FetchExternalBinary , Runner , ScalaJsLinkerConfig }
16
16
import scala .build .options .scalajs .ScalaJsLinkerOptions
17
17
import scala .build .{Logger , Positioned }
18
+ import scala .io .Source
18
19
import scala .util .Properties
19
20
20
21
object ScalaJsLinker {
21
22
23
+ case class LinkJSInput (
24
+ options : ScalaJsLinkerOptions ,
25
+ javaCommand : String ,
26
+ classPath : Seq [os.Path ],
27
+ mainClassOrNull : String ,
28
+ addTestInitializer : Boolean ,
29
+ config : ScalaJsLinkerConfig ,
30
+ fullOpt : Boolean ,
31
+ noOpt : Boolean ,
32
+ scalaJsVersion : String
33
+ )
34
+
22
35
private def linkerMainClass = " org.scalajs.cli.Scalajsld"
23
36
24
37
private def linkerCommand (
@@ -98,61 +111,157 @@ object ScalaJsLinker {
98
111
}
99
112
}
100
113
101
- def link (
102
- options : ScalaJsLinkerOptions ,
103
- javaCommand : String ,
104
- classPath : Seq [os.Path ],
105
- mainClassOrNull : String ,
106
- addTestInitializer : Boolean ,
107
- config : ScalaJsLinkerConfig ,
114
+ private def getCommand (
115
+ input : LinkJSInput ,
108
116
linkingDir : os.Path ,
109
- fullOpt : Boolean ,
110
- noOpt : Boolean ,
111
117
logger : Logger ,
112
118
cache : FileCache [Task ],
113
119
archiveCache : ArchiveCache [Task ],
114
- scalaJsVersion : String
115
- ): Either [BuildException , Unit ] = either {
116
-
120
+ useLongRunning : Boolean
121
+ ) = either {
117
122
val command = value {
118
- linkerCommand(options, javaCommand, logger, cache, archiveCache, scalaJsVersion)
123
+ linkerCommand(
124
+ input.options,
125
+ input.javaCommand,
126
+ logger,
127
+ cache,
128
+ archiveCache,
129
+ input.scalaJsVersion
130
+ )
119
131
}
120
132
121
133
val allArgs = {
122
- val outputArgs = Seq (" --outputDir" , linkingDir.toString)
134
+ val outputArgs = Seq (" --outputDir" , linkingDir.toString)
135
+ val longRunning = if (useLongRunning) Seq (" --longRunning" ) else Seq .empty[String ]
123
136
val mainClassArgs =
124
- Option (mainClassOrNull).toSeq.flatMap(mainClass => Seq (" --mainMethod" , mainClass + " .main" ))
137
+ Option (input.mainClassOrNull).toSeq.flatMap(mainClass =>
138
+ Seq (" --mainMethod" , mainClass + " .main" )
139
+ )
125
140
val testInitializerArgs =
126
- if (addTestInitializer)
141
+ if (input. addTestInitializer)
127
142
Seq (" --mainMethodWithNoArgs" , TAI .ModuleClassName + " ." + TAI .MainMethodName )
128
143
else
129
144
Nil
130
145
val optArg =
131
- if (noOpt) " --noOpt"
132
- else if (fullOpt) " --fullOpt"
146
+ if (input. noOpt) " --noOpt"
147
+ else if (input. fullOpt) " --fullOpt"
133
148
else " --fastOpt"
134
149
135
150
Seq [os.Shellable ](
136
151
outputArgs,
137
152
mainClassArgs,
138
153
testInitializerArgs,
139
154
optArg,
140
- config.linkerCliArgs,
141
- classPath.map(_.toString)
155
+ input.config.linkerCliArgs,
156
+ input.classPath.map(_.toString),
157
+ longRunning
142
158
)
143
159
}
144
160
145
- val cmd = command ++ allArgs.flatMap(_.value)
146
- val res = Runner .run(cmd, logger)
147
- val retCode = res.waitFor()
161
+ command ++ allArgs.flatMap(_.value)
162
+ }
163
+
164
+ def link (
165
+ input : LinkJSInput ,
166
+ linkingDir : os.Path ,
167
+ logger : Logger ,
168
+ cache : FileCache [Task ],
169
+ archiveCache : ArchiveCache [Task ]
170
+ ): Either [BuildException , Unit ] = either {
171
+ val useLongRunning = ! input.fullOpt
148
172
149
- if (retCode == 0 )
150
- logger.debug( " Scala.js linker ran successfully " )
173
+ if (useLongRunning )
174
+ longRunningProcess.startOrReuse(input, linkingDir, logger, cache, archiveCache )
151
175
else {
152
- logger.debug(s " Scala.js linker exited with return code $retCode" )
153
- value(Left (new ScalaJsLinkingError ))
176
+ val cmd =
177
+ value(getCommand(input, linkingDir, logger, cache, archiveCache, useLongRunning = false ))
178
+ val res = Runner .run(cmd, logger)
179
+ val retCode = res.waitFor()
180
+
181
+ if (retCode == 0 )
182
+ logger.debug(" Scala.js linker ran successfully" )
183
+ else {
184
+ logger.debug(s " Scala.js linker exited with return code $retCode" )
185
+ value(Left (new ScalaJsLinkingError ))
186
+ }
154
187
}
155
188
}
189
+
190
+ private object longRunningProcess {
191
+ case class Proc (process : Process , stdin : OutputStream , stdout : InputStream ) {
192
+ val stdoutLineIterator : Iterator [String ] = Source .fromInputStream(stdout).getLines()
193
+ }
194
+ case class Input (input : LinkJSInput , linkingDir : os.Path )
195
+ var currentInput : Option [Input ] = None
196
+ var currentProc : Option [Proc ] = None
197
+
198
+ def startOrReuse (
199
+ linkJsInput : LinkJSInput ,
200
+ linkingDir : os.Path ,
201
+ logger : Logger ,
202
+ cache : FileCache [Task ],
203
+ archiveCache : ArchiveCache [Task ]
204
+ ) = either {
205
+ val input = Input (linkJsInput, linkingDir)
206
+
207
+ def createProcess (): Proc = {
208
+ val cmd =
209
+ value(getCommand(
210
+ linkJsInput,
211
+ linkingDir,
212
+ logger,
213
+ cache,
214
+ archiveCache,
215
+ useLongRunning = true
216
+ ))
217
+ val process = Runner .run(cmd, logger, inheritStreams = false )
218
+ val stdin = process.getOutputStream()
219
+ val stdout = process.getInputStream()
220
+ val proc = Proc (process, stdin, stdout)
221
+ currentProc = Some (proc)
222
+ currentInput = Some (input)
223
+ proc
224
+ }
225
+
226
+ def loop (proc : Proc ): Unit =
227
+ if (proc.stdoutLineIterator.hasNext) {
228
+ val line = proc.stdoutLineIterator.next()
229
+
230
+ if (line == " SCALA_JS_LINKING_DONE" )
231
+ logger.debug(" Scala.js linker ran successfully" )
232
+ else {
233
+ // inherit other stdout from Scala.js
234
+ println(line)
235
+
236
+ loop(proc)
237
+ }
238
+ }
239
+ else {
240
+ val retCode = proc.process.waitFor()
241
+ logger.debug(s " Scala.js linker exited with return code $retCode" )
242
+ value(Left (new ScalaJsLinkingError ))
243
+ }
244
+
245
+ val proc = currentProc match {
246
+ case Some (proc) if currentInput.contains(input) && proc.process.isAlive() =>
247
+ // trigger new linking
248
+ proc.stdin.write('\n ' )
249
+ proc.stdin.flush()
250
+
251
+ proc
252
+ case Some (proc) =>
253
+ proc.stdin.close()
254
+ proc.stdout.close()
255
+ proc.process.destroy()
256
+ createProcess()
257
+ case _ =>
258
+ createProcess()
259
+ }
260
+
261
+ loop(proc)
262
+ }
263
+ }
264
+
156
265
def updateSourceMappingURL (mainJsPath : os.Path ) =
157
266
val content = os.read(mainJsPath)
158
267
content.replace(
0 commit comments