Skip to content

Commit f138e4c

Browse files
author
v.karamyshev
committed
Review fixes: remove runtime download and unsupported standalone runtimes
- Move --wasm flag to dedicated Wasm help group with --help-wasm option - Simplify wasmOptions parsing with fold/toRight pattern - Add runtime validation with UnrecognizedWasmRuntimeError in directives - Auto-enable WASM when wasmRuntime directive is set - Update reference documentation
1 parent 039e6e1 commit f138e4c

File tree

19 files changed

+247
-359
lines changed

19 files changed

+247
-359
lines changed

build.mill

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,8 +520,6 @@ trait Core extends ScalaCliCrossSbtModule
520520
| def toolkitVersionForNative04 = "${Deps.toolkitVersionForNative04}"
521521
| def toolkitVersionForNative05 = "${Deps.toolkitVersionForNative05}"
522522
|
523-
| def defaultDenoVersion = "2.1.4"
524-
|
525523
| def typelevelOrganization = "${Deps.typelevelToolkit.dep.module.organization.value}"
526524
| def typelevelToolkitDefaultVersion = "${Deps.typelevelToolkitVersion}"
527525
| def typelevelToolkitMaxScalaNative = "${Deps.Versions.maxScalaNativeForTypelevelToolkit}"

modules/build/src/main/scala/scala/build/internal/Runner.scala

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -377,10 +377,9 @@ object Runner {
377377

378378
def denoCommand(
379379
entrypoint: File,
380-
args: Seq[String],
381-
denoPathOpt: Option[String] = None
380+
args: Seq[String]
382381
): Seq[String] = {
383-
val denoPath = denoPathOpt.getOrElse(findInPath("deno").fold("deno")(_.toString))
382+
val denoPath = findInPath("deno").fold("deno")(_.toString)
384383
val denoFlags = Seq("run", "--allow-read")
385384
Seq(denoPath) ++ denoFlags ++ Seq(entrypoint.getAbsolutePath) ++ args
386385
}
@@ -390,14 +389,12 @@ object Runner {
390389
args: Seq[String],
391390
logger: Logger,
392391
allowExecve: Boolean = false,
393-
emitWasm: Boolean = false,
394-
denoPathOpt: Option[String] = None
392+
emitWasm: Boolean = false
395393
): Either[BuildException, Process] = either {
396-
val denoPath: String = denoPathOpt.getOrElse {
394+
val denoPath: String =
397395
value(findInPath("deno")
398396
.map(_.toString)
399397
.toRight(DenoNotFoundError()))
400-
}
401398
val denoFlags = Seq("run", "--allow-read")
402399
val extraEnv =
403400
if (emitWasm && denoNeedsWasmFlag) Map("DENO_V8_FLAGS" -> "--experimental-wasm-exnref")

modules/cli/src/main/scala/scala/cli/commands/run/Run.scala

Lines changed: 54 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import java.util.concurrent.atomic.AtomicReference
1212
import scala.build.*
1313
import scala.build.EitherCps.{either, value}
1414
import scala.build.Ops.*
15-
import scala.build.errors.{BuildException, CompositeBuildException, UnsupportedWasmRuntimeError}
15+
import scala.build.errors.{BuildException, CompositeBuildException}
1616
import scala.build.input.*
1717
import scala.build.internal.{Constants, Runner, ScalaJsLinkerConfig}
1818
import scala.build.internals.ConsoleUtils.ScalaCliConsole
@@ -28,7 +28,7 @@ import scala.cli.commands.util.BuildCommandHelpers.*
2828
import scala.cli.commands.util.{BuildCommandHelpers, RunHadoop, RunSpark}
2929
import scala.cli.commands.{CommandUtils, ScalaCommand, SpecificationLevel, WatchUtil}
3030
import scala.cli.config.Keys
31-
import scala.cli.internal.{ProcUtil, WasmRuntimeDownloader}
31+
import scala.cli.internal.ProcUtil
3232
import scala.cli.packaging.Library.fullClassPathMaybeAsJar
3333
import scala.cli.util.ArgHelpers.*
3434
import scala.cli.util.ConfigDbUtils
@@ -478,101 +478,66 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
478478

479479
// Check if WASM mode is requested
480480
if wasmOpts.enabled then {
481-
val runtime = wasmOpts.runtime
482-
483-
if runtime.isJsBased then {
484-
// JS-based WASM path - uses Scala.js WASM with JavaScript helpers (Node.js or Deno)
485-
val esModule = true // WASM backend uses ES modules
486-
scratchDirOpt.foreach(os.makeDir.all(_))
487-
val jsDest = os.temp(
488-
dir = scratchDirOpt.orNull,
489-
prefix = "main",
490-
suffix = ".mjs",
491-
deleteOnExit = scratchDirOpt.isEmpty
492-
)
493-
494-
// Resolve Deno binary: check PATH first, download if needed
495-
val denoPathOpt: Option[String] = runtime match {
496-
case WasmRuntime.Deno =>
497-
val denoCmd = value(WasmRuntimeDownloader.denoCommand(
498-
wasmOpts.finalDenoVersion,
499-
build.options.archiveCache,
500-
logger
501-
))
502-
Some(denoCmd.head)
503-
case _ => None
504-
}
481+
val runtime = wasmOpts.runtime
482+
val esModule = true // WASM backend uses ES modules
483+
scratchDirOpt.foreach(os.makeDir.all(_))
484+
val jsDest = os.temp(
485+
dir = scratchDirOpt.orNull,
486+
prefix = "main",
487+
suffix = ".mjs",
488+
deleteOnExit = scratchDirOpt.isEmpty
489+
)
505490

506-
val linkerConfig = build.options.scalaJsOptions.linkerConfig(logger)
507-
.copy(emitWasm = true, moduleKind = ScalaJsLinkerConfig.ModuleKind.ESModule)
508-
509-
val res = Package.linkJs(
510-
builds = builds,
511-
dest = jsDest,
512-
mainClassOpt = Some(mainClass),
513-
addTestInitializer = false,
514-
config = linkerConfig,
515-
fullOpt = value(build.options.scalaJsOptions.fullOpt),
516-
noOpt = build.options.scalaJsOptions.noOpt.getOrElse(false),
517-
logger = logger,
518-
scratchDirOpt = scratchDirOpt
519-
).map { outputPath =>
520-
if showCommand then
491+
val linkerConfig = build.options.scalaJsOptions.linkerConfig(logger)
492+
.copy(emitWasm = true, moduleKind = ScalaJsLinkerConfig.ModuleKind.ESModule)
493+
494+
val res = Package.linkJs(
495+
builds = builds,
496+
dest = jsDest,
497+
mainClassOpt = Some(mainClass),
498+
addTestInitializer = false,
499+
config = linkerConfig,
500+
fullOpt = value(build.options.scalaJsOptions.fullOpt),
501+
noOpt = build.options.scalaJsOptions.noOpt.getOrElse(false),
502+
logger = logger,
503+
scratchDirOpt = scratchDirOpt
504+
).map { outputPath =>
505+
if showCommand then
506+
runtime match {
507+
case WasmRuntime.Deno =>
508+
Left(Runner.denoCommand(outputPath.toIO, args))
509+
case _ =>
510+
Left(Runner.jsCommand(outputPath.toIO, args, jsDom = false, emitWasm = true))
511+
}
512+
else {
513+
val process = value {
521514
runtime match {
522515
case WasmRuntime.Deno =>
523-
Left(Runner.denoCommand(outputPath.toIO, args, denoPathOpt = denoPathOpt))
516+
Runner.runDeno(
517+
outputPath.toIO,
518+
args,
519+
logger,
520+
allowExecve = effectiveAllowExecve,
521+
emitWasm = true
522+
)
524523
case _ =>
525-
Left(Runner.jsCommand(outputPath.toIO, args, jsDom = false, emitWasm = true))
526-
}
527-
else {
528-
val process = value {
529-
runtime match {
530-
case WasmRuntime.Deno =>
531-
Runner.runDeno(
532-
outputPath.toIO,
533-
args,
534-
logger,
535-
allowExecve = effectiveAllowExecve,
536-
emitWasm = true,
537-
denoPathOpt = denoPathOpt
538-
)
539-
case _ =>
540-
Runner.runJs(
541-
outputPath.toIO,
542-
args,
543-
logger,
544-
allowExecve = effectiveAllowExecve,
545-
jsDom = false,
546-
sourceMap = build.options.scalaJsOptions.emitSourceMaps,
547-
esModule = esModule,
548-
emitWasm = true
549-
)
550-
}
524+
Runner.runJs(
525+
outputPath.toIO,
526+
args,
527+
logger,
528+
allowExecve = effectiveAllowExecve,
529+
jsDom = false,
530+
sourceMap = build.options.scalaJsOptions.emitSourceMaps,
531+
esModule = esModule,
532+
emitWasm = true
533+
)
551534
}
552-
process.onExit().thenApply(_ => if os.exists(jsDest) then os.remove(jsDest))
553-
Right((process, None))
554535
}
536+
process.onExit().thenApply(_ => if os.exists(jsDest) then os.remove(jsDest))
537+
Right((process, None))
555538
}
556-
value(res)
557-
}
558-
else {
559-
// Standalone WASM runtimes - not yet supported.
560-
// Scala.js currently produces JS-dependent WASM output.
561-
// Standalone support requires upstream Scala.js changes (scala-js/scala-js#4991).
562-
val runtimeName = runtime.name
563-
val extraNote = runtime match {
564-
case WasmRuntime.Wasmer =>
565-
" Note: Wasmer does not yet support WasmGC, which is required for Scala WASM output."
566-
case _ => ""
567-
}
568-
value(Left(new UnsupportedWasmRuntimeError(
569-
s"Standalone WASM runtime '$runtimeName' is not yet supported." +
570-
s"$extraNote" +
571-
" Scala.js currently produces JavaScript-dependent WASM output." +
572-
" Standalone WASM support is tracked at: https://github.com/scala-js/scala-js/issues/4991" +
573-
" Use --wasm-runtime node (default) or --wasm-runtime deno for JS-based WASM execution."
574-
)))
575539
}
540+
value(res)
576541
}
577542
else
578543
build.options.platform.value match {

modules/cli/src/main/scala/scala/cli/commands/shared/HelpGroupOptions.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,13 @@ case class HelpGroupOptions(
4949
@Name("fmtHelp")
5050
@Tag(tags.implementation)
5151
@Tag(tags.inShortHelp)
52-
helpScalafmt: Boolean = false
52+
helpScalafmt: Boolean = false,
53+
@Group(HelpGroup.Help.toString)
54+
@HelpMessage("Show options for WebAssembly")
55+
@Name("wasmHelp")
56+
@Tag(tags.implementation)
57+
@Tag(tags.inShortHelp)
58+
helpWasm: Boolean = false
5359
) {
5460

5561
private def printHelpWithGroup(help: Help[?], helpFormat: HelpFormat, group: String): Nothing = {
@@ -68,6 +74,7 @@ case class HelpGroupOptions(
6874
def maybePrintGroupHelp(help: Help[?], helpFormat: HelpFormat): Unit = {
6975
if (helpJs) printHelpWithGroup(help, helpFormat, HelpGroup.ScalaJs.toString)
7076
else if (helpNative) printHelpWithGroup(help, helpFormat, HelpGroup.ScalaNative.toString)
77+
else if (helpWasm) printHelpWithGroup(help, helpFormat, HelpGroup.Wasm.toString)
7178
}
7279
}
7380

modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -285,13 +285,25 @@ final case class SharedOptions(
285285
)
286286
}
287287

288-
private def buildWasmOptions(opts: WasmOptions): options.WasmOptions = {
288+
private def buildWasmOptions(
289+
opts: WasmOptions
290+
): Either[BuildException, options.WasmOptions] = {
289291
import opts._
290-
options.WasmOptions(
291-
enabled = wasm,
292-
runtime =
293-
wasmRuntime.flatMap(options.WasmRuntime.parse).getOrElse(options.WasmRuntime.default),
294-
denoVersion = denoVersion
292+
val wasmEnabled = wasm || wasmRuntime.isDefined
293+
val parsedRuntime = wasmRuntime.fold(Right(options.WasmRuntime.default): Either[
294+
BuildException,
295+
options.WasmRuntime
296+
]) { rt =>
297+
options.WasmRuntime.parse(rt).toRight {
298+
val validValues = options.WasmRuntime.all.map(_.name).mkString(", ")
299+
new scala.build.errors.UnrecognizedWasmRuntimeError(rt, validValues)
300+
}
301+
}
302+
parsedRuntime.map(runtime =>
303+
options.WasmOptions(
304+
enabled = wasmEnabled,
305+
runtime = runtime
306+
)
295307
)
296308
}
297309

@@ -320,7 +332,7 @@ final case class SharedOptions(
320332
}
321333
val parsedPlatform = platform.map(Platform.normalize).flatMap(Platform.parse)
322334
// WASM mode requires Scala.js platform for compilation
323-
val wasmEnabled = wasmOptions.wasm
335+
val wasmEnabled = wasmOptions.wasm || wasmOptions.wasmRuntime.isDefined
324336
val platformOpt = value {
325337
(parsedPlatform, js.js, native.native, wasmEnabled) match {
326338
case (Some(p: Platform.JS.type), _, false, _) => Right(Some(p))
@@ -426,7 +438,7 @@ final case class SharedOptions(
426438
),
427439
scalaJsOptions = scalaJsOptions(js),
428440
scalaNativeOptions = snOpts,
429-
wasmOptions = buildWasmOptions(wasmOptions),
441+
wasmOptions = value(buildWasmOptions(wasmOptions)),
430442
javaOptions = value(scala.cli.commands.util.JvmUtils.javaOptions(jvm)),
431443
jmhOptions = scala.build.options.JmhOptions(
432444
jmhVersion = benchmarking.jmhVersion,

modules/cli/src/main/scala/scala/cli/commands/shared/WasmOptions.scala

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,15 @@ import scala.cli.commands.tags
88

99
// format: off
1010
final case class WasmOptions(
11-
@Group(HelpGroup.Scala.toString)
11+
@Group(HelpGroup.Wasm.toString)
1212
@Tag(tags.experimental)
1313
@HelpMessage("Enable WebAssembly output (Scala.js WASM backend). Uses Node.js by default. To show more options for WASM pass `--help-wasm`")
1414
wasm: Boolean = false,
1515

1616
@Group(HelpGroup.Wasm.toString)
1717
@Tag(tags.experimental)
18-
@HelpMessage("WASM runtime to use: node (default), deno. Standalone runtimes (wasmtime, wasmedge) planned for future releases.")
19-
wasmRuntime: Option[String] = None,
20-
21-
@Group(HelpGroup.Wasm.toString)
22-
@Tag(tags.experimental)
23-
@HelpMessage("Version of Deno to use. If Deno is not found on PATH, it will be downloaded automatically.")
24-
denoVersion: Option[String] = None
18+
@HelpMessage("WASM runtime to use: node (default), deno")
19+
wasmRuntime: Option[String] = None
2520
)
2621
// format: on
2722

0 commit comments

Comments
 (0)