Skip to content

Need an option for writing scripts that are compatible across all scala3 versions #3473

Closed
@philwalk

Description

@philwalk

This is only concerned with a way to migrate scala 3 versions to scala 3.5+ and above. An alternate title for this might be:

Provide a way to execute scripts with ".sc" extension as ".scala" files

Migrating a project with many legacy scripts requires that when bugs are encountered in production, a script can be reverted by editing the hash-bang line, and without being renamed. Also, some scripts cannot be renamed, even after the migration period, so the scala_legacy script isn't a universal option.

The need is for a way to optionally treat some .sc scripts as if they have the .scala filename extension.

Prior to scala 3.5 all scripts require a main method, so a migration format that can easily switch between old and new script semantics requires a main method.

The primary challenge is that after 3.5, the main method is not automatically called unless the file is renamed. In other words, the main method is never called if a script has the .sc extension.

Example hypothetical migration format (almost adequate, but has non-portable element on line 5 Main.main(args).

#!/usr/bin/env -S scala-cli shebang
//> using scala 3.4.3
//> using dep "com.lihaoyi::os-lib::0.11.3"
import os._
Main.main(args) // required by `3.5+`, rejected by earlier scala versions
object Main {
  def main(args: Array[String]): Unit =
    val root1 = os.root("C:\\")
    printf("cRoot: %s\n", root1)
}

Describe the solution you'd like
Possible approaches to specifying the proposed .scala mode option:

  • scala-cli command line, similar to --main-class (maybe --main-method)
  • system property ( "scala.dot_scala_extension")
  • alternate supported extension such as .sc_ or similar, treated as if .scala
  • comment //> using scala-extension-semantics

Describe alternatives you've considered

Adding support for a new filename extension .sc_ turned out to be pretty simple, and seems to work nicely, although it doesn't address the case of scripts that cannot easily be renamed. Here's the implementation:

# git diff
diff --git a/modules/build/src/main/scala/scala/build/input/Inputs.scala b/modules/build/src/main/scala/scala/build/input/Inputs.scala
index 8e93d1463..4d5b44e55 100644
--- a/modules/build/src/main/scala/scala/build/input/Inputs.scala
+++ b/modules/build/src/main/scala/scala/build/input/Inputs.scala
@@ -275,8 +275,8 @@ object Inputs {
         }
         else if path.last == Constants.projectFileName then
           Right(Seq(ProjectScalaFile(dir, subPath)))
+        else if arg.endsWith(".scala") || arg.endsWith(".sc_") then Right(Seq(SourceScalaFile(dir, subPath)))
         else if arg.endsWith(".sc") then Right(Seq(Script(dir, subPath, Some(arg))))
-        else if arg.endsWith(".scala") then Right(Seq(SourceScalaFile(dir, subPath)))
         else if arg.endsWith(".java") then Right(Seq(JavaFile(dir, subPath)))
         else if arg.endsWith(".jar") then Right(Seq(JarFile(dir, subPath)))
         else if arg.endsWith(".c") || arg.endsWith(".h") then Right(Seq(CFile(dir, subPath)))

Experiments were done based on various wrappers around scala-cli, the two most successful described here.

  1. create an on-the-fly symlink in the script parent directory, but with .scala extension, which is deleted-on-exit
  2. wrapper to pipe the script to scala-cli STDIN: (similar to echo 'println("Hello, Scala!")' | scala-cli -)

Approach 1 works well most of the time, but after a crash, symlinks tend to be left behind.

The pipe-to-stdin approach is equivalent to the following conversion:

jsrc/sum.sc 1 2 3 
# the equivalent command line when piped to `scala-cli` <stdin>:
tail -n +2 jsrc/sum.sc | scala-cli run -Dscript.path=jsrc/sum.sc @/opt/ue3cps _.scala -- 1 2 3

It works much of the time, but isn't viable for interactive scripts, or those that read from stdin.
The minimum startup time with this approach is about 10.0 seconds, and if procPipe.sc is compiled, about 8.0 seconds.

For comparison, the equivalent .scala version of the script starts up in less than 4 seconds, and under 1 second after it's been compiled.

Additional context
I work with projects having 1400+ legacy scripts, only about 20% have been migrated to scala 3.6.3.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestlegacy:runner:compatIssues tied to the legacy scala runner compatibilityscriptingIssues tied to *.sc script inputs.

Type

No type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions